diff --git a/ChainPadSrv.js b/ChainPadSrv.js
index 1054670a8..1def6ee12 100644
--- a/ChainPadSrv.js
+++ b/ChainPadSrv.js
@@ -103,7 +103,7 @@ var dropClient = function (ctx, userpass) {
var chanName = client.channels[i];
var chan = ctx.channels[chanName];
var idx = chan.indexOf(client);
- if (idx < 0) { throw new Error(); }
+ if (idx < 0) { continue; }
console.log("Removing ["+client.userName+"] from channel ["+chanName+"]");
chan.splice(idx, 1);
if (chan.length === 0) {
@@ -122,6 +122,7 @@ var handleMessage = function (ctx, socket, msg) {
msg = popPassword(msg);
if (parsed.content[0] === REGISTER) {
+ if (parsed.user.length === 0) { throw new Error(); }
console.log("[" + userPass + "] registered");
var client = ctx.registeredClients[userPass] = ctx.registeredClients[userPass] || {
channels: [parsed.channelId],
@@ -131,17 +132,27 @@ console.log("[" + userPass + "] registered");
client.socket = socket;
var chan = ctx.channels[parsed.channelId] = ctx.channels[parsed.channelId] || [];
+ var newChan = (chan.length === 0);
chan.name = parsed.channelId;
- chan.push(client);
// we send a register ack right away but then we fallthrough
// to let other users know that we were registered.
sendMsg(mkMessage('', parsed.channelId, [1,0]), socket);
- sendChannelMessage(ctx, chan, msg, function () {
- ctx.store.getMessages(chan.name, function (msg) {
- sendMsg(msg, socket);
+
+ var sendMsgs = function () {
+ sendChannelMessage(ctx, chan, msg, function () {
+ chan.push(client);
+ ctx.store.getMessages(chan.name, function (msg) {
+ sendMsg(msg, socket);
+ });
});
- });
+ };
+ if (newChan) {
+ sendChannelMessage(ctx, chan, mkMessage('', chan.name, [DISCONNECT,0]), sendMsgs);
+ } else {
+ sendMsgs();
+ }
+ return;
}
if (parsed.content[0] === PING) {
diff --git a/Storage.js b/Storage.js
index 91cf2bd87..23f070afb 100644
--- a/Storage.js
+++ b/Storage.js
@@ -31,7 +31,7 @@ var insert = function (coll, channelName, content, cb) {
};
var getMessages = function (coll, channelName, cb) {
- coll.find({chan:channelName}).forEach(function (doc) {
+ coll.find({chan:channelName}).sort( { _id: 1 } ).forEach(function (doc) {
cb(doc.msg);
}, function (err) {
if (!err) { return; }
diff --git a/bower.json b/bower.json
index c5da7135c..c19dc8277 100644
--- a/bower.json
+++ b/bower.json
@@ -21,6 +21,7 @@
"jquery": "~2.1.1",
"tweetnacl": "~0.12.2",
"ckeditor": "~4.4.5",
- "requirejs": "~2.1.15"
+ "requirejs": "~2.1.15",
+ "modalBox": "~1.0.2"
}
}
diff --git a/www/chainpad.js b/www/chainpad.js
index f6d5b59f8..9e7909616 100644
--- a/www/chainpad.js
+++ b/www/chainpad.js
@@ -933,6 +933,11 @@ var handleMessage = ChainPad.handleMessage = function (realtime, msgStr) {
}
if (msg.messageType === Message.DISCONNECT) {
+ if (msg.userName === '') {
+ realtime.userList = [];
+ userListChange(realtime);
+ return;
+ }
var idx = realtime.userList.indexOf(msg.userName);
if (Common.PARANOIA) { Common.assert(idx > -1); }
if (idx > -1) {
diff --git a/www/errorbox.js b/www/errorbox.js
new file mode 100644
index 000000000..74a6b6f8d
--- /dev/null
+++ b/www/errorbox.js
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2014 XWiki SAS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program. If not, see
',
- 'This is CryptPad, the zero knowledge realtime collaborative editor.',
- '
',
- 'What you type here is encrypted so only people who have the link can access it.',
- '
',
- 'Even the server cannot see what you type.',
- '
', - '', - 'What you see here, what you hear here, when you leave here, let it stay here', - '', - '
', - ].join(''); - var module = { exports: {} }; var parseKey = function (str) { @@ -54,12 +40,11 @@ define([ editor.on('instanceReady', function () { editor.execCommand('maximize'); var ifr = window.ifr = $('iframe')[0]; - ifr.contentDocument.body.innerHTML = INITIAL_STATE; + ifr.contentDocument.body.innerHTML = Messages.initialState; var rtw = RTWysiwyg.start(Config.websocketURL, userName(), - {}, Nacl.util.encodeBase64(key.lookupKey).substring(0,10), key.cryptKey); editor.on('change', function () { rtw.onEvent(); }); diff --git a/www/messages.js b/www/messages.js new file mode 100644 index 000000000..f56ee82a3 --- /dev/null +++ b/www/messages.js @@ -0,0 +1,33 @@ +define(function () { + var out = {}; + + out.errorBox_errorType_disconnected = 'Connection Lost'; + out.errorBox_errorExplanation_disconnected = [ + 'Lost connection to server, you may reconnect by reloading the page or review your work ', + 'by clicking outside of this box.' + ].join(''); + + out.editingAlone = 'Editing alone'; + out.editingWithOneOtherPerson = 'Editing with one other person'; + out.editingWith = 'Editing with'; + out.otherPeople = 'other people'; + out.disconnected = 'Disconnected'; + out.lag = 'Lag'; + + out.initialState = [ + '',
+ 'This is CryptPad, the zero knowledge realtime collaborative editor.',
+ '
',
+ 'What you type here is encrypted so only people who have the link can access it.',
+ '
',
+ 'Even the server cannot see what you type.',
+ '
', + '', + 'What you see here, what you hear here, when you leave here, let it stay here', + '', + '
', + ].join(''); + + return out; +}); diff --git a/www/realtime-wysiwyg.js b/www/realtime-wysiwyg.js index 33db9883f..aff3e1a61 100644 --- a/www/realtime-wysiwyg.js +++ b/www/realtime-wysiwyg.js @@ -16,12 +16,17 @@ */ define([ 'html-patcher', + 'errorbox', + 'messages', 'rangy', 'chainpad', 'otaml', 'bower/jquery/dist/jquery.min', 'bower/tweetnacl/nacl-fast.min' -], function (HTMLPatcher) { +], function (HTMLPatcher, ErrorBox, Messages) { + +window.ErrorBox = ErrorBox; + var $ = window.jQuery; var Rangy = window.rangy; Rangy.init(); @@ -29,12 +34,8 @@ define([ var Otaml = window.Otaml; var Nacl = window.nacl; -var ErrorBox = {}; - var PARANOIA = true; - - var module = { exports: {} }; /** @@ -106,26 +107,34 @@ var ErrorBox = {}; }; var isSocketDisconnected = function (socket, realtime) { - return socket._socket.readyState === socket.CLOSING - || socket._socket.readyState === socket.CLOSED + var sock = socket._socket; + return sock.readyState === sock.CLOSING + || sock.readyState === sock.CLOSED || (realtime.getLag().waiting && realtime.getLag().lag > MAX_LAG_BEFORE_DISCONNECT); }; - var updateUserList = function (myUserName, listElement, userList, messages) { + var updateUserList = function (myUserName, listElement, userList) { var meIdx = userList.indexOf(myUserName); if (meIdx === -1) { - listElement.text(messages.disconnected); + listElement.text(Messages.disconnected); return; } - listElement.text(messages.editingWith + ' ' + (userList.length - 1) + ' people'); + if (userList.length === 1) { + listElement.text(Messages.editingAlone); + } else if (userList.length === 2) { + listElement.text(Messages.editingWithOneOtherPerson); + } else { + listElement.text(Messages.editingWith + ' ' + (userList.length - 1) + + Messages.otherPeople); + } }; - var createUserList = function (realtime, myUserName, container, messages) { + var createUserList = function (realtime, myUserName, container) { var id = uid(); $(container).prepend(''); var listElement = $('#'+id); realtime.onUserListChange(function (userList) { - updateUserList(myUserName, listElement, userList, messages); + updateUserList(myUserName, listElement, userList); }); return listElement; }; @@ -269,10 +278,10 @@ var ErrorBox = {}; return 'rtwysiwyg-uid-' + String(Math.random()).substring(2); }; - var checkLag = function (realtime, lagElement, messages) { + var checkLag = function (realtime, lagElement) { var lag = realtime.getLag(); var lagSec = lag.lag/1000; - var lagMsg = messages.lag + ' '; + var lagMsg = Messages.lag + ' '; if (lag.waiting && lagSec > 1) { lagMsg += "?? " + Math.floor(lagSec); } else { @@ -281,12 +290,12 @@ var ErrorBox = {}; lagElement.text(lagMsg); }; - var createLagElement = function (socket, realtime, container, messages) { + var createLagElement = function (socket, realtime, container) { var id = uid(); $(container).append(''); var lagElement = $('#'+id); var intr = setInterval(function () { - checkLag(realtime, lagElement, messages); + checkLag(realtime, lagElement); }, 3000); socket.onClose.push(function () { clearTimeout(intr); }); return lagElement; @@ -306,11 +315,11 @@ var ErrorBox = {}; '.' + TOOLBAR_CLS + ' {', ' color: #666;', ' font-weight: bold;', - ' background-color: #f0f0ee;', - ' border-bottom: 1px solid #DDD;', - ' border-top: 3px solid #CCC;', - ' border-right: 2px solid #CCC;', - ' border-left: 2px solid #CCC;', +// ' background-color: #f0f0ee;', +// ' border-bottom: 1px solid #DDD;', +// ' border-top: 3px solid #CCC;', +// ' border-right: 2px solid #CCC;', +// ' border-left: 2px solid #CCC;', ' height: 26px;', ' margin-bottom: -3px;', ' display: inline-block;', @@ -319,7 +328,7 @@ var ErrorBox = {}; '.' + TOOLBAR_CLS + ' div {', ' padding: 0 10px;', ' height: 1.5em;', - ' background: #f0f0ee;', +// ' background: #f0f0ee;', ' line-height: 25px;', ' height: 22px;', '}', @@ -338,6 +347,7 @@ var ErrorBox = {}; '.' + DEBUG_LINK_CLS + ':link { color:transparent; }', '.' + DEBUG_LINK_CLS + ':link:hover { color:blue; }', '.gwt-TabPanelBottom { border-top: 0 none; }', + '' ].join('\n')); return toolbar; @@ -417,8 +427,7 @@ var ErrorBox = {}; return spl[0] + res.length + ':' + res; }; - var start = module.exports.start = - function (websocketUrl, userName, messages, channel, cryptKey) + var start = module.exports.start = function (websocketUrl, userName, channel, cryptKey) { var passwd = 'y'; var wysiwygDiv = document.getElementById('cke_1_contents'); @@ -427,10 +436,11 @@ var ErrorBox = {}; var socket = makeWebsocket(websocketUrl); var onEvent = function () { }; - var toolbar = createRealtimeToolbar('#xwikieditcontent'); + var toolbar = createRealtimeToolbar('#cke_1_toolbox'); socket.onClose.push(function () { $(toolbar).remove(); + checkSocket(); }); var allMessages = []; @@ -474,17 +484,15 @@ var ErrorBox = {}; getDocHTML(doc), { transformFunction: Otaml.transform }); - //createDebugLink(realtime, doc, allMessages, toolbar, messages); + //createDebugLink(realtime, doc, allMessages, toolbar); createLagElement(socket, realtime, - toolbar.find('.rtwysiwyg-toolbar-rightside'), - messages); + toolbar.find('.rtwysiwyg-toolbar-rightside')); createUserList(realtime, userName, - toolbar.find('.rtwysiwyg-toolbar-leftside'), - messages); + toolbar.find('.rtwysiwyg-toolbar-leftside')); onEvent = function () { if (isErrorState) { return; } @@ -522,11 +530,10 @@ var ErrorBox = {}; }; realtime.onUserListChange(function (userList) { - if (!initializing && userList.indexOf(userName) === -1) { return; } + if (!initializing || userList.indexOf(userName) === -1) { return; } // if we spot ourselves being added to the document, we'll switch // 'initializing' off because it means we're fully synced. initializing = false; - userDocBeforePatch = realtime.getUserDoc(); incomingPatch(); });