Update to current webodf: saves on closing the session/document (needs server support still)
saves only is the user has edited something
This commit is contained in:
parent
abdd34ca50
commit
e74535bdf6
@ -33,9 +33,7 @@ var documentsMain = {
|
||||
return;
|
||||
}
|
||||
|
||||
OC.addScript('documents', 'editor/boot_editor').done(function() {
|
||||
var doclocation = response.es_id;
|
||||
|
||||
require({ }, ["webodf/editor/server/owncloud/ServerFactory", "webodf/editor/Editor"], function (ServerFactory, Editor) {
|
||||
// fade out file list and show WebODF canvas
|
||||
$('.documentslist, #emptyfolder').fadeOut('slow').promise().done(function() {
|
||||
// odf action toolbar
|
||||
@ -67,6 +65,9 @@ var documentsMain = {
|
||||
' </div>' +
|
||||
' </div>' +
|
||||
'</div>';
|
||||
|
||||
var serverFactory = new ServerFactory();
|
||||
|
||||
$(document.body).addClass("claro");
|
||||
$('.documentslist, #emptyfolder').after(canvashtml);
|
||||
// in case we are on the public sharing page we shall display the odf into the preview tag
|
||||
@ -74,21 +75,13 @@ var documentsMain = {
|
||||
|
||||
runtime.assert(response.es_id, "invalid session id.");
|
||||
memberId = response.member_id;
|
||||
webodfEditor.boot(
|
||||
{
|
||||
collaborative: "owncloud",
|
||||
docUrl: doclocation,
|
||||
loginProcedure: function(cb) {
|
||||
cb(response.es_id, OC.currentUser, "token");
|
||||
},
|
||||
joinSession: function(userId, sessionId, cb) {
|
||||
cb(memberId);
|
||||
},
|
||||
callback: function(webodfEditorInstance) {
|
||||
documentsMain.webodfEditorInstance = webodfEditorInstance;
|
||||
}
|
||||
}
|
||||
);
|
||||
documentsMain.webodfServerInstance = serverFactory.createServer();
|
||||
documentsMain.webodfEditorInstance = new Editor({}, documentsMain.webodfServerInstance, serverFactory);
|
||||
|
||||
// load the document and get called back when it's live
|
||||
documentsMain.webodfEditorInstance.loadSession(response.es_id, memberId, function() {
|
||||
documentsMain.webodfEditorInstance.startEditing();
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
@ -155,24 +148,24 @@ var documentsMain = {
|
||||
onClose: function() {
|
||||
"use strict";
|
||||
|
||||
var saveSessionRoute = OC.Router.generate('documents_session_save');
|
||||
//auto save document
|
||||
documentsMain.webodfEditorInstance.saveDocument(saveSessionRoute, function(){});
|
||||
|
||||
//close editor
|
||||
documentsMain.webodfEditorInstance.shutdown(function() {
|
||||
documentsMain.webodfEditorInstance.endEditing();
|
||||
documentsMain.webodfEditorInstance.closeDocument(function() {
|
||||
// successfull shutdown - all is good.
|
||||
// TODO: proper session leaving call to server, either by webodfServerInstance or custom
|
||||
// documentsMain.webodfServerInstance.leaveSession(sessionId, memberId, function() {
|
||||
|
||||
// Fade out odf-toolbar
|
||||
$('#odf-toolbar').fadeOut('slow');
|
||||
// Fade out editor
|
||||
$('#mainContainer').fadeOut('slow', function() {
|
||||
$('#mainContainer').remove();
|
||||
$('#odf-canvas').remove();
|
||||
$('.actions,#file_access_panel').fadeIn('slow');
|
||||
$('.documentslist, #emptyfolder').fadeIn('slow');
|
||||
$(document.body).removeClass('claro');
|
||||
});
|
||||
// Fade out odf-toolbar
|
||||
$('#odf-toolbar').fadeOut('slow');
|
||||
// Fade out editor
|
||||
$('#mainContainer').fadeOut('slow', function() {
|
||||
$('#mainContainer').remove();
|
||||
$('#odf-canvas').remove();
|
||||
$('.actions,#file_access_panel').fadeIn('slow');
|
||||
$('.documentslist, #emptyfolder').fadeIn('slow');
|
||||
$(document.body).removeClass('claro');
|
||||
});
|
||||
// });
|
||||
});
|
||||
},
|
||||
loadDocuments: function () {
|
||||
|
@ -36,28 +36,27 @@
|
||||
define("webodf/editor/Editor", [
|
||||
"dojo/i18n!webodf/editor/nls/myResources",
|
||||
"webodf/editor/EditorSession",
|
||||
"webodf/editor/MemberList",
|
||||
"webodf/editor/MemberListView",
|
||||
"dijit/layout/BorderContainer",
|
||||
"dijit/layout/ContentPane",
|
||||
"webodf/editor/widgets"],
|
||||
|
||||
function (myResources,
|
||||
EditorSession,
|
||||
MemberList,
|
||||
MemberListView,
|
||||
BorderContainer,
|
||||
ContentPane,
|
||||
loadWidgets) {
|
||||
ToolBarTools) {
|
||||
"use strict";
|
||||
|
||||
runtime.loadClass('odf.OdfCanvas');
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {{networked:boolean=,
|
||||
* memberid:!string,
|
||||
* @param {{unstableFeaturesEnabled:boolean=,
|
||||
* loadCallback:function()=,
|
||||
* saveCallback:function()=,
|
||||
* cursorAddedCallback:function(!string)=,
|
||||
* cursorRemovedCallback:function(!string)=,
|
||||
* registerCallbackForShutdown:function(!function())= }} args
|
||||
* closeCallback:function()=,
|
||||
* @param {!ops.Server=} server
|
||||
* @param {!ServerFactory=} serverFactory
|
||||
*/
|
||||
@ -65,26 +64,25 @@ define("webodf/editor/Editor", [
|
||||
|
||||
var self = this,
|
||||
// Private
|
||||
memberid = args.memberid,
|
||||
session,
|
||||
editorSession,
|
||||
memberList,
|
||||
networked = args.networked === true,
|
||||
opRouter,
|
||||
memberModel,
|
||||
memberListView,
|
||||
toolbarTools,
|
||||
loadOdtFile = args.loadCallback,
|
||||
saveOdtFile = args.saveCallback,
|
||||
cursorAddedHandler = args.cursorAddedCallback,
|
||||
cursorRemovedHandler = args.cursorRemovedCallback,
|
||||
registerCallbackForShutdown = args.registerCallbackForShutdown,
|
||||
documentUrl,
|
||||
odfCanvas;
|
||||
close = args.closeCallback,
|
||||
odfCanvas,
|
||||
pendingMemberId,
|
||||
pendingEditorReadyCallback;
|
||||
|
||||
function translator(key, context) {
|
||||
if (undefined === myResources[key]) {
|
||||
return "translation missing: " + key;
|
||||
function getFileBlob(cbSuccess, cbError) {
|
||||
var odfContainer = odfCanvas.odfContainer();
|
||||
|
||||
if (odfContainer) {
|
||||
odfContainer.createByteArray(cbSuccess, cbError);
|
||||
} else {
|
||||
cbError("No odfContainer!");
|
||||
}
|
||||
return myResources[key];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,37 +92,188 @@ define("webodf/editor/Editor", [
|
||||
* which will insert the the cursor.
|
||||
*
|
||||
* @param {!string} initialDocumentUrl
|
||||
* @param {!string} memberId
|
||||
* @param {!function()} editorReadyCallback
|
||||
* @return {undefined}
|
||||
*/
|
||||
function initGuiAndDoc(initialDocumentUrl, editorReadyCallback) {
|
||||
var odfElement, mainContainer,
|
||||
editorPane, memberListPane,
|
||||
inviteButton,
|
||||
viewOptions = {
|
||||
editInfoMarkersInitiallyVisible: networked,
|
||||
caretAvatarsInitiallyVisible: networked,
|
||||
caretBlinksOnRangeSelect: true
|
||||
},
|
||||
memberListDiv = document.getElementById('memberList');
|
||||
function initDocLoading(initialDocumentUrl, memberId, editorReadyCallback) {
|
||||
runtime.assert(initialDocumentUrl, "document should be defined here.");
|
||||
runtime.assert(memberId !== undefined, "memberId should be defined here.");
|
||||
runtime.assert(!pendingEditorReadyCallback, "pendingEditorReadyCallback should not exist here.");
|
||||
runtime.assert(!editorSession, "editorSession should not exist here.");
|
||||
runtime.assert(!session, "session should not exist here.");
|
||||
|
||||
if (networked) {
|
||||
runtime.assert(memberListDiv, "missing memberList div in HTML");
|
||||
pendingMemberId = memberId;
|
||||
pendingEditorReadyCallback = editorReadyCallback;
|
||||
|
||||
odfCanvas.load(initialDocumentUrl);
|
||||
odfCanvas.setEditable(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* create the editor, load the starting document,
|
||||
* call editorReadyCallback once everything is done.
|
||||
*
|
||||
* @param {!string} docUrl
|
||||
* @param {!string} memberId
|
||||
* @param {!function()} editorReadyCallback
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.loadDocument = function (docUrl, memberId, editorReadyCallback) {
|
||||
initDocLoading(docUrl, memberId, editorReadyCallback);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!string} filename
|
||||
* @param {?function()} callback
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.saveDocument = function (filename, callback) {
|
||||
function onsuccess(data) {
|
||||
var mimebase = "application/vnd.oasis.opendocument.",
|
||||
mimetype = mimebase + "text",
|
||||
blob;
|
||||
filename = filename || "doc.odt";
|
||||
if (filename.substr(-4) === ".odp") {
|
||||
mimetype = mimebase + "presentation";
|
||||
} else if (filename.substr(-4) === ".ods") {
|
||||
mimetype = mimebase + "spreadsheet";
|
||||
}
|
||||
blob = new Blob([data.buffer], {type: mimetype});
|
||||
saveAs(blob, filename);
|
||||
}
|
||||
function onerror(error) {
|
||||
alert(error);
|
||||
}
|
||||
|
||||
runtime.loadClass('odf.OdfCanvas');
|
||||
getFileBlob(onsuccess, onerror);
|
||||
};
|
||||
|
||||
// we might need it later
|
||||
documentUrl = initialDocumentUrl;
|
||||
runtime.assert(documentUrl, "document should be defined here.");
|
||||
/**
|
||||
* create the editor, load the starting document of an
|
||||
* editing-session, request a replay of previous operations, call
|
||||
* editorReadyCallback once everything is done.
|
||||
*
|
||||
* @param {!string} sessionId
|
||||
* @param {!string} memberId
|
||||
* @param {!function()} editorReadyCallback
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.loadSession = function (sessionId, memberId, editorReadyCallback) {
|
||||
initDocLoading(server.getGenesisUrl(sessionId), memberId, function () {
|
||||
var opRouter, memberModel;
|
||||
// overwrite router and member model
|
||||
// TODO: serverFactory should be a backendFactory,
|
||||
// and there should be a backendFactory for local editing
|
||||
opRouter = serverFactory.createOperationRouter(sessionId, memberId, server, odfCanvas.odfContainer());
|
||||
session.setOperationRouter(opRouter);
|
||||
|
||||
runtime.assert(memberid !== undefined, "memberid should be defined here.");
|
||||
memberModel = serverFactory.createMemberModel(sessionId, server);
|
||||
session.setMemberModel(memberModel);
|
||||
|
||||
odfElement = document.getElementById("canvas");
|
||||
runtime.assert(odfElement, "initGuiAndDoc failed to get odf canvas from html");
|
||||
odfCanvas = new odf.OdfCanvas(odfElement);
|
||||
// make the canvas accessible to users of editor.js
|
||||
self.odfCanvas = odfCanvas;
|
||||
opRouter.requestReplay(function done() {
|
||||
editorReadyCallback();
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the current editing running editing (polling-timer),
|
||||
* cleanup.
|
||||
* @param {!function(!Object=)} callback, passing an error object in case of error
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.closeDocument = function (callback) {
|
||||
runtime.assert(session, "session should exist here.");
|
||||
if (memberListView) {
|
||||
memberListView.setEditorSession(undefined);
|
||||
}
|
||||
// TODO: there is a better pattern for this instead of unrolling
|
||||
session.getOperationRouter().close(function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
session.getMemberModel().close(function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
editorSession.close(function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
editorSession = undefined;
|
||||
session.close(function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
session = undefined;
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a cursor and enables the tools and allows modifications.
|
||||
* Should be called inside/after editorReadyCallback.
|
||||
* TODO: turn this and endEditing() into readonly switch
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.startEditing = function () {
|
||||
runtime.assert(editorSession, "editorSession should exist here.");
|
||||
|
||||
toolbarTools.setEditorSession(editorSession);
|
||||
editorSession.sessionController.startEditing();
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the cursor and disables the tools and allows modifications.
|
||||
* Should be called before closeDocument, if startEditing was called before
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.endEditing = function () {
|
||||
runtime.assert(editorSession, "editorSession should exist here.");
|
||||
|
||||
toolbarTools.setEditorSession(undefined);
|
||||
editorSession.sessionController.endEditing();
|
||||
};
|
||||
|
||||
// init
|
||||
function init() {
|
||||
var mainContainer,
|
||||
editorPane, memberListPane,
|
||||
inviteButton,
|
||||
canvasElement = document.getElementById("canvas"),
|
||||
memberListElement = document.getElementById('memberList'),
|
||||
collabEditing = Boolean(server),
|
||||
directStylingEnabled = (! collabEditing) || args.unstableFeaturesEnabled,
|
||||
// annotations not yet properly supported for OT
|
||||
annotationsEnabled = (! collabEditing) || args.unstableFeaturesEnabled,
|
||||
// undo manager is not yet integrated with collaboration
|
||||
undoRedoEnabled = (! collabEditing),
|
||||
closeCallback;
|
||||
|
||||
if (collabEditing) {
|
||||
runtime.assert(memberListElement, 'missing "memberList" div in HTML');
|
||||
}
|
||||
|
||||
runtime.assert(canvasElement, 'missing "canvas" div in HTML');
|
||||
|
||||
// setup translations
|
||||
// TODO: move from document instance into webodf namespace
|
||||
function translator(key, context) {
|
||||
if (undefined === myResources[key]) {
|
||||
return "translation missing: " + key;
|
||||
}
|
||||
return myResources[key];
|
||||
}
|
||||
document.translator = translator;
|
||||
|
||||
function translateContent(node) {
|
||||
@ -147,43 +296,6 @@ define("webodf/editor/Editor", [
|
||||
}
|
||||
document.translateContent = translateContent;
|
||||
|
||||
odfCanvas.addListener("statereadychange", function () {
|
||||
if (!editorReadyCallback) {
|
||||
// already called once, restart session and return
|
||||
// undo manager is not yet integrated with collaboration
|
||||
if (! server) {
|
||||
editorSession.sessionController.setUndoManager(new gui.TrivialUndoManager());
|
||||
}
|
||||
editorSession.startEditing();
|
||||
return;
|
||||
}
|
||||
// Allow annotations
|
||||
odfCanvas.enableAnnotations(true);
|
||||
|
||||
session = new ops.Session(odfCanvas);
|
||||
editorSession = new EditorSession(session, memberid, {
|
||||
viewOptions: viewOptions
|
||||
});
|
||||
// undo manager is not yet integrated with collaboration
|
||||
if (! server) {
|
||||
editorSession.sessionController.setUndoManager(new gui.TrivialUndoManager());
|
||||
}
|
||||
|
||||
if (memberListDiv) {
|
||||
memberList = new MemberList(editorSession, memberListDiv);
|
||||
}
|
||||
|
||||
if (registerCallbackForShutdown) {
|
||||
registerCallbackForShutdown(editorSession.endEditing);
|
||||
}
|
||||
|
||||
loadWidgets(editorSession, loadOdtFile, saveOdtFile);
|
||||
editorReadyCallback();
|
||||
editorReadyCallback = null;
|
||||
});
|
||||
odfCanvas.load(initialDocumentUrl);
|
||||
odfCanvas.setEditable(false);
|
||||
|
||||
// App Widgets
|
||||
mainContainer = new BorderContainer({}, 'mainContainer');
|
||||
|
||||
@ -192,12 +304,13 @@ define("webodf/editor/Editor", [
|
||||
}, 'editor');
|
||||
mainContainer.addChild(editorPane);
|
||||
|
||||
if (networked && memberListDiv) {
|
||||
if (collabEditing) {
|
||||
memberListPane = new ContentPane({
|
||||
region: 'right',
|
||||
title: translator("members")
|
||||
}, 'members');
|
||||
mainContainer.addChild(memberListPane);
|
||||
memberListView = new MemberListView(memberListElement);
|
||||
}
|
||||
|
||||
mainContainer.startup();
|
||||
@ -210,107 +323,48 @@ define("webodf/editor/Editor", [
|
||||
inviteButton.onclick = window.inviteButtonProxy.clicked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create the editor, load the starting document,
|
||||
* call editorReadyCallback once everything is done.
|
||||
*
|
||||
* @param {!string} docUrl
|
||||
* @param {!function()} editorReadyCallback
|
||||
* @return {undefined}
|
||||
*/
|
||||
self.initAndLoadDocument = function (docUrl, editorReadyCallback) {
|
||||
initGuiAndDoc(docUrl, function () {
|
||||
editorReadyCallback(editorSession);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Shutdown running editing (polling-timer),
|
||||
* cleanup.
|
||||
*/
|
||||
self.shutdown = function (successfullShutdownCallback) {
|
||||
editorSession.endEditing();
|
||||
opRouter.shutdown(function() {
|
||||
memberModel.shutdown();
|
||||
successfullShutdownCallback();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Load a document in an editor that has already been initialized.
|
||||
*/
|
||||
self.loadDocument = function (docUrl) {
|
||||
self.shutdown();
|
||||
odfCanvas.load(docUrl);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {?function()} callback
|
||||
* @return {undefined}
|
||||
*/
|
||||
self.saveDocument = function (filename, callback) {
|
||||
function onsuccess(data) {
|
||||
var mimebase = "application/vnd.oasis.opendocument.",
|
||||
mimetype = mimebase + "text",
|
||||
blob;
|
||||
filename = filename || "doc.odt";
|
||||
if (filename.substr(-4) === ".odp") {
|
||||
mimetype = mimebase + "presentation";
|
||||
} else if (filename.substr(-4) === ".ods") {
|
||||
mimetype = mimebase + "spreadsheet";
|
||||
}
|
||||
blob = new Blob([data.buffer], {type: mimetype});
|
||||
saveAs(blob, filename);
|
||||
}
|
||||
function onerror(error) {
|
||||
alert(error);
|
||||
}
|
||||
var doc = odfCanvas.odfContainer();
|
||||
doc.createByteArray(onsuccess, onerror);
|
||||
};
|
||||
|
||||
/**
|
||||
* create the editor, load the starting document of an
|
||||
* editing-session, request a replay of previous operations, call
|
||||
* editorReadyCallback once everything is done.
|
||||
*
|
||||
* @param {!string} sessionId
|
||||
* @param {?function()} editorReadyCallback
|
||||
*/
|
||||
self.loadSession = function (sessionId, editorReadyCallback) {
|
||||
initGuiAndDoc(server.getGenesisUrl(sessionId), function () {
|
||||
// get router and member model
|
||||
opRouter = opRouter || serverFactory.createOperationRouter(sessionId, memberid, server);
|
||||
session.setOperationRouter(opRouter);
|
||||
|
||||
memberModel = memberModel || serverFactory.createMemberModel(sessionId, server);
|
||||
session.setMemberModel(memberModel);
|
||||
|
||||
opRouter.requestReplay(function done() {
|
||||
var odtDocument = session.getOdtDocument();
|
||||
if (cursorAddedHandler) {
|
||||
odtDocument.subscribe(ops.OdtDocument.signalCursorAdded, function (cursor) {
|
||||
cursorAddedHandler(cursor.getMemberId());
|
||||
});
|
||||
}
|
||||
if (cursorRemovedHandler) {
|
||||
odtDocument.subscribe(ops.OdtDocument.signalCursorRemoved, function (memberId) {
|
||||
cursorRemovedHandler(memberId);
|
||||
});
|
||||
}
|
||||
|
||||
editorReadyCallback(editorSession);
|
||||
toolbarTools = new ToolBarTools({
|
||||
loadOdtFile: loadOdtFile,
|
||||
saveOdtFile: saveOdtFile,
|
||||
close: close,
|
||||
directStylingEnabled: directStylingEnabled,
|
||||
annotationsEnabled: annotationsEnabled,
|
||||
undoRedoEnabled: undoRedoEnabled
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
odfCanvas = new odf.OdfCanvas(canvasElement);
|
||||
odfCanvas.enableAnnotations(annotationsEnabled);
|
||||
|
||||
// access to member model
|
||||
self.getMemberModel = function () {
|
||||
return memberModel;
|
||||
};
|
||||
odfCanvas.addListener("statereadychange", function () {
|
||||
var viewOptions = {
|
||||
editInfoMarkersInitiallyVisible: collabEditing,
|
||||
caretAvatarsInitiallyVisible: collabEditing,
|
||||
caretBlinksOnRangeSelect: true
|
||||
};
|
||||
|
||||
// create session around loaded document
|
||||
session = new ops.Session(odfCanvas);
|
||||
editorSession = new EditorSession(session, pendingMemberId, {
|
||||
viewOptions: viewOptions
|
||||
});
|
||||
if (undoRedoEnabled) {
|
||||
editorSession.sessionController.setUndoManager(new gui.TrivialUndoManager());
|
||||
}
|
||||
|
||||
if (memberListView) {
|
||||
memberListView.setEditorSession(editorSession);
|
||||
}
|
||||
|
||||
// and report back to caller
|
||||
pendingEditorReadyCallback();
|
||||
// reset
|
||||
pendingEditorReadyCallback = null;
|
||||
pendingMemberId = null;
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
return Editor;
|
||||
});
|
||||
|
@ -56,11 +56,11 @@ define("webodf/editor/EditorSession", [
|
||||
/**
|
||||
* Instantiate a new editor session attached to an existing operation session
|
||||
* @param {!ops.Session} session
|
||||
* @param {!string} memberid
|
||||
* @param {!string} localMemberId
|
||||
* @param {{viewOptions:gui.SessionViewOptions}} config
|
||||
* @constructor
|
||||
*/
|
||||
var EditorSession = function EditorSession(session, memberid, config) {
|
||||
var EditorSession = function EditorSession(session, localMemberId, config) {
|
||||
var self = this,
|
||||
currentParagraphNode = null,
|
||||
currentNamedStyleName = null,
|
||||
@ -80,7 +80,7 @@ define("webodf/editor/EditorSession", [
|
||||
EditorSession.signalUndoStackChanged]);
|
||||
|
||||
|
||||
this.sessionController = new gui.SessionController(session, memberid);
|
||||
this.sessionController = new gui.SessionController(session, localMemberId);
|
||||
this.sessionView = new gui.SessionView(config.viewOptions, session, new gui.CaretManager(self.sessionController));
|
||||
this.availableFonts = [];
|
||||
|
||||
@ -160,11 +160,11 @@ define("webodf/editor/EditorSession", [
|
||||
function uniqueParagraphStyleNCName(name) {
|
||||
var result,
|
||||
i = 0,
|
||||
ncMemberId = createNCName(memberid),
|
||||
ncMemberId = createNCName(localMemberId),
|
||||
ncName = createNCName(name);
|
||||
|
||||
// create default paragraph style
|
||||
// memberid is used to avoid id conflicts with ids created by other members
|
||||
// localMemberId is used to avoid id conflicts with ids created by other members
|
||||
result = ncName + "_" + ncMemberId;
|
||||
// then loop until result is really unique
|
||||
while (formatting.hasParagraphStyle(result)) {
|
||||
@ -206,7 +206,7 @@ define("webodf/editor/EditorSession", [
|
||||
|
||||
odtDocument.subscribe(ops.OdtDocument.signalCursorMoved, function (cursor) {
|
||||
// Emit 'cursorMoved' only when *I* am moving the cursor, not the other users
|
||||
if (cursor.getMemberId() === memberid) {
|
||||
if (cursor.getMemberId() === localMemberId) {
|
||||
self.emit(EditorSession.signalCursorMoved, cursor);
|
||||
}
|
||||
});
|
||||
@ -225,14 +225,6 @@ define("webodf/editor/EditorSession", [
|
||||
|
||||
odtDocument.subscribe(ops.OdtDocument.signalParagraphChanged, trackCurrentParagraph);
|
||||
|
||||
this.startEditing = function () {
|
||||
self.sessionController.startEditing();
|
||||
};
|
||||
|
||||
this.endEditing = function () {
|
||||
self.sessionController.endEditing();
|
||||
};
|
||||
|
||||
/**
|
||||
* Call all subscribers for the given event with the specified argument
|
||||
* @param {!string} eventid
|
||||
@ -251,6 +243,15 @@ define("webodf/editor/EditorSession", [
|
||||
eventNotifier.subscribe(eventid, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!string} eventid
|
||||
* @param {!Function} cb
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.unsubscribe = function (eventid, cb) {
|
||||
eventNotifier.unsubscribe(eventid, cb);
|
||||
};
|
||||
|
||||
this.getMemberDetailsAndUpdates = function (memberId, subscriber) {
|
||||
return session.getMemberModel().getMemberDetailsAndUpdates(memberId, subscriber);
|
||||
};
|
||||
@ -260,11 +261,11 @@ define("webodf/editor/EditorSession", [
|
||||
};
|
||||
|
||||
this.getCursorPosition = function () {
|
||||
return odtDocument.getCursorPosition(memberid);
|
||||
return odtDocument.getCursorPosition(localMemberId);
|
||||
};
|
||||
|
||||
this.getCursorSelection = function () {
|
||||
return odtDocument.getCursorSelection(memberid);
|
||||
return odtDocument.getCursorSelection(localMemberId);
|
||||
};
|
||||
|
||||
this.getOdfCanvas = function () {
|
||||
@ -280,7 +281,7 @@ define("webodf/editor/EditorSession", [
|
||||
};
|
||||
|
||||
this.isBold = function () {
|
||||
var cursor = odtDocument.getCursor(memberid);
|
||||
var cursor = odtDocument.getCursor(localMemberId);
|
||||
// no own cursor yet/currently added?
|
||||
if (!cursor) {
|
||||
return false;
|
||||
@ -289,7 +290,7 @@ define("webodf/editor/EditorSession", [
|
||||
};
|
||||
|
||||
this.isItalic = function () {
|
||||
var cursor = odtDocument.getCursor(memberid);
|
||||
var cursor = odtDocument.getCursor(localMemberId);
|
||||
// no own cursor yet/currently added?
|
||||
if (!cursor) {
|
||||
return false;
|
||||
@ -298,7 +299,7 @@ define("webodf/editor/EditorSession", [
|
||||
};
|
||||
|
||||
this.hasUnderline = function () {
|
||||
var cursor = odtDocument.getCursor(memberid);
|
||||
var cursor = odtDocument.getCursor(localMemberId);
|
||||
// no own cursor yet/currently added?
|
||||
if (!cursor) {
|
||||
return false;
|
||||
@ -307,7 +308,7 @@ define("webodf/editor/EditorSession", [
|
||||
};
|
||||
|
||||
this.hasStrikeThrough = function () {
|
||||
var cursor = odtDocument.getCursor(memberid);
|
||||
var cursor = odtDocument.getCursor(localMemberId);
|
||||
// no own cursor yet/currently added?
|
||||
if (!cursor) {
|
||||
return false;
|
||||
@ -323,7 +324,7 @@ define("webodf/editor/EditorSession", [
|
||||
var op = new ops.OpApplyDirectStyling(),
|
||||
selection = self.getCursorSelection();
|
||||
op.init({
|
||||
memberid: memberid,
|
||||
memberId: localMemberId,
|
||||
position: selection.position,
|
||||
length: selection.length,
|
||||
setProperties: value
|
||||
@ -345,10 +346,10 @@ define("webodf/editor/EditorSession", [
|
||||
length = Math.abs(length);
|
||||
|
||||
op.init({
|
||||
memberid: memberid,
|
||||
memberId: localMemberId,
|
||||
position: position,
|
||||
length: length,
|
||||
name: memberid + Date.now()
|
||||
name: localMemberId + Date.now()
|
||||
});
|
||||
session.enqueue(op);
|
||||
};
|
||||
@ -358,7 +359,7 @@ define("webodf/editor/EditorSession", [
|
||||
if (currentNamedStyleName !== value) {
|
||||
op = new ops.OpSetParagraphStyle();
|
||||
op.init({
|
||||
memberid: memberid,
|
||||
memberId: localMemberId,
|
||||
position: self.getCursorPosition(),
|
||||
styleName: value
|
||||
});
|
||||
@ -369,7 +370,7 @@ define("webodf/editor/EditorSession", [
|
||||
this.insertTable = function (initialRows, initialColumns, tableStyleName, tableColumnStyleName, tableCellStyleMatrix) {
|
||||
var op = new ops.OpInsertTable();
|
||||
op.init({
|
||||
memberid: memberid,
|
||||
memberId: localMemberId,
|
||||
position: self.getCursorPosition(),
|
||||
initialRows: initialRows,
|
||||
initialColumns: initialColumns,
|
||||
@ -409,7 +410,7 @@ define("webodf/editor/EditorSession", [
|
||||
var op;
|
||||
op = new ops.OpUpdateParagraphStyle();
|
||||
op.init({
|
||||
memberid: memberid,
|
||||
memberId: localMemberId,
|
||||
styleName: styleName,
|
||||
setProperties: setProperties,
|
||||
removedProperties: (!removedProperties) ? {} : removedProperties
|
||||
@ -447,7 +448,7 @@ define("webodf/editor/EditorSession", [
|
||||
|
||||
op = new ops.OpAddParagraphStyle();
|
||||
op.init({
|
||||
memberid: memberid,
|
||||
memberId: localMemberId,
|
||||
styleName: newStyleName,
|
||||
setProperties: setProperties
|
||||
});
|
||||
@ -460,7 +461,7 @@ define("webodf/editor/EditorSession", [
|
||||
var op;
|
||||
op = new ops.OpRemoveParagraphStyle();
|
||||
op.init({
|
||||
memberid: memberid,
|
||||
memberId: localMemberId,
|
||||
styleName: styleName
|
||||
});
|
||||
session.enqueue(op);
|
||||
@ -528,6 +529,28 @@ define("webodf/editor/EditorSession", [
|
||||
|
||||
this.subscribe(EditorSession.signalCursorMoved, trackCursor);
|
||||
|
||||
/**
|
||||
* @param {!function(!Object=)} callback, passing an error object in case of error
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.close = function(callback) {
|
||||
self.sessionController.close(function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
delete self.sessionController;
|
||||
self.sessionView.close(function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
delete self.sessionView;
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function init() {
|
||||
var head = document.getElementsByTagName('head')[0],
|
||||
fontStyles = document.createElement('style');
|
||||
@ -548,7 +571,7 @@ define("webodf/editor/EditorSession", [
|
||||
/**@const*/EditorSession.signalStyleCreated = "styleCreated";
|
||||
/**@const*/EditorSession.signalStyleDeleted = "styleDeleted";
|
||||
/**@const*/EditorSession.signalParagraphStyleModified = "paragraphStyleModified";
|
||||
/**@const*/EditorSession.signalUndoStackChanged = "signalUndoStackChanged";
|
||||
/**@const*/EditorSession.signalUndoStackChanged = "signalUndoStackChanged";
|
||||
|
||||
return EditorSession;
|
||||
});
|
||||
|
@ -32,27 +32,27 @@
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global define,runtime */
|
||||
|
||||
define("webodf/editor/MemberList",
|
||||
define("webodf/editor/MemberListView",
|
||||
["webodf/editor/EditorSession"],
|
||||
|
||||
function (EditorSession) {
|
||||
"use strict";
|
||||
|
||||
return function MemberList(editorSession, memberListDiv) {
|
||||
var self = this;
|
||||
/**
|
||||
* @param {!Element} memberListDiv
|
||||
* @constructor
|
||||
*/
|
||||
return function MemberListView(memberListDiv) {
|
||||
var editorSession = null;
|
||||
|
||||
editorSession.subscribe(EditorSession.signalMemberAdded, function (memberId) {
|
||||
self.addMember(memberId);
|
||||
});
|
||||
|
||||
editorSession.subscribe(EditorSession.signalMemberRemoved, function (memberId) {
|
||||
self.removeMember(memberId);
|
||||
});
|
||||
runtime.assert(memberListDiv, "memberListDiv unavailable");
|
||||
|
||||
/**
|
||||
* @param {!string} memberId
|
||||
* @return {undefined}
|
||||
*/
|
||||
function updateAvatarButton(memberId, memberDetails) {
|
||||
var node = memberListDiv.firstChild;
|
||||
@ -86,9 +86,9 @@ define("webodf/editor/MemberList",
|
||||
|
||||
/**
|
||||
* @param {!string} memberId
|
||||
* @return {undefined}
|
||||
*/
|
||||
function createAvatarButton(memberId) {
|
||||
runtime.assert(memberListDiv, "memberListDiv unavailable");
|
||||
var doc = memberListDiv.ownerDocument,
|
||||
htmlns = doc.documentElement.namespaceURI,
|
||||
avatarDiv = doc.createElementNS(htmlns, "div"),
|
||||
@ -123,6 +123,7 @@ define("webodf/editor/MemberList",
|
||||
|
||||
/**
|
||||
* @param {!string} memberId
|
||||
* @return {undefined}
|
||||
*/
|
||||
function removeAvatarButton(memberId) {
|
||||
var node = memberListDiv.firstChild;
|
||||
@ -137,18 +138,48 @@ define("webodf/editor/MemberList",
|
||||
|
||||
/**
|
||||
* @param {!string} memberId
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.addMember = function (memberId) {
|
||||
function addMember(memberId) {
|
||||
createAvatarButton(memberId);
|
||||
editorSession.getMemberDetailsAndUpdates(memberId, updateAvatarButton);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!string} memberId
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.removeMember = function (memberId) {
|
||||
function removeMember(memberId) {
|
||||
editorSession.unsubscribeMemberDetailsUpdates(memberId, updateAvatarButton);
|
||||
removeAvatarButton(memberId);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!EditorSession} session
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.setEditorSession = function(session) {
|
||||
var node = memberListDiv.firstChild, nextNode;
|
||||
|
||||
if (editorSession) {
|
||||
// remove all current avatars
|
||||
while (node) {
|
||||
nextNode = node.nextSibling;
|
||||
if (node.memberId) {
|
||||
editorSession.unsubscribeMemberDetailsUpdates(node.memberId, updateAvatarButton);
|
||||
}
|
||||
memberListDiv.removeChild(node);
|
||||
node = nextNode;
|
||||
}
|
||||
// unsubscribe from old editorSession
|
||||
editorSession.unsubscribe(EditorSession.signalMemberAdded, addMember);
|
||||
editorSession.unsubscribe(EditorSession.signalMemberRemoved, removeMember);
|
||||
}
|
||||
editorSession = session;
|
||||
if (editorSession) {
|
||||
editorSession.subscribe(EditorSession.signalMemberAdded, addMember);
|
||||
editorSession.subscribe(EditorSession.signalMemberRemoved, removeMember);
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
65
js/editor/SessionList.js
Normal file
65
js/editor/SessionList.js
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* (GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. The code is distributed
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
||||
*
|
||||
* As additional permission under GNU AGPL version 3 section 7, you
|
||||
* may distribute non-source (e.g., minimized or compacted) forms of
|
||||
* that code without the copy of the GNU GPL normally required by
|
||||
* section 4, provided you include this license notice and a URL
|
||||
* through which recipients can access the Corresponding Source.
|
||||
*
|
||||
* As a special exception to the AGPL, any HTML file which merely makes function
|
||||
* calls to this code, and for that purpose includes it by reference shall be
|
||||
* deemed a separate work for copyright law purposes. In addition, the copyright
|
||||
* holders of this code give you permission to combine this code with free
|
||||
* software libraries that are released under the GNU LGPL. You may copy and
|
||||
* distribute such a system following the terms of the GNU AGPL for this code
|
||||
* and the LGPL for the libraries. If you modify this code, you may extend this
|
||||
* exception to your version of the code, but you are not obligated to do so.
|
||||
* If you do not wish to do so, delete this exception statement from your
|
||||
* version.
|
||||
*
|
||||
* This license applies to this entire compilation.
|
||||
* @licend
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global ops, runtime */
|
||||
|
||||
/**
|
||||
* A model which provides information about sessions.
|
||||
* @interface
|
||||
*/
|
||||
SessionList = function SessionList() {"use strict"; };
|
||||
|
||||
/**
|
||||
* @param {{onCreated:function(!Object),
|
||||
* onUpdated:function(!Object),
|
||||
* onRemoved:function(!string) }} subscriber
|
||||
* @return {undefined}
|
||||
*/
|
||||
SessionList.prototype.getSessions = function (subscriber) {"use strict"; };
|
||||
|
||||
/**
|
||||
* @param {{onCreated:function(!Object),
|
||||
* onUpdated:function(!Object),
|
||||
* onRemoved:function(!string) }} subscriber
|
||||
* @return {undefined}
|
||||
*/
|
||||
SessionList.prototype.unsubscribe = function (subscriber) {"use strict"; };
|
||||
|
||||
/**
|
||||
* Per default updates are enabled.
|
||||
* @param {!boolean} enabled
|
||||
* @return {undefined}
|
||||
*/
|
||||
SessionList.prototype.setUpdatesEnabled = function (enabled) {"use strict"; };
|
@ -59,9 +59,6 @@ define("webodf/editor/SessionListView", [], function () {
|
||||
sessionDiv.sessionId = sessionDetails.id; // TODO: namespace?
|
||||
sessionDiv.style.cursor = "pointer"; // TODO: do not set on each element, use CSS
|
||||
sessionDiv.onclick = function () {
|
||||
// HACK: stop pulling, so that does not mess up the logs
|
||||
// Remove before merging to master
|
||||
if (sessionList.stopPulling) { sessionList.stopPulling(); }
|
||||
cb(sessionDetails.id);
|
||||
};
|
||||
|
||||
|
@ -1,462 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* (GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. The code is distributed
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
||||
*
|
||||
* As additional permission under GNU AGPL version 3 section 7, you
|
||||
* may distribute non-source (e.g., minimized or compacted) forms of
|
||||
* that code without the copy of the GNU GPL normally required by
|
||||
* section 4, provided you include this license notice and a URL
|
||||
* through which recipients can access the Corresponding Source.
|
||||
*
|
||||
* As a special exception to the AGPL, any HTML file which merely makes function
|
||||
* calls to this code, and for that purpose includes it by reference shall be
|
||||
* deemed a separate work for copyright law purposes. In addition, the copyright
|
||||
* holders of this code give you permission to combine this code with free
|
||||
* software libraries that are released under the GNU LGPL. You may copy and
|
||||
* distribute such a system following the terms of the GNU AGPL for this code
|
||||
* and the LGPL for the libraries. If you modify this code, you may extend this
|
||||
* exception to your version of the code, but you are not obligated to do so.
|
||||
* If you do not wish to do so, delete this exception statement from your
|
||||
* version.
|
||||
*
|
||||
* This license applies to this entire compilation.
|
||||
* @licend
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*
|
||||
* bootstrap the editor in different ways.
|
||||
* this file is meant to be included from HTML and used
|
||||
* by users who do not want to know much about the inner
|
||||
* complexity.
|
||||
* so we need to make it really easy.
|
||||
*
|
||||
* including this file will result in the namespace/object
|
||||
* "webodfEditor" to be available from the HTML side.
|
||||
* calling webodfEditor.boot() will start the editor.
|
||||
* the method can also take some parameters to specify
|
||||
* behaviour. see documentation of that method.
|
||||
*
|
||||
*/
|
||||
|
||||
/*global runtime, require, document, alert, net, window, SessionListView, ops */
|
||||
|
||||
// define the namespace/object we want to provide
|
||||
// this is the first line of API, the user gets.
|
||||
var webodfEditor = (function () {
|
||||
"use strict";
|
||||
|
||||
runtime.currentDirectory = function () {
|
||||
return "../../webodf/lib";
|
||||
};
|
||||
runtime.libraryPaths = function () {
|
||||
return [ runtime.currentDirectory() ];
|
||||
};
|
||||
|
||||
var editorInstance = null,
|
||||
serverFactory = null,
|
||||
server = null,
|
||||
booting = false,
|
||||
loadedFilename;
|
||||
|
||||
/**
|
||||
* wait for a network connection through nowjs to establish.
|
||||
* call the callback when done, when finally failed, or
|
||||
* when a timeout reached.
|
||||
* the parameter to the callback is a string with the possible
|
||||
* values:
|
||||
* "unavailable", "timeout", "ready"
|
||||
*
|
||||
* @param {!function(!string)} callback
|
||||
* @return {undefined}
|
||||
*/
|
||||
function connectNetwork(backend, callback) {
|
||||
function createServer(ServerFactory) {
|
||||
serverFactory = new ServerFactory();
|
||||
server = serverFactory.createServer();
|
||||
server.connect(8000, callback);
|
||||
}
|
||||
|
||||
switch (backend) {
|
||||
case "pullbox":
|
||||
require({ }, ["webodf/editor/server/pullbox/serverFactory"], createServer);
|
||||
break;
|
||||
case "nowjs":
|
||||
require({ }, ["webodf/editor/server/nowjs/serverFactory"], createServer);
|
||||
break;
|
||||
case "owncloud":
|
||||
require({ }, ["webodf/editor/server/pullbox/serverFactory"], function (ServerFactory) {
|
||||
serverFactory = new ServerFactory();
|
||||
server = serverFactory.createServer({url: "./documents/ajax/otpoll.php"});
|
||||
server.getGenesisUrl = function(sid) {
|
||||
// what a dirty hack :)
|
||||
return OC.Router.generate('documents_genesis')+'/' +sid;
|
||||
};
|
||||
server.connect(8000, callback);
|
||||
});
|
||||
default:
|
||||
callback("unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* try to auto-sense the server-backend.
|
||||
*
|
||||
* NOT IMPLEMENTED / MIGHT BE NICE TO HAVE...
|
||||
* for now: try to connect to pullbox backend
|
||||
*
|
||||
* @param {!function(!string)} callback
|
||||
* @return {undefined}
|
||||
*/
|
||||
function detectNetwork(callback) {
|
||||
connectNetwork("pullbox", callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* extract document url from the url-fragment
|
||||
*
|
||||
* @return {?string}
|
||||
*/
|
||||
function guessDocUrl() {
|
||||
var pos, docUrl = String(document.location);
|
||||
// If the URL has a fragment (#...), try to load the file it represents
|
||||
pos = docUrl.indexOf('#');
|
||||
if (pos !== -1) {
|
||||
docUrl = docUrl.substr(pos + 1);
|
||||
} else {
|
||||
docUrl = "welcome.odt";
|
||||
}
|
||||
return docUrl || null;
|
||||
}
|
||||
|
||||
function fileSelectHandler(evt) {
|
||||
var file, files, reader;
|
||||
files = (evt.target && evt.target.files) ||
|
||||
(evt.dataTransfer && evt.dataTransfer.files);
|
||||
function onloadend() {
|
||||
if (reader.readyState === 2) {
|
||||
runtime.registerFile(file.name, reader.result);
|
||||
loadedFilename = file.name;
|
||||
editorInstance.loadDocument(file.name);
|
||||
}
|
||||
}
|
||||
if (files && files.length === 1) {
|
||||
file = files[0];
|
||||
reader = new FileReader();
|
||||
reader.onloadend = onloadend;
|
||||
reader.readAsArrayBuffer(file);
|
||||
} else {
|
||||
alert("File could not be opened in this browser.");
|
||||
}
|
||||
}
|
||||
|
||||
function enhanceRuntime() {
|
||||
var openedFiles = {},
|
||||
read = runtime.read,
|
||||
getFileSize = runtime.getFileSize;
|
||||
runtime.read = function (path, offset, length, callback) {
|
||||
var array;
|
||||
if (openedFiles.hasOwnProperty(path)) {
|
||||
array = new Uint8Array(openedFiles[path], offset, length);
|
||||
callback(undefined, array);
|
||||
} else {
|
||||
return read(path, offset, length, callback);
|
||||
}
|
||||
};
|
||||
runtime.getFileSize = function (path, callback) {
|
||||
if (openedFiles.hasOwnProperty(path)) {
|
||||
return callback(openedFiles[path].byteLength);
|
||||
} else {
|
||||
return getFileSize(path, callback);
|
||||
}
|
||||
};
|
||||
runtime.registerFile = function (path, data) {
|
||||
openedFiles[path] = data;
|
||||
};
|
||||
}
|
||||
|
||||
function createFileLoadForm() {
|
||||
var form = document.createElement("form"),
|
||||
input = document.createElement("input");
|
||||
form.appendChild(input);
|
||||
form.style.display = "none";
|
||||
input.id = "fileloader";
|
||||
input.setAttribute("type", "file");
|
||||
input.addEventListener("change", fileSelectHandler, false);
|
||||
document.body.appendChild(form);
|
||||
}
|
||||
|
||||
function load() {
|
||||
var form = document.getElementById("fileloader");
|
||||
if (!form) {
|
||||
enhanceRuntime();
|
||||
createFileLoadForm();
|
||||
form = document.getElementById("fileloader");
|
||||
}
|
||||
form.click();
|
||||
}
|
||||
|
||||
function save() {
|
||||
editorInstance.saveDocument(loadedFilename);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a new editor instance, and start the editor with
|
||||
* the given document.
|
||||
*
|
||||
* @param {!string} docUrl
|
||||
* @param {?Object} editorOptions
|
||||
* @param {?function(!Object)} editorReadyCallback
|
||||
*/
|
||||
function createLocalEditor(docUrl, editorOptions, editorReadyCallback) {
|
||||
var pos;
|
||||
booting = true;
|
||||
editorOptions = editorOptions || {};
|
||||
editorOptions.memberid = "localuser";
|
||||
editorOptions.loadCallback = load;
|
||||
editorOptions.saveCallback = save;
|
||||
|
||||
if (docUrl === undefined) {
|
||||
docUrl = guessDocUrl();
|
||||
}
|
||||
runtime.assert(docUrl, "docUrl needs to be specified");
|
||||
runtime.assert(editorInstance === null, "cannot boot with instanciated editor");
|
||||
|
||||
document.getElementById("mainContainer").style.display = "";
|
||||
|
||||
require({ }, ["webodf/editor/Editor"],
|
||||
function (Editor) {
|
||||
editorInstance = new Editor(editorOptions);
|
||||
editorInstance.initAndLoadDocument(docUrl, function (editorSession) {
|
||||
editorSession.startEditing();
|
||||
editorReadyCallback(editorInstance);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* assume the network connection is established, create a new editor instance,
|
||||
* and start the editor on the network.
|
||||
*
|
||||
* @param {!string} sessionId
|
||||
* @param {!string} memberId
|
||||
* @param {?string} token
|
||||
* @param {?Object} editorOptions
|
||||
* @param {?function(!Object)} editorReadyCallback
|
||||
*/
|
||||
function createNetworkedEditor(sessionId, memberId, token, editorOptions, editorReadyCallback) {
|
||||
|
||||
runtime.assert(sessionId, "sessionId needs to be specified");
|
||||
runtime.assert(memberId, "memberId needs to be specified");
|
||||
runtime.assert(editorInstance === null, "cannot boot with instanciated editor");
|
||||
|
||||
editorOptions = editorOptions || {};
|
||||
editorOptions.memberid = memberId;
|
||||
editorOptions.networked = true;
|
||||
editorOptions.networkSecurityToken = token;
|
||||
|
||||
require({ }, ["webodf/editor/Editor"],
|
||||
function (Editor) {
|
||||
// TODO: the networkSecurityToken needs to be retrieved via now.login
|
||||
// (but this is to be implemented later)
|
||||
editorInstance = new Editor(editorOptions, server, serverFactory);
|
||||
|
||||
// load the document and get called back when it's live
|
||||
editorInstance.loadSession(sessionId, function (editorSession) {
|
||||
editorSession.startEditing();
|
||||
editorReadyCallback(editorInstance);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* start the login process by offering a login/password prompt.
|
||||
* the login is validated via nowjs namespace.
|
||||
* on success a list of sessions is offered.
|
||||
* when the user selects a session the callback is called
|
||||
* with the sessionId as parameter
|
||||
*
|
||||
* @param {!function(!string, !string, ?string)} callback
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function startLoginProcess(callback) {
|
||||
var userid, token;
|
||||
|
||||
booting = true;
|
||||
|
||||
runtime.assert(editorInstance === null, "cannot boot with instanciated editor");
|
||||
|
||||
function enterSession(selectedSessionId) {
|
||||
document.getElementById("sessionListContainer").style.display = "none";
|
||||
document.getElementById("mainContainer").style.display = "";
|
||||
|
||||
callback(selectedSessionId, userid, token);
|
||||
}
|
||||
|
||||
function showSessions() {
|
||||
require({ }, ["webodf/editor/SessionListView"],
|
||||
function (SessionListView) {
|
||||
var sessionListDiv = document.getElementById("sessionList"),
|
||||
sessionList = new serverFactory.createSessionList(server),
|
||||
sessionListView = new SessionListView(sessionList, sessionListDiv, enterSession);
|
||||
|
||||
// hide login view
|
||||
document.getElementById("loginContainer").style.display = "none";
|
||||
|
||||
// show session list
|
||||
document.getElementById("sessionListContainer").style.display = "";
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function loginSuccess(userData) {
|
||||
runtime.log("connected:" + userData.full_name);
|
||||
userid = userData.uid;
|
||||
token = userData.securityToken || null;
|
||||
|
||||
showSessions();
|
||||
}
|
||||
|
||||
function loginFail(result) {
|
||||
alert("Login failed: " + result);
|
||||
}
|
||||
|
||||
function onLoginSubmit() {
|
||||
server.login(document.loginForm.login.value, document.loginForm.password.value, loginSuccess, loginFail);
|
||||
|
||||
// block the submit button, we already dealt with the input
|
||||
return false;
|
||||
}
|
||||
|
||||
// bring up the login form
|
||||
document.loginForm.Submit.onclick = onLoginSubmit;
|
||||
document.getElementById("loginContainer").style.display = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* make a guess about the document (# in URL)
|
||||
* also guess about local/collaborative (depending on nowjs)
|
||||
*
|
||||
* @param {?Object} args
|
||||
*
|
||||
* args:
|
||||
*
|
||||
* collaborative: if set to true: connect to the network and start a
|
||||
* collaborative editor. in that case the document url
|
||||
* is ignored. and user needs to select a session.
|
||||
*
|
||||
* if set to the string "auto": it will try the above
|
||||
* but fall back to non-collaborative mode [default]
|
||||
*
|
||||
* docUrl: if given it is used as the url to the document to load
|
||||
*
|
||||
* callback: callback to be called as soon as the document is loaded
|
||||
*
|
||||
*/
|
||||
function boot(args) {
|
||||
var editorOptions = {},
|
||||
loginProcedure = startLoginProcess;
|
||||
runtime.assert(!booting, "editor creation already in progress");
|
||||
|
||||
args = args || {};
|
||||
|
||||
if (args.collaborative === undefined) {
|
||||
args.collaborative = "standalone";
|
||||
} else {
|
||||
args.collaborative = String(args.collaborative).toLowerCase();
|
||||
}
|
||||
|
||||
if (args.saveCallback) {
|
||||
editorOptions.saveCallback = args.saveCallback;
|
||||
}
|
||||
if (args.cursorAddedCallback) {
|
||||
editorOptions.cursorAddedCallback = args.cursorAddedCallback;
|
||||
}
|
||||
if (args.cursorRemovedCallback) {
|
||||
editorOptions.cursorRemovedCallback = args.cursorRemovedCallback;
|
||||
}
|
||||
if (args.registerCallbackForShutdown) {
|
||||
editorOptions.registerCallbackForShutdown = args.registerCallbackForShutdown;
|
||||
} else {
|
||||
editorOptions.registerCallbackForShutdown = function (callback) {
|
||||
window.onunload = callback;
|
||||
};
|
||||
}
|
||||
|
||||
if (args.loginProcedure) {
|
||||
loginProcedure = args.loginProcedure;
|
||||
}
|
||||
|
||||
// start the editor with network
|
||||
function handleNetworkedSituation() {
|
||||
var joinSession = server.joinSession;
|
||||
|
||||
if (args.joinSession) {
|
||||
joinSession = args.joinSession;
|
||||
}
|
||||
|
||||
loginProcedure(function (sessionId, userId, token) {
|
||||
// if pre-authentication has happened:
|
||||
if (token) {
|
||||
server.setToken(token);
|
||||
}
|
||||
|
||||
joinSession(userId, sessionId, function(memberId) {
|
||||
createNetworkedEditor(sessionId, memberId, token, editorOptions, function (ed) {
|
||||
if (args.callback) {
|
||||
args.callback(ed);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// start the editor without network
|
||||
function handleNonNetworkedSituation() {
|
||||
createLocalEditor(args.docUrl, editorOptions, function (editor) {
|
||||
if (args.callback) {
|
||||
args.callback(editor);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (args.collaborative === "auto") {
|
||||
runtime.log("detecting network...");
|
||||
detectNetwork(function (state) {
|
||||
if (state === "ready") {
|
||||
runtime.log("... network available.");
|
||||
handleNetworkedSituation();
|
||||
} else {
|
||||
runtime.log("... no network available (" + state + ").");
|
||||
handleNonNetworkedSituation();
|
||||
}
|
||||
});
|
||||
} else if ((args.collaborative === "pullbox") ||
|
||||
(args.collaborative === "owncloud")) {
|
||||
runtime.log("starting collaborative editor for ["+args.collaborative+"].");
|
||||
connectNetwork(args.collaborative, function (state) {
|
||||
if (state === "ready") {
|
||||
handleNetworkedSituation();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
runtime.log("starting local editor.");
|
||||
handleNonNetworkedSituation();
|
||||
}
|
||||
}
|
||||
|
||||
// exposed API
|
||||
return { boot: boot };
|
||||
}());
|
||||
|
@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Copyright (C) 2012 KO GmbH <copyright@kogmbh.com>
|
||||
* @license
|
||||
* Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
@ -31,14 +32,21 @@
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
define({
|
||||
// menus
|
||||
file: "Datei",
|
||||
open: "Öffnen",
|
||||
save: "Speichern",
|
||||
edit: "Bearbeiten",
|
||||
view: "Ansicht",
|
||||
annotate: "Kommentieren",
|
||||
clone: "Kopiere",
|
||||
create: "Erzeuge",
|
||||
delete: "Entferne",
|
||||
insert: "Einfügen",
|
||||
format: "Formatieren",
|
||||
close: "Schließe",
|
||||
character_DDD: "Zeichen...",
|
||||
paragraph_DDD: "Absatz...",
|
||||
// dialogs
|
||||
@ -50,13 +58,12 @@ define({
|
||||
textFlow: "Textfluß",
|
||||
character: "Zeichen",
|
||||
paragraphStyles: "Absatzstile",
|
||||
cloneThisStyle: "Kopiere diesen Stil",
|
||||
newName_C: "Neuer Name:",
|
||||
// Collaboration pane
|
||||
collaborationPane: "Zusammenarbeitsfeld",
|
||||
people: "Leute",
|
||||
chat: "Chat",
|
||||
typeYourName_DDD: "Geben Sie Ihren Namen ein...",
|
||||
invitePeople: "Leute einladen",
|
||||
startTypingToChat_DDD: "Eingabe beginnen für Chat...",
|
||||
members: "Teilnehmer",
|
||||
inviteMembers: "Teilnehmer einladen",
|
||||
// Various
|
||||
left: "Links",
|
||||
right: "Rechts",
|
||||
@ -67,6 +74,8 @@ define({
|
||||
spacing: "Abstand",
|
||||
options: "Optionen",
|
||||
style: "Stil",
|
||||
undo: "Rückgängig",
|
||||
redo: "Wiederherstellen",
|
||||
bold: "Fett",
|
||||
italic: "Kursiv",
|
||||
underline: "Unterstrichen",
|
||||
|
@ -35,11 +35,17 @@ define({
|
||||
root: {
|
||||
// menus
|
||||
file: "File",
|
||||
open: "Open",
|
||||
save: "Save",
|
||||
edit: "Edit",
|
||||
view: "View",
|
||||
annotate: "Annotate",
|
||||
clone: "Clone",
|
||||
create: "Create",
|
||||
delete: "Delete",
|
||||
insert: "Insert",
|
||||
format: "Format",
|
||||
close: "Close",
|
||||
character_DDD: "Character...",
|
||||
paragraph_DDD: "Paragraph...",
|
||||
// dialogs
|
||||
@ -51,13 +57,12 @@ define({
|
||||
textFlow: "Text Flow",
|
||||
character: "Character",
|
||||
paragraphStyles: "Paragraph Styles",
|
||||
cloneThisStyle: "Clone this style",
|
||||
newName_C: "New name:",
|
||||
// Collaboration pane
|
||||
collaborationPane: "Collaboration Pane",
|
||||
people: "People",
|
||||
chat: "Chat",
|
||||
typeYourName_DDD: "Type your name...",
|
||||
invitePeople: "Invite People",
|
||||
startTypingToChat_DDD: "Start typing to chat...",
|
||||
members: "Members",
|
||||
inviteMembers: "Invite members",
|
||||
// Various
|
||||
left: "Left",
|
||||
right: "Right",
|
||||
@ -68,6 +73,8 @@ define({
|
||||
spacing: "Spacing",
|
||||
options: "Options",
|
||||
style: "Style",
|
||||
undo: "Undo",
|
||||
redo: "Redo",
|
||||
bold: "Bold",
|
||||
italic: "Italic",
|
||||
underline: "Underline",
|
||||
|
@ -1,124 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* (GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. The code is distributed
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
||||
*
|
||||
* As additional permission under GNU AGPL version 3 section 7, you
|
||||
* may distribute non-source (e.g., minimized or compacted) forms of
|
||||
* that code without the copy of the GNU GPL normally required by
|
||||
* section 4, provided you include this license notice and a URL
|
||||
* through which recipients can access the Corresponding Source.
|
||||
*
|
||||
* As a special exception to the AGPL, any HTML file which merely makes function
|
||||
* calls to this code, and for that purpose includes it by reference shall be
|
||||
* deemed a separate work for copyright law purposes. In addition, the copyright
|
||||
* holders of this code give you permission to combine this code with free
|
||||
* software libraries that are released under the GNU LGPL. You may copy and
|
||||
* distribute such a system following the terms of the GNU AGPL for this code
|
||||
* and the LGPL for the libraries. If you modify this code, you may extend this
|
||||
* exception to your version of the code, but you are not obligated to do so.
|
||||
* If you do not wish to do so, delete this exception statement from your
|
||||
* version.
|
||||
*
|
||||
* This license applies to this entire compilation.
|
||||
* @licend
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global define, ops, runtime */
|
||||
|
||||
define("webodf/editor/server/nowjs/sessionList", [], function () {
|
||||
"use strict";
|
||||
|
||||
return function NowjsSessionList(nowjsServer) {
|
||||
|
||||
var cachedSessionData = {},
|
||||
subscribers = [];
|
||||
|
||||
function onSessionData(sessionData) {
|
||||
var i,
|
||||
isNew = ! cachedSessionData.hasOwnProperty(sessionData.id);
|
||||
|
||||
// cache
|
||||
cachedSessionData[sessionData.id] = sessionData;
|
||||
runtime.log("get session data for:"+sessionData.title+", is new:"+isNew);
|
||||
|
||||
for (i = 0; i < subscribers.length; i += 1) {
|
||||
if (isNew) {
|
||||
subscribers[i].onCreated(sessionData);
|
||||
} else {
|
||||
subscribers[i].onUpdated(sessionData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onSessionRemoved(sessionId) {
|
||||
var i;
|
||||
|
||||
if (cachedSessionData.hasOwnProperty(sessionId)) {
|
||||
delete cachedSessionData[sessionId];
|
||||
|
||||
for (i = 0; i < subscribers.length; i += 1) {
|
||||
subscribers[i].onRemoved(sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.getSessions = function (subscriber) {
|
||||
var i,
|
||||
sessionList = [];
|
||||
|
||||
if (subscriber) {
|
||||
subscribers.push(subscriber);
|
||||
}
|
||||
|
||||
for (i in cachedSessionData) {
|
||||
if (cachedSessionData.hasOwnProperty(i)) {
|
||||
sessionList.push(cachedSessionData[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return sessionList;
|
||||
};
|
||||
|
||||
this.unsubscribe = function (subscriber) {
|
||||
var i;
|
||||
|
||||
for (i=0; i<subscribers.length; i+=1) {
|
||||
if (subscribers[i] === subscriber) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
runtime.assert((i < subscribers.length),
|
||||
"tried to unsubscribe when not subscribed.");
|
||||
|
||||
subscribers.splice(i,1);
|
||||
};
|
||||
|
||||
function init() {
|
||||
var nowObject = nowjsServer.getNowObject();
|
||||
nowObject.onSessionAdded = onSessionData;
|
||||
nowObject.onSessionChanged = onSessionData;
|
||||
nowObject.onSessionRemoved = onSessionRemoved;
|
||||
|
||||
nowObject.getSessionList( function(sessionList) {
|
||||
var idx;
|
||||
runtime.log("get sessions on init:"+sessionList.length);
|
||||
for (idx=0; idx<sessionList.length; idx+=1) {
|
||||
onSessionData(sessionList[idx])
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
};
|
||||
});
|
@ -33,33 +33,42 @@
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global define, document, require, runtime, core, ops */
|
||||
/*global define, require, OC*/
|
||||
|
||||
define("webodf/editor/server/nowjs/serverFactory", [
|
||||
"webodf/editor/server/nowjs/sessionList"],
|
||||
function (NowjsSessionList) {
|
||||
define("webodf/editor/server/owncloud/ServerFactory", [
|
||||
"webodf/editor/server/pullbox/Server",
|
||||
"webodf/editor/server/pullbox/MemberModel",
|
||||
"webodf/editor/server/pullbox/OperationRouter",
|
||||
"webodf/editor/server/pullbox/SessionList"],
|
||||
function (PullBoxServer, PullBoxMemberModel, PullBoxOperationRouter, PullBoxSessionList) {
|
||||
"use strict";
|
||||
|
||||
runtime.loadClass("ops.NowjsServer");
|
||||
runtime.loadClass("ops.NowjsMemberModel");
|
||||
runtime.loadClass("ops.NowjsOperationRouter");
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @implements ServerFactory
|
||||
*/
|
||||
return function NowjsServerFactory() {
|
||||
return function OwnCloudServerFactory() {
|
||||
this.createServer = function (args) {
|
||||
return new ops.NowjsServer(args);
|
||||
var server;
|
||||
args = args || {};
|
||||
args.url = "./documents/ajax/otpoll.php";
|
||||
args.sessionStateToFileUrl = OC.Router.generate('documents_session_save');
|
||||
|
||||
server = new PullBoxServer(args);
|
||||
server.getGenesisUrl = function(sid) {
|
||||
// what a dirty hack :)
|
||||
return OC.Router.generate('documents_genesis') + '/' + sid;
|
||||
};
|
||||
return server;
|
||||
};
|
||||
this.createOperationRouter = function (sid, mid, server) {
|
||||
return new ops.NowjsOperationRouter(sid, mid, server);
|
||||
this.createOperationRouter = function (sid, mid, server, odfContainer) {
|
||||
return new PullBoxOperationRouter(sid, mid, server, odfContainer);
|
||||
};
|
||||
this.createMemberModel = function (sid, server) {
|
||||
return new ops.NowjsMemberModel(server);
|
||||
return new PullBoxMemberModel(sid, server);
|
||||
};
|
||||
this.createSessionList = function (server) {
|
||||
return new NowjsSessionList(server);
|
||||
return new PullBoxSessionList(server);
|
||||
};
|
||||
};
|
||||
});
|
281
js/editor/server/pullbox/MemberModel.js
Normal file
281
js/editor/server/pullbox/MemberModel.js
Normal file
@ -0,0 +1,281 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* (GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. The code is distributed
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
||||
*
|
||||
* As additional permission under GNU AGPL version 3 section 7, you
|
||||
* may distribute non-source (e.g., minimized or compacted) forms of
|
||||
* that code without the copy of the GNU GPL normally required by
|
||||
* section 4, provided you include this license notice and a URL
|
||||
* through which recipients can access the Corresponding Source.
|
||||
*
|
||||
* As a special exception to the AGPL, any HTML file which merely makes function
|
||||
* calls to this code, and for that purpose includes it by reference shall be
|
||||
* deemed a separate work for copyright law purposes. In addition, the copyright
|
||||
* holders of this code give you permission to combine this code with free
|
||||
* software libraries that are released under the GNU LGPL. You may copy and
|
||||
* distribute such a system following the terms of the GNU AGPL for this code
|
||||
* and the LGPL for the libraries. If you modify this code, you may extend this
|
||||
* exception to your version of the code, but you are not obligated to do so.
|
||||
* If you do not wish to do so, delete this exception statement from your
|
||||
* version.
|
||||
*
|
||||
* This license applies to this entire compilation.
|
||||
* @licend
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global runtime, ops*/
|
||||
|
||||
define("webodf/editor/server/pullbox/MemberModel", [], function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @implements ops.MemberModel
|
||||
*/
|
||||
return function PullBoxMemberModel(sessionId, server) {
|
||||
|
||||
var cachedMemberData = {},
|
||||
memberDataSubscribers = {},
|
||||
isServerPullingActivated = false,
|
||||
isServerPullingOpen = true,
|
||||
serverPullingTimeoutId = null,
|
||||
isInstantPullingRequested = false,
|
||||
isPulling = false,
|
||||
/**@const*/pullingIntervall = 20000;
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Object} memberData
|
||||
*/
|
||||
function cacheMemberDatum(memberData) {
|
||||
var subscribers,
|
||||
i;
|
||||
|
||||
// notify all subscribers who are interested in this data
|
||||
subscribers = memberDataSubscribers[memberData.memberid];
|
||||
if (subscribers) {
|
||||
// cache
|
||||
cachedMemberData[memberData.memberid] = memberData;
|
||||
|
||||
for (i = 0; i < subscribers.length; i += 1) {
|
||||
subscribers[i](memberData.memberid, memberData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pullMemberData() {
|
||||
var i,
|
||||
memberIds = Object.keys(memberDataSubscribers);
|
||||
|
||||
if (!isServerPullingOpen || isPulling) {
|
||||
return;
|
||||
}
|
||||
|
||||
// no more timeout or instant pull request in any case
|
||||
serverPullingTimeoutId = null;
|
||||
isInstantPullingRequested = false;
|
||||
// set lock
|
||||
isPulling = true;
|
||||
|
||||
runtime.log("member-list request for : " + memberIds.join(","));
|
||||
|
||||
server.call({
|
||||
command: 'query_memberdata_list',
|
||||
args: {
|
||||
es_id: sessionId,
|
||||
member_ids: memberIds
|
||||
}
|
||||
}, function(responseData) {
|
||||
var response = /**@type {{memberdata_list:Array.<{uid,member_id,display_name,avatar_url,color}>}}*/(runtime.fromJson(responseData)),
|
||||
memberDataList,
|
||||
newMemberData, oldMemberData;
|
||||
|
||||
// unlock
|
||||
isPulling = false;
|
||||
|
||||
// meanwhile closed/disactivated?
|
||||
if (!isServerPullingOpen || !isServerPullingActivated) {
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.log("member-list reply: " + responseData);
|
||||
|
||||
if (response.hasOwnProperty("memberdata_list")) {
|
||||
|
||||
// add/update with all delivered memberdata
|
||||
memberDataList = response.memberdata_list;
|
||||
for (i = 0; i < memberDataList.length; i+=1) {
|
||||
newMemberData = {
|
||||
memberid: memberDataList[i].member_id,
|
||||
fullname: memberDataList[i].display_name,
|
||||
imageurl: memberDataList[i].avatar_url,
|
||||
color: memberDataList[i].color
|
||||
};
|
||||
|
||||
oldMemberData = cachedMemberData.hasOwnProperty(newMemberData.memberid) ? cachedMemberData[newMemberData.memberid] : null;
|
||||
if (!oldMemberData ||
|
||||
oldMemberData.fullname !== newMemberData.fullname ||
|
||||
oldMemberData.imageurl !== newMemberData.imageurl ||
|
||||
oldMemberData.color !== newMemberData.color) {
|
||||
cacheMemberDatum(newMemberData);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
runtime.log("Meh, memberdata list broken: " + responseData);
|
||||
}
|
||||
|
||||
// trigger the next pulling
|
||||
if (isInstantPullingRequested) {
|
||||
pullMemberData();
|
||||
} else {
|
||||
serverPullingTimeoutId = runtime.setTimeout(pullMemberData, pullingIntervall);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the pulling
|
||||
* @return {undefined}
|
||||
*/
|
||||
function pullNewMemberData() {
|
||||
// cancel any running pulling timeout
|
||||
if (serverPullingTimeoutId !== null) {
|
||||
runtime.clearTimeout(serverPullingTimeoutId);
|
||||
}
|
||||
|
||||
isInstantPullingRequested = true;
|
||||
isServerPullingActivated = true;
|
||||
|
||||
pullMemberData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the pulling if there are no more subscribers
|
||||
* @return {undefined}
|
||||
*/
|
||||
function deactivatePeriodicMemberDataPulling() {
|
||||
var key;
|
||||
|
||||
if (!isServerPullingActivated) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if there is no more subscription
|
||||
for(key in memberDataSubscribers) {
|
||||
if (memberDataSubscribers.hasOwnProperty(key)) {
|
||||
// still subscribers, cannot deactivate yet
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
isServerPullingActivated = false;
|
||||
// cancel any running pulling timeout
|
||||
if (serverPullingTimeoutId !== null) {
|
||||
runtime.clearTimeout(serverPullingTimeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* callback is called as soon as the memberdata is available and after that
|
||||
* on every memberdata update.
|
||||
* a parameter `null` passed to the callback means that the member is finally
|
||||
* not known.
|
||||
*
|
||||
* @param {!string} memberId
|
||||
* @param {!function(!string, ?Object)} subscriber
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.getMemberDetailsAndUpdates = function (memberId, subscriber) {
|
||||
var /**@type{Object}*/
|
||||
memberData = cachedMemberData[memberId],
|
||||
subscribers = memberDataSubscribers[memberId] || [],
|
||||
i;
|
||||
memberDataSubscribers[memberId] = subscribers;
|
||||
|
||||
runtime.assert(subscriber !== undefined, "missing callback");
|
||||
|
||||
// detect double subscription
|
||||
for (i=0; i<subscribers.length; i+=1) {
|
||||
if (subscribers[i] === subscriber) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i < subscribers.length) {
|
||||
// already subscribed
|
||||
runtime.log("double subscription request for "+memberId+" in PullBoxMemberModel::getMemberDetailsAndUpdates");
|
||||
} else {
|
||||
// subscribe
|
||||
subscribers.push(subscriber);
|
||||
// query data from server, if not done yet
|
||||
if (subscribers.length === 1) {
|
||||
// TODO: only fetch data for memberId here
|
||||
pullNewMemberData();
|
||||
}
|
||||
}
|
||||
|
||||
if (memberData) {
|
||||
// data available from cache
|
||||
subscriber(memberId, memberData);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* getMemberDetailsAndUpdates subscribes a callback for updates on member details.
|
||||
* this function undoes this subscription.
|
||||
*
|
||||
* @param {!string} memberId
|
||||
* @param {!function(!string, ?Object)} subscriber
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.unsubscribeMemberDetailsUpdates = function (memberId, subscriber) {
|
||||
var i,
|
||||
subscribers = memberDataSubscribers[memberId];
|
||||
|
||||
runtime.assert(subscriber!==undefined, "missing subscriber parameter or null");
|
||||
runtime.assert(subscribers,
|
||||
"tried to unsubscribe when no one is subscribed ('" + memberId + "')");
|
||||
if (subscribers) {
|
||||
for (i=0; i<subscribers.length; i+=1) {
|
||||
if (subscribers[i] === subscriber) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
runtime.assert((i < subscribers.length),
|
||||
"tried to unsubscribe when not subscribed for memberId '" + memberId + "'");
|
||||
|
||||
subscribers.splice(i,1);
|
||||
|
||||
// clean up
|
||||
if (subscribers.length === 0) {
|
||||
runtime.log("no more subscribers for: "+memberId);
|
||||
delete memberDataSubscribers[memberId];
|
||||
delete cachedMemberData[memberId];
|
||||
deactivatePeriodicMemberDataPulling();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Requests a gracefull shutdown of the Member Model
|
||||
* No more network activity is necessary.
|
||||
*/
|
||||
this.close = function (cb) {
|
||||
isServerPullingOpen = false;
|
||||
cb();
|
||||
};
|
||||
|
||||
runtime.assert(server.networkStatus() === "ready", "network not ready");
|
||||
};
|
||||
});
|
508
js/editor/server/pullbox/OperationRouter.js
Normal file
508
js/editor/server/pullbox/OperationRouter.js
Normal file
@ -0,0 +1,508 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* (GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. The code is distributed
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
||||
*
|
||||
* As additional permission under GNU AGPL version 3 section 7, you
|
||||
* may distribute non-source (e.g., minimized or compacted) forms of
|
||||
* that code without the copy of the GNU GPL normally required by
|
||||
* section 4, provided you include this license notice and a URL
|
||||
* through which recipients can access the Corresponding Source.
|
||||
*
|
||||
* As a special exception to the AGPL, any HTML file which merely makes function
|
||||
* calls to this code, and for that purpose includes it by reference shall be
|
||||
* deemed a separate work for copyright law purposes. In addition, the copyright
|
||||
* holders of this code give you permission to combine this code with free
|
||||
* software libraries that are released under the GNU LGPL. You may copy and
|
||||
* distribute such a system following the terms of the GNU AGPL for this code
|
||||
* and the LGPL for the libraries. If you modify this code, you may extend this
|
||||
* exception to your version of the code, but you are not obligated to do so.
|
||||
* If you do not wish to do so, delete this exception statement from your
|
||||
* version.
|
||||
*
|
||||
* This license applies to this entire compilation.
|
||||
* @licend
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global runtime, ops*/
|
||||
|
||||
define("webodf/editor/server/pullbox/OperationRouter", [], function () {
|
||||
"use strict";
|
||||
|
||||
runtime.loadClass("ops.OperationTransformer");
|
||||
|
||||
/**
|
||||
* route operations in a networked collaborative manner.
|
||||
*
|
||||
* incoming operations (from controller) are sent to a server,
|
||||
* who will distribute them.
|
||||
*
|
||||
* incoming operations (from the server are played on the DOM.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @implements ops.OperationRouter
|
||||
*/
|
||||
return function PullBoxOperationRouter(sessionId, memberId, server, odfContainer) {
|
||||
"use strict";
|
||||
|
||||
var operationFactory,
|
||||
singleTimeCallbackOnCompleteServerOpSpecsPlay,
|
||||
/**@type{function(!ops.Operation)}*/
|
||||
playbackFunction,
|
||||
/**@type{?{active:!boolean}}*/
|
||||
pullingTimeOutFlag = null,
|
||||
/**@type{!boolean}*/
|
||||
triggerPushingOpsActivated = false,
|
||||
/**@type{!boolean}*/
|
||||
playUnplayedServerOpSpecsTriggered = false,
|
||||
/**@type{!boolean}*/
|
||||
syncLock = false,
|
||||
/**@type{!boolean}*/
|
||||
hasUnresolvableConflict = false,
|
||||
/**@type{!boolean}*/
|
||||
syncingBlocked = false,
|
||||
/** @type {!string} id of latest op stack state known on the server */
|
||||
lastServerSeq = "",
|
||||
/** @type {!Array.<!Object>} ops created since the last sync call to the server */
|
||||
unsyncedClientOpspecQueue = [],
|
||||
/** @type {!Array.<!Object>} ops created since the last sync call to the server */
|
||||
unplayedServerOpspecQueue = [],
|
||||
/** @type {!Array.<!function(!boolean):undefined>} ops created since the last sync call to the server */
|
||||
hasLocalUnsyncedOpsStateSubscribers = [],
|
||||
/**@type{!boolean}*/
|
||||
hasLocalUnsyncedOps = false,
|
||||
/**@type{!boolean} tells if any local ops have been modifying ops */
|
||||
hasPushedModificationOps = false,
|
||||
operationTransformer = new ops.OperationTransformer(),
|
||||
/**@const*/replayTime = 500,
|
||||
/**@const*/pushingIntervall = 3000,
|
||||
/**@const*/pullingIntervall = 8000;
|
||||
|
||||
|
||||
function updateHasLocalUnsyncedOpsState() {
|
||||
var i,
|
||||
hasLocalUnsyncedOpsNow = (unsyncedClientOpspecQueue.length > 0);
|
||||
|
||||
// no change?
|
||||
if (hasLocalUnsyncedOps === hasLocalUnsyncedOpsNow) {
|
||||
return;
|
||||
}
|
||||
|
||||
hasLocalUnsyncedOps = hasLocalUnsyncedOpsNow;
|
||||
for (i=0; i<hasLocalUnsyncedOpsStateSubscribers.length; i+=1) {
|
||||
hasLocalUnsyncedOpsStateSubscribers[i](hasLocalUnsyncedOps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array.<!Object>} opspecs
|
||||
* @return {!Array.<!Object>}
|
||||
*/
|
||||
function compressOpSpecs(opspecs) {
|
||||
var i, j, op,
|
||||
result = [];
|
||||
|
||||
i = 0;
|
||||
while (i < opspecs.length) {
|
||||
// use factory to create an instance, and playback!
|
||||
op = operationFactory.create(opspecs[i]);
|
||||
// is known op and can do merge?
|
||||
if (op !== null && op.merge) {
|
||||
// go over the following and try to merge them
|
||||
for (j = i+1; j < opspecs.length; j += 1) {
|
||||
if (!op.merge(opspecs[j])) {
|
||||
break;
|
||||
}
|
||||
runtime.log("Merged: "+opspecs[i].optype+" with "+opspecs[j].optype);
|
||||
}
|
||||
// add the resulting op to the results
|
||||
result.push(op.spec());
|
||||
// and continue with the one which could not be merged, or behind end
|
||||
i = j;
|
||||
} else {
|
||||
// just pass on
|
||||
result.push(opspecs[i]);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
runtime.log("Merged: from "+opspecs.length+" to "+result.length+" specs");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {undefined}
|
||||
*/
|
||||
function playUnplayedServerOpSpecs() {
|
||||
/**
|
||||
* @return {undefined}
|
||||
*/
|
||||
function doPlayUnplayedServerOpSpecs() {
|
||||
var opspec, op, startTime;
|
||||
|
||||
playUnplayedServerOpSpecsTriggered = false;
|
||||
|
||||
// take start time
|
||||
startTime = (new Date()).getTime();
|
||||
|
||||
// apply as much as possible in the given time
|
||||
while (unplayedServerOpspecQueue.length > 0) {
|
||||
// time over?
|
||||
if ((new Date().getTime()) - startTime > replayTime) {
|
||||
break;
|
||||
}
|
||||
|
||||
opspec = unplayedServerOpspecQueue.shift();
|
||||
|
||||
// use factory to create an instance, and playback!
|
||||
op = operationFactory.create(opspec);
|
||||
runtime.log(" op in: "+runtime.toJson(opspec));
|
||||
if (op !== null) {
|
||||
playbackFunction(op);
|
||||
} else {
|
||||
runtime.log("ignoring invalid incoming opspec: " + opspec);
|
||||
}
|
||||
}
|
||||
|
||||
// still unplayed opspecs?
|
||||
if (unplayedServerOpspecQueue.length > 0) {
|
||||
// let other events be handled. then continue
|
||||
playUnplayedServerOpSpecsTriggered = true;
|
||||
runtime.getWindow().setTimeout(doPlayUnplayedServerOpSpecs, 1);
|
||||
} else {
|
||||
// This is such a sad hack. But there is no other way for now to inject
|
||||
// the callback after the initial replay.
|
||||
if (singleTimeCallbackOnCompleteServerOpSpecsPlay) {
|
||||
singleTimeCallbackOnCompleteServerOpSpecsPlay();
|
||||
singleTimeCallbackOnCompleteServerOpSpecsPlay = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (playUnplayedServerOpSpecsTriggered) {
|
||||
return;
|
||||
}
|
||||
doPlayUnplayedServerOpSpecs();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array.<!Object>} opspecs
|
||||
* @return {undefined}
|
||||
*/
|
||||
function receiveOpSpecsFromNetwork(opspecs) {
|
||||
// append to existing unplayed
|
||||
unplayedServerOpspecQueue = unplayedServerOpspecQueue.concat(opspecs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the unsynced client ops and the server ops,
|
||||
* applies the server ops after transformation
|
||||
* @param {Array.<!Object>} serverOpspecs
|
||||
* @return {!boolean}
|
||||
*/
|
||||
function handleOpsSyncConflict(serverOpspecs) {
|
||||
var i,
|
||||
transformResult;
|
||||
|
||||
if (! serverOpspecs) {
|
||||
// TODO: proper error message, stop working
|
||||
runtime.assert(false, "no opspecs received!");
|
||||
return false;
|
||||
} // TODO: more checking of proper content in serverOpspecs
|
||||
|
||||
transformResult = operationTransformer.transform(unsyncedClientOpspecQueue, /**@type{!Array.<!Object>}*/(serverOpspecs));
|
||||
|
||||
if (!transformResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// store transformed server ops
|
||||
for (i = 0; i < transformResult.opsB.length; i += 1) {
|
||||
unplayedServerOpspecQueue.push(transformResult.opsB[i].spec());
|
||||
}
|
||||
|
||||
// store opspecs of all transformed client opspecs
|
||||
unsyncedClientOpspecQueue = [];
|
||||
for (i = 0; i < transformResult.opsA.length; i += 1) {
|
||||
unsyncedClientOpspecQueue.push(transformResult.opsA[i].spec());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {undefined}
|
||||
*/
|
||||
function syncOps() {
|
||||
function triggerPullingOps() {
|
||||
var flag = {active: true};
|
||||
// provide flag globally
|
||||
pullingTimeOutFlag = flag;
|
||||
runtime.getWindow().setTimeout(function() {
|
||||
runtime.log("Pulling activated:" + flag.active);
|
||||
// remove our flag
|
||||
pullingTimeOutFlag = null;
|
||||
if (flag.active) {
|
||||
syncOps();
|
||||
}
|
||||
}, pullingIntervall);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {undefined}
|
||||
*/
|
||||
function doSyncOps() {
|
||||
var syncedClientOpspecs;
|
||||
|
||||
if (syncLock || hasUnresolvableConflict) {
|
||||
return;
|
||||
}
|
||||
// TODO: hack, remove
|
||||
if (syncingBlocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
syncLock = true;
|
||||
|
||||
// take specs from queue, if any
|
||||
syncedClientOpspecs = unsyncedClientOpspecQueue;
|
||||
unsyncedClientOpspecQueue = [];
|
||||
|
||||
server.call({
|
||||
command: 'sync_ops',
|
||||
args: {
|
||||
es_id: sessionId,
|
||||
member_id: memberId,
|
||||
seq_head: String(lastServerSeq),
|
||||
client_ops: syncedClientOpspecs
|
||||
}
|
||||
}, function(responseData) {
|
||||
var shouldRetryInstantly = false,
|
||||
response = /** @type{{result:string, head_seq:string, ops:Array.<!Object>}} */(runtime.fromJson(responseData));
|
||||
|
||||
// TODO: hack, remove
|
||||
if (syncingBlocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.log("sync-ops reply: " + responseData);
|
||||
|
||||
// just new ops?
|
||||
if (response.result === "new_ops") {
|
||||
if (response.ops.length > 0) {
|
||||
// no new locally in the meantime?
|
||||
if (unsyncedClientOpspecQueue.length === 0) {
|
||||
receiveOpSpecsFromNetwork(compressOpSpecs(response.ops));
|
||||
} else {
|
||||
// transform server ops against new local ones and apply,
|
||||
// transform and send new local ops to server
|
||||
runtime.log("meh, have new ops locally meanwhile, have to do transformations.");
|
||||
hasUnresolvableConflict = !handleOpsSyncConflict(compressOpSpecs(response.ops));
|
||||
}
|
||||
// and note server state
|
||||
lastServerSeq = response.head_seq;
|
||||
}
|
||||
} else if (response.result === "added") {
|
||||
runtime.log("All added to server");
|
||||
// note server state
|
||||
lastServerSeq = response.head_seq;
|
||||
updateHasLocalUnsyncedOpsState();
|
||||
} else if (response.result === "conflict") {
|
||||
// put the send ops back into the outgoing queue
|
||||
unsyncedClientOpspecQueue = syncedClientOpspecs.concat(unsyncedClientOpspecQueue);
|
||||
// transform server ops against new local ones and apply,
|
||||
// transform and request new send new local ops to server
|
||||
runtime.log("meh, server has new ops meanwhile, have to do transformations.");
|
||||
hasUnresolvableConflict = !handleOpsSyncConflict(compressOpSpecs(response.ops));
|
||||
// and note server state
|
||||
lastServerSeq = response.head_seq;
|
||||
// try again instantly
|
||||
if (!hasUnresolvableConflict) {
|
||||
shouldRetryInstantly = true;
|
||||
}
|
||||
} else {
|
||||
runtime.assert(false, "Unexpected result on sync-ops call: "+response.result);
|
||||
}
|
||||
|
||||
syncLock = false;
|
||||
|
||||
if (hasUnresolvableConflict) {
|
||||
// TODO: offer option to reload session automatically?
|
||||
runtime.assert(false,
|
||||
"Sorry to tell:\n" +
|
||||
"we hit a pair of operations in a state which yet need to be supported for transformation against each other.\n" +
|
||||
"Client disconnected from session, no further editing accepted.\n\n" +
|
||||
"Please reconnect manually for now.");
|
||||
} else {
|
||||
if (shouldRetryInstantly) {
|
||||
doSyncOps();
|
||||
} else {
|
||||
runtime.log("Preparing next: " + (unsyncedClientOpspecQueue.length === 0));
|
||||
// prepare next sync
|
||||
// nothing to push right now?
|
||||
if (unsyncedClientOpspecQueue.length === 0) {
|
||||
triggerPullingOps();
|
||||
}
|
||||
}
|
||||
playUnplayedServerOpSpecs();
|
||||
}
|
||||
});
|
||||
}
|
||||
doSyncOps();
|
||||
}
|
||||
|
||||
function triggerPushingOps() {
|
||||
if (syncLock || triggerPushingOpsActivated) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerPushingOpsActivated = true;
|
||||
// disable current pulling timeout
|
||||
if (pullingTimeOutFlag) {
|
||||
pullingTimeOutFlag.active = false;
|
||||
}
|
||||
|
||||
runtime.getWindow().setTimeout(function() {
|
||||
runtime.log("Pushing activated");
|
||||
triggerPushingOpsActivated = false;
|
||||
syncOps();
|
||||
}, pushingIntervall);
|
||||
}
|
||||
|
||||
this.requestReplay = function (done_cb) {
|
||||
singleTimeCallbackOnCompleteServerOpSpecsPlay = done_cb;
|
||||
syncOps();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the factory to use to create operation instances from operation specs.
|
||||
*
|
||||
* @param {!ops.OperationFactory} f
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.setOperationFactory = function (f) {
|
||||
operationFactory = f;
|
||||
operationTransformer.setOperationFactory(f);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the method which should be called to apply operations.
|
||||
*
|
||||
* @param {!function(!ops.Operation)} playback_func
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.setPlaybackFunction = function (playback_func) {
|
||||
playbackFunction = playback_func;
|
||||
};
|
||||
|
||||
/**
|
||||
* Brings the locally created operations into the game.
|
||||
*
|
||||
* @param {!ops.Operation} op
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.push = function (op) {
|
||||
var timedOp,
|
||||
opspec = op.spec();
|
||||
|
||||
if (hasUnresolvableConflict) {
|
||||
return;
|
||||
}
|
||||
// TODO: should be an assert in the future
|
||||
// there needs to be a flag telling that processing is happening,
|
||||
// and thus any input should be dropped in the sessioncontroller
|
||||
// ideally also have some UI element showing the processing state
|
||||
if (unplayedServerOpspecQueue.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// note if any local ops modified TODO: find less fragile way, perhaps have the operationFactory check it?
|
||||
hasPushedModificationOps = hasPushedModificationOps || !/^(AddCursor|RemoveCursor)$/.test(opspec.optype);
|
||||
|
||||
// apply locally
|
||||
opspec.timestamp = (new Date()).getTime();
|
||||
timedOp = operationFactory.create(opspec);
|
||||
|
||||
playbackFunction(timedOp);
|
||||
|
||||
// send to server
|
||||
unsyncedClientOpspecQueue.push(opspec);
|
||||
|
||||
triggerPushingOps();
|
||||
|
||||
updateHasLocalUnsyncedOpsState();
|
||||
};
|
||||
|
||||
/**
|
||||
* Requests a gracefull shutdown of the Operation Router.
|
||||
* Buffered operations shall be sent to the server.
|
||||
* A callback is called on success.
|
||||
*/
|
||||
this.close = function (cb) {
|
||||
function writeSessionStateToFile() {
|
||||
function cbSuccess(fileData) {
|
||||
server.writeSessionStateToFile(sessionId, memberId, lastServerSeq, fileData, cb);
|
||||
};
|
||||
odfContainer.createByteArray(cbSuccess, cb);
|
||||
}
|
||||
|
||||
// TODO: hack, rather add callback to syncOps for success and properly close things
|
||||
syncOps();
|
||||
runtime.getWindow().setTimeout(function() {
|
||||
syncingBlocked = true;
|
||||
if (hasPushedModificationOps) {
|
||||
writeSessionStateToFile();
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
this.getHasLocalUnsyncedOpsAndUpdates = function (subscriber) {
|
||||
var i;
|
||||
|
||||
// detect double subscription
|
||||
for (i=0; i<hasLocalUnsyncedOpsStateSubscribers.length; i+=1) {
|
||||
if (subscribers[i] === subscriber) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i < hasLocalUnsyncedOpsStateSubscribers.length) {
|
||||
// already subscribed
|
||||
runtime.log("double subscription request in PullBoxMemberModel::getHasLocalUnsyncedOpsAndUpdates");
|
||||
} else {
|
||||
// subscribe
|
||||
hasLocalUnsyncedOpsStateSubscribers.push(subscriber);
|
||||
}
|
||||
|
||||
subscriber(hasLocalUnsyncedOps);
|
||||
};
|
||||
|
||||
/*jslint emptyblock: true, unparam: true*/
|
||||
this.unsubscribeHasLocalUnsyncedOpsUpdates = function (subscriber) {
|
||||
var i;
|
||||
|
||||
for (i=0; i<hasLocalUnsyncedOpsStateSubscribers.length; i+=1) {
|
||||
if (hasLocalUnsyncedOpsStateSubscribers[i] === subscriber) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
runtime.assert((i < hasLocalUnsyncedOpsStateSubscribers.length),
|
||||
"tried to unsubscribe when not subscribed in PullBoxMemberModel::getHasLocalUnsyncedOpsAndUpdates");
|
||||
|
||||
hasLocalUnsyncedOpsStateSubscribers.splice(i,1);
|
||||
};
|
||||
};
|
||||
});
|
293
js/editor/server/pullbox/Server.js
Normal file
293
js/editor/server/pullbox/Server.js
Normal file
@ -0,0 +1,293 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
* (GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. The code is distributed
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
||||
*
|
||||
* As additional permission under GNU AGPL version 3 section 7, you
|
||||
* may distribute non-source (e.g., minimized or compacted) forms of
|
||||
* that code without the copy of the GNU GPL normally required by
|
||||
* section 4, provided you include this license notice and a URL
|
||||
* through which recipients can access the Corresponding Source.
|
||||
*
|
||||
* As a special exception to the AGPL, any HTML file which merely makes function
|
||||
* calls to this code, and for that purpose includes it by reference shall be
|
||||
* deemed a separate work for copyright law purposes. In addition, the copyright
|
||||
* holders of this code give you permission to combine this code with free
|
||||
* software libraries that are released under the GNU LGPL. You may copy and
|
||||
* distribute such a system following the terms of the GNU AGPL for this code
|
||||
* and the LGPL for the libraries. If you modify this code, you may extend this
|
||||
* exception to your version of the code, but you are not obligated to do so.
|
||||
* If you do not wish to do so, delete this exception statement from your
|
||||
* version.
|
||||
*
|
||||
* This license applies to this entire compilation.
|
||||
* @licend
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global XMLHttpRequest, runtime, core, ops*/
|
||||
|
||||
define("webodf/editor/server/pullbox/Server", [], function () {
|
||||
"use strict";
|
||||
|
||||
runtime.loadClass("core.Base64");
|
||||
runtime.loadClass("core.ByteArrayWriter");
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @implements ops.Server
|
||||
* @param {{url:string}} args
|
||||
*/
|
||||
return function PullBoxServer(args) {
|
||||
|
||||
var self = this,
|
||||
token,
|
||||
base64 = new core.Base64();
|
||||
|
||||
args = args || {};
|
||||
args.url = args.url || "/WSER";
|
||||
args.sessionStateToFileUrl = args.sessionStateToFileUrl || "/SS2F"
|
||||
|
||||
this.getGenesisUrl = function (sessionId) {
|
||||
return "/session/" + sessionId + "/genesis";
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!Object} message
|
||||
* @param {!function(!string)} cb
|
||||
* @return {undefined}
|
||||
*/
|
||||
function call(message, cb) {
|
||||
var xhr = new XMLHttpRequest(),
|
||||
byteArrayWriter = new core.ByteArrayWriter("utf8"),
|
||||
messageString = JSON.stringify(message),
|
||||
data;
|
||||
|
||||
function handleResult() {
|
||||
if (xhr.readyState === 4) {
|
||||
if ((xhr.status < 200 || xhr.status >= 300) && xhr.status === 0) {
|
||||
// report error
|
||||
runtime.log("Status " + String(xhr.status) + ": " +
|
||||
xhr.responseText || xhr.statusText);
|
||||
}
|
||||
cb(xhr.responseText);
|
||||
}
|
||||
}
|
||||
runtime.log("Sending message to server: "+messageString);
|
||||
// create body data for request from metadata and payload
|
||||
byteArrayWriter.appendString(messageString);
|
||||
// byteArrayWriter.appendByteArray(zipData);
|
||||
data = byteArrayWriter.getByteArray();
|
||||
|
||||
// do the request
|
||||
xhr.open('POST', args.url, true);
|
||||
xhr.onreadystatechange = handleResult;
|
||||
// ArrayBufferView will have an ArrayBuffer property, in WebKit, XHR can send()
|
||||
// an ArrayBuffer, In Firefox, one must use sendAsBinary with a string
|
||||
if (data.buffer && !xhr.sendAsBinary) {
|
||||
data = data.buffer; // webkit supports sending an ArrayBuffer
|
||||
} else {
|
||||
// encode into a string, this works in FireFox >= 3
|
||||
data = runtime.byteArrayToString(data, "binary");
|
||||
}
|
||||
try {
|
||||
if (xhr.sendAsBinary) {
|
||||
xhr.sendAsBinary(data);
|
||||
} else {
|
||||
xhr.send(data);
|
||||
}
|
||||
} catch (e) {
|
||||
runtime.log("Problem with calling server: " + e + " " + data);
|
||||
cb(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
this.call = call;
|
||||
|
||||
this.getToken = function () {
|
||||
return token;
|
||||
};
|
||||
|
||||
/**
|
||||
* for pre-authenticated use
|
||||
*/
|
||||
this.setToken = function (a_token) {
|
||||
token = a_token;
|
||||
};
|
||||
|
||||
|
||||
/*jslint unparam: true*/
|
||||
/**
|
||||
* @param {!number} timeout in milliseconds
|
||||
* @param {!function(!string)} callback
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.connect = function (timeout, callback) {
|
||||
/*
|
||||
var accumulatedWaitingTime = 0;
|
||||
|
||||
// already tried connecting?
|
||||
if (self.networkStatus() === "ready") {
|
||||
return;
|
||||
}
|
||||
*/
|
||||
callback("ready");
|
||||
};
|
||||
/*jslint unparam: false*/
|
||||
|
||||
/**
|
||||
* @return {!string}
|
||||
*/
|
||||
this.networkStatus = function () {
|
||||
return "ready";
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!string} login
|
||||
* @param {!string} password
|
||||
* @param {function(!Object)} successCb
|
||||
* @param {function(!string)} failCb
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.login = function (login, password, successCb, failCb) {
|
||||
call({
|
||||
command: "login",
|
||||
args: {
|
||||
login: base64.toBase64(login),
|
||||
password: base64.toBase64(password)
|
||||
}
|
||||
}, function(responseData) {
|
||||
var response = /**@type {{token:string}}*/(runtime.fromJson(responseData));
|
||||
runtime.log("Login reply: " + responseData);
|
||||
|
||||
if (response.hasOwnProperty("token")) {
|
||||
token = response.token;
|
||||
runtime.log("Caching token: " + self.getToken());
|
||||
successCb(response);
|
||||
} else {
|
||||
failCb(responseData);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!string} userId
|
||||
* @param {!string} sessionId
|
||||
* @param {!function(!string)} successCb
|
||||
* @param {function()=} failCb
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.joinSession = function (userId, sessionId, successCb, failCb) {
|
||||
call({
|
||||
command: "join_session",
|
||||
args: {
|
||||
user_id: userId,
|
||||
es_id: sessionId
|
||||
}
|
||||
}, function(responseData) {
|
||||
var response = /**@type {{success:string, member_id:string}}*/(runtime.fromJson(responseData));
|
||||
runtime.log("join_session reply: " + responseData);
|
||||
|
||||
if (response.hasOwnProperty("success") && response.success) {
|
||||
successCb(response.member_id);
|
||||
} else {
|
||||
if (failCb) {
|
||||
failCb();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!string} sessionId
|
||||
* @param {!string} memberId
|
||||
* @param {!function()} successCb
|
||||
* @param {function()=} failCb
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.leaveSession = function (sessionId, memberId, successCb, failCb) {
|
||||
call({
|
||||
command: "leave_session",
|
||||
args: {
|
||||
es_id: sessionId,
|
||||
member_id: memberId
|
||||
}
|
||||
}, function(responseData) {
|
||||
var response = /**@type {{success:string, member_id:string}}*/(runtime.fromJson(responseData));
|
||||
runtime.log("leave_session reply: " + responseData);
|
||||
|
||||
if (response.hasOwnProperty("success") && response.success) {
|
||||
successCb();
|
||||
} else {
|
||||
if (failCb) {
|
||||
failCb();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!string} sessionId
|
||||
* @param {!string} memberId
|
||||
* @param {!string} seqHead
|
||||
* @param {function()=} callback
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.writeSessionStateToFile = function(sessionId, memberId, seqHead, fileData, callback) {
|
||||
// code copied from BrowserRuntime.writeFile and adapted
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
function handleResult() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 0 && !xhr.responseText) {// TODO: check makes sense here as well?
|
||||
// for local files there is no difference between missing
|
||||
// and empty files, so empty files are considered as errors
|
||||
runtime.log("File " + args.sessionStateToFileUrl + " is empty.");
|
||||
} else if ((xhr.status >= 200 && xhr.status < 300) ||
|
||||
xhr.status === 0) {
|
||||
// report success
|
||||
runtime.log(null);
|
||||
} else {
|
||||
// report error
|
||||
runtime.log("Status " + String(xhr.status) + ": " +
|
||||
xhr.responseText || xhr.statusText);
|
||||
}
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
// do the request
|
||||
xhr.open('POST', args.sessionStateToFileUrl, true);
|
||||
xhr.setRequestHeader("x-webodf-es_id", sessionId);
|
||||
xhr.setRequestHeader("x-webodf-member_id", memberId);
|
||||
xhr.setRequestHeader("x-webodf-seq_head", seqHead);
|
||||
xhr.onreadystatechange = handleResult;
|
||||
// ArrayBufferView will have an ArrayBuffer property, in WebKit, XHR can send()
|
||||
// an ArrayBuffer, In Firefox, one must use sendAsBinary with a string
|
||||
if (fileData.buffer && !xhr.sendAsBinary) {
|
||||
fileData = fileData.buffer; // webkit supports sending an ArrayBuffer
|
||||
} else {
|
||||
// encode into a string, this works in FireFox >= 3
|
||||
fileData = runtime.byteArrayToString(fileData, "binary");
|
||||
}
|
||||
try {
|
||||
if (xhr.sendAsBinary) {
|
||||
xhr.sendAsBinary(fileData);
|
||||
} else {
|
||||
xhr.send(fileData);
|
||||
}
|
||||
} catch (e) {
|
||||
runtime.log("Problem with calling \"writeSessionStateToFile\" on server");
|
||||
callback(e.message);
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
@ -33,30 +33,29 @@
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global define, document, require, runtime, core, ops */
|
||||
/*global define, document, require, runtime, ops */
|
||||
|
||||
define("webodf/editor/server/pullbox/serverFactory", [
|
||||
"webodf/editor/server/pullbox/sessionList"],
|
||||
function (PullBoxSessionList) {
|
||||
define("webodf/editor/server/pullbox/ServerFactory", [
|
||||
"webodf/editor/server/pullbox/Server",
|
||||
"webodf/editor/server/pullbox/MemberModel",
|
||||
"webodf/editor/server/pullbox/OperationRouter",
|
||||
"webodf/editor/server/pullbox/SessionList"],
|
||||
function (PullBoxServer, PullBoxMemberModel, PullBoxOperationRouter, PullBoxSessionList) {
|
||||
"use strict";
|
||||
|
||||
runtime.loadClass("ops.PullBoxServer");
|
||||
runtime.loadClass("ops.PullBoxMemberModel");
|
||||
runtime.loadClass("ops.PullBoxOperationRouter");
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @implements ServerFactory
|
||||
*/
|
||||
return function PullBoxServerFactory() {
|
||||
this.createServer = function (args) {
|
||||
return new ops.PullBoxServer(args);
|
||||
return new PullBoxServer(args);
|
||||
};
|
||||
this.createOperationRouter = function (sid, mid, server) {
|
||||
return new ops.PullBoxOperationRouter(sid, mid, server);
|
||||
this.createOperationRouter = function (sid, mid, server, odfContainer) {
|
||||
return new PullBoxOperationRouter(sid, mid, server, odfContainer);
|
||||
};
|
||||
this.createMemberModel = function (sid, server) {
|
||||
return new ops.PullBoxMemberModel(sid, server);
|
||||
return new PullBoxMemberModel(sid, server);
|
||||
};
|
||||
this.createSessionList = function (server) {
|
||||
return new PullBoxSessionList(server);
|
@ -35,14 +35,13 @@
|
||||
|
||||
/*global define, ops, runtime */
|
||||
|
||||
define("webodf/editor/server/pullbox/sessionList", [], function () {
|
||||
define("webodf/editor/server/pullbox/SessionList", [], function () {
|
||||
"use strict";
|
||||
|
||||
return function PullBoxSessionList(server) {
|
||||
var cachedSessionData = {},
|
||||
subscribers = [],
|
||||
/** HACK: allow to stop pulling, so that does not mess up the logs
|
||||
* Remove before merging to master */
|
||||
serverPullingTimeoutId = null,
|
||||
pullingActive = true;
|
||||
|
||||
function onSessionData(sessionData) {
|
||||
@ -75,7 +74,7 @@ define("webodf/editor/server/pullbox/sessionList", [], function () {
|
||||
}
|
||||
|
||||
function pullSessionList() {
|
||||
if (!pullingActive) { return; }
|
||||
serverPullingTimeoutId = null;
|
||||
|
||||
server.call({
|
||||
command: "query_sessiondata_list"
|
||||
@ -84,6 +83,11 @@ define("webodf/editor/server/pullbox/sessionList", [], function () {
|
||||
sessionList, i,
|
||||
unupdatedSessions = {};
|
||||
|
||||
// stopped meanwhile? TODO: support for cancelling calls
|
||||
if (!pullingActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
runtime.log("query_sessiondata_list reply: " + responseData);
|
||||
|
||||
if (response.hasOwnProperty("sessiondata_list")) {
|
||||
@ -111,7 +115,7 @@ define("webodf/editor/server/pullbox/sessionList", [], function () {
|
||||
}
|
||||
|
||||
// next update in 5 secs
|
||||
runtime.getWindow().setTimeout(pullSessionList, 5000);
|
||||
serverPullingTimeoutId = runtime.getWindow().setTimeout(pullSessionList, 5000);
|
||||
} else {
|
||||
runtime.log("Meh, sessionlist data broken: " + responseData);
|
||||
}
|
||||
@ -150,8 +154,21 @@ define("webodf/editor/server/pullbox/sessionList", [], function () {
|
||||
subscribers.splice(i,1);
|
||||
};
|
||||
|
||||
this.stopPulling = function () {
|
||||
pullingActive = false;
|
||||
this.setUpdatesEnabled = function (enabled) {
|
||||
if (pullingActive === enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
pullingActive = enabled;
|
||||
if (pullingActive) {
|
||||
pullSessionList();
|
||||
} else {
|
||||
// cancel any running pulling timeout
|
||||
if (serverPullingTimeoutId !== null) {
|
||||
runtime.clearTimeout(serverPullingTimeoutId);
|
||||
serverPullingTimeoutId = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function init() {
|
@ -31,122 +31,180 @@
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global define,document,require */
|
||||
|
||||
define("webodf/editor/widgets", [
|
||||
"dojo/ready",
|
||||
"dijit/MenuItem",
|
||||
"dijit/DropDownMenu",
|
||||
"dijit/form/Button",
|
||||
"dijit/form/DropDownButton",
|
||||
"dijit/Toolbar",
|
||||
"webodf/editor/widgets/simpleStyles",
|
||||
"webodf/editor/widgets/undoRedoMenu",
|
||||
"webodf/editor/widgets/toolbarWidgets/currentStyle",
|
||||
"webodf/editor/widgets/paragraphStylesDialog",
|
||||
"webodf/editor/widgets/zoomSlider"],
|
||||
function (SimpleStyles, UndoRedoMenu, CurrentStyle, ParagraphStylesDialog, ZoomSlider) {
|
||||
function (ready, MenuItem, DropDownMenu, Button, DropDownButton, Toolbar, SimpleStyles, UndoRedoMenu, CurrentStyle, ParagraphStylesDialog, ZoomSlider) {
|
||||
"use strict";
|
||||
|
||||
return function loadWidgets(editorSession, loadOdtFile, saveOdtFile) {
|
||||
var translator = document.translator;
|
||||
return function ToolBarTools(args) {
|
||||
var translator = document.translator,
|
||||
loadOdtFile = args.loadOdtFile,
|
||||
saveOdtFile = args.saveOdtFile,
|
||||
close = args.close,
|
||||
toolbar,
|
||||
loadButton, saveButton, annotateButton, closeButton,
|
||||
formatDropDownMenu, formatMenuButton,
|
||||
paragraphStylesMenuItem, paragraphStylesDialog, simpleStyles, currentStyle,
|
||||
zoomSlider,
|
||||
undoRedoMenu,
|
||||
editorSession;
|
||||
|
||||
// Menubar
|
||||
require([
|
||||
"dojo/ready",
|
||||
"dijit/MenuItem",
|
||||
"dijit/DropDownMenu",
|
||||
"dijit/form/Button",
|
||||
"dijit/form/DropDownButton",
|
||||
"dijit/Toolbar"
|
||||
], function (ready, MenuItem, DropDownMenu, Button, DropDownButton, Toolbar) {
|
||||
ready(function () {
|
||||
var loadButton, saveButton, dropDownMenu, menuButton, paragraphStylesMenuItem, dialog, toolbar, simpleStyles, currentStyle, zoomSlider,
|
||||
undoRedoMenu, annotateButton;
|
||||
this.setEditorSession = function(session) {
|
||||
editorSession = session;
|
||||
if (undoRedoMenu) {
|
||||
undoRedoMenu.setEditorSession(session);
|
||||
}
|
||||
if (simpleStyles) {
|
||||
simpleStyles.setEditorSession(session);
|
||||
}
|
||||
if (currentStyle) {
|
||||
currentStyle.setEditorSession(session);
|
||||
}
|
||||
if (zoomSlider) {
|
||||
zoomSlider.setEditorSession(session);
|
||||
}
|
||||
if (paragraphStylesDialog) {
|
||||
paragraphStylesDialog.setEditorSession(session);
|
||||
}
|
||||
};
|
||||
|
||||
dropDownMenu = new DropDownMenu({});
|
||||
paragraphStylesMenuItem = new MenuItem({
|
||||
label: translator("paragraph_DDD")
|
||||
// init
|
||||
ready(function () {
|
||||
toolbar = new Toolbar({}, "toolbar");
|
||||
|
||||
// Undo/Redo
|
||||
if (args.undoRedoEnabled) {
|
||||
undoRedoMenu = new UndoRedoMenu(function (widget) {
|
||||
widget.placeAt(toolbar);
|
||||
widget.startup();
|
||||
});
|
||||
dropDownMenu.addChild(paragraphStylesMenuItem);
|
||||
|
||||
dialog = new ParagraphStylesDialog(editorSession, function (dialog) {
|
||||
paragraphStylesMenuItem.onClick = function () {
|
||||
dialog.startup();
|
||||
dialog.show();
|
||||
};
|
||||
});
|
||||
|
||||
// Toolbar
|
||||
toolbar = new Toolbar({}, "toolbar");
|
||||
|
||||
if (editorSession.hasUndoManager()) {
|
||||
undoRedoMenu = new UndoRedoMenu(editorSession, function (widget) {
|
||||
widget.placeAt(toolbar);
|
||||
widget.startup();
|
||||
});
|
||||
}
|
||||
undoRedoMenu.setEditorSession(editorSession);
|
||||
}
|
||||
|
||||
if (args.directStylingEnabled) {
|
||||
// Simple Style Selector [B, I, U, S]
|
||||
simpleStyles = new SimpleStyles(editorSession, function (widget) {
|
||||
simpleStyles = new SimpleStyles(function (widget) {
|
||||
widget.placeAt(toolbar);
|
||||
widget.startup();
|
||||
});
|
||||
simpleStyles.setEditorSession(editorSession);
|
||||
}
|
||||
|
||||
// Paragraph Style Selector
|
||||
currentStyle = new CurrentStyle(editorSession, function (widget) {
|
||||
widget.placeAt(toolbar);
|
||||
widget.startup();
|
||||
});
|
||||
// Paragraph Style Selector
|
||||
currentStyle = new CurrentStyle(function (widget) {
|
||||
widget.placeAt(toolbar);
|
||||
widget.startup();
|
||||
});
|
||||
currentStyle.setEditorSession(editorSession);
|
||||
|
||||
// Zoom Level Selector
|
||||
zoomSlider = new ZoomSlider(editorSession, function (widget) {
|
||||
widget.placeAt(toolbar);
|
||||
widget.startup();
|
||||
});
|
||||
// Zoom Level Selector
|
||||
zoomSlider = new ZoomSlider(function (widget) {
|
||||
widget.placeAt(toolbar);
|
||||
widget.startup();
|
||||
});
|
||||
zoomSlider.setEditorSession(editorSession);
|
||||
|
||||
if (loadOdtFile) {
|
||||
loadButton = new Button({
|
||||
label: translator('open'),
|
||||
showLabel: false,
|
||||
iconClass: 'dijitIcon dijitIconFolderOpen',
|
||||
style: {
|
||||
float: 'left'
|
||||
},
|
||||
onClick: function () {
|
||||
loadOdtFile();
|
||||
}
|
||||
});
|
||||
loadButton.placeAt(toolbar);
|
||||
}
|
||||
if (saveOdtFile) {
|
||||
saveButton = new Button({
|
||||
label: translator('save'),
|
||||
showLabel: false,
|
||||
iconClass: 'dijitEditorIcon dijitEditorIconSave',
|
||||
style: {
|
||||
float: 'left'
|
||||
},
|
||||
onClick: function () {
|
||||
saveOdtFile();
|
||||
}
|
||||
});
|
||||
saveButton.placeAt(toolbar);
|
||||
}
|
||||
|
||||
menuButton = new DropDownButton({
|
||||
dropDown: dropDownMenu,
|
||||
label: translator('format'),
|
||||
iconClass: "dijitIconEditTask",
|
||||
// Load
|
||||
if (loadOdtFile) {
|
||||
loadButton = new Button({
|
||||
label: translator('open'),
|
||||
showLabel: false,
|
||||
iconClass: 'dijitIcon dijitIconFolderOpen',
|
||||
style: {
|
||||
float: 'left'
|
||||
},
|
||||
onClick: function () {
|
||||
loadOdtFile();
|
||||
}
|
||||
});
|
||||
menuButton.placeAt(toolbar);
|
||||
loadButton.placeAt(toolbar);
|
||||
}
|
||||
|
||||
// Save
|
||||
if (saveOdtFile) {
|
||||
saveButton = new Button({
|
||||
label: translator('save'),
|
||||
showLabel: false,
|
||||
iconClass: 'dijitEditorIcon dijitEditorIconSave',
|
||||
style: {
|
||||
float: 'left'
|
||||
},
|
||||
onClick: function () {
|
||||
saveOdtFile();
|
||||
}
|
||||
});
|
||||
saveButton.placeAt(toolbar);
|
||||
}
|
||||
|
||||
// Format menu
|
||||
formatDropDownMenu = new DropDownMenu({});
|
||||
paragraphStylesMenuItem = new MenuItem({
|
||||
label: translator("paragraph_DDD")
|
||||
});
|
||||
formatDropDownMenu.addChild(paragraphStylesMenuItem);
|
||||
|
||||
paragraphStylesDialog = new ParagraphStylesDialog(function (dialog) {
|
||||
paragraphStylesMenuItem.onClick = function () {
|
||||
if (editorSession) {
|
||||
dialog.startup();
|
||||
dialog.show();
|
||||
}
|
||||
};
|
||||
});
|
||||
paragraphStylesDialog.setEditorSession(editorSession);
|
||||
|
||||
formatMenuButton = new DropDownButton({
|
||||
dropDown: formatDropDownMenu,
|
||||
label: translator('format'),
|
||||
iconClass: "dijitIconEditTask",
|
||||
style: {
|
||||
float: 'left'
|
||||
}
|
||||
});
|
||||
formatMenuButton.placeAt(toolbar);
|
||||
|
||||
// Add annotation
|
||||
if (args.annotationsEnabled) {
|
||||
annotateButton = new Button({
|
||||
label: translator('annotate'),
|
||||
showLabel: false,
|
||||
iconClass: 'dijitIconBookmark',
|
||||
onClick: function () {
|
||||
editorSession.addAnnotation();
|
||||
if (editorSession) {
|
||||
editorSession.addAnnotation();
|
||||
}
|
||||
}
|
||||
});
|
||||
annotateButton.placeAt(toolbar);
|
||||
});
|
||||
}
|
||||
|
||||
if (close) {
|
||||
closeButton = new Button({
|
||||
label: translator('close'),
|
||||
showLabel: false,
|
||||
iconClass: 'dijitEditorIcon dijitEditorIconCancel',
|
||||
style: {
|
||||
float: 'right'
|
||||
},
|
||||
onClick: function () {
|
||||
close();
|
||||
}
|
||||
});
|
||||
closeButton.placeAt(toolbar);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
@ -30,14 +32,16 @@
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global runtime,core,define,require,document,dijit */
|
||||
|
||||
runtime.loadClass("core.CSSUnits");
|
||||
|
||||
define("webodf/editor/widgets/dialogWidgets/alignmentPane", [], function () {
|
||||
"use strict";
|
||||
var AlignmentPane = function (editorSession, callback) {
|
||||
var AlignmentPane = function (callback) {
|
||||
var self = this,
|
||||
editorSession,
|
||||
contentPane,
|
||||
form;
|
||||
|
||||
@ -83,6 +87,10 @@ define("webodf/editor/widgets/dialogWidgets/alignmentPane", [], function () {
|
||||
}
|
||||
};
|
||||
|
||||
this.setEditorSession = function(session) {
|
||||
editorSession = session;
|
||||
};
|
||||
|
||||
function init(cb) {
|
||||
require([
|
||||
"dojo",
|
||||
|
@ -1,5 +1,7 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
@ -30,12 +32,15 @@
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global runtime,define,require,document,dijit */
|
||||
|
||||
define("webodf/editor/widgets/dialogWidgets/fontEffectsPane", [], function () {
|
||||
"use strict";
|
||||
|
||||
var FontEffectsPane = function (editorSession, callback) {
|
||||
var FontEffectsPane = function (callback) {
|
||||
var self = this,
|
||||
editorSession,
|
||||
contentPane,
|
||||
form,
|
||||
preview,
|
||||
@ -148,10 +153,11 @@ define("webodf/editor/widgets/dialogWidgets/fontEffectsPane", [], function () {
|
||||
backgroundColorTB.set('value', value);
|
||||
};
|
||||
|
||||
fontPicker = new FontPicker(editorSession, function (picker) {
|
||||
fontPicker = new FontPicker(function (picker) {
|
||||
picker.widget().startup();
|
||||
document.getElementById('fontPicker').appendChild(picker.widget().domNode);
|
||||
picker.widget().name = 'fontName';
|
||||
picker.setEditorSession(editorSession);
|
||||
});
|
||||
|
||||
// Automatically update preview when selections change
|
||||
@ -184,6 +190,13 @@ define("webodf/editor/widgets/dialogWidgets/fontEffectsPane", [], function () {
|
||||
});
|
||||
}
|
||||
|
||||
this.setEditorSession = function(session) {
|
||||
editorSession = session;
|
||||
if (fontPicker) {
|
||||
fontPicker.setEditorSession(editorSession);
|
||||
}
|
||||
};
|
||||
|
||||
init(function () {
|
||||
return callback(self);
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Copyright (C) 2012 KO GmbH <copyright@kogmbh.com>
|
||||
* @license
|
||||
* Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
@ -31,7 +32,9 @@
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global define,require */
|
||||
|
||||
define("webodf/editor/widgets/paragraphStyles",
|
||||
["webodf/editor/EditorSession"],
|
||||
|
||||
@ -40,8 +43,9 @@ define("webodf/editor/widgets/paragraphStyles",
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
var ParagraphStyles = function (editorSession, callback) {
|
||||
var ParagraphStyles = function (callback) {
|
||||
var self = this,
|
||||
editorSession,
|
||||
select;
|
||||
|
||||
this.widget = function () {
|
||||
@ -61,9 +65,14 @@ define("webodf/editor/widgets/paragraphStyles",
|
||||
this.onRemove = null;
|
||||
|
||||
function populateStyles() {
|
||||
var i, availableStyles, selectionList;
|
||||
var i, selectionList, availableStyles;
|
||||
|
||||
if (! select) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectionList = [];
|
||||
availableStyles = editorSession.getAvailableParagraphStyles();
|
||||
availableStyles = editorSession ? editorSession.getAvailableParagraphStyles() : [];
|
||||
|
||||
for (i = 0; i < availableStyles.length; i += 1) {
|
||||
selectionList.push({
|
||||
@ -76,10 +85,34 @@ define("webodf/editor/widgets/paragraphStyles",
|
||||
select.addOption(selectionList);
|
||||
}
|
||||
|
||||
function addStyle(newStyleName) {
|
||||
var stylens = "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
|
||||
newStyleElement = editorSession.getParagraphStyleElement(newStyleName);
|
||||
|
||||
if (select) {
|
||||
select.addOption({
|
||||
value: newStyleName,
|
||||
label: newStyleElement.getAttributeNS(stylens, 'display-name')
|
||||
});
|
||||
}
|
||||
|
||||
if (self.onAdd) {
|
||||
self.onAdd(newStyleName);
|
||||
}
|
||||
}
|
||||
|
||||
function removeStyle(styleName) {
|
||||
if (select) {
|
||||
select.removeOption(styleName);
|
||||
}
|
||||
|
||||
if (self.onRemove) {
|
||||
self.onRemove(styleName);
|
||||
}
|
||||
}
|
||||
|
||||
function init(cb) {
|
||||
require(["dijit/form/Select"], function (Select) {
|
||||
var stylens = "urn:oasis:names:tc:opendocument:xmlns:style:1.0";
|
||||
|
||||
select = new Select({
|
||||
name: 'ParagraphStyles',
|
||||
maxHeight: 200,
|
||||
@ -90,29 +123,24 @@ define("webodf/editor/widgets/paragraphStyles",
|
||||
|
||||
populateStyles();
|
||||
|
||||
editorSession.subscribe(EditorSession.signalStyleCreated, function (newStyleName) {
|
||||
var newStyleElement = editorSession.getParagraphStyleElement(newStyleName);
|
||||
select.addOption({
|
||||
value: newStyleName,
|
||||
label: newStyleElement.getAttributeNS(stylens, 'display-name')
|
||||
});
|
||||
|
||||
if (self.onAdd) {
|
||||
self.onAdd(newStyleName);
|
||||
}
|
||||
});
|
||||
|
||||
editorSession.subscribe(EditorSession.signalStyleDeleted, function (styleName) {
|
||||
select.removeOption(styleName);
|
||||
|
||||
if (self.onRemove) {
|
||||
self.onRemove(styleName);
|
||||
}
|
||||
});
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
|
||||
this.setEditorSession = function(session) {
|
||||
if (editorSession) {
|
||||
editorSession.unsubscribe(EditorSession.signalStyleCreated, addStyle);
|
||||
editorSession.unsubscribe(EditorSession.signalStyleDeleted, removeStyle);
|
||||
}
|
||||
editorSession = session;
|
||||
if (editorSession) {
|
||||
editorSession.subscribe(EditorSession.signalStyleCreated, addStyle);
|
||||
editorSession.subscribe(EditorSession.signalStyleDeleted, removeStyle);
|
||||
populateStyles();
|
||||
}
|
||||
};
|
||||
|
||||
// init
|
||||
init(function () {
|
||||
return callback(self);
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Copyright (C) 2012 KO GmbH <copyright@kogmbh.com>
|
||||
* @license
|
||||
* Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
@ -31,278 +32,300 @@
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global define,require,document,dojo,dijit */
|
||||
|
||||
define("webodf/editor/widgets/paragraphStylesDialog", [], function () {
|
||||
"use strict";
|
||||
function makeWidget(editorSession, callback) {
|
||||
require([
|
||||
"dijit/Dialog",
|
||||
"dijit/TooltipDialog",
|
||||
"dijit/popup",
|
||||
"dijit/layout/TabContainer",
|
||||
"dijit/layout/ContentPane",
|
||||
"dijit/form/Button",
|
||||
"dijit/form/DropDownButton",
|
||||
"dijit/form/RadioButton"], function (Dialog, TooltipDialog, popup, TabContainer, ContentPane, Button, DropDownButton, RadioButton) {
|
||||
var i,
|
||||
dialog,
|
||||
translator = document.translator,
|
||||
tabContainer,
|
||||
alignmentPane,
|
||||
fontEffectsPane,
|
||||
stylePicker,
|
||||
flowPane,
|
||||
numberingPane,
|
||||
tabsPane,
|
||||
capsPane,
|
||||
bordersPane,
|
||||
backgroundPane,
|
||||
indentsPane,
|
||||
actionBar,
|
||||
okButton,
|
||||
cancelButton,
|
||||
cloneButton,
|
||||
deleteButton,
|
||||
cloneTooltip,
|
||||
cloneDropDown,
|
||||
newStyleName = null,
|
||||
/**
|
||||
* Mapping of the properties from edit pane properties to the attributes of style:text-properties
|
||||
* @const@type{Array.<!{propertyName:string,attributeName:string,unit:string}>}
|
||||
*/
|
||||
textPropertyMapping = [
|
||||
{
|
||||
propertyName: 'fontSize',
|
||||
attributeName: 'fo:font-size',
|
||||
unit: 'pt'
|
||||
}, {
|
||||
propertyName: 'fontName',
|
||||
attributeName: 'style:font-name'
|
||||
}, {
|
||||
propertyName: 'color',
|
||||
attributeName: 'fo:color'
|
||||
}, {
|
||||
propertyName: 'backgroundColor',
|
||||
attributeName: 'fo:background-color'
|
||||
}, {
|
||||
propertyName: 'fontWeight',
|
||||
attributeName: 'fo:font-weight'
|
||||
}, {
|
||||
propertyName: 'fontStyle',
|
||||
attributeName: 'fo:font-style'
|
||||
}, {
|
||||
propertyName: 'underline',
|
||||
attributeName: 'style:text-underline-style'
|
||||
}, {
|
||||
propertyName: 'strikethrough',
|
||||
attributeName: 'style:text-line-through-style'
|
||||
}],
|
||||
/**
|
||||
* Mapping of the properties from edit pane properties to the attributes of style:paragraph-properties
|
||||
* @const@type{Array.<!{propertyName:string,attributeName:string,unit:string}>}
|
||||
*/
|
||||
paragraphPropertyMapping = [
|
||||
{
|
||||
propertyName: 'topMargin',
|
||||
attributeName: 'fo:margin-top',
|
||||
unit: 'mm'
|
||||
}, {
|
||||
propertyName: 'bottomMargin',
|
||||
attributeName: 'fo:margin-bottom',
|
||||
unit: 'mm'
|
||||
}, {
|
||||
propertyName: 'leftMargin',
|
||||
attributeName: 'fo:margin-left',
|
||||
unit: 'mm'
|
||||
}, {
|
||||
propertyName: 'rightMargin',
|
||||
attributeName: 'fo:margin-right',
|
||||
unit: 'mm'
|
||||
}, {
|
||||
propertyName: 'textAlign',
|
||||
attributeName: 'fo:text-align'
|
||||
}];
|
||||
return function ParagraphStylesDialog(callback) {
|
||||
var editorSession,
|
||||
dialog,
|
||||
stylePicker, alignmentPane, fontEffectsPane;
|
||||
|
||||
/**
|
||||
* Sets attributes of a node by the properties of the object properties,
|
||||
* based on the mapping defined in propertyMapping.
|
||||
* @param {!Object} properties
|
||||
* @param {!Array.<!{propertyName:string,attributeName:string,unit:string}>} propertyMapping
|
||||
* @return {undefined}
|
||||
*/
|
||||
function mappedProperties(properties, propertyMapping) {
|
||||
var i, m, value,
|
||||
result = {};
|
||||
for (i = 0; i < propertyMapping.length; i += 1) {
|
||||
m = propertyMapping[i];
|
||||
value = properties[m.propertyName];
|
||||
// Set a value as the attribute of a node, if that value is defined.
|
||||
// If there is a unit specified, it is suffixed to the value.
|
||||
if (value !== undefined) {
|
||||
result[m.attributeName] = (m.unit !== undefined) ? value + m.unit : value;
|
||||
function makeWidget(callback) {
|
||||
require([
|
||||
"dijit/Dialog",
|
||||
"dijit/TooltipDialog",
|
||||
"dijit/popup",
|
||||
"dijit/layout/TabContainer",
|
||||
"dijit/layout/ContentPane",
|
||||
"dijit/form/Button",
|
||||
"dijit/form/DropDownButton",
|
||||
"dijit/form/RadioButton"], function (Dialog, TooltipDialog, popup, TabContainer, ContentPane, Button, DropDownButton, RadioButton) {
|
||||
var i,
|
||||
translator = document.translator,
|
||||
tabContainer,
|
||||
flowPane,
|
||||
numberingPane,
|
||||
tabsPane,
|
||||
capsPane,
|
||||
bordersPane,
|
||||
backgroundPane,
|
||||
indentsPane,
|
||||
actionBar,
|
||||
okButton,
|
||||
cancelButton,
|
||||
cloneButton,
|
||||
deleteButton,
|
||||
cloneTooltip,
|
||||
cloneDropDown,
|
||||
newStyleName = null,
|
||||
/**
|
||||
* Mapping of the properties from edit pane properties to the attributes of style:text-properties
|
||||
* @const@type{Array.<!{propertyName:string,attributeName:string,unit:string}>}
|
||||
*/
|
||||
textPropertyMapping = [
|
||||
{
|
||||
propertyName: 'fontSize',
|
||||
attributeName: 'fo:font-size',
|
||||
unit: 'pt'
|
||||
}, {
|
||||
propertyName: 'fontName',
|
||||
attributeName: 'style:font-name'
|
||||
}, {
|
||||
propertyName: 'color',
|
||||
attributeName: 'fo:color'
|
||||
}, {
|
||||
propertyName: 'backgroundColor',
|
||||
attributeName: 'fo:background-color'
|
||||
}, {
|
||||
propertyName: 'fontWeight',
|
||||
attributeName: 'fo:font-weight'
|
||||
}, {
|
||||
propertyName: 'fontStyle',
|
||||
attributeName: 'fo:font-style'
|
||||
}, {
|
||||
propertyName: 'underline',
|
||||
attributeName: 'style:text-underline-style'
|
||||
}, {
|
||||
propertyName: 'strikethrough',
|
||||
attributeName: 'style:text-line-through-style'
|
||||
}],
|
||||
/**
|
||||
* Mapping of the properties from edit pane properties to the attributes of style:paragraph-properties
|
||||
* @const@type{Array.<!{propertyName:string,attributeName:string,unit:string}>}
|
||||
*/
|
||||
paragraphPropertyMapping = [
|
||||
{
|
||||
propertyName: 'topMargin',
|
||||
attributeName: 'fo:margin-top',
|
||||
unit: 'mm'
|
||||
}, {
|
||||
propertyName: 'bottomMargin',
|
||||
attributeName: 'fo:margin-bottom',
|
||||
unit: 'mm'
|
||||
}, {
|
||||
propertyName: 'leftMargin',
|
||||
attributeName: 'fo:margin-left',
|
||||
unit: 'mm'
|
||||
}, {
|
||||
propertyName: 'rightMargin',
|
||||
attributeName: 'fo:margin-right',
|
||||
unit: 'mm'
|
||||
}, {
|
||||
propertyName: 'textAlign',
|
||||
attributeName: 'fo:text-align'
|
||||
}];
|
||||
|
||||
/**
|
||||
* Sets attributes of a node by the properties of the object properties,
|
||||
* based on the mapping defined in propertyMapping.
|
||||
* @param {!Object} properties
|
||||
* @param {!Array.<!{propertyName:string,attributeName:string,unit:string}>} propertyMapping
|
||||
* @return {undefined}
|
||||
*/
|
||||
function mappedProperties(properties, propertyMapping) {
|
||||
var i, m, value,
|
||||
result = {};
|
||||
for (i = 0; i < propertyMapping.length; i += 1) {
|
||||
m = propertyMapping[i];
|
||||
value = properties[m.propertyName];
|
||||
// Set a value as the attribute of a node, if that value is defined.
|
||||
// If there is a unit specified, it is suffixed to the value.
|
||||
if (value !== undefined) {
|
||||
result[m.attributeName] = (m.unit !== undefined) ? value + m.unit : value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function accept() {
|
||||
editorSession.updateParagraphStyle(stylePicker.value(), {
|
||||
"style:paragraph-properties": mappedProperties(alignmentPane.value(), paragraphPropertyMapping),
|
||||
"style:text-properties": mappedProperties(fontEffectsPane.value(), textPropertyMapping)
|
||||
function accept() {
|
||||
editorSession.updateParagraphStyle(stylePicker.value(), {
|
||||
"style:paragraph-properties": mappedProperties(alignmentPane.value(), paragraphPropertyMapping),
|
||||
"style:text-properties": mappedProperties(fontEffectsPane.value(), textPropertyMapping)
|
||||
});
|
||||
|
||||
dialog.hide();
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
dialog.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and enqueues a paragraph-style cloning operation.
|
||||
* Remembers the id of the created style in newStyleName, so the
|
||||
* style picker can be set to it, once the operation has been applied.
|
||||
* @param {!string} styleName id of the style to clone
|
||||
* @param {!string} newStyleDisplayName display name of the new style
|
||||
*/
|
||||
function cloneStyle(styleName, newStyleDisplayName) {
|
||||
newStyleName = editorSession.cloneParagraphStyle(styleName, newStyleDisplayName);
|
||||
}
|
||||
|
||||
function deleteStyle(styleName) {
|
||||
editorSession.deleteStyle(styleName);
|
||||
}
|
||||
// Dialog
|
||||
dialog = new Dialog({
|
||||
title: translator("paragraphStyles")
|
||||
});
|
||||
|
||||
dialog.hide();
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
dialog.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and enqueues a paragraph-style cloning operation.
|
||||
* Remembers the id of the created style in newStyleName, so the
|
||||
* style picker can be set to it, once the operation has been applied.
|
||||
* @param {!string} styleName id of the style to clone
|
||||
* @param {!string} newStyleDisplayName display name of the new style
|
||||
*/
|
||||
function cloneStyle(styleName, newStyleDisplayName) {
|
||||
newStyleName = editorSession.cloneParagraphStyle(styleName, newStyleDisplayName);
|
||||
}
|
||||
|
||||
function deleteStyle(styleName) {
|
||||
editorSession.deleteStyle(styleName);
|
||||
}
|
||||
// Dialog
|
||||
dialog = new Dialog({
|
||||
title: translator("paragraphStyles")
|
||||
});
|
||||
|
||||
cloneTooltip = new TooltipDialog({
|
||||
content:
|
||||
'<h2 style="margin: 0;">Clone this style</h2><br/>' +
|
||||
'<label for="name">New name </label><input data-dojo-type="dijit/form/TextBox" id="name" name="name"><br/><br/>',
|
||||
style: "width: 300px;"
|
||||
});
|
||||
cloneButton = new Button({
|
||||
label: 'Create',
|
||||
onClick: function () {
|
||||
cloneStyle(stylePicker.value(), cloneTooltip.get('value').name);
|
||||
cloneTooltip.reset();
|
||||
popup.close(cloneTooltip);
|
||||
}
|
||||
});
|
||||
cloneTooltip.addChild(cloneButton);
|
||||
cloneDropDown = new DropDownButton({
|
||||
label: 'Clone',
|
||||
showLabel: false,
|
||||
iconClass: 'dijitEditorIcon dijitEditorIconCopy',
|
||||
dropDown: cloneTooltip,
|
||||
style: "float: right; margin-bottom: 5px;"
|
||||
});
|
||||
dialog.addChild(cloneDropDown, 1);
|
||||
|
||||
deleteButton = new Button({
|
||||
label: 'Delete',
|
||||
showLabel: false,
|
||||
iconClass: 'dijitEditorIcon dijitEditorIconDelete',
|
||||
style: "float: right; margin-bottom: 5px;",
|
||||
onClick: function () {
|
||||
deleteStyle(stylePicker.value());
|
||||
}
|
||||
});
|
||||
dialog.addChild(deleteButton, 2);
|
||||
|
||||
// Tab Container
|
||||
tabContainer = new TabContainer({
|
||||
style: "height: 100%; width: 100%;"
|
||||
});
|
||||
dialog.addChild(tabContainer, 3);
|
||||
|
||||
actionBar = dojo.create("div", {
|
||||
"class": "dijitDialogPaneActionBar"
|
||||
});
|
||||
okButton = new dijit.form.Button({
|
||||
label: translator("ok"),
|
||||
onClick: accept
|
||||
}).placeAt(actionBar);
|
||||
cancelButton = new dijit.form.Button({
|
||||
label: translator("cancel"),
|
||||
onClick: cancel
|
||||
}).placeAt(actionBar);
|
||||
dialog.domNode.appendChild(actionBar);
|
||||
|
||||
|
||||
require([
|
||||
"webodf/editor/widgets/paragraphStyles",
|
||||
"webodf/editor/widgets/dialogWidgets/alignmentPane",
|
||||
"webodf/editor/widgets/dialogWidgets/fontEffectsPane"
|
||||
], function (ParagraphStyles, AlignmentPane, FontEffectsPane) {
|
||||
var p, a, f;
|
||||
|
||||
function openStyle(value) {
|
||||
alignmentPane.setStyle(value);
|
||||
fontEffectsPane.setStyle(value);
|
||||
if (editorSession.isStyleUsed(editorSession.getParagraphStyleElement(value))) {
|
||||
deleteButton.domNode.style.display = 'none';
|
||||
} else {
|
||||
deleteButton.domNode.style.display = 'block';
|
||||
cloneTooltip = new TooltipDialog({
|
||||
content:
|
||||
'<h2 style="margin: 0;">'+translator("cloneThisStyle")+'</h2><br/>' +
|
||||
'<label for="name">'+translator("newName_C")+'</label> <input data-dojo-type="dijit/form/TextBox" id="name" name="name"><br/><br/>',
|
||||
style: "width: 300px;"
|
||||
});
|
||||
cloneButton = new Button({
|
||||
label: translator("create"),
|
||||
onClick: function () {
|
||||
cloneStyle(stylePicker.value(), cloneTooltip.get('value').name);
|
||||
cloneTooltip.reset();
|
||||
popup.close(cloneTooltip);
|
||||
}
|
||||
}
|
||||
});
|
||||
cloneTooltip.addChild(cloneButton);
|
||||
cloneDropDown = new DropDownButton({
|
||||
label: translator("clone"),
|
||||
showLabel: false,
|
||||
iconClass: 'dijitEditorIcon dijitEditorIconCopy',
|
||||
dropDown: cloneTooltip,
|
||||
style: "float: right; margin-bottom: 5px;"
|
||||
});
|
||||
dialog.addChild(cloneDropDown, 1);
|
||||
|
||||
p = new ParagraphStyles(editorSession, function (paragraphStyles) {
|
||||
stylePicker = paragraphStyles;
|
||||
stylePicker.widget().startup();
|
||||
stylePicker.widget().domNode.style.float = "left";
|
||||
stylePicker.widget().domNode.style.width = "350px";
|
||||
stylePicker.widget().domNode.style.marginTop = "5px";
|
||||
dialog.addChild(stylePicker.widget(), 0);
|
||||
deleteButton = new Button({
|
||||
label: translator("delete"),
|
||||
showLabel: false,
|
||||
iconClass: 'dijitEditorIcon dijitEditorIconDelete',
|
||||
style: "float: right; margin-bottom: 5px;",
|
||||
onClick: function () {
|
||||
deleteStyle(stylePicker.value());
|
||||
}
|
||||
});
|
||||
dialog.addChild(deleteButton, 2);
|
||||
|
||||
stylePicker.onAdd = function (name) {
|
||||
if (newStyleName === name) {
|
||||
stylePicker.setValue(name);
|
||||
newStyleName = null; // reset 'flag' name
|
||||
// Tab Container
|
||||
tabContainer = new TabContainer({
|
||||
style: "height: 100%; width: 100%;"
|
||||
});
|
||||
dialog.addChild(tabContainer, 3);
|
||||
|
||||
actionBar = dojo.create("div", {
|
||||
"class": "dijitDialogPaneActionBar"
|
||||
});
|
||||
okButton = new dijit.form.Button({
|
||||
label: translator("ok"),
|
||||
onClick: accept
|
||||
}).placeAt(actionBar);
|
||||
cancelButton = new dijit.form.Button({
|
||||
label: translator("cancel"),
|
||||
onClick: cancel
|
||||
}).placeAt(actionBar);
|
||||
dialog.domNode.appendChild(actionBar);
|
||||
|
||||
|
||||
require([
|
||||
"webodf/editor/widgets/paragraphStyles",
|
||||
"webodf/editor/widgets/dialogWidgets/alignmentPane",
|
||||
"webodf/editor/widgets/dialogWidgets/fontEffectsPane"
|
||||
], function (ParagraphStyles, AlignmentPane, FontEffectsPane) {
|
||||
var p, a, f;
|
||||
|
||||
function openStyle(value) {
|
||||
alignmentPane.setStyle(value);
|
||||
fontEffectsPane.setStyle(value);
|
||||
if (editorSession.isStyleUsed(editorSession.getParagraphStyleElement(value))) {
|
||||
deleteButton.domNode.style.display = 'none';
|
||||
} else {
|
||||
deleteButton.domNode.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
p = new ParagraphStyles(function (paragraphStyles) {
|
||||
stylePicker = paragraphStyles;
|
||||
stylePicker.widget().startup();
|
||||
stylePicker.widget().domNode.style.float = "left";
|
||||
stylePicker.widget().domNode.style.width = "350px";
|
||||
stylePicker.widget().domNode.style.marginTop = "5px";
|
||||
dialog.addChild(stylePicker.widget(), 0);
|
||||
|
||||
stylePicker.onAdd = function (name) {
|
||||
if (newStyleName === name) {
|
||||
stylePicker.setValue(name);
|
||||
newStyleName = null; // reset 'flag' name
|
||||
}
|
||||
};
|
||||
|
||||
stylePicker.onRemove = function (name) {
|
||||
// Set the first style name as current
|
||||
stylePicker.setValue(stylePicker.widget().getOptions(0));
|
||||
};
|
||||
|
||||
stylePicker.widget().onChange = openStyle;
|
||||
stylePicker.setEditorSession(editorSession);
|
||||
});
|
||||
a = new AlignmentPane(function (pane) {
|
||||
alignmentPane = pane;
|
||||
alignmentPane.widget().startup();
|
||||
tabContainer.addChild(alignmentPane.widget());
|
||||
alignmentPane.setEditorSession(editorSession);
|
||||
});
|
||||
f = new FontEffectsPane(function (pane) {
|
||||
fontEffectsPane = pane;
|
||||
fontEffectsPane.widget().startup();
|
||||
tabContainer.addChild(fontEffectsPane.widget());
|
||||
fontEffectsPane.setEditorSession(editorSession);
|
||||
});
|
||||
|
||||
dialog.onShow = function () {
|
||||
var currentStyle = editorSession.getCurrentParagraphStyle();
|
||||
// setting the stylepicker value if the style name is the same
|
||||
// will not trigger onChange, so specifically open the style in
|
||||
// the panes.
|
||||
if (stylePicker.value() === currentStyle) {
|
||||
openStyle(currentStyle);
|
||||
} else {
|
||||
stylePicker.setValue(currentStyle);
|
||||
}
|
||||
};
|
||||
|
||||
stylePicker.onRemove = function (name) {
|
||||
// Set the first style name as current
|
||||
stylePicker.setValue(stylePicker.widget().getOptions(0));
|
||||
};
|
||||
|
||||
stylePicker.widget().onChange = openStyle;
|
||||
});
|
||||
a = new AlignmentPane(editorSession, function (pane) {
|
||||
alignmentPane = pane;
|
||||
alignmentPane.widget().startup();
|
||||
tabContainer.addChild(alignmentPane.widget());
|
||||
});
|
||||
f = new FontEffectsPane(editorSession, function (pane) {
|
||||
fontEffectsPane = pane;
|
||||
fontEffectsPane.widget().startup();
|
||||
tabContainer.addChild(fontEffectsPane.widget());
|
||||
});
|
||||
|
||||
dialog.onShow = function () {
|
||||
var currentStyle = editorSession.getCurrentParagraphStyle();
|
||||
// setting the stylepicker value if the style name is the same
|
||||
// will not trigger onChange, so specifically open the style in
|
||||
// the panes.
|
||||
if (stylePicker.value() === currentStyle) {
|
||||
openStyle(currentStyle);
|
||||
} else {
|
||||
stylePicker.setValue(currentStyle);
|
||||
}
|
||||
};
|
||||
tabContainer.startup();
|
||||
|
||||
return callback(dialog);
|
||||
});
|
||||
}
|
||||
|
||||
tabContainer.startup();
|
||||
this.setEditorSession = function(session) {
|
||||
editorSession = session;
|
||||
if (stylePicker) {
|
||||
stylePicker.setEditorSession(session);
|
||||
}
|
||||
if (alignmentPane) {
|
||||
alignmentPane.setEditorSession(session);
|
||||
}
|
||||
if (fontEffectsPane) {
|
||||
fontEffectsPane.setEditorSession(session);
|
||||
}
|
||||
if (!editorSession && dialog) { // TODO: check show state
|
||||
dialog.hide();
|
||||
}
|
||||
};
|
||||
|
||||
return callback(dialog);
|
||||
});
|
||||
}
|
||||
|
||||
return function ParagraphStylesDialog(editorSession, callback) {
|
||||
makeWidget(editorSession, function (dialog) {
|
||||
// init
|
||||
makeWidget(function (dialog) {
|
||||
return callback(dialog);
|
||||
});
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Copyright (C) 2012 KO GmbH <copyright@kogmbh.com>
|
||||
* @license
|
||||
* Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
@ -31,113 +32,145 @@
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global define,require,document */
|
||||
|
||||
define("webodf/editor/widgets/simpleStyles",
|
||||
["webodf/editor/EditorSession"],
|
||||
|
||||
function (EditorSession) {
|
||||
"use strict";
|
||||
|
||||
function makeWidget(editorSession, callback) {
|
||||
require(["dijit/form/ToggleButton"], function (ToggleButton) {
|
||||
var i,
|
||||
widget = {},
|
||||
boldButton,
|
||||
italicButton,
|
||||
underlineButton,
|
||||
strikethroughButton;
|
||||
return function SimpleStyles(callback) {
|
||||
var editorSession,
|
||||
boldButton,
|
||||
italicButton,
|
||||
underlineButton,
|
||||
strikethroughButton;
|
||||
|
||||
boldButton = new ToggleButton({
|
||||
label: document.translator('bold'),
|
||||
showLabel: false,
|
||||
checked: false,
|
||||
iconClass: "dijitEditorIcon dijitEditorIconBold",
|
||||
onChange: function (checked) {
|
||||
var value = checked ? 'bold' : 'normal';
|
||||
editorSession.formatSelection({
|
||||
'style:text-properties': {
|
||||
'fo:font-weight' : value
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
function makeWidget(callback) {
|
||||
require(["dijit/form/ToggleButton"], function (ToggleButton) {
|
||||
var i,
|
||||
widget = {};
|
||||
|
||||
italicButton = new ToggleButton({
|
||||
label: document.translator('italic'),
|
||||
showLabel: false,
|
||||
checked: false,
|
||||
iconClass: "dijitEditorIcon dijitEditorIconItalic",
|
||||
onChange: function (checked) {
|
||||
var value = checked ? 'italic' : 'normal';
|
||||
editorSession.formatSelection({
|
||||
'style:text-properties': {
|
||||
'fo:font-style' : value
|
||||
boldButton = new ToggleButton({
|
||||
label: document.translator('bold'),
|
||||
showLabel: false,
|
||||
checked: editorSession ? editorSession.isBold(): false,
|
||||
iconClass: "dijitEditorIcon dijitEditorIconBold",
|
||||
onChange: function (checked) {
|
||||
var value = checked ? 'bold' : 'normal';
|
||||
if (editorSession) {
|
||||
editorSession.formatSelection({
|
||||
'style:text-properties': {
|
||||
'fo:font-weight' : value
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
underlineButton = new ToggleButton({
|
||||
label: document.translator('underline'),
|
||||
showLabel: false,
|
||||
checked: false,
|
||||
iconClass: "dijitEditorIcon dijitEditorIconUnderline",
|
||||
onChange: function (checked) {
|
||||
var value = checked ? 'solid' : 'none';
|
||||
editorSession.formatSelection({
|
||||
'style:text-properties': {
|
||||
'style:text-underline-style' : value
|
||||
}
|
||||
});
|
||||
|
||||
italicButton = new ToggleButton({
|
||||
label: document.translator('italic'),
|
||||
showLabel: false,
|
||||
checked: editorSession ? editorSession.isItalic(): false,
|
||||
iconClass: "dijitEditorIcon dijitEditorIconItalic",
|
||||
onChange: function (checked) {
|
||||
var value = checked ? 'italic' : 'normal';
|
||||
if (editorSession) {
|
||||
editorSession.formatSelection({
|
||||
'style:text-properties': {
|
||||
'fo:font-style' : value
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
strikethroughButton = new ToggleButton({
|
||||
label: document.translator('strikethrough'),
|
||||
showLabel: false,
|
||||
checked: false,
|
||||
iconClass: "dijitEditorIcon dijitEditorIconStrikethrough",
|
||||
onChange: function (checked) {
|
||||
var value = checked ? 'solid' : 'none';
|
||||
editorSession.formatSelection({
|
||||
'style:text-properties': {
|
||||
'style:text-line-through-style' : value
|
||||
}
|
||||
});
|
||||
underlineButton = new ToggleButton({
|
||||
label: document.translator('underline'),
|
||||
showLabel: false,
|
||||
checked: editorSession ? editorSession.hasUnderline(): false,
|
||||
iconClass: "dijitEditorIcon dijitEditorIconUnderline",
|
||||
onChange: function (checked) {
|
||||
var value = checked ? 'solid' : 'none';
|
||||
if (editorSession) {
|
||||
editorSession.formatSelection({
|
||||
'style:text-properties': {
|
||||
'style:text-underline-style' : value
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
strikethroughButton = new ToggleButton({
|
||||
label: document.translator('strikethrough'),
|
||||
showLabel: false,
|
||||
checked: editorSession ? editorSession.hasStrikeThrough(): false,
|
||||
iconClass: "dijitEditorIcon dijitEditorIconStrikethrough",
|
||||
onChange: function (checked) {
|
||||
var value = checked ? 'solid' : 'none';
|
||||
if (editorSession) {
|
||||
editorSession.formatSelection({
|
||||
'style:text-properties': {
|
||||
'style:text-line-through-style' : value
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
widget.children = [boldButton, italicButton, underlineButton, strikethroughButton];
|
||||
widget.startup = function () {
|
||||
widget.children.forEach(function (element) {
|
||||
element.startup();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
widget.placeAt = function (container) {
|
||||
widget.children.forEach(function (element) {
|
||||
element.placeAt(container);
|
||||
});
|
||||
return widget;
|
||||
};
|
||||
|
||||
return callback(widget);
|
||||
});
|
||||
|
||||
function checkStyleButtons() {
|
||||
// The 3rd parameter is false to avoid firing onChange when setting the value
|
||||
// programmatically.
|
||||
}
|
||||
|
||||
function checkStyleButtons() {
|
||||
// The 3rd parameter is false to avoid firing onChange when setting the value
|
||||
// programmatically.
|
||||
if (boldButton) {
|
||||
boldButton.set('checked', editorSession.isBold(), false);
|
||||
}
|
||||
if (italicButton) {
|
||||
italicButton.set('checked', editorSession.isItalic(), false);
|
||||
}
|
||||
if (underlineButton) {
|
||||
underlineButton.set('checked', editorSession.hasUnderline(), false);
|
||||
}
|
||||
if (strikethroughButton) {
|
||||
strikethroughButton.set('checked', editorSession.hasStrikeThrough(), false);
|
||||
}
|
||||
}
|
||||
|
||||
editorSession.subscribe(EditorSession.signalCursorMoved, checkStyleButtons);
|
||||
editorSession.subscribe(EditorSession.signalParagraphChanged, checkStyleButtons);
|
||||
editorSession.subscribe(EditorSession.signalParagraphStyleModified, checkStyleButtons);
|
||||
this.setEditorSession = function(session) {
|
||||
if (editorSession) {
|
||||
editorSession.unsubscribe(EditorSession.signalCursorMoved, checkStyleButtons);
|
||||
editorSession.unsubscribe(EditorSession.signalParagraphChanged, checkStyleButtons);
|
||||
editorSession.unsubscribe(EditorSession.signalParagraphStyleModified, checkStyleButtons);
|
||||
}
|
||||
editorSession = session;
|
||||
if (editorSession) {
|
||||
editorSession.subscribe(EditorSession.signalCursorMoved, checkStyleButtons);
|
||||
editorSession.subscribe(EditorSession.signalParagraphChanged, checkStyleButtons);
|
||||
editorSession.subscribe(EditorSession.signalParagraphStyleModified, checkStyleButtons);
|
||||
checkStyleButtons();
|
||||
}
|
||||
};
|
||||
|
||||
widget.children = [boldButton, italicButton, underlineButton, strikethroughButton];
|
||||
widget.startup = function () {
|
||||
widget.children.forEach(function (element) {
|
||||
element.startup();
|
||||
});
|
||||
};
|
||||
|
||||
widget.placeAt = function (container) {
|
||||
widget.children.forEach(function (element) {
|
||||
element.placeAt(container);
|
||||
});
|
||||
return widget;
|
||||
};
|
||||
|
||||
return callback(widget);
|
||||
});
|
||||
}
|
||||
|
||||
return function SimpleStyles(editorSession, callback) {
|
||||
makeWidget(editorSession, function (widget) {
|
||||
// init
|
||||
makeWidget(function (widget) {
|
||||
return callback(widget);
|
||||
});
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Copyright (C) 2012 KO GmbH <copyright@kogmbh.com>
|
||||
* @license
|
||||
* Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
@ -31,35 +32,63 @@
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global define, require */
|
||||
|
||||
define("webodf/editor/widgets/toolbarWidgets/currentStyle",
|
||||
["webodf/editor/EditorSession"],
|
||||
|
||||
function (EditorSession) {
|
||||
"use strict";
|
||||
function makeWidget(editorSession, callback) {
|
||||
require(["webodf/editor/widgets/paragraphStyles"], function (ParagraphStyles) {
|
||||
var paragraphStyles, widget;
|
||||
|
||||
paragraphStyles = new ParagraphStyles(editorSession, function (pStyles) {
|
||||
// if the current paragraph style changes, update the widget
|
||||
editorSession.subscribe(EditorSession.signalParagraphChanged, function (info) {
|
||||
if (info.type === 'style') {
|
||||
pStyles.widget().set("value", info.styleName);
|
||||
}
|
||||
return function CurrentStyle(callback) {
|
||||
var editorSession,
|
||||
paragraphStyles;
|
||||
|
||||
function selectParagraphStyle(info) {
|
||||
if (paragraphStyles) {
|
||||
if (info.type === 'style') {
|
||||
paragraphStyles.widget().set("value", info.styleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setParagraphStyle(value) {
|
||||
if (editorSession) {
|
||||
editorSession.setCurrentParagraphStyle(value);
|
||||
}
|
||||
}
|
||||
|
||||
function makeWidget(callback) {
|
||||
require(["webodf/editor/widgets/paragraphStyles"], function (ParagraphStyles) {
|
||||
var p;
|
||||
|
||||
p = new ParagraphStyles(function (pStyles) {
|
||||
paragraphStyles = pStyles;
|
||||
|
||||
paragraphStyles.widget().onChange = setParagraphStyle;
|
||||
|
||||
paragraphStyles.setEditorSession(editorSession);
|
||||
return callback(paragraphStyles.widget());
|
||||
});
|
||||
|
||||
pStyles.widget().onChange = function (value) {
|
||||
editorSession.setCurrentParagraphStyle(value);
|
||||
};
|
||||
|
||||
return callback(pStyles.widget());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return function CurrentStyle(editorSession, callback) {
|
||||
makeWidget(editorSession, function (widget) {
|
||||
this.setEditorSession = function(session) {
|
||||
if (editorSession) {
|
||||
editorSession.unsubscribe(EditorSession.signalParagraphChanged, selectParagraphStyle);
|
||||
}
|
||||
editorSession = session;
|
||||
if (paragraphStyles) {
|
||||
paragraphStyles.setEditorSession(editorSession);
|
||||
}
|
||||
if (editorSession) {
|
||||
editorSession.subscribe(EditorSession.signalParagraphChanged, selectParagraphStyle);
|
||||
// TODO: selectParagraphStyle(editorSession.getCurrentParagraphStyle());
|
||||
}
|
||||
};
|
||||
|
||||
makeWidget(function (widget) {
|
||||
return callback(widget);
|
||||
});
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Copyright (C) 2012 KO GmbH <copyright@kogmbh.com>
|
||||
* @license
|
||||
* Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
@ -31,66 +32,88 @@
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global define,require,document */
|
||||
|
||||
define("webodf/editor/widgets/undoRedoMenu",
|
||||
["webodf/editor/EditorSession"],
|
||||
|
||||
function (EditorSession) {
|
||||
"use strict";
|
||||
|
||||
function makeWidget(editorSession, callback) {
|
||||
require(["dijit/form/Button"], function (Button) {
|
||||
var widget = {},
|
||||
undoButton,
|
||||
redoButton;
|
||||
return function UndoRedoMenu(callback) {
|
||||
var editorSession,
|
||||
undoButton,
|
||||
redoButton;
|
||||
|
||||
undoButton = new Button({
|
||||
label: document.translator('undo'),
|
||||
showLabel: false,
|
||||
disabled: true,
|
||||
iconClass: "dijitEditorIcon dijitEditorIconUndo",
|
||||
onClick: function () {
|
||||
editorSession.undo();
|
||||
}
|
||||
function makeWidget(callback) {
|
||||
require(["dijit/form/Button"], function (Button) {
|
||||
var widget = {};
|
||||
|
||||
undoButton = new Button({
|
||||
label: document.translator('undo'),
|
||||
showLabel: false,
|
||||
disabled: true, // TODO: get current session state
|
||||
iconClass: "dijitEditorIcon dijitEditorIconUndo",
|
||||
onClick: function () {
|
||||
if (editorSession) {
|
||||
editorSession.undo();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
redoButton = new Button({
|
||||
label: document.translator('redo'),
|
||||
showLabel: false,
|
||||
disabled: true, // TODO: get current session state
|
||||
iconClass: "dijitEditorIcon dijitEditorIconRedo",
|
||||
onClick: function () {
|
||||
if (editorSession) {
|
||||
editorSession.redo();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
widget.children = [undoButton, redoButton];
|
||||
widget.startup = function () {
|
||||
widget.children.forEach(function (element) {
|
||||
element.startup();
|
||||
});
|
||||
};
|
||||
|
||||
widget.placeAt = function (container) {
|
||||
widget.children.forEach(function (element) {
|
||||
element.placeAt(container);
|
||||
});
|
||||
return widget;
|
||||
};
|
||||
|
||||
return callback(widget);
|
||||
});
|
||||
}
|
||||
|
||||
redoButton = new Button({
|
||||
label: document.translator('redo'),
|
||||
showLabel: false,
|
||||
disabled: true,
|
||||
iconClass: "dijitEditorIcon dijitEditorIconRedo",
|
||||
onClick: function () {
|
||||
editorSession.redo();
|
||||
}
|
||||
});
|
||||
|
||||
function checkUndoButtons(e) {
|
||||
function checkUndoButtons(e) {
|
||||
if (undoButton) {
|
||||
undoButton.set('disabled', e.undoAvailable === false);
|
||||
}
|
||||
if (redoButton) {
|
||||
redoButton.set('disabled', e.redoAvailable === false);
|
||||
}
|
||||
}
|
||||
|
||||
editorSession.subscribe(EditorSession.signalUndoStackChanged, checkUndoButtons);
|
||||
this.setEditorSession = function(session) {
|
||||
if (editorSession) {
|
||||
editorSession.unsubscribe(EditorSession.signalUndoStackChanged, checkUndoButtons);
|
||||
}
|
||||
editorSession = session;
|
||||
if (editorSession) {
|
||||
editorSession.subscribe(EditorSession.signalUndoStackChanged, checkUndoButtons);
|
||||
// TODO: checkUndoButtons(editorSession.getundoredoavailablalalo());
|
||||
}
|
||||
};
|
||||
|
||||
widget.children = [undoButton, redoButton];
|
||||
widget.startup = function () {
|
||||
widget.children.forEach(function (element) {
|
||||
element.startup();
|
||||
});
|
||||
};
|
||||
|
||||
widget.placeAt = function (container) {
|
||||
widget.children.forEach(function (element) {
|
||||
element.placeAt(container);
|
||||
});
|
||||
return widget;
|
||||
};
|
||||
|
||||
return callback(widget);
|
||||
});
|
||||
}
|
||||
|
||||
return function UndoRedoMenu(editorSession, callback) {
|
||||
makeWidget(editorSession, function (widget) {
|
||||
// init
|
||||
makeWidget(function (widget) {
|
||||
return callback(widget);
|
||||
});
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Copyright (C) 2012 KO GmbH <copyright@kogmbh.com>
|
||||
|
||||
* @license
|
||||
* Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com>
|
||||
*
|
||||
* @licstart
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU Affero General Public License
|
||||
@ -31,39 +32,51 @@
|
||||
* @source: http://www.webodf.org/
|
||||
* @source: http://gitorious.org/webodf/webodf/
|
||||
*/
|
||||
|
||||
/*global define,require,document */
|
||||
|
||||
define("webodf/editor/widgets/zoomSlider", [], function () {
|
||||
"use strict";
|
||||
function makeWidget(editorSession, callback) {
|
||||
require(["dijit/form/HorizontalSlider", "dijit/form/NumberTextBox", "dojo"], function (HorizontalSlider, NumberTextBox, dojo) {
|
||||
var widget = {},
|
||||
canvas;
|
||||
|
||||
widget = new HorizontalSlider({
|
||||
name: 'zoomSlider',
|
||||
value: 100,
|
||||
minimum: 30,
|
||||
maximum: 150,
|
||||
discreteValues: 100,
|
||||
intermediateChanges: true,
|
||||
style: {
|
||||
width: '150px',
|
||||
height: '25px',
|
||||
float: 'right'
|
||||
}
|
||||
return function ZoomSlider(callback) {
|
||||
var editorSession,
|
||||
slider;
|
||||
|
||||
function makeWidget(callback) {
|
||||
require(["dijit/form/HorizontalSlider", "dijit/form/NumberTextBox", "dojo"], function (HorizontalSlider, NumberTextBox, dojo) {
|
||||
var widget = {};
|
||||
|
||||
slider = new HorizontalSlider({
|
||||
name: 'zoomSlider',
|
||||
value: 100,
|
||||
minimum: 30,
|
||||
maximum: 150,
|
||||
discreteValues: 100,
|
||||
intermediateChanges: true,
|
||||
style: {
|
||||
width: '150px',
|
||||
height: '25px',
|
||||
float: 'right'
|
||||
}
|
||||
});
|
||||
|
||||
slider.onChange = function (value) {
|
||||
if (editorSession) {
|
||||
editorSession.getOdfCanvas().setZoomLevel(value / 100.0);
|
||||
}
|
||||
};
|
||||
|
||||
return callback(slider);
|
||||
});
|
||||
}
|
||||
|
||||
canvas = dojo.byId('canvas');
|
||||
widget.onChange = function (value) {
|
||||
editorSession.getOdfCanvas().setZoomLevel(value / 100.0);
|
||||
};
|
||||
this.setEditorSession = function(session) {
|
||||
editorSession = session;
|
||||
// if (slider) { slider.setValue(editorSession.getOdfCanvas().getZoomLevel() ); TODO!
|
||||
};
|
||||
|
||||
return callback(widget);
|
||||
});
|
||||
}
|
||||
|
||||
return function ZoomSlider(editorSession, callback) {
|
||||
makeWidget(editorSession, function (widget) {
|
||||
// init
|
||||
makeWidget(function (widget) {
|
||||
return callback(widget);
|
||||
});
|
||||
};
|
||||
|
File diff suppressed because one or more lines are too long
1420
js/webodf.js
1420
js/webodf.js
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user