Sync with webodf 6c0affd94e1aa9d706ea8aadd3509db04bc17235

* write correct meta-data into every document on save
* remove need for regular polling of data about sessionmember
* fix for spaces entered after a tab
* small performance improvements
pull/1/head
Friedrich W. H. Kossebau 11 years ago committed by Victor Dubiniuk
parent f93edc4e35
commit c0da073794

@ -124,7 +124,20 @@ define("webodf/editor/Editor", [
* @return {undefined}
*/
this.openDocument = function (docUrl, memberId, editorReadyCallback) {
initDocLoading(docUrl, memberId, editorReadyCallback);
initDocLoading(docUrl, memberId, function () {
runtime.loadClass("ops.OpAddMember");
var op = new ops.OpAddMember();
op.init({
memberid: memberId,
setProperties: {
fullName: runtime.tr("Unknown Author"),
color: "black",
imageUrl: "avatar-joe.png"
}
});
session.enqueue([op]);
editorReadyCallback();
});
};
/**
@ -134,6 +147,14 @@ define("webodf/editor/Editor", [
*/
this.closeDocument = function (callback) {
runtime.assert(session, "session should exist here.");
runtime.loadClass("ops.OpRemoveMember");
var op = new ops.OpRemoveMember();
op.init({
memberid: editorSession.sessionController.getInputMemberId()
});
session.enqueue([op]);
session.close(function (err) {
if (err) {
callback(err);
@ -195,16 +216,12 @@ define("webodf/editor/Editor", [
*/
this.openSession = function (sessionId, memberId, editorReadyCallback) {
initDocLoading(server.getGenesisUrl(sessionId), memberId, function () {
var opRouter, memberModel;
// overwrite router and member model
// overwrite router
// TODO: serverFactory should be a backendFactory,
// and there should be a backendFactory for local editing
opRouter = serverFactory.createOperationRouter(sessionId, memberId, server, odfCanvas.odfContainer());
var opRouter = serverFactory.createOperationRouter(sessionId, memberId, server, odfCanvas.odfContainer());
session.setOperationRouter(opRouter);
memberModel = serverFactory.createMemberModel(sessionId, server);
session.setMemberModel(memberModel);
opRouter.requestReplay(function done() {
editorReadyCallback();
});

@ -82,8 +82,11 @@ define("webodf/editor/EditorSession", [
domUtils = new core.DomUtils(),
eventNotifier = new core.EventNotifier([
EditorSession.signalMemberAdded,
EditorSession.signalMemberUpdated,
EditorSession.signalMemberRemoved,
EditorSession.signalCursorAdded,
EditorSession.signalCursorMoved,
EditorSession.signalCursorRemoved,
EditorSession.signalParagraphChanged,
EditorSession.signalCommonStyleCreated,
EditorSession.signalCommonStyleDeleted,
@ -211,13 +214,25 @@ define("webodf/editor/EditorSession", [
paragraphRange.detach();
}
function onMemberAdded(member) {
self.emit(EditorSession.signalMemberAdded, member.getMemberId());
}
function onMemberUpdated(member) {
self.emit(EditorSession.signalMemberUpdated, member.getMemberId());
}
function onMemberRemoved(memberId) {
self.emit(EditorSession.signalMemberRemoved, memberId);
}
function onCursorAdded(cursor) {
self.emit(EditorSession.signalMemberAdded, cursor.getMemberId());
self.emit(EditorSession.signalCursorAdded, cursor.getMemberId());
trackCursor(cursor);
}
function onCursorRemoved(memberId) {
self.emit(EditorSession.signalMemberRemoved, memberId);
self.emit(EditorSession.signalCursorRemoved, memberId);
}
function onCursorMoved(cursor) {
@ -267,14 +282,6 @@ define("webodf/editor/EditorSession", [
eventNotifier.unsubscribe(eventid, cb);
};
this.getMemberDetailsAndUpdates = function (memberId, subscriber) {
return session.getMemberModel().getMemberDetailsAndUpdates(memberId, subscriber);
};
this.unsubscribeMemberDetailsUpdates = function (memberId, subscriber) {
return session.getMemberModel().unsubscribeMemberDetailsUpdates(memberId, subscriber);
};
this.getCursorPosition = function () {
return odtDocument.getCursorPosition(localMemberId);
};
@ -513,6 +520,14 @@ define("webodf/editor/EditorSession", [
self.sessionController.getImageManager().insertImage(mimetype, content, width, height);
};
/**
* @param {!string} memberId
* @return {?ops.Member}
*/
this.getMember = function (memberId) {
return odtDocument.getMember(memberId);
};
/**
* @param {!function(!Object=)} callback, passing an error object in case of error
* @return {undefined}
@ -522,6 +537,9 @@ define("webodf/editor/EditorSession", [
head.removeChild(fontStyles);
odtDocument.unsubscribe(ops.OdtDocument.signalMemberAdded, onMemberAdded);
odtDocument.unsubscribe(ops.OdtDocument.signalMemberUpdated, onMemberUpdated);
odtDocument.unsubscribe(ops.OdtDocument.signalMemberRemoved, onMemberRemoved);
odtDocument.unsubscribe(ops.OdtDocument.signalCursorAdded, onCursorAdded);
odtDocument.unsubscribe(ops.OdtDocument.signalCursorRemoved, onCursorRemoved);
odtDocument.unsubscribe(ops.OdtDocument.signalCursorMoved, onCursorMoved);
@ -578,6 +596,9 @@ define("webodf/editor/EditorSession", [
self.availableFonts = getAvailableFonts();
selectionViewManager.registerCursor(shadowCursor, true);
// Custom signals, that make sense in the Editor context. We do not want to expose webodf's ops signals to random bits of the editor UI.
odtDocument.subscribe(ops.OdtDocument.signalMemberAdded, onMemberAdded);
odtDocument.subscribe(ops.OdtDocument.signalMemberUpdated, onMemberUpdated);
odtDocument.subscribe(ops.OdtDocument.signalMemberRemoved, onMemberRemoved);
odtDocument.subscribe(ops.OdtDocument.signalCursorAdded, onCursorAdded);
odtDocument.subscribe(ops.OdtDocument.signalCursorRemoved, onCursorRemoved);
odtDocument.subscribe(ops.OdtDocument.signalCursorMoved, onCursorMoved);
@ -592,7 +613,10 @@ define("webodf/editor/EditorSession", [
};
/**@const*/EditorSession.signalMemberAdded = "memberAdded";
/**@const*/EditorSession.signalMemberUpdated = "memberUpdated";
/**@const*/EditorSession.signalMemberRemoved = "memberRemoved";
/**@const*/EditorSession.signalCursorAdded = "cursorAdded";
/**@const*/EditorSession.signalCursorRemoved = "cursorRemoved";
/**@const*/EditorSession.signalCursorMoved = "cursorMoved";
/**@const*/EditorSession.signalParagraphChanged = "paragraphChanged";
/**@const*/EditorSession.signalCommonStyleCreated = "styleCreated";

@ -73,14 +73,14 @@ define("webodf/editor/MemberListView",
while (node) {
if (node.localName === "img") {
// update avatar image
node.src = memberDetails.imageurl;
node.src = memberDetails.imageUrl;
// update border color
node.style.borderColor = memberDetails.color;
} else if (node.localName === "span" && memberDetails.imageurl){
$(node).avatar(memberDetails.imageurl, 60);
node.style.borderColor = memberDetails.color;
} else if (node.localName === "div") {
node.setAttribute('fullname', memberDetails.fullname);
node.setAttribute('fullname', memberDetails.fullName);
}
node = node.nextSibling;
}
@ -142,8 +142,21 @@ define("webodf/editor/MemberListView",
* @return {undefined}
*/
function addMember(memberId) {
var member = editorSession.getMember(memberId),
properties = member.getProperties();
createAvatarButton(memberId);
editorSession.getMemberDetailsAndUpdates(memberId, updateAvatarButton);
updateAvatarButton(memberId, properties);
}
/**
* @param {!string} memberId
* @return {undefined}
*/
function updateMember(memberId) {
var member = editorSession.getMember(memberId),
properties = member.getProperties();
updateAvatarButton(memberId, properties);
}
/**
@ -151,7 +164,6 @@ define("webodf/editor/MemberListView",
* @return {undefined}
*/
function removeMember(memberId) {
editorSession.unsubscribeMemberDetailsUpdates(memberId, updateAvatarButton);
removeAvatarButton(memberId);
}
@ -161,14 +173,12 @@ define("webodf/editor/MemberListView",
if (editorSession) {
// unsubscribe from editorSession
editorSession.unsubscribe(EditorSession.signalMemberAdded, addMember);
editorSession.unsubscribe(EditorSession.signalMemberUpdated, updateMember);
editorSession.unsubscribe(EditorSession.signalMemberRemoved, removeMember);
// remove all current avatars
node = memberListDiv.firstChild;
while (node) {
nextNode = node.nextSibling;
if (node.memberId) {
editorSession.unsubscribeMemberDetailsUpdates(node.memberId, updateAvatarButton);
}
memberListDiv.removeChild(node);
node = nextNode;
}
@ -185,6 +195,7 @@ define("webodf/editor/MemberListView",
editorSession = session;
if (editorSession) {
editorSession.subscribe(EditorSession.signalMemberAdded, addMember);
editorSession.subscribe(EditorSession.signalMemberUpdated, updateMember);
editorSession.subscribe(EditorSession.signalMemberRemoved, removeMember);
}
};

@ -57,13 +57,6 @@ ServerFactory.prototype.createServer = function () {"use strict"; };
*/
ServerFactory.prototype.createOperationRouter = function (sessionId, memberId, server, odfContainer) {"use strict"; };
/**
* @param {!string} sessionId
* @param {!ops.Server} server
* @return {!ops.MemberModel}
*/
ServerFactory.prototype.createMemberModel = function (sessionId, server) {"use strict"; };
/**
* @param {!ops.Server} server
* @return {!SessionList}

@ -27,10 +27,9 @@
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) {
function (PullBoxServer, PullBoxOperationRouter, PullBoxSessionList) {
"use strict";
/**
@ -54,9 +53,6 @@ define("webodf/editor/server/owncloud/ServerFactory", [
this.createOperationRouter = function (sid, mid, server, odfContainer) {
return new PullBoxOperationRouter(sid, mid, server, odfContainer);
};
this.createMemberModel = function (sid, server) {
return new PullBoxMemberModel(sid, server);
};
this.createSessionList = function (server) {
return new PullBoxSessionList(server);
};

@ -1,279 +0,0 @@
/**
* @license
* Copyright (C) 2013 KO GmbH <copyright@kogmbh.com>
*
* @licstart
* This file is part of WebODF.
*
* WebODF 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.
*
* WebODF is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
* @licend
*
* @source: http://www.webodf.org/
* @source: https://github.com/kogmbh/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);
} else {
// pass temporary data
subscriber(memberId, {
memberid: memberId,
fullname: "Unknown",
color: "black",
imageurl: ""
});
}
};
/**
* 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");
};
});

@ -410,8 +410,8 @@ runtime.log("OperationRouter: instant opsSync requested");
var timedOp,
opspec = op.spec();
// note if any local ops modified TODO: find less fragile way, perhaps have the operationFactory check it?
hasPushedModificationOps = hasPushedModificationOps || !/^(AddCursor|MoveCursor|RemoveCursor)$/.test(opspec.optype);
// note if any local ops modified
hasPushedModificationOps = hasPushedModificationOps || op.isEdit;
// apply locally
opspec.timestamp = (new Date()).getTime();

@ -57,9 +57,7 @@ define("webodf/editor/server/pullbox/Server", [], function () {
*/
function call(message, cb) {
var xhr = new XMLHttpRequest(),
byteArrayWriter = new core.ByteArrayWriter("utf8"),
messageString = JSON.stringify(message),
data;
messageString = JSON.stringify(message);
function handleResult() {
if (xhr.readyState === 4) {
@ -73,9 +71,6 @@ define("webodf/editor/server/pullbox/Server", [], function () {
}
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);
@ -83,20 +78,8 @@ runtime.log("Sending message to server: "+messageString);
xhr.setRequestHeader("requesttoken", token);
}
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);
}
xhr.send(messageString);
} catch (e) {
runtime.log("Problem with calling server: " + e + " " + data);
cb(e.message);

@ -27,10 +27,9 @@
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) {
function (PullBoxServer, PullBoxOperationRouter, PullBoxSessionList) {
"use strict";
/**
@ -44,9 +43,6 @@ define("webodf/editor/server/pullbox/ServerFactory", [
this.createOperationRouter = function (sid, mid, server, odfContainer) {
return new PullBoxOperationRouter(sid, mid, server, odfContainer);
};
this.createMemberModel = function (sid, server) {
return new PullBoxMemberModel(sid, server);
};
this.createSessionList = function (server) {
return new PullBoxSessionList(server);
};

@ -48,7 +48,7 @@ define("webodf/editor/widgets/annotation", [
var self = this,
widget = {},
addAnnotationButton,
annotationManager;
annotationController;
addAnnotationButton = new Button({
@ -57,8 +57,8 @@ define("webodf/editor/widgets/annotation", [
showLabel: false,
iconClass: 'dijitIconBookmark',
onClick: function () {
if (annotationManager) {
annotationManager.addAnnotation();
if (annotationController) {
annotationController.addAnnotation();
self.onToolDone();
}
}
@ -83,14 +83,14 @@ define("webodf/editor/widgets/annotation", [
}
this.setEditorSession = function (session) {
if (annotationManager) {
annotationManager.unsubscribe(gui.AnnotationManager.annotatableChanged, onAnnotatableChanged);
if (annotationController) {
annotationController.unsubscribe(gui.AnnotationController.annotatableChanged, onAnnotatableChanged);
}
annotationManager = session && session.sessionController.getAnnotationManager();
if (annotationManager) {
annotationManager.subscribe(gui.AnnotationManager.annotatableChanged, onAnnotatableChanged);
annotationController = session && session.sessionController.getAnnotationController();
if (annotationController) {
annotationController.subscribe(gui.AnnotationController.annotatableChanged, onAnnotatableChanged);
}
onAnnotatableChanged(annotationManager && annotationManager.isAnnotatable());
onAnnotatableChanged(annotationController && annotationController.isAnnotatable());
};
this.onToolDone = function () {};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save