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;
}
});