Sync with webodf 82510ae020f8ee8a1f7b65a27b6af107b6023e90
* exposes some state-change/error events in the Editor API * catches more possible errors and handles them (e.g. staying cool on temporary disconnection to server, but warning about it) * improves selection by mouse * fixes selected paragraph style not being set to all selected paragraphs * fixes leaking of some helper attributes into saved ODT files
This commit is contained in:
parent
daf553d06c
commit
e3adf6bd19
90
js/3rdparty/webodf/editor/Editor.js
vendored
90
js/3rdparty/webodf/editor/Editor.js
vendored
@ -76,9 +76,25 @@ define("webodf/editor/Editor", [
|
|||||||
saveOdtFile = args.saveCallback,
|
saveOdtFile = args.saveCallback,
|
||||||
close = args.closeCallback,
|
close = args.closeCallback,
|
||||||
odfCanvas,
|
odfCanvas,
|
||||||
|
eventNotifier = new core.EventNotifier([
|
||||||
|
Editor.EVENT_ERROR,
|
||||||
|
Editor.EVENT_BEFORESAVETOFILE,
|
||||||
|
Editor.EVENT_SAVEDTOFILE,
|
||||||
|
Editor.EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED,
|
||||||
|
Editor.EVENT_HASSESSIONHOSTCONNECTIONCHANGED
|
||||||
|
]),
|
||||||
pendingMemberId,
|
pendingMemberId,
|
||||||
pendingEditorReadyCallback;
|
pendingEditorReadyCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!string} eventid
|
||||||
|
* @param {*} args
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
function fireEvent(eventid, args) {
|
||||||
|
eventNotifier.emit(eventid, args);
|
||||||
|
}
|
||||||
|
|
||||||
function getFileBlob(cbSuccess, cbError) {
|
function getFileBlob(cbSuccess, cbError) {
|
||||||
var odfContainer = odfCanvas.odfContainer();
|
var odfContainer = odfCanvas.odfContainer();
|
||||||
|
|
||||||
@ -196,14 +212,27 @@ define("webodf/editor/Editor", [
|
|||||||
}
|
}
|
||||||
blob = new Blob([data.buffer], {type: mimetype});
|
blob = new Blob([data.buffer], {type: mimetype});
|
||||||
saveAs(blob, filename);
|
saveAs(blob, filename);
|
||||||
|
//TODO: add callback as event handler to saveAs
|
||||||
|
fireEvent(Editor.EVENT_SAVEDTOFILE, null);
|
||||||
}
|
}
|
||||||
function onerror(error) {
|
function onerror(error) {
|
||||||
|
// TODO: use callback for that
|
||||||
alert(error);
|
alert(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fireEvent(Editor.EVENT_BEFORESAVETOFILE, null);
|
||||||
getFileBlob(onsuccess, onerror);
|
getFileBlob(onsuccess, onerror);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!Object} error
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
function handleOperationRouterErrors(error) {
|
||||||
|
// TODO: translate error into Editor ids or at least document the possible values
|
||||||
|
fireEvent(Editor.EVENT_ERROR, error);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* open the initial document of an editing-session,
|
* open the initial document of an editing-session,
|
||||||
* request a replay of previous operations, call
|
* request a replay of previous operations, call
|
||||||
@ -219,9 +248,24 @@ define("webodf/editor/Editor", [
|
|||||||
// overwrite router
|
// overwrite router
|
||||||
// TODO: serverFactory should be a backendFactory,
|
// TODO: serverFactory should be a backendFactory,
|
||||||
// and there should be a backendFactory for local editing
|
// and there should be a backendFactory for local editing
|
||||||
var opRouter = serverFactory.createOperationRouter(sessionId, memberId, server, odfCanvas.odfContainer());
|
var opRouter = serverFactory.createOperationRouter(sessionId, memberId, server, odfCanvas.odfContainer(), handleOperationRouterErrors);
|
||||||
session.setOperationRouter(opRouter);
|
session.setOperationRouter(opRouter);
|
||||||
|
// forward events
|
||||||
|
// TODO: relying here on that opRouter uses the same id strings ATM, those should be defined at OperationRouter interface
|
||||||
|
opRouter.subscribe(Editor.EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED, function (hasUnsyncedOps) {
|
||||||
|
fireEvent(Editor.EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED, hasUnsyncedOps);
|
||||||
|
});
|
||||||
|
opRouter.subscribe(Editor.EVENT_HASSESSIONHOSTCONNECTIONCHANGED, function (hasSessionHostConnection) {
|
||||||
|
fireEvent(Editor.EVENT_HASSESSIONHOSTCONNECTIONCHANGED, hasSessionHostConnection);
|
||||||
|
});
|
||||||
|
opRouter.subscribe(Editor.EVENT_BEFORESAVETOFILE, function () {
|
||||||
|
fireEvent(Editor.EVENT_BEFORESAVETOFILE, null);
|
||||||
|
});
|
||||||
|
opRouter.subscribe(Editor.EVENT_SAVEDTOFILE, function () {
|
||||||
|
fireEvent(Editor.EVENT_SAVEDTOFILE, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// now get existing ops and after that let the user edit
|
||||||
opRouter.requestReplay(function done() {
|
opRouter.requestReplay(function done() {
|
||||||
editorReadyCallback();
|
editorReadyCallback();
|
||||||
});
|
});
|
||||||
@ -288,6 +332,38 @@ define("webodf/editor/Editor", [
|
|||||||
editorSession.sessionController.endEditing();
|
editorSession.sessionController.endEditing();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to register listeners for certain events. Currently
|
||||||
|
* available events are, with the type of the argument passed to the callback:
|
||||||
|
* Editor.EVENT_BEFORESAVETOFILE - no argument
|
||||||
|
* Editor.EVENT_SAVEDTOFILE - no argument
|
||||||
|
* Editor.EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED - boolean, reflecting new hasLocalUnsyncedOperations state
|
||||||
|
* Editor.EVENT_HASSESSIONHOSTCONNECTIONCHANGED - boolean, reflecting new hasSessionhostConnection state
|
||||||
|
* Editor.EVENT_ERROR - string, one of these errorcodes:
|
||||||
|
* "notMemberOfSession"
|
||||||
|
* "opExecutionFailure"
|
||||||
|
* "sessionDoesNotExist"
|
||||||
|
* "unknownOpReceived"
|
||||||
|
* "unknownServerReply"
|
||||||
|
* "unresolvableConflictingOps"
|
||||||
|
*
|
||||||
|
* @param {!string} eventid
|
||||||
|
* @param {!Function} listener
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
this.addEventListener = function (eventid, listener) {
|
||||||
|
eventNotifier.subscribe(eventid, listener);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!string} eventid
|
||||||
|
* @param {!Function} listener
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
this.removeEventListener = function (eventid, listener) {
|
||||||
|
eventNotifier.unsubscribe(eventid, listener);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!function(!Object=)} callback, passing an error object in case of error
|
* @param {!function(!Object=)} callback, passing an error object in case of error
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
@ -440,6 +516,18 @@ define("webodf/editor/Editor", [
|
|||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**@const @type {!string}*/
|
||||||
|
Editor.EVENT_ERROR = "error";
|
||||||
|
/**@const @type {!string}*/
|
||||||
|
Editor.EVENT_BEFORESAVETOFILE = "beforeSaveToFile";
|
||||||
|
/**@const @type {!string}*/
|
||||||
|
Editor.EVENT_SAVEDTOFILE = "savedToFile";
|
||||||
|
/**@const @type {!string}*/
|
||||||
|
Editor.EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED = "hasLocalUnsyncedOperationsChanged";
|
||||||
|
/**@const @type {!string}*/
|
||||||
|
Editor.EVENT_HASSESSIONHOSTCONNECTIONCHANGED = "hasSessionHostConnectionChanged";
|
||||||
|
|
||||||
return Editor;
|
return Editor;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
52
js/3rdparty/webodf/editor/EditorSession.js
vendored
52
js/3rdparty/webodf/editor/EditorSession.js
vendored
@ -48,7 +48,9 @@ define("webodf/editor/EditorSession", [
|
|||||||
};
|
};
|
||||||
|
|
||||||
runtime.loadClass("core.DomUtils");
|
runtime.loadClass("core.DomUtils");
|
||||||
|
runtime.loadClass("odf.OdfUtils");
|
||||||
runtime.loadClass("ops.OdtDocument");
|
runtime.loadClass("ops.OdtDocument");
|
||||||
|
runtime.loadClass("ops.StepsTranslator");
|
||||||
runtime.loadClass("ops.Session");
|
runtime.loadClass("ops.Session");
|
||||||
runtime.loadClass("odf.Namespaces");
|
runtime.loadClass("odf.Namespaces");
|
||||||
runtime.loadClass("odf.OdfCanvas");
|
runtime.loadClass("odf.OdfCanvas");
|
||||||
@ -80,6 +82,7 @@ define("webodf/editor/EditorSession", [
|
|||||||
fontStyles = document.createElement('style'),
|
fontStyles = document.createElement('style'),
|
||||||
formatting = odtDocument.getFormatting(),
|
formatting = odtDocument.getFormatting(),
|
||||||
domUtils = new core.DomUtils(),
|
domUtils = new core.DomUtils(),
|
||||||
|
odfUtils = new odf.OdfUtils(),
|
||||||
eventNotifier = new core.EventNotifier([
|
eventNotifier = new core.EventNotifier([
|
||||||
EditorSession.signalMemberAdded,
|
EditorSession.signalMemberAdded,
|
||||||
EditorSession.signalMemberUpdated,
|
EditorSession.signalMemberUpdated,
|
||||||
@ -306,16 +309,45 @@ define("webodf/editor/EditorSession", [
|
|||||||
return currentCommonStyleName;
|
return currentCommonStyleName;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setCurrentParagraphStyle = function (value) {
|
/**
|
||||||
var op;
|
* Round the step up to the next step
|
||||||
if (currentCommonStyleName !== value) {
|
* @param {!number} step
|
||||||
op = new ops.OpSetParagraphStyle();
|
* @returns {!boolean}
|
||||||
op.init({
|
*/
|
||||||
memberid: localMemberId,
|
function roundUp(step) {
|
||||||
position: self.getCursorPosition(),
|
return step === ops.StepsTranslator.NEXT_STEP;
|
||||||
styleName: value
|
}
|
||||||
});
|
|
||||||
session.enqueue([op]);
|
/**
|
||||||
|
* Applies the paragraph style with the given
|
||||||
|
* style name to all the paragraphs within
|
||||||
|
* the cursor selection.
|
||||||
|
* @param {!string} styleName
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
this.setCurrentParagraphStyle = function (styleName) {
|
||||||
|
var range = odtDocument.getCursor(localMemberId).getSelectedRange(),
|
||||||
|
paragraphs = odfUtils.getParagraphElements(range),
|
||||||
|
opQueue = [];
|
||||||
|
|
||||||
|
paragraphs.forEach(function (paragraph) {
|
||||||
|
var paragraphStartPoint = odtDocument.convertDomPointToCursorStep(paragraph, 0, roundUp),
|
||||||
|
paragraphStyleName = paragraph.getAttributeNS(odf.Namespaces.textns, "style-name"),
|
||||||
|
opSetParagraphStyle;
|
||||||
|
|
||||||
|
if (paragraphStyleName !== styleName) {
|
||||||
|
opSetParagraphStyle = new ops.OpSetParagraphStyle();
|
||||||
|
opSetParagraphStyle.init({
|
||||||
|
memberid: localMemberId,
|
||||||
|
styleName: styleName,
|
||||||
|
position: paragraphStartPoint
|
||||||
|
});
|
||||||
|
opQueue.push(opSetParagraphStyle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (opQueue.length > 0) {
|
||||||
|
session.enqueue(opQueue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,9 +53,10 @@ ServerFactory.prototype.createServer = function () {"use strict"; };
|
|||||||
* @param {!string} memberId
|
* @param {!string} memberId
|
||||||
* @param {!ops.Server} server
|
* @param {!ops.Server} server
|
||||||
* @param {!odf.OdfContainer} odfContainer TODO: needed for pullbox writing to server at end, find better solution
|
* @param {!odf.OdfContainer} odfContainer TODO: needed for pullbox writing to server at end, find better solution
|
||||||
|
* @param {!function(!Object)} errorCallback
|
||||||
* @return {!ops.OperationRouter}
|
* @return {!ops.OperationRouter}
|
||||||
*/
|
*/
|
||||||
ServerFactory.prototype.createOperationRouter = function (sessionId, memberId, server, odfContainer) {"use strict"; };
|
ServerFactory.prototype.createOperationRouter = function (sessionId, memberId, server, odfContainer, errorCallback) {"use strict"; };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!ops.Server} server
|
* @param {!ops.Server} server
|
||||||
|
@ -50,8 +50,8 @@ define("webodf/editor/server/owncloud/ServerFactory", [
|
|||||||
};
|
};
|
||||||
return server;
|
return server;
|
||||||
};
|
};
|
||||||
this.createOperationRouter = function (sid, mid, server, odfContainer) {
|
this.createOperationRouter = function (sid, mid, server, odfContainer, errorCallback) {
|
||||||
return new PullBoxOperationRouter(sid, mid, server, odfContainer);
|
return new PullBoxOperationRouter(sid, mid, server, odfContainer, errorCallback);
|
||||||
};
|
};
|
||||||
this.createSessionList = function (server) {
|
this.createSessionList = function (server) {
|
||||||
return new PullBoxSessionList(server);
|
return new PullBoxSessionList(server);
|
||||||
|
@ -28,7 +28,18 @@
|
|||||||
define("webodf/editor/server/pullbox/OperationRouter", [], function () {
|
define("webodf/editor/server/pullbox/OperationRouter", [], function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
// TODO: these eventid strings should be defined at OperationRouter interface
|
||||||
|
var /**@const @type {!string}*/
|
||||||
|
EVENT_BEFORESAVETOFILE = "beforeSaveToFile",
|
||||||
|
/**@const @type {!string}*/
|
||||||
|
EVENT_SAVEDTOFILE = "savedToFile",
|
||||||
|
/**@const @type {!string}*/
|
||||||
|
EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED = "hasLocalUnsyncedOperationsChanged",
|
||||||
|
/**@const @type {!string}*/
|
||||||
|
EVENT_HASSESSIONHOSTCONNECTIONCHANGED = "hasSessionHostConnectionChanged";
|
||||||
|
|
||||||
runtime.loadClass("ops.OperationTransformer");
|
runtime.loadClass("ops.OperationTransformer");
|
||||||
|
runtime.loadClass("core.EventNotifier");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* route operations in a networked collaborative manner.
|
* route operations in a networked collaborative manner.
|
||||||
@ -43,11 +54,11 @@ define("webodf/editor/server/pullbox/OperationRouter", [], function () {
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @implements ops.OperationRouter
|
* @implements ops.OperationRouter
|
||||||
*/
|
*/
|
||||||
return function PullBoxOperationRouter(sessionId, memberId, server, odfContainer) {
|
return function PullBoxOperationRouter(sessionId, memberId, server, odfContainer, errorCallback) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var operationFactory,
|
var operationFactory,
|
||||||
/**@type{function(!ops.Operation)}*/
|
/**@type{function(!ops.Operation):boolean}*/
|
||||||
playbackFunction,
|
playbackFunction,
|
||||||
idleTimeout = null,
|
idleTimeout = null,
|
||||||
syncOpsTimeout = null,
|
syncOpsTimeout = null,
|
||||||
@ -58,7 +69,7 @@ define("webodf/editor/server/pullbox/OperationRouter", [], function () {
|
|||||||
/**@type{!boolean}*/
|
/**@type{!boolean}*/
|
||||||
isSyncCallRunning = false,
|
isSyncCallRunning = false,
|
||||||
/**@type{!boolean}*/
|
/**@type{!boolean}*/
|
||||||
hasUnresolvableConflict = false,
|
hasError = false,
|
||||||
/**@type{!boolean}*/
|
/**@type{!boolean}*/
|
||||||
syncingBlocked = false,
|
syncingBlocked = false,
|
||||||
/** @type {!string} id of latest op stack state known on the server */
|
/** @type {!string} id of latest op stack state known on the server */
|
||||||
@ -71,8 +82,20 @@ define("webodf/editor/server/pullbox/OperationRouter", [], function () {
|
|||||||
unplayedServerOpspecQueue = [],
|
unplayedServerOpspecQueue = [],
|
||||||
/** @type {!Array.<!Function>} sync request callbacks which should be called after the received ops have been applied server */
|
/** @type {!Array.<!Function>} sync request callbacks which should be called after the received ops have been applied server */
|
||||||
uncalledSyncRequestCallbacksQueue = [],
|
uncalledSyncRequestCallbacksQueue = [],
|
||||||
|
/** @type {!Array.<!function(!boolean):undefined>} ops created since the last sync call to the server */
|
||||||
|
hasLocalUnsyncedOpsStateSubscribers = [],
|
||||||
/**@type{!boolean}*/
|
/**@type{!boolean}*/
|
||||||
hasLocalUnsyncedOps = false,
|
hasLocalUnsyncedOps = false,
|
||||||
|
/** @type {!Array.<!function(!boolean):undefined>} */
|
||||||
|
hasSessionHostConnectionStateSubscribers = [],
|
||||||
|
/**@type{!boolean}*/
|
||||||
|
hasSessionHostConnection = true,
|
||||||
|
eventNotifier = new core.EventNotifier([
|
||||||
|
EVENT_BEFORESAVETOFILE,
|
||||||
|
EVENT_SAVEDTOFILE,
|
||||||
|
EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED,
|
||||||
|
EVENT_HASSESSIONHOSTCONNECTIONCHANGED
|
||||||
|
]),
|
||||||
/**@type{!boolean} tells if any local ops have been modifying ops */
|
/**@type{!boolean} tells if any local ops have been modifying ops */
|
||||||
hasPushedModificationOps = false,
|
hasPushedModificationOps = false,
|
||||||
operationTransformer = new ops.OperationTransformer(),
|
operationTransformer = new ops.OperationTransformer(),
|
||||||
@ -84,7 +107,8 @@ define("webodf/editor/server/pullbox/OperationRouter", [], function () {
|
|||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
function updateHasLocalUnsyncedOpsState() {
|
function updateHasLocalUnsyncedOpsState() {
|
||||||
var hasLocalUnsyncedOpsNow = (unsyncedClientOpspecQueue.length > 0);
|
var i,
|
||||||
|
hasLocalUnsyncedOpsNow = (unsyncedClientOpspecQueue.length > 0);
|
||||||
|
|
||||||
// no change?
|
// no change?
|
||||||
if (hasLocalUnsyncedOps === hasLocalUnsyncedOpsNow) {
|
if (hasLocalUnsyncedOps === hasLocalUnsyncedOpsNow) {
|
||||||
@ -92,6 +116,23 @@ define("webodf/editor/server/pullbox/OperationRouter", [], function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasLocalUnsyncedOps = hasLocalUnsyncedOpsNow;
|
hasLocalUnsyncedOps = hasLocalUnsyncedOpsNow;
|
||||||
|
eventNotifier.emit(EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED, hasLocalUnsyncedOps);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!boolean} hasConnection
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
function updateHasSessionHostConnectionState(hasConnection) {
|
||||||
|
var i;
|
||||||
|
|
||||||
|
// no change?
|
||||||
|
if (hasSessionHostConnection === hasConnection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSessionHostConnection = hasConnection;
|
||||||
|
eventNotifier.emit(EVENT_HASSESSIONHOSTCONNECTIONCHANGED, hasSessionHostConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,9 +163,16 @@ define("webodf/editor/server/pullbox/OperationRouter", [], function () {
|
|||||||
op = operationFactory.create(opspec);
|
op = operationFactory.create(opspec);
|
||||||
runtime.log(" op in: "+runtime.toJson(opspec));
|
runtime.log(" op in: "+runtime.toJson(opspec));
|
||||||
if (op !== null) {
|
if (op !== null) {
|
||||||
playbackFunction(op);
|
if (!playbackFunction(op)) {
|
||||||
|
hasError = true;
|
||||||
|
errorCallback("opExecutionFailure");
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
hasError = true;
|
||||||
runtime.log("ignoring invalid incoming opspec: " + opspec);
|
runtime.log("ignoring invalid incoming opspec: " + opspec);
|
||||||
|
errorCallback("unknownOpReceived");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +262,7 @@ define("webodf/editor/server/pullbox/OperationRouter", [], function () {
|
|||||||
}, syncOpsDelay);
|
}, syncOpsDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSyncCallRunning || hasUnresolvableConflict) {
|
if (isSyncCallRunning || hasError) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: hack, remove
|
// TODO: hack, remove
|
||||||
@ -243,13 +291,25 @@ runtime.log("OperationRouter: sending sync_ops call");
|
|||||||
client_ops: syncedClientOpspecs
|
client_ops: syncedClientOpspecs
|
||||||
}
|
}
|
||||||
}, function(responseData) {
|
}, function(responseData) {
|
||||||
var response = /** @type{{result:string, head_seq:string, ops:Array.<!Object>}} */(runtime.fromJson(responseData));
|
var response,
|
||||||
|
/**@type{!boolean}*/
|
||||||
|
hasUnresolvableConflict = false;
|
||||||
|
|
||||||
|
updateHasSessionHostConnectionState(true);
|
||||||
|
|
||||||
// TODO: hack, remove
|
|
||||||
if (syncingBlocked) {
|
if (syncingBlocked) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = /** @type{{result:string, head_seq:string, ops:Array.<!Object>}} */(runtime.fromJson(responseData));
|
||||||
|
} catch (e) {
|
||||||
|
hasError = true;
|
||||||
|
runtime.log("Could not parse reply: "+responseData);
|
||||||
|
errorCallback("unknownServerReply");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: hack, remove
|
||||||
runtime.log("sync_ops reply: " + responseData);
|
runtime.log("sync_ops reply: " + responseData);
|
||||||
|
|
||||||
// just new ops?
|
// just new ops?
|
||||||
@ -290,20 +350,28 @@ runtime.log("OperationRouter: sending sync_ops call");
|
|||||||
if (!hasUnresolvableConflict) {
|
if (!hasUnresolvableConflict) {
|
||||||
isInstantSyncRequested = true;
|
isInstantSyncRequested = true;
|
||||||
}
|
}
|
||||||
|
} else if (response.result === "error") {
|
||||||
|
runtime.log("server reports an error: "+response.error);
|
||||||
|
hasError = true;
|
||||||
|
errorCallback(
|
||||||
|
response.error === "ENOSESSION" ? "sessionDoesNotExist":
|
||||||
|
response.error === "ENOMEMBER" ? "notMemberOfSession":
|
||||||
|
"unknownServerReply"
|
||||||
|
);
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
runtime.assert(false, "Unexpected result on sync-ops call: "+response.result);
|
hasError = true;
|
||||||
|
runtime.log("Unexpected result on sync-ops call: "+response.result);
|
||||||
|
errorCallback("unknownServerReply");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// unlock
|
// unlock
|
||||||
isSyncCallRunning = false;
|
isSyncCallRunning = false;
|
||||||
|
|
||||||
if (hasUnresolvableConflict) {
|
if (hasUnresolvableConflict) {
|
||||||
// TODO: offer option to reload session automatically?
|
hasError = true;
|
||||||
runtime.assert(false,
|
errorCallback("unresolvableConflictingOps");
|
||||||
"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 {
|
} else {
|
||||||
// prepare next sync
|
// prepare next sync
|
||||||
if (isInstantSyncRequested) {
|
if (isInstantSyncRequested) {
|
||||||
@ -318,6 +386,22 @@ runtime.log("OperationRouter: sending sync_ops call");
|
|||||||
}
|
}
|
||||||
playUnplayedServerOpSpecs();
|
playUnplayedServerOpSpecs();
|
||||||
}
|
}
|
||||||
|
}, function() {
|
||||||
|
runtime.log("meh, server cannot be reached ATM.");
|
||||||
|
// signal connection problem, but do not give up for now
|
||||||
|
updateHasSessionHostConnectionState(false);
|
||||||
|
// put the (not) send ops back into the outgoing queue
|
||||||
|
unsyncedClientOpspecQueue = syncedClientOpspecs.concat(unsyncedClientOpspecQueue);
|
||||||
|
syncRequestCallbacksQueue = syncRequestCallbacksArray.concat(syncRequestCallbacksQueue);
|
||||||
|
// unlock
|
||||||
|
isSyncCallRunning = false;
|
||||||
|
// nothing on client to sync?
|
||||||
|
if (unsyncedClientOpspecQueue.length === 0) {
|
||||||
|
idleTimeout = runtime.getWindow().setTimeout(startSyncOpsTimeout, idleDelay);
|
||||||
|
} else {
|
||||||
|
startSyncOpsTimeout();
|
||||||
|
}
|
||||||
|
playUnplayedServerOpSpecs();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,7 +465,7 @@ runtime.log("OperationRouter: instant opsSync requested");
|
|||||||
/**
|
/**
|
||||||
* Sets the method which should be called to apply operations.
|
* Sets the method which should be called to apply operations.
|
||||||
*
|
*
|
||||||
* @param {!function(!ops.Operation)} playback_func
|
* @param {!function(!ops.Operation):boolean} playback_func
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
this.setPlaybackFunction = function (playback_func) {
|
this.setPlaybackFunction = function (playback_func) {
|
||||||
@ -395,7 +479,10 @@ runtime.log("OperationRouter: instant opsSync requested");
|
|||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
this.push = function (operations) {
|
this.push = function (operations) {
|
||||||
if (hasUnresolvableConflict) {
|
var i, op, opspec,
|
||||||
|
timestamp = (new Date()).getTime();
|
||||||
|
|
||||||
|
if (hasError) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: should be an assert in the future
|
// TODO: should be an assert in the future
|
||||||
@ -406,22 +493,27 @@ runtime.log("OperationRouter: instant opsSync requested");
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
operations.forEach(function(op) {
|
for (i = 0; i < operations.length; i += 1) {
|
||||||
var timedOp,
|
op = operations[i];
|
||||||
opspec = op.spec();
|
opspec = op.spec();
|
||||||
|
|
||||||
// note if any local ops modified
|
// note if any local ops modified
|
||||||
hasPushedModificationOps = hasPushedModificationOps || op.isEdit;
|
hasPushedModificationOps = hasPushedModificationOps || op.isEdit;
|
||||||
|
|
||||||
// apply locally
|
// add timestamp TODO: improve the useless recreation of the op
|
||||||
opspec.timestamp = (new Date()).getTime();
|
opspec.timestamp = timestamp;
|
||||||
timedOp = operationFactory.create(opspec);
|
op = operationFactory.create(opspec);
|
||||||
|
|
||||||
playbackFunction(timedOp);
|
// apply locally
|
||||||
|
if (!playbackFunction(op)) {
|
||||||
|
hasError = true;
|
||||||
|
errorCallback("opExecutionFailure");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// send to server
|
// send to server
|
||||||
unsyncedClientOpspecQueue.push(opspec);
|
unsyncedClientOpspecQueue.push(opspec);
|
||||||
});
|
}
|
||||||
|
|
||||||
triggerPushingOps();
|
triggerPushingOps();
|
||||||
|
|
||||||
@ -434,25 +526,65 @@ runtime.log("OperationRouter: instant opsSync requested");
|
|||||||
* A callback is called on success.
|
* A callback is called on success.
|
||||||
*/
|
*/
|
||||||
this.close = function (cb) {
|
this.close = function (cb) {
|
||||||
|
function cbDoneSaving(err) {
|
||||||
|
eventNotifier.emit(EVENT_SAVEDTOFILE, null);
|
||||||
|
cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
function cbSuccess(fileData) {
|
function cbSuccess(fileData) {
|
||||||
server.writeSessionStateToFile(sessionId, memberId, lastServerSeq, fileData, cb);
|
server.writeSessionStateToFile(sessionId, memberId, lastServerSeq, fileData, cbDoneSaving);
|
||||||
}
|
}
|
||||||
|
|
||||||
function doClose() {
|
function doClose() {
|
||||||
syncingBlocked = true;
|
syncingBlocked = true;
|
||||||
if (hasPushedModificationOps) {
|
if (hasPushedModificationOps) {
|
||||||
odfContainer.createByteArray(cbSuccess, cb);
|
eventNotifier.emit(EVENT_BEFORESAVETOFILE, null);
|
||||||
|
|
||||||
|
odfContainer.createByteArray(cbSuccess, cbDoneSaving);
|
||||||
} else {
|
} else {
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasLocalUnsyncedOps) {
|
if (hasError) {
|
||||||
|
cb();
|
||||||
|
} else if (hasLocalUnsyncedOps) {
|
||||||
requestInstantOpsSync(doClose);
|
requestInstantOpsSync(doClose);
|
||||||
} else {
|
} else {
|
||||||
doClose();
|
doClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!string} eventId
|
||||||
|
* @param {!Function} cb
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
this.subscribe = function (eventId, cb) {
|
||||||
|
eventNotifier.subscribe(eventId, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {!string} eventId
|
||||||
|
* @param {!Function} cb
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
this.unsubscribe = function (eventId, cb) {
|
||||||
|
eventNotifier.unsubscribe(eventId, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {!boolean}
|
||||||
|
*/
|
||||||
|
this.hasLocalUnsyncedOps = function () {
|
||||||
|
return hasLocalUnsyncedOps;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {!boolean}
|
||||||
|
*/
|
||||||
|
this.hasSessionHostConnection = function () {
|
||||||
|
return hasSessionHostConnection;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -40,6 +40,7 @@ define("webodf/editor/server/pullbox/Server", [], function () {
|
|||||||
|
|
||||||
var self = this,
|
var self = this,
|
||||||
token,
|
token,
|
||||||
|
/**@const*/serverCallTimeout = 10000,
|
||||||
base64 = new core.Base64();
|
base64 = new core.Base64();
|
||||||
|
|
||||||
args = args || {};
|
args = args || {};
|
||||||
@ -53,22 +54,33 @@ define("webodf/editor/server/pullbox/Server", [], function () {
|
|||||||
/**
|
/**
|
||||||
* @param {!Object} message
|
* @param {!Object} message
|
||||||
* @param {!function(!string)} cb
|
* @param {!function(!string)} cb
|
||||||
|
* @param {!function(!number,!string)} cbError passes the status number
|
||||||
|
* and the statustext, or -1 if there was an exception on sending
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
function call(message, cb) {
|
function call(message, cb, cbError) {
|
||||||
var xhr = new XMLHttpRequest(),
|
var xhr = new XMLHttpRequest(),
|
||||||
messageString = JSON.stringify(message);
|
messageString = JSON.stringify(message);
|
||||||
|
|
||||||
function handleResult() {
|
function handleResult() {
|
||||||
if (xhr.readyState === 4) {
|
if (xhr.readyState === 4) {
|
||||||
if ((xhr.status < 200 || xhr.status >= 300) && xhr.status === 0) {
|
if (xhr.status < 200 || xhr.status >= 300) {
|
||||||
// report error
|
// report error
|
||||||
runtime.log("Status " + String(xhr.status) + ": " +
|
runtime.log("Status " + String(xhr.status) + ": " +
|
||||||
xhr.responseText || xhr.statusText);
|
xhr.responseText || xhr.statusText);
|
||||||
|
cbError(xhr.status, xhr.statusText);
|
||||||
|
} else {
|
||||||
|
runtime.log("Status " + String(xhr.status) + ": " +
|
||||||
|
xhr.responseText || xhr.statusText);
|
||||||
|
cb(xhr.responseText);
|
||||||
}
|
}
|
||||||
cb(xhr.responseText);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function handleTimeout() {
|
||||||
|
runtime.log("Timeout on call to server.");
|
||||||
|
cbError(0, xhr.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
runtime.log("Sending message to server: "+messageString);
|
runtime.log("Sending message to server: "+messageString);
|
||||||
// create body data for request from metadata and payload
|
// create body data for request from metadata and payload
|
||||||
|
|
||||||
@ -78,11 +90,14 @@ runtime.log("Sending message to server: "+messageString);
|
|||||||
xhr.setRequestHeader("requesttoken", token);
|
xhr.setRequestHeader("requesttoken", token);
|
||||||
}
|
}
|
||||||
xhr.onreadystatechange = handleResult;
|
xhr.onreadystatechange = handleResult;
|
||||||
|
xhr.timeout = serverCallTimeout;
|
||||||
|
// TODO: seems handleResult is called on timeout as well, with xhr.status === 0
|
||||||
|
// xhr.ontimeout = handleTimeout;
|
||||||
try {
|
try {
|
||||||
xhr.send(messageString);
|
xhr.send(messageString);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
runtime.log("Problem with calling server: " + e + " " + data);
|
runtime.log("Problem with calling server: " + e + " " + data);
|
||||||
cb(e.message);
|
cbError(-1, e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,14 +166,14 @@ runtime.log("Sending message to server: "+messageString);
|
|||||||
} else {
|
} else {
|
||||||
failCb(responseData);
|
failCb(responseData);
|
||||||
}
|
}
|
||||||
});
|
}, failCb);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!string} userId
|
* @param {!string} userId
|
||||||
* @param {!string} sessionId
|
* @param {!string} sessionId
|
||||||
* @param {!function(!string)} successCb
|
* @param {!function(!string)} successCb
|
||||||
* @param {function()=} failCb
|
* @param {!function()} failCb
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
this.joinSession = function (userId, sessionId, successCb, failCb) {
|
this.joinSession = function (userId, sessionId, successCb, failCb) {
|
||||||
@ -175,18 +190,16 @@ runtime.log("Sending message to server: "+messageString);
|
|||||||
if (response.hasOwnProperty("success") && response.success) {
|
if (response.hasOwnProperty("success") && response.success) {
|
||||||
successCb(response.member_id);
|
successCb(response.member_id);
|
||||||
} else {
|
} else {
|
||||||
if (failCb) {
|
failCb();
|
||||||
failCb();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}, failCb);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!string} sessionId
|
* @param {!string} sessionId
|
||||||
* @param {!string} memberId
|
* @param {!string} memberId
|
||||||
* @param {!function()} successCb
|
* @param {!function()} successCb
|
||||||
* @param {function()=} failCb
|
* @param {!function()} failCb
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
this.leaveSession = function (sessionId, memberId, successCb, failCb) {
|
this.leaveSession = function (sessionId, memberId, successCb, failCb) {
|
||||||
@ -203,18 +216,16 @@ runtime.log("Sending message to server: "+messageString);
|
|||||||
if (response.hasOwnProperty("success") && response.success) {
|
if (response.hasOwnProperty("success") && response.success) {
|
||||||
successCb();
|
successCb();
|
||||||
} else {
|
} else {
|
||||||
if (failCb) {
|
failCb();
|
||||||
failCb();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}, failCb);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!string} sessionId
|
* @param {!string} sessionId
|
||||||
* @param {!string} memberId
|
* @param {!string} memberId
|
||||||
* @param {!string} seqHead
|
* @param {!string} seqHead
|
||||||
* @param {function()=} callback
|
* @param {!function(!Object=)} callback
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
this.writeSessionStateToFile = function(sessionId, memberId, seqHead, fileData, callback) {
|
this.writeSessionStateToFile = function(sessionId, memberId, seqHead, fileData, callback) {
|
||||||
|
@ -40,8 +40,8 @@ define("webodf/editor/server/pullbox/ServerFactory", [
|
|||||||
this.createServer = function (args) {
|
this.createServer = function (args) {
|
||||||
return new PullBoxServer(args);
|
return new PullBoxServer(args);
|
||||||
};
|
};
|
||||||
this.createOperationRouter = function (sid, mid, server, odfContainer) {
|
this.createOperationRouter = function (sid, mid, server, odfContainer, errorCallback) {
|
||||||
return new PullBoxOperationRouter(sid, mid, server, odfContainer);
|
return new PullBoxOperationRouter(sid, mid, server, odfContainer, errorCallback);
|
||||||
};
|
};
|
||||||
this.createSessionList = function (server) {
|
this.createSessionList = function (server) {
|
||||||
return new PullBoxSessionList(server);
|
return new PullBoxSessionList(server);
|
||||||
|
@ -111,6 +111,8 @@ define("webodf/editor/server/pullbox/SessionList", [], function () {
|
|||||||
} else {
|
} else {
|
||||||
runtime.log("Meh, sessionlist data broken: " + responseData);
|
runtime.log("Meh, sessionlist data broken: " + responseData);
|
||||||
}
|
}
|
||||||
|
}, function() {
|
||||||
|
// ignore error for now
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ define("webodf/editor/widgets/paragraphStyles",
|
|||||||
if (value === "") {
|
if (value === "") {
|
||||||
value = defaultStyleUIId;
|
value = defaultStyleUIId;
|
||||||
}
|
}
|
||||||
select.set('value', value);
|
select.set('value', value, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// events
|
// events
|
||||||
|
24333
js/3rdparty/webodf/webodf-debug.js
vendored
24333
js/3rdparty/webodf/webodf-debug.js
vendored
File diff suppressed because one or more lines are too long
4013
js/3rdparty/webodf/webodf.js
vendored
4013
js/3rdparty/webodf/webodf.js
vendored
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user