diff --git a/src/static/js/ace.js b/src/static/js/ace.js index e08036fa9..ff3b32080 100644 --- a/src/static/js/ace.js +++ b/src/static/js/ace.js @@ -199,28 +199,7 @@ define(['ep_etherpad-lite/static/js/pluginfw/hooks', 'underscore'], function (ho ')); iframeHTML.push(''); - - iframeHTML.push(scriptTag('\n\ - var pathComponents = parent.parent.location.pathname.split("/");\n\ - var baseURL = pathComponents.slice(0,pathComponents.length-2).join("/") + "/";\n\ - requirejs.config({\n\ - baseUrl: baseURL + "static/plugins",\n\ - paths: {underscore: baseURL + "static/plugins/underscore/underscore"}\n\ - });\n\ - \n\ - requirejs(["ep_etherpad-lite/static/js/rjquery", "ep_etherpad-lite/static/js/pluginfw/client_plugins", "ep_etherpad-lite/static/js/ace2_inner"], function (j, plugins, Ace2Inner) {\n\ - jQuery = $ = window.jQuery = window.$ = j; // Expose jQuery #HACK\n\ - \n\ - plugins.adoptPluginsFromAncestorsOf(window, function () {\n\ - var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");\n\ - hooks.plugins = plugins;\n\ - \n\ - plugins.ensure(function () {\n\ - Ace2Inner.init();\n\ - });\n\ - });\n\ - });\n\ - ')); + iframeHTML.push(''); iframeHTML.push(''); diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index f8c2565ea..8ed7f91b1 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -20,7 +20,9 @@ * limitations under the License. */ -define(["ep_etherpad-lite/static/js/rjquery", "underscore", 'ep_etherpad-lite/static/js/pluginfw/hooks'], function ($, _, hooks) { +define(["ep_etherpad-lite/static/js/rjquery", "underscore", 'ep_etherpad-lite/static/js/pluginfw/hooks', 'ep_etherpad-lite/static/js/linestylefilter', 'ep_etherpad-lite/static/js/domline'], function ($, _, hooks, linestylefilterMod, domlineMod) { + var linestylefilter = linestylefilterMod.linestylefilter; + var domline = domlineMod.domline; var exports = {}; var browser = require('./browser').browser; @@ -50,11 +52,9 @@ function Ace2Inner(){ var colorutils = require('./colorutils').colorutils; var makeContentCollector = require('./contentcollector').makeContentCollector; var makeCSSManager = require('./cssmanager').makeCSSManager; - var domline = require('./domline').domline; var AttribPool = require('./AttributePool'); var Changeset = require('./Changeset'); var ChangesetUtils = require('./ChangesetUtils'); - var linestylefilter = require('./linestylefilter').linestylefilter; var SkipList = require('./skiplist'); var undoModule = require('./undomodule').undoModule; var AttributeManager = require('./AttributeManager'); @@ -475,6 +475,7 @@ function Ace2Inner(){ //console.log("Just did action for: "+type); cleanExit = true; } +/* catch (e) { caughtErrors.push( @@ -485,6 +486,7 @@ function Ace2Inner(){ dmesg(e.toString()); throw e; } +*/ finally { var cs = currentCallStack; diff --git a/src/static/js/ace2_inner_main.js b/src/static/js/ace2_inner_main.js new file mode 100644 index 000000000..21b77a5b6 --- /dev/null +++ b/src/static/js/ace2_inner_main.js @@ -0,0 +1,26 @@ +var pathComponents = parent.parent.location.pathname.split("/"); +var baseURL = pathComponents.slice(0,pathComponents.length-2).join("/") + "/"; +requirejs.config({ + baseUrl: baseURL + "static/plugins", + paths: {underscore: baseURL + "static/plugins/underscore/underscore"} +}); + +requirejs( + [ + "ep_etherpad-lite/static/js/rjquery", + "ep_etherpad-lite/static/js/pluginfw/client_plugins", + "ep_etherpad-lite/static/js/pluginfw/hooks", + "ep_etherpad-lite/static/js/ace2_inner" + ], + function (j, plugins, hooks, Ace2Inner) { + jQuery = $ = window.jQuery = window.$ = j; // Expose jQuery #HACK + + plugins.adoptPluginsFromAncestorsOf(window, function () { + hooks.plugins = plugins; + + plugins.ensure(function () { + Ace2Inner.init(); + }); + }); + } +); diff --git a/src/static/js/broadcast.js b/src/static/js/broadcast.js index 983402683..75560a9ad 100644 --- a/src/static/js/broadcast.js +++ b/src/static/js/broadcast.js @@ -20,577 +20,580 @@ * limitations under the License. */ -var makeCSSManager = require('./cssmanager').makeCSSManager; -var domline = require('./domline').domline; -var AttribPool = require('./AttributePool'); -var Changeset = require('./Changeset'); -var linestylefilter = require('./linestylefilter').linestylefilter; -var colorutils = require('./colorutils').colorutils; -var _ = require('./underscore'); -var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); +define(['ep_etherpad-lite/static/js/pluginfw/hooks', 'ep_etherpad-lite/static/js/linestylefilter', 'ep_etherpad-lite/static/js/domline', 'underscore'], function (hooks, linetyleFilterMod, domlineMod, _) { + var exports = {}; + var makeCSSManager = require('./cssmanager').makeCSSManager; + var domline = domlineMod.domline; + var AttribPool = require('./AttributePool'); + var Changeset = require('./Changeset'); + var linestylefilter = linetyleFilterMod.linestylefilter; + var colorutils = require('./colorutils').colorutils; -// These parameters were global, now they are injected. A reference to the -// Timeslider controller would probably be more appropriate. -function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider) -{ - var changesetLoader = undefined; - - // Below Array#indexOf code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_indexof.htm - if (!Array.prototype.indexOf) + // These parameters were global, now they are injected. A reference to the + // Timeslider controller would probably be more appropriate. + function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider) { - Array.prototype.indexOf = function(elt /*, from*/ ) + var changesetLoader = undefined; + + // Below Array#indexOf code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_indexof.htm + if (!Array.prototype.indexOf) { - var len = this.length >>> 0; - - var from = Number(arguments[1]) || 0; - from = (from < 0) ? Math.ceil(from) : Math.floor(from); - if (from < 0) from += len; - - for (; from < len; from++) + Array.prototype.indexOf = function(elt /*, from*/ ) { - if (from in this && this[from] === elt) return from; - } - return -1; - }; - } + var len = this.length >>> 0; - function debugLog() - { - try - { - if (window.console) console.log.apply(console, arguments); - } - catch (e) - { - if (window.console) console.log("error printing: ", e); - } - } + var from = Number(arguments[1]) || 0; + from = (from < 0) ? Math.ceil(from) : Math.floor(from); + if (from < 0) from += len; - //var socket; - var channelState = "DISCONNECTED"; - - var appLevelDisconnectReason = null; - - var padContents = { - currentRevision: clientVars.collab_client_vars.rev, - currentTime: clientVars.collab_client_vars.time, - currentLines: Changeset.splitTextLines(clientVars.collab_client_vars.initialAttributedText.text), - currentDivs: null, - // to be filled in once the dom loads - apool: (new AttribPool()).fromJsonable(clientVars.collab_client_vars.apool), - alines: Changeset.splitAttributionLines( - clientVars.collab_client_vars.initialAttributedText.attribs, clientVars.collab_client_vars.initialAttributedText.text), - - // generates a jquery element containing HTML for a line - lineToElement: function(line, aline) - { - var element = document.createElement("div"); - var emptyLine = (line == '\n'); - var domInfo = domline.createDomLine(!emptyLine, true); - linestylefilter.populateDomLine(line, aline, this.apool, domInfo); - domInfo.prepareForAdd(); - element.className = domInfo.node.className; - element.innerHTML = domInfo.node.innerHTML; - element.id = Math.random(); - return $(element); - }, - - applySpliceToDivs: function(start, numRemoved, newLines) - { - // remove spliced-out lines from DOM - for (var i = start; i < start + numRemoved && i < this.currentDivs.length; i++) - { - debugLog("removing", this.currentDivs[i].attr('id')); - this.currentDivs[i].remove(); - } - - // remove spliced-out line divs from currentDivs array - this.currentDivs.splice(start, numRemoved); - - var newDivs = []; - for (var i = 0; i < newLines.length; i++) - { - newDivs.push(this.lineToElement(newLines[i], this.alines[start + i])); - } - - // grab the div just before the first one - var startDiv = this.currentDivs[start - 1] || null; - - // insert the div elements into the correct place, in the correct order - for (var i = 0; i < newDivs.length; i++) - { - if (startDiv) + for (; from < len; from++) { - startDiv.after(newDivs[i]); + if (from in this && this[from] === elt) return from; } - else - { - $("#padcontent").prepend(newDivs[i]); - } - startDiv = newDivs[i]; - } - - // insert new divs into currentDivs array - newDivs.unshift(0); // remove 0 elements - newDivs.unshift(start); - this.currentDivs.splice.apply(this.currentDivs, newDivs); - return this; - }, - - // splice the lines - splice: function(start, numRemoved, newLinesVA) - { - var newLines = _.map(Array.prototype.slice.call(arguments, 2), function(s) { - return s; - }); - - // apply this splice to the divs - this.applySpliceToDivs(start, numRemoved, newLines); - - // call currentLines.splice, to keep the currentLines array up to date - newLines.unshift(numRemoved); - newLines.unshift(start); - this.currentLines.splice.apply(this.currentLines, arguments); - }, - // returns the contents of the specified line I - get: function(i) - { - return this.currentLines[i]; - }, - // returns the number of lines in the document - length: function() - { - return this.currentLines.length; - }, - - getActiveAuthors: function() - { - var self = this; - var authors = []; - var seenNums = {}; - var alines = self.alines; - for (var i = 0; i < alines.length; i++) - { - Changeset.eachAttribNumber(alines[i], function(n) - { - if (!seenNums[n]) - { - seenNums[n] = true; - if (self.apool.getAttribKey(n) == 'author') - { - var a = self.apool.getAttribValue(n); - if (a) - { - authors.push(a); - } - } - } - }); - } - authors.sort(); - return authors; + return -1; + }; } - }; - function callCatchingErrors(catcher, func) - { - try - { - wrapRecordingErrors(catcher, func)(); - } - catch (e) - { /*absorb*/ - } - } - - function wrapRecordingErrors(catcher, func) - { - return function() + function debugLog() { try { - return func.apply(this, Array.prototype.slice.call(arguments)); + if (window.console) console.log.apply(console, arguments); } catch (e) { - // caughtErrors.push(e); - // caughtErrorCatchers.push(catcher); - // caughtErrorTimes.push(+new Date()); - // console.dir({catcher: catcher, e: e}); - debugLog(e); // TODO(kroo): added temporary, to catch errors - throw e; + if (window.console) console.log("error printing: ", e); + } + } + + //var socket; + var channelState = "DISCONNECTED"; + + var appLevelDisconnectReason = null; + + var padContents = { + currentRevision: clientVars.collab_client_vars.rev, + currentTime: clientVars.collab_client_vars.time, + currentLines: Changeset.splitTextLines(clientVars.collab_client_vars.initialAttributedText.text), + currentDivs: null, + // to be filled in once the dom loads + apool: (new AttribPool()).fromJsonable(clientVars.collab_client_vars.apool), + alines: Changeset.splitAttributionLines( + clientVars.collab_client_vars.initialAttributedText.attribs, clientVars.collab_client_vars.initialAttributedText.text), + + // generates a jquery element containing HTML for a line + lineToElement: function(line, aline) + { + var element = document.createElement("div"); + var emptyLine = (line == '\n'); + var domInfo = domline.createDomLine(!emptyLine, true); + linestylefilter.populateDomLine(line, aline, this.apool, domInfo); + domInfo.prepareForAdd(); + element.className = domInfo.node.className; + element.innerHTML = domInfo.node.innerHTML; + element.id = Math.random(); + return $(element); + }, + + applySpliceToDivs: function(start, numRemoved, newLines) + { + // remove spliced-out lines from DOM + for (var i = start; i < start + numRemoved && i < this.currentDivs.length; i++) + { + debugLog("removing", this.currentDivs[i].attr('id')); + this.currentDivs[i].remove(); + } + + // remove spliced-out line divs from currentDivs array + this.currentDivs.splice(start, numRemoved); + + var newDivs = []; + for (var i = 0; i < newLines.length; i++) + { + newDivs.push(this.lineToElement(newLines[i], this.alines[start + i])); + } + + // grab the div just before the first one + var startDiv = this.currentDivs[start - 1] || null; + + // insert the div elements into the correct place, in the correct order + for (var i = 0; i < newDivs.length; i++) + { + if (startDiv) + { + startDiv.after(newDivs[i]); + } + else + { + $("#padcontent").prepend(newDivs[i]); + } + startDiv = newDivs[i]; + } + + // insert new divs into currentDivs array + newDivs.unshift(0); // remove 0 elements + newDivs.unshift(start); + this.currentDivs.splice.apply(this.currentDivs, newDivs); + return this; + }, + + // splice the lines + splice: function(start, numRemoved, newLinesVA) + { + var newLines = _.map(Array.prototype.slice.call(arguments, 2), function(s) { + return s; + }); + + // apply this splice to the divs + this.applySpliceToDivs(start, numRemoved, newLines); + + // call currentLines.splice, to keep the currentLines array up to date + newLines.unshift(numRemoved); + newLines.unshift(start); + this.currentLines.splice.apply(this.currentLines, arguments); + }, + // returns the contents of the specified line I + get: function(i) + { + return this.currentLines[i]; + }, + // returns the number of lines in the document + length: function() + { + return this.currentLines.length; + }, + + getActiveAuthors: function() + { + var self = this; + var authors = []; + var seenNums = {}; + var alines = self.alines; + for (var i = 0; i < alines.length; i++) + { + Changeset.eachAttribNumber(alines[i], function(n) + { + if (!seenNums[n]) + { + seenNums[n] = true; + if (self.apool.getAttribKey(n) == 'author') + { + var a = self.apool.getAttribValue(n); + if (a) + { + authors.push(a); + } + } + } + }); + } + authors.sort(); + return authors; } }; - } - function loadedNewChangeset(changesetForward, changesetBackward, revision, timeDelta) - { - var broadcasting = (BroadcastSlider.getSliderPosition() == revisionInfo.latest); - debugLog("broadcasting:", broadcasting, BroadcastSlider.getSliderPosition(), revisionInfo.latest, revision); - revisionInfo.addChangeset(revision, revision + 1, changesetForward, changesetBackward, timeDelta); - BroadcastSlider.setSliderLength(revisionInfo.latest); - if (broadcasting) applyChangeset(changesetForward, revision + 1, false, timeDelta); - } - - /* - At this point, we must be certain that the changeset really does map from - the current revision to the specified revision. Any mistakes here will - cause the whole slider to get out of sync. - */ - - function applyChangeset(changeset, revision, preventSliderMovement, timeDelta) - { - // disable the next 'gotorevision' call handled by a timeslider update - if (!preventSliderMovement) + function callCatchingErrors(catcher, func) { - goToRevisionIfEnabledCount++; - BroadcastSlider.setSliderPosition(revision); + try + { + wrapRecordingErrors(catcher, func)(); + } + catch (e) + { /*absorb*/ + } } - try + function wrapRecordingErrors(catcher, func) { - // must mutate attribution lines before text lines - Changeset.mutateAttributionLines(changeset, padContents.alines, padContents.apool); - } - catch (e) - { - debugLog(e); + return function() + { + try + { + return func.apply(this, Array.prototype.slice.call(arguments)); + } + catch (e) + { + // caughtErrors.push(e); + // caughtErrorCatchers.push(catcher); + // caughtErrorTimes.push(+new Date()); + // console.dir({catcher: catcher, e: e}); + debugLog(e); // TODO(kroo): added temporary, to catch errors + throw e; + } + }; } - Changeset.mutateTextLines(changeset, padContents); - padContents.currentRevision = revision; - padContents.currentTime += timeDelta * 1000; + function loadedNewChangeset(changesetForward, changesetBackward, revision, timeDelta) + { + var broadcasting = (BroadcastSlider.getSliderPosition() == revisionInfo.latest); + debugLog("broadcasting:", broadcasting, BroadcastSlider.getSliderPosition(), revisionInfo.latest, revision); + revisionInfo.addChangeset(revision, revision + 1, changesetForward, changesetBackward, timeDelta); + BroadcastSlider.setSliderLength(revisionInfo.latest); + if (broadcasting) applyChangeset(changesetForward, revision + 1, false, timeDelta); + } + + /* + At this point, we must be certain that the changeset really does map from + the current revision to the specified revision. Any mistakes here will + cause the whole slider to get out of sync. + */ + + function applyChangeset(changeset, revision, preventSliderMovement, timeDelta) + { + // disable the next 'gotorevision' call handled by a timeslider update + if (!preventSliderMovement) + { + goToRevisionIfEnabledCount++; + BroadcastSlider.setSliderPosition(revision); + } + + try + { + // must mutate attribution lines before text lines + Changeset.mutateAttributionLines(changeset, padContents.alines, padContents.apool); + } + catch (e) + { + debugLog(e); + } + + Changeset.mutateTextLines(changeset, padContents); + padContents.currentRevision = revision; + padContents.currentTime += timeDelta * 1000; + + debugLog('Time Delta: ', timeDelta) + updateTimer(); + + var authors = _.map(padContents.getActiveAuthors(), function(name) + { + return authorData[name]; + }); + + BroadcastSlider.setAuthors(authors); + } + + function updateTimer() + { + var zpad = function(str, length) + { + str = str + ""; + while (str.length < length) + str = '0' + str; + return str; + } + + var date = new Date(padContents.currentTime); + var dateFormat = function() + { + var month = zpad(date.getMonth() + 1, 2); + var day = zpad(date.getDate(), 2); + var year = (date.getFullYear()); + var hours = zpad(date.getHours(), 2); + var minutes = zpad(date.getMinutes(), 2); + var seconds = zpad(date.getSeconds(), 2); + return (html10n.get("timeslider.dateformat", { + "day": day, + "month": month, + "year": year, + "hours": hours, + "minutes": minutes, + "seconds": seconds + })); + } + + + + + + $('#timer').html(dateFormat()); + var revisionDate = html10n.get("timeslider.saved", { + "day": date.getDate(), + "month": [ + html10n.get("timeslider.month.january"), + html10n.get("timeslider.month.february"), + html10n.get("timeslider.month.march"), + html10n.get("timeslider.month.april"), + html10n.get("timeslider.month.may"), + html10n.get("timeslider.month.june"), + html10n.get("timeslider.month.july"), + html10n.get("timeslider.month.august"), + html10n.get("timeslider.month.september"), + html10n.get("timeslider.month.october"), + html10n.get("timeslider.month.november"), + html10n.get("timeslider.month.december") + ][date.getMonth()], + "year": date.getFullYear() + }); + $('#revision_date').html(revisionDate) + + } - debugLog('Time Delta: ', timeDelta) updateTimer(); - - var authors = _.map(padContents.getActiveAuthors(), function(name) - { - return authorData[name]; - }); - - BroadcastSlider.setAuthors(authors); - } - function updateTimer() - { - var zpad = function(str, length) - { - str = str + ""; - while (str.length < length) - str = '0' + str; - return str; - } - - var date = new Date(padContents.currentTime); - var dateFormat = function() - { - var month = zpad(date.getMonth() + 1, 2); - var day = zpad(date.getDate(), 2); - var year = (date.getFullYear()); - var hours = zpad(date.getHours(), 2); - var minutes = zpad(date.getMinutes(), 2); - var seconds = zpad(date.getSeconds(), 2); - return (html10n.get("timeslider.dateformat", { - "day": day, - "month": month, - "year": year, - "hours": hours, - "minutes": minutes, - "seconds": seconds - })); - } - - - - - - $('#timer').html(dateFormat()); - var revisionDate = html10n.get("timeslider.saved", { - "day": date.getDate(), - "month": [ - html10n.get("timeslider.month.january"), - html10n.get("timeslider.month.february"), - html10n.get("timeslider.month.march"), - html10n.get("timeslider.month.april"), - html10n.get("timeslider.month.may"), - html10n.get("timeslider.month.june"), - html10n.get("timeslider.month.july"), - html10n.get("timeslider.month.august"), - html10n.get("timeslider.month.september"), - html10n.get("timeslider.month.october"), - html10n.get("timeslider.month.november"), - html10n.get("timeslider.month.december") - ][date.getMonth()], - "year": date.getFullYear() - }); - $('#revision_date').html(revisionDate) - - } - - updateTimer(); - - function goToRevision(newRevision) - { - padContents.targetRevision = newRevision; - var self = this; - var path = revisionInfo.getPath(padContents.currentRevision, newRevision); - debugLog('newRev: ', padContents.currentRevision, path); - if (path.status == 'complete') + function goToRevision(newRevision) { - var cs = path.changesets; - debugLog("status: complete, changesets: ", cs, "path:", path); - var changeset = cs[0]; - var timeDelta = path.times[0]; - for (var i = 1; i < cs.length; i++) + padContents.targetRevision = newRevision; + var self = this; + var path = revisionInfo.getPath(padContents.currentRevision, newRevision); + debugLog('newRev: ', padContents.currentRevision, path); + if (path.status == 'complete') { - changeset = Changeset.compose(changeset, cs[i], padContents.apool); - timeDelta += path.times[i]; - } - if (changeset) applyChangeset(changeset, path.rev, true, timeDelta); - } - else if (path.status == "partial") - { - debugLog('partial'); - var sliderLocation = padContents.currentRevision; - // callback is called after changeset information is pulled from server - // this may never get called, if the changeset has already been loaded - var update = function(start, end) + var cs = path.changesets; + debugLog("status: complete, changesets: ", cs, "path:", path); + var changeset = cs[0]; + var timeDelta = path.times[0]; + for (var i = 1; i < cs.length; i++) { - // if we've called goToRevision in the time since, don't goToRevision - goToRevision(padContents.targetRevision); - }; - - // do our best with what we have... - var cs = path.changesets; - - var changeset = cs[0]; - var timeDelta = path.times[0]; - for (var i = 1; i < cs.length; i++) - { - changeset = Changeset.compose(changeset, cs[i], padContents.apool); - timeDelta += path.times[i]; + changeset = Changeset.compose(changeset, cs[i], padContents.apool); + timeDelta += path.times[i]; + } + if (changeset) applyChangeset(changeset, path.rev, true, timeDelta); } - if (changeset) applyChangeset(changeset, path.rev, true, timeDelta); - - // Loading changeset history for new revision - loadChangesetsForRevision(newRevision, update); - // Loading changeset history for old revision (to make diff between old and new revision) - loadChangesetsForRevision(padContents.currentRevision - 1); - } - - var authors = _.map(padContents.getActiveAuthors(), function(name){ - return authorData[name]; - }); - BroadcastSlider.setAuthors(authors); - } - - function loadChangesetsForRevision(revision, callback) { - if (BroadcastSlider.getSliderLength() > 10000) - { - var start = (Math.floor((revision) / 10000) * 10000); // revision 0 to 10 - changesetLoader.queueUp(start, 100); - } - - if (BroadcastSlider.getSliderLength() > 1000) - { - var start = (Math.floor((revision) / 1000) * 1000); // (start from -1, go to 19) + 1 - changesetLoader.queueUp(start, 10); - } - - start = (Math.floor((revision) / 100) * 100); - - changesetLoader.queueUp(start, 1, callback); - } - - changesetLoader = { - running: false, - resolved: [], - requestQueue1: [], - requestQueue2: [], - requestQueue3: [], - reqCallbacks: [], - queueUp: function(revision, width, callback) - { - if (revision < 0) revision = 0; - // if(changesetLoader.requestQueue.indexOf(revision) != -1) - // return; // already in the queue. - if (changesetLoader.resolved.indexOf(revision + "_" + width) != -1) return; // already loaded from the server - changesetLoader.resolved.push(revision + "_" + width); - - var requestQueue = width == 1 ? changesetLoader.requestQueue3 : width == 10 ? changesetLoader.requestQueue2 : changesetLoader.requestQueue1; - requestQueue.push( + else if (path.status == "partial") { - 'rev': revision, - 'res': width, - 'callback': callback + debugLog('partial'); + var sliderLocation = padContents.currentRevision; + // callback is called after changeset information is pulled from server + // this may never get called, if the changeset has already been loaded + var update = function(start, end) + { + // if we've called goToRevision in the time since, don't goToRevision + goToRevision(padContents.targetRevision); + }; + + // do our best with what we have... + var cs = path.changesets; + + var changeset = cs[0]; + var timeDelta = path.times[0]; + for (var i = 1; i < cs.length; i++) + { + changeset = Changeset.compose(changeset, cs[i], padContents.apool); + timeDelta += path.times[i]; + } + if (changeset) applyChangeset(changeset, path.rev, true, timeDelta); + + // Loading changeset history for new revision + loadChangesetsForRevision(newRevision, update); + // Loading changeset history for old revision (to make diff between old and new revision) + loadChangesetsForRevision(padContents.currentRevision - 1); + } + + var authors = _.map(padContents.getActiveAuthors(), function(name){ + return authorData[name]; }); - if (!changesetLoader.running) - { - changesetLoader.running = true; - setTimeout(changesetLoader.loadFromQueue, 10); - } - }, - loadFromQueue: function() - { - var self = changesetLoader; - var requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : self.requestQueue2.length > 0 ? self.requestQueue2 : self.requestQueue3.length > 0 ? self.requestQueue3 : null; + BroadcastSlider.setAuthors(authors); + } - if (!requestQueue) + function loadChangesetsForRevision(revision, callback) { + if (BroadcastSlider.getSliderLength() > 10000) { - self.running = false; - return; + var start = (Math.floor((revision) / 10000) * 10000); // revision 0 to 10 + changesetLoader.queueUp(start, 100); } - var request = requestQueue.pop(); - var granularity = request.res; - var callback = request.callback; - var start = request.rev; - var requestID = Math.floor(Math.random() * 100000); - - sendSocketMsg("CHANGESET_REQ", { - "start": start, - "granularity": granularity, - "requestID": requestID - }); - - self.reqCallbacks[requestID] = callback; - }, - handleSocketResponse: function(message) - { - var self = changesetLoader; - - var start = message.data.start; - var granularity = message.data.granularity; - var callback = self.reqCallbacks[message.data.requestID]; - delete self.reqCallbacks[message.data.requestID]; - - self.handleResponse(message.data, start, granularity, callback); - setTimeout(self.loadFromQueue, 10); - }, - handleResponse: function(data, start, granularity, callback) - { - debugLog("response: ", data); - var pool = (new AttribPool()).fromJsonable(data.apool); - for (var i = 0; i < data.forwardsChangesets.length; i++) + if (BroadcastSlider.getSliderLength() > 1000) { - var astart = start + i * granularity - 1; // rev -1 is a blank single line - var aend = start + (i + 1) * granularity - 1; // totalRevs is the most recent revision - if (aend > data.actualEndNum - 1) aend = data.actualEndNum - 1; - //debugLog("adding changeset:", astart, aend); - var forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool); - var backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool); - revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]); + var start = (Math.floor((revision) / 1000) * 1000); // (start from -1, go to 19) + 1 + changesetLoader.queueUp(start, 10); } - if (callback) callback(start - 1, start + data.forwardsChangesets.length * granularity - 1); - }, - handleMessageFromServer: function (obj) - { - debugLog("handleMessage:", arguments); - if (obj.type == "COLLABROOM") + start = (Math.floor((revision) / 100) * 100); + + changesetLoader.queueUp(start, 1, callback); + } + + changesetLoader = { + running: false, + resolved: [], + requestQueue1: [], + requestQueue2: [], + requestQueue3: [], + reqCallbacks: [], + queueUp: function(revision, width, callback) { - obj = obj.data; + if (revision < 0) revision = 0; + // if(changesetLoader.requestQueue.indexOf(revision) != -1) + // return; // already in the queue. + if (changesetLoader.resolved.indexOf(revision + "_" + width) != -1) return; // already loaded from the server + changesetLoader.resolved.push(revision + "_" + width); - if (obj.type == "NEW_CHANGES") + var requestQueue = width == 1 ? changesetLoader.requestQueue3 : width == 10 ? changesetLoader.requestQueue2 : changesetLoader.requestQueue1; + requestQueue.push( { - debugLog(obj); - var changeset = Changeset.moveOpsToNewPool( - obj.changeset, (new AttribPool()).fromJsonable(obj.apool), padContents.apool); - - var changesetBack = Changeset.inverse( - obj.changeset, padContents.currentLines, padContents.alines, padContents.apool); - - var changesetBack = Changeset.moveOpsToNewPool( - changesetBack, (new AttribPool()).fromJsonable(obj.apool), padContents.apool); - - loadedNewChangeset(changeset, changesetBack, obj.newRev - 1, obj.timeDelta); - } - else if (obj.type == "NEW_AUTHORDATA") + 'rev': revision, + 'res': width, + 'callback': callback + }); + if (!changesetLoader.running) { - var authorMap = {}; - authorMap[obj.author] = obj.data; - receiveAuthorData(authorMap); - - var authors = _.map(padContents.getActiveAuthors(), function(name) { - return authorData[name]; - }); - - BroadcastSlider.setAuthors(authors); + changesetLoader.running = true; + setTimeout(changesetLoader.loadFromQueue, 10); } - else if (obj.type == "NEW_SAVEDREV") - { - var savedRev = obj.savedRev; - BroadcastSlider.addSavedRevision(savedRev.revNum, savedRev); - } - hooks.callAll('handleClientTimesliderMessage_' + obj.type, {payload: obj}); - } - else if(obj.type == "CHANGESET_REQ") + }, + loadFromQueue: function() { - changesetLoader.handleSocketResponse(obj); + var self = changesetLoader; + var requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : self.requestQueue2.length > 0 ? self.requestQueue2 : self.requestQueue3.length > 0 ? self.requestQueue3 : null; + + if (!requestQueue) + { + self.running = false; + return; + } + + var request = requestQueue.pop(); + var granularity = request.res; + var callback = request.callback; + var start = request.rev; + var requestID = Math.floor(Math.random() * 100000); + + sendSocketMsg("CHANGESET_REQ", { + "start": start, + "granularity": granularity, + "requestID": requestID + }); + + self.reqCallbacks[requestID] = callback; + }, + handleSocketResponse: function(message) + { + var self = changesetLoader; + + var start = message.data.start; + var granularity = message.data.granularity; + var callback = self.reqCallbacks[message.data.requestID]; + delete self.reqCallbacks[message.data.requestID]; + + self.handleResponse(message.data, start, granularity, callback); + setTimeout(self.loadFromQueue, 10); + }, + handleResponse: function(data, start, granularity, callback) + { + debugLog("response: ", data); + var pool = (new AttribPool()).fromJsonable(data.apool); + for (var i = 0; i < data.forwardsChangesets.length; i++) + { + var astart = start + i * granularity - 1; // rev -1 is a blank single line + var aend = start + (i + 1) * granularity - 1; // totalRevs is the most recent revision + if (aend > data.actualEndNum - 1) aend = data.actualEndNum - 1; + //debugLog("adding changeset:", astart, aend); + var forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool); + var backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool); + revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]); + } + if (callback) callback(start - 1, start + data.forwardsChangesets.length * granularity - 1); + }, + handleMessageFromServer: function (obj) + { + debugLog("handleMessage:", arguments); + + if (obj.type == "COLLABROOM") + { + obj = obj.data; + + if (obj.type == "NEW_CHANGES") + { + debugLog(obj); + var changeset = Changeset.moveOpsToNewPool( + obj.changeset, (new AttribPool()).fromJsonable(obj.apool), padContents.apool); + + var changesetBack = Changeset.inverse( + obj.changeset, padContents.currentLines, padContents.alines, padContents.apool); + + var changesetBack = Changeset.moveOpsToNewPool( + changesetBack, (new AttribPool()).fromJsonable(obj.apool), padContents.apool); + + loadedNewChangeset(changeset, changesetBack, obj.newRev - 1, obj.timeDelta); + } + else if (obj.type == "NEW_AUTHORDATA") + { + var authorMap = {}; + authorMap[obj.author] = obj.data; + receiveAuthorData(authorMap); + + var authors = _.map(padContents.getActiveAuthors(), function(name) { + return authorData[name]; + }); + + BroadcastSlider.setAuthors(authors); + } + else if (obj.type == "NEW_SAVEDREV") + { + var savedRev = obj.savedRev; + BroadcastSlider.addSavedRevision(savedRev.revNum, savedRev); + } + hooks.callAll('handleClientTimesliderMessage_' + obj.type, {payload: obj}); + } + else if(obj.type == "CHANGESET_REQ") + { + changesetLoader.handleSocketResponse(obj); + } + else + { + debugLog("Unknown message type: " + obj.type); + } + } + }; + + // to start upon window load, just push a function onto this array + //window['onloadFuncts'].push(setUpSocket); + //window['onloadFuncts'].push(function () + fireWhenAllScriptsAreLoaded.push(function() + { + // set up the currentDivs and DOM + padContents.currentDivs = []; + $("#padcontent").html(""); + for (var i = 0; i < padContents.currentLines.length; i++) + { + var div = padContents.lineToElement(padContents.currentLines[i], padContents.alines[i]); + padContents.currentDivs.push(div); + $("#padcontent").append(div); + } + debugLog(padContents.currentDivs); + }); + + // this is necessary to keep infinite loops of events firing, + // since goToRevision changes the slider position + var goToRevisionIfEnabledCount = 0; + var goToRevisionIfEnabled = function() { + if (goToRevisionIfEnabledCount > 0) + { + goToRevisionIfEnabledCount--; } else { - debugLog("Unknown message type: " + obj.type); + goToRevision.apply(goToRevision, arguments); } } - }; - // to start upon window load, just push a function onto this array - //window['onloadFuncts'].push(setUpSocket); - //window['onloadFuncts'].push(function () - fireWhenAllScriptsAreLoaded.push(function() - { - // set up the currentDivs and DOM - padContents.currentDivs = []; - $("#padcontent").html(""); - for (var i = 0; i < padContents.currentLines.length; i++) - { - var div = padContents.lineToElement(padContents.currentLines[i], padContents.alines[i]); - padContents.currentDivs.push(div); - $("#padcontent").append(div); - } - debugLog(padContents.currentDivs); - }); + BroadcastSlider.onSlider(goToRevisionIfEnabled); - // this is necessary to keep infinite loops of events firing, - // since goToRevision changes the slider position - var goToRevisionIfEnabledCount = 0; - var goToRevisionIfEnabled = function() { - if (goToRevisionIfEnabledCount > 0) - { - goToRevisionIfEnabledCount--; - } - else - { - goToRevision.apply(goToRevision, arguments); - } - } - - BroadcastSlider.onSlider(goToRevisionIfEnabled); + var dynamicCSS = makeCSSManager('dynamicsyntax'); + var authorData = {}; - var dynamicCSS = makeCSSManager('dynamicsyntax'); - var authorData = {}; - - function receiveAuthorData(newAuthorData) - { - for (var author in newAuthorData) + function receiveAuthorData(newAuthorData) { - var data = newAuthorData[author]; - var bgcolor = typeof data.colorId == "number" ? clientVars.colorPalette[data.colorId] : data.colorId; - if (bgcolor && dynamicCSS) + for (var author in newAuthorData) { - var selector = dynamicCSS.selectorStyle('.' + linestylefilter.getAuthorClassName(author)); - selector.backgroundColor = bgcolor - selector.color = (colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5) ? '#ffffff' : '#000000'; //see ace2_inner.js for the other part + var data = newAuthorData[author]; + var bgcolor = typeof data.colorId == "number" ? clientVars.colorPalette[data.colorId] : data.colorId; + if (bgcolor && dynamicCSS) + { + var selector = dynamicCSS.selectorStyle('.' + linestylefilter.getAuthorClassName(author)); + selector.backgroundColor = bgcolor + selector.color = (colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5) ? '#ffffff' : '#000000'; //see ace2_inner.js for the other part + } + authorData[author] = data; } - authorData[author] = data; } + + receiveAuthorData(clientVars.collab_client_vars.historicalAuthorData); + + return changesetLoader; } - receiveAuthorData(clientVars.collab_client_vars.historicalAuthorData); + exports.loadBroadcastJS = loadBroadcastJS; - return changesetLoader; -} - -exports.loadBroadcastJS = loadBroadcastJS; + return exports; +}); diff --git a/src/static/js/domline.js b/src/static/js/domline.js index c40c6e204..f97a5ca80 100644 --- a/src/static/js/domline.js +++ b/src/static/js/domline.js @@ -26,296 +26,300 @@ // requires: plugins // requires: undefined -var Security = require('./security'); -var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); -var _ = require('./underscore'); -var lineAttributeMarker = require('./linestylefilter').lineAttributeMarker; -var noop = function(){}; +define(['ep_etherpad-lite/static/js/pluginfw/hooks', 'ep_etherpad-lite/static/js/linestylefilter', 'underscore'], function(hooks, linestylefilterMod, _) { + var exports = {}; + + var Security = require('./security'); + var lineAttributeMarker = linestylefilterMod.lineAttributeMarker; + var noop = function(){}; -var domline = {}; + var domline = {}; -domline.addToLineClass = function(lineClass, cls) -{ - // an "empty span" at any point can be used to add classes to - // the line, using line:className. otherwise, we ignore - // the span. - cls.replace(/\S+/g, function(c) + domline.addToLineClass = function(lineClass, cls) { - if (c.indexOf("line:") == 0) + // an "empty span" at any point can be used to add classes to + // the line, using line:className. otherwise, we ignore + // the span. + cls.replace(/\S+/g, function(c) { - // add class to line - lineClass = (lineClass ? lineClass + ' ' : '') + c.substring(5); - } - }); - return lineClass; -} - -// if "document" is falsy we don't create a DOM node, just -// an object with innerHTML and className -domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) -{ - var result = { - node: null, - appendSpan: noop, - prepareForAdd: noop, - notifyAdded: noop, - clearSpans: noop, - finishUpdate: noop, - lineMarker: 0 - }; - - var document = optDocument; - - if (document) - { - result.node = document.createElement("div"); + if (c.indexOf("line:") == 0) + { + // add class to line + lineClass = (lineClass ? lineClass + ' ' : '') + c.substring(5); + } + }); + return lineClass; } - else + + // if "document" is falsy we don't create a DOM node, just + // an object with innerHTML and className + domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) { - result.node = { - innerHTML: '', - className: '' + var result = { + node: null, + appendSpan: noop, + prepareForAdd: noop, + notifyAdded: noop, + clearSpans: noop, + finishUpdate: noop, + lineMarker: 0 }; - } - var html = []; - var preHtml = '', - postHtml = ''; - var curHTML = null; + var document = optDocument; - function processSpaces(s) - { - return domline.processSpaces(s, doesWrap); - } - - var perTextNodeProcess = (doesWrap ? _.identity : processSpaces); - var perHtmlLineProcess = (doesWrap ? processSpaces : _.identity); - var lineClass = 'ace-line'; - - result.appendSpan = function(txt, cls) - { - - var processedMarker = false; - // Handle lineAttributeMarker, if present - if (cls.indexOf(lineAttributeMarker) >= 0) + if (document) { - var listType = /(?:^| )list:(\S+)/.exec(cls); - var start = /(?:^| )start:(\S+)/.exec(cls); + result.node = document.createElement("div"); + } + else + { + result.node = { + innerHTML: '', + className: '' + }; + } - _.map(hooks.callAll("aceDomLinePreProcessLineAttributes", { - domline: domline, - cls: cls - }), function(modifier) - { - preHtml += modifier.preHtml; - postHtml += modifier.postHtml; - processedMarker |= modifier.processedMarker; - }); + var html = []; + var preHtml = '', + postHtml = ''; + var curHTML = null; - if (listType) + function processSpaces(s) + { + return domline.processSpaces(s, doesWrap); + } + + var perTextNodeProcess = (doesWrap ? _.identity : processSpaces); + var perHtmlLineProcess = (doesWrap ? processSpaces : _.identity); + var lineClass = 'ace-line'; + + result.appendSpan = function(txt, cls) + { + + var processedMarker = false; + // Handle lineAttributeMarker, if present + if (cls.indexOf(lineAttributeMarker) >= 0) { - listType = listType[1]; + var listType = /(?:^| )list:(\S+)/.exec(cls); + var start = /(?:^| )start:(\S+)/.exec(cls); + + _.map(hooks.callAll("aceDomLinePreProcessLineAttributes", { + domline: domline, + cls: cls + }), function(modifier) + { + preHtml += modifier.preHtml; + postHtml += modifier.postHtml; + processedMarker |= modifier.processedMarker; + }); + if (listType) { - if(listType.indexOf("number") < 0) + listType = listType[1]; + if (listType) { - preHtml += '