changeset in labs/bespinclient: Added the current version of col...

1 view
Skip to first unread message

Eugene Lazutkin

unread,
Jul 12, 2010, 1:42:30 PM7/12/10
to bespin-...@googlegroups.com
changeset 2ddb31ca89ae in /repo/hg/mozilla/labs/bespinclient
details: http://hg.mozilla.org/labs/bespinclient?cmd=changeset;node=2ddb31ca89ae
description:
Added the current version of collab:
1) mobwrite is still acting up :-(
2) UI is all based on notifications.
3) social commands work as expected.

diffstat:

plugins/labs/collab/index.js | 589 +++++++++++
plugins/labs/collab/mobwrite/core.js | 1509 ++++++++++++++++++++++++++++++
plugins/labs/collab/package.json | 273 +++++
plugins/labs/collab/resources/social.css | 82 +
plugins/labs/collab/social.js | 759 +++++++++++++++
plugins/labs/collab/user.js | 192 +++
plugins/labs/collab/view.js | 303 ++++++
7 files changed, 3707 insertions(+), 0 deletions(-)

diffs (truncated from 3743 to 300 lines):

diff --git a/plugins/labs/collab/index.js b/plugins/labs/collab/index.js
new file mode 100644
--- /dev/null
+++ b/plugins/labs/collab/index.js
@@ -0,0 +1,589 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Bespin.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Bespin Team (bes...@mozilla.com)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+
+I took out from package.json following extension points:
+
+ {
+ "ep": "appcomponent",
+ "name": "social_view",
+ "pointer": "view#SocialView"
+ },
+ {
+ "ep": "bufferFileChanged",
+ "pointer": "#mobwriteFileChanged"
+ },
+
+*/
+
+/**
+ * This session module provides functionality that both stores session
+ * information and handle collaboration.
+ */
+
+var console = require('bespin:console').console;
+var env = require('environment').env;
+var project = require('project');
+var server = require('bespin_server').server;
+
+var mobwrite = require('collab:mobwrite/core').mobwrite;
+var diff_match_patch = require('diff');
+
+var m_view = require('collab:view');
+
+/**
+ * Mobwrite has a set of shareObjs which are designed to wrap DOM nodes.
+ * This creates a fake DOM node to be wrapped in a Mobwrite ShareObj.
+ * @param onFirstSync a function to call when the first sync has happened
+ * This allows us to support onSuccess. onFirstSync should NOT be null or
+ * some of the logic below might break.
+ */
+var ShareNode = function() {
+ this.username = env.session.currentUser;
+ this.project = project.getProjectAndPath(env.file.path);
+ var projectname = this.project[0].name;
+ if (projectname.indexOf('+') < 0) {
+ // add username
+ projectname = this.username + '+' + projectname;
+ }
+ mobwrite.shareObj.call(this, projectname + '/' + this.project[1]);
+ this.onFirstSync = function() { };
+};
+
+ShareNode.prototype = {
+
+ onFirstSync: null,
+ errorRaised: false,
+ pausedText: '',
+
+ /**
+ * What is the contents of the editor?
+ */
+ getClientText: function(allowUnsynced) {
+ if (!allowUnsynced && this.onFirstSync) {
+ console.trace();
+ throw new Error('Attempt to getClientText() before onFirstSync() called.');
+ }
+ return env.model.getValue();
+ },
+
+ /**
+ * Called by mobwrite when it (correctly) assumes that we start blank and
+ * that there are therefore no changes to make, however we need call
+ * things like onSuccess.
+ */
+ syncWithoutChange: function() {
+ this.syncDone();
+ },
+
+ /**
+ * Nasty hack to allow the editor to know that something has changed.
+ * In the first instance the use is restricted to calling the loaded
+ * callback
+ */
+ syncDone: function() {
+ if (this.onFirstSync) {
+ this.onFirstSync();
+ delete this.onFirstSync;
+ }
+
+ if (this.errorRaised) {
+ if (!this.readOnlyStateBeforeError) {
+ // TODO: how to replace it?
+ //this.editor.setReadOnly(false);
+ }
+ this.errorRaised = false;
+ }
+ },
+
+ /**
+ * Notification used by mobwrite to announce an update.
+ * Used by startSession to detect when it is safe to fire onSuccess
+ */
+ setClientText: function(text) {
+ var cursor = this.captureCursor();
+ env.model.setValue(text);
+ this.restoreCursor(cursor);
+
+ this.syncDone();
+ },
+
+ /**
+ * Set the read-only flag on the editor
+ */
+ setReadOnly: function(readonly) {
+ // TODO: how to replace it?
+ //this.editor.setReadOnly(readonly);
+ },
+
+ /**
+ * The session handles the collaborators side-bar
+ */
+ reportCollaborators: function(userEntries) {
+ var social = m_view.social;
+ if (social) {
+ var list = social.getPath('topLeftView.contentView');
+ var content = [];
+ // we can have many "dead" sessions, which leads to duplication of users.
+ var seen = {}, username = this.username;
+ userEntries.forEach(function (user) {
+ if (user.handle != username && !seen.hasOwnProperty(user.handle)) {
+ seen[user.handle] = true;
+ content.push(user.handle);
+ }
+ });
+ content.sort();
+ content.unshift(username); // we are #1!
+ var old = list.get('content');
+ if (old.length == content.length) {
+ var same = old.every(function (username, i) {
+ return username == content[i];
+ });
+ if (same) {
+ // bail out
+ return;
+ }
+ }
+ list.set('content', content);
+ }
+ },
+
+ /**
+ * Something in mobwrite has died. Attempt to tell the user and go into
+ * read-only mode if it's fatally broken
+ */
+ raiseError: function(text, recoverable) {
+ text = text || '';
+ var prefix = '<strong>' + (recoverable ? '' : 'Fatal ') + 'Collaboration Error</strong>: ';
+ var suffix = '<br/><strong>Warning</strong>: Changes since the last sync could be lost';
+
+ if (!this.errorRaised) {
+ // TODO: how to replace it?
+ //this.readOnlyStateBeforeError = this.editor.readonly;
+ //this.editor.setReadOnly(true);
+ this.errorRaised = true;
+ }
+ },
+
+ /**
+ * Called by mobwrite to apply patches
+ */
+ patchClientText: function(patches) {
+ // Set some constants which tweak the matching behavior.
+ // Maximum distance to search from expected location.
+ this.dmp.Match_Distance = 1000;
+ // At what point is no match declared (0.0 = perfection, 1.0 = very loose)
+ this.dmp.Match_Threshold = 0.6;
+
+ var oldClientText = this.getClientText(true);
+ var cursor = this.captureCursor();
+ // Pack the cursor offsets into an array to be adjusted.
+ // See http://neil.fraser.name/writing/cursor/
+ var offsets = [];
+ if (cursor) {
+ offsets[0] = cursor.startOffset;
+ if ('endOffset' in cursor) {
+ offsets[1] = cursor.endOffset;
+ }
+ }
+
+ var newClientText = this._patchApply(patches, oldClientText, offsets);
+ // Set the new text only if there is a change to be made.
+ if (oldClientText != newClientText) {
+ env.model.setValue(newClientText);
+ if (cursor) {
+ // Unpack the offset array.
+ cursor.startOffset = offsets[0];
+ if (offsets.length > 1) {
+ cursor.endOffset = offsets[1];
+ if (cursor.startOffset >= cursor.endOffset) {
+ cursor.collapsed = true;
+ }
+ }
+ this.restoreCursor(cursor);
+ }
+ }
+
+ this.syncDone();
+ },
+
+ /**
+ * Merge a set of patches onto the text. Return a patched text.
+ * This is taken from mobwrite.shareTextareaObj.prototype.patch_apply_
+ * and we should find a better way to share. Maybe shareBespinObj should
+ * inherit from shareTextareaObj? In the mean time we need to take extra
+ * care when doing merges
+ * @param {Array.<patch_obj>} patches Array of patch objects.
+ * @param {string} text Old text.
+ * @param {Array.<number>} offsets Offset indices to adjust.
+ * @return {string} New text.
+ * @private
+ */
+ _patchApply: function(patches, text, offsets) {
+ if (patches.length == 0) {
+ return text;
+ }
+
+ // Deep copy the patches so that no changes are made to originals.
+ patches = this.dmp.patch_deepCopy(patches);
+ var nullPadding = this.dmp.patch_addPadding(patches);
+ text = nullPadding + text + nullPadding;
+
+ this.dmp.patch_splitMax(patches);
+
+ // delta keeps track of the offset between the expected and actual
+ // location of the previous patch. If there are patches expected at
+ // positions 10 and 20, but the first patch was found at 12, delta is 2
+ // and the second patch has an effective expected position of 22.
+ var delta = 0;
+ for (var x = 0; x < patches.length; x++) {
+ var expected_loc = patches[x].start2 + delta;
+ var text1 = this.dmp.diff_text1(patches[x].diffs);
+ var start_loc = this.dmp.match_main(text, text1, expected_loc);
+ if (start_loc == -1) {
+ // No match found. :(
+ if (mobwrite.debug) {
+ window.console.warn('Patch failed: ' + patches[x]);
+ }
+ } else {
+ // Found a match. :)
+ delta = start_loc - expected_loc;
+ var text2 = text.substring(start_loc, start_loc + text1.length);
+
+ // Run a diff to get a framework of equivalent indices.
+ var diffs = this.dmp.diff_main(text1, text2, false);
+ var index1 = 0;
+ var index2;
+ var i;
+ for (var y = 0; y < patches[x].diffs.length; y++) {
+ var mod = patches[x].diffs[y];
+ if (mod[0] !== diff_match_patch.DIFF_EQUAL) {

Reply all
Reply to author
Forward
0 new messages