lint: broadcast_revisions (#4571)

Co-authored-by: Richard Hansen <rhansen@rhansen.org>
This commit is contained in:
John McLear 2020-12-15 17:37:15 +00:00 committed by Richard Hansen
parent 4aef15cb11
commit 8bf463fb00
3 changed files with 231 additions and 271 deletions

View File

@ -1,3 +1,5 @@
'use strict';
/** /**
* This code is mostly from the old Etherpad. Please help us to comment this code. * This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it. * This helps other people to understand this code better and helps them to improve it.
@ -32,51 +34,33 @@ const hooks = require('./pluginfw/hooks');
// These parameters were global, now they are injected. A reference to the // These parameters were global, now they are injected. A reference to the
// Timeslider controller would probably be more appropriate. // Timeslider controller would probably be more appropriate.
function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider) { function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider) {
let goToRevisionIfEnabledCount = 0;
let changesetLoader = undefined; let changesetLoader = undefined;
// Below Array#indexOf code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_indexof.htm const debugLog = (...args) => {
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (elt /* , from*/) {
const len = this.length >>> 0;
let from = Number(arguments[1]) || 0;
from = (from < 0) ? Math.ceil(from) : Math.floor(from);
if (from < 0) from += len;
for (; from < len; from++) {
if (from in this && this[from] === elt) return from;
}
return -1;
};
}
function debugLog() {
try { try {
if (window.console) console.log.apply(console, arguments); if (window.console) console.log(...args);
} catch (e) { } catch (e) {
if (window.console) console.log('error printing: ', e); if (window.console) console.log('error printing: ', e);
} }
} };
// var socket;
const channelState = 'DISCONNECTED';
const appLevelDisconnectReason = null;
const padContents = { const padContents = {
currentRevision: clientVars.collab_client_vars.rev, currentRevision: clientVars.collab_client_vars.rev,
currentTime: clientVars.collab_client_vars.time, currentTime: clientVars.collab_client_vars.time,
currentLines: Changeset.splitTextLines(clientVars.collab_client_vars.initialAttributedText.text), currentLines:
Changeset.splitTextLines(clientVars.collab_client_vars.initialAttributedText.text),
currentDivs: null, currentDivs: null,
// to be filled in once the dom loads // to be filled in once the dom loads
apool: (new AttribPool()).fromJsonable(clientVars.collab_client_vars.apool), apool: (new AttribPool()).fromJsonable(clientVars.collab_client_vars.apool),
alines: Changeset.splitAttributionLines( alines: Changeset.splitAttributionLines(
clientVars.collab_client_vars.initialAttributedText.attribs, clientVars.collab_client_vars.initialAttributedText.text), clientVars.collab_client_vars.initialAttributedText.attribs,
clientVars.collab_client_vars.initialAttributedText.text),
// generates a jquery element containing HTML for a line // generates a jquery element containing HTML for a line
lineToElement(line, aline) { lineToElement(line, aline) {
const element = document.createElement('div'); const element = document.createElement('div');
const emptyLine = (line == '\n'); const emptyLine = (line === '\n');
const domInfo = domline.createDomLine(!emptyLine, true); const domInfo = domline.createDomLine(!emptyLine, true);
linestylefilter.populateDomLine(line, aline, this.apool, domInfo); linestylefilter.populateDomLine(line, aline, this.apool, domInfo);
domInfo.prepareForAdd(); domInfo.prepareForAdd();
@ -86,9 +70,10 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
return $(element); return $(element);
}, },
applySpliceToDivs(start, numRemoved, newLines) { // splice the lines
splice(start, numRemoved, ...newLines) {
// remove spliced-out lines from DOM // remove spliced-out lines from DOM
for (var i = start; i < start + numRemoved && i < this.currentDivs.length; i++) { for (let i = start; i < start + numRemoved && i < this.currentDivs.length; i++) {
this.currentDivs[i].remove(); this.currentDivs[i].remove();
} }
@ -96,7 +81,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
this.currentDivs.splice(start, numRemoved); this.currentDivs.splice(start, numRemoved);
const newDivs = []; const newDivs = [];
for (var i = 0; i < newLines.length; i++) { for (let i = 0; i < newLines.length; i++) {
newDivs.push(this.lineToElement(newLines[i], this.alines[start + i])); newDivs.push(this.lineToElement(newLines[i], this.alines[start + i]));
} }
@ -104,7 +89,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
let startDiv = this.currentDivs[start - 1] || null; let startDiv = this.currentDivs[start - 1] || null;
// insert the div elements into the correct place, in the correct order // insert the div elements into the correct place, in the correct order
for (var i = 0; i < newDivs.length; i++) { for (let i = 0; i < newDivs.length; i++) {
if (startDiv) { if (startDiv) {
startDiv.after(newDivs[i]); startDiv.after(newDivs[i]);
} else { } else {
@ -114,23 +99,10 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
} }
// insert new divs into currentDivs array // insert new divs into currentDivs array
newDivs.unshift(0); // remove 0 elements this.currentDivs.splice(start, 0, ...newDivs);
newDivs.unshift(start);
this.currentDivs.splice.apply(this.currentDivs, newDivs);
return this;
},
// splice the lines
splice(start, numRemoved, newLinesVA) {
const newLines = _.map(Array.prototype.slice.call(arguments, 2), (s) => s);
// apply this splice to the divs
this.applySpliceToDivs(start, numRemoved, newLines);
// call currentLines.splice, to keep the currentLines array up to date // call currentLines.splice, to keep the currentLines array up to date
newLines.unshift(numRemoved); this.currentLines.splice(start, numRemoved, ...newLines);
newLines.unshift(start);
this.currentLines.splice.apply(this.currentLines, arguments);
}, },
// returns the contents of the specified line I // returns the contents of the specified line I
get(i) { get(i) {
@ -142,16 +114,15 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
}, },
getActiveAuthors() { getActiveAuthors() {
const self = this;
const authors = []; const authors = [];
const seenNums = {}; const seenNums = {};
const alines = self.alines; const alines = this.alines;
for (let i = 0; i < alines.length; i++) { for (let i = 0; i < alines.length; i++) {
Changeset.eachAttribNumber(alines[i], (n) => { Changeset.eachAttribNumber(alines[i], (n) => {
if (!seenNums[n]) { if (!seenNums[n]) {
seenNums[n] = true; seenNums[n] = true;
if (self.apool.getAttribKey(n) == 'author') { if (this.apool.getAttribKey(n) === 'author') {
const a = self.apool.getAttribValue(n); const a = this.apool.getAttribValue(n);
if (a) { if (a) {
authors.push(a); authors.push(a);
} }
@ -164,42 +135,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
}, },
}; };
function callCatchingErrors(catcher, func) { const applyChangeset = (changeset, revision, preventSliderMovement, timeDelta) => {
try {
wrapRecordingErrors(catcher, func)();
} catch (e) { /* absorb*/
}
}
function wrapRecordingErrors(catcher, func) {
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;
}
};
}
function loadedNewChangeset(changesetForward, changesetBackward, revision, timeDelta) {
const broadcasting = (BroadcastSlider.getSliderPosition() == revisionInfo.latest);
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 // disable the next 'gotorevision' call handled by a timeslider update
if (!preventSliderMovement) { if (!preventSliderMovement) {
goToRevisionIfEnabledCount++; goToRevisionIfEnabledCount++;
@ -215,7 +151,8 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
} }
// scroll to the area that is changed before the lines are mutated // scroll to the area that is changed before the lines are mutated
if ($('#options-followContents').is(':checked') || $('#options-followContents').prop('checked')) { if ($('#options-followContents').is(':checked') ||
$('#options-followContents').prop('checked')) {
// get the index of the first line that has mutated attributes // get the index of the first line that has mutated attributes
// the last line in `oldAlines` should always equal to "|1+1", ie newline without attributes // the last line in `oldAlines` should always equal to "|1+1", ie newline without attributes
// so it should be safe to assume this line has changed attributes when inserting content at // so it should be safe to assume this line has changed attributes when inserting content at
@ -227,11 +164,25 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
return true; // break return true; // break
} }
}); });
// deal with someone is the author of a line and changes one character, so the alines won't change // deal with someone is the author of a line and changes one character,
// so the alines won't change
if (lineChanged === undefined) { if (lineChanged === undefined) {
lineChanged = Changeset.opIterator(Changeset.unpack(changeset).ops).next().lines; lineChanged = Changeset.opIterator(Changeset.unpack(changeset).ops).next().lines;
} }
const goToLineNumber = (lineNumber) => {
// Sets the Y scrolling of the browser to go to this line
const line = $('#innerdocbody').find(`div:nth-child(${lineNumber + 1})`);
const newY = $(line)[0].offsetTop;
const ecb = document.getElementById('editorcontainerbox');
// Chrome 55 - 59 bugfix
if (ecb.scrollTo) {
ecb.scrollTo({top: newY, behavior: 'smooth'});
} else {
$('#editorcontainerbox').scrollTop(newY);
}
};
goToLineNumber(lineChanged); goToLineNumber(lineChanged);
} }
@ -244,17 +195,32 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]); const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors); BroadcastSlider.setAuthors(authors);
} };
function updateTimer() { const loadedNewChangeset = (changesetForward, changesetBackward, revision, timeDelta) => {
const zpad = function (str, length) { const revisionInfo = window.revisionInfo;
const broadcasting = (BroadcastSlider.getSliderPosition() === revisionInfo.latest);
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.
*/
const updateTimer = () => {
const zpad = (str, length) => {
str = `${str}`; str = `${str}`;
while (str.length < length) str = `0${str}`; while (str.length < length) str = `0${str}`;
return str; return str;
}; };
const date = new Date(padContents.currentTime); const date = new Date(padContents.currentTime);
const dateFormat = function () { const dateFormat = () => {
const month = zpad(date.getMonth() + 1, 2); const month = zpad(date.getMonth() + 1, 2);
const day = zpad(date.getDate(), 2); const day = zpad(date.getDate(), 2);
const year = (date.getFullYear()); const year = (date.getFullYear());
@ -292,43 +258,41 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
year: date.getFullYear(), year: date.getFullYear(),
}); });
$('#revision_date').html(revisionDate); $('#revision_date').html(revisionDate);
} };
updateTimer(); updateTimer();
function goToRevision(newRevision) { const goToRevision = (newRevision) => {
padContents.targetRevision = newRevision; padContents.targetRevision = newRevision;
const self = this; const path = window.revisionInfo.getPath(padContents.currentRevision, newRevision);
const path = revisionInfo.getPath(padContents.currentRevision, newRevision);
hooks.aCallAll('goToRevisionEvent', { hooks.aCallAll('goToRevisionEvent', {
rev: newRevision, rev: newRevision,
}); });
if (path.status == 'complete') { if (path.status === 'complete') {
var cs = path.changesets; const cs = path.changesets;
var changeset = cs[0]; let changeset = cs[0];
var timeDelta = path.times[0]; let timeDelta = path.times[0];
for (var i = 1; i < cs.length; i++) { for (let i = 1; i < cs.length; i++) {
changeset = Changeset.compose(changeset, cs[i], padContents.apool); changeset = Changeset.compose(changeset, cs[i], padContents.apool);
timeDelta += path.times[i]; timeDelta += path.times[i];
} }
if (changeset) applyChangeset(changeset, path.rev, true, timeDelta); if (changeset) applyChangeset(changeset, path.rev, true, timeDelta);
} else if (path.status == 'partial') { } else if (path.status === 'partial') {
const sliderLocation = padContents.currentRevision;
// callback is called after changeset information is pulled from server // callback is called after changeset information is pulled from server
// this may never get called, if the changeset has already been loaded // this may never get called, if the changeset has already been loaded
const update = function (start, end) { const update = (start, end) => {
// if we've called goToRevision in the time since, don't goToRevision // if we've called goToRevision in the time since, don't goToRevision
goToRevision(padContents.targetRevision); goToRevision(padContents.targetRevision);
}; };
// do our best with what we have... // do our best with what we have...
var cs = path.changesets; const cs = path.changesets;
var changeset = cs[0]; let changeset = cs[0];
var timeDelta = path.times[0]; let timeDelta = path.times[0];
for (var i = 1; i < cs.length; i++) { for (let i = 1; i < cs.length; i++) {
changeset = Changeset.compose(changeset, cs[i], padContents.apool); changeset = Changeset.compose(changeset, cs[i], padContents.apool);
timeDelta += path.times[i]; timeDelta += path.times[i];
} }
@ -342,23 +306,23 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]); const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors); BroadcastSlider.setAuthors(authors);
} };
function loadChangesetsForRevision(revision, callback) { const loadChangesetsForRevision = (revision, callback) => {
if (BroadcastSlider.getSliderLength() > 10000) { if (BroadcastSlider.getSliderLength() > 10000) {
var start = (Math.floor((revision) / 10000) * 10000); // revision 0 to 10 const start = (Math.floor((revision) / 10000) * 10000); // revision 0 to 10
changesetLoader.queueUp(start, 100); changesetLoader.queueUp(start, 100);
} }
if (BroadcastSlider.getSliderLength() > 1000) { if (BroadcastSlider.getSliderLength() > 1000) {
var start = (Math.floor((revision) / 1000) * 1000); // (start from -1, go to 19) + 1 const start = (Math.floor((revision) / 1000) * 1000); // (start from -1, go to 19) + 1
changesetLoader.queueUp(start, 10); changesetLoader.queueUp(start, 10);
} }
start = (Math.floor((revision) / 100) * 100); const start = (Math.floor((revision) / 100) * 100);
changesetLoader.queueUp(start, 1, callback); changesetLoader.queueUp(start, 1, callback);
} };
changesetLoader = { changesetLoader = {
running: false, running: false,
@ -369,29 +333,38 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
reqCallbacks: [], reqCallbacks: [],
queueUp(revision, width, callback) { queueUp(revision, width, callback) {
if (revision < 0) revision = 0; if (revision < 0) revision = 0;
// if(changesetLoader.requestQueue.indexOf(revision) != -1) // if(this.requestQueue.indexOf(revision) != -1)
// return; // already in the queue. // return; // already in the queue.
if (changesetLoader.resolved.indexOf(`${revision}_${width}`) != -1) return; // already loaded from the server if (this.resolved.indexOf(`${revision}_${width}`) !== -1) {
changesetLoader.resolved.push(`${revision}_${width}`); // already loaded from the server
return;
}
this.resolved.push(`${revision}_${width}`);
const requestQueue = width == 1 ? changesetLoader.requestQueue3 : width == 10 ? changesetLoader.requestQueue2 : changesetLoader.requestQueue1; const requestQueue =
width === 1 ? this.requestQueue3
: width === 10 ? this.requestQueue2
: this.requestQueue1;
requestQueue.push( requestQueue.push(
{ {
rev: revision, rev: revision,
res: width, res: width,
callback, callback,
}); });
if (!changesetLoader.running) { if (!this.running) {
changesetLoader.running = true; this.running = true;
setTimeout(changesetLoader.loadFromQueue, 10); setTimeout(() => this.loadFromQueue(), 10);
} }
}, },
loadFromQueue() { loadFromQueue() {
const self = changesetLoader; const requestQueue =
const requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : self.requestQueue2.length > 0 ? self.requestQueue2 : self.requestQueue3.length > 0 ? self.requestQueue3 : null; this.requestQueue1.length > 0 ? this.requestQueue1
: this.requestQueue2.length > 0 ? this.requestQueue2
: this.requestQueue3.length > 0 ? this.requestQueue3
: null;
if (!requestQueue) { if (!requestQueue) {
self.running = false; this.running = false;
return; return;
} }
@ -407,48 +380,48 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
requestID, requestID,
}); });
self.reqCallbacks[requestID] = callback; this.reqCallbacks[requestID] = callback;
}, },
handleSocketResponse(message) { handleSocketResponse(message) {
const self = changesetLoader;
const start = message.data.start; const start = message.data.start;
const granularity = message.data.granularity; const granularity = message.data.granularity;
const callback = self.reqCallbacks[message.data.requestID]; const callback = this.reqCallbacks[message.data.requestID];
delete self.reqCallbacks[message.data.requestID]; delete this.reqCallbacks[message.data.requestID];
self.handleResponse(message.data, start, granularity, callback); this.handleResponse(message.data, start, granularity, callback);
setTimeout(self.loadFromQueue, 10); setTimeout(() => this.loadFromQueue(), 10);
}, },
handleResponse(data, start, granularity, callback) { handleResponse: (data, start, granularity, callback) => {
const pool = (new AttribPool()).fromJsonable(data.apool); const pool = (new AttribPool()).fromJsonable(data.apool);
for (let i = 0; i < data.forwardsChangesets.length; i++) { for (let i = 0; i < data.forwardsChangesets.length; i++) {
const astart = start + i * granularity - 1; // rev -1 is a blank single line const astart = start + i * granularity - 1; // rev -1 is a blank single line
let aend = start + (i + 1) * granularity - 1; // totalRevs is the most recent revision let aend = start + (i + 1) * granularity - 1; // totalRevs is the most recent revision
if (aend > data.actualEndNum - 1) aend = data.actualEndNum - 1; if (aend > data.actualEndNum - 1) aend = data.actualEndNum - 1;
// debugLog("adding changeset:", astart, aend); // debugLog("adding changeset:", astart, aend);
const forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool); const forwardcs =
const backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool); Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool);
revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]); const backwardcs =
Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool);
window.revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]);
} }
if (callback) callback(start - 1, start + data.forwardsChangesets.length * granularity - 1); if (callback) callback(start - 1, start + data.forwardsChangesets.length * granularity - 1);
}, },
handleMessageFromServer(obj) { handleMessageFromServer(obj) {
if (obj.type == 'COLLABROOM') { if (obj.type === 'COLLABROOM') {
obj = obj.data; obj = obj.data;
if (obj.type == 'NEW_CHANGES') { if (obj.type === 'NEW_CHANGES') {
const changeset = Changeset.moveOpsToNewPool( const changeset = Changeset.moveOpsToNewPool(
obj.changeset, (new AttribPool()).fromJsonable(obj.apool), padContents.apool); obj.changeset, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
var changesetBack = Changeset.inverse( let changesetBack = Changeset.inverse(
obj.changeset, padContents.currentLines, padContents.alines, padContents.apool); obj.changeset, padContents.currentLines, padContents.alines, padContents.apool);
var changesetBack = Changeset.moveOpsToNewPool( changesetBack = Changeset.moveOpsToNewPool(
changesetBack, (new AttribPool()).fromJsonable(obj.apool), padContents.apool); changesetBack, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
loadedNewChangeset(changeset, changesetBack, obj.newRev - 1, obj.timeDelta); loadedNewChangeset(changeset, changesetBack, obj.newRev - 1, obj.timeDelta);
} else if (obj.type == 'NEW_AUTHORDATA') { } else if (obj.type === 'NEW_AUTHORDATA') {
const authorMap = {}; const authorMap = {};
authorMap[obj.author] = obj.data; authorMap[obj.author] = obj.data;
receiveAuthorData(authorMap); receiveAuthorData(authorMap);
@ -456,13 +429,13 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]); const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors); BroadcastSlider.setAuthors(authors);
} else if (obj.type == 'NEW_SAVEDREV') { } else if (obj.type === 'NEW_SAVEDREV') {
const savedRev = obj.savedRev; const savedRev = obj.savedRev;
BroadcastSlider.addSavedRevision(savedRev.revNum, savedRev); BroadcastSlider.addSavedRevision(savedRev.revNum, savedRev);
} }
hooks.callAll(`handleClientTimesliderMessage_${obj.type}`, {payload: obj}); hooks.callAll(`handleClientTimesliderMessage_${obj.type}`, {payload: obj});
} else if (obj.type == 'CHANGESET_REQ') { } else if (obj.type === 'CHANGESET_REQ') {
changesetLoader.handleSocketResponse(obj); this.handleSocketResponse(obj);
} else { } else {
debugLog(`Unknown message type: ${obj.type}`); debugLog(`Unknown message type: ${obj.type}`);
} }
@ -485,49 +458,36 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
// this is necessary to keep infinite loops of events firing, // this is necessary to keep infinite loops of events firing,
// since goToRevision changes the slider position // since goToRevision changes the slider position
var goToRevisionIfEnabledCount = 0; const goToRevisionIfEnabled = (...args) => {
const goToRevisionIfEnabled = function () {
if (goToRevisionIfEnabledCount > 0) { if (goToRevisionIfEnabledCount > 0) {
goToRevisionIfEnabledCount--; goToRevisionIfEnabledCount--;
} else { } else {
goToRevision.apply(goToRevision, arguments); goToRevision(...args);
} }
}; };
BroadcastSlider.onSlider(goToRevisionIfEnabled); BroadcastSlider.onSlider(goToRevisionIfEnabled);
const dynamicCSS = makeCSSManager('dynamicsyntax'); const dynamicCSS = makeCSSManager('dynamicsyntax');
var authorData = {}; const authorData = {};
function receiveAuthorData(newAuthorData) { const receiveAuthorData = (newAuthorData) => {
for (const author in newAuthorData) { for (const [author, data] of Object.entries(newAuthorData)) {
const data = newAuthorData[author]; const bgcolor = typeof data.colorId === 'number'
const bgcolor = typeof data.colorId === 'number' ? clientVars.colorPalette[data.colorId] : data.colorId; ? clientVars.colorPalette[data.colorId] : data.colorId;
if (bgcolor && dynamicCSS) { if (bgcolor && dynamicCSS) {
const selector = dynamicCSS.selectorStyle(`.${linestylefilter.getAuthorClassName(author)}`); const selector = dynamicCSS.selectorStyle(`.${linestylefilter.getAuthorClassName(author)}`);
selector.backgroundColor = bgcolor; selector.backgroundColor = bgcolor;
selector.color = (colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5) ? '#ffffff' : '#000000'; // see ace2_inner.js for the other part 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); receiveAuthorData(clientVars.collab_client_vars.historicalAuthorData);
return changesetLoader; return changesetLoader;
function goToLineNumber(lineNumber) {
// Sets the Y scrolling of the browser to go to this line
const line = $('#innerdocbody').find(`div:nth-child(${lineNumber + 1})`);
const newY = $(line)[0].offsetTop;
const ecb = document.getElementById('editorcontainerbox');
// Chrome 55 - 59 bugfix
if (ecb.scrollTo) {
ecb.scrollTo({top: newY, behavior: 'smooth'});
} else {
$('#editorcontainerbox').scrollTop(newY);
}
}
} }
exports.loadBroadcastJS = loadBroadcastJS; exports.loadBroadcastJS = loadBroadcastJS;

View File

@ -1,3 +1,5 @@
'use strict';
/** /**
* This code is mostly from the old Etherpad. Please help us to comment this code. * This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it. * This helps other people to understand this code better and helps them to improve it.
@ -23,7 +25,7 @@
// of the document. These revisions are connected together by various // of the document. These revisions are connected together by various
// changesets, or deltas, between any two revisions. // changesets, or deltas, between any two revisions.
function loadBroadcastRevisionsJS() { const loadBroadcastRevisionsJS = () => {
function Revision(revNum) { function Revision(revNum) {
this.rev = revNum; this.rev = revNum;
this.changesets = []; this.changesets = [];
@ -33,18 +35,16 @@ function loadBroadcastRevisionsJS() {
const changesetWrapper = { const changesetWrapper = {
deltaRev: destIndex - this.rev, deltaRev: destIndex - this.rev,
deltaTime: timeDelta, deltaTime: timeDelta,
getValue() { getValue: () => changeset,
return changeset;
},
}; };
this.changesets.push(changesetWrapper); this.changesets.push(changesetWrapper);
this.changesets.sort((a, b) => (b.deltaRev - a.deltaRev)); this.changesets.sort((a, b) => (b.deltaRev - a.deltaRev));
}; };
revisionInfo = {}; const revisionInfo = {};
revisionInfo.addChangeset = function (fromIndex, toIndex, changeset, backChangeset, timeDelta) { revisionInfo.addChangeset = function (fromIndex, toIndex, changeset, backChangeset, timeDelta) {
const startRevision = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex); const startRevision = this[fromIndex] || this.createNew(fromIndex);
const endRevision = revisionInfo[toIndex] || revisionInfo.createNew(toIndex); const endRevision = this[toIndex] || this.createNew(toIndex);
startRevision.addChangeset(toIndex, changeset, timeDelta); startRevision.addChangeset(toIndex, changeset, timeDelta);
endRevision.addChangeset(fromIndex, backChangeset, -1 * timeDelta); endRevision.addChangeset(fromIndex, backChangeset, -1 * timeDelta);
}; };
@ -52,12 +52,12 @@ function loadBroadcastRevisionsJS() {
revisionInfo.latest = clientVars.collab_client_vars.rev || -1; revisionInfo.latest = clientVars.collab_client_vars.rev || -1;
revisionInfo.createNew = function (index) { revisionInfo.createNew = function (index) {
revisionInfo[index] = new Revision(index); this[index] = new Revision(index);
if (index > revisionInfo.latest) { if (index > this.latest) {
revisionInfo.latest = index; this.latest = index;
} }
return revisionInfo[index]; return this[index];
}; };
// assuming that there is a path from fromIndex to toIndex, and that the links // assuming that there is a path from fromIndex to toIndex, and that the links
@ -66,8 +66,8 @@ function loadBroadcastRevisionsJS() {
const changesets = []; const changesets = [];
const spans = []; const spans = [];
const times = []; const times = [];
let elem = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex); let elem = this[fromIndex] || this.createNew(fromIndex);
if (elem.changesets.length != 0 && fromIndex != toIndex) { if (elem.changesets.length !== 0 && fromIndex !== toIndex) {
const reverse = !(fromIndex < toIndex); const reverse = !(fromIndex < toIndex);
while (((elem.rev < toIndex) && !reverse) || ((elem.rev > toIndex) && reverse)) { while (((elem.rev < toIndex) && !reverse) || ((elem.rev > toIndex) && reverse)) {
let couldNotContinue = false; let couldNotContinue = false;
@ -76,27 +76,29 @@ function loadBroadcastRevisionsJS() {
for (let i = reverse ? elem.changesets.length - 1 : 0; for (let i = reverse ? elem.changesets.length - 1 : 0;
reverse ? i >= 0 : i < elem.changesets.length; reverse ? i >= 0 : i < elem.changesets.length;
i += reverse ? -1 : 1) { i += reverse ? -1 : 1) {
if (((elem.changesets[i].deltaRev < 0) && !reverse) || ((elem.changesets[i].deltaRev > 0) && reverse)) { if (((elem.changesets[i].deltaRev < 0) && !reverse) ||
((elem.changesets[i].deltaRev > 0) && reverse)) {
couldNotContinue = true; couldNotContinue = true;
break; break;
} }
if (((elem.rev + elem.changesets[i].deltaRev <= toIndex) && !reverse) || ((elem.rev + elem.changesets[i].deltaRev >= toIndex) && reverse)) { if (((elem.rev + elem.changesets[i].deltaRev <= toIndex) && !reverse) ||
((elem.rev + elem.changesets[i].deltaRev >= toIndex) && reverse)) {
const topush = elem.changesets[i]; const topush = elem.changesets[i];
changesets.push(topush.getValue()); changesets.push(topush.getValue());
spans.push(elem.changesets[i].deltaRev); spans.push(elem.changesets[i].deltaRev);
times.push(topush.deltaTime); times.push(topush.deltaTime);
elem = revisionInfo[elem.rev + elem.changesets[i].deltaRev]; elem = this[elem.rev + elem.changesets[i].deltaRev];
break; break;
} }
} }
if (couldNotContinue || oldRev == elem.rev) break; if (couldNotContinue || oldRev === elem.rev) break;
} }
} }
let status = 'partial'; let status = 'partial';
if (elem.rev == toIndex) status = 'complete'; if (elem.rev === toIndex) status = 'complete';
return { return {
fromRev: fromIndex, fromRev: fromIndex,
@ -107,6 +109,7 @@ function loadBroadcastRevisionsJS() {
times, times,
}; };
}; };
} window.revisionInfo = revisionInfo;
};
exports.loadBroadcastRevisionsJS = loadBroadcastRevisionsJS; exports.loadBroadcastRevisionsJS = loadBroadcastRevisionsJS;

View File

@ -1,3 +1,4 @@
'use strict';
/** /**
* This code is mostly from the old Etherpad. Please help us to comment this code. * This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it. * This helps other people to understand this code better and helps them to improve it.
@ -26,13 +27,14 @@ const _ = require('./underscore');
const padmodals = require('./pad_modals').padmodals; const padmodals = require('./pad_modals').padmodals;
const colorutils = require('./colorutils').colorutils; const colorutils = require('./colorutils').colorutils;
function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) { const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
let BroadcastSlider; let BroadcastSlider;
// Hack to ensure timeslider i18n values are in // Hack to ensure timeslider i18n values are in
$("[data-key='timeslider_returnToPad'] > a > span").html(html10n.get('timeslider.toolbar.returnbutton')); $("[data-key='timeslider_returnToPad'] > a > span").html(
html10n.get('timeslider.toolbar.returnbutton'));
(function () { // wrap this code in its own namespace (() => { // wrap this code in its own namespace
let sliderLength = 1000; let sliderLength = 1000;
let sliderPos = 0; let sliderPos = 0;
let sliderActive = false; let sliderActive = false;
@ -40,27 +42,30 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
const savedRevisions = []; const savedRevisions = [];
let sliderPlaying = false; let sliderPlaying = false;
const _callSliderCallbacks = function (newval) { const _callSliderCallbacks = (newval) => {
sliderPos = newval; sliderPos = newval;
for (let i = 0; i < slidercallbacks.length; i++) { for (let i = 0; i < slidercallbacks.length; i++) {
slidercallbacks[i](newval); slidercallbacks[i](newval);
} }
}; };
const updateSliderElements = function () { const updateSliderElements = () => {
for (let i = 0; i < savedRevisions.length; i++) { for (let i = 0; i < savedRevisions.length; i++) {
const position = parseInt(savedRevisions[i].attr('pos')); const position = parseInt(savedRevisions[i].attr('pos'));
savedRevisions[i].css('left', (position * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0)) - 1); savedRevisions[i].css(
'left', (position * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0)) - 1);
} }
$('#ui-slider-handle').css('left', sliderPos * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0)); $('#ui-slider-handle').css(
'left', sliderPos * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0));
}; };
const addSavedRevision = function (position, info) { const addSavedRevision = (position, info) => {
const newSavedRevision = $('<div></div>'); const newSavedRevision = $('<div></div>');
newSavedRevision.addClass('star'); newSavedRevision.addClass('star');
newSavedRevision.attr('pos', position); newSavedRevision.attr('pos', position);
newSavedRevision.css('left', (position * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0)) - 1); newSavedRevision.css(
'left', (position * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0)) - 1);
$('#ui-slider-bar').append(newSavedRevision); $('#ui-slider-bar').append(newSavedRevision);
newSavedRevision.mouseup((evt) => { newSavedRevision.mouseup((evt) => {
BroadcastSlider.setSliderPosition(position); BroadcastSlider.setSliderPosition(position);
@ -68,60 +73,50 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
savedRevisions.push(newSavedRevision); savedRevisions.push(newSavedRevision);
}; };
const removeSavedRevision = function (position) {
const element = $(`div.star [pos=${position}]`);
savedRevisions.remove(element);
element.remove();
return element;
};
/* Begin small 'API' */ /* Begin small 'API' */
function onSlider(callback) { const onSlider = (callback) => {
slidercallbacks.push(callback); slidercallbacks.push(callback);
} };
function getSliderPosition() { const getSliderPosition = () => sliderPos;
return sliderPos;
}
function setSliderPosition(newpos) { const setSliderPosition = (newpos) => {
newpos = Number(newpos); newpos = Number(newpos);
if (newpos < 0 || newpos > sliderLength) return; if (newpos < 0 || newpos > sliderLength) return;
if (!newpos) { if (!newpos) {
newpos = 0; // stops it from displaying NaN if newpos isn't set newpos = 0; // stops it from displaying NaN if newpos isn't set
} }
window.location.hash = `#${newpos}`; window.location.hash = `#${newpos}`;
$('#ui-slider-handle').css('left', newpos * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0)); $('#ui-slider-handle').css(
'left', newpos * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0));
$('a.tlink').map(function () { $('a.tlink').map(function () {
$(this).attr('href', $(this).attr('thref').replace('%revision%', newpos)); $(this).attr('href', $(this).attr('thref').replace('%revision%', newpos));
}); });
$('#revision_label').html(html10n.get('timeslider.version', {version: newpos})); $('#revision_label').html(html10n.get('timeslider.version', {version: newpos}));
$('#leftstar, #leftstep').toggleClass('disabled', newpos == 0); $('#leftstar, #leftstep').toggleClass('disabled', newpos === 0);
$('#rightstar, #rightstep').toggleClass('disabled', newpos == sliderLength); $('#rightstar, #rightstep').toggleClass('disabled', newpos === sliderLength);
sliderPos = newpos; sliderPos = newpos;
_callSliderCallbacks(newpos); _callSliderCallbacks(newpos);
} };
function getSliderLength() { const getSliderLength = () => sliderLength;
return sliderLength;
}
function setSliderLength(newlength) { const setSliderLength = (newlength) => {
sliderLength = newlength; sliderLength = newlength;
updateSliderElements(); updateSliderElements();
} };
// just take over the whole slider screen with a reconnect message // just take over the whole slider screen with a reconnect message
function showReconnectUI() { const showReconnectUI = () => {
padmodals.showModal('disconnected'); padmodals.showModal('disconnected');
} };
function setAuthors(authors) { const setAuthors = (authors) => {
const authorsList = $('#authorsList'); const authorsList = $('#authorsList');
authorsList.empty(); authorsList.empty();
let numAnonymous = 0; let numAnonymous = 0;
@ -132,7 +127,8 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
const authorColor = clientVars.colorPalette[author.colorId] || author.colorId; const authorColor = clientVars.colorPalette[author.colorId] || author.colorId;
if (author.name) { if (author.name) {
if (numNamed !== 0) authorsList.append(', '); if (numNamed !== 0) authorsList.append(', ');
const textColor = colorutils.textColorFromBackgroundColor(authorColor, clientVars.skinName); const textColor =
colorutils.textColorFromBackgroundColor(authorColor, clientVars.skinName);
$('<span />') $('<span />')
.text(author.name || 'unnamed') .text(author.name || 'unnamed')
.css('background-color', authorColor) .css('background-color', authorColor)
@ -168,27 +164,12 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
authorsList.append(')'); authorsList.append(')');
} }
} }
if (authors.length == 0) { if (authors.length === 0) {
authorsList.append(html10n.get('timeslider.toolbar.authorsList')); authorsList.append(html10n.get('timeslider.toolbar.authorsList'));
} }
}
BroadcastSlider = {
onSlider,
getSliderPosition,
setSliderPosition,
getSliderLength,
setSliderLength,
isSliderActive() {
return sliderActive;
},
playpause,
addSavedRevision,
showReconnectUI,
setAuthors,
}; };
function playButtonUpdater() { const playButtonUpdater = () => {
if (sliderPlaying) { if (sliderPlaying) {
if (getSliderPosition() + 1 > sliderLength) { if (getSliderPosition() + 1 > sliderLength) {
$('#playpause_button_icon').toggleClass('pause'); $('#playpause_button_icon').toggleClass('pause');
@ -199,39 +180,52 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
setTimeout(playButtonUpdater, 100); setTimeout(playButtonUpdater, 100);
} }
} };
function playpause() { const playpause = () => {
$('#playpause_button_icon').toggleClass('pause'); $('#playpause_button_icon').toggleClass('pause');
if (!sliderPlaying) { if (!sliderPlaying) {
if (getSliderPosition() == sliderLength) setSliderPosition(0); if (getSliderPosition() === sliderLength) setSliderPosition(0);
sliderPlaying = true; sliderPlaying = true;
playButtonUpdater(); playButtonUpdater();
} else { } else {
sliderPlaying = false; sliderPlaying = false;
} }
} };
BroadcastSlider = {
onSlider,
getSliderPosition,
setSliderPosition,
getSliderLength,
setSliderLength,
isSliderActive: () => sliderActive,
playpause,
addSavedRevision,
showReconnectUI,
setAuthors,
};
// assign event handlers to html UI elements after page load // assign event handlers to html UI elements after page load
fireWhenAllScriptsAreLoaded.push(() => { fireWhenAllScriptsAreLoaded.push(() => {
$(document).keyup((e) => { $(document).keyup((e) => {
if (!e) var e = window.event; if (!e) e = window.event;
const code = e.keyCode || e.which; const code = e.keyCode || e.which;
if (code == 37) { // left if (code === 37) { // left
if (e.shiftKey) { if (e.shiftKey) {
$('#leftstar').click(); $('#leftstar').click();
} else { } else {
$('#leftstep').click(); $('#leftstep').click();
} }
} else if (code == 39) { // right } else if (code === 39) { // right
if (e.shiftKey) { if (e.shiftKey) {
$('#rightstar').click(); $('#rightstar').click();
} else { } else {
$('#rightstep').click(); $('#rightstep').click();
} }
} else if (code == 32) { // spacebar } else if (code === 32) { // spacebar
$('#playpause_button_icon').trigger('click'); $('#playpause_button_icon').trigger('click');
} }
}); });
@ -251,31 +245,32 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
$('#ui-slider-handle').mousedown(function (evt) { $('#ui-slider-handle').mousedown(function (evt) {
this.startLoc = evt.clientX; this.startLoc = evt.clientX;
this.currentLoc = parseInt($(this).css('left')); this.currentLoc = parseInt($(this).css('left'));
const self = this;
sliderActive = true; sliderActive = true;
$(document).mousemove((evt2) => { $(document).mousemove((evt2) => {
$(self).css('pointer', 'move'); $(this).css('pointer', 'move');
let newloc = self.currentLoc + (evt2.clientX - self.startLoc); let newloc = this.currentLoc + (evt2.clientX - this.startLoc);
if (newloc < 0) newloc = 0; if (newloc < 0) newloc = 0;
if (newloc > ($('#ui-slider-bar').width() - 2)) newloc = ($('#ui-slider-bar').width() - 2); const maxPos = $('#ui-slider-bar').width() - 2;
$('#revision_label').html(html10n.get('timeslider.version', {version: Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2))})); if (newloc > maxPos) newloc = maxPos;
$(self).css('left', newloc); const version = Math.floor(newloc * sliderLength / maxPos);
if (getSliderPosition() != Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2))) _callSliderCallbacks(Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2))); $('#revision_label').html(html10n.get('timeslider.version', {version}));
$(this).css('left', newloc);
if (getSliderPosition() !== version) _callSliderCallbacks(version);
}); });
$(document).mouseup((evt2) => { $(document).mouseup((evt2) => {
$(document).unbind('mousemove'); $(document).unbind('mousemove');
$(document).unbind('mouseup'); $(document).unbind('mouseup');
sliderActive = false; sliderActive = false;
let newloc = self.currentLoc + (evt2.clientX - self.startLoc); let newloc = this.currentLoc + (evt2.clientX - this.startLoc);
if (newloc < 0) newloc = 0; if (newloc < 0) newloc = 0;
if (newloc > ($('#ui-slider-bar').width() - 2)) newloc = ($('#ui-slider-bar').width() - 2); const maxPos = $('#ui-slider-bar').width() - 2;
$(self).css('left', newloc); if (newloc > maxPos) newloc = maxPos;
// if(getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2))) $(this).css('left', newloc);
setSliderPosition(Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2))); setSliderPosition(Math.floor(newloc * sliderLength / maxPos));
if (parseInt($(self).css('left')) < 2) { if (parseInt($(this).css('left')) < 2) {
$(self).css('left', '2px'); $(this).css('left', '2px');
} else { } else {
self.currentLoc = parseInt($(self).css('left')); this.currentLoc = parseInt($(this).css('left'));
} }
}); });
}); });
@ -294,29 +289,30 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
case 'rightstep': case 'rightstep':
setSliderPosition(getSliderPosition() + 1); setSliderPosition(getSliderPosition() + 1);
break; break;
case 'leftstar': case 'leftstar': {
var nextStar = 0; // default to first revision in document let nextStar = 0; // default to first revision in document
for (var i = 0; i < savedRevisions.length; i++) { for (let i = 0; i < savedRevisions.length; i++) {
var pos = parseInt(savedRevisions[i].attr('pos')); const pos = parseInt(savedRevisions[i].attr('pos'));
if (pos < getSliderPosition() && nextStar < pos) nextStar = pos; if (pos < getSliderPosition() && nextStar < pos) nextStar = pos;
} }
setSliderPosition(nextStar); setSliderPosition(nextStar);
break; break;
case 'rightstar': }
var nextStar = sliderLength; // default to last revision in document case 'rightstar': {
for (var i = 0; i < savedRevisions.length; i++) { let nextStar = sliderLength; // default to last revision in document
var pos = parseInt(savedRevisions[i].attr('pos')); for (let i = 0; i < savedRevisions.length; i++) {
const pos = parseInt(savedRevisions[i].attr('pos'));
if (pos > getSliderPosition() && nextStar > pos) nextStar = pos; if (pos > getSliderPosition() && nextStar > pos) nextStar = pos;
} }
setSliderPosition(nextStar); setSliderPosition(nextStar);
break; break;
}
} }
}); });
if (clientVars) { if (clientVars) {
$('#timeslider-wrapper').show(); $('#timeslider-wrapper').show();
const startPos = clientVars.collab_client_vars.rev;
if (window.location.hash.length > 1) { if (window.location.hash.length > 1) {
const hashRev = Number(window.location.hash.substr(1)); const hashRev = Number(window.location.hash.substr(1));
if (!isNaN(hashRev)) { if (!isNaN(hashRev)) {
@ -336,10 +332,11 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
})(); })();
BroadcastSlider.onSlider((loc) => { BroadcastSlider.onSlider((loc) => {
$('#viewlatest').html(loc == BroadcastSlider.getSliderLength() ? 'Viewing latest content' : 'View latest content'); $('#viewlatest').html(
`${loc === BroadcastSlider.getSliderLength() ? 'Viewing' : 'View'} latest content`);
}); });
return BroadcastSlider; return BroadcastSlider;
} };
exports.loadBroadcastSliderJS = loadBroadcastSliderJS; exports.loadBroadcastSliderJS = loadBroadcastSliderJS;