From 60d337192b9e0a37f49075741d45d737b8423c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Fri, 11 Oct 2013 18:09:52 +0100 Subject: [PATCH 01/29] Cursor spike --- dispatch-socket.js | 79 ++++++++++++++++++++++++++++++++++++++ examples/index.html | 10 ++++- examples/server.js | 26 ++++++++++++- share-codemirror-cursor.js | 64 ++++++++++++++++++++++++++++++ share-codemirror.js | 10 +++-- 5 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 dispatch-socket.js create mode 100644 share-codemirror-cursor.js diff --git a/dispatch-socket.js b/dispatch-socket.js new file mode 100644 index 0000000..eba759b --- /dev/null +++ b/dispatch-socket.js @@ -0,0 +1,79 @@ +(function () { + 'use strict'; + + /** + * A DispatchSocket is a drop-in replacement for a BCSocket or WebSocket with some additional capabilities. + * + * Incoming messages can be dispatched to custom event handlers by assigning `on_xxx` event handlers. + * This causes incoming messages with a `_type: 'xxx'` attribute to be routed to that handler instead of + * the default `onmessage` handler. + * + * This makes it possible to use a single connection as a transport for different kinds of messages. + * For example, ShareJS messages will be routed as usual, but messages with a `_type` attribute will + * be routed to other event handlers so that it doesn't interfere. + * + * In order for this to work for outgoing messages, a similar dispatch mechanism will have to be implemented + * on the server. + * + * @param {WebSocket} ws - a WebSocket or BCSocket object. + * @constructor + */ + function DispatchSocket(ws) { + var self = this; + + ws.onopen = function () { + self.onopen && self.onopen(); + }; + + ws.onmessage = function (msg) { + var data = msg.data ? msg.data : msg; + if (data._type) { + var handlerName = 'on_' + data._type; + var handler = self[handlerName]; + if (typeof handler === 'function') { + handler.call(self, msg); + } else { + // Deliver in the usual way + self.onmessage && self.onmessage(msg); + } + } else { + // Deliver in the usual way + self.onmessage && self.onmessage(msg); + } + }; + + ws.onerror = function (err) { + self.onerror && self.onerror(err); + }; + + ws.onclose = function () { + self.onclose && self.onclose(); + }; + + this.send = function (msg) { + ws.send(msg); + }; + + this.__defineGetter__('readyState', function () { + return ws.readyState; + }); + } + + // Exporting + if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + // Node.js + module.exports = DispatchSocket; + module.exports.scriptsDir = __dirname; + } else { + if (typeof define === 'function' && define.amd) { + // AMD + define([], function () { + return DispatchSocket; + }); + } else { + // Browser, no AMD + window.DispatchSocket = DispatchSocket; + } + } + +})(); diff --git a/examples/index.html b/examples/index.html index 940e116..e0b89df 100644 --- a/examples/index.html +++ b/examples/index.html @@ -5,7 +5,9 @@ + + @@ -16,8 +18,12 @@ mode: "text/plain" }); -var s = new BCSocket(null, {reconnect: true}); -var sjs = new window.sharejs.Connection(s); +var bcs = new BCSocket(null, {reconnect: true}); +var ds = new DispatchSocket(bcs); +shareCodeMirrorCursor(cm, ds); // TODO: cm.attachSharedCursors(ds); + +var sjs = new window.sharejs.Connection(ds); + var doc = sjs.get('users', 'sephx'); doc.subscribe(); diff --git a/examples/server.js b/examples/server.js index c37edc5..81a056f 100644 --- a/examples/server.js +++ b/examples/server.js @@ -19,7 +19,12 @@ var backend = livedb.client(livedbMongo('localhost:27017/test?auto_reconnect', { var share = sharejs.server.createClient({backend: backend}); +var clientsById = {}; + webserver.use(browserChannel({webserver: webserver}, function (client) { + clientsById[client.id] = client; + client.send({_type: 'connectionId', connectionId: client.id}); + var stream = new Duplex({objectMode: true}); stream._write = function (chunk, encoding, callback) { if (client.state !== 'closed') { @@ -32,15 +37,34 @@ webserver.use(browserChannel({webserver: webserver}, function (client) { stream.headers = client.headers; stream.remoteAddress = stream.address; client.on('message', function (data) { - stream.push(data); + if(data._type) { + data.connectionId = client.id; + for(var clientId in clientsById) { + var c = clientsById[clientId]; + c.send(data); + } + } else { + + if(data.a == 'sub') { + var docId = data.d; + var collectionName = data.c; + // Client client.id is subbing to docId inside collectionName + console.log('SUB', data); + } + + stream.push(data); + } }); stream.on('error', function (msg) { + console.log('ERROR', msg, client.id); client.stop(); }); client.on('close', function (reason) { + console.log('CLOSE', reason, client.id); stream.emit('close'); stream.emit('end'); stream.end(); + delete clientsById[client.id]; }); return share.listen(stream); })); diff --git a/share-codemirror-cursor.js b/share-codemirror-cursor.js new file mode 100644 index 0000000..aeaee12 --- /dev/null +++ b/share-codemirror-cursor.js @@ -0,0 +1,64 @@ +(function () { + 'use strict'; + + function shareCodeMirrorCursor(cm, ds) { + cm.on('cursorActivity', function (doc) { + var startCur = doc.getCursor('start'); + //var endCur = doc.getCursor('end'); + + var from = startCur; + var to = {line: startCur.line, ch: startCur.ch + 1}; + + if (ds) { + ds.send({_type: 'cursor', from: from, to: to}); + } + }); + + var cursorsByConnectionId = {}; + var connectionId; + + ds.on_connectionId = function (msg) { + connectionId = msg.connectionId; + }; + + ds.on_cursor = function (msg) { + if(msg.connectionId == connectionId) return; + console.log(msg); + var cursor = cursorsByConnectionId[connectionId]; + if(cursor === undefined) { + cursor = createCursorWidget(cm); + cursorsByConnectionId[connectionId] = cursor; + } + cm.addWidget(msg.from, cursor); + }; + } + + function createCursorWidget(cm) { + var square = document.createElement('div'); + square.style.width = cm.defaultCharWidth() + 'px'; + square.style.height = cm.defaultTextHeight() + 'px'; + square.style.background = '#BBBBFF'; + + var cursor = document.createElement('div'); + cursor.appendChild(square); + return cursor; + } + + // Exporting + if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + // Node.js + module.exports = shareCodeMirrorCursor; + module.exports.scriptsDir = __dirname; + } else { + if (typeof define === 'function' && define.amd) { + // AMD + define([], function () { + return shareCodeMirrorCursor; + }); + } else { + // Browser, no AMD + window.shareCodeMirrorCursor = shareCodeMirrorCursor; + } + } + +})(); diff --git a/share-codemirror.js b/share-codemirror.js index 1529292..9f66c7c 100644 --- a/share-codemirror.js +++ b/share-codemirror.js @@ -1,6 +1,10 @@ (function () { 'use strict'; + /** + * @param cm - CodeMirror instance + * @param ctx - Share context + */ function shareCodeMirror(cm, ctx) { if (!ctx.provides.text) throw new Error('Cannot attach to non-text document'); @@ -94,14 +98,14 @@ module.exports.scriptsDir = __dirname; } else { if (typeof define === 'function' && define.amd) { - // Require.js & co + // AMD define([], function () { return shareCodeMirror; }); } else { // Browser, no AMD - window.sharejs.Doc.prototype.attachCodeMirror = function(cm, ctx) { - if(!ctx) ctx = this.createContext(); + window.sharejs.Doc.prototype.attachCodeMirror = function (cm, ctx) { + if (!ctx) ctx = this.createContext(); shareCodeMirror(cm, ctx); }; } From 28a8dcbcf5b4aac723f53e8e8eb1c06d56dfa983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Tue, 3 Dec 2013 14:57:18 +0000 Subject: [PATCH 02/29] move cursor up one line --- share-codemirror-cursor.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/share-codemirror-cursor.js b/share-codemirror-cursor.js index aeaee12..0f2cdfc 100644 --- a/share-codemirror-cursor.js +++ b/share-codemirror-cursor.js @@ -23,7 +23,6 @@ ds.on_cursor = function (msg) { if(msg.connectionId == connectionId) return; - console.log(msg); var cursor = cursorsByConnectionId[connectionId]; if(cursor === undefined) { cursor = createCursorWidget(cm); @@ -37,8 +36,9 @@ var square = document.createElement('div'); square.style.width = cm.defaultCharWidth() + 'px'; square.style.height = cm.defaultTextHeight() + 'px'; - square.style.background = '#BBBBFF'; - + square.style.top = '-' + cm.defaultTextHeight() + 'px'; + square.style.position = 'relative'; + square.style.background = '#FF00FF'; var cursor = document.createElement('div'); cursor.appendChild(square); return cursor; From 9d72388de923aabf2c7bfe046f91c84226ffbb9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Wed, 4 Dec 2013 08:39:57 +0000 Subject: [PATCH 03/29] Show multiple cursors. Still buggy --- share-codemirror-cursor.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/share-codemirror-cursor.js b/share-codemirror-cursor.js index 0f2cdfc..406db40 100644 --- a/share-codemirror-cursor.js +++ b/share-codemirror-cursor.js @@ -15,19 +15,20 @@ }); var cursorsByConnectionId = {}; - var connectionId; + var myConnectionId; ds.on_connectionId = function (msg) { - connectionId = msg.connectionId; + myConnectionId = msg.connectionId; }; ds.on_cursor = function (msg) { - if(msg.connectionId == connectionId) return; - var cursor = cursorsByConnectionId[connectionId]; + if(msg.connectionId === myConnectionId) return; + var cursor = cursorsByConnectionId[msg.connectionId]; if(cursor === undefined) { cursor = createCursorWidget(cm); - cursorsByConnectionId[connectionId] = cursor; + cursorsByConnectionId[msg.connectionId] = cursor; } + console.log(cursorsByConnectionId); cm.addWidget(msg.from, cursor); }; } From 5207b4c7e21b17bb5972cff01bcd40c0b43502c8 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Thu, 10 Apr 2014 15:40:13 -0700 Subject: [PATCH 04/29] use the new cursor api in share/livedb --- dispatch-socket.js | 79 ------------------------ examples/index.html | 122 ++++++++++++++++++++++++------------ examples/server.js | 5 +- package.json | 7 ++- share-codemirror-cursor.js | 123 +++++++++++++++++++++++++++++-------- 5 files changed, 188 insertions(+), 148 deletions(-) delete mode 100644 dispatch-socket.js diff --git a/dispatch-socket.js b/dispatch-socket.js deleted file mode 100644 index eba759b..0000000 --- a/dispatch-socket.js +++ /dev/null @@ -1,79 +0,0 @@ -(function () { - 'use strict'; - - /** - * A DispatchSocket is a drop-in replacement for a BCSocket or WebSocket with some additional capabilities. - * - * Incoming messages can be dispatched to custom event handlers by assigning `on_xxx` event handlers. - * This causes incoming messages with a `_type: 'xxx'` attribute to be routed to that handler instead of - * the default `onmessage` handler. - * - * This makes it possible to use a single connection as a transport for different kinds of messages. - * For example, ShareJS messages will be routed as usual, but messages with a `_type` attribute will - * be routed to other event handlers so that it doesn't interfere. - * - * In order for this to work for outgoing messages, a similar dispatch mechanism will have to be implemented - * on the server. - * - * @param {WebSocket} ws - a WebSocket or BCSocket object. - * @constructor - */ - function DispatchSocket(ws) { - var self = this; - - ws.onopen = function () { - self.onopen && self.onopen(); - }; - - ws.onmessage = function (msg) { - var data = msg.data ? msg.data : msg; - if (data._type) { - var handlerName = 'on_' + data._type; - var handler = self[handlerName]; - if (typeof handler === 'function') { - handler.call(self, msg); - } else { - // Deliver in the usual way - self.onmessage && self.onmessage(msg); - } - } else { - // Deliver in the usual way - self.onmessage && self.onmessage(msg); - } - }; - - ws.onerror = function (err) { - self.onerror && self.onerror(err); - }; - - ws.onclose = function () { - self.onclose && self.onclose(); - }; - - this.send = function (msg) { - ws.send(msg); - }; - - this.__defineGetter__('readyState', function () { - return ws.readyState; - }); - } - - // Exporting - if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { - // Node.js - module.exports = DispatchSocket; - module.exports.scriptsDir = __dirname; - } else { - if (typeof define === 'function' && define.amd) { - // AMD - define([], function () { - return DispatchSocket; - }); - } else { - // Browser, no AMD - window.DispatchSocket = DispatchSocket; - } - } - -})(); diff --git a/examples/index.html b/examples/index.html index e0b89df..2e46713 100644 --- a/examples/index.html +++ b/examples/index.html @@ -5,13 +5,42 @@ - + + - +
+ +
+
diff --git a/examples/server.js b/examples/server.js index 81a056f..b358a1f 100644 --- a/examples/server.js +++ b/examples/server.js @@ -10,6 +10,7 @@ var webserver = connect( connect["static"](__dirname), connect["static"](shareCodeMirror.scriptsDir), connect["static"](__dirname + '/../node_modules/codemirror/lib'), + connect["static"](__dirname + '/../node_modules/d3'), connect["static"](sharejs.scriptsDir) ); @@ -21,7 +22,7 @@ var share = sharejs.server.createClient({backend: backend}); var clientsById = {}; -webserver.use(browserChannel({webserver: webserver}, function (client) { +webserver.use(browserChannel({webserver: webserver, sessionTimeoutInterval: 5000}, function (client) { clientsById[client.id] = client; client.send({_type: 'connectionId', connectionId: client.id}); @@ -44,14 +45,12 @@ webserver.use(browserChannel({webserver: webserver}, function (client) { c.send(data); } } else { - if(data.a == 'sub') { var docId = data.d; var collectionName = data.c; // Client client.id is subbing to docId inside collectionName console.log('SUB', data); } - stream.push(data); } }); diff --git a/package.json b/package.json index 1b2c933..1dd71c0 100644 --- a/package.json +++ b/package.json @@ -21,14 +21,17 @@ "url": "https://github.com/share/share-codemirror/issues" }, "devDependencies": { - "share": "v0.7.0-alpha9", + "share": "~0.7", "codemirror": "git://github.com/marijnh/CodeMirror.git#master", "connect": "~2.11.0", "browserchannel": "~1.0.8", - "livedb": "~0.2.6", + "livedb": "~0.3", "livedb-mongo": "~0.2.5", "mocha": "~1.14.0", "jsdom": "~0.8.8", "istanbul": "~0.1.44" + }, + "dependencies": { + "d3": "~3.4.5" } } diff --git a/share-codemirror-cursor.js b/share-codemirror-cursor.js index 406db40..3a8af8c 100644 --- a/share-codemirror-cursor.js +++ b/share-codemirror-cursor.js @@ -1,50 +1,120 @@ (function () { 'use strict'; - function shareCodeMirrorCursor(cm, ds) { - cm.on('cursorActivity', function (doc) { - var startCur = doc.getCursor('start'); - //var endCur = doc.getCursor('end'); + function shareCodeMirrorCursor(cm, ctx) { + cm.on('cursorActivity', function (editor) { + var startCur = editor.getCursor('start'); + var endCur = editor.getCursor('end'); var from = startCur; - var to = {line: startCur.line, ch: startCur.ch + 1}; + var to = {line: endCur.line, ch: endCur.ch+1}; - if (ds) { - ds.send({_type: 'cursor', from: from, to: to}); - } + ctx.setSelection([editor.indexFromPos(from), editor.indexFromPos(to)]); }); - var cursorsByConnectionId = {}; + var cursorsBySessionId = {}; + var markersBySessionId = {}; var myConnectionId; - ds.on_connectionId = function (msg) { - myConnectionId = msg.connectionId; + ctx.onPresence = function() { + var presence = ctx.getPresence(); + var sessionIds = Object.keys(presence); + makeUserStyles(presence); + sessionIds.forEach(function(sessionId) { + var session = presence[sessionId]; + displayCursor(sessionId, session); + }) + var cursorIds = Object.keys(cursorsBySessionId); + cursorIds.forEach(function(cid) { + if(sessionIds.indexOf(cid) < 0) { + cursorsBySessionId[cid].parentElement.removeChild(cursorsBySessionId[cid]) + markersBySessionId[cid].clear(); + delete cursorsBySessionId[cid]; + delete markersBySessionId[cid]; + } + }) }; - ds.on_cursor = function (msg) { - if(msg.connectionId === myConnectionId) return; - var cursor = cursorsByConnectionId[msg.connectionId]; + function makeUserStyles(sessions) { + var ids = Object.keys(sessions); + var headStyle = document.getElementById("user-styles"); + if(!headStyle) { + var head = document.getElementsByTagName("head")[0]; + var headStyle = document.createElement("style"); + headStyle.setAttribute("id", "user-styles"); + head.appendChild(headStyle); + } + var style = ""; + for(var i = 0; i < ids.length; i++ ) { + //".user { background: green; } .user-cursor { background: blue; }" + style += ".user-" + ids[i] + " { background: " + (sessions[ids[i]].color || "yellow") + "; }"; + } + headStyle.innerHTML = style; + } + + function displayCursor(sessionId, session) { + // we make a cursor widget to display where the other user's cursor is + var selection = session._selection; + if(!selection) return; + if(typeof selection == "number") selection = [selection, selection]; + var from = cm.posFromIndex(selection[0]); + var to = cm.posFromIndex(selection[1]); + var cursor = cursorsBySessionId[sessionId]; if(cursor === undefined) { - cursor = createCursorWidget(cm); - cursorsByConnectionId[msg.connectionId] = cursor; + cursor = createCursorWidget(cm, sessionId, session); + cursorsBySessionId[sessionId] = cursor; + } else { + updateCursor(cursor, sessionId, session); } - console.log(cursorsByConnectionId); - cm.addWidget(msg.from, cursor); - }; + cm.addWidget(to, cursor); + + // we mark up the range of text the other user has highlighted + var marker = markersBySessionId[sessionId]; + if(marker) { + marker.clear(); + } + markersBySessionId[sessionId] = markCursor(cm, sessionId, to, from); + } + + function markCursor(cm, sessionId, to, from) { + var marker = cm.markText(from, to, { className: "user-" + sessionId }); + return marker; + } } - function createCursorWidget(cm) { + function createCursorWidget(cm, sessionId, session) { var square = document.createElement('div'); - square.style.width = cm.defaultCharWidth() + 'px'; - square.style.height = cm.defaultTextHeight() + 'px'; - square.style.top = '-' + cm.defaultTextHeight() + 'px'; + //square.style.width = 3 + 'px'; + //square.style.height = 3 + 'px'; + square.style.top = '-' + (2.1 * cm.defaultTextHeight()) + 'px'; square.style.position = 'relative'; - square.style.background = '#FF00FF'; + square.style.background = session.color || "black"; + square.classList.add("user-name"); + square.innerHTML = session.name || sessionId; + + var line = document.createElement('div'); + line.style.width = 1 + 'px'; + line.style.height = 0.8 * cm.defaultTextHeight() + 'px'; + line.style.top = '-' + cm.defaultTextHeight() + 'px'; + line.style.position = 'relative'; + line.style.background = session.color || "black"; + line.classList.add("line"); + var cursor = document.createElement('div'); + cursor.style.position = 'absolute'; + cursor.appendChild(line); cursor.appendChild(square); return cursor; } + function updateCursor(cursor, sessionId, session) { + var square = cursor.querySelector(".user-name"); + square.style.background = session.color || "black"; + square.innerHTML = session.name || sessionId; + var line = cursor.querySelector(".line"); + line.style.background = session.color || "black"; + } + // Exporting if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { // Node.js @@ -58,7 +128,10 @@ }); } else { // Browser, no AMD - window.shareCodeMirrorCursor = shareCodeMirrorCursor; + window.sharejs.Doc.prototype.attachCodeMirrorCursor = function (cm, ctx) { + if (!ctx) ctx = this.createContext(); + shareCodeMirrorCursor(cm, ctx); + }; } } From 4f5e081951ade2c6b987bdb6b4a75ce83cc58aea Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Thu, 10 Apr 2014 15:57:52 -0700 Subject: [PATCH 05/29] minor stylistic changes --- examples/index.html | 2 +- share-codemirror-cursor.js | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/index.html b/examples/index.html index 2e46713..c0e7711 100644 --- a/examples/index.html +++ b/examples/index.html @@ -15,7 +15,7 @@ border: 1px solid grey; } .CodeMirror-lines { - padding: 20px 10px; + padding: 20px 0px; } .user-name { color: white; diff --git a/share-codemirror-cursor.js b/share-codemirror-cursor.js index 3a8af8c..09cd9bb 100644 --- a/share-codemirror-cursor.js +++ b/share-codemirror-cursor.js @@ -7,7 +7,7 @@ var endCur = editor.getCursor('end'); var from = startCur; - var to = {line: endCur.line, ch: endCur.ch+1}; + var to = {line: endCur.line, ch: endCur.ch}; ctx.setSelection([editor.indexFromPos(from), editor.indexFromPos(to)]); }); @@ -46,7 +46,6 @@ } var style = ""; for(var i = 0; i < ids.length; i++ ) { - //".user { background: green; } .user-cursor { background: blue; }" style += ".user-" + ids[i] + " { background: " + (sessions[ids[i]].color || "yellow") + "; }"; } headStyle.innerHTML = style; @@ -59,6 +58,14 @@ if(typeof selection == "number") selection = [selection, selection]; var from = cm.posFromIndex(selection[0]); var to = cm.posFromIndex(selection[1]); + + // we mark up the range of text the other user has highlighted + var marker = markersBySessionId[sessionId]; + if(marker) { + marker.clear(); + } + markersBySessionId[sessionId] = markCursor(cm, sessionId, to, from); + var cursor = cursorsBySessionId[sessionId]; if(cursor === undefined) { cursor = createCursorWidget(cm, sessionId, session); @@ -67,13 +74,6 @@ updateCursor(cursor, sessionId, session); } cm.addWidget(to, cursor); - - // we mark up the range of text the other user has highlighted - var marker = markersBySessionId[sessionId]; - if(marker) { - marker.clear(); - } - markersBySessionId[sessionId] = markCursor(cm, sessionId, to, from); } function markCursor(cm, sessionId, to, from) { From 9e9b3bc4d397aa324defd34ff05f4a87156f4a27 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Thu, 10 Apr 2014 16:26:45 -0700 Subject: [PATCH 06/29] point to git for packages --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 1dd71c0..404aef3 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,12 @@ "url": "https://github.com/share/share-codemirror/issues" }, "devDependencies": { - "share": "~0.7", + "share": "git://github.com/share/ShareJS.git#presence", + "livedb": "git://github.com/share/livedb.git#presence", + "livedb-mongo": "~0.2.5", "codemirror": "git://github.com/marijnh/CodeMirror.git#master", "connect": "~2.11.0", "browserchannel": "~1.0.8", - "livedb": "~0.3", - "livedb-mongo": "~0.2.5", "mocha": "~1.14.0", "jsdom": "~0.8.8", "istanbul": "~0.1.44" From da612d97e4d61e963fd407f4727968183bae1c2e Mon Sep 17 00:00:00 2001 From: Joseph Gentle Date: Thu, 10 Apr 2014 17:28:46 -0700 Subject: [PATCH 07/29] Removed some old event propogation and supressed some cursor events --- examples/server.js | 18 ++---------------- share-codemirror-cursor.js | 2 ++ share-codemirror.js | 13 +++++++------ 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/examples/server.js b/examples/server.js index b358a1f..f627f08 100644 --- a/examples/server.js +++ b/examples/server.js @@ -24,7 +24,7 @@ var clientsById = {}; webserver.use(browserChannel({webserver: webserver, sessionTimeoutInterval: 5000}, function (client) { clientsById[client.id] = client; - client.send({_type: 'connectionId', connectionId: client.id}); + //client.send({_type: 'connectionId', connectionId: client.id}); var stream = new Duplex({objectMode: true}); stream._write = function (chunk, encoding, callback) { @@ -38,21 +38,7 @@ webserver.use(browserChannel({webserver: webserver, sessionTimeoutInterval: 5000 stream.headers = client.headers; stream.remoteAddress = stream.address; client.on('message', function (data) { - if(data._type) { - data.connectionId = client.id; - for(var clientId in clientsById) { - var c = clientsById[clientId]; - c.send(data); - } - } else { - if(data.a == 'sub') { - var docId = data.d; - var collectionName = data.c; - // Client client.id is subbing to docId inside collectionName - console.log('SUB', data); - } - stream.push(data); - } + stream.push(data); }); stream.on('error', function (msg) { console.log('ERROR', msg, client.id); diff --git a/share-codemirror-cursor.js b/share-codemirror-cursor.js index 09cd9bb..faef219 100644 --- a/share-codemirror-cursor.js +++ b/share-codemirror-cursor.js @@ -3,6 +3,8 @@ function shareCodeMirrorCursor(cm, ctx) { cm.on('cursorActivity', function (editor) { + if (ctx.suppress) return; + var startCur = editor.getCursor('start'); var endCur = editor.getCursor('end'); diff --git a/share-codemirror.js b/share-codemirror.js index 9f66c7c..139e856 100644 --- a/share-codemirror.js +++ b/share-codemirror.js @@ -8,7 +8,6 @@ function shareCodeMirror(cm, ctx) { if (!ctx.provides.text) throw new Error('Cannot attach to non-text document'); - var suppress = false; var text = ctx.get() || ''; // Due to a bug in share - get() returns undefined for empty docs. cm.setValue(text); check(); @@ -16,26 +15,28 @@ // *** remote -> local changes ctx.onInsert = function (pos, text) { - suppress = true; + ctx.suppress = true; cm.replaceRange(text, cm.posFromIndex(pos)); - suppress = false; + ctx.suppress = false; check(); }; ctx.onRemove = function (pos, length) { - suppress = true; + ctx.suppress = true; var from = cm.posFromIndex(pos); var to = cm.posFromIndex(pos + length); cm.replaceRange('', from, to); - suppress = false; + ctx.suppress = false; check(); }; // *** local -> remote changes cm.on('change', function (cm, change) { - if (suppress) return; + if (ctx.suppress) return; + ctx.suppress = true; applyToShareJS(cm, change); + ctx.suppress = false; check(); }); From 08070e70b7345a9744be4d7b44d700ac5005fbbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Sat, 12 Apr 2014 20:02:29 +0100 Subject: [PATCH 08/29] Formatting --- .gitignore | 2 ++ share-codemirror-cursor.js | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 94faf2d..57862c9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ node_modules/ npm-debug.log coverage/ .DS_Store +.idea/ +*.iml diff --git a/share-codemirror-cursor.js b/share-codemirror-cursor.js index faef219..084ac1e 100644 --- a/share-codemirror-cursor.js +++ b/share-codemirror-cursor.js @@ -4,7 +4,7 @@ function shareCodeMirrorCursor(cm, ctx) { cm.on('cursorActivity', function (editor) { if (ctx.suppress) return; - + var startCur = editor.getCursor('start'); var endCur = editor.getCursor('end'); @@ -18,17 +18,17 @@ var markersBySessionId = {}; var myConnectionId; - ctx.onPresence = function() { + ctx.onPresence = function () { var presence = ctx.getPresence(); var sessionIds = Object.keys(presence); makeUserStyles(presence); - sessionIds.forEach(function(sessionId) { + sessionIds.forEach(function (sessionId) { var session = presence[sessionId]; displayCursor(sessionId, session); }) var cursorIds = Object.keys(cursorsBySessionId); - cursorIds.forEach(function(cid) { - if(sessionIds.indexOf(cid) < 0) { + cursorIds.forEach(function (cid) { + if (sessionIds.indexOf(cid) < 0) { cursorsBySessionId[cid].parentElement.removeChild(cursorsBySessionId[cid]) markersBySessionId[cid].clear(); delete cursorsBySessionId[cid]; @@ -40,14 +40,14 @@ function makeUserStyles(sessions) { var ids = Object.keys(sessions); var headStyle = document.getElementById("user-styles"); - if(!headStyle) { + if (!headStyle) { var head = document.getElementsByTagName("head")[0]; var headStyle = document.createElement("style"); headStyle.setAttribute("id", "user-styles"); head.appendChild(headStyle); } var style = ""; - for(var i = 0; i < ids.length; i++ ) { + for (var i = 0; i < ids.length; i++) { style += ".user-" + ids[i] + " { background: " + (sessions[ids[i]].color || "yellow") + "; }"; } headStyle.innerHTML = style; @@ -56,20 +56,20 @@ function displayCursor(sessionId, session) { // we make a cursor widget to display where the other user's cursor is var selection = session._selection; - if(!selection) return; - if(typeof selection == "number") selection = [selection, selection]; + if (!selection) return; + if (typeof selection == "number") selection = [selection, selection]; var from = cm.posFromIndex(selection[0]); var to = cm.posFromIndex(selection[1]); // we mark up the range of text the other user has highlighted var marker = markersBySessionId[sessionId]; - if(marker) { + if (marker) { marker.clear(); } markersBySessionId[sessionId] = markCursor(cm, sessionId, to, from); var cursor = cursorsBySessionId[sessionId]; - if(cursor === undefined) { + if (cursor === undefined) { cursor = createCursorWidget(cm, sessionId, session); cursorsBySessionId[sessionId] = cursor; } else { From 548e1876004c379d2d1ba1305fd4018b639c4392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Sat, 12 Apr 2014 20:03:22 +0100 Subject: [PATCH 09/29] Fix warnings --- share-codemirror-cursor.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/share-codemirror-cursor.js b/share-codemirror-cursor.js index 084ac1e..ca0f45c 100644 --- a/share-codemirror-cursor.js +++ b/share-codemirror-cursor.js @@ -16,7 +16,6 @@ var cursorsBySessionId = {}; var markersBySessionId = {}; - var myConnectionId; ctx.onPresence = function () { var presence = ctx.getPresence(); @@ -42,7 +41,7 @@ var headStyle = document.getElementById("user-styles"); if (!headStyle) { var head = document.getElementsByTagName("head")[0]; - var headStyle = document.createElement("style"); + headStyle = document.createElement("style"); headStyle.setAttribute("id", "user-styles"); head.appendChild(headStyle); } @@ -79,8 +78,7 @@ } function markCursor(cm, sessionId, to, from) { - var marker = cm.markText(from, to, { className: "user-" + sessionId }); - return marker; + return cm.markText(from, to, { className: "user-" + sessionId }); } } From 9ff6aa534aab3a50634a181c9cd95342d9bab441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Sat, 12 Apr 2014 20:04:49 +0100 Subject: [PATCH 10/29] Semicolons --- share-codemirror-cursor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share-codemirror-cursor.js b/share-codemirror-cursor.js index ca0f45c..6ebdc01 100644 --- a/share-codemirror-cursor.js +++ b/share-codemirror-cursor.js @@ -24,11 +24,11 @@ sessionIds.forEach(function (sessionId) { var session = presence[sessionId]; displayCursor(sessionId, session); - }) + }); var cursorIds = Object.keys(cursorsBySessionId); cursorIds.forEach(function (cid) { if (sessionIds.indexOf(cid) < 0) { - cursorsBySessionId[cid].parentElement.removeChild(cursorsBySessionId[cid]) + cursorsBySessionId[cid].parentElement.removeChild(cursorsBySessionId[cid]); markersBySessionId[cid].clear(); delete cursorsBySessionId[cid]; delete markersBySessionId[cid]; From f9636536faeae26458650f58985f9f41baace158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Sat, 12 Apr 2014 20:10:50 +0100 Subject: [PATCH 11/29] Simplify --- share-codemirror-cursor.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/share-codemirror-cursor.js b/share-codemirror-cursor.js index 6ebdc01..d1e7b9a 100644 --- a/share-codemirror-cursor.js +++ b/share-codemirror-cursor.js @@ -5,13 +5,9 @@ cm.on('cursorActivity', function (editor) { if (ctx.suppress) return; - var startCur = editor.getCursor('start'); - var endCur = editor.getCursor('end'); - - var from = startCur; - var to = {line: endCur.line, ch: endCur.ch}; - - ctx.setSelection([editor.indexFromPos(from), editor.indexFromPos(to)]); + var start = editor.indexFromPos(editor.getCursor('start')); + var end = editor.indexFromPos(editor.getCursor('end')); + ctx.setSelection([ start, end]); }); var cursorsBySessionId = {}; From a284b4cd9fa87c989aa2d5c9e43dcb32542774b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Sat, 12 Apr 2014 20:25:01 +0100 Subject: [PATCH 12/29] Cleanup --- examples/index.html | 17 +++++++---------- share-codemirror-cursor.js | 5 +++-- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/examples/index.html b/examples/index.html index c0e7711..3d09f4e 100644 --- a/examples/index.html +++ b/examples/index.html @@ -49,6 +49,7 @@ var bcs = new BCSocket(null, {reconnect: true}); var sjs = new window.sharejs.Connection(bcs); +sjs.debug = false; var doc = sjs.get('users', 'sephx'); @@ -75,7 +76,7 @@ .attr("value", doc.connection.id) .on("keyup", function() { doc.setPresenceProperty("name", this.value); - }) + }); var ctx2 = doc.createContext(); ctx2.onPresence = function() { @@ -83,27 +84,23 @@ var sessionIds = Object.keys(presence); var sessions = sessionIds.map(function(sid) { return { id: sid, data: presence[sid] }; - }) + }); var userSel = d3.select("#users").selectAll("div.user-ui") .data(sessions, function(d) { return d.id }); - var userEnter = userSel.enter().append("div").classed("user-ui", true) - .append("span") - userSel.each(function(d) { var clss = "user-" + d.id; if(!this.classList.contains(clss)) this.classList.add(clss); - }) + }); - userSel.select("span").text(function(d) { - var name = d.data.name || d.id; - return name; + userSel.select("span").text(function name(d) { + return d.data.name || d.id; }) .style({ background: function(d) { return d.data.color || ""; } - }) + }); userSel.exit().remove(); }; diff --git a/share-codemirror-cursor.js b/share-codemirror-cursor.js index d1e7b9a..1d012bf 100644 --- a/share-codemirror-cursor.js +++ b/share-codemirror-cursor.js @@ -24,6 +24,7 @@ var cursorIds = Object.keys(cursorsBySessionId); cursorIds.forEach(function (cid) { if (sessionIds.indexOf(cid) < 0) { + // Remove widget cursorsBySessionId[cid].parentElement.removeChild(cursorsBySessionId[cid]); markersBySessionId[cid].clear(); delete cursorsBySessionId[cid]; @@ -61,7 +62,7 @@ if (marker) { marker.clear(); } - markersBySessionId[sessionId] = markCursor(cm, sessionId, to, from); + markersBySessionId[sessionId] = markCursor(cm, sessionId, from, to); var cursor = cursorsBySessionId[sessionId]; if (cursor === undefined) { @@ -73,7 +74,7 @@ cm.addWidget(to, cursor); } - function markCursor(cm, sessionId, to, from) { + function markCursor(cm, sessionId, from, to) { return cm.markText(from, to, { className: "user-" + sessionId }); } } From 58e8badb06d0f18c1544ddfc82726bb15feeb617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Mon, 14 Apr 2014 12:40:17 +0100 Subject: [PATCH 13/29] Use a Cursor object to manage state --- examples/index.html | 7 --- share-codemirror-cursor.js | 93 ++++++++++++++++++-------------------- 2 files changed, 43 insertions(+), 57 deletions(-) diff --git a/examples/index.html b/examples/index.html index 3d09f4e..4774d5e 100644 --- a/examples/index.html +++ b/examples/index.html @@ -10,13 +10,6 @@ + + + + + + + + + +
- +
From 1830d8a97efcb6ff171cc768f339d248c781c3e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Tue, 15 Apr 2014 22:51:58 +0100 Subject: [PATCH 17/29] Remove unneeded d3 dependency. Remove broken timeout code --- examples/index.html | 43 ++++++-------------------------------- package.json | 3 --- share-codemirror-cursor.js | 19 ++--------------- 3 files changed, 8 insertions(+), 57 deletions(-) diff --git a/examples/index.html b/examples/index.html index abae6b6..6600122 100644 --- a/examples/index.html +++ b/examples/index.html @@ -67,43 +67,12 @@ var color = colors[sessionIds.length % colors.length]; doc.setPresenceProperty("color", color); - d3.select("#myself") - .style("background", color) - .select("input") - .attr("value", doc.connection.id) - .on("keyup", function () { - doc.setPresenceProperty("name", this.value); - }); - - var ctx2 = doc.createContext(); - ctx2.onPresence = function () { - var presence = ctx2.getPresence(); - var sessionIds = Object.keys(presence); - var sessions = sessionIds.map(function (sid) { - return { id: sid, data: presence[sid] }; - }); - - var userSel = d3.select("#users").selectAll("div.user-ui") - .data(sessions, function (d) { - return d.id - }); - - userSel.each(function (d) { - var clss = "user-" + d.id; - if (!this.classList.contains(clss)) - this.classList.add(clss); - }); - - userSel.select("span").text(function name(d) { - return d.data.name || d.id; - }) - .style({ - background: function (d) { - return d.data.color || ""; - } - }); - - userSel.exit().remove(); + var myself = document.getElementById('myself'); + myself.style.background = color; + var input = myself.getElementsByTagName("input")[0]; + input.value = doc.connection.id; + input.onkeyup = function () { + doc.setPresenceProperty("name", input.value); }; } }); diff --git a/package.json b/package.json index 7b4fffd..ea0ba99 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,5 @@ "mocha": "~1.14.0", "jsdom": "~0.8.8", "istanbul": "~0.1.44" - }, - "dependencies": { - "d3": "~3.4.5" } } diff --git a/share-codemirror-cursor.js b/share-codemirror-cursor.js index 3629ce5..876c91a 100644 --- a/share-codemirror-cursor.js +++ b/share-codemirror-cursor.js @@ -7,7 +7,7 @@ var start = editor.indexFromPos(editor.getCursor('start')); var end = editor.indexFromPos(editor.getCursor('end')); - ctx.setSelection([ start, end]); + ctx.setSelection([start, end]); }); var cursorsBySessionId = {}; @@ -80,28 +80,13 @@ widget.appendChild(caret); widget.appendChild(owner); - var marker, displayTimer, lastFrom, lastTo; + var marker; this.update = function (session, from, to) { - var hasMoved = !lastFrom || !lastTo || - lastFrom.ch != from.ch || lastFrom.line != from.line || - lastTo.ch != to.ch || lastTo.line != to.line; - if (!hasMoved) return; - - lastFrom = from; - lastTo = to; - caret.style.borderLeftColor = session.color || 'black'; owner.style.background = session.color || "black"; owner.innerHTML = session.name || sessionId; - // We show the cursor owner for 5 seconds, then hide it until the next time it moves. - owner.style.display = 'block'; - if (displayTimer) clearTimeout(displayTimer); - displayTimer = setTimeout(function () { - owner.style.display = 'none'; - }, 5000); - // We mark up the range of text the other user has highlighted if (marker) { marker.clear(); From f4a0e03aa058c90dbbd7a9ea40f03e61053f582d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Tue, 15 Apr 2014 23:47:00 +0100 Subject: [PATCH 18/29] Set default background to grey --- share-codemirror-cursor.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/share-codemirror-cursor.js b/share-codemirror-cursor.js index 876c91a..1e5bb2d 100644 --- a/share-codemirror-cursor.js +++ b/share-codemirror-cursor.js @@ -83,8 +83,10 @@ var marker; this.update = function (session, from, to) { - caret.style.borderLeftColor = session.color || 'black'; - owner.style.background = session.color || "black"; + var defaultColor = '#dddddd'; + + caret.style.borderLeftColor = session.color || defaultColor; + owner.style.background = session.color || defaultColor; owner.innerHTML = session.name || sessionId; // We mark up the range of text the other user has highlighted From 8473e7faaa317ca44736f5b3c519910ec04a681d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Fri, 30 May 2014 10:32:23 -0500 Subject: [PATCH 19/29] Update dependencies and build script --- README.md | 6 +++--- package.json | 17 +++++++++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7b552e8..62f5502 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ var cm = CodeMirror.fromTextArea(elem); shareDoc.attachCodeMirror(cm); ``` -That's it. You now have 2-way sync between your ShareJS and CodeMirror. +That's it. You now have 2-way sync between your ShareJS and CodeMirror. ## Install with Bower @@ -22,7 +22,7 @@ bower install share-codemirror npm install share-codemirror ``` -On Node.js you can mount the `scriptsDir` (where `share-codemirror.js` lives) as a static resource +On Node.js you can mount the `scriptsDir` (where `share-codemirror.js` lives) as a static resource in your web server: ```javascript @@ -41,6 +41,7 @@ In the HTML: ``` npm install +npm test node examples/server.js # in a couple of browsers... open http://localhost:7007 @@ -78,4 +79,3 @@ git push --tags ``` There is no `bower publish` - the existance of a git tag is enough. - diff --git a/package.json b/package.json index ea0ba99..4a55e77 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "CodeMirror bindings for ShareJS", "main": "share-codemirror.js", "scripts": { - "pretest": "cd node_modules/share && npm install", + "pretest": "cd node_modules/share && UGLIFY=../../node_modules/.bin/uglifyjs make webclient/share.uncompressed.js", "test": "node_modules/.bin/mocha" }, "repository": { @@ -21,14 +21,15 @@ "url": "https://github.com/share/share-codemirror/issues" }, "devDependencies": { - "share": "git://github.com/share/ShareJS.git#presence", - "livedb": "git://github.com/share/livedb.git#presence", - "livedb-mongo": "~0.3.0", - "codemirror": "~4.0.3", + "browserchannel": "~1.2.0", + "codemirror": "~4.2.0", "connect": "~2.11.0", - "browserchannel": "~1.0.8", - "mocha": "~1.14.0", + "istanbul": "~0.2.10", "jsdom": "~0.8.8", - "istanbul": "~0.1.44" + "livedb": "git://github.com/share/livedb.git#f03086cdf51ffc80c725f105c7236c2997d74eac", + "livedb-mongo": "~0.3.2", + "mocha": "~1.20.0", + "share": "git://github.com/share/ShareJS.git#94dd9e0659c9b6953ba888f5dde8ce223a71f2ec", + "uglify-js": "~2.4.13" } } From 418cf56161eb8d9efa47e0c24bbf5d0b7d4718ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Fri, 30 May 2014 10:33:52 -0500 Subject: [PATCH 20/29] Bump jsdom --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4a55e77..d9e21bb 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "codemirror": "~4.2.0", "connect": "~2.11.0", "istanbul": "~0.2.10", - "jsdom": "~0.8.8", + "jsdom": "~0.10.6", "livedb": "git://github.com/share/livedb.git#f03086cdf51ffc80c725f105c7236c2997d74eac", "livedb-mongo": "~0.3.2", "mocha": "~1.20.0", From ff99689cda046ecec29471b00770c6d81df5e6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aslak=20Helles=C3=B8y?= Date: Fri, 30 May 2014 23:09:35 -0500 Subject: [PATCH 21/29] Smaller owner text. Remove d3. --- examples/index.html | 1 - share-codemirror-cursor.js | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/index.html b/examples/index.html index 6600122..a60ceb6 100644 --- a/examples/index.html +++ b/examples/index.html @@ -7,7 +7,6 @@ -