diff --git a/site/examples/acecursors/index.html b/site/examples/acecursors/index.html new file mode 100644 index 000000000..c786a305e --- /dev/null +++ b/site/examples/acecursors/index.html @@ -0,0 +1,47 @@ + + + +ACE in Action + + + + +
function foo(items) { + var x = "All this is syntax highlighted"; + return x; +}
+ + + + + + + + + + + \ No newline at end of file diff --git a/togetherjs/forms.js b/togetherjs/forms.js index 6c0ef8530..ca58b92e8 100644 --- a/togetherjs/forms.js +++ b/togetherjs/forms.js @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -define(["jquery", "util", "session", "elementFinder", "eventMaker", "templating", "ot"], function ($, util, session, elementFinder, eventMaker, templating, ot) { +define(["jquery", "util", "session", "elementFinder", "eventMaker", "templating", "ot", "peers"], function ($, util, session, elementFinder, eventMaker, templating, ot, peers) { var forms = util.Module("forms"); var assert = util.assert; @@ -101,8 +101,19 @@ define(["jquery", "util", "session", "elementFinder", "eventMaker", "templating" constructor: function (el) { this.element = $(el); assert(this.element.hasClass("ace_editor")); + this.cursorMarkers= {}; this._change = this._change.bind(this); this._editor().document.on("change", this._change); + this._changeSelection = this._changeSelection.bind(this); + this._syncCursor = ace.require("ace/lib/lang").delayedCall(this._changeSelection); + this._delayedChangeSelection = this._delayedChangeSelection.bind(this); + this._editor().editor.on("changeSelection", this._delayedChangeSelection); + //changeSelection doesn't fire when a user presses Esc to leave the multi-select + //mode, so we need this extra event listener + this._editor().editor.selection.on("singleSelect", this._delayedChangeSelection); + + this.peerUpdate = this.peerUpdate.bind(this); + peers.on("new-peer identity-updated status-updated url-updated idle-updated", this.peerUpdate); }, tracked: function (el) { @@ -111,6 +122,8 @@ define(["jquery", "util", "session", "elementFinder", "eventMaker", "templating" destroy: function (el) { this._editor().document.removeListener("change", this._change); + this._editor().editor.off("changeSelection", this._changeSelection); + peers.off("new-peer identity-updated status-updated url-updated idle-updated", this.peerUpdate); }, update: function (msg) { @@ -118,7 +131,73 @@ define(["jquery", "util", "session", "elementFinder", "eventMaker", "templating" this.init(msg); return; } - this._editor().document.getDocument().applyDeltas([msg.delta]); + if(msg.hasOwnProperty('delta')) { + this._editor().document.getDocument().applyDeltas([msg.delta]); + } + if(msg.hasOwnProperty('cursors')) { + this.updateCursorMarker(msg); + } + }, + + peerUpdate: function (peer) { + //status-updated + if (peer.status=='bye') { + this.peerBye(peer); + return; + } + //new-peer, identity-updated, url-updated, idle-updated + if( this.cursorMarkers.hasOwnProperty(peer.id)) { + var msg = { + peer: peer, + cursors: this.cursorMarkers[peer.id].cursors + }; + this.updateCursorMarker(msg); + } + }, + + peerBye: function (peer) { + if ( this.cursorMarkers.hasOwnProperty(peer.id)) { + var oldMarkers = this.cursorMarkers[peer.id].markers; + oldMarkers.forEach(function(markerId){ + this._editor() + .document + .removeMarker(markerId); + }, this); + delete this.cursorMarkers[peer.id]; + } + }, + + updateCursorMarker: function (msg) { + if (msg === undefined) { + return; + } + if ( this.cursorMarkers.hasOwnProperty(msg.peer.id)) { + var oldMarkers = this.cursorMarkers[msg.peer.id].markers; + oldMarkers.forEach(function(markerId){ + this._editor() + .document + .removeMarker(markerId); + }, this); + } + var markers = msg.cursors.map(function(cursor) { + var Range = ace.require('ace/range').Range; + var range = new Range(cursor.row, cursor.column, + cursor.row, cursor.column + 1); + + var markerId = this._editor() + .document + .addMarker(range, "ace_selection", drawMarker, false); + return markerId; + }, this); + this.cursorMarkers[msg.peer.id] = { + markers:markers, + cursors:msg.cursors + }; + function drawMarker(stringBuilder, range, left, top, config) { + var color = "background-color:"+ msg.peer.color; + var opacity = (msg.peer.url != session.currentUrl() || msg.peer.idle=="inactive") ? 0.5 : 1; + stringBuilder.push("
", "
"); + } }, init: function (update, msg) { @@ -137,7 +216,7 @@ define(["jquery", "util", "session", "elementFinder", "eventMaker", "templating" return this.element[0].env; }, - _change: function (e) { + _change: function (e, document) { // FIXME: I should have an internal .send() function that automatically // asserts !inRemoteUpdate, among other things if (inRemoteUpdate) { @@ -151,6 +230,31 @@ define(["jquery", "util", "session", "elementFinder", "eventMaker", "templating" element: elementFinder.elementLocation(this.element), delta: JSON.parse(JSON.stringify(e.data)) }); + }, + + _changeSelection: function () { + // FIXME: I should have an internal .send() function that automatically + // asserts !inRemoteUpdate, among other things + if (inRemoteUpdate) { + return; + } + var selection = this._editor().document.getSelection(); + var cursors = []; + if (selection.inMultiSelectMode) { + cursors = selection.getAllRanges().map(function(range) {return range.cursor; }); + } else { + cursors.push(selection.getCursor()); + } + session.send({ + type: "form-update", + tracker: this.trackerName, + element: elementFinder.elementLocation(this.element), + cursors: cursors + }); + }, + + _delayedChangeSelection: function(){ + this._syncCursor.delay(50); } }); diff --git a/togetherjs/youtubeVideos.js b/togetherjs/youtubeVideos.js index b6d1c7897..a6bce441e 100644 --- a/togetherjs/youtubeVideos.js +++ b/togetherjs/youtubeVideos.js @@ -81,7 +81,7 @@ function ($, util, session, elementFinder) { // if the iframe's unique id is already set, skip it if (($(iframe).attr("src") || "").indexOf("youtube") != -1 && !$(iframe).attr("id")) { $(iframe).attr("id", "youtube-player"+i); - $(iframe).attr("ensablejsapi", 1); + $(iframe).attr("enablejsapi", 1); youTubeIframes[i] = iframe; } });