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 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
// Timeslider controller would probably be more appropriate.
function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider) {
let goToRevisionIfEnabledCount = 0;
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
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() {
const debugLog = (...args) => {
try {
if (window.console) console.log.apply(console, arguments);
if (window.console) console.log(...args);
} catch (e) {
if (window.console) console.log('error printing: ', e);
}
}
// var socket;
const channelState = 'DISCONNECTED';
const appLevelDisconnectReason = null;
};
const padContents = {
currentRevision: clientVars.collab_client_vars.rev,
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,
// 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),
clientVars.collab_client_vars.initialAttributedText.attribs,
clientVars.collab_client_vars.initialAttributedText.text),
// generates a jquery element containing HTML for a line
lineToElement(line, aline) {
const element = document.createElement('div');
const emptyLine = (line == '\n');
const emptyLine = (line === '\n');
const domInfo = domline.createDomLine(!emptyLine, true);
linestylefilter.populateDomLine(line, aline, this.apool, domInfo);
domInfo.prepareForAdd();
@ -86,9 +70,10 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
return $(element);
},
applySpliceToDivs(start, numRemoved, newLines) {
// splice the lines
splice(start, numRemoved, ...newLines) {
// 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();
}
@ -96,7 +81,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
this.currentDivs.splice(start, numRemoved);
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]));
}
@ -104,7 +89,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
let 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++) {
for (let i = 0; i < newDivs.length; i++) {
if (startDiv) {
startDiv.after(newDivs[i]);
} else {
@ -114,23 +99,10 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
}
// 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(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);
this.currentDivs.splice(start, 0, ...newDivs);
// call currentLines.splice, to keep the currentLines array up to date
newLines.unshift(numRemoved);
newLines.unshift(start);
this.currentLines.splice.apply(this.currentLines, arguments);
this.currentLines.splice(start, numRemoved, ...newLines);
},
// returns the contents of the specified line I
get(i) {
@ -142,16 +114,15 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
},
getActiveAuthors() {
const self = this;
const authors = [];
const seenNums = {};
const alines = self.alines;
const alines = this.alines;
for (let i = 0; i < alines.length; i++) {
Changeset.eachAttribNumber(alines[i], (n) => {
if (!seenNums[n]) {
seenNums[n] = true;
if (self.apool.getAttribKey(n) == 'author') {
const a = self.apool.getAttribValue(n);
if (this.apool.getAttribKey(n) === 'author') {
const a = this.apool.getAttribValue(n);
if (a) {
authors.push(a);
}
@ -164,42 +135,7 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
},
};
function callCatchingErrors(catcher, func) {
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) {
const applyChangeset = (changeset, revision, preventSliderMovement, timeDelta) => {
// disable the next 'gotorevision' call handled by a timeslider update
if (!preventSliderMovement) {
goToRevisionIfEnabledCount++;
@ -215,7 +151,8 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
}
// 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
// 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
@ -227,11 +164,25 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
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) {
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);
}
@ -244,17 +195,32 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors);
}
};
function updateTimer() {
const zpad = function (str, length) {
const loadedNewChangeset = (changesetForward, changesetBackward, revision, timeDelta) => {
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}`;
while (str.length < length) str = `0${str}`;
return str;
};
const date = new Date(padContents.currentTime);
const dateFormat = function () {
const dateFormat = () => {
const month = zpad(date.getMonth() + 1, 2);
const day = zpad(date.getDate(), 2);
const year = (date.getFullYear());
@ -292,43 +258,41 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
year: date.getFullYear(),
});
$('#revision_date').html(revisionDate);
}
};
updateTimer();
function goToRevision(newRevision) {
const goToRevision = (newRevision) => {
padContents.targetRevision = newRevision;
const self = this;
const path = revisionInfo.getPath(padContents.currentRevision, newRevision);
const path = window.revisionInfo.getPath(padContents.currentRevision, newRevision);
hooks.aCallAll('goToRevisionEvent', {
rev: newRevision,
});
if (path.status == 'complete') {
var cs = path.changesets;
var changeset = cs[0];
var timeDelta = path.times[0];
for (var i = 1; i < cs.length; i++) {
if (path.status === 'complete') {
const cs = path.changesets;
let changeset = cs[0];
let timeDelta = path.times[0];
for (let 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);
} else if (path.status == 'partial') {
const sliderLocation = padContents.currentRevision;
} else if (path.status === 'partial') {
// callback is called after changeset information is pulled from server
// 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
goToRevision(padContents.targetRevision);
};
// do our best with what we have...
var cs = path.changesets;
const cs = path.changesets;
var changeset = cs[0];
var timeDelta = path.times[0];
for (var i = 1; i < cs.length; i++) {
let changeset = cs[0];
let timeDelta = path.times[0];
for (let i = 1; i < cs.length; i++) {
changeset = Changeset.compose(changeset, cs[i], padContents.apool);
timeDelta += path.times[i];
}
@ -342,23 +306,23 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors);
}
};
function loadChangesetsForRevision(revision, callback) {
const loadChangesetsForRevision = (revision, callback) => {
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);
}
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);
}
start = (Math.floor((revision) / 100) * 100);
const start = (Math.floor((revision) / 100) * 100);
changesetLoader.queueUp(start, 1, callback);
}
};
changesetLoader = {
running: false,
@ -369,29 +333,38 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
reqCallbacks: [],
queueUp(revision, width, callback) {
if (revision < 0) revision = 0;
// if(changesetLoader.requestQueue.indexOf(revision) != -1)
// if(this.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 (this.resolved.indexOf(`${revision}_${width}`) !== -1) {
// 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(
{
rev: revision,
res: width,
callback,
});
if (!changesetLoader.running) {
changesetLoader.running = true;
setTimeout(changesetLoader.loadFromQueue, 10);
if (!this.running) {
this.running = true;
setTimeout(() => this.loadFromQueue(), 10);
}
},
loadFromQueue() {
const self = changesetLoader;
const requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : self.requestQueue2.length > 0 ? self.requestQueue2 : self.requestQueue3.length > 0 ? self.requestQueue3 : null;
const requestQueue =
this.requestQueue1.length > 0 ? this.requestQueue1
: this.requestQueue2.length > 0 ? this.requestQueue2
: this.requestQueue3.length > 0 ? this.requestQueue3
: null;
if (!requestQueue) {
self.running = false;
this.running = false;
return;
}
@ -407,48 +380,48 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
requestID,
});
self.reqCallbacks[requestID] = callback;
this.reqCallbacks[requestID] = callback;
},
handleSocketResponse(message) {
const self = changesetLoader;
const start = message.data.start;
const granularity = message.data.granularity;
const callback = self.reqCallbacks[message.data.requestID];
delete self.reqCallbacks[message.data.requestID];
const callback = this.reqCallbacks[message.data.requestID];
delete this.reqCallbacks[message.data.requestID];
self.handleResponse(message.data, start, granularity, callback);
setTimeout(self.loadFromQueue, 10);
this.handleResponse(message.data, start, granularity, callback);
setTimeout(() => this.loadFromQueue(), 10);
},
handleResponse(data, start, granularity, callback) {
handleResponse: (data, start, granularity, callback) => {
const pool = (new AttribPool()).fromJsonable(data.apool);
for (let i = 0; i < data.forwardsChangesets.length; i++) {
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
if (aend > data.actualEndNum - 1) aend = data.actualEndNum - 1;
// debugLog("adding changeset:", astart, aend);
const forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool);
const backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool);
revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]);
const forwardcs =
Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool);
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);
},
handleMessageFromServer(obj) {
if (obj.type == 'COLLABROOM') {
if (obj.type === 'COLLABROOM') {
obj = obj.data;
if (obj.type == 'NEW_CHANGES') {
if (obj.type === 'NEW_CHANGES') {
const changeset = Changeset.moveOpsToNewPool(
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);
var changesetBack = Changeset.moveOpsToNewPool(
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') {
} else if (obj.type === 'NEW_AUTHORDATA') {
const authorMap = {};
authorMap[obj.author] = obj.data;
receiveAuthorData(authorMap);
@ -456,13 +429,13 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors);
} else if (obj.type == 'NEW_SAVEDREV') {
} else if (obj.type === 'NEW_SAVEDREV') {
const 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 if (obj.type === 'CHANGESET_REQ') {
this.handleSocketResponse(obj);
} else {
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,
// since goToRevision changes the slider position
var goToRevisionIfEnabledCount = 0;
const goToRevisionIfEnabled = function () {
const goToRevisionIfEnabled = (...args) => {
if (goToRevisionIfEnabledCount > 0) {
goToRevisionIfEnabledCount--;
} else {
goToRevision.apply(goToRevision, arguments);
goToRevision(...args);
}
};
BroadcastSlider.onSlider(goToRevisionIfEnabled);
const dynamicCSS = makeCSSManager('dynamicsyntax');
var authorData = {};
const authorData = {};
function receiveAuthorData(newAuthorData) {
for (const author in newAuthorData) {
const data = newAuthorData[author];
const bgcolor = typeof data.colorId === 'number' ? clientVars.colorPalette[data.colorId] : data.colorId;
const receiveAuthorData = (newAuthorData) => {
for (const [author, data] of Object.entries(newAuthorData)) {
const bgcolor = typeof data.colorId === 'number'
? clientVars.colorPalette[data.colorId] : data.colorId;
if (bgcolor && dynamicCSS) {
const 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
selector.color = (colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5)
? '#ffffff' : '#000000'; // see ace2_inner.js for the other part
}
authorData[author] = data;
}
}
};
receiveAuthorData(clientVars.collab_client_vars.historicalAuthorData);
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;

View File

@ -1,3 +1,5 @@
'use strict';
/**
* 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.
@ -23,7 +25,7 @@
// of the document. These revisions are connected together by various
// changesets, or deltas, between any two revisions.
function loadBroadcastRevisionsJS() {
const loadBroadcastRevisionsJS = () => {
function Revision(revNum) {
this.rev = revNum;
this.changesets = [];
@ -33,18 +35,16 @@ function loadBroadcastRevisionsJS() {
const changesetWrapper = {
deltaRev: destIndex - this.rev,
deltaTime: timeDelta,
getValue() {
return changeset;
},
getValue: () => changeset,
};
this.changesets.push(changesetWrapper);
this.changesets.sort((a, b) => (b.deltaRev - a.deltaRev));
};
revisionInfo = {};
const revisionInfo = {};
revisionInfo.addChangeset = function (fromIndex, toIndex, changeset, backChangeset, timeDelta) {
const startRevision = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
const endRevision = revisionInfo[toIndex] || revisionInfo.createNew(toIndex);
const startRevision = this[fromIndex] || this.createNew(fromIndex);
const endRevision = this[toIndex] || this.createNew(toIndex);
startRevision.addChangeset(toIndex, changeset, timeDelta);
endRevision.addChangeset(fromIndex, backChangeset, -1 * timeDelta);
};
@ -52,12 +52,12 @@ function loadBroadcastRevisionsJS() {
revisionInfo.latest = clientVars.collab_client_vars.rev || -1;
revisionInfo.createNew = function (index) {
revisionInfo[index] = new Revision(index);
if (index > revisionInfo.latest) {
revisionInfo.latest = index;
this[index] = new Revision(index);
if (index > this.latest) {
this.latest = index;
}
return revisionInfo[index];
return this[index];
};
// assuming that there is a path from fromIndex to toIndex, and that the links
@ -66,8 +66,8 @@ function loadBroadcastRevisionsJS() {
const changesets = [];
const spans = [];
const times = [];
let elem = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
if (elem.changesets.length != 0 && fromIndex != toIndex) {
let elem = this[fromIndex] || this.createNew(fromIndex);
if (elem.changesets.length !== 0 && fromIndex !== toIndex) {
const reverse = !(fromIndex < toIndex);
while (((elem.rev < toIndex) && !reverse) || ((elem.rev > toIndex) && reverse)) {
let couldNotContinue = false;
@ -76,27 +76,29 @@ function loadBroadcastRevisionsJS() {
for (let i = reverse ? elem.changesets.length - 1 : 0;
reverse ? i >= 0 : i < elem.changesets.length;
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;
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];
changesets.push(topush.getValue());
spans.push(elem.changesets[i].deltaRev);
times.push(topush.deltaTime);
elem = revisionInfo[elem.rev + elem.changesets[i].deltaRev];
elem = this[elem.rev + elem.changesets[i].deltaRev];
break;
}
}
if (couldNotContinue || oldRev == elem.rev) break;
if (couldNotContinue || oldRev === elem.rev) break;
}
}
let status = 'partial';
if (elem.rev == toIndex) status = 'complete';
if (elem.rev === toIndex) status = 'complete';
return {
fromRev: fromIndex,
@ -107,6 +109,7 @@ function loadBroadcastRevisionsJS() {
times,
};
};
}
window.revisionInfo = revisionInfo;
};
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 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 colorutils = require('./colorutils').colorutils;
function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
let BroadcastSlider;
// 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 sliderPos = 0;
let sliderActive = false;
@ -40,27 +42,30 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
const savedRevisions = [];
let sliderPlaying = false;
const _callSliderCallbacks = function (newval) {
const _callSliderCallbacks = (newval) => {
sliderPos = newval;
for (let i = 0; i < slidercallbacks.length; i++) {
slidercallbacks[i](newval);
}
};
const updateSliderElements = function () {
const updateSliderElements = () => {
for (let i = 0; i < savedRevisions.length; i++) {
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>');
newSavedRevision.addClass('star');
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);
newSavedRevision.mouseup((evt) => {
BroadcastSlider.setSliderPosition(position);
@ -68,60 +73,50 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
savedRevisions.push(newSavedRevision);
};
const removeSavedRevision = function (position) {
const element = $(`div.star [pos=${position}]`);
savedRevisions.remove(element);
element.remove();
return element;
};
/* Begin small 'API' */
function onSlider(callback) {
const onSlider = (callback) => {
slidercallbacks.push(callback);
}
};
function getSliderPosition() {
return sliderPos;
}
const getSliderPosition = () => sliderPos;
function setSliderPosition(newpos) {
const setSliderPosition = (newpos) => {
newpos = Number(newpos);
if (newpos < 0 || newpos > sliderLength) return;
if (!newpos) {
newpos = 0; // stops it from displaying NaN if newpos isn't set
}
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 () {
$(this).attr('href', $(this).attr('thref').replace('%revision%', newpos));
});
$('#revision_label').html(html10n.get('timeslider.version', {version: newpos}));
$('#leftstar, #leftstep').toggleClass('disabled', newpos == 0);
$('#rightstar, #rightstep').toggleClass('disabled', newpos == sliderLength);
$('#leftstar, #leftstep').toggleClass('disabled', newpos === 0);
$('#rightstar, #rightstep').toggleClass('disabled', newpos === sliderLength);
sliderPos = newpos;
_callSliderCallbacks(newpos);
}
};
function getSliderLength() {
return sliderLength;
}
const getSliderLength = () => sliderLength;
function setSliderLength(newlength) {
const setSliderLength = (newlength) => {
sliderLength = newlength;
updateSliderElements();
}
};
// just take over the whole slider screen with a reconnect message
function showReconnectUI() {
const showReconnectUI = () => {
padmodals.showModal('disconnected');
}
};
function setAuthors(authors) {
const setAuthors = (authors) => {
const authorsList = $('#authorsList');
authorsList.empty();
let numAnonymous = 0;
@ -132,7 +127,8 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
const authorColor = clientVars.colorPalette[author.colorId] || author.colorId;
if (author.name) {
if (numNamed !== 0) authorsList.append(', ');
const textColor = colorutils.textColorFromBackgroundColor(authorColor, clientVars.skinName);
const textColor =
colorutils.textColorFromBackgroundColor(authorColor, clientVars.skinName);
$('<span />')
.text(author.name || 'unnamed')
.css('background-color', authorColor)
@ -168,27 +164,12 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
authorsList.append(')');
}
}
if (authors.length == 0) {
if (authors.length === 0) {
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 (getSliderPosition() + 1 > sliderLength) {
$('#playpause_button_icon').toggleClass('pause');
@ -199,39 +180,52 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
setTimeout(playButtonUpdater, 100);
}
}
};
function playpause() {
const playpause = () => {
$('#playpause_button_icon').toggleClass('pause');
if (!sliderPlaying) {
if (getSliderPosition() == sliderLength) setSliderPosition(0);
if (getSliderPosition() === sliderLength) setSliderPosition(0);
sliderPlaying = true;
playButtonUpdater();
} else {
sliderPlaying = false;
}
}
};
BroadcastSlider = {
onSlider,
getSliderPosition,
setSliderPosition,
getSliderLength,
setSliderLength,
isSliderActive: () => sliderActive,
playpause,
addSavedRevision,
showReconnectUI,
setAuthors,
};
// assign event handlers to html UI elements after page load
fireWhenAllScriptsAreLoaded.push(() => {
$(document).keyup((e) => {
if (!e) var e = window.event;
if (!e) e = window.event;
const code = e.keyCode || e.which;
if (code == 37) { // left
if (code === 37) { // left
if (e.shiftKey) {
$('#leftstar').click();
} else {
$('#leftstep').click();
}
} else if (code == 39) { // right
} else if (code === 39) { // right
if (e.shiftKey) {
$('#rightstar').click();
} else {
$('#rightstep').click();
}
} else if (code == 32) { // spacebar
} else if (code === 32) { // spacebar
$('#playpause_button_icon').trigger('click');
}
});
@ -251,31 +245,32 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
$('#ui-slider-handle').mousedown(function (evt) {
this.startLoc = evt.clientX;
this.currentLoc = parseInt($(this).css('left'));
const self = this;
sliderActive = true;
$(document).mousemove((evt2) => {
$(self).css('pointer', 'move');
let newloc = self.currentLoc + (evt2.clientX - self.startLoc);
$(this).css('pointer', 'move');
let newloc = this.currentLoc + (evt2.clientX - this.startLoc);
if (newloc < 0) newloc = 0;
if (newloc > ($('#ui-slider-bar').width() - 2)) newloc = ($('#ui-slider-bar').width() - 2);
$('#revision_label').html(html10n.get('timeslider.version', {version: Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2))}));
$(self).css('left', newloc);
if (getSliderPosition() != Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2))) _callSliderCallbacks(Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2)));
const maxPos = $('#ui-slider-bar').width() - 2;
if (newloc > maxPos) newloc = maxPos;
const version = Math.floor(newloc * sliderLength / maxPos);
$('#revision_label').html(html10n.get('timeslider.version', {version}));
$(this).css('left', newloc);
if (getSliderPosition() !== version) _callSliderCallbacks(version);
});
$(document).mouseup((evt2) => {
$(document).unbind('mousemove');
$(document).unbind('mouseup');
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 > ($('#ui-slider-bar').width() - 2)) newloc = ($('#ui-slider-bar').width() - 2);
$(self).css('left', newloc);
// if(getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2)))
setSliderPosition(Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2)));
if (parseInt($(self).css('left')) < 2) {
$(self).css('left', '2px');
const maxPos = $('#ui-slider-bar').width() - 2;
if (newloc > maxPos) newloc = maxPos;
$(this).css('left', newloc);
setSliderPosition(Math.floor(newloc * sliderLength / maxPos));
if (parseInt($(this).css('left')) < 2) {
$(this).css('left', '2px');
} else {
self.currentLoc = parseInt($(self).css('left'));
this.currentLoc = parseInt($(this).css('left'));
}
});
});
@ -294,29 +289,30 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
case 'rightstep':
setSliderPosition(getSliderPosition() + 1);
break;
case 'leftstar':
var nextStar = 0; // default to first revision in document
for (var i = 0; i < savedRevisions.length; i++) {
var pos = parseInt(savedRevisions[i].attr('pos'));
case 'leftstar': {
let nextStar = 0; // default to first revision in document
for (let i = 0; i < savedRevisions.length; i++) {
const pos = parseInt(savedRevisions[i].attr('pos'));
if (pos < getSliderPosition() && nextStar < pos) nextStar = pos;
}
setSliderPosition(nextStar);
break;
case 'rightstar':
var nextStar = sliderLength; // default to last revision in document
for (var i = 0; i < savedRevisions.length; i++) {
var pos = parseInt(savedRevisions[i].attr('pos'));
}
case 'rightstar': {
let nextStar = sliderLength; // default to last revision in document
for (let i = 0; i < savedRevisions.length; i++) {
const pos = parseInt(savedRevisions[i].attr('pos'));
if (pos > getSliderPosition() && nextStar > pos) nextStar = pos;
}
setSliderPosition(nextStar);
break;
}
}
});
if (clientVars) {
$('#timeslider-wrapper').show();
const startPos = clientVars.collab_client_vars.rev;
if (window.location.hash.length > 1) {
const hashRev = Number(window.location.hash.substr(1));
if (!isNaN(hashRev)) {
@ -336,10 +332,11 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
})();
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;
}
};
exports.loadBroadcastSliderJS = loadBroadcastSliderJS;