lint: Run eslint --fix on src/

This commit is contained in:
Richard Hansen 2020-11-23 13:24:19 -05:00 committed by John McLear
parent b8d07a42eb
commit 8e5fd19db2
109 changed files with 9061 additions and 10572 deletions

View file

@ -18,19 +18,19 @@
* limitations under the License. * limitations under the License.
*/ */
var Changeset = require("ep_etherpad-lite/static/js/Changeset"); const Changeset = require('ep_etherpad-lite/static/js/Changeset');
var customError = require("../utils/customError"); const customError = require('../utils/customError');
var padManager = require("./PadManager"); const padManager = require('./PadManager');
var padMessageHandler = require("../handler/PadMessageHandler"); const padMessageHandler = require('../handler/PadMessageHandler');
var readOnlyManager = require("./ReadOnlyManager"); const readOnlyManager = require('./ReadOnlyManager');
var groupManager = require("./GroupManager"); const groupManager = require('./GroupManager');
var authorManager = require("./AuthorManager"); const authorManager = require('./AuthorManager');
var sessionManager = require("./SessionManager"); const sessionManager = require('./SessionManager');
var exportHtml = require("../utils/ExportHtml"); const exportHtml = require('../utils/ExportHtml');
var exportTxt = require("../utils/ExportTxt"); const exportTxt = require('../utils/ExportTxt');
var importHtml = require("../utils/ImportHtml"); const importHtml = require('../utils/ImportHtml');
var cleanText = require("./Pad").cleanText; const cleanText = require('./Pad').cleanText;
var PadDiff = require("../utils/padDiff"); const PadDiff = require('../utils/padDiff');
/* ******************** /* ********************
* GROUP FUNCTIONS **** * GROUP FUNCTIONS ****
@ -101,10 +101,10 @@ Example returns:
} }
*/ */
exports.getAttributePool = async function(padID) { exports.getAttributePool = async function (padID) {
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
return { pool: pad.pool }; return {pool: pad.pool};
} };
/** /**
getRevisionChangeset (padID, [rev]) getRevisionChangeset (padID, [rev])
@ -119,22 +119,21 @@ Example returns:
} }
*/ */
exports.getRevisionChangeset = async function(padID, rev) { exports.getRevisionChangeset = async function (padID, rev) {
// try to parse the revision number // try to parse the revision number
if (rev !== undefined) { if (rev !== undefined) {
rev = checkValidRev(rev); rev = checkValidRev(rev);
} }
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
let head = pad.getHeadRevisionNumber(); const head = pad.getHeadRevisionNumber();
// the client asked for a special revision // the client asked for a special revision
if (rev !== undefined) { if (rev !== undefined) {
// check if this is a valid revision // check if this is a valid revision
if (rev > head) { if (rev > head) {
throw new customError("rev is higher than the head revision of the pad", "apierror"); throw new customError('rev is higher than the head revision of the pad', 'apierror');
} }
// get the changeset for this revision // get the changeset for this revision
@ -143,7 +142,7 @@ exports.getRevisionChangeset = async function(padID, rev) {
// the client wants the latest changeset, lets return it to him // the client wants the latest changeset, lets return it to him
return pad.getRevisionChangeset(head); return pad.getRevisionChangeset(head);
} };
/** /**
getText(padID, [rev]) returns the text of a pad getText(padID, [rev]) returns the text of a pad
@ -153,33 +152,32 @@ Example returns:
{code: 0, message:"ok", data: {text:"Welcome Text"}} {code: 0, message:"ok", data: {text:"Welcome Text"}}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.getText = async function(padID, rev) { exports.getText = async function (padID, rev) {
// try to parse the revision number // try to parse the revision number
if (rev !== undefined) { if (rev !== undefined) {
rev = checkValidRev(rev); rev = checkValidRev(rev);
} }
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
let head = pad.getHeadRevisionNumber(); const head = pad.getHeadRevisionNumber();
// the client asked for a special revision // the client asked for a special revision
if (rev !== undefined) { if (rev !== undefined) {
// check if this is a valid revision // check if this is a valid revision
if (rev > head) { if (rev > head) {
throw new customError("rev is higher than the head revision of the pad", "apierror"); throw new customError('rev is higher than the head revision of the pad', 'apierror');
} }
// get the text of this revision // get the text of this revision
let text = await pad.getInternalRevisionAText(rev); const text = await pad.getInternalRevisionAText(rev);
return { text }; return {text};
} }
// the client wants the latest text, lets return it to him // the client wants the latest text, lets return it to him
let text = exportTxt.getTXTFromAtext(pad, pad.atext); const text = exportTxt.getTXTFromAtext(pad, pad.atext);
return { text }; return {text};
} };
/** /**
setText(padID, text) sets the text of a pad setText(padID, text) sets the text of a pad
@ -190,20 +188,20 @@ Example returns:
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
{code: 1, message:"text too long", data: null} {code: 1, message:"text too long", data: null}
*/ */
exports.setText = async function(padID, text) { exports.setText = async function (padID, text) {
// text is required // text is required
if (typeof text !== "string") { if (typeof text !== 'string') {
throw new customError("text is not a string", "apierror"); throw new customError('text is not a string', 'apierror');
} }
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
await Promise.all([ await Promise.all([
pad.setText(text), pad.setText(text),
padMessageHandler.updatePadClients(pad), padMessageHandler.updatePadClients(pad),
]); ]);
} };
/** /**
appendText(padID, text) appends text to a pad appendText(padID, text) appends text to a pad
@ -214,18 +212,18 @@ Example returns:
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
{code: 1, message:"text too long", data: null} {code: 1, message:"text too long", data: null}
*/ */
exports.appendText = async function(padID, text) { exports.appendText = async function (padID, text) {
// text is required // text is required
if (typeof text !== "string") { if (typeof text !== 'string') {
throw new customError("text is not a string", "apierror"); throw new customError('text is not a string', 'apierror');
} }
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
await Promise.all([ await Promise.all([
pad.appendText(text), pad.appendText(text),
padMessageHandler.updatePadClients(pad), padMessageHandler.updatePadClients(pad),
]); ]);
} };
/** /**
getHTML(padID, [rev]) returns the html of a pad getHTML(padID, [rev]) returns the html of a pad
@ -235,19 +233,19 @@ Example returns:
{code: 0, message:"ok", data: {text:"Welcome <strong>Text</strong>"}} {code: 0, message:"ok", data: {text:"Welcome <strong>Text</strong>"}}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.getHTML = async function(padID, rev) { exports.getHTML = async function (padID, rev) {
if (rev !== undefined) { if (rev !== undefined) {
rev = checkValidRev(rev); rev = checkValidRev(rev);
} }
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
// the client asked for a special revision // the client asked for a special revision
if (rev !== undefined) { if (rev !== undefined) {
// check if this is a valid revision // check if this is a valid revision
let head = pad.getHeadRevisionNumber(); const head = pad.getHeadRevisionNumber();
if (rev > head) { if (rev > head) {
throw new customError("rev is higher than the head revision of the pad", "apierror"); throw new customError('rev is higher than the head revision of the pad', 'apierror');
} }
} }
@ -255,9 +253,9 @@ exports.getHTML = async function(padID, rev) {
let html = await exportHtml.getPadHTML(pad, rev); let html = await exportHtml.getPadHTML(pad, rev);
// wrap the HTML // wrap the HTML
html = "<!DOCTYPE HTML><html><body>" + html + "</body></html>"; html = `<!DOCTYPE HTML><html><body>${html}</body></html>`;
return { html }; return {html};
} };
/** /**
setHTML(padID, html) sets the text of a pad based on HTML setHTML(padID, html) sets the text of a pad based on HTML
@ -267,20 +265,20 @@ Example returns:
{code: 0, message:"ok", data: null} {code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.setHTML = async function(padID, html) { exports.setHTML = async function (padID, html) {
// html string is required // html string is required
if (typeof html !== "string") { if (typeof html !== 'string') {
throw new customError("html is not a string", "apierror"); throw new customError('html is not a string', 'apierror');
} }
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
// add a new changeset with the new html to the pad // add a new changeset with the new html to the pad
try { try {
await importHtml.setPadHTML(pad, cleanText(html)); await importHtml.setPadHTML(pad, cleanText(html));
} catch (e) { } catch (e) {
throw new customError("HTML is malformed", "apierror"); throw new customError('HTML is malformed', 'apierror');
} }
// update the clients on the pad // update the clients on the pad
@ -303,23 +301,23 @@ Example returns:
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.getChatHistory = async function(padID, start, end) { exports.getChatHistory = async function (padID, start, end) {
if (start && end) { if (start && end) {
if (start < 0) { if (start < 0) {
throw new customError("start is below zero", "apierror"); throw new customError('start is below zero', 'apierror');
} }
if (end < 0) { if (end < 0) {
throw new customError("end is below zero", "apierror"); throw new customError('end is below zero', 'apierror');
} }
if (start > end) { if (start > end) {
throw new customError("start is higher than end", "apierror"); throw new customError('start is higher than end', 'apierror');
} }
} }
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
var chatHead = pad.chatHead; const chatHead = pad.chatHead;
// fall back to getting the whole chat-history if a parameter is missing // fall back to getting the whole chat-history if a parameter is missing
if (!start || !end) { if (!start || !end) {
@ -328,17 +326,17 @@ exports.getChatHistory = async function(padID, start, end) {
} }
if (start > chatHead) { if (start > chatHead) {
throw new customError("start is higher than the current chatHead", "apierror"); throw new customError('start is higher than the current chatHead', 'apierror');
} }
if (end > chatHead) { if (end > chatHead) {
throw new customError("end is higher than the current chatHead", "apierror"); throw new customError('end is higher than the current chatHead', 'apierror');
} }
// the the whole message-log and return it to the client // the the whole message-log and return it to the client
let messages = await pad.getChatMessages(start, end); const messages = await pad.getChatMessages(start, end);
return { messages }; return {messages};
} };
/** /**
appendChatMessage(padID, text, authorID, time), creates a chat message for the pad id, time is a timestamp appendChatMessage(padID, text, authorID, time), creates a chat message for the pad id, time is a timestamp
@ -348,10 +346,10 @@ Example returns:
{code: 0, message:"ok", data: null} {code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.appendChatMessage = async function(padID, text, authorID, time) { exports.appendChatMessage = async function (padID, text, authorID, time) {
// text is required // text is required
if (typeof text !== "string") { if (typeof text !== 'string') {
throw new customError("text is not a string", "apierror"); throw new customError('text is not a string', 'apierror');
} }
// if time is not an integer value set time to current timestamp // if time is not an integer value set time to current timestamp
@ -363,7 +361,7 @@ exports.appendChatMessage = async function(padID, text, authorID, time) {
// save chat message to database and send message to all connected clients // save chat message to database and send message to all connected clients
await padMessageHandler.sendChatMessageToPadClients(time, authorID, text, padID); await padMessageHandler.sendChatMessageToPadClients(time, authorID, text, padID);
} };
/* *************** /* ***************
* PAD FUNCTIONS * * PAD FUNCTIONS *
@ -377,11 +375,11 @@ Example returns:
{code: 0, message:"ok", data: {revisions: 56}} {code: 0, message:"ok", data: {revisions: 56}}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.getRevisionsCount = async function(padID) { exports.getRevisionsCount = async function (padID) {
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
return { revisions: pad.getHeadRevisionNumber() }; return {revisions: pad.getHeadRevisionNumber()};
} };
/** /**
getSavedRevisionsCount(padID) returns the number of saved revisions of this pad getSavedRevisionsCount(padID) returns the number of saved revisions of this pad
@ -391,11 +389,11 @@ Example returns:
{code: 0, message:"ok", data: {savedRevisions: 42}} {code: 0, message:"ok", data: {savedRevisions: 42}}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.getSavedRevisionsCount = async function(padID) { exports.getSavedRevisionsCount = async function (padID) {
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
return { savedRevisions: pad.getSavedRevisionsNumber() }; return {savedRevisions: pad.getSavedRevisionsNumber()};
} };
/** /**
listSavedRevisions(padID) returns the list of saved revisions of this pad listSavedRevisions(padID) returns the list of saved revisions of this pad
@ -405,11 +403,11 @@ Example returns:
{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}} {code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.listSavedRevisions = async function(padID) { exports.listSavedRevisions = async function (padID) {
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
return { savedRevisions: pad.getSavedRevisionsList() }; return {savedRevisions: pad.getSavedRevisionsList()};
} };
/** /**
saveRevision(padID) returns the list of saved revisions of this pad saveRevision(padID) returns the list of saved revisions of this pad
@ -419,28 +417,28 @@ Example returns:
{code: 0, message:"ok", data: null} {code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.saveRevision = async function(padID, rev) { exports.saveRevision = async function (padID, rev) {
// check if rev is a number // check if rev is a number
if (rev !== undefined) { if (rev !== undefined) {
rev = checkValidRev(rev); rev = checkValidRev(rev);
} }
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
let head = pad.getHeadRevisionNumber(); const head = pad.getHeadRevisionNumber();
// the client asked for a special revision // the client asked for a special revision
if (rev !== undefined) { if (rev !== undefined) {
if (rev > head) { if (rev > head) {
throw new customError("rev is higher than the head revision of the pad", "apierror"); throw new customError('rev is higher than the head revision of the pad', 'apierror');
} }
} else { } else {
rev = pad.getHeadRevisionNumber(); rev = pad.getHeadRevisionNumber();
} }
let author = await authorManager.createAuthor('API'); const author = await authorManager.createAuthor('API');
await pad.addSavedRevision(rev, author.authorID, 'Saved through API call'); await pad.addSavedRevision(rev, author.authorID, 'Saved through API call');
} };
/** /**
getLastEdited(padID) returns the timestamp of the last revision of the pad getLastEdited(padID) returns the timestamp of the last revision of the pad
@ -450,12 +448,12 @@ Example returns:
{code: 0, message:"ok", data: {lastEdited: 1340815946602}} {code: 0, message:"ok", data: {lastEdited: 1340815946602}}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.getLastEdited = async function(padID) { exports.getLastEdited = async function (padID) {
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
let lastEdited = await pad.getLastEdit(); const lastEdited = await pad.getLastEdit();
return { lastEdited }; return {lastEdited};
} };
/** /**
createPad(padName [, text]) creates a new pad in this group createPad(padName [, text]) creates a new pad in this group
@ -465,22 +463,22 @@ Example returns:
{code: 0, message:"ok", data: null} {code: 0, message:"ok", data: null}
{code: 1, message:"pad does already exist", data: null} {code: 1, message:"pad does already exist", data: null}
*/ */
exports.createPad = async function(padID, text) { exports.createPad = async function (padID, text) {
if (padID) { if (padID) {
// ensure there is no $ in the padID // ensure there is no $ in the padID
if (padID.indexOf("$") !== -1) { if (padID.indexOf('$') !== -1) {
throw new customError("createPad can't create group pads", "apierror"); throw new customError("createPad can't create group pads", 'apierror');
} }
// check for url special characters // check for url special characters
if (padID.match(/(\/|\?|&|#)/)) { if (padID.match(/(\/|\?|&|#)/)) {
throw new customError("malformed padID: Remove special characters", "apierror"); throw new customError('malformed padID: Remove special characters', 'apierror');
} }
} }
// create pad // create pad
await getPadSafe(padID, false, text); await getPadSafe(padID, false, text);
} };
/** /**
deletePad(padID) deletes a pad deletePad(padID) deletes a pad
@ -490,10 +488,10 @@ Example returns:
{code: 0, message:"ok", data: null} {code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.deletePad = async function(padID) { exports.deletePad = async function (padID) {
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
await pad.remove(); await pad.remove();
} };
/** /**
restoreRevision(padID, [rev]) Restores revision from past as new changeset restoreRevision(padID, [rev]) Restores revision from past as new changeset
@ -503,34 +501,34 @@ exports.deletePad = async function(padID) {
{code:0, message:"ok", data:null} {code:0, message:"ok", data:null}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.restoreRevision = async function(padID, rev) { exports.restoreRevision = async function (padID, rev) {
// check if rev is a number // check if rev is a number
if (rev === undefined) { if (rev === undefined) {
throw new customError("rev is not defined", "apierror"); throw new customError('rev is not defined', 'apierror');
} }
rev = checkValidRev(rev); rev = checkValidRev(rev);
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
// check if this is a valid revision // check if this is a valid revision
if (rev > pad.getHeadRevisionNumber()) { if (rev > pad.getHeadRevisionNumber()) {
throw new customError("rev is higher than the head revision of the pad", "apierror"); throw new customError('rev is higher than the head revision of the pad', 'apierror');
} }
let atext = await pad.getInternalRevisionAText(rev); const atext = await pad.getInternalRevisionAText(rev);
var oldText = pad.text(); const oldText = pad.text();
atext.text += "\n"; atext.text += '\n';
function eachAttribRun(attribs, func) { function eachAttribRun(attribs, func) {
var attribsIter = Changeset.opIterator(attribs); const attribsIter = Changeset.opIterator(attribs);
var textIndex = 0; let textIndex = 0;
var newTextStart = 0; const newTextStart = 0;
var newTextEnd = atext.text.length; const newTextEnd = atext.text.length;
while (attribsIter.hasNext()) { while (attribsIter.hasNext()) {
var op = attribsIter.next(); const op = attribsIter.next();
var nextIndex = textIndex + op.chars; const nextIndex = textIndex + op.chars;
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) { if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs); func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
} }
@ -539,14 +537,14 @@ exports.restoreRevision = async function(padID, rev) {
} }
// create a new changeset with a helper builder object // create a new changeset with a helper builder object
var builder = Changeset.builder(oldText.length); const builder = Changeset.builder(oldText.length);
// assemble each line into the builder // assemble each line into the builder
eachAttribRun(atext.attribs, function(start, end, attribs) { eachAttribRun(atext.attribs, (start, end, attribs) => {
builder.insert(atext.text.substring(start, end), attribs); builder.insert(atext.text.substring(start, end), attribs);
}); });
var lastNewlinePos = oldText.lastIndexOf('\n'); const lastNewlinePos = oldText.lastIndexOf('\n');
if (lastNewlinePos < 0) { if (lastNewlinePos < 0) {
builder.remove(oldText.length - 1, 0); builder.remove(oldText.length - 1, 0);
} else { } else {
@ -554,13 +552,13 @@ exports.restoreRevision = async function(padID, rev) {
builder.remove(oldText.length - lastNewlinePos - 1, 0); builder.remove(oldText.length - lastNewlinePos - 1, 0);
} }
var changeset = builder.toString(); const changeset = builder.toString();
await Promise.all([ await Promise.all([
pad.appendRevision(changeset), pad.appendRevision(changeset),
padMessageHandler.updatePadClients(pad), padMessageHandler.updatePadClients(pad),
]); ]);
} };
/** /**
copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true, copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true,
@ -571,10 +569,10 @@ Example returns:
{code: 0, message:"ok", data: {padID: destinationID}} {code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.copyPad = async function(sourceID, destinationID, force) { exports.copyPad = async function (sourceID, destinationID, force) {
let pad = await getPadSafe(sourceID, true); const pad = await getPadSafe(sourceID, true);
await pad.copy(destinationID, force); await pad.copy(destinationID, force);
} };
/** /**
copyPadWithoutHistory(sourceID, destinationID[, force=false]) copies a pad. If force is true, copyPadWithoutHistory(sourceID, destinationID[, force=false]) copies a pad. If force is true,
@ -585,10 +583,10 @@ Example returns:
{code: 0, message:"ok", data: {padID: destinationID}} {code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.copyPadWithoutHistory = async function(sourceID, destinationID, force) { exports.copyPadWithoutHistory = async function (sourceID, destinationID, force) {
let pad = await getPadSafe(sourceID, true); const pad = await getPadSafe(sourceID, true);
await pad.copyPadWithoutHistory(destinationID, force); await pad.copyPadWithoutHistory(destinationID, force);
} };
/** /**
movePad(sourceID, destinationID[, force=false]) moves a pad. If force is true, movePad(sourceID, destinationID[, force=false]) moves a pad. If force is true,
@ -599,11 +597,11 @@ Example returns:
{code: 0, message:"ok", data: {padID: destinationID}} {code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.movePad = async function(sourceID, destinationID, force) { exports.movePad = async function (sourceID, destinationID, force) {
let pad = await getPadSafe(sourceID, true); const pad = await getPadSafe(sourceID, true);
await pad.copy(destinationID, force); await pad.copy(destinationID, force);
await pad.remove(); await pad.remove();
} };
/** /**
getReadOnlyLink(padID) returns the read only link of a pad getReadOnlyLink(padID) returns the read only link of a pad
@ -613,15 +611,15 @@ Example returns:
{code: 0, message:"ok", data: null} {code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.getReadOnlyID = async function(padID) { exports.getReadOnlyID = async function (padID) {
// we don't need the pad object, but this function does all the security stuff for us // we don't need the pad object, but this function does all the security stuff for us
await getPadSafe(padID, true); await getPadSafe(padID, true);
// get the readonlyId // get the readonlyId
let readOnlyID = await readOnlyManager.getReadOnlyId(padID); const readOnlyID = await readOnlyManager.getReadOnlyId(padID);
return { readOnlyID }; return {readOnlyID};
} };
/** /**
getPadID(roID) returns the padID of a pad based on the readonlyID(roID) getPadID(roID) returns the padID of a pad based on the readonlyID(roID)
@ -631,15 +629,15 @@ Example returns:
{code: 0, message:"ok", data: {padID: padID}} {code: 0, message:"ok", data: {padID: padID}}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.getPadID = async function(roID) { exports.getPadID = async function (roID) {
// get the PadId // get the PadId
let padID = await readOnlyManager.getPadId(roID); const padID = await readOnlyManager.getPadId(roID);
if (padID === null) { if (padID === null) {
throw new customError("padID does not exist", "apierror"); throw new customError('padID does not exist', 'apierror');
} }
return { padID }; return {padID};
} };
/** /**
setPublicStatus(padID, publicStatus) sets a boolean for the public status of a pad setPublicStatus(padID, publicStatus) sets a boolean for the public status of a pad
@ -649,20 +647,20 @@ Example returns:
{code: 0, message:"ok", data: null} {code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.setPublicStatus = async function(padID, publicStatus) { exports.setPublicStatus = async function (padID, publicStatus) {
// ensure this is a group pad // ensure this is a group pad
checkGroupPad(padID, "publicStatus"); checkGroupPad(padID, 'publicStatus');
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
// convert string to boolean // convert string to boolean
if (typeof publicStatus === "string") { if (typeof publicStatus === 'string') {
publicStatus = (publicStatus.toLowerCase() === "true"); publicStatus = (publicStatus.toLowerCase() === 'true');
} }
await pad.setPublicStatus(publicStatus); await pad.setPublicStatus(publicStatus);
} };
/** /**
getPublicStatus(padID) return true of false getPublicStatus(padID) return true of false
@ -672,14 +670,14 @@ Example returns:
{code: 0, message:"ok", data: {publicStatus: true}} {code: 0, message:"ok", data: {publicStatus: true}}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.getPublicStatus = async function(padID) { exports.getPublicStatus = async function (padID) {
// ensure this is a group pad // ensure this is a group pad
checkGroupPad(padID, "publicStatus"); checkGroupPad(padID, 'publicStatus');
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
return { publicStatus: pad.getPublicStatus() }; return {publicStatus: pad.getPublicStatus()};
} };
/** /**
listAuthorsOfPad(padID) returns an array of authors who contributed to this pad listAuthorsOfPad(padID) returns an array of authors who contributed to this pad
@ -689,12 +687,12 @@ Example returns:
{code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]} {code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.listAuthorsOfPad = async function(padID) { exports.listAuthorsOfPad = async function (padID) {
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
let authorIDs = pad.getAllAuthors(); const authorIDs = pad.getAllAuthors();
return { authorIDs }; return {authorIDs};
} };
/** /**
sendClientsMessage(padID, msg) sends a message to all clients connected to the sendClientsMessage(padID, msg) sends a message to all clients connected to the
@ -719,10 +717,10 @@ Example returns:
{code: 1, message:"padID does not exist"} {code: 1, message:"padID does not exist"}
*/ */
exports.sendClientsMessage = async function(padID, msg) { exports.sendClientsMessage = async function (padID, msg) {
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
padMessageHandler.handleCustomMessage(padID, msg); padMessageHandler.handleCustomMessage(padID, msg);
} };
/** /**
checkToken() returns ok when the current api token is valid checkToken() returns ok when the current api token is valid
@ -732,8 +730,8 @@ Example returns:
{"code":0,"message":"ok","data":null} {"code":0,"message":"ok","data":null}
{"code":4,"message":"no or wrong API Key","data":null} {"code":4,"message":"no or wrong API Key","data":null}
*/ */
exports.checkToken = async function() { exports.checkToken = async function () {
} };
/** /**
getChatHead(padID) returns the chatHead (last number of the last chat-message) of the pad getChatHead(padID) returns the chatHead (last number of the last chat-message) of the pad
@ -743,11 +741,11 @@ Example returns:
{code: 0, message:"ok", data: {chatHead: 42}} {code: 0, message:"ok", data: {chatHead: 42}}
{code: 1, message:"padID does not exist", data: null} {code: 1, message:"padID does not exist", data: null}
*/ */
exports.getChatHead = async function(padID) { exports.getChatHead = async function (padID) {
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
return { chatHead: pad.chatHead }; return {chatHead: pad.chatHead};
} };
/** /**
createDiffHTML(padID, startRev, endRev) returns an object of diffs from 2 points in a pad createDiffHTML(padID, startRev, endRev) returns an object of diffs from 2 points in a pad
@ -757,8 +755,7 @@ Example returns:
{"code":0,"message":"ok","data":{"html":"<style>\n.authora_HKIv23mEbachFYfH {background-color: #a979d9}\n.authora_n4gEeMLsv1GivNeh {background-color: #a9b5d9}\n.removed {text-decoration: line-through; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; filter: alpha(opacity=80); opacity: 0.8; }\n</style>Welcome to Etherpad!<br><br>This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!<br><br>Get involved with Etherpad at <a href=\"http&#x3a;&#x2F;&#x2F;etherpad&#x2e;org\">http:&#x2F;&#x2F;etherpad.org</a><br><span class=\"authora_HKIv23mEbachFYfH\">aw</span><br><br>","authors":["a.HKIv23mEbachFYfH",""]}} {"code":0,"message":"ok","data":{"html":"<style>\n.authora_HKIv23mEbachFYfH {background-color: #a979d9}\n.authora_n4gEeMLsv1GivNeh {background-color: #a9b5d9}\n.removed {text-decoration: line-through; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; filter: alpha(opacity=80); opacity: 0.8; }\n</style>Welcome to Etherpad!<br><br>This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!<br><br>Get involved with Etherpad at <a href=\"http&#x3a;&#x2F;&#x2F;etherpad&#x2e;org\">http:&#x2F;&#x2F;etherpad.org</a><br><span class=\"authora_HKIv23mEbachFYfH\">aw</span><br><br>","authors":["a.HKIv23mEbachFYfH",""]}}
{"code":4,"message":"no or wrong API Key","data":null} {"code":4,"message":"no or wrong API Key","data":null}
*/ */
exports.createDiffHTML = async function(padID, startRev, endRev) { exports.createDiffHTML = async function (padID, startRev, endRev) {
// check if startRev is a number // check if startRev is a number
if (startRev !== undefined) { if (startRev !== undefined) {
startRev = checkValidRev(startRev); startRev = checkValidRev(startRev);
@ -770,18 +767,18 @@ exports.createDiffHTML = async function(padID, startRev, endRev) {
} }
// get the pad // get the pad
let pad = await getPadSafe(padID, true); const pad = await getPadSafe(padID, true);
try { try {
var padDiff = new PadDiff(pad, startRev, endRev); var padDiff = new PadDiff(pad, startRev, endRev);
} catch (e) { } catch (e) {
throw { stop: e.message }; throw {stop: e.message};
} }
let html = await padDiff.getHtml(); const html = await padDiff.getHtml();
let authors = await padDiff.getAuthors(); const authors = await padDiff.getAuthors();
return { html, authors }; return {html, authors};
} };
/* ******************** /* ********************
** GLOBAL FUNCTIONS ** ** GLOBAL FUNCTIONS **
@ -796,20 +793,20 @@ exports.createDiffHTML = async function(padID, startRev, endRev) {
{"code":4,"message":"no or wrong API Key","data":null} {"code":4,"message":"no or wrong API Key","data":null}
*/ */
exports.getStats = async function() { exports.getStats = async function () {
const sessionInfos = padMessageHandler.sessioninfos; const sessionInfos = padMessageHandler.sessioninfos;
const sessionKeys = Object.keys(sessionInfos); const sessionKeys = Object.keys(sessionInfos);
const activePads = new Set(Object.entries(sessionInfos).map(k => k[1].padId)); const activePads = new Set(Object.entries(sessionInfos).map((k) => k[1].padId));
const { padIDs } = await padManager.listAllPads(); const {padIDs} = await padManager.listAllPads();
return { return {
totalPads: padIDs.length, totalPads: padIDs.length,
totalSessions: sessionKeys.length, totalSessions: sessionKeys.length,
totalActivePads: activePads.size, totalActivePads: activePads.size,
} };
} };
/* **************************** /* ****************************
** INTERNAL HELPER FUNCTIONS * ** INTERNAL HELPER FUNCTIONS *
@ -817,32 +814,32 @@ exports.getStats = async function() {
// checks if a number is an int // checks if a number is an int
function is_int(value) { function is_int(value) {
return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value) return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value);
} }
// gets a pad safe // gets a pad safe
async function getPadSafe(padID, shouldExist, text) { async function getPadSafe(padID, shouldExist, text) {
// check if padID is a string // check if padID is a string
if (typeof padID !== "string") { if (typeof padID !== 'string') {
throw new customError("padID is not a string", "apierror"); throw new customError('padID is not a string', 'apierror');
} }
// check if the padID maches the requirements // check if the padID maches the requirements
if (!padManager.isValidPadId(padID)) { if (!padManager.isValidPadId(padID)) {
throw new customError("padID did not match requirements", "apierror"); throw new customError('padID did not match requirements', 'apierror');
} }
// check if the pad exists // check if the pad exists
let exists = await padManager.doesPadExists(padID); const exists = await padManager.doesPadExists(padID);
if (!exists && shouldExist) { if (!exists && shouldExist) {
// does not exist, but should // does not exist, but should
throw new customError("padID does not exist", "apierror"); throw new customError('padID does not exist', 'apierror');
} }
if (exists && !shouldExist) { if (exists && !shouldExist) {
// does exist, but shouldn't // does exist, but shouldn't
throw new customError("padID does already exist", "apierror"); throw new customError('padID does already exist', 'apierror');
} }
// pad exists, let's get it // pad exists, let's get it
@ -852,23 +849,23 @@ async function getPadSafe(padID, shouldExist, text) {
// checks if a rev is a legal number // checks if a rev is a legal number
// pre-condition is that `rev` is not undefined // pre-condition is that `rev` is not undefined
function checkValidRev(rev) { function checkValidRev(rev) {
if (typeof rev !== "number") { if (typeof rev !== 'number') {
rev = parseInt(rev, 10); rev = parseInt(rev, 10);
} }
// check if rev is a number // check if rev is a number
if (isNaN(rev)) { if (isNaN(rev)) {
throw new customError("rev is not a number", "apierror"); throw new customError('rev is not a number', 'apierror');
} }
// ensure this is not a negative number // ensure this is not a negative number
if (rev < 0) { if (rev < 0) {
throw new customError("rev is not a negative number", "apierror"); throw new customError('rev is not a negative number', 'apierror');
} }
// ensure this is not a float value // ensure this is not a float value
if (!is_int(rev)) { if (!is_int(rev)) {
throw new customError("rev is a float value", "apierror"); throw new customError('rev is a float value', 'apierror');
} }
return rev; return rev;
@ -877,7 +874,7 @@ function checkValidRev(rev) {
// checks if a padID is part of a group // checks if a padID is part of a group
function checkGroupPad(padID, field) { function checkGroupPad(padID, field) {
// ensure this is a group pad // ensure this is a group pad
if (padID && padID.indexOf("$") === -1) { if (padID && padID.indexOf('$') === -1) {
throw new customError(`You can only get/set the ${field} of pads that belong to a group`, "apierror"); throw new customError(`You can only get/set the ${field} of pads that belong to a group`, 'apierror');
} }
} }

View file

@ -18,31 +18,87 @@
* limitations under the License. * limitations under the License.
*/ */
var db = require("./DB"); const db = require('./DB');
var customError = require("../utils/customError"); const customError = require('../utils/customError');
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; const randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
exports.getColorPalette = function() { exports.getColorPalette = function () {
return [ return [
"#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", '#ffc7c7',
"#ffa8a8", "#ffe699", "#cfff9e", "#99ffb3", "#a3ffff", "#99b3ff", "#cc99ff", "#ff99e5", '#fff1c7',
"#e7b1b1", "#e9dcAf", "#cde9af", "#bfedcc", "#b1e7e7", "#c3cdee", "#d2b8ea", "#eec3e6", '#e3ffc7',
"#e9cece", "#e7e0ca", "#d3e5c7", "#bce1c5", "#c1e2e2", "#c1c9e2", "#cfc1e2", "#e0bdd9", '#c7ffd5',
"#baded3", "#a0f8eb", "#b1e7e0", "#c3c8e4", "#cec5e2", "#b1d5e7", "#cda8f0", "#f0f0a8", '#c7ffff',
"#f2f2a6", "#f5a8eb", "#c5f9a9", "#ececbb", "#e7c4bc", "#daf0b2", "#b0a0fd", "#bce2e7", '#c7d5ff',
"#cce2bb", "#ec9afe", "#edabbd", "#aeaeea", "#c4e7b1", "#d722bb", "#f3a5e7", "#ffa8a8", '#e3c7ff',
"#d8c0c5", "#eaaedd", "#adc6eb", "#bedad1", "#dee9af", "#e9afc2", "#f8d2a0", "#b3b3e6" '#ffc7f1',
'#ffa8a8',
'#ffe699',
'#cfff9e',
'#99ffb3',
'#a3ffff',
'#99b3ff',
'#cc99ff',
'#ff99e5',
'#e7b1b1',
'#e9dcAf',
'#cde9af',
'#bfedcc',
'#b1e7e7',
'#c3cdee',
'#d2b8ea',
'#eec3e6',
'#e9cece',
'#e7e0ca',
'#d3e5c7',
'#bce1c5',
'#c1e2e2',
'#c1c9e2',
'#cfc1e2',
'#e0bdd9',
'#baded3',
'#a0f8eb',
'#b1e7e0',
'#c3c8e4',
'#cec5e2',
'#b1d5e7',
'#cda8f0',
'#f0f0a8',
'#f2f2a6',
'#f5a8eb',
'#c5f9a9',
'#ececbb',
'#e7c4bc',
'#daf0b2',
'#b0a0fd',
'#bce2e7',
'#cce2bb',
'#ec9afe',
'#edabbd',
'#aeaeea',
'#c4e7b1',
'#d722bb',
'#f3a5e7',
'#ffa8a8',
'#d8c0c5',
'#eaaedd',
'#adc6eb',
'#bedad1',
'#dee9af',
'#e9afc2',
'#f8d2a0',
'#b3b3e6',
]; ];
}; };
/** /**
* Checks if the author exists * Checks if the author exists
*/ */
exports.doesAuthorExist = async function(authorID) { exports.doesAuthorExist = async function (authorID) {
let author = await db.get("globalAuthor:" + authorID); const author = await db.get(`globalAuthor:${authorID}`);
return author !== null; return author !== null;
} };
/* exported for backwards compatibility */ /* exported for backwards compatibility */
exports.doesAuthorExists = exports.doesAuthorExist; exports.doesAuthorExists = exports.doesAuthorExist;
@ -51,20 +107,20 @@ exports.doesAuthorExists = exports.doesAuthorExist;
* Returns the AuthorID for a token. * Returns the AuthorID for a token.
* @param {String} token The token * @param {String} token The token
*/ */
exports.getAuthor4Token = async function(token) { exports.getAuthor4Token = async function (token) {
let author = await mapAuthorWithDBKey("token2author", token); const author = await mapAuthorWithDBKey('token2author', token);
// return only the sub value authorID // return only the sub value authorID
return author ? author.authorID : author; return author ? author.authorID : author;
} };
/** /**
* Returns the AuthorID for a mapper. * Returns the AuthorID for a mapper.
* @param {String} token The mapper * @param {String} token The mapper
* @param {String} name The name of the author (optional) * @param {String} name The name of the author (optional)
*/ */
exports.createAuthorIfNotExistsFor = async function(authorMapper, name) { exports.createAuthorIfNotExistsFor = async function (authorMapper, name) {
let author = await mapAuthorWithDBKey("mapper2author", authorMapper); const author = await mapAuthorWithDBKey('mapper2author', authorMapper);
if (name) { if (name) {
// set the name of this author // set the name of this author
@ -80,16 +136,16 @@ exports.createAuthorIfNotExistsFor = async function(authorMapper, name) {
* @param {String} mapperkey The database key name for this mapper * @param {String} mapperkey The database key name for this mapper
* @param {String} mapper The mapper * @param {String} mapper The mapper
*/ */
async function mapAuthorWithDBKey (mapperkey, mapper) { async function mapAuthorWithDBKey(mapperkey, mapper) {
// try to map to an author // try to map to an author
let author = await db.get(mapperkey + ":" + mapper); const author = await db.get(`${mapperkey}:${mapper}`);
if (author === null) { if (author === null) {
// there is no author with this mapper, so create one // there is no author with this mapper, so create one
let author = await exports.createAuthor(null); const author = await exports.createAuthor(null);
// create the token2author relation // create the token2author relation
await db.set(mapperkey + ":" + mapper, author.authorID); await db.set(`${mapperkey}:${mapper}`, author.authorID);
// return the author // return the author
return author; return author;
@ -97,109 +153,109 @@ async function mapAuthorWithDBKey (mapperkey, mapper) {
// there is an author with this mapper // there is an author with this mapper
// update the timestamp of this author // update the timestamp of this author
await db.setSub("globalAuthor:" + author, ["timestamp"], Date.now()); await db.setSub(`globalAuthor:${author}`, ['timestamp'], Date.now());
// return the author // return the author
return { authorID: author}; return {authorID: author};
} }
/** /**
* Internal function that creates the database entry for an author * Internal function that creates the database entry for an author
* @param {String} name The name of the author * @param {String} name The name of the author
*/ */
exports.createAuthor = function(name) { exports.createAuthor = function (name) {
// create the new author name // create the new author name
let author = "a." + randomString(16); const author = `a.${randomString(16)}`;
// create the globalAuthors db entry // create the globalAuthors db entry
let authorObj = { const authorObj = {
"colorId": Math.floor(Math.random() * (exports.getColorPalette().length)), colorId: Math.floor(Math.random() * (exports.getColorPalette().length)),
"name": name, name,
"timestamp": Date.now() timestamp: Date.now(),
}; };
// set the global author db entry // set the global author db entry
// NB: no await, since we're not waiting for the DB set to finish // NB: no await, since we're not waiting for the DB set to finish
db.set("globalAuthor:" + author, authorObj); db.set(`globalAuthor:${author}`, authorObj);
return { authorID: author }; return {authorID: author};
} };
/** /**
* Returns the Author Obj of the author * Returns the Author Obj of the author
* @param {String} author The id of the author * @param {String} author The id of the author
*/ */
exports.getAuthor = function(author) { exports.getAuthor = function (author) {
// NB: result is already a Promise // NB: result is already a Promise
return db.get("globalAuthor:" + author); return db.get(`globalAuthor:${author}`);
} };
/** /**
* Returns the color Id of the author * Returns the color Id of the author
* @param {String} author The id of the author * @param {String} author The id of the author
*/ */
exports.getAuthorColorId = function(author) { exports.getAuthorColorId = function (author) {
return db.getSub("globalAuthor:" + author, ["colorId"]); return db.getSub(`globalAuthor:${author}`, ['colorId']);
} };
/** /**
* Sets the color Id of the author * Sets the color Id of the author
* @param {String} author The id of the author * @param {String} author The id of the author
* @param {String} colorId The color id of the author * @param {String} colorId The color id of the author
*/ */
exports.setAuthorColorId = function(author, colorId) { exports.setAuthorColorId = function (author, colorId) {
return db.setSub("globalAuthor:" + author, ["colorId"], colorId); return db.setSub(`globalAuthor:${author}`, ['colorId'], colorId);
} };
/** /**
* Returns the name of the author * Returns the name of the author
* @param {String} author The id of the author * @param {String} author The id of the author
*/ */
exports.getAuthorName = function(author) { exports.getAuthorName = function (author) {
return db.getSub("globalAuthor:" + author, ["name"]); return db.getSub(`globalAuthor:${author}`, ['name']);
} };
/** /**
* Sets the name of the author * Sets the name of the author
* @param {String} author The id of the author * @param {String} author The id of the author
* @param {String} name The name of the author * @param {String} name The name of the author
*/ */
exports.setAuthorName = function(author, name) { exports.setAuthorName = function (author, name) {
return db.setSub("globalAuthor:" + author, ["name"], name); return db.setSub(`globalAuthor:${author}`, ['name'], name);
} };
/** /**
* Returns an array of all pads this author contributed to * Returns an array of all pads this author contributed to
* @param {String} author The id of the author * @param {String} author The id of the author
*/ */
exports.listPadsOfAuthor = async function(authorID) { exports.listPadsOfAuthor = async function (authorID) {
/* There are two other places where this array is manipulated: /* There are two other places where this array is manipulated:
* (1) When the author is added to a pad, the author object is also updated * (1) When the author is added to a pad, the author object is also updated
* (2) When a pad is deleted, each author of that pad is also updated * (2) When a pad is deleted, each author of that pad is also updated
*/ */
// get the globalAuthor // get the globalAuthor
let author = await db.get("globalAuthor:" + authorID); const author = await db.get(`globalAuthor:${authorID}`);
if (author === null) { if (author === null) {
// author does not exist // author does not exist
throw new customError("authorID does not exist", "apierror"); throw new customError('authorID does not exist', 'apierror');
} }
// everything is fine, return the pad IDs // everything is fine, return the pad IDs
let padIDs = Object.keys(author.padIDs || {}); const padIDs = Object.keys(author.padIDs || {});
return { padIDs }; return {padIDs};
} };
/** /**
* Adds a new pad to the list of contributions * Adds a new pad to the list of contributions
* @param {String} author The id of the author * @param {String} author The id of the author
* @param {String} padID The id of the pad the author contributes to * @param {String} padID The id of the pad the author contributes to
*/ */
exports.addPad = async function(authorID, padID) { exports.addPad = async function (authorID, padID) {
// get the entry // get the entry
let author = await db.get("globalAuthor:" + authorID); const author = await db.get(`globalAuthor:${authorID}`);
if (author === null) return; if (author === null) return;
@ -216,22 +272,22 @@ exports.addPad = async function(authorID, padID) {
author.padIDs[padID] = 1; // anything, because value is not used author.padIDs[padID] = 1; // anything, because value is not used
// save the new element back // save the new element back
db.set("globalAuthor:" + authorID, author); db.set(`globalAuthor:${authorID}`, author);
} };
/** /**
* Removes a pad from the list of contributions * Removes a pad from the list of contributions
* @param {String} author The id of the author * @param {String} author The id of the author
* @param {String} padID The id of the pad the author contributes to * @param {String} padID The id of the pad the author contributes to
*/ */
exports.removePad = async function(authorID, padID) { exports.removePad = async function (authorID, padID) {
let author = await db.get("globalAuthor:" + authorID); const author = await db.get(`globalAuthor:${authorID}`);
if (author === null) return; if (author === null) return;
if (author.padIDs !== null) { if (author.padIDs !== null) {
// remove pad from author // remove pad from author
delete author.padIDs[padID]; delete author.padIDs[padID];
await db.set('globalAuthor:' + authorID, author); await db.set(`globalAuthor:${authorID}`, author);
} }
} };

View file

@ -19,13 +19,13 @@
* limitations under the License. * limitations under the License.
*/ */
var ueberDB = require("ueberdb2"); const ueberDB = require('ueberdb2');
var settings = require("../utils/Settings"); const settings = require('../utils/Settings');
var log4js = require('log4js'); const log4js = require('log4js');
const util = require("util"); const util = require('util');
// set database settings // set database settings
let db = new ueberDB.database(settings.dbType, settings.dbSettings, null, log4js.getLogger("ueberDB")); const db = new ueberDB.database(settings.dbType, settings.dbSettings, null, log4js.getLogger('ueberDB'));
/** /**
* The UeberDB Object that provides the database functions * The UeberDB Object that provides the database functions
@ -36,32 +36,32 @@ exports.db = null;
* Initalizes the database with the settings provided by the settings module * Initalizes the database with the settings provided by the settings module
* @param {Function} callback * @param {Function} callback
*/ */
exports.init = function() { exports.init = function () {
// initalize the database async // initalize the database async
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
db.init(function(err) { db.init((err) => {
if (err) { if (err) {
// there was an error while initializing the database, output it and stop // there was an error while initializing the database, output it and stop
console.error("ERROR: Problem while initalizing the database"); console.error('ERROR: Problem while initalizing the database');
console.error(err.stack ? err.stack : err); console.error(err.stack ? err.stack : err);
process.exit(1); process.exit(1);
} }
// everything ok, set up Promise-based methods // everything ok, set up Promise-based methods
['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove', 'doShutdown'].forEach(fn => { ['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove', 'doShutdown'].forEach((fn) => {
exports[fn] = util.promisify(db[fn].bind(db)); exports[fn] = util.promisify(db[fn].bind(db));
}); });
// set up wrappers for get and getSub that can't return "undefined" // set up wrappers for get and getSub that can't return "undefined"
let get = exports.get; const get = exports.get;
exports.get = async function(key) { exports.get = async function (key) {
let result = await get(key); const result = await get(key);
return (result === undefined) ? null : result; return (result === undefined) ? null : result;
}; };
let getSub = exports.getSub; const getSub = exports.getSub;
exports.getSub = async function(key, sub) { exports.getSub = async function (key, sub) {
let result = await getSub(key, sub); const result = await getSub(key, sub);
return (result === undefined) ? null : result; return (result === undefined) ? null : result;
}; };
@ -70,7 +70,7 @@ exports.init = function() {
resolve(); resolve();
}); });
}); });
} };
exports.shutdown = async (hookName, context) => { exports.shutdown = async (hookName, context) => {
await exports.doShutdown(); await exports.doShutdown();

View file

@ -18,52 +18,48 @@
* limitations under the License. * limitations under the License.
*/ */
var customError = require("../utils/customError"); const customError = require('../utils/customError');
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; const randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
var db = require("./DB"); const db = require('./DB');
var padManager = require("./PadManager"); const padManager = require('./PadManager');
var sessionManager = require("./SessionManager"); const sessionManager = require('./SessionManager');
exports.listAllGroups = async function() { exports.listAllGroups = async function () {
let groups = await db.get("groups"); let groups = await db.get('groups');
groups = groups || {}; groups = groups || {};
let groupIDs = Object.keys(groups); const groupIDs = Object.keys(groups);
return { groupIDs }; return {groupIDs};
} };
exports.deleteGroup = async function(groupID) { exports.deleteGroup = async function (groupID) {
let group = await db.get("group:" + groupID); const group = await db.get(`group:${groupID}`);
// ensure group exists // ensure group exists
if (group == null) { if (group == null) {
// group does not exist // group does not exist
throw new customError("groupID does not exist", "apierror"); throw new customError('groupID does not exist', 'apierror');
} }
// iterate through all pads of this group and delete them (in parallel) // iterate through all pads of this group and delete them (in parallel)
await Promise.all(Object.keys(group.pads).map(padID => { await Promise.all(Object.keys(group.pads).map((padID) => padManager.getPad(padID).then((pad) => pad.remove())));
return padManager.getPad(padID).then(pad => pad.remove());
}));
// iterate through group2sessions and delete all sessions // iterate through group2sessions and delete all sessions
let group2sessions = await db.get("group2sessions:" + groupID); const group2sessions = await db.get(`group2sessions:${groupID}`);
let sessions = group2sessions ? group2sessions.sessionIDs : {}; const sessions = group2sessions ? group2sessions.sessionIDs : {};
// loop through all sessions and delete them (in parallel) // loop through all sessions and delete them (in parallel)
await Promise.all(Object.keys(sessions).map(session => { await Promise.all(Object.keys(sessions).map((session) => sessionManager.deleteSession(session)));
return sessionManager.deleteSession(session);
}));
// remove group and group2sessions entry // remove group and group2sessions entry
await db.remove("group2sessions:" + groupID); await db.remove(`group2sessions:${groupID}`);
await db.remove("group:" + groupID); await db.remove(`group:${groupID}`);
// unlist the group // unlist the group
let groups = await exports.listAllGroups(); let groups = await exports.listAllGroups();
groups = groups ? groups.groupIDs : []; groups = groups ? groups.groupIDs : [];
let index = groups.indexOf(groupID); const index = groups.indexOf(groupID);
if (index === -1) { if (index === -1) {
// it's not listed // it's not listed
@ -75,102 +71,102 @@ exports.deleteGroup = async function(groupID) {
groups.splice(index, 1); groups.splice(index, 1);
// regenerate group list // regenerate group list
var newGroups = {}; const newGroups = {};
groups.forEach(group => newGroups[group] = 1); groups.forEach((group) => newGroups[group] = 1);
await db.set("groups", newGroups); await db.set('groups', newGroups);
} };
exports.doesGroupExist = async function(groupID) { exports.doesGroupExist = async function (groupID) {
// try to get the group entry // try to get the group entry
let group = await db.get("group:" + groupID); const group = await db.get(`group:${groupID}`);
return (group != null); return (group != null);
} };
exports.createGroup = async function() { exports.createGroup = async function () {
// search for non existing groupID // search for non existing groupID
var groupID = "g." + randomString(16); const groupID = `g.${randomString(16)}`;
// create the group // create the group
await db.set("group:" + groupID, {pads: {}}); await db.set(`group:${groupID}`, {pads: {}});
// list the group // list the group
let groups = await exports.listAllGroups(); let groups = await exports.listAllGroups();
groups = groups? groups.groupIDs : []; groups = groups ? groups.groupIDs : [];
groups.push(groupID); groups.push(groupID);
// regenerate group list // regenerate group list
var newGroups = {}; const newGroups = {};
groups.forEach(group => newGroups[group] = 1); groups.forEach((group) => newGroups[group] = 1);
await db.set("groups", newGroups); await db.set('groups', newGroups);
return { groupID }; return {groupID};
} };
exports.createGroupIfNotExistsFor = async function(groupMapper) { exports.createGroupIfNotExistsFor = async function (groupMapper) {
// ensure mapper is optional // ensure mapper is optional
if (typeof groupMapper !== "string") { if (typeof groupMapper !== 'string') {
throw new customError("groupMapper is not a string", "apierror"); throw new customError('groupMapper is not a string', 'apierror');
} }
// try to get a group for this mapper // try to get a group for this mapper
let groupID = await db.get("mapper2group:" + groupMapper); const groupID = await db.get(`mapper2group:${groupMapper}`);
if (groupID) { if (groupID) {
// there is a group for this mapper // there is a group for this mapper
let exists = await exports.doesGroupExist(groupID); const exists = await exports.doesGroupExist(groupID);
if (exists) return { groupID }; if (exists) return {groupID};
} }
// hah, the returned group doesn't exist, let's create one // hah, the returned group doesn't exist, let's create one
let result = await exports.createGroup(); const result = await exports.createGroup();
// create the mapper entry for this group // create the mapper entry for this group
await db.set("mapper2group:" + groupMapper, result.groupID); await db.set(`mapper2group:${groupMapper}`, result.groupID);
return result; return result;
} };
exports.createGroupPad = async function(groupID, padName, text) { exports.createGroupPad = async function (groupID, padName, text) {
// create the padID // create the padID
let padID = groupID + "$" + padName; const padID = `${groupID}$${padName}`;
// ensure group exists // ensure group exists
let groupExists = await exports.doesGroupExist(groupID); const groupExists = await exports.doesGroupExist(groupID);
if (!groupExists) { if (!groupExists) {
throw new customError("groupID does not exist", "apierror"); throw new customError('groupID does not exist', 'apierror');
} }
// ensure pad doesn't exist already // ensure pad doesn't exist already
let padExists = await padManager.doesPadExists(padID); const padExists = await padManager.doesPadExists(padID);
if (padExists) { if (padExists) {
// pad exists already // pad exists already
throw new customError("padName does already exist", "apierror"); throw new customError('padName does already exist', 'apierror');
} }
// create the pad // create the pad
await padManager.getPad(padID, text); await padManager.getPad(padID, text);
//create an entry in the group for this pad // create an entry in the group for this pad
await db.setSub("group:" + groupID, ["pads", padID], 1); await db.setSub(`group:${groupID}`, ['pads', padID], 1);
return { padID }; return {padID};
} };
exports.listPads = async function(groupID) { exports.listPads = async function (groupID) {
let exists = await exports.doesGroupExist(groupID); const exists = await exports.doesGroupExist(groupID);
// ensure the group exists // ensure the group exists
if (!exists) { if (!exists) {
throw new customError("groupID does not exist", "apierror"); throw new customError('groupID does not exist', 'apierror');
} }
// group exists, let's get the pads // group exists, let's get the pads
let result = await db.getSub("group:" + groupID, ["pads"]); const result = await db.getSub(`group:${groupID}`, ['pads']);
let padIDs = Object.keys(result); const padIDs = Object.keys(result);
return { padIDs }; return {padIDs};
} };

View file

@ -3,36 +3,36 @@
*/ */
var Changeset = require("ep_etherpad-lite/static/js/Changeset"); const Changeset = require('ep_etherpad-lite/static/js/Changeset');
var AttributePool = require("ep_etherpad-lite/static/js/AttributePool"); const AttributePool = require('ep_etherpad-lite/static/js/AttributePool');
var db = require("./DB"); const db = require('./DB');
var settings = require('../utils/Settings'); const settings = require('../utils/Settings');
var authorManager = require("./AuthorManager"); const authorManager = require('./AuthorManager');
var padManager = require("./PadManager"); const padManager = require('./PadManager');
var padMessageHandler = require("../handler/PadMessageHandler"); const padMessageHandler = require('../handler/PadMessageHandler');
var groupManager = require("./GroupManager"); const groupManager = require('./GroupManager');
var customError = require("../utils/customError"); const customError = require('../utils/customError');
var readOnlyManager = require("./ReadOnlyManager"); const readOnlyManager = require('./ReadOnlyManager');
var crypto = require("crypto"); const crypto = require('crypto');
var randomString = require("../utils/randomstring"); const randomString = require('../utils/randomstring');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var promises = require('../utils/promises') const promises = require('../utils/promises');
// serialization/deserialization attributes // serialization/deserialization attributes
var attributeBlackList = ["id"]; const attributeBlackList = ['id'];
var jsonableList = ["pool"]; const jsonableList = ['pool'];
/** /**
* Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces * Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces
* @param txt * @param txt
*/ */
exports.cleanText = function (txt) { exports.cleanText = function (txt) {
return txt.replace(/\r\n/g,'\n').replace(/\r/g,'\n').replace(/\t/g, ' ').replace(/\xa0/g, ' '); return txt.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\t/g, ' ').replace(/\xa0/g, ' ');
}; };
let Pad = function Pad(id) { const Pad = function Pad(id) {
this.atext = Changeset.makeAText("\n"); this.atext = Changeset.makeAText('\n');
this.pool = new AttributePool(); this.pool = new AttributePool();
this.head = -1; this.head = -1;
this.chatHead = -1; this.chatHead = -1;
@ -56,13 +56,11 @@ Pad.prototype.getSavedRevisionsNumber = function getSavedRevisionsNumber() {
}; };
Pad.prototype.getSavedRevisionsList = function getSavedRevisionsList() { Pad.prototype.getSavedRevisionsList = function getSavedRevisionsList() {
var savedRev = new Array(); const savedRev = new Array();
for (var rev in this.savedRevisions) { for (const rev in this.savedRevisions) {
savedRev.push(this.savedRevisions[rev].revNum); savedRev.push(this.savedRevisions[rev].revNum);
} }
savedRev.sort(function(a, b) { savedRev.sort((a, b) => a - b);
return a - b;
});
return savedRev; return savedRev;
}; };
@ -75,12 +73,12 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
author = ''; author = '';
} }
var newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool); const newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool);
Changeset.copyAText(newAText, this.atext); Changeset.copyAText(newAText, this.atext);
var newRev = ++this.head; const newRev = ++this.head;
var newRevData = {}; const newRevData = {};
newRevData.changeset = aChangeset; newRevData.changeset = aChangeset;
newRevData.meta = {}; newRevData.meta = {};
newRevData.meta.author = author; newRevData.meta.author = author;
@ -97,7 +95,7 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
} }
const p = [ const p = [
db.set('pad:' + this.id + ':revs:' + newRev, newRevData), db.set(`pad:${this.id}:revs:${newRev}`, newRevData),
this.saveToDatabase(), this.saveToDatabase(),
]; ];
@ -107,9 +105,9 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
} }
if (this.head == 0) { if (this.head == 0) {
hooks.callAll("padCreate", {'pad':this, 'author': author}); hooks.callAll('padCreate', {pad: this, author});
} else { } else {
hooks.callAll("padUpdate", {'pad':this, 'author': author, 'revs': newRev, 'changeset': aChangeset}); hooks.callAll('padUpdate', {pad: this, author, revs: newRev, changeset: aChangeset});
} }
await Promise.all(p); await Promise.all(p);
@ -117,10 +115,10 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
// save all attributes to the database // save all attributes to the database
Pad.prototype.saveToDatabase = async function saveToDatabase() { Pad.prototype.saveToDatabase = async function saveToDatabase() {
var dbObject = {}; const dbObject = {};
for (var attr in this) { for (const attr in this) {
if (typeof this[attr] === "function") continue; if (typeof this[attr] === 'function') continue;
if (attributeBlackList.indexOf(attr) !== -1) continue; if (attributeBlackList.indexOf(attr) !== -1) continue;
dbObject[attr] = this[attr]; dbObject[attr] = this[attr];
@ -130,32 +128,32 @@ Pad.prototype.saveToDatabase = async function saveToDatabase() {
} }
} }
await db.set('pad:' + this.id, dbObject); await db.set(`pad:${this.id}`, dbObject);
} };
// get time of last edit (changeset application) // get time of last edit (changeset application)
Pad.prototype.getLastEdit = function getLastEdit() { Pad.prototype.getLastEdit = function getLastEdit() {
var revNum = this.getHeadRevisionNumber(); const revNum = this.getHeadRevisionNumber();
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "timestamp"]); return db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'timestamp']);
} };
Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum) { Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum) {
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["changeset"]); return db.getSub(`pad:${this.id}:revs:${revNum}`, ['changeset']);
} };
Pad.prototype.getRevisionAuthor = function getRevisionAuthor(revNum) { Pad.prototype.getRevisionAuthor = function getRevisionAuthor(revNum) {
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "author"]); return db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'author']);
} };
Pad.prototype.getRevisionDate = function getRevisionDate(revNum) { Pad.prototype.getRevisionDate = function getRevisionDate(revNum) {
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "timestamp"]); return db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'timestamp']);
} };
Pad.prototype.getAllAuthors = function getAllAuthors() { Pad.prototype.getAllAuthors = function getAllAuthors() {
var authors = []; const authors = [];
for(var key in this.pool.numToAttrib) { for (const key in this.pool.numToAttrib) {
if (this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "") { if (this.pool.numToAttrib[key][0] == 'author' && this.pool.numToAttrib[key][1] != '') {
authors.push(this.pool.numToAttrib[key][1]); authors.push(this.pool.numToAttrib[key][1]);
} }
} }
@ -164,63 +162,59 @@ Pad.prototype.getAllAuthors = function getAllAuthors() {
}; };
Pad.prototype.getInternalRevisionAText = async function getInternalRevisionAText(targetRev) { Pad.prototype.getInternalRevisionAText = async function getInternalRevisionAText(targetRev) {
let keyRev = this.getKeyRevisionNumber(targetRev); const keyRev = this.getKeyRevisionNumber(targetRev);
// find out which changesets are needed // find out which changesets are needed
let neededChangesets = []; const neededChangesets = [];
for (let curRev = keyRev; curRev < targetRev; ) { for (let curRev = keyRev; curRev < targetRev;) {
neededChangesets.push(++curRev); neededChangesets.push(++curRev);
} }
// get all needed data out of the database // get all needed data out of the database
// start to get the atext of the key revision // start to get the atext of the key revision
let p_atext = db.getSub("pad:" + this.id + ":revs:" + keyRev, ["meta", "atext"]); const p_atext = db.getSub(`pad:${this.id}:revs:${keyRev}`, ['meta', 'atext']);
// get all needed changesets // get all needed changesets
let changesets = []; const changesets = [];
await Promise.all(neededChangesets.map(item => { await Promise.all(neededChangesets.map((item) => this.getRevisionChangeset(item).then((changeset) => {
return this.getRevisionChangeset(item).then(changeset => { changesets[item] = changeset;
changesets[item] = changeset; })));
});
}));
// we should have the atext by now // we should have the atext by now
let atext = await p_atext; let atext = await p_atext;
atext = Changeset.cloneAText(atext); atext = Changeset.cloneAText(atext);
// apply all changesets to the key changeset // apply all changesets to the key changeset
let apool = this.apool(); const apool = this.apool();
for (let curRev = keyRev; curRev < targetRev; ) { for (let curRev = keyRev; curRev < targetRev;) {
let cs = changesets[++curRev]; const cs = changesets[++curRev];
atext = Changeset.applyToAText(cs, atext, apool); atext = Changeset.applyToAText(cs, atext, apool);
} }
return atext; return atext;
} };
Pad.prototype.getRevision = function getRevisionChangeset(revNum) { Pad.prototype.getRevision = function getRevisionChangeset(revNum) {
return db.get("pad:" + this.id + ":revs:" + revNum); return db.get(`pad:${this.id}:revs:${revNum}`);
} };
Pad.prototype.getAllAuthorColors = async function getAllAuthorColors() { Pad.prototype.getAllAuthorColors = async function getAllAuthorColors() {
let authors = this.getAllAuthors(); const authors = this.getAllAuthors();
let returnTable = {}; const returnTable = {};
let colorPalette = authorManager.getColorPalette(); const colorPalette = authorManager.getColorPalette();
await Promise.all(authors.map(author => { await Promise.all(authors.map((author) => authorManager.getAuthorColorId(author).then((colorId) => {
return authorManager.getAuthorColorId(author).then(colorId => { // colorId might be a hex color or an number out of the palette
// colorId might be a hex color or an number out of the palette returnTable[author] = colorPalette[colorId] || colorId;
returnTable[author] = colorPalette[colorId] || colorId; })));
});
}));
return returnTable; return returnTable;
} };
Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, endRev) { Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, endRev) {
startRev = parseInt(startRev, 10); startRev = parseInt(startRev, 10);
var head = this.getHeadRevisionNumber(); const head = this.getHeadRevisionNumber();
endRev = endRev ? parseInt(endRev, 10) : head; endRev = endRev ? parseInt(endRev, 10) : head;
if (isNaN(startRev) || startRev < 0 || startRev > head) { if (isNaN(startRev) || startRev < 0 || startRev > head) {
@ -234,7 +228,7 @@ Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, e
} }
if (startRev !== null && endRev !== null) { if (startRev !== null && endRev !== null) {
return { startRev: startRev , endRev: endRev } return {startRev, endRev};
} }
return null; return null;
}; };
@ -251,16 +245,16 @@ Pad.prototype.setText = async function setText(newText) {
// clean the new text // clean the new text
newText = exports.cleanText(newText); newText = exports.cleanText(newText);
var oldText = this.text(); const oldText = this.text();
// create the changeset // create the changeset
// We want to ensure the pad still ends with a \n, but otherwise keep // We want to ensure the pad still ends with a \n, but otherwise keep
// getText() and setText() consistent. // getText() and setText() consistent.
var changeset; let changeset;
if (newText[newText.length - 1] == '\n') { if (newText[newText.length - 1] == '\n') {
changeset = Changeset.makeSplice(oldText, 0, oldText.length, newText); changeset = Changeset.makeSplice(oldText, 0, oldText.length, newText);
} else { } else {
changeset = Changeset.makeSplice(oldText, 0, oldText.length-1, newText); changeset = Changeset.makeSplice(oldText, 0, oldText.length - 1, newText);
} }
// append the changeset // append the changeset
@ -271,10 +265,10 @@ Pad.prototype.appendText = async function appendText(newText) {
// clean the new text // clean the new text
newText = exports.cleanText(newText); newText = exports.cleanText(newText);
var oldText = this.text(); const oldText = this.text();
// create the changeset // create the changeset
var changeset = Changeset.makeSplice(oldText, oldText.length, 0, newText); const changeset = Changeset.makeSplice(oldText, oldText.length, 0, newText);
// append the changeset // append the changeset
await this.appendRevision(changeset); await this.appendRevision(changeset);
@ -284,14 +278,14 @@ Pad.prototype.appendChatMessage = async function appendChatMessage(text, userId,
this.chatHead++; this.chatHead++;
// save the chat entry in the database // save the chat entry in the database
await Promise.all([ await Promise.all([
db.set('pad:' + this.id + ':chat:' + this.chatHead, {text, userId, time}), db.set(`pad:${this.id}:chat:${this.chatHead}`, {text, userId, time}),
this.saveToDatabase(), this.saveToDatabase(),
]); ]);
}; };
Pad.prototype.getChatMessage = async function getChatMessage(entryNum) { Pad.prototype.getChatMessage = async function getChatMessage(entryNum) {
// get the chat entry // get the chat entry
let entry = await db.get("pad:" + this.id + ":chat:" + entryNum); const entry = await db.get(`pad:${this.id}:chat:${entryNum}`);
// get the authorName if the entry exists // get the authorName if the entry exists
if (entry != null) { if (entry != null) {
@ -302,49 +296,45 @@ Pad.prototype.getChatMessage = async function getChatMessage(entryNum) {
}; };
Pad.prototype.getChatMessages = async function getChatMessages(start, end) { Pad.prototype.getChatMessages = async function getChatMessages(start, end) {
// collect the numbers of chat entries and in which order we need them // collect the numbers of chat entries and in which order we need them
let neededEntries = []; const neededEntries = [];
for (let order = 0, entryNum = start; entryNum <= end; ++order, ++entryNum) { for (let order = 0, entryNum = start; entryNum <= end; ++order, ++entryNum) {
neededEntries.push({ entryNum, order }); neededEntries.push({entryNum, order});
} }
// get all entries out of the database // get all entries out of the database
let entries = []; const entries = [];
await Promise.all(neededEntries.map(entryObject => { await Promise.all(neededEntries.map((entryObject) => this.getChatMessage(entryObject.entryNum).then((entry) => {
return this.getChatMessage(entryObject.entryNum).then(entry => { entries[entryObject.order] = entry;
entries[entryObject.order] = entry; })));
});
}));
// sort out broken chat entries // sort out broken chat entries
// it looks like in happened in the past that the chat head was // it looks like in happened in the past that the chat head was
// incremented, but the chat message wasn't added // incremented, but the chat message wasn't added
let cleanedEntries = entries.filter(entry => { const cleanedEntries = entries.filter((entry) => {
let pass = (entry != null); const pass = (entry != null);
if (!pass) { if (!pass) {
console.warn("WARNING: Found broken chat entry in pad " + this.id); console.warn(`WARNING: Found broken chat entry in pad ${this.id}`);
} }
return pass; return pass;
}); });
return cleanedEntries; return cleanedEntries;
} };
Pad.prototype.init = async function init(text) { Pad.prototype.init = async function init(text) {
// replace text with default text if text isn't set // replace text with default text if text isn't set
if (text == null) { if (text == null) {
text = settings.defaultPadText; text = settings.defaultPadText;
} }
// try to load the pad // try to load the pad
let value = await db.get("pad:" + this.id); const value = await db.get(`pad:${this.id}`);
// if this pad exists, load it // if this pad exists, load it
if (value != null) { if (value != null) {
// copy all attr. To a transfrom via fromJsonable if necassary // copy all attr. To a transfrom via fromJsonable if necassary
for (var attr in value) { for (const attr in value) {
if (jsonableList.indexOf(attr) !== -1) { if (jsonableList.indexOf(attr) !== -1) {
this[attr] = this[attr].fromJsonable(value[attr]); this[attr] = this[attr].fromJsonable(value[attr]);
} else { } else {
@ -353,17 +343,17 @@ Pad.prototype.init = async function init(text) {
} }
} else { } else {
// this pad doesn't exist, so create it // this pad doesn't exist, so create it
let firstChangeset = Changeset.makeSplice("\n", 0, 0, exports.cleanText(text)); const firstChangeset = Changeset.makeSplice('\n', 0, 0, exports.cleanText(text));
await this.appendRevision(firstChangeset, ''); await this.appendRevision(firstChangeset, '');
} }
hooks.callAll("padLoad", { 'pad': this }); hooks.callAll('padLoad', {pad: this});
} };
Pad.prototype.copy = async function copy(destinationID, force) { Pad.prototype.copy = async function copy(destinationID, force) {
let destGroupID; let destGroupID;
let sourceID = this.id; const sourceID = this.id;
// Kick everyone from this pad. // Kick everyone from this pad.
// This was commented due to https://github.com/ether/etherpad-lite/issues/3183. // This was commented due to https://github.com/ether/etherpad-lite/issues/3183.
@ -380,32 +370,28 @@ Pad.prototype.copy = async function copy(destinationID, force) {
// if force is true and already exists a Pad with the same id, remove that Pad // if force is true and already exists a Pad with the same id, remove that Pad
await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force); await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force);
} catch(err) { } catch (err) {
throw err; throw err;
} }
// copy the 'pad' entry // copy the 'pad' entry
let pad = await db.get("pad:" + sourceID); const pad = await db.get(`pad:${sourceID}`);
db.set("pad:" + destinationID, pad); db.set(`pad:${destinationID}`, pad);
// copy all relations in parallel // copy all relations in parallel
let promises = []; const promises = [];
// copy all chat messages // copy all chat messages
let chatHead = this.chatHead; const chatHead = this.chatHead;
for (let i = 0; i <= chatHead; ++i) { for (let i = 0; i <= chatHead; ++i) {
let p = db.get("pad:" + sourceID + ":chat:" + i).then(chat => { const p = db.get(`pad:${sourceID}:chat:${i}`).then((chat) => db.set(`pad:${destinationID}:chat:${i}`, chat));
return db.set("pad:" + destinationID + ":chat:" + i, chat);
});
promises.push(p); promises.push(p);
} }
// copy all revisions // copy all revisions
let revHead = this.head; const revHead = this.head;
for (let i = 0; i <= revHead; ++i) { for (let i = 0; i <= revHead; ++i) {
let p = db.get("pad:" + sourceID + ":revs:" + i).then(rev => { const p = db.get(`pad:${sourceID}:revs:${i}`).then((rev) => db.set(`pad:${destinationID}:revs:${i}`, rev));
return db.set("pad:" + destinationID + ":revs:" + i, rev);
});
promises.push(p); promises.push(p);
} }
@ -416,70 +402,69 @@ Pad.prototype.copy = async function copy(destinationID, force) {
// Group pad? Add it to the group's list // Group pad? Add it to the group's list
if (destGroupID) { if (destGroupID) {
await db.setSub("group:" + destGroupID, ["pads", destinationID], 1); await db.setSub(`group:${destGroupID}`, ['pads', destinationID], 1);
} }
// delay still necessary? // delay still necessary?
await new Promise(resolve => setTimeout(resolve, 10)); await new Promise((resolve) => setTimeout(resolve, 10));
// Initialize the new pad (will update the listAllPads cache) // Initialize the new pad (will update the listAllPads cache)
await padManager.getPad(destinationID, null); // this runs too early. await padManager.getPad(destinationID, null); // this runs too early.
// let the plugins know the pad was copied // let the plugins know the pad was copied
hooks.callAll('padCopy', { 'originalPad': this, 'destinationID': destinationID }); hooks.callAll('padCopy', {originalPad: this, destinationID});
return { padID: destinationID }; return {padID: destinationID};
} };
Pad.prototype.checkIfGroupExistAndReturnIt = async function checkIfGroupExistAndReturnIt(destinationID) { Pad.prototype.checkIfGroupExistAndReturnIt = async function checkIfGroupExistAndReturnIt(destinationID) {
let destGroupID = false; let destGroupID = false;
if (destinationID.indexOf("$") >= 0) { if (destinationID.indexOf('$') >= 0) {
destGroupID = destinationID.split('$')[0];
destGroupID = destinationID.split("$")[0] const groupExists = await groupManager.doesGroupExist(destGroupID);
let groupExists = await groupManager.doesGroupExist(destGroupID);
// group does not exist // group does not exist
if (!groupExists) { if (!groupExists) {
throw new customError("groupID does not exist for destinationID", "apierror"); throw new customError('groupID does not exist for destinationID', 'apierror');
} }
} }
return destGroupID; return destGroupID;
} };
Pad.prototype.removePadIfForceIsTrueAndAlreadyExist = async function removePadIfForceIsTrueAndAlreadyExist(destinationID, force) { Pad.prototype.removePadIfForceIsTrueAndAlreadyExist = async function removePadIfForceIsTrueAndAlreadyExist(destinationID, force) {
// if the pad exists, we should abort, unless forced. // if the pad exists, we should abort, unless forced.
let exists = await padManager.doesPadExist(destinationID); const exists = await padManager.doesPadExist(destinationID);
// allow force to be a string // allow force to be a string
if (typeof force === "string") { if (typeof force === 'string') {
force = (force.toLowerCase() === "true"); force = (force.toLowerCase() === 'true');
} else { } else {
force = !!force; force = !!force;
} }
if (exists) { if (exists) {
if (!force) { if (!force) {
console.error("erroring out without force"); console.error('erroring out without force');
throw new customError("destinationID already exists", "apierror"); throw new customError('destinationID already exists', 'apierror');
} }
// exists and forcing // exists and forcing
let pad = await padManager.getPad(destinationID); const pad = await padManager.getPad(destinationID);
await pad.remove(); await pad.remove();
} }
} };
Pad.prototype.copyAuthorInfoToDestinationPad = function copyAuthorInfoToDestinationPad(destinationID) { Pad.prototype.copyAuthorInfoToDestinationPad = function copyAuthorInfoToDestinationPad(destinationID) {
// add the new sourcePad to all authors who contributed to the old one // add the new sourcePad to all authors who contributed to the old one
this.getAllAuthors().forEach(authorID => { this.getAllAuthors().forEach((authorID) => {
authorManager.addPad(authorID, destinationID); authorManager.addPad(authorID, destinationID);
}); });
} };
Pad.prototype.copyPadWithoutHistory = async function copyPadWithoutHistory(destinationID, force) { Pad.prototype.copyPadWithoutHistory = async function copyPadWithoutHistory(destinationID, force) {
let destGroupID; let destGroupID;
let sourceID = this.id; const sourceID = this.id;
// flush the source pad // flush the source pad
this.saveToDatabase(); this.saveToDatabase();
@ -490,53 +475,53 @@ Pad.prototype.copyPadWithoutHistory = async function copyPadWithoutHistory(desti
// if force is true and already exists a Pad with the same id, remove that Pad // if force is true and already exists a Pad with the same id, remove that Pad
await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force); await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force);
} catch(err) { } catch (err) {
throw err; throw err;
} }
let sourcePad = await padManager.getPad(sourceID); const sourcePad = await padManager.getPad(sourceID);
// add the new sourcePad to all authors who contributed to the old one // add the new sourcePad to all authors who contributed to the old one
this.copyAuthorInfoToDestinationPad(destinationID); this.copyAuthorInfoToDestinationPad(destinationID);
// Group pad? Add it to the group's list // Group pad? Add it to the group's list
if (destGroupID) { if (destGroupID) {
await db.setSub("group:" + destGroupID, ["pads", destinationID], 1); await db.setSub(`group:${destGroupID}`, ['pads', destinationID], 1);
} }
// initialize the pad with a new line to avoid getting the defaultText // initialize the pad with a new line to avoid getting the defaultText
let newPad = await padManager.getPad(destinationID, '\n'); const newPad = await padManager.getPad(destinationID, '\n');
let oldAText = this.atext; const oldAText = this.atext;
let newPool = newPad.pool; const newPool = newPad.pool;
newPool.fromJsonable(sourcePad.pool.toJsonable()); // copy that sourceId pool to the new pad newPool.fromJsonable(sourcePad.pool.toJsonable()); // copy that sourceId pool to the new pad
// based on Changeset.makeSplice // based on Changeset.makeSplice
let assem = Changeset.smartOpAssembler(); const assem = Changeset.smartOpAssembler();
assem.appendOpWithText('=', ''); assem.appendOpWithText('=', '');
Changeset.appendATextToAssembler(oldAText, assem); Changeset.appendATextToAssembler(oldAText, assem);
assem.endDocument(); assem.endDocument();
// although we have instantiated the newPad with '\n', an additional '\n' is // although we have instantiated the newPad with '\n', an additional '\n' is
// added internally, so the pad text on the revision 0 is "\n\n" // added internally, so the pad text on the revision 0 is "\n\n"
let oldLength = 2; const oldLength = 2;
let newLength = assem.getLengthChange(); const newLength = assem.getLengthChange();
let newText = oldAText.text; const newText = oldAText.text;
// create a changeset that removes the previous text and add the newText with // create a changeset that removes the previous text and add the newText with
// all atributes present on the source pad // all atributes present on the source pad
let changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText); const changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
newPad.appendRevision(changeset); newPad.appendRevision(changeset);
hooks.callAll('padCopy', { 'originalPad': this, 'destinationID': destinationID }); hooks.callAll('padCopy', {originalPad: this, destinationID});
return { padID: destinationID }; return {padID: destinationID};
} };
Pad.prototype.remove = async function remove() { Pad.prototype.remove = async function remove() {
var padID = this.id; const padID = this.id;
const p = []; const p = [];
// kick everyone from this pad // kick everyone from this pad
@ -548,45 +533,44 @@ Pad.prototype.remove = async function remove() {
// run to completion // run to completion
// is it a group pad? -> delete the entry of this pad in the group // is it a group pad? -> delete the entry of this pad in the group
if (padID.indexOf("$") >= 0) { if (padID.indexOf('$') >= 0) {
// it is a group pad // it is a group pad
let groupID = padID.substring(0, padID.indexOf("$")); const groupID = padID.substring(0, padID.indexOf('$'));
let group = await db.get("group:" + groupID); const group = await db.get(`group:${groupID}`);
// remove the pad entry // remove the pad entry
delete group.pads[padID]; delete group.pads[padID];
// set the new value // set the new value
p.push(db.set('group:' + groupID, group)); p.push(db.set(`group:${groupID}`, group));
} }
// remove the readonly entries // remove the readonly entries
p.push(readOnlyManager.getReadOnlyId(padID).then(async (readonlyID) => { p.push(readOnlyManager.getReadOnlyId(padID).then(async (readonlyID) => {
await db.remove('readonly2pad:' + readonlyID); await db.remove(`readonly2pad:${readonlyID}`);
})); }));
p.push(db.remove('pad2readonly:' + padID)); p.push(db.remove(`pad2readonly:${padID}`));
// delete all chat messages // delete all chat messages
p.push(promises.timesLimit(this.chatHead + 1, 500, async (i) => { p.push(promises.timesLimit(this.chatHead + 1, 500, async (i) => {
await db.remove('pad:' + padID + ':chat:' + i, null); await db.remove(`pad:${padID}:chat:${i}`, null);
})); }));
// delete all revisions // delete all revisions
p.push(promises.timesLimit(this.head + 1, 500, async (i) => { p.push(promises.timesLimit(this.head + 1, 500, async (i) => {
await db.remove('pad:' + padID + ':revs:' + i, null); await db.remove(`pad:${padID}:revs:${i}`, null);
})); }));
// remove pad from all authors who contributed // remove pad from all authors who contributed
this.getAllAuthors().forEach(authorID => { this.getAllAuthors().forEach((authorID) => {
p.push(authorManager.removePad(authorID, padID)); p.push(authorManager.removePad(authorID, padID));
}); });
// delete the pad entry and delete pad from padManager // delete the pad entry and delete pad from padManager
p.push(padManager.removePad(padID)); p.push(padManager.removePad(padID));
hooks.callAll("padRemove", { padID }); hooks.callAll('padRemove', {padID});
await Promise.all(p); await Promise.all(p);
} };
// set in db // set in db
Pad.prototype.setPublicStatus = async function setPublicStatus(publicStatus) { Pad.prototype.setPublicStatus = async function setPublicStatus(publicStatus) {
@ -596,17 +580,17 @@ Pad.prototype.setPublicStatus = async function setPublicStatus(publicStatus) {
Pad.prototype.addSavedRevision = async function addSavedRevision(revNum, savedById, label) { Pad.prototype.addSavedRevision = async function addSavedRevision(revNum, savedById, label) {
// if this revision is already saved, return silently // if this revision is already saved, return silently
for (var i in this.savedRevisions) { for (const i in this.savedRevisions) {
if (this.savedRevisions[i] && this.savedRevisions[i].revNum === revNum) { if (this.savedRevisions[i] && this.savedRevisions[i].revNum === revNum) {
return; return;
} }
} }
// build the saved revision object // build the saved revision object
var savedRevision = {}; const savedRevision = {};
savedRevision.revNum = revNum; savedRevision.revNum = revNum;
savedRevision.savedById = savedById; savedRevision.savedById = savedById;
savedRevision.label = label || "Revision " + revNum; savedRevision.label = label || `Revision ${revNum}`;
savedRevision.timestamp = Date.now(); savedRevision.timestamp = Date.now();
savedRevision.id = randomString(10); savedRevision.id = randomString(10);
@ -618,4 +602,3 @@ Pad.prototype.addSavedRevision = async function addSavedRevision(revNum, savedBy
Pad.prototype.getSavedRevisions = function getSavedRevisions() { Pad.prototype.getSavedRevisions = function getSavedRevisions() {
return this.savedRevisions; return this.savedRevisions;
}; };

View file

@ -18,9 +18,9 @@
* limitations under the License. * limitations under the License.
*/ */
var customError = require("../utils/customError"); const customError = require('../utils/customError');
var Pad = require("../db/Pad").Pad; const Pad = require('../db/Pad').Pad;
var db = require("./DB"); const db = require('./DB');
/** /**
* A cache of all loaded Pads. * A cache of all loaded Pads.
@ -33,14 +33,14 @@ var db = require("./DB");
* If this is needed in other places, it would be wise to make this a prototype * If this is needed in other places, it would be wise to make this a prototype
* that's defined somewhere more sensible. * that's defined somewhere more sensible.
*/ */
var globalPads = { const globalPads = {
get: function(name) { return this[':'+name]; }, get(name) { return this[`:${name}`]; },
set: function(name, value) { set(name, value) {
this[':'+name] = value; this[`:${name}`] = value;
}, },
remove: function(name) { remove(name) {
delete this[':'+name]; delete this[`:${name}`];
} },
}; };
/** /**
@ -48,24 +48,24 @@ var globalPads = {
* *
* Updated without db access as new pads are created/old ones removed. * Updated without db access as new pads are created/old ones removed.
*/ */
let padList = { const padList = {
list: new Set(), list: new Set(),
cachedList: undefined, cachedList: undefined,
initiated: false, initiated: false,
init: async function() { async init() {
let dbData = await db.findKeys("pad:*", "*:*:*"); const dbData = await db.findKeys('pad:*', '*:*:*');
if (dbData != null) { if (dbData != null) {
this.initiated = true; this.initiated = true;
for (let val of dbData) { for (const val of dbData) {
this.addPad(val.replace(/^pad:/,""), false); this.addPad(val.replace(/^pad:/, ''), false);
} }
} }
return this; return this;
}, },
load: async function() { async load() {
if (!this.initiated) { if (!this.initiated) {
return this.init(); return this.init();
} }
@ -75,7 +75,7 @@ let padList = {
/** /**
* Returns all pads in alphabetical order as array. * Returns all pads in alphabetical order as array.
*/ */
getPads: async function() { async getPads() {
await this.load(); await this.load();
if (!this.cachedList) { if (!this.cachedList) {
@ -84,7 +84,7 @@ let padList = {
return this.cachedList; return this.cachedList;
}, },
addPad: function(name) { addPad(name) {
if (!this.initiated) return; if (!this.initiated) return;
if (!this.list.has(name)) { if (!this.list.has(name)) {
@ -92,14 +92,14 @@ let padList = {
this.cachedList = undefined; this.cachedList = undefined;
} }
}, },
removePad: function(name) { removePad(name) {
if (!this.initiated) return; if (!this.initiated) return;
if (this.list.has(name)) { if (this.list.has(name)) {
this.list.delete(name); this.list.delete(name);
this.cachedList = undefined; this.cachedList = undefined;
} }
} },
}; };
// initialises the all-knowing data structure // initialises the all-knowing data structure
@ -109,22 +109,22 @@ let padList = {
* @param id A String with the id of the pad * @param id A String with the id of the pad
* @param {Function} callback * @param {Function} callback
*/ */
exports.getPad = async function(id, text) { exports.getPad = async function (id, text) {
// check if this is a valid padId // check if this is a valid padId
if (!exports.isValidPadId(id)) { if (!exports.isValidPadId(id)) {
throw new customError(id + " is not a valid padId", "apierror"); throw new customError(`${id} is not a valid padId`, 'apierror');
} }
// check if this is a valid text // check if this is a valid text
if (text != null) { if (text != null) {
// check if text is a string // check if text is a string
if (typeof text != "string") { if (typeof text !== 'string') {
throw new customError("text is not a string", "apierror"); throw new customError('text is not a string', 'apierror');
} }
// check if text is less than 100k chars // check if text is less than 100k chars
if (text.length > 100000) { if (text.length > 100000) {
throw new customError("text must be less than 100k chars", "apierror"); throw new customError('text must be less than 100k chars', 'apierror');
} }
} }
@ -144,20 +144,20 @@ exports.getPad = async function(id, text) {
padList.addPad(id); padList.addPad(id);
return pad; return pad;
} };
exports.listAllPads = async function() { exports.listAllPads = async function () {
let padIDs = await padList.getPads(); const padIDs = await padList.getPads();
return { padIDs }; return {padIDs};
} };
// checks if a pad exists // checks if a pad exists
exports.doesPadExist = async function(padId) { exports.doesPadExist = async function (padId) {
let value = await db.get("pad:" + padId); const value = await db.get(`pad:${padId}`);
return (value != null && value.atext); return (value != null && value.atext);
} };
// alias for backwards compatibility // alias for backwards compatibility
exports.doesPadExists = exports.doesPadExist; exports.doesPadExists = exports.doesPadExist;
@ -168,42 +168,42 @@ exports.doesPadExists = exports.doesPadExist;
*/ */
const padIdTransforms = [ const padIdTransforms = [
[/\s+/g, '_'], [/\s+/g, '_'],
[/:+/g, '_'] [/:+/g, '_'],
]; ];
// returns a sanitized padId, respecting legacy pad id formats // returns a sanitized padId, respecting legacy pad id formats
exports.sanitizePadId = async function sanitizePadId(padId) { exports.sanitizePadId = async function sanitizePadId(padId) {
for (let i = 0, n = padIdTransforms.length; i < n; ++i) { for (let i = 0, n = padIdTransforms.length; i < n; ++i) {
let exists = await exports.doesPadExist(padId); const exists = await exports.doesPadExist(padId);
if (exists) { if (exists) {
return padId; return padId;
} }
let [from, to] = padIdTransforms[i]; const [from, to] = padIdTransforms[i];
padId = padId.replace(from, to); padId = padId.replace(from, to);
} }
// we're out of possible transformations, so just return it // we're out of possible transformations, so just return it
return padId; return padId;
} };
exports.isValidPadId = function(padId) { exports.isValidPadId = function (padId) {
return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId); return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
} };
/** /**
* Removes the pad from database and unloads it. * Removes the pad from database and unloads it.
*/ */
exports.removePad = async (padId) => { exports.removePad = async (padId) => {
const p = db.remove('pad:' + padId); const p = db.remove(`pad:${padId}`);
exports.unloadPad(padId); exports.unloadPad(padId);
padList.removePad(padId); padList.removePad(padId);
await p; await p;
} };
// removes a pad from the cache // removes a pad from the cache
exports.unloadPad = function(padId) { exports.unloadPad = function (padId) {
globalPads.remove(padId); globalPads.remove(padId);
} };

View file

@ -19,17 +19,17 @@
*/ */
var db = require("./DB"); const db = require('./DB');
var randomString = require("../utils/randomstring"); const randomString = require('../utils/randomstring');
/** /**
* checks if the id pattern matches a read-only pad id * checks if the id pattern matches a read-only pad id
* @param {String} the pad's id * @param {String} the pad's id
*/ */
exports.isReadOnlyId = function(id) { exports.isReadOnlyId = function (id) {
return id.indexOf("r.") === 0; return id.indexOf('r.') === 0;
} };
/** /**
* returns a read only id for a pad * returns a read only id for a pad
@ -37,36 +37,36 @@ exports.isReadOnlyId = function(id) {
*/ */
exports.getReadOnlyId = async function (padId) { exports.getReadOnlyId = async function (padId) {
// check if there is a pad2readonly entry // check if there is a pad2readonly entry
let readOnlyId = await db.get("pad2readonly:" + padId); let readOnlyId = await db.get(`pad2readonly:${padId}`);
// there is no readOnly Entry in the database, let's create one // there is no readOnly Entry in the database, let's create one
if (readOnlyId == null) { if (readOnlyId == null) {
readOnlyId = "r." + randomString(16); readOnlyId = `r.${randomString(16)}`;
db.set("pad2readonly:" + padId, readOnlyId); db.set(`pad2readonly:${padId}`, readOnlyId);
db.set("readonly2pad:" + readOnlyId, padId); db.set(`readonly2pad:${readOnlyId}`, padId);
} }
return readOnlyId; return readOnlyId;
} };
/** /**
* returns the padId for a read only id * returns the padId for a read only id
* @param {String} readOnlyId read only id * @param {String} readOnlyId read only id
*/ */
exports.getPadId = function(readOnlyId) { exports.getPadId = function (readOnlyId) {
return db.get("readonly2pad:" + readOnlyId); return db.get(`readonly2pad:${readOnlyId}`);
} };
/** /**
* returns the padId and readonlyPadId in an object for any id * returns the padId and readonlyPadId in an object for any id
* @param {String} padIdOrReadonlyPadId read only id or real pad id * @param {String} padIdOrReadonlyPadId read only id or real pad id
*/ */
exports.getIds = async function(id) { exports.getIds = async function (id) {
let readonly = (id.indexOf("r.") === 0); const readonly = (id.indexOf('r.') === 0);
// Might be null, if this is an unknown read-only id // Might be null, if this is an unknown read-only id
let readOnlyPadId = readonly ? id : await exports.getReadOnlyId(id); const readOnlyPadId = readonly ? id : await exports.getReadOnlyId(id);
let padId = readonly ? await exports.getPadId(id) : id; const padId = readonly ? await exports.getPadId(id) : id;
return { readOnlyPadId, padId, readonly }; return {readOnlyPadId, padId, readonly};
} };

View file

@ -18,14 +18,14 @@
* limitations under the License. * limitations under the License.
*/ */
var authorManager = require("./AuthorManager"); const authorManager = require('./AuthorManager');
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js"); const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
var padManager = require("./PadManager"); const padManager = require('./PadManager');
var sessionManager = require("./SessionManager"); const sessionManager = require('./SessionManager');
var settings = require("../utils/Settings"); const settings = require('../utils/Settings');
const webaccess = require('../hooks/express/webaccess'); const webaccess = require('../hooks/express/webaccess');
var log4js = require('log4js'); const log4js = require('log4js');
var authLogger = log4js.getLogger("auth"); const authLogger = log4js.getLogger('auth');
const DENY = Object.freeze({accessStatus: 'deny'}); const DENY = Object.freeze({accessStatus: 'deny'});
@ -47,7 +47,7 @@ const DENY = Object.freeze({accessStatus: 'deny'});
* WARNING: Tokens and session IDs MUST be kept secret, otherwise users will be able to impersonate * WARNING: Tokens and session IDs MUST be kept secret, otherwise users will be able to impersonate
* each other (which might allow them to gain privileges). * each other (which might allow them to gain privileges).
*/ */
exports.checkAccess = async function(padID, sessionCookie, token, userSettings) { exports.checkAccess = async function (padID, sessionCookie, token, userSettings) {
if (!padID) { if (!padID) {
authLogger.debug('access denied: missing padID'); authLogger.debug('access denied: missing padID');
return DENY; return DENY;

View file

@ -18,12 +18,12 @@
* limitations under the License. * limitations under the License.
*/ */
var customError = require("../utils/customError"); const customError = require('../utils/customError');
const promises = require('../utils/promises'); const promises = require('../utils/promises');
var randomString = require("../utils/randomstring"); const randomString = require('../utils/randomstring');
var db = require("./DB"); const db = require('./DB');
var groupManager = require("./GroupManager"); const groupManager = require('./GroupManager');
var authorManager = require("./AuthorManager"); const authorManager = require('./AuthorManager');
/** /**
* Finds the author ID for a session with matching ID and group. * Finds the author ID for a session with matching ID and group.
@ -78,61 +78,61 @@ exports.findAuthorID = async (groupID, sessionCookie) => {
return sessionInfo.authorID; return sessionInfo.authorID;
}; };
exports.doesSessionExist = async function(sessionID) { exports.doesSessionExist = async function (sessionID) {
//check if the database entry of this session exists // check if the database entry of this session exists
let session = await db.get("session:" + sessionID); const session = await db.get(`session:${sessionID}`);
return (session !== null); return (session !== null);
} };
/** /**
* Creates a new session between an author and a group * Creates a new session between an author and a group
*/ */
exports.createSession = async function(groupID, authorID, validUntil) { exports.createSession = async function (groupID, authorID, validUntil) {
// check if the group exists // check if the group exists
let groupExists = await groupManager.doesGroupExist(groupID); const groupExists = await groupManager.doesGroupExist(groupID);
if (!groupExists) { if (!groupExists) {
throw new customError("groupID does not exist", "apierror"); throw new customError('groupID does not exist', 'apierror');
} }
// check if the author exists // check if the author exists
let authorExists = await authorManager.doesAuthorExist(authorID); const authorExists = await authorManager.doesAuthorExist(authorID);
if (!authorExists) { if (!authorExists) {
throw new customError("authorID does not exist", "apierror"); throw new customError('authorID does not exist', 'apierror');
} }
// try to parse validUntil if it's not a number // try to parse validUntil if it's not a number
if (typeof validUntil !== "number") { if (typeof validUntil !== 'number') {
validUntil = parseInt(validUntil); validUntil = parseInt(validUntil);
} }
// check it's a valid number // check it's a valid number
if (isNaN(validUntil)) { if (isNaN(validUntil)) {
throw new customError("validUntil is not a number", "apierror"); throw new customError('validUntil is not a number', 'apierror');
} }
// ensure this is not a negative number // ensure this is not a negative number
if (validUntil < 0) { if (validUntil < 0) {
throw new customError("validUntil is a negative number", "apierror"); throw new customError('validUntil is a negative number', 'apierror');
} }
// ensure this is not a float value // ensure this is not a float value
if (!is_int(validUntil)) { if (!is_int(validUntil)) {
throw new customError("validUntil is a float value", "apierror"); throw new customError('validUntil is a float value', 'apierror');
} }
// check if validUntil is in the future // check if validUntil is in the future
if (validUntil < Math.floor(Date.now() / 1000)) { if (validUntil < Math.floor(Date.now() / 1000)) {
throw new customError("validUntil is in the past", "apierror"); throw new customError('validUntil is in the past', 'apierror');
} }
// generate sessionID // generate sessionID
let sessionID = "s." + randomString(16); const sessionID = `s.${randomString(16)}`;
// set the session into the database // set the session into the database
await db.set("session:" + sessionID, {"groupID": groupID, "authorID": authorID, "validUntil": validUntil}); await db.set(`session:${sessionID}`, {groupID, authorID, validUntil});
// get the entry // get the entry
let group2sessions = await db.get("group2sessions:" + groupID); let group2sessions = await db.get(`group2sessions:${groupID}`);
/* /*
* In some cases, the db layer could return "undefined" as well as "null". * In some cases, the db layer could return "undefined" as well as "null".
@ -144,115 +144,115 @@ exports.createSession = async function(groupID, authorID, validUntil) {
*/ */
if (!group2sessions || !group2sessions.sessionIDs) { if (!group2sessions || !group2sessions.sessionIDs) {
// the entry doesn't exist so far, let's create it // the entry doesn't exist so far, let's create it
group2sessions = {sessionIDs : {}}; group2sessions = {sessionIDs: {}};
} }
// add the entry for this session // add the entry for this session
group2sessions.sessionIDs[sessionID] = 1; group2sessions.sessionIDs[sessionID] = 1;
// save the new element back // save the new element back
await db.set("group2sessions:" + groupID, group2sessions); await db.set(`group2sessions:${groupID}`, group2sessions);
// get the author2sessions entry // get the author2sessions entry
let author2sessions = await db.get("author2sessions:" + authorID); let author2sessions = await db.get(`author2sessions:${authorID}`);
if (author2sessions == null || author2sessions.sessionIDs == null) { if (author2sessions == null || author2sessions.sessionIDs == null) {
// the entry doesn't exist so far, let's create it // the entry doesn't exist so far, let's create it
author2sessions = {sessionIDs : {}}; author2sessions = {sessionIDs: {}};
} }
// add the entry for this session // add the entry for this session
author2sessions.sessionIDs[sessionID] = 1; author2sessions.sessionIDs[sessionID] = 1;
//save the new element back // save the new element back
await db.set("author2sessions:" + authorID, author2sessions); await db.set(`author2sessions:${authorID}`, author2sessions);
return { sessionID }; return {sessionID};
} };
exports.getSessionInfo = async function(sessionID) { exports.getSessionInfo = async function (sessionID) {
// check if the database entry of this session exists // check if the database entry of this session exists
let session = await db.get("session:" + sessionID); const session = await db.get(`session:${sessionID}`);
if (session == null) { if (session == null) {
// session does not exist // session does not exist
throw new customError("sessionID does not exist", "apierror"); throw new customError('sessionID does not exist', 'apierror');
} }
// everything is fine, return the sessioninfos // everything is fine, return the sessioninfos
return session; return session;
} };
/** /**
* Deletes a session * Deletes a session
*/ */
exports.deleteSession = async function(sessionID) { exports.deleteSession = async function (sessionID) {
// ensure that the session exists // ensure that the session exists
let session = await db.get("session:" + sessionID); const session = await db.get(`session:${sessionID}`);
if (session == null) { if (session == null) {
throw new customError("sessionID does not exist", "apierror"); throw new customError('sessionID does not exist', 'apierror');
} }
// everything is fine, use the sessioninfos // everything is fine, use the sessioninfos
let groupID = session.groupID; const groupID = session.groupID;
let authorID = session.authorID; const authorID = session.authorID;
// get the group2sessions and author2sessions entries // get the group2sessions and author2sessions entries
let group2sessions = await db.get("group2sessions:" + groupID); const group2sessions = await db.get(`group2sessions:${groupID}`);
let author2sessions = await db.get("author2sessions:" + authorID); const author2sessions = await db.get(`author2sessions:${authorID}`);
// remove the session // remove the session
await db.remove("session:" + sessionID); await db.remove(`session:${sessionID}`);
// remove session from group2sessions // remove session from group2sessions
if (group2sessions != null) { // Maybe the group was already deleted if (group2sessions != null) { // Maybe the group was already deleted
delete group2sessions.sessionIDs[sessionID]; delete group2sessions.sessionIDs[sessionID];
await db.set("group2sessions:" + groupID, group2sessions); await db.set(`group2sessions:${groupID}`, group2sessions);
} }
// remove session from author2sessions // remove session from author2sessions
if (author2sessions != null) { // Maybe the author was already deleted if (author2sessions != null) { // Maybe the author was already deleted
delete author2sessions.sessionIDs[sessionID]; delete author2sessions.sessionIDs[sessionID];
await db.set("author2sessions:" + authorID, author2sessions); await db.set(`author2sessions:${authorID}`, author2sessions);
} }
} };
exports.listSessionsOfGroup = async function(groupID) { exports.listSessionsOfGroup = async function (groupID) {
// check that the group exists // check that the group exists
let exists = await groupManager.doesGroupExist(groupID); const exists = await groupManager.doesGroupExist(groupID);
if (!exists) { if (!exists) {
throw new customError("groupID does not exist", "apierror"); throw new customError('groupID does not exist', 'apierror');
} }
let sessions = await listSessionsWithDBKey("group2sessions:" + groupID); const sessions = await listSessionsWithDBKey(`group2sessions:${groupID}`);
return sessions; return sessions;
} };
exports.listSessionsOfAuthor = async function(authorID) { exports.listSessionsOfAuthor = async function (authorID) {
// check that the author exists // check that the author exists
let exists = await authorManager.doesAuthorExist(authorID) const exists = await authorManager.doesAuthorExist(authorID);
if (!exists) { if (!exists) {
throw new customError("authorID does not exist", "apierror"); throw new customError('authorID does not exist', 'apierror');
} }
let sessions = await listSessionsWithDBKey("author2sessions:" + authorID); const sessions = await listSessionsWithDBKey(`author2sessions:${authorID}`);
return sessions; return sessions;
} };
// this function is basically the code listSessionsOfAuthor and listSessionsOfGroup has in common // this function is basically the code listSessionsOfAuthor and listSessionsOfGroup has in common
// required to return null rather than an empty object if there are none // required to return null rather than an empty object if there are none
async function listSessionsWithDBKey(dbkey) { async function listSessionsWithDBKey(dbkey) {
// get the group2sessions entry // get the group2sessions entry
let sessionObject = await db.get(dbkey); const sessionObject = await db.get(dbkey);
let sessions = sessionObject ? sessionObject.sessionIDs : null; const sessions = sessionObject ? sessionObject.sessionIDs : null;
// iterate through the sessions and get the sessioninfos // iterate through the sessions and get the sessioninfos
for (let sessionID in sessions) { for (const sessionID in sessions) {
try { try {
let sessionInfo = await exports.getSessionInfo(sessionID); const sessionInfo = await exports.getSessionInfo(sessionID);
sessions[sessionID] = sessionInfo; sessions[sessionID] = sessionInfo;
} catch (err) { } catch (err) {
if (err == "apierror: sessionID does not exist") { if (err == 'apierror: sessionID does not exist') {
console.warn(`Found bad session ${sessionID} in ${dbkey}`); console.warn(`Found bad session ${sessionID} in ${dbkey}`);
sessions[sessionID] = null; sessions[sessionID] = null;
} else { } else {

View file

@ -15,11 +15,11 @@ const logger = log4js.getLogger('SessionStore');
module.exports = class SessionStore extends Store { module.exports = class SessionStore extends Store {
get(sid, fn) { get(sid, fn) {
logger.debug('GET ' + sid); logger.debug(`GET ${sid}`);
DB.db.get('sessionstorage:' + sid, (err, sess) => { DB.db.get(`sessionstorage:${sid}`, (err, sess) => {
if (sess) { if (sess) {
sess.cookie.expires = ('string' == typeof sess.cookie.expires sess.cookie.expires = ('string' === typeof sess.cookie.expires
? new Date(sess.cookie.expires) : sess.cookie.expires); ? new Date(sess.cookie.expires) : sess.cookie.expires);
if (!sess.cookie.expires || new Date() < sess.cookie.expires) { if (!sess.cookie.expires || new Date() < sess.cookie.expires) {
fn(null, sess); fn(null, sess);
} else { } else {
@ -32,12 +32,12 @@ module.exports = class SessionStore extends Store {
} }
set(sid, sess, fn) { set(sid, sess, fn) {
logger.debug('SET ' + sid); logger.debug(`SET ${sid}`);
DB.db.set('sessionstorage:' + sid, sess, fn); DB.db.set(`sessionstorage:${sid}`, sess, fn);
} }
destroy(sid, fn) { destroy(sid, fn) {
logger.debug('DESTROY ' + sid); logger.debug(`DESTROY ${sid}`);
DB.db.remove('sessionstorage:' + sid, fn); DB.db.remove(`sessionstorage:${sid}`, fn);
} }
}; };

File diff suppressed because it is too large Load diff

View file

@ -19,84 +19,84 @@
* require("./index").require("./examples/foo.ejs") * require("./index").require("./examples/foo.ejs")
*/ */
var ejs = require("ejs"); const ejs = require('ejs');
var fs = require("fs"); const fs = require('fs');
var path = require("path"); const path = require('path');
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js"); const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
var resolve = require("resolve"); const resolve = require('resolve');
var settings = require('../utils/Settings'); const settings = require('../utils/Settings');
const templateCache = new Map() const templateCache = new Map();
exports.info = { exports.info = {
__output_stack: [], __output_stack: [],
block_stack: [], block_stack: [],
file_stack: [], file_stack: [],
args: [] args: [],
}; };
function getCurrentFile() { function getCurrentFile() {
return exports.info.file_stack[exports.info.file_stack.length-1]; return exports.info.file_stack[exports.info.file_stack.length - 1];
} }
function createBlockId(name) { function createBlockId(name) {
return getCurrentFile().path + '|' + name; return `${getCurrentFile().path}|${name}`;
} }
exports._init = function (b, recursive) { exports._init = function (b, recursive) {
exports.info.__output_stack.push(exports.info.__output); exports.info.__output_stack.push(exports.info.__output);
exports.info.__output = b; exports.info.__output = b;
} };
exports._exit = function (b, recursive) { exports._exit = function (b, recursive) {
getCurrentFile().inherit.forEach(function (item) { getCurrentFile().inherit.forEach((item) => {
exports._require(item.name, item.args); exports._require(item.name, item.args);
}); });
exports.info.__output = exports.info.__output_stack.pop(); exports.info.__output = exports.info.__output_stack.pop();
} };
exports.begin_capture = function() { exports.begin_capture = function () {
exports.info.__output_stack.push(exports.info.__output.concat()); exports.info.__output_stack.push(exports.info.__output.concat());
exports.info.__output.splice(0, exports.info.__output.length); exports.info.__output.splice(0, exports.info.__output.length);
} };
exports.end_capture = function () { exports.end_capture = function () {
var res = exports.info.__output.join(""); const res = exports.info.__output.join('');
exports.info.__output.splice.apply( exports.info.__output.splice.apply(
exports.info.__output, exports.info.__output,
[0, exports.info.__output.length].concat(exports.info.__output_stack.pop())); [0, exports.info.__output.length].concat(exports.info.__output_stack.pop()));
return res; return res;
} };
exports.begin_define_block = function (name) { exports.begin_define_block = function (name) {
exports.info.block_stack.push(name); exports.info.block_stack.push(name);
exports.begin_capture(); exports.begin_capture();
} };
exports.end_define_block = function () { exports.end_define_block = function () {
var content = exports.end_capture(); const content = exports.end_capture();
return content; return content;
} };
exports.end_block = function () { exports.end_block = function () {
var name = exports.info.block_stack.pop(); const name = exports.info.block_stack.pop();
var renderContext = exports.info.args[exports.info.args.length-1]; const renderContext = exports.info.args[exports.info.args.length - 1];
var args = {content: exports.end_define_block(), renderContext: renderContext}; const args = {content: exports.end_define_block(), renderContext};
hooks.callAll("eejsBlock_" + name, args); hooks.callAll(`eejsBlock_${name}`, args);
exports.info.__output.push(args.content); exports.info.__output.push(args.content);
} };
exports.begin_block = exports.begin_define_block; exports.begin_block = exports.begin_define_block;
exports.inherit = function (name, args) { exports.inherit = function (name, args) {
getCurrentFile().inherit.push({name:name, args:args}); getCurrentFile().inherit.push({name, args});
} };
exports.require = function (name, args, mod) { exports.require = function (name, args, mod) {
if (args == undefined) args = {}; if (args == undefined) args = {};
var basedir = __dirname; let basedir = __dirname;
var paths = []; let paths = [];
if (exports.info.file_stack.length) { if (exports.info.file_stack.length) {
basedir = path.dirname(getCurrentFile().path); basedir = path.dirname(getCurrentFile().path);
@ -106,43 +106,43 @@ exports.require = function (name, args, mod) {
paths = mod.paths; paths = mod.paths;
} }
var ejspath = resolve.sync( const ejspath = resolve.sync(
name, name,
{ {
paths : paths, paths,
basedir : basedir, basedir,
extensions : [ '.html', '.ejs' ], extensions: ['.html', '.ejs'],
} },
) );
args.e = exports; args.e = exports;
args.require = require; args.require = require;
let template let template;
if (settings.maxAge !== 0){ // don't cache if maxAge is 0 if (settings.maxAge !== 0) { // don't cache if maxAge is 0
if (!templateCache.has(ejspath)) { if (!templateCache.has(ejspath)) {
template = '<% e._init(__output); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>'; template = `<% e._init(__output); %>${fs.readFileSync(ejspath).toString()}<% e._exit(); %>`;
templateCache.set(ejspath, template) templateCache.set(ejspath, template);
} else { } else {
template = templateCache.get(ejspath) template = templateCache.get(ejspath);
} }
}else{ } else {
template = '<% e._init(__output); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>'; template = `<% e._init(__output); %>${fs.readFileSync(ejspath).toString()}<% e._exit(); %>`;
} }
exports.info.args.push(args); exports.info.args.push(args);
exports.info.file_stack.push({path: ejspath, inherit: []}); exports.info.file_stack.push({path: ejspath, inherit: []});
if(settings.maxAge !== 0){ if (settings.maxAge !== 0) {
var res = ejs.render(template, args, { cache: true, filename: ejspath }); var res = ejs.render(template, args, {cache: true, filename: ejspath});
}else{ } else {
var res = ejs.render(template, args, { cache: false, filename: ejspath }); var res = ejs.render(template, args, {cache: false, filename: ejspath});
} }
exports.info.file_stack.pop(); exports.info.file_stack.pop();
exports.info.args.pop(); exports.info.args.pop();
return res; return res;
} };
exports._require = function (name, args) { exports._require = function (name, args) {
exports.info.__output.push(exports.require(name, args)); exports.info.__output.push(exports.require(name, args));
} };

View file

@ -18,131 +18,118 @@
* limitations under the License. * limitations under the License.
*/ */
var absolutePaths = require('../utils/AbsolutePaths'); const absolutePaths = require('../utils/AbsolutePaths');
var fs = require("fs"); const fs = require('fs');
var api = require("../db/API"); const api = require('../db/API');
var log4js = require('log4js'); const log4js = require('log4js');
var padManager = require("../db/PadManager"); const padManager = require('../db/PadManager');
var randomString = require("../utils/randomstring"); const randomString = require('../utils/randomstring');
var argv = require('../utils/Cli').argv; const argv = require('../utils/Cli').argv;
var createHTTPError = require('http-errors'); const createHTTPError = require('http-errors');
var apiHandlerLogger = log4js.getLogger('APIHandler'); const apiHandlerLogger = log4js.getLogger('APIHandler');
//ensure we have an apikey // ensure we have an apikey
var apikey = null; let apikey = null;
var apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || "./APIKEY.txt"); const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt');
try { try {
apikey = fs.readFileSync(apikeyFilename,"utf8"); apikey = fs.readFileSync(apikeyFilename, 'utf8');
apiHandlerLogger.info(`Api key file read from: "${apikeyFilename}"`); apiHandlerLogger.info(`Api key file read from: "${apikeyFilename}"`);
} catch(e) { } catch (e) {
apiHandlerLogger.info(`Api key file "${apikeyFilename}" not found. Creating with random contents.`); apiHandlerLogger.info(`Api key file "${apikeyFilename}" not found. Creating with random contents.`);
apikey = randomString(32); apikey = randomString(32);
fs.writeFileSync(apikeyFilename,apikey,"utf8"); fs.writeFileSync(apikeyFilename, apikey, 'utf8');
} }
// a list of all functions // a list of all functions
var version = {}; const version = {};
version["1"] = Object.assign({}, version['1'] = Object.assign({},
{ "createGroup" : [] {createGroup: [],
, "createGroupIfNotExistsFor" : ["groupMapper"] createGroupIfNotExistsFor: ['groupMapper'],
, "deleteGroup" : ["groupID"] deleteGroup: ['groupID'],
, "listPads" : ["groupID"] listPads: ['groupID'],
, "createPad" : ["padID", "text"] createPad: ['padID', 'text'],
, "createGroupPad" : ["groupID", "padName", "text"] createGroupPad: ['groupID', 'padName', 'text'],
, "createAuthor" : ["name"] createAuthor: ['name'],
, "createAuthorIfNotExistsFor": ["authorMapper" , "name"] createAuthorIfNotExistsFor: ['authorMapper', 'name'],
, "listPadsOfAuthor" : ["authorID"] listPadsOfAuthor: ['authorID'],
, "createSession" : ["groupID", "authorID", "validUntil"] createSession: ['groupID', 'authorID', 'validUntil'],
, "deleteSession" : ["sessionID"] deleteSession: ['sessionID'],
, "getSessionInfo" : ["sessionID"] getSessionInfo: ['sessionID'],
, "listSessionsOfGroup" : ["groupID"] listSessionsOfGroup: ['groupID'],
, "listSessionsOfAuthor" : ["authorID"] listSessionsOfAuthor: ['authorID'],
, "getText" : ["padID", "rev"] getText: ['padID', 'rev'],
, "setText" : ["padID", "text"] setText: ['padID', 'text'],
, "getHTML" : ["padID", "rev"] getHTML: ['padID', 'rev'],
, "setHTML" : ["padID", "html"] setHTML: ['padID', 'html'],
, "getRevisionsCount" : ["padID"] getRevisionsCount: ['padID'],
, "getLastEdited" : ["padID"] getLastEdited: ['padID'],
, "deletePad" : ["padID"] deletePad: ['padID'],
, "getReadOnlyID" : ["padID"] getReadOnlyID: ['padID'],
, "setPublicStatus" : ["padID", "publicStatus"] setPublicStatus: ['padID', 'publicStatus'],
, "getPublicStatus" : ["padID"] getPublicStatus: ['padID'],
, "listAuthorsOfPad" : ["padID"] listAuthorsOfPad: ['padID'],
, "padUsersCount" : ["padID"] padUsersCount: ['padID']},
}
); );
version["1.1"] = Object.assign({}, version["1"], version['1.1'] = Object.assign({}, version['1'],
{ "getAuthorName" : ["authorID"] {getAuthorName: ['authorID'],
, "padUsers" : ["padID"] padUsers: ['padID'],
, "sendClientsMessage" : ["padID", "msg"] sendClientsMessage: ['padID', 'msg'],
, "listAllGroups" : [] listAllGroups: []},
}
); );
version["1.2"] = Object.assign({}, version["1.1"], version['1.2'] = Object.assign({}, version['1.1'],
{ "checkToken" : [] {checkToken: []},
}
); );
version["1.2.1"] = Object.assign({}, version["1.2"], version['1.2.1'] = Object.assign({}, version['1.2'],
{ "listAllPads" : [] {listAllPads: []},
}
); );
version["1.2.7"] = Object.assign({}, version["1.2.1"], version['1.2.7'] = Object.assign({}, version['1.2.1'],
{ "createDiffHTML" : ["padID", "startRev", "endRev"] {createDiffHTML: ['padID', 'startRev', 'endRev'],
, "getChatHistory" : ["padID", "start", "end"] getChatHistory: ['padID', 'start', 'end'],
, "getChatHead" : ["padID"] getChatHead: ['padID']},
}
); );
version["1.2.8"] = Object.assign({}, version["1.2.7"], version['1.2.8'] = Object.assign({}, version['1.2.7'],
{ "getAttributePool" : ["padID"] {getAttributePool: ['padID'],
, "getRevisionChangeset" : ["padID", "rev"] getRevisionChangeset: ['padID', 'rev']},
}
); );
version["1.2.9"] = Object.assign({}, version["1.2.8"], version['1.2.9'] = Object.assign({}, version['1.2.8'],
{ "copyPad" : ["sourceID", "destinationID", "force"] {copyPad: ['sourceID', 'destinationID', 'force'],
, "movePad" : ["sourceID", "destinationID", "force"] movePad: ['sourceID', 'destinationID', 'force']},
}
); );
version["1.2.10"] = Object.assign({}, version["1.2.9"], version['1.2.10'] = Object.assign({}, version['1.2.9'],
{ "getPadID" : ["roID"] {getPadID: ['roID']},
}
); );
version["1.2.11"] = Object.assign({}, version["1.2.10"], version['1.2.11'] = Object.assign({}, version['1.2.10'],
{ "getSavedRevisionsCount" : ["padID"] {getSavedRevisionsCount: ['padID'],
, "listSavedRevisions" : ["padID"] listSavedRevisions: ['padID'],
, "saveRevision" : ["padID", "rev"] saveRevision: ['padID', 'rev'],
, "restoreRevision" : ["padID", "rev"] restoreRevision: ['padID', 'rev']},
}
); );
version["1.2.12"] = Object.assign({}, version["1.2.11"], version['1.2.12'] = Object.assign({}, version['1.2.11'],
{ "appendChatMessage" : ["padID", "text", "authorID", "time"] {appendChatMessage: ['padID', 'text', 'authorID', 'time']},
}
); );
version["1.2.13"] = Object.assign({}, version["1.2.12"], version['1.2.13'] = Object.assign({}, version['1.2.12'],
{ "appendText" : ["padID", "text"] {appendText: ['padID', 'text']},
}
); );
version["1.2.14"] = Object.assign({}, version["1.2.13"], version['1.2.14'] = Object.assign({}, version['1.2.13'],
{ "getStats" : [] {getStats: []},
}
); );
version["1.2.15"] = Object.assign({}, version["1.2.14"], version['1.2.15'] = Object.assign({}, version['1.2.14'],
{ "copyPadWithoutHistory" : ["sourceID", "destinationID", "force"] {copyPadWithoutHistory: ['sourceID', 'destinationID', 'force']},
}
); );
// set the latest available API version here // set the latest available API version here
@ -158,7 +145,7 @@ exports.version = version;
* @req express request object * @req express request object
* @res express response object * @res express response object
*/ */
exports.handle = async function(apiVersion, functionName, fields, req, res) { exports.handle = async function (apiVersion, functionName, fields, req, res) {
// say goodbye if this is an unknown API version // say goodbye if this is an unknown API version
if (!(apiVersion in version)) { if (!(apiVersion in version)) {
throw new createHTTPError.NotFound('no such api version'); throw new createHTTPError.NotFound('no such api version');
@ -170,31 +157,29 @@ exports.handle = async function(apiVersion, functionName, fields, req, res) {
} }
// check the api key! // check the api key!
fields["apikey"] = fields["apikey"] || fields["api_key"]; fields.apikey = fields.apikey || fields.api_key;
if (fields["apikey"] !== apikey.trim()) { if (fields.apikey !== apikey.trim()) {
throw new createHTTPError.Unauthorized('no or wrong API Key'); throw new createHTTPError.Unauthorized('no or wrong API Key');
} }
// sanitize any padIDs before continuing // sanitize any padIDs before continuing
if (fields["padID"]) { if (fields.padID) {
fields["padID"] = await padManager.sanitizePadId(fields["padID"]); fields.padID = await padManager.sanitizePadId(fields.padID);
} }
// there was an 'else' here before - removed it to ensure // there was an 'else' here before - removed it to ensure
// that this sanitize step can't be circumvented by forcing // that this sanitize step can't be circumvented by forcing
// the first branch to be taken // the first branch to be taken
if (fields["padName"]) { if (fields.padName) {
fields["padName"] = await padManager.sanitizePadId(fields["padName"]); fields.padName = await padManager.sanitizePadId(fields.padName);
} }
// put the function parameters in an array // put the function parameters in an array
var functionParams = version[apiVersion][functionName].map(function (field) { const functionParams = version[apiVersion][functionName].map((field) => fields[field]);
return fields[field]
});
// call the api function // call the api function
return api[functionName].apply(this, functionParams); return api[functionName].apply(this, functionParams);
} };
exports.exportedForTestingOnly = { exports.exportedForTestingOnly = {
apiKey: apikey, apiKey: apikey,

View file

@ -19,15 +19,15 @@
* limitations under the License. * limitations under the License.
*/ */
var exporthtml = require("../utils/ExportHtml"); const exporthtml = require('../utils/ExportHtml');
var exporttxt = require("../utils/ExportTxt"); const exporttxt = require('../utils/ExportTxt');
var exportEtherpad = require("../utils/ExportEtherpad"); const exportEtherpad = require('../utils/ExportEtherpad');
var fs = require("fs"); const fs = require('fs');
var settings = require('../utils/Settings'); const settings = require('../utils/Settings');
var os = require('os'); const os = require('os');
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var TidyHtml = require('../utils/TidyHtml'); const TidyHtml = require('../utils/TidyHtml');
const util = require("util"); const util = require('util');
const fsp_writeFile = util.promisify(fs.writeFile); const fsp_writeFile = util.promisify(fs.writeFile);
const fsp_unlink = util.promisify(fs.unlink); const fsp_unlink = util.promisify(fs.unlink);
@ -36,12 +36,12 @@ let convertor = null;
// load abiword only if it is enabled // load abiword only if it is enabled
if (settings.abiword != null) { if (settings.abiword != null) {
convertor = require("../utils/Abiword"); convertor = require('../utils/Abiword');
} }
// Use LibreOffice if an executable has been defined in the settings // Use LibreOffice if an executable has been defined in the settings
if (settings.soffice != null) { if (settings.soffice != null) {
convertor = require("../utils/LibreOffice"); convertor = require('../utils/LibreOffice');
} }
const tempDirectory = os.tmpdir(); const tempDirectory = os.tmpdir();
@ -51,10 +51,10 @@ const tempDirectory = os.tmpdir();
*/ */
async function doExport(req, res, padId, readOnlyId, type) { async function doExport(req, res, padId, readOnlyId, type) {
// avoid naming the read-only file as the original pad's id // avoid naming the read-only file as the original pad's id
var fileName = readOnlyId ? readOnlyId : padId; let fileName = readOnlyId ? readOnlyId : padId;
// allow fileName to be overwritten by a hook, the type type is kept static for security reasons // allow fileName to be overwritten by a hook, the type type is kept static for security reasons
let hookFileName = await hooks.aCallFirst("exportFileName", padId); const hookFileName = await hooks.aCallFirst('exportFileName', padId);
// if fileName is set then set it to the padId, note that fileName is returned as an array. // if fileName is set then set it to the padId, note that fileName is returned as an array.
if (hookFileName.length) { if (hookFileName.length) {
@ -62,15 +62,15 @@ async function doExport(req, res, padId, readOnlyId, type) {
} }
// tell the browser that this is a downloadable file // tell the browser that this is a downloadable file
res.attachment(fileName + "." + type); res.attachment(`${fileName}.${type}`);
// if this is a plain text export, we can do this directly // if this is a plain text export, we can do this directly
// We have to over engineer this because tabs are stored as attributes and not plain text // We have to over engineer this because tabs are stored as attributes and not plain text
if (type === "etherpad") { if (type === 'etherpad') {
let pad = await exportEtherpad.getPadRaw(padId); const pad = await exportEtherpad.getPadRaw(padId);
res.send(pad); res.send(pad);
} else if (type === "txt") { } else if (type === 'txt') {
let txt = await exporttxt.getPadTXTDocument(padId, req.params.rev); const txt = await exporttxt.getPadTXTDocument(padId, req.params.rev);
res.send(txt); res.send(txt);
} else { } else {
// render the html document // render the html document
@ -79,17 +79,17 @@ async function doExport(req, res, padId, readOnlyId, type) {
// decide what to do with the html export // decide what to do with the html export
// if this is a html export, we can send this from here directly // if this is a html export, we can send this from here directly
if (type === "html") { if (type === 'html') {
// do any final changes the plugin might want to make // do any final changes the plugin might want to make
let newHTML = await hooks.aCallFirst("exportHTMLSend", html); const newHTML = await hooks.aCallFirst('exportHTMLSend', html);
if (newHTML.length) html = newHTML; if (newHTML.length) html = newHTML;
res.send(html); res.send(html);
throw "stop"; throw 'stop';
} }
// else write the html export to a file // else write the html export to a file
let randNum = Math.floor(Math.random()*0xFFFFFFFF); const randNum = Math.floor(Math.random() * 0xFFFFFFFF);
let srcFile = tempDirectory + "/etherpad_export_" + randNum + ".html"; const srcFile = `${tempDirectory}/etherpad_export_${randNum}.html`;
await fsp_writeFile(srcFile, html); await fsp_writeFile(srcFile, html);
// Tidy up the exported HTML // Tidy up the exported HTML
@ -98,42 +98,42 @@ async function doExport(req, res, padId, readOnlyId, type) {
await TidyHtml.tidy(srcFile); await TidyHtml.tidy(srcFile);
// send the convert job to the convertor (abiword, libreoffice, ..) // send the convert job to the convertor (abiword, libreoffice, ..)
let destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type; const destFile = `${tempDirectory}/etherpad_export_${randNum}.${type}`;
// Allow plugins to overwrite the convert in export process // Allow plugins to overwrite the convert in export process
let result = await hooks.aCallAll("exportConvert", { srcFile, destFile, req, res }); const result = await hooks.aCallAll('exportConvert', {srcFile, destFile, req, res});
if (result.length > 0) { if (result.length > 0) {
// console.log("export handled by plugin", destFile); // console.log("export handled by plugin", destFile);
handledByPlugin = true; handledByPlugin = true;
} else { } else {
// @TODO no Promise interface for convertors (yet) // @TODO no Promise interface for convertors (yet)
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
convertor.convertFile(srcFile, destFile, type, function(err) { convertor.convertFile(srcFile, destFile, type, (err) => {
err ? reject("convertFailed") : resolve(); err ? reject('convertFailed') : resolve();
}); });
}); });
} }
// send the file // send the file
let sendFile = util.promisify(res.sendFile); const sendFile = util.promisify(res.sendFile);
await res.sendFile(destFile, null); await res.sendFile(destFile, null);
// clean up temporary files // clean up temporary files
await fsp_unlink(srcFile); await fsp_unlink(srcFile);
// 100ms delay to accommodate for slow windows fs // 100ms delay to accommodate for slow windows fs
if (os.type().indexOf("Windows") > -1) { if (os.type().indexOf('Windows') > -1) {
await new Promise(resolve => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
} }
await fsp_unlink(destFile); await fsp_unlink(destFile);
} }
} }
exports.doExport = function(req, res, padId, readOnlyId, type) { exports.doExport = function (req, res, padId, readOnlyId, type) {
doExport(req, res, padId, readOnlyId, type).catch(err => { doExport(req, res, padId, readOnlyId, type).catch((err) => {
if (err !== "stop") { if (err !== 'stop') {
throw err; throw err;
} }
}); });
} };

View file

@ -20,36 +20,36 @@
* limitations under the License. * limitations under the License.
*/ */
var padManager = require("../db/PadManager") const padManager = require('../db/PadManager');
, padMessageHandler = require("./PadMessageHandler") const padMessageHandler = require('./PadMessageHandler');
, fs = require("fs") const fs = require('fs');
, path = require("path") const path = require('path');
, settings = require('../utils/Settings') const settings = require('../utils/Settings');
, formidable = require('formidable') const formidable = require('formidable');
, os = require("os") const os = require('os');
, importHtml = require("../utils/ImportHtml") const importHtml = require('../utils/ImportHtml');
, importEtherpad = require("../utils/ImportEtherpad") const importEtherpad = require('../utils/ImportEtherpad');
, log4js = require("log4js") const log4js = require('log4js');
, hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js") const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
, util = require("util"); const util = require('util');
let fsp_exists = util.promisify(fs.exists); const fsp_exists = util.promisify(fs.exists);
let fsp_rename = util.promisify(fs.rename); const fsp_rename = util.promisify(fs.rename);
let fsp_readFile = util.promisify(fs.readFile); const fsp_readFile = util.promisify(fs.readFile);
let fsp_unlink = util.promisify(fs.unlink) const fsp_unlink = util.promisify(fs.unlink);
let convertor = null; let convertor = null;
let exportExtension = "htm"; let exportExtension = 'htm';
// load abiword only if it is enabled and if soffice is disabled // load abiword only if it is enabled and if soffice is disabled
if (settings.abiword != null && settings.soffice === null) { if (settings.abiword != null && settings.soffice === null) {
convertor = require("../utils/Abiword"); convertor = require('../utils/Abiword');
} }
// load soffice only if it is enabled // load soffice only if it is enabled
if (settings.soffice != null) { if (settings.soffice != null) {
convertor = require("../utils/LibreOffice"); convertor = require('../utils/LibreOffice');
exportExtension = "html"; exportExtension = 'html';
} }
const tmpDirectory = os.tmpdir(); const tmpDirectory = os.tmpdir();
@ -58,17 +58,17 @@ const tmpDirectory = os.tmpdir();
* do a requested import * do a requested import
*/ */
async function doImport(req, res, padId) { async function doImport(req, res, padId) {
var apiLogger = log4js.getLogger("ImportHandler"); const apiLogger = log4js.getLogger('ImportHandler');
// pipe to a file // pipe to a file
// convert file to html via abiword or soffice // convert file to html via abiword or soffice
// set html in the pad // set html in the pad
var randNum = Math.floor(Math.random()*0xFFFFFFFF); const randNum = Math.floor(Math.random() * 0xFFFFFFFF);
// setting flag for whether to use convertor or not // setting flag for whether to use convertor or not
let useConvertor = (convertor != null); let useConvertor = (convertor != null);
let form = new formidable.IncomingForm(); const form = new formidable.IncomingForm();
form.keepExtensions = true; form.keepExtensions = true;
form.uploadDir = tmpDirectory; form.uploadDir = tmpDirectory;
form.maxFileSize = settings.importMaxFileSize; form.maxFileSize = settings.importMaxFileSize;
@ -76,10 +76,10 @@ async function doImport(req, res, padId) {
// Ref: https://github.com/node-formidable/formidable/issues/469 // Ref: https://github.com/node-formidable/formidable/issues/469
// Crash in Etherpad was Uploading Error: Error: Request aborted // Crash in Etherpad was Uploading Error: Error: Request aborted
// [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed // [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed
form.onPart = part => { form.onPart = (part) => {
form.handlePart(part); form.handlePart(part);
if (part.filename !== undefined) { if (part.filename !== undefined) {
form.openedFiles[form.openedFiles.length - 1]._writeStream.on('error', err => { form.openedFiles[form.openedFiles.length - 1]._writeStream.on('error', (err) => {
form.emit('error', err); form.emit('error', err);
}); });
} }
@ -87,23 +87,23 @@ async function doImport(req, res, padId) {
// locally wrapped Promise, since form.parse requires a callback // locally wrapped Promise, since form.parse requires a callback
let srcFile = await new Promise((resolve, reject) => { let srcFile = await new Promise((resolve, reject) => {
form.parse(req, function(err, fields, files) { form.parse(req, (err, fields, files) => {
if (err || files.file === undefined) { if (err || files.file === undefined) {
// the upload failed, stop at this point // the upload failed, stop at this point
if (err) { if (err) {
console.warn("Uploading Error: " + err.stack); console.warn(`Uploading Error: ${err.stack}`);
} }
// I hate doing indexOf here but I can't see anything to use... // I hate doing indexOf here but I can't see anything to use...
if (err && err.stack && err.stack.indexOf("maxFileSize") !== -1) { if (err && err.stack && err.stack.indexOf('maxFileSize') !== -1) {
reject("maxFileSize"); reject('maxFileSize');
} }
reject("uploadFailed"); reject('uploadFailed');
} }
if(!files.file){ // might not be a graceful fix but it works if (!files.file) { // might not be a graceful fix but it works
reject("uploadFailed"); reject('uploadFailed');
}else{ } else {
resolve(files.file.path); resolve(files.file.path);
} }
}); });
@ -111,47 +111,47 @@ async function doImport(req, res, padId) {
// ensure this is a file ending we know, else we change the file ending to .txt // ensure this is a file ending we know, else we change the file ending to .txt
// this allows us to accept source code files like .c or .java // this allows us to accept source code files like .c or .java
let fileEnding = path.extname(srcFile).toLowerCase() const fileEnding = path.extname(srcFile).toLowerCase();
, knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm", ".etherpad", ".rtf"] const knownFileEndings = ['.txt', '.doc', '.docx', '.pdf', '.odt', '.html', '.htm', '.etherpad', '.rtf'];
, fileEndingUnknown = (knownFileEndings.indexOf(fileEnding) < 0); const fileEndingUnknown = (knownFileEndings.indexOf(fileEnding) < 0);
if (fileEndingUnknown) { if (fileEndingUnknown) {
// the file ending is not known // the file ending is not known
if (settings.allowUnknownFileEnds === true) { if (settings.allowUnknownFileEnds === true) {
// we need to rename this file with a .txt ending // we need to rename this file with a .txt ending
let oldSrcFile = srcFile; const oldSrcFile = srcFile;
srcFile = path.join(path.dirname(srcFile), path.basename(srcFile, fileEnding) + ".txt"); srcFile = path.join(path.dirname(srcFile), `${path.basename(srcFile, fileEnding)}.txt`);
await fsp_rename(oldSrcFile, srcFile); await fsp_rename(oldSrcFile, srcFile);
} else { } else {
console.warn("Not allowing unknown file type to be imported", fileEnding); console.warn('Not allowing unknown file type to be imported', fileEnding);
throw "uploadFailed"; throw 'uploadFailed';
} }
} }
let destFile = path.join(tmpDirectory, "etherpad_import_" + randNum + "." + exportExtension); const destFile = path.join(tmpDirectory, `etherpad_import_${randNum}.${exportExtension}`);
// Logic for allowing external Import Plugins // Logic for allowing external Import Plugins
let result = await hooks.aCallAll("import", { srcFile, destFile, fileEnding }); const result = await hooks.aCallAll('import', {srcFile, destFile, fileEnding});
let importHandledByPlugin = (result.length > 0); // This feels hacky and wrong.. const importHandledByPlugin = (result.length > 0); // This feels hacky and wrong..
let fileIsEtherpad = (fileEnding === ".etherpad"); const fileIsEtherpad = (fileEnding === '.etherpad');
let fileIsHTML = (fileEnding === ".html" || fileEnding === ".htm"); const fileIsHTML = (fileEnding === '.html' || fileEnding === '.htm');
let fileIsTXT = (fileEnding === ".txt"); const fileIsTXT = (fileEnding === '.txt');
if (fileIsEtherpad) { if (fileIsEtherpad) {
// we do this here so we can see if the pad has quite a few edits // we do this here so we can see if the pad has quite a few edits
let _pad = await padManager.getPad(padId); const _pad = await padManager.getPad(padId);
let headCount = _pad.head; const headCount = _pad.head;
if (headCount >= 10) { if (headCount >= 10) {
apiLogger.warn("Direct database Import attempt of a pad that already has content, we won't be doing this"); apiLogger.warn("Direct database Import attempt of a pad that already has content, we won't be doing this");
throw "padHasData"; throw 'padHasData';
} }
const fsp_readFile = util.promisify(fs.readFile); const fsp_readFile = util.promisify(fs.readFile);
let _text = await fsp_readFile(srcFile, "utf8"); const _text = await fsp_readFile(srcFile, 'utf8');
req.directDatabaseAccess = true; req.directDatabaseAccess = true;
await importEtherpad.setPadRaw(padId, _text); await importEtherpad.setPadRaw(padId, _text);
} }
@ -170,11 +170,11 @@ async function doImport(req, res, padId) {
} else { } else {
// @TODO - no Promise interface for convertors (yet) // @TODO - no Promise interface for convertors (yet)
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
convertor.convertFile(srcFile, destFile, exportExtension, function(err) { convertor.convertFile(srcFile, destFile, exportExtension, (err) => {
// catch convert errors // catch convert errors
if (err) { if (err) {
console.warn("Converting Error:", err); console.warn('Converting Error:', err);
reject("convertFailed"); reject('convertFailed');
} }
resolve(); resolve();
}); });
@ -184,13 +184,13 @@ async function doImport(req, res, padId) {
if (!useConvertor && !req.directDatabaseAccess) { if (!useConvertor && !req.directDatabaseAccess) {
// Read the file with no encoding for raw buffer access. // Read the file with no encoding for raw buffer access.
let buf = await fsp_readFile(destFile); const buf = await fsp_readFile(destFile);
// Check if there are only ascii chars in the uploaded file // Check if there are only ascii chars in the uploaded file
let isAscii = ! Array.prototype.some.call(buf, c => (c > 240)); const isAscii = !Array.prototype.some.call(buf, (c) => (c > 240));
if (!isAscii) { if (!isAscii) {
throw "uploadFailed"; throw 'uploadFailed';
} }
} }
@ -201,7 +201,7 @@ async function doImport(req, res, padId) {
let text; let text;
if (!req.directDatabaseAccess) { if (!req.directDatabaseAccess) {
text = await fsp_readFile(destFile, "utf8"); text = await fsp_readFile(destFile, 'utf8');
/* /*
* The <title> tag needs to be stripped out, otherwise it is appended to the * The <title> tag needs to be stripped out, otherwise it is appended to the
@ -211,13 +211,13 @@ async function doImport(req, res, padId) {
* added to the <title> tag. This is a quick & dirty way of matching the * added to the <title> tag. This is a quick & dirty way of matching the
* title and comment it out independently on the classes that are set on it. * title and comment it out independently on the classes that are set on it.
*/ */
text = text.replace("<title", "<!-- <title"); text = text.replace('<title', '<!-- <title');
text = text.replace("</title>","</title>-->"); text = text.replace('</title>', '</title>-->');
// node on windows has a delay on releasing of the file lock. // node on windows has a delay on releasing of the file lock.
// We add a 100ms delay to work around this // We add a 100ms delay to work around this
if (os.type().indexOf("Windows") > -1){ if (os.type().indexOf('Windows') > -1) {
await new Promise(resolve => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
} }
} }
@ -227,7 +227,7 @@ async function doImport(req, res, padId) {
try { try {
await importHtml.setPadHTML(pad, text); await importHtml.setPadHTML(pad, text);
} catch (e) { } catch (e) {
apiLogger.warn("Error importing, possibly caused by malformed HTML"); apiLogger.warn('Error importing, possibly caused by malformed HTML');
} }
} else { } else {
await pad.setText(text); await pad.setText(text);
@ -274,16 +274,16 @@ exports.doImport = function (req, res, padId) {
* the function above there's no other way to return * the function above there's no other way to return
* a value to the caller. * a value to the caller.
*/ */
let status = "ok"; let status = 'ok';
doImport(req, res, padId).catch(err => { doImport(req, res, padId).catch((err) => {
// check for known errors and replace the status // check for known errors and replace the status
if (err == "uploadFailed" || err == "convertFailed" || err == "padHasData" || err == "maxFileSize") { if (err == 'uploadFailed' || err == 'convertFailed' || err == 'padHasData' || err == 'maxFileSize') {
status = err; status = err;
} else { } else {
throw err; throw err;
} }
}).then(() => { }).then(() => {
// close the connection // close the connection
res.send("<script>document.addEventListener('DOMContentLoaded', function(){ var impexp = window.parent.padimpexp.handleFrameCall('" + req.directDatabaseAccess +"', '" + status + "'); })</script>"); res.send(`<script>document.addEventListener('DOMContentLoaded', function(){ var impexp = window.parent.padimpexp.handleFrameCall('${req.directDatabaseAccess}', '${status}'); })</script>`);
}); });
} };

File diff suppressed because it is too large Load diff

View file

@ -19,53 +19,53 @@
* limitations under the License. * limitations under the License.
*/ */
var log4js = require('log4js'); const log4js = require('log4js');
var messageLogger = log4js.getLogger("message"); const messageLogger = log4js.getLogger('message');
var securityManager = require("../db/SecurityManager"); const securityManager = require('../db/SecurityManager');
var readOnlyManager = require("../db/ReadOnlyManager"); const readOnlyManager = require('../db/ReadOnlyManager');
var settings = require('../utils/Settings'); const settings = require('../utils/Settings');
/** /**
* Saves all components * Saves all components
* key is the component name * key is the component name
* value is the component module * value is the component module
*/ */
var components = {}; const components = {};
var socket; let socket;
/** /**
* adds a component * adds a component
*/ */
exports.addComponent = function(moduleName, module) { exports.addComponent = function (moduleName, module) {
// save the component // save the component
components[moduleName] = module; components[moduleName] = module;
// give the module the socket // give the module the socket
module.setSocketIO(socket); module.setSocketIO(socket);
} };
/** /**
* sets the socket.io and adds event functions for routing * sets the socket.io and adds event functions for routing
*/ */
exports.setSocketIO = function(_socket) { exports.setSocketIO = function (_socket) {
// save this socket internaly // save this socket internaly
socket = _socket; socket = _socket;
socket.sockets.on('connection', function(client) { socket.sockets.on('connection', (client) => {
// wrap the original send function to log the messages // wrap the original send function to log the messages
client._send = client.send; client._send = client.send;
client.send = function(message) { client.send = function (message) {
messageLogger.debug(`to ${client.id}: ${JSON.stringify(message)}`); messageLogger.debug(`to ${client.id}: ${JSON.stringify(message)}`);
client._send(message); client._send(message);
} };
// tell all components about this connect // tell all components about this connect
for (let i in components) { for (const i in components) {
components[i].handleConnect(client); components[i].handleConnect(client);
} }
client.on('message', async function(message) { client.on('message', async (message) => {
if (message.protocolVersion && message.protocolVersion != 2) { if (message.protocolVersion && message.protocolVersion != 2) {
messageLogger.warn(`Protocolversion header is not correct: ${JSON.stringify(message)}`); messageLogger.warn(`Protocolversion header is not correct: ${JSON.stringify(message)}`);
return; return;
@ -78,11 +78,11 @@ exports.setSocketIO = function(_socket) {
await components[message.component].handleMessage(client, message); await components[message.component].handleMessage(client, message);
}); });
client.on('disconnect', function() { client.on('disconnect', () => {
// tell all components about this disconnect // tell all components about this disconnect
for (let i in components) { for (const i in components) {
components[i].handleDisconnect(client); components[i].handleDisconnect(client);
} }
}); });
}); });
} };

View file

@ -18,7 +18,7 @@ let serverName;
exports.server = null; exports.server = null;
exports.createServer = async () => { exports.createServer = async () => {
console.log("Report bugs at https://github.com/ether/etherpad-lite/issues") console.log('Report bugs at https://github.com/ether/etherpad-lite/issues');
serverName = `Etherpad ${settings.getGitCommit()} (https://etherpad.org)`; serverName = `Etherpad ${settings.getGitCommit()} (https://etherpad.org)`;
@ -26,7 +26,7 @@ exports.createServer = async () => {
await exports.restartServer(); await exports.restartServer();
if (settings.ip === "") { if (settings.ip === '') {
// using Unix socket for connectivity // using Unix socket for connectivity
console.log(`You can access your Etherpad instance using the Unix socket at ${settings.port}`); console.log(`You can access your Etherpad instance using the Unix socket at ${settings.port}`);
} else { } else {
@ -42,26 +42,26 @@ exports.createServer = async () => {
const env = process.env.NODE_ENV || 'development'; const env = process.env.NODE_ENV || 'development';
if (env !== 'production') { if (env !== 'production') {
console.warn("Etherpad is running in Development mode. This mode is slower for users and less secure than production mode. You should set the NODE_ENV environment variable to production by using: export NODE_ENV=production"); console.warn('Etherpad is running in Development mode. This mode is slower for users and less secure than production mode. You should set the NODE_ENV environment variable to production by using: export NODE_ENV=production');
} }
} };
exports.restartServer = async () => { exports.restartServer = async () => {
if (exports.server) { if (exports.server) {
console.log("Restarting express server"); console.log('Restarting express server');
await util.promisify(exports.server.close).bind(exports.server)(); await util.promisify(exports.server.close).bind(exports.server)();
} }
const app = express(); // New syntax for express v3 const app = express(); // New syntax for express v3
if (settings.ssl) { if (settings.ssl) {
console.log("SSL -- enabled"); console.log('SSL -- enabled');
console.log(`SSL -- server key file: ${settings.ssl.key}`); console.log(`SSL -- server key file: ${settings.ssl.key}`);
console.log(`SSL -- Certificate Authority's certificate file: ${settings.ssl.cert}`); console.log(`SSL -- Certificate Authority's certificate file: ${settings.ssl.cert}`);
const options = { const options = {
key: fs.readFileSync( settings.ssl.key ), key: fs.readFileSync(settings.ssl.key),
cert: fs.readFileSync( settings.ssl.cert ) cert: fs.readFileSync(settings.ssl.cert),
}; };
if (settings.ssl.ca) { if (settings.ssl.ca) {
@ -79,16 +79,16 @@ exports.restartServer = async () => {
exports.server = http.createServer(app); exports.server = http.createServer(app);
} }
app.use(function(req, res, next) { app.use((req, res, next) => {
// res.header("X-Frame-Options", "deny"); // breaks embedded pads // res.header("X-Frame-Options", "deny"); // breaks embedded pads
if (settings.ssl) { if (settings.ssl) {
// we use SSL // we use SSL
res.header("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); res.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
} }
// Stop IE going into compatability mode // Stop IE going into compatability mode
// https://github.com/ether/etherpad-lite/issues/2547 // https://github.com/ether/etherpad-lite/issues/2547
res.header("X-UA-Compatible", "IE=Edge,chrome=1"); res.header('X-UA-Compatible', 'IE=Edge,chrome=1');
// Enable a strong referrer policy. Same-origin won't drop Referers when // Enable a strong referrer policy. Same-origin won't drop Referers when
// loading local resources, but it will drop them when loading foreign resources. // loading local resources, but it will drop them when loading foreign resources.
@ -97,11 +97,11 @@ exports.restartServer = async () => {
// marked with <meta name="referrer" content="no-referrer"> // marked with <meta name="referrer" content="no-referrer">
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
// https://github.com/ether/etherpad-lite/pull/3636 // https://github.com/ether/etherpad-lite/pull/3636
res.header("Referrer-Policy", "same-origin"); res.header('Referrer-Policy', 'same-origin');
// send git version in the Server response header if exposeVersion is true. // send git version in the Server response header if exposeVersion is true.
if (settings.exposeVersion) { if (settings.exposeVersion) {
res.header("Server", serverName); res.header('Server', serverName);
} }
next(); next();
@ -165,13 +165,13 @@ exports.restartServer = async () => {
// //
// reference: https://github.com/expressjs/session/blob/v1.17.0/README.md#cookiesecure // reference: https://github.com/expressjs/session/blob/v1.17.0/README.md#cookiesecure
secure: 'auto', secure: 'auto',
} },
}); });
app.use(exports.sessionMiddleware); app.use(exports.sessionMiddleware);
app.use(cookieParser(settings.sessionKey, {})); app.use(cookieParser(settings.sessionKey, {}));
hooks.callAll("expressConfigure", {"app": app}); hooks.callAll('expressConfigure', {app});
hooks.callAll('expressCreateServer', {app, server: exports.server}); hooks.callAll('expressCreateServer', {app, server: exports.server});
await util.promisify(exports.server.listen).bind(exports.server)(settings.port, settings.ip); await util.promisify(exports.server.listen).bind(exports.server)(settings.port, settings.ip);

View file

@ -1,9 +1,9 @@
var eejs = require('ep_etherpad-lite/node/eejs'); const eejs = require('ep_etherpad-lite/node/eejs');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/admin', function(req, res) { args.app.get('/admin', (req, res) => {
if('/' != req.path[req.path.length-1]) return res.redirect('./admin/'); if ('/' != req.path[req.path.length - 1]) return res.redirect('./admin/');
res.send(eejs.require('ep_etherpad-lite/templates/admin/index.html', {req})); res.send(eejs.require('ep_etherpad-lite/templates/admin/index.html', {req}));
}); });
return cb(); return cb();
} };

View file

@ -1,13 +1,13 @@
var eejs = require('ep_etherpad-lite/node/eejs'); const eejs = require('ep_etherpad-lite/node/eejs');
var settings = require('ep_etherpad-lite/node/utils/Settings'); const settings = require('ep_etherpad-lite/node/utils/Settings');
var installer = require('ep_etherpad-lite/static/js/pluginfw/installer'); const installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs'); const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
var _ = require('underscore'); const _ = require('underscore');
var semver = require('semver'); const semver = require('semver');
const UpdateCheck = require('ep_etherpad-lite/node/utils/UpdateCheck'); const UpdateCheck = require('ep_etherpad-lite/node/utils/UpdateCheck');
exports.expressCreateServer = function(hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/admin/plugins', function(req, res) { args.app.get('/admin/plugins', (req, res) => {
res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins.html', { res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins.html', {
plugins: plugins.plugins, plugins: plugins.plugins,
req, req,
@ -16,114 +16,108 @@ exports.expressCreateServer = function(hook_name, args, cb) {
})); }));
}); });
args.app.get('/admin/plugins/info', function(req, res) { args.app.get('/admin/plugins/info', (req, res) => {
var gitCommit = settings.getGitCommit(); const gitCommit = settings.getGitCommit();
var epVersion = settings.getEpVersion(); const epVersion = settings.getEpVersion();
res.send(eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", { res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins-info.html', {
gitCommit: gitCommit, gitCommit,
epVersion: epVersion, epVersion,
latestVersion: UpdateCheck.getLatestVersion(), latestVersion: UpdateCheck.getLatestVersion(),
req, req,
})); }));
}); });
return cb(); return cb();
} };
exports.socketio = function(hook_name, args, cb) { exports.socketio = function (hook_name, args, cb) {
var io = args.io.of("/pluginfw/installer"); const io = args.io.of('/pluginfw/installer');
io.on('connection', function(socket) { io.on('connection', (socket) => {
if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return; if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return;
socket.on("getInstalled", function(query) { socket.on('getInstalled', (query) => {
// send currently installed plugins // send currently installed plugins
var installed = Object.keys(plugins.plugins).map(function(plugin) { const installed = Object.keys(plugins.plugins).map((plugin) => plugins.plugins[plugin].package);
return plugins.plugins[plugin].package
});
socket.emit("results:installed", {installed: installed}); socket.emit('results:installed', {installed});
}); });
socket.on("checkUpdates", async function() { socket.on('checkUpdates', async () => {
// Check plugins for updates // Check plugins for updates
try { try {
let results = await installer.getAvailablePlugins(/*maxCacheAge:*/ 60 * 10); const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ 60 * 10);
var updatable = _(plugins.plugins).keys().filter(function(plugin) { const updatable = _(plugins.plugins).keys().filter((plugin) => {
if (!results[plugin]) return false; if (!results[plugin]) return false;
var latestVersion = results[plugin].version; const latestVersion = results[plugin].version;
var currentVersion = plugins.plugins[plugin].package.version; const currentVersion = plugins.plugins[plugin].package.version;
return semver.gt(latestVersion, currentVersion); return semver.gt(latestVersion, currentVersion);
}); });
socket.emit("results:updatable", {updatable: updatable}); socket.emit('results:updatable', {updatable});
} catch (er) { } catch (er) {
console.warn(er); console.warn(er);
socket.emit("results:updatable", {updatable: {}}); socket.emit('results:updatable', {updatable: {}});
} }
}); });
socket.on("getAvailable", async function(query) { socket.on('getAvailable', async (query) => {
try { try {
let results = await installer.getAvailablePlugins(/*maxCacheAge:*/ false); const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ false);
socket.emit("results:available", results); socket.emit('results:available', results);
} catch (er) { } catch (er) {
console.error(er); console.error(er);
socket.emit("results:available", {}); socket.emit('results:available', {});
} }
}); });
socket.on("search", async function(query) { socket.on('search', async (query) => {
try { try {
let results = await installer.search(query.searchTerm, /*maxCacheAge:*/ 60 * 10); const results = await installer.search(query.searchTerm, /* maxCacheAge:*/ 60 * 10);
var res = Object.keys(results) let res = Object.keys(results)
.map(function(pluginName) { .map((pluginName) => results[pluginName])
return results[pluginName]; .filter((plugin) => !plugins.plugins[plugin.name]);
})
.filter(function(plugin) {
return !plugins.plugins[plugin.name];
});
res = sortPluginList(res, query.sortBy, query.sortDir) res = sortPluginList(res, query.sortBy, query.sortDir)
.slice(query.offset, query.offset+query.limit); .slice(query.offset, query.offset + query.limit);
socket.emit("results:search", {results: res, query: query}); socket.emit('results:search', {results: res, query});
} catch (er) { } catch (er) {
console.error(er); console.error(er);
socket.emit("results:search", {results: {}, query: query}); socket.emit('results:search', {results: {}, query});
} }
}); });
socket.on("install", function(plugin_name) { socket.on('install', (plugin_name) => {
installer.install(plugin_name, function(er) { installer.install(plugin_name, (er) => {
if (er) console.warn(er); if (er) console.warn(er);
socket.emit("finished:install", {plugin: plugin_name, code: er? er.code : null, error: er? er.message : null}); socket.emit('finished:install', {plugin: plugin_name, code: er ? er.code : null, error: er ? er.message : null});
}); });
}); });
socket.on("uninstall", function(plugin_name) { socket.on('uninstall', (plugin_name) => {
installer.uninstall(plugin_name, function(er) { installer.uninstall(plugin_name, (er) => {
if (er) console.warn(er); if (er) console.warn(er);
socket.emit("finished:uninstall", {plugin: plugin_name, error: er? er.message : null}); socket.emit('finished:uninstall', {plugin: plugin_name, error: er ? er.message : null});
}); });
}); });
}); });
return cb(); return cb();
} };
function sortPluginList(plugins, property, /*ASC?*/dir) { function sortPluginList(plugins, property, /* ASC?*/dir) {
return plugins.sort(function(a, b) { return plugins.sort((a, b) => {
if (a[property] < b[property]) { if (a[property] < b[property]) {
return dir? -1 : 1; return dir ? -1 : 1;
} }
if (a[property] > b[property]) { if (a[property] > b[property]) {
return dir? 1 : -1; return dir ? 1 : -1;
} }
// a must be equal to b // a must be equal to b

View file

@ -1,55 +1,52 @@
var eejs = require('ep_etherpad-lite/node/eejs'); const eejs = require('ep_etherpad-lite/node/eejs');
var settings = require('ep_etherpad-lite/node/utils/Settings'); const settings = require('ep_etherpad-lite/node/utils/Settings');
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var fs = require('fs'); const fs = require('fs');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/admin/settings', function(req, res) { args.app.get('/admin/settings', (req, res) => {
res.send(eejs.require('ep_etherpad-lite/templates/admin/settings.html', { res.send(eejs.require('ep_etherpad-lite/templates/admin/settings.html', {
req, req,
settings: "", settings: '',
search_results: {}, search_results: {},
errors: [] errors: [],
})); }));
}); });
return cb(); return cb();
} };
exports.socketio = function (hook_name, args, cb) { exports.socketio = function (hook_name, args, cb) {
var io = args.io.of("/settings"); const io = args.io.of('/settings');
io.on('connection', function (socket) { io.on('connection', (socket) => {
if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return; if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return;
socket.on("load", function (query) { socket.on('load', (query) => {
fs.readFile('settings.json', 'utf8', function (err,data) { fs.readFile('settings.json', 'utf8', (err, data) => {
if (err) { if (err) {
return console.log(err); return console.log(err);
} }
// if showSettingsInAdminPage is set to false, then return NOT_ALLOWED in the result // if showSettingsInAdminPage is set to false, then return NOT_ALLOWED in the result
if(settings.showSettingsInAdminPage === false) { if (settings.showSettingsInAdminPage === false) {
socket.emit("settings", {results: 'NOT_ALLOWED'}); socket.emit('settings', {results: 'NOT_ALLOWED'});
} } else {
else { socket.emit('settings', {results: data});
socket.emit("settings", {results: data});
} }
}); });
}); });
socket.on("saveSettings", function (settings) { socket.on('saveSettings', (settings) => {
fs.writeFile('settings.json', settings, function (err) { fs.writeFile('settings.json', settings, (err) => {
if (err) throw err; if (err) throw err;
socket.emit("saveprogress", "saved"); socket.emit('saveprogress', 'saved');
}); });
}); });
socket.on('restartServer', async () => { socket.on('restartServer', async () => {
console.log("Admin request to restart server through a socket on /admin/settings"); console.log('Admin request to restart server through a socket on /admin/settings');
settings.reloadSettings(); settings.reloadSettings();
await hooks.aCallAll('restartServer'); await hooks.aCallAll('restartServer');
}); });
}); });
return cb(); return cb();
} };

View file

@ -1,34 +1,34 @@
var log4js = require('log4js'); const log4js = require('log4js');
var clientLogger = log4js.getLogger("client"); const clientLogger = log4js.getLogger('client');
var formidable = require('formidable'); const formidable = require('formidable');
var apiHandler = require('../../handler/APIHandler'); const apiHandler = require('../../handler/APIHandler');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
//The Etherpad client side sends information about how a disconnect happened // The Etherpad client side sends information about how a disconnect happened
args.app.post('/ep/pad/connection-diagnostic-info', function(req, res) { args.app.post('/ep/pad/connection-diagnostic-info', (req, res) => {
new formidable.IncomingForm().parse(req, function(err, fields, files) { new formidable.IncomingForm().parse(req, (err, fields, files) => {
clientLogger.info("DIAGNOSTIC-INFO: " + fields.diagnosticInfo); clientLogger.info(`DIAGNOSTIC-INFO: ${fields.diagnosticInfo}`);
res.end("OK"); res.end('OK');
}); });
}); });
//The Etherpad client side sends information about client side javscript errors // The Etherpad client side sends information about client side javscript errors
args.app.post('/jserror', function(req, res) { args.app.post('/jserror', (req, res) => {
new formidable.IncomingForm().parse(req, function(err, fields, files) { new formidable.IncomingForm().parse(req, (err, fields, files) => {
try { try {
var data = JSON.parse(fields.errorInfo) var data = JSON.parse(fields.errorInfo);
}catch(e){ } catch (e) {
return res.end() return res.end();
} }
clientLogger.warn(data.msg+' --', data); clientLogger.warn(`${data.msg} --`, data);
res.end("OK"); res.end('OK');
}); });
}); });
//Provide a possibility to query the latest available API version // Provide a possibility to query the latest available API version
args.app.get('/api', function (req, res) { args.app.get('/api', (req, res) => {
res.json({"currentVersion" : apiHandler.latestApiVersion}); res.json({currentVersion: apiHandler.latestApiVersion});
}); });
return cb(); return cb();
} };

View file

@ -1,17 +1,17 @@
var stats = require('ep_etherpad-lite/node/stats') const stats = require('ep_etherpad-lite/node/stats');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
exports.app = args.app; exports.app = args.app;
// Handle errors // Handle errors
args.app.use(function(err, req, res, next) { args.app.use((err, req, res, next) => {
// if an error occurs Connect will pass it down // if an error occurs Connect will pass it down
// through these "error-handling" middleware // through these "error-handling" middleware
// allowing you to respond however you like // allowing you to respond however you like
res.status(500).send({ error: 'Sorry, something bad happened!' }); res.status(500).send({error: 'Sorry, something bad happened!'});
console.error(err.stack? err.stack : err.toString()); console.error(err.stack ? err.stack : err.toString());
stats.meter('http500').mark() stats.meter('http500').mark();
}); });
return cb(); return cb();
} };

View file

@ -1,44 +1,43 @@
const assert = require('assert').strict; const assert = require('assert').strict;
var hasPadAccess = require("../../padaccess"); const hasPadAccess = require('../../padaccess');
var settings = require('../../utils/Settings'); const settings = require('../../utils/Settings');
var exportHandler = require('../../handler/ExportHandler'); const exportHandler = require('../../handler/ExportHandler');
var importHandler = require('../../handler/ImportHandler'); const importHandler = require('../../handler/ImportHandler');
var padManager = require("../../db/PadManager"); const padManager = require('../../db/PadManager');
var readOnlyManager = require("../../db/ReadOnlyManager"); const readOnlyManager = require('../../db/ReadOnlyManager');
var authorManager = require("../../db/AuthorManager"); const authorManager = require('../../db/AuthorManager');
const rateLimit = require("express-rate-limit"); const rateLimit = require('express-rate-limit');
const securityManager = require("../../db/SecurityManager"); const securityManager = require('../../db/SecurityManager');
const webaccess = require("./webaccess"); const webaccess = require('./webaccess');
settings.importExportRateLimiting.onLimitReached = function(req, res, options) { settings.importExportRateLimiting.onLimitReached = function (req, res, options) {
// when the rate limiter triggers, write a warning in the logs // when the rate limiter triggers, write a warning in the logs
console.warn(`Import/Export rate limiter triggered on "${req.originalUrl}" for IP address ${req.ip}`); console.warn(`Import/Export rate limiter triggered on "${req.originalUrl}" for IP address ${req.ip}`);
} };
var limiter = rateLimit(settings.importExportRateLimiting); const limiter = rateLimit(settings.importExportRateLimiting);
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
// handle export requests // handle export requests
args.app.use('/p/:pad/:rev?/export/:type', limiter); args.app.use('/p/:pad/:rev?/export/:type', limiter);
args.app.get('/p/:pad/:rev?/export/:type', async function(req, res, next) { args.app.get('/p/:pad/:rev?/export/:type', async (req, res, next) => {
var types = ["pdf", "doc", "txt", "html", "odt", "etherpad"]; const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad'];
//send a 404 if we don't support this filetype // send a 404 if we don't support this filetype
if (types.indexOf(req.params.type) == -1) { if (types.indexOf(req.params.type) == -1) {
return next(); return next();
} }
// if abiword is disabled, and this is a format we only support with abiword, output a message // if abiword is disabled, and this is a format we only support with abiword, output a message
if (settings.exportAvailable() == "no" && if (settings.exportAvailable() == 'no' &&
["odt", "pdf", "doc"].indexOf(req.params.type) !== -1) { ['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) {
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format. There is no converter configured`); console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format. There is no converter configured`);
// ACHTUNG: do not include req.params.type in res.send() because there is no HTML escaping and it would lead to an XSS // ACHTUNG: do not include req.params.type in res.send() because there is no HTML escaping and it would lead to an XSS
res.send("This export is not enabled at this Etherpad instance. Set the path to Abiword or soffice (LibreOffice) in settings.json to enable this feature"); res.send('This export is not enabled at this Etherpad instance. Set the path to Abiword or soffice (LibreOffice) in settings.json to enable this feature');
return; return;
} }
res.header("Access-Control-Allow-Origin", "*"); res.header('Access-Control-Allow-Origin', '*');
if (await hasPadAccess(req, res)) { if (await hasPadAccess(req, res)) {
let padId = req.params.pad; let padId = req.params.pad;
@ -49,7 +48,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
padId = await readOnlyManager.getPadId(readOnlyId); padId = await readOnlyManager.getPadId(readOnlyId);
} }
let exists = await padManager.doesPadExists(padId); const exists = await padManager.doesPadExists(padId);
if (!exists) { if (!exists) {
console.warn(`Someone tried to export a pad that doesn't exist (${padId})`); console.warn(`Someone tried to export a pad that doesn't exist (${padId})`);
return next(); return next();
@ -62,7 +61,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// handle import requests // handle import requests
args.app.use('/p/:pad/import', limiter); args.app.use('/p/:pad/import', limiter);
args.app.post('/p/:pad/import', async function(req, res, next) { args.app.post('/p/:pad/import', async (req, res, next) => {
const {session: {user} = {}} = req; const {session: {user} = {}} = req;
const {accessStatus} = await securityManager.checkAccess( const {accessStatus} = await securityManager.checkAccess(
req.params.pad, req.cookies.sessionID, req.cookies.token, user); req.params.pad, req.cookies.sessionID, req.cookies.token, user);
@ -73,4 +72,4 @@ exports.expressCreateServer = function (hook_name, args, cb) {
}); });
return cb(); return cb();
} };

View file

@ -62,14 +62,14 @@ const RESERVED_WORDS = [
'volatile', 'volatile',
'while', 'while',
'with', 'with',
'yield' 'yield',
]; ];
const regex = /^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:".+"|\'.+\'|\d+)\])*?$/; const regex = /^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:".+"|\'.+\'|\d+)\])*?$/;
module.exports.check = function(inputStr) { module.exports.check = function (inputStr) {
var isValid = true; let isValid = true;
inputStr.split(".").forEach(function(part) { inputStr.split('.').forEach((part) => {
if (!regex.test(part)) { if (!regex.test(part)) {
isValid = false; isValid = false;
} }
@ -80,4 +80,4 @@ module.exports.check = function(inputStr) {
}); });
return isValid; return isValid;
} };

View file

@ -14,7 +14,7 @@
const OpenAPIBackend = require('openapi-backend').default; const OpenAPIBackend = require('openapi-backend').default;
const formidable = require('formidable'); const formidable = require('formidable');
const { promisify } = require('util'); const {promisify} = require('util');
const cloneDeep = require('lodash.clonedeep'); const cloneDeep = require('lodash.clonedeep');
const createHTTPError = require('http-errors'); const createHTTPError = require('http-errors');
@ -57,12 +57,12 @@ const resources = {
create: { create: {
operationId: 'createGroup', operationId: 'createGroup',
summary: 'creates a new group', summary: 'creates a new group',
responseSchema: { groupID: { type: 'string' } }, responseSchema: {groupID: {type: 'string'}},
}, },
createIfNotExistsFor: { createIfNotExistsFor: {
operationId: 'createGroupIfNotExistsFor', operationId: 'createGroupIfNotExistsFor',
summary: 'this functions helps you to map your application group ids to Etherpad group ids', summary: 'this functions helps you to map your application group ids to Etherpad group ids',
responseSchema: { groupID: { type: 'string' } }, responseSchema: {groupID: {type: 'string'}},
}, },
delete: { delete: {
operationId: 'deleteGroup', operationId: 'deleteGroup',
@ -71,7 +71,7 @@ const resources = {
listPads: { listPads: {
operationId: 'listPads', operationId: 'listPads',
summary: 'returns all pads of this group', summary: 'returns all pads of this group',
responseSchema: { padIDs: { type: 'array', items: { type: 'string' } } }, responseSchema: {padIDs: {type: 'array', items: {type: 'string'}}},
}, },
createPad: { createPad: {
operationId: 'createGroupPad', operationId: 'createGroupPad',
@ -80,12 +80,12 @@ const resources = {
listSessions: { listSessions: {
operationId: 'listSessionsOfGroup', operationId: 'listSessionsOfGroup',
summary: '', summary: '',
responseSchema: { sessions: { type: 'array', items: { $ref: '#/components/schemas/SessionInfo' } } }, responseSchema: {sessions: {type: 'array', items: {$ref: '#/components/schemas/SessionInfo'}}},
}, },
list: { list: {
operationId: 'listAllGroups', operationId: 'listAllGroups',
summary: '', summary: '',
responseSchema: { groupIDs: { type: 'array', items: { type: 'string' } } }, responseSchema: {groupIDs: {type: 'array', items: {type: 'string'}}},
}, },
}, },
@ -94,28 +94,28 @@ const resources = {
create: { create: {
operationId: 'createAuthor', operationId: 'createAuthor',
summary: 'creates a new author', summary: 'creates a new author',
responseSchema: { authorID: { type: 'string' } }, responseSchema: {authorID: {type: 'string'}},
}, },
createIfNotExistsFor: { createIfNotExistsFor: {
operationId: 'createAuthorIfNotExistsFor', operationId: 'createAuthorIfNotExistsFor',
summary: 'this functions helps you to map your application author ids to Etherpad author ids', summary: 'this functions helps you to map your application author ids to Etherpad author ids',
responseSchema: { authorID: { type: 'string' } }, responseSchema: {authorID: {type: 'string'}},
}, },
listPads: { listPads: {
operationId: 'listPadsOfAuthor', operationId: 'listPadsOfAuthor',
summary: 'returns an array of all pads this author contributed to', summary: 'returns an array of all pads this author contributed to',
responseSchema: { padIDs: { type: 'array', items: { type: 'string' } } }, responseSchema: {padIDs: {type: 'array', items: {type: 'string'}}},
}, },
listSessions: { listSessions: {
operationId: 'listSessionsOfAuthor', operationId: 'listSessionsOfAuthor',
summary: 'returns all sessions of an author', summary: 'returns all sessions of an author',
responseSchema: { sessions: { type: 'array', items: { $ref: '#/components/schemas/SessionInfo' } } }, responseSchema: {sessions: {type: 'array', items: {$ref: '#/components/schemas/SessionInfo'}}},
}, },
// We need an operation that return a UserInfo so it can be picked up by the codegen :( // We need an operation that return a UserInfo so it can be picked up by the codegen :(
getName: { getName: {
operationId: 'getAuthorName', operationId: 'getAuthorName',
summary: 'Returns the Author Name of the author', summary: 'Returns the Author Name of the author',
responseSchema: { info: { $ref: '#/components/schemas/UserInfo' } }, responseSchema: {info: {$ref: '#/components/schemas/UserInfo'}},
}, },
}, },
@ -124,7 +124,7 @@ const resources = {
create: { create: {
operationId: 'createSession', operationId: 'createSession',
summary: 'creates a new session. validUntil is an unix timestamp in seconds', summary: 'creates a new session. validUntil is an unix timestamp in seconds',
responseSchema: { sessionID: { type: 'string' } }, responseSchema: {sessionID: {type: 'string'}},
}, },
delete: { delete: {
operationId: 'deleteSession', operationId: 'deleteSession',
@ -134,7 +134,7 @@ const resources = {
info: { info: {
operationId: 'getSessionInfo', operationId: 'getSessionInfo',
summary: 'returns informations about a session', summary: 'returns informations about a session',
responseSchema: { info: { $ref: '#/components/schemas/SessionInfo' } }, responseSchema: {info: {$ref: '#/components/schemas/SessionInfo'}},
}, },
}, },
@ -143,7 +143,7 @@ const resources = {
listAll: { listAll: {
operationId: 'listAllPads', operationId: 'listAllPads',
summary: 'list all the pads', summary: 'list all the pads',
responseSchema: { padIDs: { type: 'array', items: { type: 'string' } } }, responseSchema: {padIDs: {type: 'array', items: {type: 'string'}}},
}, },
createDiffHTML: { createDiffHTML: {
operationId: 'createDiffHTML', operationId: 'createDiffHTML',
@ -158,7 +158,7 @@ const resources = {
getText: { getText: {
operationId: 'getText', operationId: 'getText',
summary: 'returns the text of a pad', summary: 'returns the text of a pad',
responseSchema: { text: { type: 'string' } }, responseSchema: {text: {type: 'string'}},
}, },
setText: { setText: {
operationId: 'setText', operationId: 'setText',
@ -167,7 +167,7 @@ const resources = {
getHTML: { getHTML: {
operationId: 'getHTML', operationId: 'getHTML',
summary: 'returns the text of a pad formatted as HTML', summary: 'returns the text of a pad formatted as HTML',
responseSchema: { html: { type: 'string' } }, responseSchema: {html: {type: 'string'}},
}, },
setHTML: { setHTML: {
operationId: 'setHTML', operationId: 'setHTML',
@ -176,12 +176,12 @@ const resources = {
getRevisionsCount: { getRevisionsCount: {
operationId: 'getRevisionsCount', operationId: 'getRevisionsCount',
summary: 'returns the number of revisions of this pad', summary: 'returns the number of revisions of this pad',
responseSchema: { revisions: { type: 'integer' } }, responseSchema: {revisions: {type: 'integer'}},
}, },
getLastEdited: { getLastEdited: {
operationId: 'getLastEdited', operationId: 'getLastEdited',
summary: 'returns the timestamp of the last revision of the pad', summary: 'returns the timestamp of the last revision of the pad',
responseSchema: { lastEdited: { type: 'integer' } }, responseSchema: {lastEdited: {type: 'integer'}},
}, },
delete: { delete: {
operationId: 'deletePad', operationId: 'deletePad',
@ -190,7 +190,7 @@ const resources = {
getReadOnlyID: { getReadOnlyID: {
operationId: 'getReadOnlyID', operationId: 'getReadOnlyID',
summary: 'returns the read only link of a pad', summary: 'returns the read only link of a pad',
responseSchema: { readOnlyID: { type: 'string' } }, responseSchema: {readOnlyID: {type: 'string'}},
}, },
setPublicStatus: { setPublicStatus: {
operationId: 'setPublicStatus', operationId: 'setPublicStatus',
@ -199,22 +199,22 @@ const resources = {
getPublicStatus: { getPublicStatus: {
operationId: 'getPublicStatus', operationId: 'getPublicStatus',
summary: 'return true of false', summary: 'return true of false',
responseSchema: { publicStatus: { type: 'boolean' } }, responseSchema: {publicStatus: {type: 'boolean'}},
}, },
authors: { authors: {
operationId: 'listAuthorsOfPad', operationId: 'listAuthorsOfPad',
summary: 'returns an array of authors who contributed to this pad', summary: 'returns an array of authors who contributed to this pad',
responseSchema: { authorIDs: { type: 'array', items: { type: 'string' } } }, responseSchema: {authorIDs: {type: 'array', items: {type: 'string'}}},
}, },
usersCount: { usersCount: {
operationId: 'padUsersCount', operationId: 'padUsersCount',
summary: 'returns the number of user that are currently editing this pad', summary: 'returns the number of user that are currently editing this pad',
responseSchema: { padUsersCount: { type: 'integer' } }, responseSchema: {padUsersCount: {type: 'integer'}},
}, },
users: { users: {
operationId: 'padUsers', operationId: 'padUsers',
summary: 'returns the list of users that are currently editing this pad', summary: 'returns the list of users that are currently editing this pad',
responseSchema: { padUsers: { type: 'array', items: { $ref: '#/components/schemas/UserInfo' } } }, responseSchema: {padUsers: {type: 'array', items: {$ref: '#/components/schemas/UserInfo'}}},
}, },
sendClientsMessage: { sendClientsMessage: {
operationId: 'sendClientsMessage', operationId: 'sendClientsMessage',
@ -227,13 +227,13 @@ const resources = {
getChatHistory: { getChatHistory: {
operationId: 'getChatHistory', operationId: 'getChatHistory',
summary: 'returns the chat history', summary: 'returns the chat history',
responseSchema: { messages: { type: 'array', items: { $ref: '#/components/schemas/Message' } } }, responseSchema: {messages: {type: 'array', items: {$ref: '#/components/schemas/Message'}}},
}, },
// We need an operation that returns a Message so it can be picked up by the codegen :( // We need an operation that returns a Message so it can be picked up by the codegen :(
getChatHead: { getChatHead: {
operationId: 'getChatHead', operationId: 'getChatHead',
summary: 'returns the chatHead (chat-message) of the pad', summary: 'returns the chatHead (chat-message) of the pad',
responseSchema: { chatHead: { $ref: '#/components/schemas/Message' } }, responseSchema: {chatHead: {$ref: '#/components/schemas/Message'}},
}, },
appendChatMessage: { appendChatMessage: {
operationId: 'appendChatMessage', operationId: 'appendChatMessage',
@ -384,10 +384,10 @@ const defaultResponseRefs = {
const operations = {}; const operations = {};
for (const resource in resources) { for (const resource in resources) {
for (const action in resources[resource]) { for (const action in resources[resource]) {
const { operationId, responseSchema, ...operation } = resources[resource][action]; const {operationId, responseSchema, ...operation} = resources[resource][action];
// add response objects // add response objects
const responses = { ...defaultResponseRefs }; const responses = {...defaultResponseRefs};
if (responseSchema) { if (responseSchema) {
responses[200] = cloneDeep(defaultResponses.Success); responses[200] = cloneDeep(defaultResponses.Success);
responses[200].content['application/json'].schema.properties.data = { responses[200].content['application/json'].schema.properties.data = {
@ -478,14 +478,14 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
}, },
}, },
}, },
security: [{ ApiKey: [] }], security: [{ApiKey: []}],
}; };
// build operations // build operations
for (const funcName in apiHandler.version[version]) { for (const funcName in apiHandler.version[version]) {
let operation = {}; let operation = {};
if (operations[funcName]) { if (operations[funcName]) {
operation = { ...operations[funcName] }; operation = {...operations[funcName]};
} else { } else {
// console.warn(`No operation found for function: ${funcName}`); // console.warn(`No operation found for function: ${funcName}`);
operation = { operation = {
@ -497,7 +497,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
// set parameters // set parameters
operation.parameters = operation.parameters || []; operation.parameters = operation.parameters || [];
for (const paramName of apiHandler.version[version][funcName]) { for (const paramName of apiHandler.version[version][funcName]) {
operation.parameters.push({ $ref: `#/components/parameters/${paramName}` }); operation.parameters.push({$ref: `#/components/parameters/${paramName}`});
if (!definition.components.parameters[paramName]) { if (!definition.components.parameters[paramName]) {
definition.components.parameters[paramName] = { definition.components.parameters[paramName] = {
name: paramName, name: paramName,
@ -533,7 +533,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
}; };
exports.expressCreateServer = (hookName, args, cb) => { exports.expressCreateServer = (hookName, args, cb) => {
const { app } = args; const {app} = args;
// create openapi-backend handlers for each api version under /api/{version}/* // create openapi-backend handlers for each api version under /api/{version}/*
for (const version in apiHandler.version) { for (const version in apiHandler.version) {
@ -550,7 +550,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
app.get(`${apiRoot}/openapi.json`, (req, res) => { app.get(`${apiRoot}/openapi.json`, (req, res) => {
// For openapi definitions, wide CORS is probably fine // For openapi definitions, wide CORS is probably fine
res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Origin', '*');
res.json({ ...definition, servers: [generateServerForApiVersion(apiRoot, req)] }); res.json({...definition, servers: [generateServerForApiVersion(apiRoot, req)]});
}); });
// serve latest openapi definition file under /api/openapi.json // serve latest openapi definition file under /api/openapi.json
@ -558,7 +558,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
if (isLatestAPIVersion) { if (isLatestAPIVersion) {
app.get(`/${style}/openapi.json`, (req, res) => { app.get(`/${style}/openapi.json`, (req, res) => {
res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Origin', '*');
res.json({ ...definition, servers: [generateServerForApiVersion(apiRoot, req)] }); res.json({...definition, servers: [generateServerForApiVersion(apiRoot, req)]});
}); });
} }
@ -586,7 +586,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
for (const funcName in apiHandler.version[version]) { for (const funcName in apiHandler.version[version]) {
const handler = async (c, req, res) => { const handler = async (c, req, res) => {
// parse fields from request // parse fields from request
const { header, params, query } = c.request; const {header, params, query} = c.request;
// read form data if method was POST // read form data if method was POST
let formData = {}; let formData = {};
@ -602,7 +602,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
apiLogger.info(`REQUEST, v${version}:${funcName}, ${JSON.stringify(fields)}`); apiLogger.info(`REQUEST, v${version}:${funcName}, ${JSON.stringify(fields)}`);
// pass to api handler // pass to api handler
let data = await apiHandler.handle(version, funcName, fields, req, res).catch((err) => { const data = await apiHandler.handle(version, funcName, fields, req, res).catch((err) => {
// convert all errors to http errors // convert all errors to http errors
if (createHTTPError.isHttpError(err)) { if (createHTTPError.isHttpError(err)) {
// pass http errors thrown by handler forward // pass http errors thrown by handler forward
@ -620,7 +620,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
}); });
// return in common format // return in common format
let response = { code: 0, message: 'ok', data: data || null }; const response = {code: 0, message: 'ok', data: data || null};
// log response // log response
apiLogger.info(`RESPONSE, ${funcName}, ${JSON.stringify(response)}`); apiLogger.info(`RESPONSE, ${funcName}, ${JSON.stringify(response)}`);
@ -654,24 +654,24 @@ exports.expressCreateServer = (hookName, args, cb) => {
// https://github.com/ether/etherpad-lite/tree/master/doc/api/http_api.md#response-format // https://github.com/ether/etherpad-lite/tree/master/doc/api/http_api.md#response-format
switch (res.statusCode) { switch (res.statusCode) {
case 403: // forbidden case 403: // forbidden
response = { code: 4, message: err.message, data: null }; response = {code: 4, message: err.message, data: null};
break; break;
case 401: // unauthorized (no or wrong api key) case 401: // unauthorized (no or wrong api key)
response = { code: 4, message: err.message, data: null }; response = {code: 4, message: err.message, data: null};
break; break;
case 404: // not found (no such function) case 404: // not found (no such function)
response = { code: 3, message: err.message, data: null }; response = {code: 3, message: err.message, data: null};
break; break;
case 500: // server error (internal error) case 500: // server error (internal error)
response = { code: 2, message: err.message, data: null }; response = {code: 2, message: err.message, data: null};
break; break;
case 400: // bad request (wrong parameters) case 400: // bad request (wrong parameters)
// respond with 200 OK to keep old behavior and pass tests // respond with 200 OK to keep old behavior and pass tests
res.statusCode = 200; // @TODO: this is bad api design res.statusCode = 200; // @TODO: this is bad api design
response = { code: 1, message: err.message, data: null }; response = {code: 1, message: err.message, data: null};
break; break;
default: default:
response = { code: 1, message: err.message, data: null }; response = {code: 1, message: err.message, data: null};
break; break;
} }
} }

View file

@ -1,13 +1,12 @@
var readOnlyManager = require("../../db/ReadOnlyManager"); const readOnlyManager = require('../../db/ReadOnlyManager');
var hasPadAccess = require("../../padaccess"); const hasPadAccess = require('../../padaccess');
var exporthtml = require("../../utils/ExportHtml"); const exporthtml = require('../../utils/ExportHtml');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
// serve read only pad // serve read only pad
args.app.get('/ro/:id', async function(req, res) { args.app.get('/ro/:id', async (req, res) => {
// translate the read only pad to a padId // translate the read only pad to a padId
let padId = await readOnlyManager.getPadId(req.params.id); const padId = await readOnlyManager.getPadId(req.params.id);
if (padId == null) { if (padId == null) {
res.status(404).send('404 - Not Found'); res.status(404).send('404 - Not Found');
return; return;
@ -18,9 +17,9 @@ exports.expressCreateServer = function (hook_name, args, cb) {
if (await hasPadAccess(req, res)) { if (await hasPadAccess(req, res)) {
// render the html document // render the html document
let html = await exporthtml.getPadHTMLDocument(padId, null); const html = await exporthtml.getPadHTMLDocument(padId, null);
res.send(html); res.send(html);
} }
}); });
return cb(); return cb();
} };

View file

@ -1,30 +1,29 @@
var padManager = require('../../db/PadManager'); const padManager = require('../../db/PadManager');
var url = require('url'); const url = require('url');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
// redirects browser to the pad's sanitized url if needed. otherwise, renders the html // redirects browser to the pad's sanitized url if needed. otherwise, renders the html
args.app.param('pad', async function (req, res, next, padId) { args.app.param('pad', async (req, res, next, padId) => {
// ensure the padname is valid and the url doesn't end with a / // ensure the padname is valid and the url doesn't end with a /
if (!padManager.isValidPadId(padId) || /\/$/.test(req.url)) { if (!padManager.isValidPadId(padId) || /\/$/.test(req.url)) {
res.status(404).send('Such a padname is forbidden'); res.status(404).send('Such a padname is forbidden');
return; return;
} }
let sanitizedPadId = await padManager.sanitizePadId(padId); const sanitizedPadId = await padManager.sanitizePadId(padId);
if (sanitizedPadId === padId) { if (sanitizedPadId === padId) {
// the pad id was fine, so just render it // the pad id was fine, so just render it
next(); next();
} else { } else {
// the pad id was sanitized, so we redirect to the sanitized version // the pad id was sanitized, so we redirect to the sanitized version
var real_url = sanitizedPadId; let real_url = sanitizedPadId;
real_url = encodeURIComponent(real_url); real_url = encodeURIComponent(real_url);
var query = url.parse(req.url).query; const query = url.parse(req.url).query;
if ( query ) real_url += '?' + query; if (query) real_url += `?${query}`;
res.header('Location', real_url); res.header('Location', real_url);
res.status(302).send('You should be redirected to <a href="' + real_url + '">' + real_url + '</a>'); res.status(302).send(`You should be redirected to <a href="${real_url}">${real_url}</a>`);
} }
}); });
return cb(); return cb();
} };

View file

@ -1,19 +1,19 @@
const express = require("../express"); const express = require('../express');
const proxyaddr = require('proxy-addr'); const proxyaddr = require('proxy-addr');
var settings = require('../../utils/Settings'); const settings = require('../../utils/Settings');
var socketio = require('socket.io'); const socketio = require('socket.io');
var socketIORouter = require("../../handler/SocketIORouter"); const socketIORouter = require('../../handler/SocketIORouter');
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var padMessageHandler = require("../../handler/PadMessageHandler"); const padMessageHandler = require('../../handler/PadMessageHandler');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
//init socket.io and redirect all requests to the MessageHandler // init socket.io and redirect all requests to the MessageHandler
// there shouldn't be a browser that isn't compatible to all // there shouldn't be a browser that isn't compatible to all
// transports in this list at once // transports in this list at once
// e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling // e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling
var io = socketio({ const io = socketio({
transports: settings.socketTransportProtocols transports: settings.socketTransportProtocols,
}).listen(args.server, { }).listen(args.server, {
/* /*
* Do not set the "io" cookie. * Do not set the "io" cookie.
@ -61,17 +61,17 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// https://github.com/Automattic/socket.io/wiki/Migrating-to-1.0 // https://github.com/Automattic/socket.io/wiki/Migrating-to-1.0
// This debug logging environment is set in Settings.js // This debug logging environment is set in Settings.js
//minify socket.io javascript // minify socket.io javascript
// Due to a shitty decision by the SocketIO team minification is // Due to a shitty decision by the SocketIO team minification is
// no longer available, details available at: // no longer available, details available at:
// http://stackoverflow.com/questions/23981741/minify-socket-io-socket-io-js-with-1-0 // http://stackoverflow.com/questions/23981741/minify-socket-io-socket-io-js-with-1-0
// if(settings.minify) io.enable('browser client minification'); // if(settings.minify) io.enable('browser client minification');
//Initalize the Socket.IO Router // Initalize the Socket.IO Router
socketIORouter.setSocketIO(io); socketIORouter.setSocketIO(io);
socketIORouter.addComponent("pad", padMessageHandler); socketIORouter.addComponent('pad', padMessageHandler);
hooks.callAll("socketio", {"app": args.app, "io": io, "server": args.server}); hooks.callAll('socketio', {app: args.app, io, server: args.server});
return cb(); return cb();
} };

View file

@ -1,83 +1,81 @@
var path = require('path'); const path = require('path');
var eejs = require('ep_etherpad-lite/node/eejs'); const eejs = require('ep_etherpad-lite/node/eejs');
var toolbar = require("ep_etherpad-lite/node/utils/toolbar"); const toolbar = require('ep_etherpad-lite/node/utils/toolbar');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var settings = require('../../utils/Settings'); const settings = require('../../utils/Settings');
const webaccess = require('./webaccess'); const webaccess = require('./webaccess');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
// expose current stats // expose current stats
args.app.get('/stats', function(req, res) { args.app.get('/stats', (req, res) => {
res.json(require('ep_etherpad-lite/node/stats').toJSON()) res.json(require('ep_etherpad-lite/node/stats').toJSON());
}) });
//serve index.html under / // serve index.html under /
args.app.get('/', function(req, res) { args.app.get('/', (req, res) => {
res.send(eejs.require('ep_etherpad-lite/templates/index.html', {req})); res.send(eejs.require('ep_etherpad-lite/templates/index.html', {req}));
}); });
//serve javascript.html // serve javascript.html
args.app.get('/javascript', function(req, res) { args.app.get('/javascript', (req, res) => {
res.send(eejs.require('ep_etherpad-lite/templates/javascript.html', {req})); res.send(eejs.require('ep_etherpad-lite/templates/javascript.html', {req}));
}); });
//serve robots.txt // serve robots.txt
args.app.get('/robots.txt', function(req, res) { args.app.get('/robots.txt', (req, res) => {
var filePath = path.join(settings.root, "src", "static", "skins", settings.skinName, "robots.txt"); let filePath = path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'robots.txt');
res.sendFile(filePath, function(err) { res.sendFile(filePath, (err) => {
//there is no custom robots.txt, send the default robots.txt which dissallows all // there is no custom robots.txt, send the default robots.txt which dissallows all
if(err) if (err) {
{ filePath = path.join(settings.root, 'src', 'static', 'robots.txt');
filePath = path.join(settings.root, "src", "static", "robots.txt");
res.sendFile(filePath); res.sendFile(filePath);
} }
}); });
}); });
//serve pad.html under /p // serve pad.html under /p
args.app.get('/p/:pad', function(req, res, next) { args.app.get('/p/:pad', (req, res, next) => {
// The below might break for pads being rewritten // The below might break for pads being rewritten
const isReadOnly = const isReadOnly =
req.url.indexOf("/p/r.") === 0 || !webaccess.userCanModify(req.params.pad, req); req.url.indexOf('/p/r.') === 0 || !webaccess.userCanModify(req.params.pad, req);
hooks.callAll("padInitToolbar", { hooks.callAll('padInitToolbar', {
toolbar: toolbar, toolbar,
isReadOnly: isReadOnly isReadOnly,
}); });
res.send(eejs.require("ep_etherpad-lite/templates/pad.html", { res.send(eejs.require('ep_etherpad-lite/templates/pad.html', {
req: req, req,
toolbar: toolbar, toolbar,
isReadOnly: isReadOnly isReadOnly,
})); }));
}); });
//serve timeslider.html under /p/$padname/timeslider // serve timeslider.html under /p/$padname/timeslider
args.app.get('/p/:pad/timeslider', function(req, res, next) { args.app.get('/p/:pad/timeslider', (req, res, next) => {
hooks.callAll("padInitToolbar", { hooks.callAll('padInitToolbar', {
toolbar: toolbar toolbar,
}); });
res.send(eejs.require("ep_etherpad-lite/templates/timeslider.html", { res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', {
req: req, req,
toolbar: toolbar toolbar,
})); }));
}); });
//serve favicon.ico from all path levels except as a pad name // serve favicon.ico from all path levels except as a pad name
args.app.get( /\/favicon.ico$/, function(req, res) { args.app.get(/\/favicon.ico$/, (req, res) => {
var filePath = path.join(settings.root, "src", "static", "skins", settings.skinName, "favicon.ico"); let filePath = path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico');
res.sendFile(filePath, function(err) { res.sendFile(filePath, (err) => {
//there is no custom favicon, send the default favicon // there is no custom favicon, send the default favicon
if(err) if (err) {
{ filePath = path.join(settings.root, 'src', 'static', 'favicon.ico');
filePath = path.join(settings.root, "src", "static", "favicon.ico");
res.sendFile(filePath); res.sendFile(filePath);
} }
}); });
}); });
return cb(); return cb();
} };

View file

@ -1,14 +1,13 @@
var minify = require('../../utils/Minify'); const minify = require('../../utils/Minify');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugin_defs"); const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
var CachingMiddleware = require('../../utils/caching_middleware'); const CachingMiddleware = require('../../utils/caching_middleware');
var settings = require("../../utils/Settings"); const settings = require('../../utils/Settings');
var Yajsml = require('etherpad-yajsml'); const Yajsml = require('etherpad-yajsml');
var _ = require("underscore"); const _ = require('underscore');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
// Cache both minified and static. // Cache both minified and static.
var assetCache = new CachingMiddleware; const assetCache = new CachingMiddleware();
args.app.all(/\/javascripts\/(.*)/, assetCache.handle); args.app.all(/\/javascripts\/(.*)/, assetCache.handle);
// Minify will serve static files compressed (minify enabled). It also has // Minify will serve static files compressed (minify enabled). It also has
@ -18,43 +17,42 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// Setup middleware that will package JavaScript files served by minify for // Setup middleware that will package JavaScript files served by minify for
// CommonJS loader on the client-side. // CommonJS loader on the client-side.
// Hostname "invalid.invalid" is a dummy value to allow parsing as a URI. // Hostname "invalid.invalid" is a dummy value to allow parsing as a URI.
var jsServer = new (Yajsml.Server)({ const jsServer = new (Yajsml.Server)({
rootPath: 'javascripts/src/' rootPath: 'javascripts/src/',
, rootURI: 'http://invalid.invalid/static/js/' rootURI: 'http://invalid.invalid/static/js/',
, libraryPath: 'javascripts/lib/' libraryPath: 'javascripts/lib/',
, libraryURI: 'http://invalid.invalid/static/plugins/' libraryURI: 'http://invalid.invalid/static/plugins/',
, requestURIs: minify.requestURIs // Loop-back is causing problems, this is a workaround. requestURIs: minify.requestURIs, // Loop-back is causing problems, this is a workaround.
}); });
var StaticAssociator = Yajsml.associators.StaticAssociator; const StaticAssociator = Yajsml.associators.StaticAssociator;
var associations = const associations =
Yajsml.associators.associationsForSimpleMapping(minify.tar); Yajsml.associators.associationsForSimpleMapping(minify.tar);
var associator = new StaticAssociator(associations); const associator = new StaticAssociator(associations);
jsServer.setAssociator(associator); jsServer.setAssociator(associator);
args.app.use(jsServer.handle.bind(jsServer)); args.app.use(jsServer.handle.bind(jsServer));
// serve plugin definitions // serve plugin definitions
// not very static, but served here so that client can do require("pluginfw/static/js/plugin-definitions.js"); // not very static, but served here so that client can do require("pluginfw/static/js/plugin-definitions.js");
args.app.get('/pluginfw/plugin-definitions.json', function (req, res, next) { args.app.get('/pluginfw/plugin-definitions.json', (req, res, next) => {
const clientParts = _(plugins.parts)
.filter((part) => _(part).has('client_hooks'));
var clientParts = _(plugins.parts) const clientPlugins = {};
.filter(function(part){ return _(part).has('client_hooks') });
var clientPlugins = {};
_(clientParts).chain() _(clientParts).chain()
.map(function(part){ return part.plugin }) .map((part) => part.plugin)
.uniq() .uniq()
.each(function(name){ .each((name) => {
clientPlugins[name] = _(plugins.plugins[name]).clone(); clientPlugins[name] = _(plugins.plugins[name]).clone();
delete clientPlugins[name]['package']; delete clientPlugins[name].package;
}); });
res.header("Content-Type","application/json; charset=utf-8"); res.header('Content-Type', 'application/json; charset=utf-8');
res.write(JSON.stringify({"plugins": clientPlugins, "parts": clientParts})); res.write(JSON.stringify({plugins: clientPlugins, parts: clientParts}));
res.end(); res.end();
}); });
return cb(); return cb();
} };

View file

@ -1,13 +1,13 @@
var path = require("path") const path = require('path');
, npm = require("npm") const npm = require('npm');
, fs = require("fs") const fs = require('fs');
, util = require("util"); const util = require('util');
exports.expressCreateServer = function (hook_name, args, cb) { exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/tests/frontend/specs_list.js', async function(req, res) { args.app.get('/tests/frontend/specs_list.js', async (req, res) => {
let [coreTests, pluginTests] = await Promise.all([ const [coreTests, pluginTests] = await Promise.all([
exports.getCoreTests(), exports.getCoreTests(),
exports.getPluginTests() exports.getPluginTests(),
]); ]);
// merge the two sets of results // merge the two sets of results
@ -16,79 +16,77 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// Keep only *.js files // Keep only *.js files
files = files.filter((f) => f.endsWith('.js')); files = files.filter((f) => f.endsWith('.js'));
console.debug("Sent browser the following test specs:", files); console.debug('Sent browser the following test specs:', files);
res.setHeader('content-type', 'text/javascript'); res.setHeader('content-type', 'text/javascript');
res.end("var specs_list = " + JSON.stringify(files) + ";\n"); res.end(`var specs_list = ${JSON.stringify(files)};\n`);
}); });
// path.join seems to normalize by default, but we'll just be explicit // path.join seems to normalize by default, but we'll just be explicit
var rootTestFolder = path.normalize(path.join(npm.root, "../tests/frontend/")); const rootTestFolder = path.normalize(path.join(npm.root, '../tests/frontend/'));
var url2FilePath = function(url) { const url2FilePath = function (url) {
var subPath = url.substr("/tests/frontend".length); let subPath = url.substr('/tests/frontend'.length);
if (subPath == "") { if (subPath == '') {
subPath = "index.html" subPath = 'index.html';
} }
subPath = subPath.split("?")[0]; subPath = subPath.split('?')[0];
var filePath = path.normalize(path.join(rootTestFolder, subPath)); let filePath = path.normalize(path.join(rootTestFolder, subPath));
// make sure we jail the paths to the test folder, otherwise serve index // make sure we jail the paths to the test folder, otherwise serve index
if (filePath.indexOf(rootTestFolder) !== 0) { if (filePath.indexOf(rootTestFolder) !== 0) {
filePath = path.join(rootTestFolder, "index.html"); filePath = path.join(rootTestFolder, 'index.html');
} }
return filePath; return filePath;
} };
args.app.get('/tests/frontend/specs/*', function (req, res) { args.app.get('/tests/frontend/specs/*', (req, res) => {
var specFilePath = url2FilePath(req.url); const specFilePath = url2FilePath(req.url);
var specFileName = path.basename(specFilePath); const specFileName = path.basename(specFilePath);
fs.readFile(specFilePath, function(err, content) { fs.readFile(specFilePath, (err, content) => {
if (err) { return res.send(500); } if (err) { return res.send(500); }
content = "describe(" + JSON.stringify(specFileName) + ", function(){ " + content + " });"; content = `describe(${JSON.stringify(specFileName)}, function(){ ${content} });`;
res.send(content); res.send(content);
}); });
}); });
args.app.get('/tests/frontend/*', function (req, res) { args.app.get('/tests/frontend/*', (req, res) => {
var filePath = url2FilePath(req.url); const filePath = url2FilePath(req.url);
res.sendFile(filePath); res.sendFile(filePath);
}); });
args.app.get('/tests/frontend', function (req, res) { args.app.get('/tests/frontend', (req, res) => {
res.redirect('/tests/frontend/index.html'); res.redirect('/tests/frontend/index.html');
}); });
return cb(); return cb();
} };
const readdir = util.promisify(fs.readdir); const readdir = util.promisify(fs.readdir);
exports.getPluginTests = async function(callback) { exports.getPluginTests = async function (callback) {
const moduleDir = "node_modules/"; const moduleDir = 'node_modules/';
const specPath = "/static/tests/frontend/specs/"; const specPath = '/static/tests/frontend/specs/';
const staticDir = "/static/plugins/"; const staticDir = '/static/plugins/';
let pluginSpecs = []; const pluginSpecs = [];
let plugins = await readdir(moduleDir); const plugins = await readdir(moduleDir);
let promises = plugins const promises = plugins
.map(plugin => [ plugin, moduleDir + plugin + specPath] ) .map((plugin) => [plugin, moduleDir + plugin + specPath])
.filter(([plugin, specDir]) => fs.existsSync(specDir)) // check plugin exists .filter(([plugin, specDir]) => fs.existsSync(specDir)) // check plugin exists
.map(([plugin, specDir]) => { .map(([plugin, specDir]) => readdir(specDir)
return readdir(specDir) .then((specFiles) => specFiles.map((spec) => {
.then(specFiles => specFiles.map(spec => { pluginSpecs.push(staticDir + plugin + specPath + spec);
pluginSpecs.push(staticDir + plugin + specPath + spec); })));
}));
});
return Promise.all(promises).then(() => pluginSpecs); return Promise.all(promises).then(() => pluginSpecs);
} };
exports.getCoreTests = function() { exports.getCoreTests = function () {
// get the core test specs // get the core test specs
return readdir('tests/frontend/specs'); return readdir('tests/frontend/specs');
} };

View file

@ -1,58 +1,58 @@
var languages = require('languages4translatewiki') const languages = require('languages4translatewiki');
, fs = require('fs') const fs = require('fs');
, path = require('path') const path = require('path');
, _ = require('underscore') const _ = require('underscore');
, npm = require('npm') const npm = require('npm');
, plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs.js').plugins const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs.js').plugins;
, semver = require('semver') const semver = require('semver');
, existsSync = require('../utils/path_exists') const existsSync = require('../utils/path_exists');
, settings = require('../utils/Settings') const settings = require('../utils/Settings')
; ;
// returns all existing messages merged together and grouped by langcode // returns all existing messages merged together and grouped by langcode
// {es: {"foo": "string"}, en:...} // {es: {"foo": "string"}, en:...}
function getAllLocales() { function getAllLocales() {
var locales2paths = {}; const locales2paths = {};
// Puts the paths of all locale files contained in a given directory // Puts the paths of all locale files contained in a given directory
// into `locales2paths` (files from various dirs are grouped by lang code) // into `locales2paths` (files from various dirs are grouped by lang code)
// (only json files with valid language code as name) // (only json files with valid language code as name)
function extractLangs(dir) { function extractLangs(dir) {
if(!existsSync(dir)) return; if (!existsSync(dir)) return;
var stat = fs.lstatSync(dir); let stat = fs.lstatSync(dir);
if (!stat.isDirectory() || stat.isSymbolicLink()) return; if (!stat.isDirectory() || stat.isSymbolicLink()) return;
fs.readdirSync(dir).forEach(function(file) { fs.readdirSync(dir).forEach((file) => {
file = path.resolve(dir, file); file = path.resolve(dir, file);
stat = fs.lstatSync(file); stat = fs.lstatSync(file);
if (stat.isDirectory() || stat.isSymbolicLink()) return; if (stat.isDirectory() || stat.isSymbolicLink()) return;
var ext = path.extname(file) const ext = path.extname(file);
, locale = path.basename(file, ext).toLowerCase(); const locale = path.basename(file, ext).toLowerCase();
if ((ext == '.json') && languages.isValid(locale)) { if ((ext == '.json') && languages.isValid(locale)) {
if(!locales2paths[locale]) locales2paths[locale] = []; if (!locales2paths[locale]) locales2paths[locale] = [];
locales2paths[locale].push(file); locales2paths[locale].push(file);
} }
}); });
} }
//add core supported languages first // add core supported languages first
extractLangs(npm.root+"/ep_etherpad-lite/locales"); extractLangs(`${npm.root}/ep_etherpad-lite/locales`);
//add plugins languages (if any) // add plugins languages (if any)
for(var pluginName in plugins) extractLangs(path.join(npm.root, pluginName, 'locales')); for (const pluginName in plugins) extractLangs(path.join(npm.root, pluginName, 'locales'));
// Build a locale index (merge all locale data other than user-supplied overrides) // Build a locale index (merge all locale data other than user-supplied overrides)
var locales = {} const locales = {};
_.each (locales2paths, function(files, langcode) { _.each(locales2paths, (files, langcode) => {
locales[langcode]={}; locales[langcode] = {};
files.forEach(function(file) { files.forEach((file) => {
let fileContents; let fileContents;
try { try {
fileContents = JSON.parse(fs.readFileSync(file,'utf8')); fileContents = JSON.parse(fs.readFileSync(file, 'utf8'));
} catch (err) { } catch (err) {
console.error(`failed to read JSON file ${file}: ${err}`); console.error(`failed to read JSON file ${file}: ${err}`);
throw err; throw err;
@ -64,17 +64,17 @@ function getAllLocales() {
// Add custom strings from settings.json // Add custom strings from settings.json
// Since this is user-supplied, we'll do some extra sanity checks // Since this is user-supplied, we'll do some extra sanity checks
const wrongFormatErr = Error( const wrongFormatErr = Error(
"customLocaleStrings in wrong format. See documentation " + 'customLocaleStrings in wrong format. See documentation ' +
"for Customization for Administrators, under Localization.") 'for Customization for Administrators, under Localization.');
if (settings.customLocaleStrings) { if (settings.customLocaleStrings) {
if (typeof settings.customLocaleStrings !== "object") throw wrongFormatErr if (typeof settings.customLocaleStrings !== 'object') throw wrongFormatErr;
_.each(settings.customLocaleStrings, function(overrides, langcode) { _.each(settings.customLocaleStrings, (overrides, langcode) => {
if (typeof overrides !== "object") throw wrongFormatErr if (typeof overrides !== 'object') throw wrongFormatErr;
_.each(overrides, function(localeString, key) { _.each(overrides, (localeString, key) => {
if (typeof localeString !== "string") throw wrongFormatErr if (typeof localeString !== 'string') throw wrongFormatErr;
locales[langcode][key] = localeString locales[langcode][key] = localeString;
}) });
}) });
} }
return locales; return locales;
@ -83,45 +83,44 @@ function getAllLocales() {
// returns a hash of all available languages availables with nativeName and direction // returns a hash of all available languages availables with nativeName and direction
// e.g. { es: {nativeName: "español", direction: "ltr"}, ... } // e.g. { es: {nativeName: "español", direction: "ltr"}, ... }
function getAvailableLangs(locales) { function getAvailableLangs(locales) {
var result = {}; const result = {};
_.each(_.keys(locales), function(langcode) { _.each(_.keys(locales), (langcode) => {
result[langcode] = languages.getLanguageInfo(langcode); result[langcode] = languages.getLanguageInfo(langcode);
}); });
return result; return result;
} }
// returns locale index that will be served in /locales.json // returns locale index that will be served in /locales.json
var generateLocaleIndex = function (locales) { const generateLocaleIndex = function (locales) {
var result = _.clone(locales) // keep English strings const result = _.clone(locales); // keep English strings
_.each(_.keys(locales), function(langcode) { _.each(_.keys(locales), (langcode) => {
if (langcode != 'en') result[langcode]='locales/'+langcode+'.json'; if (langcode != 'en') result[langcode] = `locales/${langcode}.json`;
}); });
return JSON.stringify(result); return JSON.stringify(result);
} };
exports.expressCreateServer = function(n, args, cb) { exports.expressCreateServer = function (n, args, cb) {
// regenerate locales on server restart
//regenerate locales on server restart const locales = getAllLocales();
var locales = getAllLocales(); const localeIndex = generateLocaleIndex(locales);
var localeIndex = generateLocaleIndex(locales);
exports.availableLangs = getAvailableLangs(locales); exports.availableLangs = getAvailableLangs(locales);
args.app.get ('/locales/:locale', function(req, res) { args.app.get('/locales/:locale', (req, res) => {
//works with /locale/en and /locale/en.json requests // works with /locale/en and /locale/en.json requests
var locale = req.params.locale.split('.')[0]; const locale = req.params.locale.split('.')[0];
if (exports.availableLangs.hasOwnProperty(locale)) { if (exports.availableLangs.hasOwnProperty(locale)) {
res.setHeader('Content-Type', 'application/json; charset=utf-8'); res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.send('{"'+locale+'":'+JSON.stringify(locales[locale])+'}'); res.send(`{"${locale}":${JSON.stringify(locales[locale])}}`);
} else { } else {
res.status(404).send('Language not available'); res.status(404).send('Language not available');
} }
}) });
args.app.get('/locales.json', function(req, res) { args.app.get('/locales.json', (req, res) => {
res.setHeader('Content-Type', 'application/json; charset=utf-8'); res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.send(localeIndex); res.send(localeIndex);
}) });
return cb(); return cb();
} };

View file

@ -1,4 +1,4 @@
var securityManager = require('./db/SecurityManager'); const securityManager = require('./db/SecurityManager');
// checks for padAccess // checks for padAccess
module.exports = async function (req, res) { module.exports = async function (req, res) {
@ -7,7 +7,7 @@ module.exports = async function (req, res) {
const accessObj = await securityManager.checkAccess( const accessObj = await securityManager.checkAccess(
req.params.pad, req.cookies.sessionID, req.cookies.token, user); req.params.pad, req.cookies.sessionID, req.cookies.token, user);
if (accessObj.accessStatus === "grant") { if (accessObj.accessStatus === 'grant') {
// there is access, continue // there is access, continue
return true; return true;
} else { } else {
@ -19,4 +19,4 @@ module.exports = async function (req, res) {
// @TODO - send internal server error here? // @TODO - send internal server error here?
throw err; throw err;
} }
} };

View file

@ -61,13 +61,13 @@ exports.start = async () => {
try { try {
await db.init(); await db.init();
await plugins.update(); await plugins.update();
console.info('Installed plugins: ' + plugins.formatPluginsWithVersion()); console.info(`Installed plugins: ${plugins.formatPluginsWithVersion()}`);
console.debug('Installed parts:\n' + plugins.formatParts()); console.debug(`Installed parts:\n${plugins.formatParts()}`);
console.debug('Installed hooks:\n' + plugins.formatHooks()); console.debug(`Installed hooks:\n${plugins.formatHooks()}`);
await hooks.aCallAll('loadSettings', {settings}); await hooks.aCallAll('loadSettings', {settings});
await hooks.aCallAll('createServer'); await hooks.aCallAll('createServer');
} catch (e) { } catch (e) {
console.error('exception thrown: ' + e.message); console.error(`exception thrown: ${e.message}`);
if (e.stack) console.log(e.stack); if (e.stack) console.log(e.stack);
process.exit(1); process.exit(1);
} }

View file

@ -1,4 +1,4 @@
var measured = require('measured-core') const measured = require('measured-core');
module.exports = measured.createCollection(); module.exports = measured.createCollection();

View file

@ -18,41 +18,39 @@
* limitations under the License. * limitations under the License.
*/ */
var spawn = require('child_process').spawn; const spawn = require('child_process').spawn;
var async = require("async"); const async = require('async');
var settings = require("./Settings"); const settings = require('./Settings');
var os = require('os'); const os = require('os');
var doConvertTask; let doConvertTask;
//on windows we have to spawn a process for each convertion, cause the plugin abicommand doesn't exist on this platform // on windows we have to spawn a process for each convertion, cause the plugin abicommand doesn't exist on this platform
if(os.type().indexOf("Windows") > -1) if (os.type().indexOf('Windows') > -1) {
{ let stdoutBuffer = '';
var stdoutBuffer = "";
doConvertTask = function(task, callback) { doConvertTask = function (task, callback) {
//span an abiword process to perform the conversion // span an abiword process to perform the conversion
var abiword = spawn(settings.abiword, ["--to=" + task.destFile, task.srcFile]); const abiword = spawn(settings.abiword, [`--to=${task.destFile}`, task.srcFile]);
//delegate the processing of stdout to another function // delegate the processing of stdout to another function
abiword.stdout.on('data', function (data) { abiword.stdout.on('data', (data) => {
//add data to buffer // add data to buffer
stdoutBuffer+=data.toString();
});
//append error messages to the buffer
abiword.stderr.on('data', function (data) {
stdoutBuffer += data.toString(); stdoutBuffer += data.toString();
}); });
//throw exceptions if abiword is dieing // append error messages to the buffer
abiword.on('exit', function (code) { abiword.stderr.on('data', (data) => {
if(code != 0) { stdoutBuffer += data.toString();
});
// throw exceptions if abiword is dieing
abiword.on('exit', (code) => {
if (code != 0) {
return callback(`Abiword died with exit code ${code}`); return callback(`Abiword died with exit code ${code}`);
} }
if(stdoutBuffer != "") if (stdoutBuffer != '') {
{
console.log(stdoutBuffer); console.log(stdoutBuffer);
} }
@ -60,51 +58,48 @@ if(os.type().indexOf("Windows") > -1)
}); });
}; };
exports.convertFile = function(srcFile, destFile, type, callback) { exports.convertFile = function (srcFile, destFile, type, callback) {
doConvertTask({"srcFile": srcFile, "destFile": destFile, "type": type}, callback); doConvertTask({srcFile, destFile, type}, callback);
}; };
} }
//on unix operating systems, we can start abiword with abicommand and communicate with it via stdin/stdout // on unix operating systems, we can start abiword with abicommand and communicate with it via stdin/stdout
//thats much faster, about factor 10 // thats much faster, about factor 10
else else {
{ // spawn the abiword process
//spawn the abiword process let abiword;
var abiword; let stdoutCallback = null;
var stdoutCallback = null; var spawnAbiword = function () {
var spawnAbiword = function (){ abiword = spawn(settings.abiword, ['--plugin', 'AbiCommand']);
abiword = spawn(settings.abiword, ["--plugin", "AbiCommand"]); let stdoutBuffer = '';
var stdoutBuffer = ""; let firstPrompt = true;
var firstPrompt = true;
//append error messages to the buffer // append error messages to the buffer
abiword.stderr.on('data', function (data) { abiword.stderr.on('data', (data) => {
stdoutBuffer += data.toString(); stdoutBuffer += data.toString();
}); });
//abiword died, let's restart abiword and return an error with the callback // abiword died, let's restart abiword and return an error with the callback
abiword.on('exit', function (code) { abiword.on('exit', (code) => {
spawnAbiword(); spawnAbiword();
stdoutCallback(`Abiword died with exit code ${code}`); stdoutCallback(`Abiword died with exit code ${code}`);
}); });
//delegate the processing of stdout to a other function // delegate the processing of stdout to a other function
abiword.stdout.on('data',function (data) { abiword.stdout.on('data', (data) => {
//add data to buffer // add data to buffer
stdoutBuffer+=data.toString(); stdoutBuffer += data.toString();
//we're searching for the prompt, cause this means everything we need is in the buffer // we're searching for the prompt, cause this means everything we need is in the buffer
if(stdoutBuffer.search("AbiWord:>") != -1) if (stdoutBuffer.search('AbiWord:>') != -1) {
{ // filter the feedback message
//filter the feedback message const err = stdoutBuffer.search('OK') != -1 ? null : stdoutBuffer;
var err = stdoutBuffer.search("OK") != -1 ? null : stdoutBuffer;
//reset the buffer // reset the buffer
stdoutBuffer = ""; stdoutBuffer = '';
//call the callback with the error message // call the callback with the error message
//skip the first prompt // skip the first prompt
if(stdoutCallback != null && !firstPrompt) if (stdoutCallback != null && !firstPrompt) {
{
stdoutCallback(err); stdoutCallback(err);
stdoutCallback = null; stdoutCallback = null;
} }
@ -115,23 +110,23 @@ else
}; };
spawnAbiword(); spawnAbiword();
doConvertTask = function(task, callback) { doConvertTask = function (task, callback) {
abiword.stdin.write("convert " + task.srcFile + " " + task.destFile + " " + task.type + "\n"); abiword.stdin.write(`convert ${task.srcFile} ${task.destFile} ${task.type}\n`);
//create a callback that calls the task callback and the caller callback // create a callback that calls the task callback and the caller callback
stdoutCallback = function (err) { stdoutCallback = function (err) {
callback(); callback();
console.log("queue continue"); console.log('queue continue');
try{ try {
task.callback(err); task.callback(err);
}catch(e){ } catch (e) {
console.error("Abiword File failed to convert", e); console.error('Abiword File failed to convert', e);
} }
}; };
}; };
//Queue with the converts we have to do // Queue with the converts we have to do
var queue = async.queue(doConvertTask, 1); const queue = async.queue(doConvertTask, 1);
exports.convertFile = function(srcFile, destFile, type, callback) { exports.convertFile = function (srcFile, destFile, type, callback) {
queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback}); queue.push({srcFile, destFile, type, callback});
}; };
} }

View file

@ -18,17 +18,17 @@
* limitations under the License. * limitations under the License.
*/ */
var log4js = require('log4js'); const log4js = require('log4js');
var path = require('path'); const path = require('path');
var _ = require('underscore'); const _ = require('underscore');
var absPathLogger = log4js.getLogger('AbsolutePaths'); const absPathLogger = log4js.getLogger('AbsolutePaths');
/* /*
* findEtherpadRoot() computes its value only on first invocation. * findEtherpadRoot() computes its value only on first invocation.
* Subsequent invocations are served from this variable. * Subsequent invocations are served from this variable.
*/ */
var etherpadRoot = null; let etherpadRoot = null;
/** /**
* If stringArray's last elements are exactly equal to lastDesiredElements, * If stringArray's last elements are exactly equal to lastDesiredElements,
@ -40,9 +40,9 @@ var etherpadRoot = null;
* @return {string[]|boolean} The shortened array, or false if there was no * @return {string[]|boolean} The shortened array, or false if there was no
* overlap. * overlap.
*/ */
var popIfEndsWith = function(stringArray, lastDesiredElements) { const popIfEndsWith = function (stringArray, lastDesiredElements) {
if (stringArray.length <= lastDesiredElements.length) { if (stringArray.length <= lastDesiredElements.length) {
absPathLogger.debug(`In order to pop "${lastDesiredElements.join(path.sep)}" from "${stringArray.join(path.sep)}", it should contain at least ${lastDesiredElements.length + 1 } elements`); absPathLogger.debug(`In order to pop "${lastDesiredElements.join(path.sep)}" from "${stringArray.join(path.sep)}", it should contain at least ${lastDesiredElements.length + 1} elements`);
return false; return false;
} }
@ -72,7 +72,7 @@ var popIfEndsWith = function(stringArray, lastDesiredElements) {
* @return {string} The identified absolute base path. If such path cannot be * @return {string} The identified absolute base path. If such path cannot be
* identified, prints a log and exits the application. * identified, prints a log and exits the application.
*/ */
exports.findEtherpadRoot = function() { exports.findEtherpadRoot = function () {
if (etherpadRoot !== null) { if (etherpadRoot !== null) {
return etherpadRoot; return etherpadRoot;
} }
@ -87,7 +87,7 @@ exports.findEtherpadRoot = function() {
* *
* <BASE_DIR>\src * <BASE_DIR>\src
*/ */
var maybeEtherpadRoot = popIfEndsWith(splitFoundRoot, ['src']); let maybeEtherpadRoot = popIfEndsWith(splitFoundRoot, ['src']);
if ((maybeEtherpadRoot === false) && (process.platform === 'win32')) { if ((maybeEtherpadRoot === false) && (process.platform === 'win32')) {
/* /*
@ -126,7 +126,7 @@ exports.findEtherpadRoot = function() {
* it is returned unchanged. Otherwise it is interpreted * it is returned unchanged. Otherwise it is interpreted
* relative to exports.root. * relative to exports.root.
*/ */
exports.makeAbsolute = function(somePath) { exports.makeAbsolute = function (somePath) {
if (path.isAbsolute(somePath)) { if (path.isAbsolute(somePath)) {
return somePath; return somePath;
} }
@ -145,7 +145,7 @@ exports.makeAbsolute = function(somePath) {
* a subdirectory of the base one * a subdirectory of the base one
* @return {boolean} * @return {boolean}
*/ */
exports.isSubdir = function(parent, arbitraryDir) { exports.isSubdir = function (parent, arbitraryDir) {
// modified from: https://stackoverflow.com/questions/37521893/determine-if-a-path-is-subdirectory-of-another-in-node-js#45242825 // modified from: https://stackoverflow.com/questions/37521893/determine-if-a-path-is-subdirectory-of-another-in-node-js#45242825
const relative = path.relative(parent, arbitraryDir); const relative = path.relative(parent, arbitraryDir);
const isSubdir = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative); const isSubdir = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);

View file

@ -22,30 +22,30 @@
// An object containing the parsed command-line options // An object containing the parsed command-line options
exports.argv = {}; exports.argv = {};
var argv = process.argv.slice(2); const argv = process.argv.slice(2);
var arg, prevArg; let arg, prevArg;
// Loop through args // Loop through args
for ( var i = 0; i < argv.length; i++ ) { for (let i = 0; i < argv.length; i++) {
arg = argv[i]; arg = argv[i];
// Override location of settings.json file // Override location of settings.json file
if ( prevArg == '--settings' || prevArg == '-s' ) { if (prevArg == '--settings' || prevArg == '-s') {
exports.argv.settings = arg; exports.argv.settings = arg;
} }
// Override location of credentials.json file // Override location of credentials.json file
if ( prevArg == '--credentials' ) { if (prevArg == '--credentials') {
exports.argv.credentials = arg; exports.argv.credentials = arg;
} }
// Override location of settings.json file // Override location of settings.json file
if ( prevArg == '--sessionkey' ) { if (prevArg == '--sessionkey') {
exports.argv.sessionkey = arg; exports.argv.sessionkey = arg;
} }
// Override location of settings.json file // Override location of settings.json file
if ( prevArg == '--apikey' ) { if (prevArg == '--apikey') {
exports.argv.apikey = arg; exports.argv.apikey = arg;
} }

View file

@ -15,41 +15,39 @@
*/ */
let db = require("../db/DB"); const db = require('../db/DB');
let hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
exports.getPadRaw = async function(padId) { exports.getPadRaw = async function (padId) {
const padKey = `pad:${padId}`;
const padcontent = await db.get(padKey);
let padKey = "pad:" + padId; const records = [padKey];
let padcontent = await db.get(padKey);
let records = [ padKey ];
for (let i = 0; i <= padcontent.head; i++) { for (let i = 0; i <= padcontent.head; i++) {
records.push(padKey + ":revs:" + i); records.push(`${padKey}:revs:${i}`);
} }
for (let i = 0; i <= padcontent.chatHead; i++) { for (let i = 0; i <= padcontent.chatHead; i++) {
records.push(padKey + ":chat:" + i); records.push(`${padKey}:chat:${i}`);
} }
let data = {}; const data = {};
for (let key of records) { for (const key of records) {
// For each piece of info about a pad. // For each piece of info about a pad.
let entry = data[key] = await db.get(key); const entry = data[key] = await db.get(key);
// Get the Pad Authors // Get the Pad Authors
if (entry.pool && entry.pool.numToAttrib) { if (entry.pool && entry.pool.numToAttrib) {
let authors = entry.pool.numToAttrib; const authors = entry.pool.numToAttrib;
for (let k of Object.keys(authors)) { for (const k of Object.keys(authors)) {
if (authors[k][0] === "author") { if (authors[k][0] === 'author') {
let authorId = authors[k][1]; const authorId = authors[k][1];
// Get the author info // Get the author info
let authorEntry = await db.get("globalAuthor:" + authorId); const authorEntry = await db.get(`globalAuthor:${authorId}`);
if (authorEntry) { if (authorEntry) {
data["globalAuthor:" + authorId] = authorEntry; data[`globalAuthor:${authorId}`] = authorEntry;
if (authorEntry.padIDs) { if (authorEntry.padIDs) {
authorEntry.padIDs = padId; authorEntry.padIDs = padId;
} }
@ -68,4 +66,4 @@ exports.getPadRaw = async function(padId) {
})); }));
return data; return data;
} };

View file

@ -18,24 +18,23 @@
* limitations under the License. * limitations under the License.
*/ */
var Changeset = require("ep_etherpad-lite/static/js/Changeset"); const Changeset = require('ep_etherpad-lite/static/js/Changeset');
exports.getPadPlainText = function(pad, revNum){ exports.getPadPlainText = function (pad, revNum) {
var _analyzeLine = exports._analyzeLine; const _analyzeLine = exports._analyzeLine;
var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext); const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext);
var textLines = atext.text.slice(0, -1).split('\n'); const textLines = atext.text.slice(0, -1).split('\n');
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
var apool = pad.pool; const apool = pad.pool;
var pieces = []; const pieces = [];
for (var i = 0; i < textLines.length; i++){ for (let i = 0; i < textLines.length; i++) {
var line = _analyzeLine(textLines[i], attribLines[i], apool); const line = _analyzeLine(textLines[i], attribLines[i], apool);
if (line.listLevel){ if (line.listLevel) {
var numSpaces = line.listLevel * 2 - 1; const numSpaces = line.listLevel * 2 - 1;
var bullet = '*'; const bullet = '*';
pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n'); pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n');
} } else {
else{
pieces.push(line.text, '\n'); pieces.push(line.text, '\n');
} }
} }
@ -44,38 +43,37 @@ exports.getPadPlainText = function(pad, revNum){
}; };
exports._analyzeLine = function(text, aline, apool){ exports._analyzeLine = function (text, aline, apool) {
var line = {}; const line = {};
// identify list // identify list
var lineMarker = 0; let lineMarker = 0;
line.listLevel = 0; line.listLevel = 0;
if (aline){ if (aline) {
var opIter = Changeset.opIterator(aline); const opIter = Changeset.opIterator(aline);
if (opIter.hasNext()){ if (opIter.hasNext()) {
var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool); let listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
if (listType){ if (listType) {
lineMarker = 1; lineMarker = 1;
listType = /([a-z]+)([0-9]+)/.exec(listType); listType = /([a-z]+)([0-9]+)/.exec(listType);
if (listType){ if (listType) {
line.listTypeName = listType[1]; line.listTypeName = listType[1];
line.listLevel = Number(listType[2]); line.listLevel = Number(listType[2]);
} }
} }
} }
var opIter2 = Changeset.opIterator(aline); const opIter2 = Changeset.opIterator(aline);
if (opIter2.hasNext()){ if (opIter2.hasNext()) {
var start = Changeset.opAttributeValue(opIter2.next(), 'start', apool); const start = Changeset.opAttributeValue(opIter2.next(), 'start', apool);
if (start){ if (start) {
line.start = start; line.start = start;
} }
} }
} }
if (lineMarker){ if (lineMarker) {
line.text = text.substring(1); line.text = text.substring(1);
line.aline = Changeset.subattribution(aline, 1); line.aline = Changeset.subattribution(aline, 1);
} } else {
else{
line.text = text; line.text = text;
line.aline = aline; line.aline = aline;
} }
@ -83,8 +81,6 @@ exports._analyzeLine = function(text, aline, apool){
}; };
exports._encodeWhitespace = function(s){ exports._encodeWhitespace = function (s) {
return s.replace(/[^\x21-\x7E\s\t\n\r]/gu, function(c){ return s.replace(/[^\x21-\x7E\s\t\n\r]/gu, (c) => `&#${c.codePointAt(0)};`);
return "&#" +c.codePointAt(0) + ";";
});
}; };

View file

@ -14,14 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
var Changeset = require("ep_etherpad-lite/static/js/Changeset"); const Changeset = require('ep_etherpad-lite/static/js/Changeset');
var padManager = require("../db/PadManager"); const padManager = require('../db/PadManager');
var _ = require('underscore'); const _ = require('underscore');
var Security = require('ep_etherpad-lite/static/js/security'); const Security = require('ep_etherpad-lite/static/js/security');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var eejs = require('ep_etherpad-lite/node/eejs'); const eejs = require('ep_etherpad-lite/node/eejs');
var _analyzeLine = require('./ExportHelper')._analyzeLine; const _analyzeLine = require('./ExportHelper')._analyzeLine;
var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; const _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
async function getPadHTML(pad, revNum) { async function getPadHTML(pad, revNum) {
let atext = pad.atext; let atext = pad.atext;
@ -39,12 +39,12 @@ exports.getPadHTML = getPadHTML;
exports.getHTMLFromAtext = getHTMLFromAtext; exports.getHTMLFromAtext = getHTMLFromAtext;
async function getHTMLFromAtext(pad, atext, authorColors) { async function getHTMLFromAtext(pad, atext, authorColors) {
var apool = pad.apool(); const apool = pad.apool();
var textLines = atext.text.slice(0, -1).split('\n'); const textLines = atext.text.slice(0, -1).split('\n');
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
var tags = ['h1', 'h2', 'strong', 'em', 'u', 's']; const tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
await Promise.all([ await Promise.all([
// prepare tags stored as ['tag', true] to be exported // prepare tags stored as ['tag', true] to be exported
@ -68,56 +68,55 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// and maps them to an index in props // and maps them to an index in props
// *3:2 -> the attribute *3 means strong // *3:2 -> the attribute *3 means strong
// *2:5 -> the attribute *2 means s(trikethrough) // *2:5 -> the attribute *2 means s(trikethrough)
var anumMap = {}; const anumMap = {};
var css = ""; let css = '';
var stripDotFromAuthorID = function(id){ const stripDotFromAuthorID = function (id) {
return id.replace(/\./g,'_'); return id.replace(/\./g, '_');
}; };
if(authorColors){ if (authorColors) {
css+="<style>\n"; css += '<style>\n';
for (var a in apool.numToAttrib) { for (const a in apool.numToAttrib) {
var attr = apool.numToAttrib[a]; const attr = apool.numToAttrib[a];
//skip non author attributes // skip non author attributes
if(attr[0] === "author" && attr[1] !== ""){ if (attr[0] === 'author' && attr[1] !== '') {
//add to props array // add to props array
var propName = "author" + stripDotFromAuthorID(attr[1]); var propName = `author${stripDotFromAuthorID(attr[1])}`;
var newLength = props.push(propName); var newLength = props.push(propName);
anumMap[a] = newLength -1; anumMap[a] = newLength - 1;
css+="." + propName + " {background-color: " + authorColors[attr[1]]+ "}\n"; css += `.${propName} {background-color: ${authorColors[attr[1]]}}\n`;
} else if(attr[0] === "removed") { } else if (attr[0] === 'removed') {
var propName = "removed"; var propName = 'removed';
var newLength = props.push(propName); var newLength = props.push(propName);
anumMap[a] = newLength -1; anumMap[a] = newLength - 1;
css+=".removed {text-decoration: line-through; " + css += '.removed {text-decoration: line-through; ' +
"-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; "+ "-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; " +
"filter: alpha(opacity=80); "+ 'filter: alpha(opacity=80); ' +
"opacity: 0.8; "+ 'opacity: 0.8; ' +
"}\n"; '}\n';
} }
} }
css+="</style>"; css += '</style>';
} }
// iterates over all props(h1,h2,strong,...), checks if it is used in // iterates over all props(h1,h2,strong,...), checks if it is used in
// this pad, and if yes puts its attrib id->props value into anumMap // this pad, and if yes puts its attrib id->props value into anumMap
props.forEach(function (propName, i) { props.forEach((propName, i) => {
var attrib = [propName, true]; let attrib = [propName, true];
if (_.isArray(propName)) { if (_.isArray(propName)) {
// propName can be in the form of ['color', 'red'], // propName can be in the form of ['color', 'red'],
// see hook exportHtmlAdditionalTagsWithData // see hook exportHtmlAdditionalTagsWithData
attrib = propName; attrib = propName;
} }
var propTrueNum = apool.putAttrib(attrib, true); const propTrueNum = apool.putAttrib(attrib, true);
if (propTrueNum >= 0) if (propTrueNum >= 0) {
{
anumMap[propTrueNum] = i; anumMap[propTrueNum] = i;
} }
}); });
@ -128,15 +127,15 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i> // <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
// becomes // becomes
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i> // <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
var taker = Changeset.stringIterator(text); const taker = Changeset.stringIterator(text);
var assem = Changeset.stringAssembler(); const assem = Changeset.stringAssembler();
var openTags = []; const openTags = [];
function getSpanClassFor(i){ function getSpanClassFor(i) {
//return if author colors are disabled // return if author colors are disabled
if (!authorColors) return false; if (!authorColors) return false;
var property = props[i]; const property = props[i];
// we are not insterested on properties in the form of ['color', 'red'], // we are not insterested on properties in the form of ['color', 'red'],
// see hook exportHtmlAdditionalTagsWithData // see hook exportHtmlAdditionalTagsWithData
@ -144,12 +143,12 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
return false; return false;
} }
if(property.substr(0,6) === "author"){ if (property.substr(0, 6) === 'author') {
return stripDotFromAuthorID(property); return stripDotFromAuthorID(property);
} }
if(property === "removed"){ if (property === 'removed') {
return "removed"; return 'removed';
} }
return false; return false;
@ -157,16 +156,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// tags added by exportHtmlAdditionalTagsWithData will be exported as <span> with // tags added by exportHtmlAdditionalTagsWithData will be exported as <span> with
// data attributes // data attributes
function isSpanWithData(i){ function isSpanWithData(i) {
var property = props[i]; const property = props[i];
return _.isArray(property); return _.isArray(property);
} }
function emitOpenTag(i) { function emitOpenTag(i) {
openTags.unshift(i); openTags.unshift(i);
var spanClass = getSpanClassFor(i); const spanClass = getSpanClassFor(i);
if(spanClass){ if (spanClass) {
assem.append('<span class="'); assem.append('<span class="');
assem.append(spanClass); assem.append(spanClass);
assem.append('">'); assem.append('">');
@ -180,10 +179,10 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// this closes an open tag and removes its reference from openTags // this closes an open tag and removes its reference from openTags
function emitCloseTag(i) { function emitCloseTag(i) {
openTags.shift(); openTags.shift();
var spanClass = getSpanClassFor(i); const spanClass = getSpanClassFor(i);
var spanWithData = isSpanWithData(i); const spanWithData = isSpanWithData(i);
if(spanClass || spanWithData){ if (spanClass || spanWithData) {
assem.append('</span>'); assem.append('</span>');
} else { } else {
assem.append('</'); assem.append('</');
@ -192,90 +191,78 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
} }
} }
var urls = _findURLs(text); const urls = _findURLs(text);
var idx = 0; let idx = 0;
function processNextChars(numChars) { function processNextChars(numChars) {
if (numChars <= 0) if (numChars <= 0) {
{
return; return;
} }
var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars)); const iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
idx += numChars; idx += numChars;
// this iterates over every op string and decides which tags to open or to close // this iterates over every op string and decides which tags to open or to close
// based on the attribs used // based on the attribs used
while (iter.hasNext()) while (iter.hasNext()) {
{ const o = iter.next();
var o = iter.next();
var usedAttribs = []; var usedAttribs = [];
// mark all attribs as used // mark all attribs as used
Changeset.eachAttribNumber(o.attribs, function (a) { Changeset.eachAttribNumber(o.attribs, (a) => {
if (a in anumMap) if (a in anumMap) {
{
usedAttribs.push(anumMap[a]); // i = 0 => bold, etc. usedAttribs.push(anumMap[a]); // i = 0 => bold, etc.
} }
}); });
var outermostTag = -1; let outermostTag = -1;
// find the outer most open tag that is no longer used // find the outer most open tag that is no longer used
for (var i = openTags.length - 1; i >= 0; i--) for (var i = openTags.length - 1; i >= 0; i--) {
{ if (usedAttribs.indexOf(openTags[i]) === -1) {
if (usedAttribs.indexOf(openTags[i]) === -1)
{
outermostTag = i; outermostTag = i;
break; break;
} }
} }
// close all tags upto the outer most // close all tags upto the outer most
if (outermostTag !== -1) if (outermostTag !== -1) {
{ while (outermostTag >= 0) {
while ( outermostTag >= 0 )
{
emitCloseTag(openTags[0]); emitCloseTag(openTags[0]);
outermostTag--; outermostTag--;
} }
} }
// open all tags that are used but not open // open all tags that are used but not open
for (i=0; i < usedAttribs.length; i++) for (i = 0; i < usedAttribs.length; i++) {
{ if (openTags.indexOf(usedAttribs[i]) === -1) {
if (openTags.indexOf(usedAttribs[i]) === -1)
{
emitOpenTag(usedAttribs[i]); emitOpenTag(usedAttribs[i]);
} }
} }
var chars = o.chars; let chars = o.chars;
if (o.lines) if (o.lines) {
{
chars--; // exclude newline at end of line, if present chars--; // exclude newline at end of line, if present
} }
var s = taker.take(chars); let s = taker.take(chars);
//removes the characters with the code 12. Don't know where they come // removes the characters with the code 12. Don't know where they come
//from but they break the abiword parser and are completly useless // from but they break the abiword parser and are completly useless
s = s.replace(String.fromCharCode(12), ""); s = s.replace(String.fromCharCode(12), '');
assem.append(_encodeWhitespace(Security.escapeHTML(s))); assem.append(_encodeWhitespace(Security.escapeHTML(s)));
} // end iteration over spans in line } // end iteration over spans in line
// close all the tags that are open after the last op // close all the tags that are open after the last op
while (openTags.length > 0) while (openTags.length > 0) {
{
emitCloseTag(openTags[0]); emitCloseTag(openTags[0]);
} }
} // end processNextChars } // end processNextChars
if (urls) if (urls) {
{ urls.forEach((urlData) => {
urls.forEach(function (urlData) { const startIndex = urlData[0];
var startIndex = urlData[0]; const url = urlData[1];
var url = urlData[1]; const urlLength = url.length;
var urlLength = url.length;
processNextChars(startIndex - idx); processNextChars(startIndex - idx);
// Using rel="noreferrer" stops leaking the URL/location of the exported HTML when clicking links in the document. // Using rel="noreferrer" stops leaking the URL/location of the exported HTML when clicking links in the document.
// Not all browsers understand this attribute, but it's part of the HTML5 standard. // Not all browsers understand this attribute, but it's part of the HTML5 standard.
@ -284,7 +271,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener // https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
// https://mathiasbynens.github.io/rel-noopener/ // https://mathiasbynens.github.io/rel-noopener/
// https://github.com/ether/etherpad-lite/pull/3636 // https://github.com/ether/etherpad-lite/pull/3636
assem.append('<a href="' + Security.escapeHTMLAttribute(url) + '" rel="noreferrer noopener">'); assem.append(`<a href="${Security.escapeHTMLAttribute(url)}" rel="noreferrer noopener">`);
processNextChars(urlLength); processNextChars(urlLength);
assem.append('</a>'); assem.append('</a>');
}); });
@ -293,7 +280,7 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
return _processSpaces(assem.toString()); return _processSpaces(assem.toString());
} // end getLineHTML } // end getLineHTML
var pieces = [css]; const pieces = [css];
// Need to deal with constraints imposed on HTML lists; can // Need to deal with constraints imposed on HTML lists; can
// only gain one level of nesting at once, can't change type // only gain one level of nesting at once, can't change type
@ -302,56 +289,48 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// so we want to do something reasonable there. We also // so we want to do something reasonable there. We also
// want to deal gracefully with blank lines. // want to deal gracefully with blank lines.
// => keeps track of the parents level of indentation // => keeps track of the parents level of indentation
var openLists = []; let openLists = [];
for (var i = 0; i < textLines.length; i++) for (let i = 0; i < textLines.length; i++) {
{
var context; var context;
var line = _analyzeLine(textLines[i], attribLines[i], apool); var line = _analyzeLine(textLines[i], attribLines[i], apool);
var lineContent = getLineHTML(line.text, line.aline); const lineContent = getLineHTML(line.text, line.aline);
if (line.listLevel)//If we are inside a list if (line.listLevel)// If we are inside a list
{ {
context = { context = {
line: line, line,
lineContent: lineContent, lineContent,
apool: apool, apool,
attribLine: attribLines[i], attribLine: attribLines[i],
text: textLines[i], text: textLines[i],
padId: pad.id padId: pad.id,
}; };
var prevLine = null; let prevLine = null;
var nextLine = null; let nextLine = null;
if (i > 0) if (i > 0) {
{ prevLine = _analyzeLine(textLines[i - 1], attribLines[i - 1], apool);
prevLine = _analyzeLine(textLines[i -1], attribLines[i -1], apool);
} }
if (i < textLines.length) if (i < textLines.length) {
{
nextLine = _analyzeLine(textLines[i + 1], attribLines[i + 1], apool); nextLine = _analyzeLine(textLines[i + 1], attribLines[i + 1], apool);
} }
await hooks.aCallAll('getLineHTMLForExport', context); await hooks.aCallAll('getLineHTMLForExport', context);
//To create list parent elements // To create list parent elements
if ((!prevLine || prevLine.listLevel !== line.listLevel) || (prevLine && line.listTypeName !== prevLine.listTypeName)) if ((!prevLine || prevLine.listLevel !== line.listLevel) || (prevLine && line.listTypeName !== prevLine.listTypeName)) {
{ const exists = _.find(openLists, (item) => (item.level === line.listLevel && item.type === line.listTypeName));
var exists = _.find(openLists, function (item) {
return (item.level === line.listLevel && item.type === line.listTypeName);
});
if (!exists) { if (!exists) {
var prevLevel = 0; let prevLevel = 0;
if (prevLine && prevLine.listLevel) { if (prevLine && prevLine.listLevel) {
prevLevel = prevLine.listLevel; prevLevel = prevLine.listLevel;
} }
if (prevLine && line.listTypeName !== prevLine.listTypeName) if (prevLine && line.listTypeName !== prevLine.listTypeName) {
{
prevLevel = 0; prevLevel = 0;
} }
for (var diff = prevLevel; diff < line.listLevel; diff++) { for (var diff = prevLevel; diff < line.listLevel; diff++) {
openLists.push({level: diff, type: line.listTypeName}); openLists.push({level: diff, type: line.listTypeName});
var prevPiece = pieces[pieces.length - 1]; const prevPiece = pieces[pieces.length - 1];
if (prevPiece.indexOf("<ul") === 0 || prevPiece.indexOf("<ol") === 0 || prevPiece.indexOf("</li>") === 0) if (prevPiece.indexOf('<ul') === 0 || prevPiece.indexOf('<ol') === 0 || prevPiece.indexOf('</li>') === 0) {
{ /*
/*
uncommenting this breaks nested ols.. uncommenting this breaks nested ols..
if the previous item is NOT a ul, NOT an ol OR closing li then close the list if the previous item is NOT a ul, NOT an ol OR closing li then close the list
so we consider this HTML, I inserted ** where it throws a problem in Example Wrong.. so we consider this HTML, I inserted ** where it throws a problem in Example Wrong..
@ -367,19 +346,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// pieces.push("</li>"); // pieces.push("</li>");
*/ */
if( (nextLine.listTypeName === 'number') && (nextLine.text === '') ){ if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) {
// is the listTypeName check needed here? null text might be completely fine! // is the listTypeName check needed here? null text might be completely fine!
// TODO Check against Uls // TODO Check against Uls
// don't do anything because the next item is a nested ol openener so we need to keep the li open // don't do anything because the next item is a nested ol openener so we need to keep the li open
}else{ } else {
pieces.push("<li>"); pieces.push('<li>');
} }
} }
if (line.listTypeName === "number") if (line.listTypeName === 'number') {
{
// We introduce line.start here, this is useful for continuing Ordered list line numbers // We introduce line.start here, this is useful for continuing Ordered list line numbers
// in case you have a bullet in a list IE you Want // in case you have a bullet in a list IE you Want
// 1. hello // 1. hello
@ -390,80 +366,66 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// TODO: This logic could also be used to continue OL with indented content // TODO: This logic could also be used to continue OL with indented content
// but that's a job for another day.... // but that's a job for another day....
if(line.start){ if (line.start) {
pieces.push("<ol start=\""+Number(line.start)+"\" class=\"" + line.listTypeName + "\">"); pieces.push(`<ol start="${Number(line.start)}" class="${line.listTypeName}">`);
}else{ } else {
pieces.push("<ol class=\"" + line.listTypeName + "\">"); pieces.push(`<ol class="${line.listTypeName}">`);
} }
} } else {
else pieces.push(`<ul class="${line.listTypeName}">`);
{
pieces.push("<ul class=\"" + line.listTypeName + "\">");
} }
} }
} }
} }
// if we're going up a level we shouldn't be adding.. // if we're going up a level we shouldn't be adding..
if(context.lineContent){ if (context.lineContent) {
pieces.push("<li>", context.lineContent); pieces.push('<li>', context.lineContent);
} }
// To close list elements // To close list elements
if (nextLine && nextLine.listLevel === line.listLevel && line.listTypeName === nextLine.listTypeName) if (nextLine && nextLine.listLevel === line.listLevel && line.listTypeName === nextLine.listTypeName) {
{ if (context.lineContent) {
if(context.lineContent){ if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) {
if( (nextLine.listTypeName === 'number') && (nextLine.text === '') ){
// is the listTypeName check needed here? null text might be completely fine! // is the listTypeName check needed here? null text might be completely fine!
// TODO Check against Uls // TODO Check against Uls
// don't do anything because the next item is a nested ol openener so we need to keep the li open // don't do anything because the next item is a nested ol openener so we need to keep the li open
}else{ } else {
pieces.push("</li>"); pieces.push('</li>');
} }
} }
} }
if ((!nextLine || !nextLine.listLevel || nextLine.listLevel < line.listLevel) || (nextLine && line.listTypeName !== nextLine.listTypeName)) if ((!nextLine || !nextLine.listLevel || nextLine.listLevel < line.listLevel) || (nextLine && line.listTypeName !== nextLine.listTypeName)) {
{ let nextLevel = 0;
var nextLevel = 0;
if (nextLine && nextLine.listLevel) { if (nextLine && nextLine.listLevel) {
nextLevel = nextLine.listLevel; nextLevel = nextLine.listLevel;
} }
if (nextLine && line.listTypeName !== nextLine.listTypeName) if (nextLine && line.listTypeName !== nextLine.listTypeName) {
{
nextLevel = 0; nextLevel = 0;
} }
for (var diff = nextLevel; diff < line.listLevel; diff++) for (var diff = nextLevel; diff < line.listLevel; diff++) {
{ openLists = openLists.filter((el) => el.level !== diff && el.type !== line.listTypeName);
openLists = openLists.filter(function(el) {
return el.level !== diff && el.type !== line.listTypeName;
});
if (pieces[pieces.length - 1].indexOf("</ul") === 0 || pieces[pieces.length - 1].indexOf("</ol") === 0) if (pieces[pieces.length - 1].indexOf('</ul') === 0 || pieces[pieces.length - 1].indexOf('</ol') === 0) {
{ pieces.push('</li>');
pieces.push("</li>");
} }
if (line.listTypeName === "number") if (line.listTypeName === 'number') {
{ pieces.push('</ol>');
pieces.push("</ol>"); } else {
} pieces.push('</ul>');
else
{
pieces.push("</ul>");
} }
} }
} }
} } else// outside any list, need to close line.listLevel of lists
else//outside any list, need to close line.listLevel of lists
{ {
context = { context = {
line: line, line,
lineContent: lineContent, lineContent,
apool: apool, apool,
attribLine: attribLines[i], attribLine: attribLines[i],
text: textLines[i], text: textLines[i],
padId: pad.id padId: pad.id,
}; };
await hooks.aCallAll('getLineHTMLForExport', context); await hooks.aCallAll('getLineHTMLForExport', context);
@ -475,46 +437,45 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
} }
exports.getPadHTMLDocument = async function (padId, revNum) { exports.getPadHTMLDocument = async function (padId, revNum) {
let pad = await padManager.getPad(padId); const pad = await padManager.getPad(padId);
// Include some Styles into the Head for Export // Include some Styles into the Head for Export
let stylesForExportCSS = ""; let stylesForExportCSS = '';
let stylesForExport = await hooks.aCallAll("stylesForExport", padId); const stylesForExport = await hooks.aCallAll('stylesForExport', padId);
stylesForExport.forEach(function(css){ stylesForExport.forEach((css) => {
stylesForExportCSS += css; stylesForExportCSS += css;
}); });
let html = await getPadHTML(pad, revNum); let html = await getPadHTML(pad, revNum);
for (const hookHtml of await hooks.aCallAll("exportHTMLAdditionalContent", {padId})) { for (const hookHtml of await hooks.aCallAll('exportHTMLAdditionalContent', {padId})) {
html += hookHtml; html += hookHtml;
} }
return eejs.require("ep_etherpad-lite/templates/export_html.html", { return eejs.require('ep_etherpad-lite/templates/export_html.html', {
body: html, body: html,
padId: Security.escapeHTML(padId), padId: Security.escapeHTML(padId),
extraCSS: stylesForExportCSS extraCSS: stylesForExportCSS,
}); });
} };
// copied from ACE // copied from ACE
var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; const _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
var _REGEX_SPACE = /\s/; const _REGEX_SPACE = /\s/;
var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')'); const _REGEX_URLCHAR = new RegExp(`(${/[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source}|${_REGEX_WORDCHAR.source})`);
var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g'); const _REGEX_URL = new RegExp(`${/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source}*(?![:.,;])${_REGEX_URLCHAR.source}`, 'g');
// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...] // returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
function _findURLs(text) { function _findURLs(text) {
_REGEX_URL.lastIndex = 0; _REGEX_URL.lastIndex = 0;
var urls = null; let urls = null;
var execResult; let execResult;
while ((execResult = _REGEX_URL.exec(text))) while ((execResult = _REGEX_URL.exec(text))) {
{
urls = (urls || []); urls = (urls || []);
var startIndex = execResult.index; const startIndex = execResult.index;
var url = execResult[0]; const url = execResult[0];
urls.push([startIndex, url]); urls.push([startIndex, url]);
} }
@ -523,50 +484,46 @@ function _findURLs(text) {
// copied from ACE // copied from ACE
function _processSpaces(s){ function _processSpaces(s) {
var doesWrap = true; const doesWrap = true;
if (s.indexOf("<") < 0 && !doesWrap){ if (s.indexOf('<') < 0 && !doesWrap) {
// short-cut // short-cut
return s.replace(/ /g, '&nbsp;'); return s.replace(/ /g, '&nbsp;');
} }
var parts = []; const parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, function (m){ s.replace(/<[^>]*>?| |[^ <]+/g, (m) => {
parts.push(m); parts.push(m);
}); });
if (doesWrap){ if (doesWrap) {
var endOfLine = true; let endOfLine = true;
var beforeSpace = false; let beforeSpace = false;
// last space in a run is normal, others are nbsp, // last space in a run is normal, others are nbsp,
// end of line is nbsp // end of line is nbsp
for (var i = parts.length - 1; i >= 0; i--){ for (var i = parts.length - 1; i >= 0; i--) {
var p = parts[i]; var p = parts[i];
if (p == " "){ if (p == ' ') {
if (endOfLine || beforeSpace) parts[i] = '&nbsp;'; if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false; endOfLine = false;
beforeSpace = true; beforeSpace = true;
} } else if (p.charAt(0) != '<') {
else if (p.charAt(0) != "<"){
endOfLine = false; endOfLine = false;
beforeSpace = false; beforeSpace = false;
} }
} }
// beginning of line is nbsp // beginning of line is nbsp
for (i = 0; i < parts.length; i++){ for (i = 0; i < parts.length; i++) {
p = parts[i]; p = parts[i];
if (p == " "){ if (p == ' ') {
parts[i] = '&nbsp;'; parts[i] = '&nbsp;';
break; break;
} } else if (p.charAt(0) != '<') {
else if (p.charAt(0) != "<"){
break; break;
} }
} }
} } else {
else for (i = 0; i < parts.length; i++) {
{
for (i = 0; i < parts.length; i++){
p = parts[i]; p = parts[i];
if (p == " "){ if (p == ' ') {
parts[i] = '&nbsp;'; parts[i] = '&nbsp;';
} }
} }

View file

@ -18,12 +18,12 @@
* limitations under the License. * limitations under the License.
*/ */
var Changeset = require("ep_etherpad-lite/static/js/Changeset"); const Changeset = require('ep_etherpad-lite/static/js/Changeset');
var padManager = require("../db/PadManager"); const padManager = require('../db/PadManager');
var _analyzeLine = require('./ExportHelper')._analyzeLine; const _analyzeLine = require('./ExportHelper')._analyzeLine;
// This is slightly different than the HTML method as it passes the output to getTXTFromAText // This is slightly different than the HTML method as it passes the output to getTXTFromAText
var getPadTXT = async function(pad, revNum) { const getPadTXT = async function (pad, revNum) {
let atext = pad.atext; let atext = pad.atext;
if (revNum != undefined) { if (revNum != undefined) {
@ -33,57 +33,57 @@ var getPadTXT = async function(pad, revNum) {
// convert atext to html // convert atext to html
return getTXTFromAtext(pad, atext); return getTXTFromAtext(pad, atext);
} };
// This is different than the functionality provided in ExportHtml as it provides formatting // This is different than the functionality provided in ExportHtml as it provides formatting
// functionality that is designed specifically for TXT exports // functionality that is designed specifically for TXT exports
function getTXTFromAtext(pad, atext, authorColors) { function getTXTFromAtext(pad, atext, authorColors) {
var apool = pad.apool(); const apool = pad.apool();
var textLines = atext.text.slice(0, -1).split('\n'); const textLines = atext.text.slice(0, -1).split('\n');
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
var anumMap = {}; const anumMap = {};
var css = ""; const css = '';
props.forEach(function(propName, i) { props.forEach((propName, i) => {
var propTrueNum = apool.putAttrib([propName, true], true); const propTrueNum = apool.putAttrib([propName, true], true);
if (propTrueNum >= 0) { if (propTrueNum >= 0) {
anumMap[propTrueNum] = i; anumMap[propTrueNum] = i;
} }
}); });
function getLineTXT(text, attribs) { function getLineTXT(text, attribs) {
var propVals = [false, false, false]; const propVals = [false, false, false];
var ENTER = 1; const ENTER = 1;
var STAY = 2; const STAY = 2;
var LEAVE = 0; const LEAVE = 0;
// Use order of tags (b/i/u) as order of nesting, for simplicity // Use order of tags (b/i/u) as order of nesting, for simplicity
// and decent nesting. For example, // and decent nesting. For example,
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i> // <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
// becomes // becomes
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i> // <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
var taker = Changeset.stringIterator(text); const taker = Changeset.stringIterator(text);
var assem = Changeset.stringAssembler(); const assem = Changeset.stringAssembler();
var idx = 0; let idx = 0;
function processNextChars(numChars) { function processNextChars(numChars) {
if (numChars <= 0) { if (numChars <= 0) {
return; return;
} }
var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars)); const iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
idx += numChars; idx += numChars;
while (iter.hasNext()) { while (iter.hasNext()) {
var o = iter.next(); const o = iter.next();
var propChanged = false; var propChanged = false;
Changeset.eachAttribNumber(o.attribs, function(a) { Changeset.eachAttribNumber(o.attribs, (a) => {
if (a in anumMap) { if (a in anumMap) {
var i = anumMap[a]; // i = 0 => bold, etc. const i = anumMap[a]; // i = 0 => bold, etc.
if (!propVals[i]) { if (!propVals[i]) {
propVals[i] = ENTER; propVals[i] = ENTER;
@ -108,20 +108,18 @@ function getTXTFromAtext(pad, atext, authorColors) {
// according to what happens at start of span // according to what happens at start of span
if (propChanged) { if (propChanged) {
// leaving bold (e.g.) also leaves italics, etc. // leaving bold (e.g.) also leaves italics, etc.
var left = false; let left = false;
for (var i = 0; i < propVals.length; i++) { for (var i = 0; i < propVals.length; i++) {
var v = propVals[i]; const v = propVals[i];
if (!left) { if (!left) {
if (v === LEAVE) { if (v === LEAVE) {
left = true; left = true;
} }
} else { } else if (v === true) {
if (v === true) { // tag will be closed and re-opened
// tag will be closed and re-opened propVals[i] = STAY;
propVals[i] = STAY;
}
} }
} }
@ -129,11 +127,11 @@ function getTXTFromAtext(pad, atext, authorColors) {
for (var i = propVals.length - 1; i >= 0; i--) { for (var i = propVals.length - 1; i >= 0; i--) {
if (propVals[i] === LEAVE) { if (propVals[i] === LEAVE) {
//emitCloseTag(i); // emitCloseTag(i);
tags2close.push(i); tags2close.push(i);
propVals[i] = false; propVals[i] = false;
} else if (propVals[i] === STAY) { } else if (propVals[i] === STAY) {
//emitCloseTag(i); // emitCloseTag(i);
tags2close.push(i); tags2close.push(i);
} }
} }
@ -146,13 +144,13 @@ function getTXTFromAtext(pad, atext, authorColors) {
// propVals is now all {true,false} again // propVals is now all {true,false} again
} // end if (propChanged) } // end if (propChanged)
var chars = o.chars; let chars = o.chars;
if (o.lines) { if (o.lines) {
// exclude newline at end of line, if present // exclude newline at end of line, if present
chars--; chars--;
} }
var s = taker.take(chars); const s = taker.take(chars);
// removes the characters with the code 12. Don't know where they come // removes the characters with the code 12. Don't know where they come
// from but they break the abiword parser and are completly useless // from but they break the abiword parser and are completly useless
@ -172,14 +170,13 @@ function getTXTFromAtext(pad, atext, authorColors) {
propVals[i] = false; propVals[i] = false;
} }
} }
} // end processNextChars } // end processNextChars
processNextChars(text.length - idx); processNextChars(text.length - idx);
return(assem.toString()); return (assem.toString());
} // end getLineHTML } // end getLineHTML
var pieces = [css]; const pieces = [css];
// Need to deal with constraints imposed on HTML lists; can // Need to deal with constraints imposed on HTML lists; can
// only gain one level of nesting at once, can't change type // only gain one level of nesting at once, can't change type
@ -189,34 +186,33 @@ function getTXTFromAtext(pad, atext, authorColors) {
// want to deal gracefully with blank lines. // want to deal gracefully with blank lines.
// => keeps track of the parents level of indentation // => keeps track of the parents level of indentation
var listNumbers = {}; const listNumbers = {};
var prevListLevel; let prevListLevel;
for (var i = 0; i < textLines.length; i++) { for (let i = 0; i < textLines.length; i++) {
const line = _analyzeLine(textLines[i], attribLines[i], apool);
let lineContent = getLineTXT(line.text, line.aline);
var line = _analyzeLine(textLines[i], attribLines[i], apool); if (line.listTypeName == 'bullet') {
var lineContent = getLineTXT(line.text, line.aline); lineContent = `* ${lineContent}`; // add a bullet
if (line.listTypeName == "bullet") {
lineContent = "* " + lineContent; // add a bullet
} }
if (line.listTypeName !== "number") { if (line.listTypeName !== 'number') {
// We're no longer in an OL so we can reset counting // We're no longer in an OL so we can reset counting
for (var key in listNumbers) { for (const key in listNumbers) {
delete listNumbers[key]; delete listNumbers[key];
} }
} }
if (line.listLevel > 0) { if (line.listLevel > 0) {
for (var j = line.listLevel - 1; j >= 0; j--) { for (let j = line.listLevel - 1; j >= 0; j--) {
pieces.push('\t'); // tab indent list numbers.. pieces.push('\t'); // tab indent list numbers..
if(!listNumbers[line.listLevel]){ if (!listNumbers[line.listLevel]) {
listNumbers[line.listLevel] = 0; listNumbers[line.listLevel] = 0;
} }
} }
if (line.listTypeName == "number") { if (line.listTypeName == 'number') {
/* /*
* listLevel == amount of indentation * listLevel == amount of indentation
* listNumber(s) == item number * listNumber(s) == item number
@ -230,19 +226,19 @@ function getTXTFromAtext(pad, atext, authorColors) {
* To handle going back to 2.1 when prevListLevel is lower number * To handle going back to 2.1 when prevListLevel is lower number
* than current line.listLevel then reset the object value * than current line.listLevel then reset the object value
*/ */
if(line.listLevel < prevListLevel){ if (line.listLevel < prevListLevel) {
delete listNumbers[prevListLevel]; delete listNumbers[prevListLevel];
} }
listNumbers[line.listLevel]++; listNumbers[line.listLevel]++;
if(line.listLevel > 1){ if (line.listLevel > 1) {
var x = 1; let x = 1;
while(x <= line.listLevel-1){ while (x <= line.listLevel - 1) {
pieces.push(listNumbers[x]+".") pieces.push(`${listNumbers[x]}.`);
x++; x++;
} }
} }
pieces.push(listNumbers[line.listLevel]+". ") pieces.push(`${listNumbers[line.listLevel]}. `);
prevListLevel = line.listLevel; prevListLevel = line.listLevel;
} }
@ -257,7 +253,7 @@ function getTXTFromAtext(pad, atext, authorColors) {
exports.getTXTFromAtext = getTXTFromAtext; exports.getTXTFromAtext = getTXTFromAtext;
exports.getPadTXTDocument = async function(padId, revNum) { exports.getPadTXTDocument = async function (padId, revNum) {
let pad = await padManager.getPad(padId); const pad = await padManager.getPad(padId);
return getPadTXT(pad, revNum); return getPadTXT(pad, revNum);
} };

View file

@ -14,14 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
var log4js = require('log4js'); const log4js = require('log4js');
const db = require("../db/DB"); const db = require('../db/DB');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
exports.setPadRaw = function(padId, records) { exports.setPadRaw = function (padId, records) {
records = JSON.parse(records); records = JSON.parse(records);
Object.keys(records).forEach(async function(key) { Object.keys(records).forEach(async (key) => {
let value = records[key]; let value = records[key];
if (!value) { if (!value) {
@ -36,7 +36,7 @@ exports.setPadRaw = function(padId, records) {
newKey = key; newKey = key;
// Does this author already exist? // Does this author already exist?
let author = await db.get(key); const author = await db.get(key);
if (author) { if (author) {
// Yes, add the padID to the author // Yes, add the padID to the author
@ -47,20 +47,20 @@ exports.setPadRaw = function(padId, records) {
value = author; value = author;
} else { } else {
// No, create a new array with the author info in // No, create a new array with the author info in
value.padIDs = [ padId ]; value.padIDs = [padId];
} }
} else { } else {
// Not author data, probably pad data // Not author data, probably pad data
// we can split it to look to see if it's pad data // we can split it to look to see if it's pad data
let oldPadId = key.split(":"); const oldPadId = key.split(':');
// we know it's pad data // we know it's pad data
if (oldPadId[0] === "pad") { if (oldPadId[0] === 'pad') {
// so set the new pad id for the author // so set the new pad id for the author
oldPadId[1] = padId; oldPadId[1] = padId;
// and create the value // and create the value
newKey = oldPadId.join(":"); // create the new key newKey = oldPadId.join(':'); // create the new key
} }
// is this a key that is supported through a plugin? // is this a key that is supported through a plugin?
@ -74,4 +74,4 @@ exports.setPadRaw = function(padId, records) {
// Write the value to the server // Write the value to the server
await db.set(newKey, value); await db.set(newKey, value);
}); });
} };

View file

@ -14,75 +14,75 @@
* limitations under the License. * limitations under the License.
*/ */
const log4js = require('log4js'); const log4js = require('log4js');
const Changeset = require("ep_etherpad-lite/static/js/Changeset"); const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const contentcollector = require("ep_etherpad-lite/static/js/contentcollector"); const contentcollector = require('ep_etherpad-lite/static/js/contentcollector');
const cheerio = require("cheerio"); const cheerio = require('cheerio');
const rehype = require("rehype") const rehype = require('rehype');
const format = require("rehype-format") const format = require('rehype-format');
exports.setPadHTML = async (pad, html) => { exports.setPadHTML = async (pad, html) => {
var apiLogger = log4js.getLogger("ImportHtml"); const apiLogger = log4js.getLogger('ImportHtml');
var opts = { const opts = {
indentInitial: false, indentInitial: false,
indent: -1 indent: -1,
} };
rehype() rehype()
.use(format, opts) .use(format, opts)
.process(html, function(err, output){ .process(html, (err, output) => {
html = String(output).replace(/(\r\n|\n|\r)/gm,""); html = String(output).replace(/(\r\n|\n|\r)/gm, '');
}) });
var $ = cheerio.load(html); const $ = cheerio.load(html);
// Appends a line break, used by Etherpad to ensure a caret is available // Appends a line break, used by Etherpad to ensure a caret is available
// below the last line of an import // below the last line of an import
$('body').append("<p></p>"); $('body').append('<p></p>');
var doc = $('html')[0]; const doc = $('html')[0];
apiLogger.debug('html:'); apiLogger.debug('html:');
apiLogger.debug(html); apiLogger.debug(html);
// Convert a dom tree into a list of lines and attribute liens // Convert a dom tree into a list of lines and attribute liens
// using the content collector object // using the content collector object
var cc = contentcollector.makeContentCollector(true, null, pad.pool); const cc = contentcollector.makeContentCollector(true, null, pad.pool);
try { try {
// we use a try here because if the HTML is bad it will blow up // we use a try here because if the HTML is bad it will blow up
cc.collectContent(doc); cc.collectContent(doc);
} catch(e) { } catch (e) {
apiLogger.warn("HTML was not properly formed", e); apiLogger.warn('HTML was not properly formed', e);
// don't process the HTML because it was bad // don't process the HTML because it was bad
throw e; throw e;
} }
var result = cc.finish(); const result = cc.finish();
apiLogger.debug('Lines:'); apiLogger.debug('Lines:');
var i; let i;
for (i = 0; i < result.lines.length; i++) { for (i = 0; i < result.lines.length; i++) {
apiLogger.debug('Line ' + (i + 1) + ' text: ' + result.lines[i]); apiLogger.debug(`Line ${i + 1} text: ${result.lines[i]}`);
apiLogger.debug('Line ' + (i + 1) + ' attributes: ' + result.lineAttribs[i]); apiLogger.debug(`Line ${i + 1} attributes: ${result.lineAttribs[i]}`);
} }
// Get the new plain text and its attributes // Get the new plain text and its attributes
var newText = result.lines.join('\n'); const newText = result.lines.join('\n');
apiLogger.debug('newText:'); apiLogger.debug('newText:');
apiLogger.debug(newText); apiLogger.debug(newText);
var newAttribs = result.lineAttribs.join('|1+1') + '|1+1'; const newAttribs = `${result.lineAttribs.join('|1+1')}|1+1`;
function eachAttribRun(attribs, func /*(startInNewText, endInNewText, attribs)*/ ) { function eachAttribRun(attribs, func /* (startInNewText, endInNewText, attribs)*/) {
var attribsIter = Changeset.opIterator(attribs); const attribsIter = Changeset.opIterator(attribs);
var textIndex = 0; let textIndex = 0;
var newTextStart = 0; const newTextStart = 0;
var newTextEnd = newText.length; const newTextEnd = newText.length;
while (attribsIter.hasNext()) { while (attribsIter.hasNext()) {
var op = attribsIter.next(); const op = attribsIter.next();
var nextIndex = textIndex + op.chars; const nextIndex = textIndex + op.chars;
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) { if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs); func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
} }
@ -91,19 +91,19 @@ exports.setPadHTML = async (pad, html) => {
} }
// create a new changeset with a helper builder object // create a new changeset with a helper builder object
var builder = Changeset.builder(1); const builder = Changeset.builder(1);
// assemble each line into the builder // assemble each line into the builder
eachAttribRun(newAttribs, function(start, end, attribs) { eachAttribRun(newAttribs, (start, end, attribs) => {
builder.insert(newText.substring(start, end), attribs); builder.insert(newText.substring(start, end), attribs);
}); });
// the changeset is ready! // the changeset is ready!
var theChangeset = builder.toString(); const theChangeset = builder.toString();
apiLogger.debug('The changeset: ' + theChangeset); apiLogger.debug(`The changeset: ${theChangeset}`);
await Promise.all([ await Promise.all([
pad.setText('\n'), pad.setText('\n'),
pad.appendRevision(theChangeset), pad.appendRevision(theChangeset),
]); ]);
} };

View file

@ -16,18 +16,18 @@
* limitations under the License. * limitations under the License.
*/ */
var async = require("async"); const async = require('async');
var fs = require("fs"); const fs = require('fs');
var log4js = require('log4js'); const log4js = require('log4js');
var os = require("os"); const os = require('os');
var path = require("path"); const path = require('path');
var settings = require("./Settings"); const settings = require('./Settings');
var spawn = require("child_process").spawn; const spawn = require('child_process').spawn;
// Conversion tasks will be queued up, so we don't overload the system // Conversion tasks will be queued up, so we don't overload the system
var queue = async.queue(doConvertTask, 1); const queue = async.queue(doConvertTask, 1);
var libreOfficeLogger = log4js.getLogger('LibreOffice'); const libreOfficeLogger = log4js.getLogger('LibreOffice');
/** /**
* Convert a file from one type to another * Convert a file from one type to another
@ -37,18 +37,18 @@ var libreOfficeLogger = log4js.getLogger('LibreOffice');
* @param {String} type The type to convert into * @param {String} type The type to convert into
* @param {Function} callback Standard callback function * @param {Function} callback Standard callback function
*/ */
exports.convertFile = function(srcFile, destFile, type, callback) { exports.convertFile = function (srcFile, destFile, type, callback) {
// Used for the moving of the file, not the conversion // Used for the moving of the file, not the conversion
var fileExtension = type; const fileExtension = type;
if (type === "html") { if (type === 'html') {
// "html:XHTML Writer File:UTF8" does a better job than normal html exports // "html:XHTML Writer File:UTF8" does a better job than normal html exports
if (path.extname(srcFile).toLowerCase() === ".doc") { if (path.extname(srcFile).toLowerCase() === '.doc') {
type = "html"; type = 'html';
} }
// PDF files need to be converted with LO Draw ref https://github.com/ether/etherpad-lite/issues/4151 // PDF files need to be converted with LO Draw ref https://github.com/ether/etherpad-lite/issues/4151
if (path.extname(srcFile).toLowerCase() === ".pdf") { if (path.extname(srcFile).toLowerCase() === '.pdf') {
type = "html:XHTML Draw File" type = 'html:XHTML Draw File';
} }
} }
@ -57,37 +57,39 @@ exports.convertFile = function(srcFile, destFile, type, callback) {
// to avoid `Error: no export filter for /tmp/xxxx.doc` error // to avoid `Error: no export filter for /tmp/xxxx.doc` error
if (type === 'doc') { if (type === 'doc') {
queue.push({ queue.push({
"srcFile": srcFile, srcFile,
"destFile": destFile.replace(/\.doc$/, '.odt'), destFile: destFile.replace(/\.doc$/, '.odt'),
"type": 'odt', type: 'odt',
"callback": function () { callback() {
queue.push({"srcFile": srcFile.replace(/\.html$/, '.odt'), "destFile": destFile, "type": type, "callback": callback, "fileExtension": fileExtension }); queue.push({srcFile: srcFile.replace(/\.html$/, '.odt'), destFile, type, callback, fileExtension});
} },
}); });
} else { } else {
queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback, "fileExtension": fileExtension}); queue.push({srcFile, destFile, type, callback, fileExtension});
} }
}; };
function doConvertTask(task, callback) { function doConvertTask(task, callback) {
var tmpDir = os.tmpdir(); const tmpDir = os.tmpdir();
async.series([ async.series([
/* /*
* use LibreOffice to convert task.srcFile to another format, given in * use LibreOffice to convert task.srcFile to another format, given in
* task.type * task.type
*/ */
function(callback) { function (callback) {
libreOfficeLogger.debug(`Converting ${task.srcFile} to format ${task.type}. The result will be put in ${tmpDir}`); libreOfficeLogger.debug(`Converting ${task.srcFile} to format ${task.type}. The result will be put in ${tmpDir}`);
var soffice = spawn(settings.soffice, [ const soffice = spawn(settings.soffice, [
'--headless', '--headless',
'--invisible', '--invisible',
'--nologo', '--nologo',
'--nolockcheck', '--nolockcheck',
'--writer', '--writer',
'--convert-to', task.type, '--convert-to',
task.type,
task.srcFile, task.srcFile,
'--outdir', tmpDir '--outdir',
tmpDir,
]); ]);
// Soffice/libreoffice is buggy and often hangs. // Soffice/libreoffice is buggy and often hangs.
// To remedy this we kill the spawned process after a while. // To remedy this we kill the spawned process after a while.
@ -96,19 +98,19 @@ function doConvertTask(task, callback) {
soffice.kill(); soffice.kill();
}, 120000); }, 120000);
var stdoutBuffer = ''; let stdoutBuffer = '';
// Delegate the processing of stdout to another function // Delegate the processing of stdout to another function
soffice.stdout.on('data', function(data) { soffice.stdout.on('data', (data) => {
stdoutBuffer += data.toString(); stdoutBuffer += data.toString();
}); });
// Append error messages to the buffer // Append error messages to the buffer
soffice.stderr.on('data', function(data) { soffice.stderr.on('data', (data) => {
stdoutBuffer += data.toString(); stdoutBuffer += data.toString();
}); });
soffice.on('exit', function(code) { soffice.on('exit', (code) => {
clearTimeout(hangTimeout); clearTimeout(hangTimeout);
if (code != 0) { if (code != 0) {
// Throw an exception if libreoffice failed // Throw an exception if libreoffice failed
@ -117,18 +119,18 @@ function doConvertTask(task, callback) {
// if LibreOffice exited succesfully, go on with processing // if LibreOffice exited succesfully, go on with processing
callback(); callback();
}) });
}, },
// Move the converted file to the correct place // Move the converted file to the correct place
function(callback) { function (callback) {
var filename = path.basename(task.srcFile); const filename = path.basename(task.srcFile);
var sourceFilename = filename.substr(0, filename.lastIndexOf('.')) + '.' + task.fileExtension; const sourceFilename = `${filename.substr(0, filename.lastIndexOf('.'))}.${task.fileExtension}`;
var sourcePath = path.join(tmpDir, sourceFilename); const sourcePath = path.join(tmpDir, sourceFilename);
libreOfficeLogger.debug(`Renaming ${sourcePath} to ${task.destFile}`); libreOfficeLogger.debug(`Renaming ${sourcePath} to ${task.destFile}`);
fs.rename(sourcePath, task.destFile, callback); fs.rename(sourcePath, task.destFile, callback);
} },
], function(err) { ], (err) => {
// Invoke the callback for the local queue // Invoke the callback for the local queue
callback(); callback();

View file

@ -19,31 +19,29 @@
* limitations under the License. * limitations under the License.
*/ */
var ERR = require("async-stacktrace"); const ERR = require('async-stacktrace');
var settings = require('./Settings'); const settings = require('./Settings');
var async = require('async'); const async = require('async');
var fs = require('fs'); const fs = require('fs');
var StringDecoder = require('string_decoder').StringDecoder; const StringDecoder = require('string_decoder').StringDecoder;
var CleanCSS = require('clean-css'); const CleanCSS = require('clean-css');
var path = require('path'); const path = require('path');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugin_defs"); const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
var RequireKernel = require('etherpad-require-kernel'); const RequireKernel = require('etherpad-require-kernel');
var urlutil = require('url'); const urlutil = require('url');
var mime = require('mime-types') const mime = require('mime-types');
var Threads = require('threads') const Threads = require('threads');
var log4js = require('log4js'); const log4js = require('log4js');
var logger = log4js.getLogger("Minify"); const logger = log4js.getLogger('Minify');
var ROOT_DIR = path.normalize(__dirname + "/../../static/"); const ROOT_DIR = path.normalize(`${__dirname}/../../static/`);
var TAR_PATH = path.join(__dirname, 'tar.json'); const TAR_PATH = path.join(__dirname, 'tar.json');
var tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8')); const tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8'));
var threadsPool = Threads.Pool(function () { const threadsPool = Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2);
return Threads.spawn(new Threads.Worker("./MinifyWorker"))
}, 2)
var LIBRARY_WHITELIST = [ const LIBRARY_WHITELIST = [
'async', 'async',
'js-cookie', 'js-cookie',
'security', 'security',
@ -53,71 +51,68 @@ var LIBRARY_WHITELIST = [
]; ];
// Rewrite tar to include modules with no extensions and proper rooted paths. // Rewrite tar to include modules with no extensions and proper rooted paths.
var LIBRARY_PREFIX = 'ep_etherpad-lite/static/js'; const LIBRARY_PREFIX = 'ep_etherpad-lite/static/js';
exports.tar = {}; exports.tar = {};
function prefixLocalLibraryPath(path) { function prefixLocalLibraryPath(path) {
if (path.charAt(0) == '$') { if (path.charAt(0) == '$') {
return path.slice(1); return path.slice(1);
} else { } else {
return LIBRARY_PREFIX + '/' + path; return `${LIBRARY_PREFIX}/${path}`;
} }
} }
for (var key in tar) { for (const key in tar) {
exports.tar[prefixLocalLibraryPath(key)] = exports.tar[prefixLocalLibraryPath(key)] =
tar[key].map(prefixLocalLibraryPath).concat( tar[key].map(prefixLocalLibraryPath).concat(
tar[key].map(prefixLocalLibraryPath).map(function (p) { tar[key].map(prefixLocalLibraryPath).map((p) => p.replace(/\.js$/, '')),
return p.replace(/\.js$/, '');
})
).concat( ).concat(
tar[key].map(prefixLocalLibraryPath).map(function (p) { tar[key].map(prefixLocalLibraryPath).map((p) => `${p.replace(/\.js$/, '')}/index.js`),
return p.replace(/\.js$/, '') + '/index.js';
})
); );
} }
// What follows is a terrible hack to avoid loop-back within the server. // What follows is a terrible hack to avoid loop-back within the server.
// TODO: Serve files from another service, or directly from the file system. // TODO: Serve files from another service, or directly from the file system.
function requestURI(url, method, headers, callback) { function requestURI(url, method, headers, callback) {
var parsedURL = urlutil.parse(url); const parsedURL = urlutil.parse(url);
var status = 500, headers = {}, content = []; let status = 500; var headers = {}; const
content = [];
var mockRequest = { const mockRequest = {
url: url url,
, method: method method,
, params: {filename: parsedURL.path.replace(/^\/static\//, '')} params: {filename: parsedURL.path.replace(/^\/static\//, '')},
, headers: headers headers,
}; };
var mockResponse = { const mockResponse = {
writeHead: function (_status, _headers) { writeHead(_status, _headers) {
status = _status; status = _status;
for (var header in _headers) { for (const header in _headers) {
if (Object.prototype.hasOwnProperty.call(_headers, header)) { if (Object.prototype.hasOwnProperty.call(_headers, header)) {
headers[header] = _headers[header]; headers[header] = _headers[header];
} }
} }
} },
, setHeader: function (header, value) { setHeader(header, value) {
headers[header.toLowerCase()] = value.toString(); headers[header.toLowerCase()] = value.toString();
} },
, header: function (header, value) { header(header, value) {
headers[header.toLowerCase()] = value.toString(); headers[header.toLowerCase()] = value.toString();
} },
, write: function (_content) { write(_content) {
_content && content.push(_content); _content && content.push(_content);
} },
, end: function (_content) { end(_content) {
_content && content.push(_content); _content && content.push(_content);
callback(status, headers, content.join('')); callback(status, headers, content.join(''));
} },
}; };
minify(mockRequest, mockResponse); minify(mockRequest, mockResponse);
} }
function requestURIs(locations, method, headers, callback) { function requestURIs(locations, method, headers, callback) {
var pendingRequests = locations.length; let pendingRequests = locations.length;
var responses = []; const responses = [];
function respondFor(i) { function respondFor(i) {
return function (status, headers, content) { return function (status, headers, content) {
@ -128,14 +123,14 @@ function requestURIs(locations, method, headers, callback) {
}; };
} }
for (var i = 0, ii = locations.length; i < ii; i++) { for (let i = 0, ii = locations.length; i < ii; i++) {
requestURI(locations[i], method, headers, respondFor(i)); requestURI(locations[i], method, headers, respondFor(i));
} }
function completed() { function completed() {
var statuss = responses.map(function (x) {return x[0];}); const statuss = responses.map((x) => x[0]);
var headerss = responses.map(function (x) {return x[1];}); const headerss = responses.map((x) => x[1]);
var contentss = responses.map(function (x) {return x[2];}); const contentss = responses.map((x) => x[2]);
callback(statuss, headerss, contentss); callback(statuss, headerss, contentss);
} }
} }
@ -146,15 +141,15 @@ function requestURIs(locations, method, headers, callback) {
* @param res the Express response * @param res the Express response
*/ */
function minify(req, res) { function minify(req, res) {
var filename = req.params['filename']; let filename = req.params.filename;
// No relative paths, especially if they may go up the file hierarchy. // No relative paths, especially if they may go up the file hierarchy.
filename = path.normalize(path.join(ROOT_DIR, filename)); filename = path.normalize(path.join(ROOT_DIR, filename));
filename = filename.replace(/\.\./g, '') filename = filename.replace(/\.\./g, '');
if (filename.indexOf(ROOT_DIR) == 0) { if (filename.indexOf(ROOT_DIR) == 0) {
filename = filename.slice(ROOT_DIR.length); filename = filename.slice(ROOT_DIR.length);
filename = filename.replace(/\\/g, '/') filename = filename.replace(/\\/g, '/');
} else { } else {
res.writeHead(404, {}); res.writeHead(404, {});
res.end(); res.end();
@ -166,36 +161,36 @@ function minify(req, res) {
are rewritten into ROOT_PATH_OF_MYPLUGIN/static/js/test.js, are rewritten into ROOT_PATH_OF_MYPLUGIN/static/js/test.js,
commonly ETHERPAD_ROOT/node_modules/ep_myplugin/static/js/test.js commonly ETHERPAD_ROOT/node_modules/ep_myplugin/static/js/test.js
*/ */
var match = filename.match(/^plugins\/([^\/]+)(\/(?:(static\/.*)|.*))?$/); const match = filename.match(/^plugins\/([^\/]+)(\/(?:(static\/.*)|.*))?$/);
if (match) { if (match) {
var library = match[1]; const library = match[1];
var libraryPath = match[2] || ''; const libraryPath = match[2] || '';
if (plugins.plugins[library] && match[3]) { if (plugins.plugins[library] && match[3]) {
var plugin = plugins.plugins[library]; const plugin = plugins.plugins[library];
var pluginPath = plugin.package.realPath; const pluginPath = plugin.package.realPath;
filename = path.relative(ROOT_DIR, pluginPath + libraryPath); filename = path.relative(ROOT_DIR, pluginPath + libraryPath);
filename = filename.replace(/\\/g, '/'); // windows path fix filename = filename.replace(/\\/g, '/'); // windows path fix
} else if (LIBRARY_WHITELIST.indexOf(library) != -1) { } else if (LIBRARY_WHITELIST.indexOf(library) != -1) {
// Go straight into node_modules // Go straight into node_modules
// Avoid `require.resolve()`, since 'mustache' and 'mustache/index.js' // Avoid `require.resolve()`, since 'mustache' and 'mustache/index.js'
// would end up resolving to logically distinct resources. // would end up resolving to logically distinct resources.
filename = '../node_modules/' + library + libraryPath; filename = `../node_modules/${library}${libraryPath}`;
} }
} }
var contentType = mime.lookup(filename); const contentType = mime.lookup(filename);
statFile(filename, function (error, date, exists) { statFile(filename, (error, date, exists) => {
if (date) { if (date) {
date = new Date(date); date = new Date(date);
date.setMilliseconds(0); date.setMilliseconds(0);
res.setHeader('last-modified', date.toUTCString()); res.setHeader('last-modified', date.toUTCString());
res.setHeader('date', (new Date()).toUTCString()); res.setHeader('date', (new Date()).toUTCString());
if (settings.maxAge !== undefined) { if (settings.maxAge !== undefined) {
var expiresDate = new Date(Date.now()+settings.maxAge*1000); const expiresDate = new Date(Date.now() + settings.maxAge * 1000);
res.setHeader('expires', expiresDate.toUTCString()); res.setHeader('expires', expiresDate.toUTCString());
res.setHeader('cache-control', 'max-age=' + settings.maxAge); res.setHeader('cache-control', `max-age=${settings.maxAge}`);
} }
} }
@ -208,37 +203,35 @@ function minify(req, res) {
} else if (new Date(req.headers['if-modified-since']) >= date) { } else if (new Date(req.headers['if-modified-since']) >= date) {
res.writeHead(304, {}); res.writeHead(304, {});
res.end(); res.end();
} else { } else if (req.method == 'HEAD') {
if (req.method == 'HEAD') { res.header('Content-Type', contentType);
res.header("Content-Type", contentType); res.writeHead(200, {});
res.writeHead(200, {}); res.end();
res.end(); } else if (req.method == 'GET') {
} else if (req.method == 'GET') { getFileCompressed(filename, contentType, (error, content) => {
getFileCompressed(filename, contentType, function (error, content) { if (ERR(error, () => {
if(ERR(error, function(){ res.writeHead(500, {});
res.writeHead(500, {});
res.end();
})) return;
res.header("Content-Type", contentType);
res.writeHead(200, {});
res.write(content);
res.end(); res.end();
}); })) return;
} else { res.header('Content-Type', contentType);
res.writeHead(405, {'allow': 'HEAD, GET'}); res.writeHead(200, {});
res.write(content);
res.end(); res.end();
} });
} else {
res.writeHead(405, {allow: 'HEAD, GET'});
res.end();
} }
}, 3); }, 3);
} }
// find all includes in ace.js and embed them. // find all includes in ace.js and embed them.
function getAceFile(callback) { function getAceFile(callback) {
fs.readFile(ROOT_DIR + 'js/ace.js', "utf8", function(err, data) { fs.readFile(`${ROOT_DIR}js/ace.js`, 'utf8', (err, data) => {
if(ERR(err, callback)) return; if (ERR(err, callback)) return;
// Find all includes in ace.js and embed them // Find all includes in ace.js and embed them
var founds = data.match(/\$\$INCLUDE_[a-zA-Z_]+\("[^"]*"\)/gi); let founds = data.match(/\$\$INCLUDE_[a-zA-Z_]+\("[^"]*"\)/gi);
if (!settings.minify) { if (!settings.minify) {
founds = []; founds = [];
} }
@ -250,25 +243,25 @@ function getAceFile(callback) {
// Request the contents of the included file on the server-side and write // Request the contents of the included file on the server-side and write
// them into the file. // them into the file.
async.forEach(founds, function (item, callback) { async.forEach(founds, (item, callback) => {
var filename = item.match(/"([^"]*)"/)[1]; const filename = item.match(/"([^"]*)"/)[1];
// Hostname "invalid.invalid" is a dummy value to allow parsing as a URI. // Hostname "invalid.invalid" is a dummy value to allow parsing as a URI.
var baseURI = 'http://invalid.invalid'; const baseURI = 'http://invalid.invalid';
var resourceURI = baseURI + path.normalize(path.join('/static/', filename)); let resourceURI = baseURI + path.normalize(path.join('/static/', filename));
resourceURI = resourceURI.replace(/\\/g, '/'); // Windows (safe generally?) resourceURI = resourceURI.replace(/\\/g, '/'); // Windows (safe generally?)
requestURI(resourceURI, 'GET', {}, function (status, headers, body) { requestURI(resourceURI, 'GET', {}, (status, headers, body) => {
var error = !(status == 200 || status == 404); const error = !(status == 200 || status == 404);
if (!error) { if (!error) {
data += 'Ace2Editor.EMBEDED[' + JSON.stringify(filename) + '] = ' data += `Ace2Editor.EMBEDED[${JSON.stringify(filename)}] = ${
+ JSON.stringify(status == 200 ? body || '' : null) + ';\n'; JSON.stringify(status == 200 ? body || '' : null)};\n`;
} else { } else {
console.error(`getAceFile(): error getting ${resourceURI}. Status code: ${status}`); console.error(`getAceFile(): error getting ${resourceURI}. Status code: ${status}`);
} }
callback(); callback();
}); });
}, function(error) { }, (error) => {
callback(error, data); callback(error, data);
}); });
}); });
@ -289,19 +282,19 @@ function statFile(filename, callback, dirStatLimit) {
} else if (filename == 'js/ace.js') { } else if (filename == 'js/ace.js') {
// Sometimes static assets are inlined into this file, so we have to stat // Sometimes static assets are inlined into this file, so we have to stat
// everything. // everything.
lastModifiedDateOfEverything(function (error, date) { lastModifiedDateOfEverything((error, date) => {
callback(error, date, !error); callback(error, date, !error);
}); });
} else if (filename == 'js/require-kernel.js') { } else if (filename == 'js/require-kernel.js') {
callback(null, requireLastModified(), true); callback(null, requireLastModified(), true);
} else { } else {
fs.stat(ROOT_DIR + filename, function (error, stats) { fs.stat(ROOT_DIR + filename, (error, stats) => {
if (error) { if (error) {
if (error.code == "ENOENT") { if (error.code == 'ENOENT') {
// Stat the directory instead. // Stat the directory instead.
statFile(path.dirname(filename), function (error, date, exists) { statFile(path.dirname(filename), (error, date, exists) => {
callback(error, date, false); callback(error, date, false);
}, dirStatLimit-1); }, dirStatLimit - 1);
} else { } else {
callback(error); callback(error);
} }
@ -314,29 +307,28 @@ function statFile(filename, callback, dirStatLimit) {
} }
} }
function lastModifiedDateOfEverything(callback) { function lastModifiedDateOfEverything(callback) {
var folders2check = [ROOT_DIR + 'js/', ROOT_DIR + 'css/']; const folders2check = [`${ROOT_DIR}js/`, `${ROOT_DIR}css/`];
var latestModification = 0; let latestModification = 0;
//go trough this two folders // go trough this two folders
async.forEach(folders2check, function(path, callback) { async.forEach(folders2check, (path, callback) => {
//read the files in the folder // read the files in the folder
fs.readdir(path, function(err, files) { fs.readdir(path, (err, files) => {
if(ERR(err, callback)) return; if (ERR(err, callback)) return;
//we wanna check the directory itself for changes too // we wanna check the directory itself for changes too
files.push("."); files.push('.');
//go trough all files in this folder // go trough all files in this folder
async.forEach(files, function(filename, callback) { async.forEach(files, (filename, callback) => {
//get the stat data of this file // get the stat data of this file
fs.stat(path + "/" + filename, function(err, stats) { fs.stat(`${path}/${filename}`, (err, stats) => {
if(ERR(err, callback)) return; if (ERR(err, callback)) return;
//get the modification time // get the modification time
var modificationTime = stats.mtime.getTime(); const modificationTime = stats.mtime.getTime();
//compare the modification time to the highest found // compare the modification time to the highest found
if(modificationTime > latestModification) if (modificationTime > latestModification) {
{
latestModification = modificationTime; latestModification = modificationTime;
} }
@ -344,29 +336,29 @@ function lastModifiedDateOfEverything(callback) {
}); });
}, callback); }, callback);
}); });
}, function () { }, () => {
callback(null, latestModification); callback(null, latestModification);
}); });
} }
// This should be provided by the module, but until then, just use startup // This should be provided by the module, but until then, just use startup
// time. // time.
var _requireLastModified = new Date(); const _requireLastModified = new Date();
function requireLastModified() { function requireLastModified() {
return _requireLastModified.toUTCString(); return _requireLastModified.toUTCString();
} }
function requireDefinition() { function requireDefinition() {
return 'var require = ' + RequireKernel.kernelSource + ';\n'; return `var require = ${RequireKernel.kernelSource};\n`;
} }
function getFileCompressed(filename, contentType, callback) { function getFileCompressed(filename, contentType, callback) {
getFile(filename, function (error, content) { getFile(filename, (error, content) => {
if (error || !content || !settings.minify) { if (error || !content || !settings.minify) {
callback(error, content); callback(error, content);
} else if (contentType == 'application/javascript') { } else if (contentType == 'application/javascript') {
threadsPool.queue(async ({ compressJS }) => { threadsPool.queue(async ({compressJS}) => {
try { try {
logger.info('Compress JS file %s.', filename) logger.info('Compress JS file %s.', filename);
content = content.toString(); content = content.toString();
const compressResult = await compressJS(content); const compressResult = await compressJS(content);
@ -381,11 +373,11 @@ function getFileCompressed(filename, contentType, callback) {
} }
callback(null, content); callback(null, content);
}) });
} else if (contentType == 'text/css') { } else if (contentType == 'text/css') {
threadsPool.queue(async ({ compressCSS }) => { threadsPool.queue(async ({compressCSS}) => {
try { try {
logger.info('Compress CSS file %s.', filename) logger.info('Compress CSS file %s.', filename);
content = await compressCSS(filename, ROOT_DIR); content = await compressCSS(filename, ROOT_DIR);
} catch (error) { } catch (error) {
@ -393,7 +385,7 @@ function getFileCompressed(filename, contentType, callback) {
} }
callback(null, content); callback(null, content);
}) });
} else { } else {
callback(null, content); callback(null, content);
} }

View file

@ -2,10 +2,10 @@
* Worker thread to minify JS & CSS files out of the main NodeJS thread * Worker thread to minify JS & CSS files out of the main NodeJS thread
*/ */
var CleanCSS = require('clean-css'); const CleanCSS = require('clean-css');
var Terser = require("terser"); const Terser = require('terser');
var path = require('path'); const path = require('path');
var Threads = require('threads') const Threads = require('threads');
function compressJS(content) { function compressJS(content) {
return Terser.minify(content); return Terser.minify(content);
@ -46,20 +46,20 @@ function compressCSS(filename, ROOT_DIR) {
new CleanCSS({ new CleanCSS({
rebase: true, rebase: true,
rebaseTo: basePath, rebaseTo: basePath,
}).minify([absPath], function (errors, minified) { }).minify([absPath], (errors, minified) => {
if (errors) return rej(errors) if (errors) return rej(errors);
return res(minified.styles) return res(minified.styles);
}); });
} catch (error) { } catch (error) {
// on error, just yield the un-minified original, but write a log message // on error, just yield the un-minified original, but write a log message
console.error(`Unexpected error minifying ${filename} (${absPath}): ${error}`); console.error(`Unexpected error minifying ${filename} (${absPath}): ${error}`);
callback(null, content); callback(null, content);
} }
}) });
} }
Threads.expose({ Threads.expose({
compressJS, compressJS,
compressCSS compressCSS,
}) });

View file

@ -25,17 +25,17 @@ const semver = require('semver');
* *
* @param {String} minNodeVersion Minimum required Node version * @param {String} minNodeVersion Minimum required Node version
*/ */
exports.enforceMinNodeVersion = function(minNodeVersion) { exports.enforceMinNodeVersion = function (minNodeVersion) {
const currentNodeVersion = process.version; const currentNodeVersion = process.version;
// we cannot use template literals, since we still do not know if we are // we cannot use template literals, since we still do not know if we are
// running under Node >= 4.0 // running under Node >= 4.0
if (semver.lt(currentNodeVersion, minNodeVersion)) { if (semver.lt(currentNodeVersion, minNodeVersion)) {
console.error('Running Etherpad on Node ' + currentNodeVersion + ' is not supported. Please upgrade at least to Node ' + minNodeVersion); console.error(`Running Etherpad on Node ${currentNodeVersion} is not supported. Please upgrade at least to Node ${minNodeVersion}`);
process.exit(1); process.exit(1);
} }
console.debug('Running on Node ' + currentNodeVersion + ' (minimum required Node version: ' + minNodeVersion + ')'); console.debug(`Running on Node ${currentNodeVersion} (minimum required Node version: ${minNodeVersion})`);
}; };
/** /**
@ -44,7 +44,7 @@ exports.enforceMinNodeVersion = function(minNodeVersion) {
* @param {String} lowestNonDeprecatedNodeVersion all Node version less than this one are deprecated * @param {String} lowestNonDeprecatedNodeVersion all Node version less than this one are deprecated
* @param {Function} epRemovalVersion Etherpad version that will remove support for deprecated Node releases * @param {Function} epRemovalVersion Etherpad version that will remove support for deprecated Node releases
*/ */
exports.checkDeprecationStatus = function(lowestNonDeprecatedNodeVersion, epRemovalVersion) { exports.checkDeprecationStatus = function (lowestNonDeprecatedNodeVersion, epRemovalVersion) {
const currentNodeVersion = process.version; const currentNodeVersion = process.version;
if (semver.lt(currentNodeVersion, lowestNonDeprecatedNodeVersion)) { if (semver.lt(currentNodeVersion, lowestNonDeprecatedNodeVersion)) {

View file

@ -26,17 +26,17 @@
* limitations under the License. * limitations under the License.
*/ */
var absolutePaths = require('./AbsolutePaths'); const absolutePaths = require('./AbsolutePaths');
var fs = require("fs"); const fs = require('fs');
var os = require("os"); const os = require('os');
var path = require('path'); const path = require('path');
var argv = require('./Cli').argv; const argv = require('./Cli').argv;
var npm = require("npm/lib/npm.js"); const npm = require('npm/lib/npm.js');
var jsonminify = require("jsonminify"); const jsonminify = require('jsonminify');
var log4js = require("log4js"); const log4js = require('log4js');
var randomString = require("./randomstring"); const randomString = require('./randomstring');
var suppressDisableMsg = " -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n"; const suppressDisableMsg = ' -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n';
var _ = require("underscore"); const _ = require('underscore');
/* Root path of the installation */ /* Root path of the installation */
exports.root = absolutePaths.findEtherpadRoot(); exports.root = absolutePaths.findEtherpadRoot();
@ -59,14 +59,14 @@ console.log(`Random string used for versioning assets: ${exports.randomVersionSt
/** /**
* The app title, visible e.g. in the browser window * The app title, visible e.g. in the browser window
*/ */
exports.title = "Etherpad"; exports.title = 'Etherpad';
/** /**
* The app favicon fully specified url, visible e.g. in the browser window * The app favicon fully specified url, visible e.g. in the browser window
*/ */
exports.favicon = "favicon.ico"; exports.favicon = 'favicon.ico';
exports.faviconPad = "../" + exports.favicon; exports.faviconPad = `../${exports.favicon}`;
exports.faviconTimeslider = "../../" + exports.favicon; exports.faviconTimeslider = `../../${exports.favicon}`;
/* /*
* Skin name. * Skin name.
@ -76,12 +76,12 @@ exports.faviconTimeslider = "../../" + exports.favicon;
*/ */
exports.skinName = null; exports.skinName = null;
exports.skinVariants = "super-light-toolbar super-light-editor light-background"; exports.skinVariants = 'super-light-toolbar super-light-editor light-background';
/** /**
* The IP ep-lite should listen to * The IP ep-lite should listen to
*/ */
exports.ip = "0.0.0.0"; exports.ip = '0.0.0.0';
/** /**
* The Port ep-lite should listen to * The Port ep-lite should listen to
@ -107,60 +107,60 @@ exports.socketTransportProtocols = ['xhr-polling', 'jsonp-polling', 'htmlfile'];
/* /*
* The Type of the database * The Type of the database
*/ */
exports.dbType = "dirty"; exports.dbType = 'dirty';
/** /**
* This setting is passed with dbType to ueberDB to set up the database * This setting is passed with dbType to ueberDB to set up the database
*/ */
exports.dbSettings = { "filename" : path.join(exports.root, "var/dirty.db") }; exports.dbSettings = {filename: path.join(exports.root, 'var/dirty.db')};
/** /**
* The default Text of a new pad * The default Text of a new pad
*/ */
exports.defaultPadText = "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad on Github: https:\/\/github.com\/ether\/etherpad-lite\n"; exports.defaultPadText = 'Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad on Github: https:\/\/github.com\/ether\/etherpad-lite\n';
/** /**
* The default Pad Settings for a user (Can be overridden by changing the setting * The default Pad Settings for a user (Can be overridden by changing the setting
*/ */
exports.padOptions = { exports.padOptions = {
"noColors": false, noColors: false,
"showControls": true, showControls: true,
"showChat": true, showChat: true,
"showLineNumbers": true, showLineNumbers: true,
"useMonospaceFont": false, useMonospaceFont: false,
"userName": false, userName: false,
"userColor": false, userColor: false,
"rtl": false, rtl: false,
"alwaysShowChat": false, alwaysShowChat: false,
"chatAndUsers": false, chatAndUsers: false,
"lang": "en-gb" lang: 'en-gb',
}, },
/** /**
* Whether certain shortcut keys are enabled for a user in the pad * Whether certain shortcut keys are enabled for a user in the pad
*/ */
exports.padShortcutEnabled = { exports.padShortcutEnabled = {
"altF9" : true, altF9: true,
"altC" : true, altC: true,
"delete" : true, delete: true,
"cmdShift2" : true, cmdShift2: true,
"return" : true, return: true,
"esc" : true, esc: true,
"cmdS" : true, cmdS: true,
"tab" : true, tab: true,
"cmdZ" : true, cmdZ: true,
"cmdY" : true, cmdY: true,
"cmdB" : true, cmdB: true,
"cmdI" : true, cmdI: true,
"cmdU" : true, cmdU: true,
"cmd5" : true, cmd5: true,
"cmdShiftL" : true, cmdShiftL: true,
"cmdShiftN" : true, cmdShiftN: true,
"cmdShift1" : true, cmdShift1: true,
"cmdShiftC" : true, cmdShiftC: true,
"cmdH" : true, cmdH: true,
"ctrlHome" : true, ctrlHome: true,
"pageUp" : true, pageUp: true,
"pageDown" : true, pageDown: true,
}, },
/** /**
@ -168,20 +168,20 @@ exports.padShortcutEnabled = {
*/ */
exports.toolbar = { exports.toolbar = {
left: [ left: [
["bold", "italic", "underline", "strikethrough"], ['bold', 'italic', 'underline', 'strikethrough'],
["orderedlist", "unorderedlist", "indent", "outdent"], ['orderedlist', 'unorderedlist', 'indent', 'outdent'],
["undo", "redo"], ['undo', 'redo'],
["clearauthorship"] ['clearauthorship'],
], ],
right: [ right: [
["importexport", "timeslider", "savedrevision"], ['importexport', 'timeslider', 'savedrevision'],
["settings", "embed"], ['settings', 'embed'],
["showusers"] ['showusers'],
], ],
timeslider: [ timeslider: [
["timeslider_export", "timeslider_settings", "timeslider_returnToPad"] ['timeslider_export', 'timeslider_settings', 'timeslider_returnToPad'],
] ],
} };
/** /**
* A flag that requires any user to have a valid session (via the api) before accessing a pad * A flag that requires any user to have a valid session (via the api) before accessing a pad
@ -196,7 +196,7 @@ exports.editOnly = false;
/** /**
* Max age that responses will have (affects caching layer). * Max age that responses will have (affects caching layer).
*/ */
exports.maxAge = 1000*60*60*6; // 6 hours exports.maxAge = 1000 * 60 * 60 * 6; // 6 hours
/** /**
* A flag that shows if minification is enabled or not * A flag that shows if minification is enabled or not
@ -226,7 +226,7 @@ exports.allowUnknownFileEnds = true;
/** /**
* The log level of log4js * The log level of log4js
*/ */
exports.loglevel = "INFO"; exports.loglevel = 'INFO';
/** /**
* Disable IP logging * Disable IP logging
@ -251,7 +251,7 @@ exports.indentationOnNewLine = true;
/* /*
* log4js appender configuration * log4js appender configuration
*/ */
exports.logconfig = { appenders: [{ type: "console" }]}; exports.logconfig = {appenders: [{type: 'console'}]};
/* /*
* Session Key, do not sure this. * Session Key, do not sure this.
@ -303,28 +303,28 @@ exports.scrollWhenFocusLineIsOutOfViewport = {
/* /*
* Percentage of viewport height to be additionally scrolled. * Percentage of viewport height to be additionally scrolled.
*/ */
"percentage": { percentage: {
"editionAboveViewport": 0, editionAboveViewport: 0,
"editionBelowViewport": 0 editionBelowViewport: 0,
}, },
/* /*
* Time (in milliseconds) used to animate the scroll transition. Set to 0 to * Time (in milliseconds) used to animate the scroll transition. Set to 0 to
* disable animation * disable animation
*/ */
"duration": 0, duration: 0,
/* /*
* Percentage of viewport height to be additionally scrolled when user presses arrow up * Percentage of viewport height to be additionally scrolled when user presses arrow up
* in the line of the top of the viewport. * in the line of the top of the viewport.
*/ */
"percentageToScrollWhenUserPressesArrowUp": 0, percentageToScrollWhenUserPressesArrowUp: 0,
/* /*
* Flag to control if it should scroll when user places the caret in the last * Flag to control if it should scroll when user places the caret in the last
* line of the viewport * line of the viewport
*/ */
"scrollWhenCaretIsInTheLastLineOfViewport": false scrollWhenCaretIsInTheLastLineOfViewport: false,
}; };
/* /*
@ -350,10 +350,10 @@ exports.customLocaleStrings = {};
*/ */
exports.importExportRateLimiting = { exports.importExportRateLimiting = {
// duration of the rate limit window (milliseconds) // duration of the rate limit window (milliseconds)
"windowMs": 90000, windowMs: 90000,
// maximum number of requests per IP to allow during the rate limit window // maximum number of requests per IP to allow during the rate limit window
"max": 10 max: 10,
}; };
/* /*
@ -366,10 +366,10 @@ exports.importExportRateLimiting = {
*/ */
exports.commitRateLimiting = { exports.commitRateLimiting = {
// duration of the rate limit window (seconds) // duration of the rate limit window (seconds)
"duration": 1, duration: 1,
// maximum number of chanes per IP to allow during the rate limit window // maximum number of chanes per IP to allow during the rate limit window
"points": 10 points: 10,
}; };
/* /*
@ -381,64 +381,64 @@ exports.commitRateLimiting = {
exports.importMaxFileSize = 50 * 1024 * 1024; exports.importMaxFileSize = 50 * 1024 * 1024;
// checks if abiword is avaiable // checks if abiword is avaiable
exports.abiwordAvailable = function() { exports.abiwordAvailable = function () {
if (exports.abiword != null) { if (exports.abiword != null) {
return os.type().indexOf("Windows") != -1 ? "withoutPDF" : "yes"; return os.type().indexOf('Windows') != -1 ? 'withoutPDF' : 'yes';
} else { } else {
return "no"; return 'no';
} }
}; };
exports.sofficeAvailable = function() { exports.sofficeAvailable = function () {
if (exports.soffice != null) { if (exports.soffice != null) {
return os.type().indexOf("Windows") != -1 ? "withoutPDF": "yes"; return os.type().indexOf('Windows') != -1 ? 'withoutPDF' : 'yes';
} else { } else {
return "no"; return 'no';
} }
}; };
exports.exportAvailable = function() { exports.exportAvailable = function () {
var abiword = exports.abiwordAvailable(); const abiword = exports.abiwordAvailable();
var soffice = exports.sofficeAvailable(); const soffice = exports.sofficeAvailable();
if (abiword == "no" && soffice == "no") { if (abiword == 'no' && soffice == 'no') {
return "no"; return 'no';
} else if ((abiword == "withoutPDF" && soffice == "no") || (abiword == "no" && soffice == "withoutPDF")) { } else if ((abiword == 'withoutPDF' && soffice == 'no') || (abiword == 'no' && soffice == 'withoutPDF')) {
return "withoutPDF"; return 'withoutPDF';
} else { } else {
return "yes"; return 'yes';
} }
}; };
// Provide git version if available // Provide git version if available
exports.getGitCommit = function() { exports.getGitCommit = function () {
var version = ""; let version = '';
try { try {
var rootPath = exports.root; let rootPath = exports.root;
if (fs.lstatSync(rootPath + '/.git').isFile()) { if (fs.lstatSync(`${rootPath}/.git`).isFile()) {
rootPath = fs.readFileSync(rootPath + '/.git', "utf8"); rootPath = fs.readFileSync(`${rootPath}/.git`, 'utf8');
rootPath = rootPath.split(' ').pop().trim(); rootPath = rootPath.split(' ').pop().trim();
} else { } else {
rootPath += '/.git'; rootPath += '/.git';
} }
var ref = fs.readFileSync(rootPath + "/HEAD", "utf-8"); const ref = fs.readFileSync(`${rootPath}/HEAD`, 'utf-8');
if (ref.startsWith("ref: ")) { if (ref.startsWith('ref: ')) {
var refPath = rootPath + "/" + ref.substring(5, ref.indexOf("\n")); const refPath = `${rootPath}/${ref.substring(5, ref.indexOf('\n'))}`;
version = fs.readFileSync(refPath, "utf-8"); version = fs.readFileSync(refPath, 'utf-8');
} else { } else {
version = ref; version = ref;
} }
version = version.substring(0, 7); version = version.substring(0, 7);
} catch(e) { } catch (e) {
console.warn("Can't get git version for server header\n" + e.message) console.warn(`Can't get git version for server header\n${e.message}`);
} }
return version; return version;
} };
// Return etherpad version from package.json // Return etherpad version from package.json
exports.getEpVersion = function() { exports.getEpVersion = function () {
return require('ep_etherpad-lite/package.json').version; return require('ep_etherpad-lite/package.json').version;
} };
/** /**
* Receives a settingsObj and, if the property name is a valid configuration * Receives a settingsObj and, if the property name is a valid configuration
@ -448,9 +448,9 @@ exports.getEpVersion = function() {
* both "settings.json" and "credentials.json". * both "settings.json" and "credentials.json".
*/ */
function storeSettings(settingsObj) { function storeSettings(settingsObj) {
for (var i in settingsObj) { for (const i in settingsObj) {
// test if the setting starts with a lowercase character // test if the setting starts with a lowercase character
if (i.charAt(0).search("[a-z]") !== 0) { if (i.charAt(0).search('[a-z]') !== 0) {
console.warn(`Settings should start with a lowercase character: '${i}'`); console.warn(`Settings should start with a lowercase character: '${i}'`);
} }
@ -482,26 +482,26 @@ function storeSettings(settingsObj) {
* in the literal string "null", instead. * in the literal string "null", instead.
*/ */
function coerceValue(stringValue) { function coerceValue(stringValue) {
// cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number // cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number
const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue)); const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue));
if (isNumeric) { if (isNumeric) {
// detected numeric string. Coerce to a number // detected numeric string. Coerce to a number
return +stringValue; return +stringValue;
} }
// the boolean literal case is easy. // the boolean literal case is easy.
if (stringValue === "true" ) { if (stringValue === 'true') {
return true; return true;
} }
if (stringValue === "false") { if (stringValue === 'false') {
return false; return false;
} }
// otherwise, return this value as-is // otherwise, return this value as-is
return stringValue; return stringValue;
} }
/** /**
@ -624,24 +624,24 @@ function lookupEnvironmentVariables(obj) {
* The isSettings variable only controls the error logging. * The isSettings variable only controls the error logging.
*/ */
function parseSettings(settingsFilename, isSettings) { function parseSettings(settingsFilename, isSettings) {
let settingsStr = ""; let settingsStr = '';
let settingsType, notFoundMessage, notFoundFunction; let settingsType, notFoundMessage, notFoundFunction;
if (isSettings) { if (isSettings) {
settingsType = "settings"; settingsType = 'settings';
notFoundMessage = "Continuing using defaults!"; notFoundMessage = 'Continuing using defaults!';
notFoundFunction = console.warn; notFoundFunction = console.warn;
} else { } else {
settingsType = "credentials"; settingsType = 'credentials';
notFoundMessage = "Ignoring."; notFoundMessage = 'Ignoring.';
notFoundFunction = console.info; notFoundFunction = console.info;
} }
try { try {
//read the settings file // read the settings file
settingsStr = fs.readFileSync(settingsFilename).toString(); settingsStr = fs.readFileSync(settingsFilename).toString();
} catch(e) { } catch (e) {
notFoundFunction(`No ${settingsType} file found in ${settingsFilename}. ${notFoundMessage}`); notFoundFunction(`No ${settingsType} file found in ${settingsFilename}. ${notFoundMessage}`);
// or maybe undefined! // or maybe undefined!
@ -649,7 +649,7 @@ function parseSettings(settingsFilename, isSettings) {
} }
try { try {
settingsStr = jsonminify(settingsStr).replace(",]","]").replace(",}","}"); settingsStr = jsonminify(settingsStr).replace(',]', ']').replace(',}', '}');
const settings = JSON.parse(settingsStr); const settings = JSON.parse(settingsStr);
@ -658,7 +658,7 @@ function parseSettings(settingsFilename, isSettings) {
const replacedSettings = lookupEnvironmentVariables(settings); const replacedSettings = lookupEnvironmentVariables(settings);
return replacedSettings; return replacedSettings;
} catch(e) { } catch (e) {
console.error(`There was an error processing your ${settingsType} file from ${settingsFilename}: ${e.message}`); console.error(`There was an error processing your ${settingsType} file from ${settingsFilename}: ${e.message}`);
process.exit(1); process.exit(1);
@ -667,55 +667,55 @@ function parseSettings(settingsFilename, isSettings) {
exports.reloadSettings = function reloadSettings() { exports.reloadSettings = function reloadSettings() {
// Discover where the settings file lives // Discover where the settings file lives
var settingsFilename = absolutePaths.makeAbsolute(argv.settings || "settings.json"); const settingsFilename = absolutePaths.makeAbsolute(argv.settings || 'settings.json');
// Discover if a credential file exists // Discover if a credential file exists
var credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || "credentials.json"); const credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || 'credentials.json');
// try to parse the settings // try to parse the settings
var settings = parseSettings(settingsFilename, true); const settings = parseSettings(settingsFilename, true);
// try to parse the credentials // try to parse the credentials
var credentials = parseSettings(credentialsFilename, false); const credentials = parseSettings(credentialsFilename, false);
storeSettings(settings); storeSettings(settings);
storeSettings(credentials); storeSettings(credentials);
log4js.configure(exports.logconfig);//Configure the logging appenders log4js.configure(exports.logconfig);// Configure the logging appenders
log4js.setGlobalLogLevel(exports.loglevel);//set loglevel log4js.setGlobalLogLevel(exports.loglevel);// set loglevel
process.env['DEBUG'] = 'socket.io:' + exports.loglevel; // Used by SocketIO for Debug process.env.DEBUG = `socket.io:${exports.loglevel}`; // Used by SocketIO for Debug
log4js.replaceConsole(); log4js.replaceConsole();
if (!exports.skinName) { if (!exports.skinName) {
console.warn(`No "skinName" parameter found. Please check out settings.json.template and update your settings.json. Falling back to the default "colibris".`); console.warn('No "skinName" parameter found. Please check out settings.json.template and update your settings.json. Falling back to the default "colibris".');
exports.skinName = "colibris"; exports.skinName = 'colibris';
} }
// checks if skinName has an acceptable value, otherwise falls back to "colibris" // checks if skinName has an acceptable value, otherwise falls back to "colibris"
if (exports.skinName) { if (exports.skinName) {
const skinBasePath = path.join(exports.root, "src", "static", "skins"); const skinBasePath = path.join(exports.root, 'src', 'static', 'skins');
const countPieces = exports.skinName.split(path.sep).length; const countPieces = exports.skinName.split(path.sep).length;
if (countPieces != 1) { if (countPieces != 1) {
console.error(`skinName must be the name of a directory under "${skinBasePath}". This is not valid: "${exports.skinName}". Falling back to the default "colibris".`); console.error(`skinName must be the name of a directory under "${skinBasePath}". This is not valid: "${exports.skinName}". Falling back to the default "colibris".`);
exports.skinName = "colibris"; exports.skinName = 'colibris';
} }
// informative variable, just for the log messages // informative variable, just for the log messages
var skinPath = path.normalize(path.join(skinBasePath, exports.skinName)); let skinPath = path.normalize(path.join(skinBasePath, exports.skinName));
// what if someone sets skinName == ".." or "."? We catch him! // what if someone sets skinName == ".." or "."? We catch him!
if (absolutePaths.isSubdir(skinBasePath, skinPath) === false) { if (absolutePaths.isSubdir(skinBasePath, skinPath) === false) {
console.error(`Skin path ${skinPath} must be a subdirectory of ${skinBasePath}. Falling back to the default "colibris".`); console.error(`Skin path ${skinPath} must be a subdirectory of ${skinBasePath}. Falling back to the default "colibris".`);
exports.skinName = "colibris"; exports.skinName = 'colibris';
skinPath = path.join(skinBasePath, exports.skinName); skinPath = path.join(skinBasePath, exports.skinName);
} }
if (fs.existsSync(skinPath) === false) { if (fs.existsSync(skinPath) === false) {
console.error(`Skin path ${skinPath} does not exist. Falling back to the default "colibris".`); console.error(`Skin path ${skinPath} does not exist. Falling back to the default "colibris".`);
exports.skinName = "colibris"; exports.skinName = 'colibris';
skinPath = path.join(skinBasePath, exports.skinName); skinPath = path.join(skinBasePath, exports.skinName);
} }
@ -725,13 +725,13 @@ exports.reloadSettings = function reloadSettings() {
if (exports.abiword) { if (exports.abiword) {
// Check abiword actually exists // Check abiword actually exists
if (exports.abiword != null) { if (exports.abiword != null) {
fs.exists(exports.abiword, function(exists) { fs.exists(exports.abiword, (exists) => {
if (!exists) { if (!exists) {
var abiwordError = "Abiword does not exist at this path, check your settings file."; const abiwordError = 'Abiword does not exist at this path, check your settings file.';
if (!exports.suppressErrorsInPadText) { if (!exports.suppressErrorsInPadText) {
exports.defaultPadText = exports.defaultPadText + "\nError: " + abiwordError + suppressDisableMsg; exports.defaultPadText = `${exports.defaultPadText}\nError: ${abiwordError}${suppressDisableMsg}`;
} }
console.error(abiwordError + ` File location: ${exports.abiword}`); console.error(`${abiwordError} File location: ${exports.abiword}`);
exports.abiword = null; exports.abiword = null;
} }
}); });
@ -739,46 +739,46 @@ exports.reloadSettings = function reloadSettings() {
} }
if (exports.soffice) { if (exports.soffice) {
fs.exists(exports.soffice, function(exists) { fs.exists(exports.soffice, (exists) => {
if (!exists) { if (!exists) {
var sofficeError = "soffice (libreoffice) does not exist at this path, check your settings file."; const sofficeError = 'soffice (libreoffice) does not exist at this path, check your settings file.';
if (!exports.suppressErrorsInPadText) { if (!exports.suppressErrorsInPadText) {
exports.defaultPadText = exports.defaultPadText + "\nError: " + sofficeError + suppressDisableMsg; exports.defaultPadText = `${exports.defaultPadText}\nError: ${sofficeError}${suppressDisableMsg}`;
} }
console.error(sofficeError + ` File location: ${exports.soffice}`); console.error(`${sofficeError} File location: ${exports.soffice}`);
exports.soffice = null; exports.soffice = null;
} }
}); });
} }
if (!exports.sessionKey) { if (!exports.sessionKey) {
var sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || "./SESSIONKEY.txt"); const sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || './SESSIONKEY.txt');
try { try {
exports.sessionKey = fs.readFileSync(sessionkeyFilename,"utf8"); exports.sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8');
console.info(`Session key loaded from: ${sessionkeyFilename}`); console.info(`Session key loaded from: ${sessionkeyFilename}`);
} catch(e) { } catch (e) {
console.info(`Session key file "${sessionkeyFilename}" not found. Creating with random contents.`); console.info(`Session key file "${sessionkeyFilename}" not found. Creating with random contents.`);
exports.sessionKey = randomString(32); exports.sessionKey = randomString(32);
fs.writeFileSync(sessionkeyFilename,exports.sessionKey,"utf8"); fs.writeFileSync(sessionkeyFilename, exports.sessionKey, 'utf8');
} }
} else { } else {
console.warn("Declaring the sessionKey in the settings.json is deprecated. This value is auto-generated now. Please remove the setting from the file. -- If you are seeing this error after restarting using the Admin User Interface then you can ignore this message."); console.warn('Declaring the sessionKey in the settings.json is deprecated. This value is auto-generated now. Please remove the setting from the file. -- If you are seeing this error after restarting using the Admin User Interface then you can ignore this message.');
} }
if (exports.dbType === "dirty") { if (exports.dbType === 'dirty') {
var dirtyWarning = "DirtyDB is used. This is fine for testing but not recommended for production."; const dirtyWarning = 'DirtyDB is used. This is fine for testing but not recommended for production.';
if (!exports.suppressErrorsInPadText) { if (!exports.suppressErrorsInPadText) {
exports.defaultPadText = exports.defaultPadText + "\nWarning: " + dirtyWarning + suppressDisableMsg; exports.defaultPadText = `${exports.defaultPadText}\nWarning: ${dirtyWarning}${suppressDisableMsg}`;
} }
exports.dbSettings.filename = absolutePaths.makeAbsolute(exports.dbSettings.filename); exports.dbSettings.filename = absolutePaths.makeAbsolute(exports.dbSettings.filename);
console.warn(dirtyWarning + ` File location: ${exports.dbSettings.filename}`); console.warn(`${dirtyWarning} File location: ${exports.dbSettings.filename}`);
} }
if (exports.ip === "") { if (exports.ip === '') {
// using Unix socket for connectivity // using Unix socket for connectivity
console.warn(`The settings file contains an empty string ("") for the "ip" parameter. The "port" parameter will be interpreted as the path to a Unix socket to bind at.`); console.warn('The settings file contains an empty string ("") for the "ip" parameter. The "port" parameter will be interpreted as the path to a Unix socket to bind at.');
} }
}; };

View file

@ -2,42 +2,41 @@
* Tidy up the HTML in a given file * Tidy up the HTML in a given file
*/ */
var log4js = require('log4js'); const log4js = require('log4js');
var settings = require('./Settings'); const settings = require('./Settings');
var spawn = require('child_process').spawn; const spawn = require('child_process').spawn;
exports.tidy = function(srcFile) { exports.tidy = function (srcFile) {
var logger = log4js.getLogger('TidyHtml'); const logger = log4js.getLogger('TidyHtml');
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Don't do anything if Tidy hasn't been enabled // Don't do anything if Tidy hasn't been enabled
if (!settings.tidyHtml) { if (!settings.tidyHtml) {
logger.debug('tidyHtml has not been configured yet, ignoring tidy request'); logger.debug('tidyHtml has not been configured yet, ignoring tidy request');
return resolve(null); return resolve(null);
} }
var errMessage = ''; let errMessage = '';
// Spawn a new tidy instance that cleans up the file inline // Spawn a new tidy instance that cleans up the file inline
logger.debug('Tidying ' + srcFile); logger.debug(`Tidying ${srcFile}`);
var tidy = spawn(settings.tidyHtml, ['-modify', srcFile]); const tidy = spawn(settings.tidyHtml, ['-modify', srcFile]);
// Keep track of any error messages // Keep track of any error messages
tidy.stderr.on('data', function (data) { tidy.stderr.on('data', (data) => {
errMessage += data.toString(); errMessage += data.toString();
}); });
tidy.on('close', function(code) { tidy.on('close', (code) => {
// Tidy returns a 0 when no errors occur and a 1 exit code when // Tidy returns a 0 when no errors occur and a 1 exit code when
// the file could be tidied but a few warnings were generated // the file could be tidied but a few warnings were generated
if (code === 0 || code === 1) { if (code === 0 || code === 1) {
logger.debug('Tidied ' + srcFile + ' successfully'); logger.debug(`Tidied ${srcFile} successfully`);
resolve(null); resolve(null);
} else { } else {
logger.error('Failed to tidy ' + srcFile + '\n' + errMessage); logger.error(`Failed to tidy ${srcFile}\n${errMessage}`);
reject('Tidy died with exit code ' + code); reject(`Tidy died with exit code ${code}`);
} }
}); });
}); });
} };

View file

@ -5,8 +5,8 @@ const request = require('request');
let infos; let infos;
function loadEtherpadInformations() { function loadEtherpadInformations() {
return new Promise(function(resolve, reject) { return new Promise((resolve, reject) => {
request('https://static.etherpad.org/info.json', function (er, response, body) { request('https://static.etherpad.org/info.json', (er, response, body) => {
if (er) return reject(er); if (er) return reject(er);
try { try {
@ -16,29 +16,29 @@ function loadEtherpadInformations() {
return reject(err); return reject(err);
} }
}); });
}) });
} }
exports.getLatestVersion = function() { exports.getLatestVersion = function () {
exports.needsUpdate(); exports.needsUpdate();
return infos.latestVersion; return infos.latestVersion;
} };
exports.needsUpdate = function(cb) { exports.needsUpdate = function (cb) {
loadEtherpadInformations().then(function(info) { loadEtherpadInformations().then((info) => {
if (semver.gt(info.latestVersion, settings.getEpVersion())) { if (semver.gt(info.latestVersion, settings.getEpVersion())) {
if (cb) return cb(true); if (cb) return cb(true);
} }
}).catch(function (err) { }).catch((err) => {
console.error('Can not perform Etherpad update check: ' + err) console.error(`Can not perform Etherpad update check: ${err}`);
if (cb) return cb(false); if (cb) return cb(false);
}) });
} };
exports.check = function() { exports.check = function () {
exports.needsUpdate(function (needsUpdate) { exports.needsUpdate((needsUpdate) => {
if (needsUpdate) { if (needsUpdate) {
console.warn('Update available: Download the actual version ' + infos.latestVersion) console.warn(`Update available: Download the actual version ${infos.latestVersion}`);
} }
}) });
} };

View file

@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
var async = require('async'); const async = require('async');
var Buffer = require('buffer').Buffer; const Buffer = require('buffer').Buffer;
var fs = require('fs'); const fs = require('fs');
var path = require('path'); const path = require('path');
var zlib = require('zlib'); const zlib = require('zlib');
var settings = require('./Settings'); const settings = require('./Settings');
var existsSync = require('./path_exists'); const existsSync = require('./path_exists');
/* /*
* The crypto module can be absent on reduced node installations. * The crypto module can be absent on reduced node installations.
@ -42,13 +42,13 @@ try {
_crypto = undefined; _crypto = undefined;
} }
var CACHE_DIR = path.normalize(path.join(settings.root, 'var/')); let CACHE_DIR = path.normalize(path.join(settings.root, 'var/'));
CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined; CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
var responseCache = {}; const responseCache = {};
function djb2Hash(data) { function djb2Hash(data) {
const chars = data.split("").map(str => str.charCodeAt(0)); const chars = data.split('').map((str) => str.charCodeAt(0));
return `${chars.reduce((prev, curr) => ((prev << 5) + prev) + curr, 5381)}`; return `${chars.reduce((prev, curr) => ((prev << 5) + prev) + curr, 5381)}`;
} }
@ -81,23 +81,23 @@ function CachingMiddleware() {
} }
CachingMiddleware.prototype = new function () { CachingMiddleware.prototype = new function () {
function handle(req, res, next) { function handle(req, res, next) {
if (!(req.method == "GET" || req.method == "HEAD") || !CACHE_DIR) { if (!(req.method == 'GET' || req.method == 'HEAD') || !CACHE_DIR) {
return next(undefined, req, res); return next(undefined, req, res);
} }
var old_req = {}; const old_req = {};
var old_res = {}; const old_res = {};
var supportsGzip = const supportsGzip =
(req.get('Accept-Encoding') || '').indexOf('gzip') != -1; (req.get('Accept-Encoding') || '').indexOf('gzip') != -1;
var path = require('url').parse(req.url).path; const path = require('url').parse(req.url).path;
var cacheKey = generateCacheKey(path); const cacheKey = generateCacheKey(path);
fs.stat(CACHE_DIR + 'minified_' + cacheKey, function (error, stats) { fs.stat(`${CACHE_DIR}minified_${cacheKey}`, (error, stats) => {
var modifiedSince = (req.headers['if-modified-since'] const modifiedSince = (req.headers['if-modified-since'] &&
&& new Date(req.headers['if-modified-since'])); new Date(req.headers['if-modified-since']));
var lastModifiedCache = !error && stats.mtime; const lastModifiedCache = !error && stats.mtime;
if (lastModifiedCache && responseCache[cacheKey]) { if (lastModifiedCache && responseCache[cacheKey]) {
req.headers['if-modified-since'] = lastModifiedCache.toUTCString(); req.headers['if-modified-since'] = lastModifiedCache.toUTCString();
} else { } else {
@ -108,13 +108,13 @@ CachingMiddleware.prototype = new function () {
old_req.method = req.method; old_req.method = req.method;
req.method = 'GET'; req.method = 'GET';
var expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {})['expires']); const expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {}).expires);
if (expirationDate > new Date()) { if (expirationDate > new Date()) {
// Our cached version is still valid. // Our cached version is still valid.
return respond(); return respond();
} }
var _headers = {}; const _headers = {};
old_res.setHeader = res.setHeader; old_res.setHeader = res.setHeader;
res.setHeader = function (key, value) { res.setHeader = function (key, value) {
// Don't set cookies, see issue #707 // Don't set cookies, see issue #707
@ -126,46 +126,46 @@ CachingMiddleware.prototype = new function () {
old_res.writeHead = res.writeHead; old_res.writeHead = res.writeHead;
res.writeHead = function (status, headers) { res.writeHead = function (status, headers) {
var lastModified = (res.getHeader('last-modified') const lastModified = (res.getHeader('last-modified') &&
&& new Date(res.getHeader('last-modified'))); new Date(res.getHeader('last-modified')));
res.writeHead = old_res.writeHead; res.writeHead = old_res.writeHead;
if (status == 200) { if (status == 200) {
// Update cache // Update cache
var buffer = ''; let buffer = '';
Object.keys(headers || {}).forEach(function (key) { Object.keys(headers || {}).forEach((key) => {
res.setHeader(key, headers[key]); res.setHeader(key, headers[key]);
}); });
headers = _headers; headers = _headers;
old_res.write = res.write; old_res.write = res.write;
old_res.end = res.end; old_res.end = res.end;
res.write = function(data, encoding) { res.write = function (data, encoding) {
buffer += data.toString(encoding); buffer += data.toString(encoding);
}; };
res.end = function(data, encoding) { res.end = function (data, encoding) {
async.parallel([ async.parallel([
function (callback) { function (callback) {
var path = CACHE_DIR + 'minified_' + cacheKey; const path = `${CACHE_DIR}minified_${cacheKey}`;
fs.writeFile(path, buffer, function (error, stats) { fs.writeFile(path, buffer, (error, stats) => {
callback(); callback();
}); });
} },
, function (callback) { function (callback) {
var path = CACHE_DIR + 'minified_' + cacheKey + '.gz'; const path = `${CACHE_DIR}minified_${cacheKey}.gz`;
zlib.gzip(buffer, function(error, content) { zlib.gzip(buffer, (error, content) => {
if (error) { if (error) {
callback(); callback();
} else { } else {
fs.writeFile(path, content, function (error, stats) { fs.writeFile(path, content, (error, stats) => {
callback(); callback();
}); });
} }
}); });
} },
], function () { ], () => {
responseCache[cacheKey] = {statusCode: status, headers: headers}; responseCache[cacheKey] = {statusCode: status, headers};
respond(); respond();
}); });
}; };
@ -173,8 +173,8 @@ CachingMiddleware.prototype = new function () {
// Nothing new changed from the cached version. // Nothing new changed from the cached version.
old_res.write = res.write; old_res.write = res.write;
old_res.end = res.end; old_res.end = res.end;
res.write = function(data, encoding) {}; res.write = function (data, encoding) {};
res.end = function(data, encoding) { respond(); }; res.end = function (data, encoding) { respond(); };
} else { } else {
res.writeHead(status, headers); res.writeHead(status, headers);
} }
@ -191,24 +191,24 @@ CachingMiddleware.prototype = new function () {
res.write = old_res.write || res.write; res.write = old_res.write || res.write;
res.end = old_res.end || res.end; res.end = old_res.end || res.end;
let headers = {}; const headers = {};
Object.assign(headers, (responseCache[cacheKey].headers || {})); Object.assign(headers, (responseCache[cacheKey].headers || {}));
var statusCode = responseCache[cacheKey].statusCode; const statusCode = responseCache[cacheKey].statusCode;
var pathStr = CACHE_DIR + 'minified_' + cacheKey; let pathStr = `${CACHE_DIR}minified_${cacheKey}`;
if (supportsGzip && /application\/javascript/.test(headers['content-type'])) { if (supportsGzip && /application\/javascript/.test(headers['content-type'])) {
pathStr = pathStr + '.gz'; pathStr += '.gz';
headers['content-encoding'] = 'gzip'; headers['content-encoding'] = 'gzip';
} }
var lastModified = (headers['last-modified'] const lastModified = (headers['last-modified'] &&
&& new Date(headers['last-modified'])); new Date(headers['last-modified']));
if (statusCode == 200 && lastModified <= modifiedSince) { if (statusCode == 200 && lastModified <= modifiedSince) {
res.writeHead(304, headers); res.writeHead(304, headers);
res.end(); res.end();
} else if (req.method == 'GET') { } else if (req.method == 'GET') {
var readStream = fs.createReadStream(pathStr); const readStream = fs.createReadStream(pathStr);
res.writeHead(statusCode, headers); res.writeHead(statusCode, headers);
readStream.pipe(res); readStream.pipe(res);
} else { } else {

View file

@ -1,17 +1,17 @@
var Changeset = require("../../static/js/Changeset"); const Changeset = require('../../static/js/Changeset');
var exportHtml = require('./ExportHtml'); const exportHtml = require('./ExportHtml');
function PadDiff (pad, fromRev, toRev) { function PadDiff(pad, fromRev, toRev) {
// check parameters // check parameters
if (!pad || !pad.id || !pad.atext || !pad.pool) { if (!pad || !pad.id || !pad.atext || !pad.pool) {
throw new Error('Invalid pad'); throw new Error('Invalid pad');
} }
var range = pad.getValidRevisionRange(fromRev, toRev); const range = pad.getValidRevisionRange(fromRev, toRev);
if (!range) { if (!range) {
throw new Error('Invalid revision range.' + throw new Error(`${'Invalid revision range.' +
' startRev: ' + fromRev + ' startRev: '}${fromRev
' endRev: ' + toRev); } endRev: ${toRev}`);
} }
this._pad = pad; this._pad = pad;
@ -21,12 +21,12 @@ function PadDiff (pad, fromRev, toRev) {
this._authors = []; this._authors = [];
} }
PadDiff.prototype._isClearAuthorship = function(changeset) { PadDiff.prototype._isClearAuthorship = function (changeset) {
// unpack // unpack
var unpacked = Changeset.unpack(changeset); const unpacked = Changeset.unpack(changeset);
// check if there is nothing in the charBank // check if there is nothing in the charBank
if (unpacked.charBank !== "") { if (unpacked.charBank !== '') {
return false; return false;
} }
@ -36,10 +36,10 @@ PadDiff.prototype._isClearAuthorship = function(changeset) {
} }
// lets iterator over the operators // lets iterator over the operators
var iterator = Changeset.opIterator(unpacked.ops); const iterator = Changeset.opIterator(unpacked.ops);
// get the first operator, this should be a clear operator // get the first operator, this should be a clear operator
var clearOperator = iterator.next(); const clearOperator = iterator.next();
// check if there is only one operator // check if there is only one operator
if (iterator.hasNext() === true) { if (iterator.hasNext() === true) {
@ -47,18 +47,18 @@ PadDiff.prototype._isClearAuthorship = function(changeset) {
} }
// check if this operator doesn't change text // check if this operator doesn't change text
if (clearOperator.opcode !== "=") { if (clearOperator.opcode !== '=') {
return false; return false;
} }
// check that this operator applys to the complete text // check that this operator applys to the complete text
// if the text ends with a new line, its exactly one character less, else it has the same length // if the text ends with a new line, its exactly one character less, else it has the same length
if (clearOperator.chars !== unpacked.oldLen-1 && clearOperator.chars !== unpacked.oldLen) { if (clearOperator.chars !== unpacked.oldLen - 1 && clearOperator.chars !== unpacked.oldLen) {
return false; return false;
} }
var attributes = []; const attributes = [];
Changeset.eachAttribNumber(changeset, function(attrNum) { Changeset.eachAttribNumber(changeset, (attrNum) => {
attributes.push(attrNum); attributes.push(attrNum);
}); });
@ -67,90 +67,84 @@ PadDiff.prototype._isClearAuthorship = function(changeset) {
return false; return false;
} }
var appliedAttribute = this._pad.pool.getAttrib(attributes[0]); const appliedAttribute = this._pad.pool.getAttrib(attributes[0]);
// check if the applied attribute is an anonymous author attribute // check if the applied attribute is an anonymous author attribute
if (appliedAttribute[0] !== "author" || appliedAttribute[1] !== "") { if (appliedAttribute[0] !== 'author' || appliedAttribute[1] !== '') {
return false; return false;
} }
return true; return true;
}; };
PadDiff.prototype._createClearAuthorship = async function(rev) { PadDiff.prototype._createClearAuthorship = async function (rev) {
const atext = await this._pad.getInternalRevisionAText(rev);
let atext = await this._pad.getInternalRevisionAText(rev);
// build clearAuthorship changeset // build clearAuthorship changeset
var builder = Changeset.builder(atext.text.length); const builder = Changeset.builder(atext.text.length);
builder.keepText(atext.text, [['author','']], this._pad.pool); builder.keepText(atext.text, [['author', '']], this._pad.pool);
var changeset = builder.toString(); const changeset = builder.toString();
return changeset; return changeset;
} };
PadDiff.prototype._createClearStartAtext = async function(rev) {
PadDiff.prototype._createClearStartAtext = async function (rev) {
// get the atext of this revision // get the atext of this revision
let atext = this._pad.getInternalRevisionAText(rev); const atext = this._pad.getInternalRevisionAText(rev);
// create the clearAuthorship changeset // create the clearAuthorship changeset
let changeset = await this._createClearAuthorship(rev); const changeset = await this._createClearAuthorship(rev);
// apply the clearAuthorship changeset // apply the clearAuthorship changeset
let newAText = Changeset.applyToAText(changeset, atext, this._pad.pool); const newAText = Changeset.applyToAText(changeset, atext, this._pad.pool);
return newAText; return newAText;
} };
PadDiff.prototype._getChangesetsInBulk = async function(startRev, count) {
PadDiff.prototype._getChangesetsInBulk = async function (startRev, count) {
// find out which revisions we need // find out which revisions we need
let revisions = []; const revisions = [];
for (let i = startRev; i < (startRev + count) && i <= this._pad.head; i++) { for (let i = startRev; i < (startRev + count) && i <= this._pad.head; i++) {
revisions.push(i); revisions.push(i);
} }
// get all needed revisions (in parallel) // get all needed revisions (in parallel)
let changesets = [], authors = []; const changesets = []; const
await Promise.all(revisions.map(rev => { authors = [];
return this._pad.getRevision(rev).then(revision => { await Promise.all(revisions.map((rev) => this._pad.getRevision(rev).then((revision) => {
let arrayNum = rev - startRev; const arrayNum = rev - startRev;
changesets[arrayNum] = revision.changeset; changesets[arrayNum] = revision.changeset;
authors[arrayNum] = revision.meta.author; authors[arrayNum] = revision.meta.author;
}); })));
}));
return { changesets, authors }; return {changesets, authors};
} };
PadDiff.prototype._addAuthors = function(authors) { PadDiff.prototype._addAuthors = function (authors) {
var self = this; const self = this;
// add to array if not in the array // add to array if not in the array
authors.forEach(function(author) { authors.forEach((author) => {
if (self._authors.indexOf(author) == -1) { if (self._authors.indexOf(author) == -1) {
self._authors.push(author); self._authors.push(author);
} }
}); });
}; };
PadDiff.prototype._createDiffAtext = async function() { PadDiff.prototype._createDiffAtext = async function () {
const bulkSize = 100;
let bulkSize = 100;
// get the cleaned startAText // get the cleaned startAText
let atext = await this._createClearStartAtext(this._fromRev); let atext = await this._createClearStartAtext(this._fromRev);
let superChangeset = null; let superChangeset = null;
let rev = this._fromRev + 1; const rev = this._fromRev + 1;
for (let rev = this._fromRev + 1; rev <= this._toRev; rev += bulkSize) { for (let rev = this._fromRev + 1; rev <= this._toRev; rev += bulkSize) {
// get the bulk // get the bulk
let { changesets, authors } = await this._getChangesetsInBulk(rev, bulkSize); const {changesets, authors} = await this._getChangesetsInBulk(rev, bulkSize);
let addedAuthors = []; const addedAuthors = [];
// run through all changesets // run through all changesets
for (let i = 0; i < changesets.length && (rev + i) <= this._toRev; ++i) { for (let i = 0; i < changesets.length && (rev + i) <= this._toRev; ++i) {
@ -180,7 +174,7 @@ PadDiff.prototype._createDiffAtext = async function() {
// if there are only clearAuthorship changesets, we don't get a superChangeset, so we can skip this step // if there are only clearAuthorship changesets, we don't get a superChangeset, so we can skip this step
if (superChangeset) { if (superChangeset) {
let deletionChangeset = this._createDeletionChangeset(superChangeset, atext, this._pad.pool); const deletionChangeset = this._createDeletionChangeset(superChangeset, atext, this._pad.pool);
// apply the superChangeset, which includes all addings // apply the superChangeset, which includes all addings
atext = Changeset.applyToAText(superChangeset, atext, this._pad.pool); atext = Changeset.applyToAText(superChangeset, atext, this._pad.pool);
@ -190,59 +184,57 @@ PadDiff.prototype._createDiffAtext = async function() {
} }
return atext; return atext;
} };
PadDiff.prototype.getHtml = async function() {
PadDiff.prototype.getHtml = async function () {
// cache the html // cache the html
if (this._html != null) { if (this._html != null) {
return this._html; return this._html;
} }
// get the diff atext // get the diff atext
let atext = await this._createDiffAtext(); const atext = await this._createDiffAtext();
// get the authorColor table // get the authorColor table
let authorColors = await this._pad.getAllAuthorColors(); const authorColors = await this._pad.getAllAuthorColors();
// convert the atext to html // convert the atext to html
this._html = await exportHtml.getHTMLFromAtext(this._pad, atext, authorColors); this._html = await exportHtml.getHTMLFromAtext(this._pad, atext, authorColors);
return this._html; return this._html;
} };
PadDiff.prototype.getAuthors = async function() {
PadDiff.prototype.getAuthors = async function () {
// check if html was already produced, if not produce it, this generates the author array at the same time // check if html was already produced, if not produce it, this generates the author array at the same time
if (this._html == null) { if (this._html == null) {
await this.getHtml(); await this.getHtml();
} }
return self._authors; return self._authors;
} };
PadDiff.prototype._extendChangesetWithAuthor = function(changeset, author, apool) { PadDiff.prototype._extendChangesetWithAuthor = function (changeset, author, apool) {
// unpack // unpack
var unpacked = Changeset.unpack(changeset); const unpacked = Changeset.unpack(changeset);
var iterator = Changeset.opIterator(unpacked.ops); const iterator = Changeset.opIterator(unpacked.ops);
var assem = Changeset.opAssembler(); const assem = Changeset.opAssembler();
// create deleted attribs // create deleted attribs
var authorAttrib = apool.putAttrib(["author", author || ""]); const authorAttrib = apool.putAttrib(['author', author || '']);
var deletedAttrib = apool.putAttrib(["removed", true]); const deletedAttrib = apool.putAttrib(['removed', true]);
var attribs = "*" + Changeset.numToString(authorAttrib) + "*" + Changeset.numToString(deletedAttrib); const attribs = `*${Changeset.numToString(authorAttrib)}*${Changeset.numToString(deletedAttrib)}`;
// iteratore over the operators of the changeset // iteratore over the operators of the changeset
while(iterator.hasNext()) { while (iterator.hasNext()) {
var operator = iterator.next(); const operator = iterator.next();
if (operator.opcode === "-") { if (operator.opcode === '-') {
// this is a delete operator, extend it with the author // this is a delete operator, extend it with the author
operator.attribs = attribs; operator.attribs = attribs;
} else if (operator.opcode === "=" && operator.attribs) { } else if (operator.opcode === '=' && operator.attribs) {
// this is operator changes only attributes, let's mark which author did that // this is operator changes only attributes, let's mark which author did that
operator.attribs+="*"+Changeset.numToString(authorAttrib); operator.attribs += `*${Changeset.numToString(authorAttrib)}`;
} }
// append the new operator to our assembler // append the new operator to our assembler
@ -254,9 +246,9 @@ PadDiff.prototype._extendChangesetWithAuthor = function(changeset, author, apool
}; };
// this method is 80% like Changeset.inverse. I just changed so instead of reverting, it adds deletions and attribute changes to to the atext. // this method is 80% like Changeset.inverse. I just changed so instead of reverting, it adds deletions and attribute changes to to the atext.
PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
var lines = Changeset.splitTextLines(startAText.text); const lines = Changeset.splitTextLines(startAText.text);
var alines = Changeset.splitAttributionLines(startAText.attribs, startAText.text); const alines = Changeset.splitAttributionLines(startAText.attribs, startAText.text);
// lines and alines are what the exports is meant to apply to. // lines and alines are what the exports is meant to apply to.
// They may be arrays or objects with .get(i) and .length methods. // They may be arrays or objects with .get(i) and .length methods.
@ -278,24 +270,23 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
} }
} }
var curLine = 0; let curLine = 0;
var curChar = 0; let curChar = 0;
var curLineOpIter = null; let curLineOpIter = null;
var curLineOpIterLine; let curLineOpIterLine;
var curLineNextOp = Changeset.newOp('+'); const curLineNextOp = Changeset.newOp('+');
var unpacked = Changeset.unpack(cs); const unpacked = Changeset.unpack(cs);
var csIter = Changeset.opIterator(unpacked.ops); const csIter = Changeset.opIterator(unpacked.ops);
var builder = Changeset.builder(unpacked.newLen); const builder = Changeset.builder(unpacked.newLen);
function consumeAttribRuns(numChars, func /*(len, attribs, endsLine)*/ ) {
function consumeAttribRuns(numChars, func /* (len, attribs, endsLine)*/) {
if ((!curLineOpIter) || (curLineOpIterLine != curLine)) { if ((!curLineOpIter) || (curLineOpIterLine != curLine)) {
// create curLineOpIter and advance it to curChar // create curLineOpIter and advance it to curChar
curLineOpIter = Changeset.opIterator(alines_get(curLine)); curLineOpIter = Changeset.opIterator(alines_get(curLine));
curLineOpIterLine = curLine; curLineOpIterLine = curLine;
var indexIntoLine = 0; let indexIntoLine = 0;
var done = false; let done = false;
while (!done) { while (!done) {
curLineOpIter.next(curLineNextOp); curLineOpIter.next(curLineNextOp);
if (indexIntoLine + curLineNextOp.chars >= curChar) { if (indexIntoLine + curLineNextOp.chars >= curChar) {
@ -320,7 +311,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
curLineOpIter.next(curLineNextOp); curLineOpIter.next(curLineNextOp);
} }
var charsToUse = Math.min(numChars, curLineNextOp.chars); const charsToUse = Math.min(numChars, curLineNextOp.chars);
func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0); func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0);
numChars -= charsToUse; numChars -= charsToUse;
@ -338,26 +329,24 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
if (L) { if (L) {
curLine += L; curLine += L;
curChar = 0; curChar = 0;
} else if (curLineOpIter && curLineOpIterLine == curLine) {
consumeAttribRuns(N, () => {});
} else { } else {
if (curLineOpIter && curLineOpIterLine == curLine) { curChar += N;
consumeAttribRuns(N, function () {});
} else {
curChar += N;
}
} }
} }
function nextText(numChars) { function nextText(numChars) {
var len = 0; let len = 0;
var assem = Changeset.stringAssembler(); const assem = Changeset.stringAssembler();
var firstString = lines_get(curLine).substring(curChar); const firstString = lines_get(curLine).substring(curChar);
len += firstString.length; len += firstString.length;
assem.append(firstString); assem.append(firstString);
var lineNum = curLine + 1; let lineNum = curLine + 1;
while (len < numChars) { while (len < numChars) {
var nextString = lines_get(lineNum); const nextString = lines_get(lineNum);
len += nextString.length; len += nextString.length;
assem.append(nextString); assem.append(nextString);
lineNum++; lineNum++;
@ -367,7 +356,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
} }
function cachedStrFunc(func) { function cachedStrFunc(func) {
var cache = {}; const cache = {};
return function (s) { return function (s) {
if (!cache[s]) { if (!cache[s]) {
@ -377,8 +366,8 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
}; };
} }
var attribKeys = []; const attribKeys = [];
var attribValues = []; const attribValues = [];
// iterate over all operators of this changeset // iterate over all operators of this changeset
while (csIter.hasNext()) { while (csIter.hasNext()) {
@ -389,27 +378,27 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
// decide if this equal operator is an attribution change or not. We can see this by checkinf if attribs is set. // decide if this equal operator is an attribution change or not. We can see this by checkinf if attribs is set.
// If the text this operator applies to is only a star, than this is a false positive and should be ignored // If the text this operator applies to is only a star, than this is a false positive and should be ignored
if (csOp.attribs && textBank != "*") { if (csOp.attribs && textBank != '*') {
var deletedAttrib = apool.putAttrib(["removed", true]); const deletedAttrib = apool.putAttrib(['removed', true]);
var authorAttrib = apool.putAttrib(["author", ""]); var authorAttrib = apool.putAttrib(['author', '']);
attribKeys.length = 0; attribKeys.length = 0;
attribValues.length = 0; attribValues.length = 0;
Changeset.eachAttribNumber(csOp.attribs, function (n) { Changeset.eachAttribNumber(csOp.attribs, (n) => {
attribKeys.push(apool.getAttribKey(n)); attribKeys.push(apool.getAttribKey(n));
attribValues.push(apool.getAttribValue(n)); attribValues.push(apool.getAttribValue(n));
if (apool.getAttribKey(n) === "author") { if (apool.getAttribKey(n) === 'author') {
authorAttrib = n; authorAttrib = n;
} }
}); });
var undoBackToAttribs = cachedStrFunc(function (attribs) { var undoBackToAttribs = cachedStrFunc((attribs) => {
var backAttribs = []; const backAttribs = [];
for (var i = 0; i < attribKeys.length; i++) { for (let i = 0; i < attribKeys.length; i++) {
var appliedKey = attribKeys[i]; const appliedKey = attribKeys[i];
var appliedValue = attribValues[i]; const appliedValue = attribValues[i];
var oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, apool); const oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, apool);
if (appliedValue != oldValue) { if (appliedValue != oldValue) {
backAttribs.push([appliedKey, oldValue]); backAttribs.push([appliedKey, oldValue]);
@ -419,21 +408,21 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
return Changeset.makeAttribsString('=', backAttribs, apool); return Changeset.makeAttribsString('=', backAttribs, apool);
}); });
var oldAttribsAddition = "*" + Changeset.numToString(deletedAttrib) + "*" + Changeset.numToString(authorAttrib); var oldAttribsAddition = `*${Changeset.numToString(deletedAttrib)}*${Changeset.numToString(authorAttrib)}`;
var textLeftToProcess = textBank; let textLeftToProcess = textBank;
while(textLeftToProcess.length > 0) { while (textLeftToProcess.length > 0) {
// process till the next line break or process only one line break // process till the next line break or process only one line break
var lengthToProcess = textLeftToProcess.indexOf("\n"); let lengthToProcess = textLeftToProcess.indexOf('\n');
var lineBreak = false; let lineBreak = false;
switch(lengthToProcess) { switch (lengthToProcess) {
case -1: case -1:
lengthToProcess=textLeftToProcess.length; lengthToProcess = textLeftToProcess.length;
break; break;
case 0: case 0:
lineBreak = true; lineBreak = true;
lengthToProcess=1; lengthToProcess = 1;
break; break;
} }
@ -446,13 +435,13 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
builder.keep(1, 1); // just skip linebreaks, don't do a insert + keep for a linebreak builder.keep(1, 1); // just skip linebreaks, don't do a insert + keep for a linebreak
// consume the attributes of this linebreak // consume the attributes of this linebreak
consumeAttribRuns(1, function() {}); consumeAttribRuns(1, () => {});
} else { } else {
// add the old text via an insert, but add a deletion attribute + the author attribute of the author who deleted it // add the old text via an insert, but add a deletion attribute + the author attribute of the author who deleted it
var textBankIndex = 0; var textBankIndex = 0;
consumeAttribRuns(lengthToProcess, function (len, attribs, endsLine) { consumeAttribRuns(lengthToProcess, (len, attribs, endsLine) => {
// get the old attributes back // get the old attributes back
var attribs = (undoBackToAttribs(attribs) || "") + oldAttribsAddition; var attribs = (undoBackToAttribs(attribs) || '') + oldAttribsAddition;
builder.insert(processText.substr(textBankIndex, len), attribs); builder.insert(processText.substr(textBankIndex, len), attribs);
textBankIndex += len; textBankIndex += len;
@ -471,7 +460,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
var textBank = nextText(csOp.chars); var textBank = nextText(csOp.chars);
var textBankIndex = 0; var textBankIndex = 0;
consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) { consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => {
builder.insert(textBank.substr(textBankIndex, len), attribs + csOp.attribs); builder.insert(textBank.substr(textBankIndex, len), attribs + csOp.attribs);
textBankIndex += len; textBankIndex += len;
}); });

View file

@ -1,15 +1,15 @@
var fs = require('fs'); const fs = require('fs');
var check = function(path) { const check = function (path) {
var existsSync = fs.statSync || fs.existsSync || path.existsSync; const existsSync = fs.statSync || fs.existsSync || path.existsSync;
var result; let result;
try { try {
result = existsSync(path); result = existsSync(path);
} catch (e) { } catch (e) {
result = false; result = false;
} }
return result; return result;
} };
module.exports = check; module.exports = check;

View file

@ -13,7 +13,7 @@ exports.firstSatisfies = (promises, predicate) => {
// value does not satisfy `predicate`. These transformed Promises will be passed to Promise.race, // value does not satisfy `predicate`. These transformed Promises will be passed to Promise.race,
// yielding the first resolved value that satisfies `predicate`. // yielding the first resolved value that satisfies `predicate`.
const newPromises = promises.map( const newPromises = promises.map(
(p) => new Promise((resolve, reject) => p.then((v) => predicate(v) && resolve(v), reject))); (p) => new Promise((resolve, reject) => p.then((v) => predicate(v) && resolve(v), reject)));
// If `promises` is an empty array or if none of them resolve to a value that satisfies // If `promises` is an empty array or if none of them resolve to a value that satisfies
// `predicate`, then `Promise.race(newPromises)` will never resolve. To handle that, add another // `predicate`, then `Promise.race(newPromises)` will never resolve. To handle that, add another
@ -48,8 +48,8 @@ exports.timesLimit = async (total, concurrency, promiseCreator) => {
if (next < total) return addAnother(); if (next < total) return addAnother();
}); });
const promises = []; const promises = [];
for (var i = 0; i < concurrency && i < total; i++) { for (let i = 0; i < concurrency && i < total; i++) {
promises.push(addAnother()); promises.push(addAnother());
} }
await Promise.all(promises); await Promise.all(promises);
} };

View file

@ -1,10 +1,10 @@
/** /**
* Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids * Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
*/ */
var crypto = require('crypto'); const crypto = require('crypto');
var randomString = function(len) { const randomString = function (len) {
return crypto.randomBytes(len).toString('hex') return crypto.randomBytes(len).toString('hex');
}; };
module.exports = randomString; module.exports = randomString;

View file

@ -1,53 +1,50 @@
/** /**
* The Toolbar Module creates and renders the toolbars and buttons * The Toolbar Module creates and renders the toolbars and buttons
*/ */
var _ = require("underscore") const _ = require('underscore');
, tagAttributes let tagAttributes;
, tag let tag;
, Button let Button;
, ButtonsGroup let ButtonsGroup;
, Separator let Separator;
, defaultButtonAttributes let defaultButtonAttributes;
, removeItem; let removeItem;
removeItem = function(array,what) { removeItem = function (array, what) {
var ax; let ax;
while ((ax = array.indexOf(what)) !== -1) { while ((ax = array.indexOf(what)) !== -1) {
array.splice(ax, 1); array.splice(ax, 1);
} }
return array; return array;
}; };
defaultButtonAttributes = function (name, overrides) { defaultButtonAttributes = function (name, overrides) {
return { return {
command: name, command: name,
localizationId: "pad.toolbar." + name + ".title", localizationId: `pad.toolbar.${name}.title`,
class: "buttonicon buttonicon-" + name class: `buttonicon buttonicon-${name}`,
}; };
}; };
tag = function (name, attributes, contents) { tag = function (name, attributes, contents) {
var aStr = tagAttributes(attributes); const aStr = tagAttributes(attributes);
if (_.isString(contents) && contents.length > 0) { if (_.isString(contents) && contents.length > 0) {
return '<' + name + aStr + '>' + contents + '</' + name + '>'; return `<${name}${aStr}>${contents}</${name}>`;
} } else {
else { return `<${name}${aStr}></${name}>`;
return '<' + name + aStr + '></' + name + '>';
} }
}; };
tagAttributes = function (attributes) { tagAttributes = function (attributes) {
attributes = _.reduce(attributes || {}, function (o, val, name) { attributes = _.reduce(attributes || {}, (o, val, name) => {
if (!_.isUndefined(val)) { if (!_.isUndefined(val)) {
o[name] = val; o[name] = val;
} }
return o; return o;
}, {}); }, {});
return " " + _.map(attributes, function (val, name) { return ` ${_.map(attributes, (val, name) => `${name}="${_.escape(val)}"`).join(' ')}`;
return "" + name + '="' + _.escape(val) + '"';
}).join(" ");
}; };
ButtonsGroup = function () { ButtonsGroup = function () {
@ -55,8 +52,8 @@ ButtonsGroup = function () {
}; };
ButtonsGroup.fromArray = function (array) { ButtonsGroup.fromArray = function (array) {
var btnGroup = new this; const btnGroup = new this();
_.each(array, function (btnName) { _.each(array, (btnName) => {
btnGroup.addButton(Button.load(btnName)); btnGroup.addButton(Button.load(btnName));
}); });
return btnGroup; return btnGroup;
@ -69,19 +66,18 @@ ButtonsGroup.prototype.addButton = function (button) {
ButtonsGroup.prototype.render = function () { ButtonsGroup.prototype.render = function () {
if (this.buttons && this.buttons.length == 1) { if (this.buttons && this.buttons.length == 1) {
this.buttons[0].grouping = ""; this.buttons[0].grouping = '';
} } else {
else { _.first(this.buttons).grouping = 'grouped-left';
_.first(this.buttons).grouping = "grouped-left"; _.last(this.buttons).grouping = 'grouped-right';
_.last(this.buttons).grouping = "grouped-right"; _.each(this.buttons.slice(1, -1), (btn) => {
_.each(this.buttons.slice(1, -1), function (btn) { btn.grouping = 'grouped-middle';
btn.grouping = "grouped-middle"
}); });
} }
return _.map(this.buttons, function (btn) { return _.map(this.buttons, (btn) => {
if(btn) return btn.render(); if (btn) return btn.render();
}).join("\n"); }).join('\n');
}; };
Button = function (attributes) { Button = function (attributes) {
@ -89,165 +85,163 @@ Button = function (attributes) {
}; };
Button.load = function (btnName) { Button.load = function (btnName) {
var button = module.exports.availableButtons[btnName]; const button = module.exports.availableButtons[btnName];
try{ try {
if (button.constructor === Button || button.constructor === SelectButton) { if (button.constructor === Button || button.constructor === SelectButton) {
return button; return button;
} } else {
else {
return new Button(button); return new Button(button);
} }
}catch(e){ } catch (e) {
console.warn("Error loading button", btnName); console.warn('Error loading button', btnName);
return false; return false;
} }
}; };
_.extend(Button.prototype, { _.extend(Button.prototype, {
grouping: "", grouping: '',
render: function () { render() {
var liAttributes = { const liAttributes = {
"data-type": "button", 'data-type': 'button',
"data-key": this.attributes.command, 'data-key': this.attributes.command,
}; };
return tag("li", liAttributes, return tag('li', liAttributes,
tag("a", { "class": this.grouping, "data-l10n-id": this.attributes.localizationId }, tag('a', {'class': this.grouping, 'data-l10n-id': this.attributes.localizationId},
tag("button", { "class": " "+ this.attributes.class, "data-l10n-id": this.attributes.localizationId }) tag('button', {'class': ` ${this.attributes.class}`, 'data-l10n-id': this.attributes.localizationId}),
) ),
); );
} },
}); });
var SelectButton = function (attributes) { var SelectButton = function (attributes) {
this.attributes = attributes; this.attributes = attributes;
this.options = []; this.options = [];
}; };
_.extend(SelectButton.prototype, Button.prototype, { _.extend(SelectButton.prototype, Button.prototype, {
addOption: function (value, text, attributes) { addOption(value, text, attributes) {
this.options.push({ this.options.push({
value: value, value,
text: text, text,
attributes: attributes attributes,
}); });
return this; return this;
}, },
select: function (attributes) { select(attributes) {
var options = []; const options = [];
_.each(this.options, function (opt) { _.each(this.options, (opt) => {
var a = _.extend({ const a = _.extend({
value: opt.value value: opt.value,
}, opt.attributes); }, opt.attributes);
options.push( tag("option", a, opt.text) ); options.push(tag('option', a, opt.text));
}); });
return tag("select", attributes, options.join("")); return tag('select', attributes, options.join(''));
}, },
render: function () { render() {
var attributes = { const attributes = {
id: this.attributes.id, 'id': this.attributes.id,
"data-key": this.attributes.command, 'data-key': this.attributes.command,
"data-type": "select" 'data-type': 'select',
}; };
return tag("li", attributes, return tag('li', attributes,
this.select({ id: this.attributes.selectId }) this.select({id: this.attributes.selectId}),
); );
} },
}); });
Separator = function () {}; Separator = function () {};
Separator.prototype.render = function () { Separator.prototype.render = function () {
return tag("li", { "class": "separator" }); return tag('li', {class: 'separator'});
}; };
module.exports = { module.exports = {
availableButtons: { availableButtons: {
bold: defaultButtonAttributes("bold"), bold: defaultButtonAttributes('bold'),
italic: defaultButtonAttributes("italic"), italic: defaultButtonAttributes('italic'),
underline: defaultButtonAttributes("underline"), underline: defaultButtonAttributes('underline'),
strikethrough: defaultButtonAttributes("strikethrough"), strikethrough: defaultButtonAttributes('strikethrough'),
orderedlist: { orderedlist: {
command: "insertorderedlist", command: 'insertorderedlist',
localizationId: "pad.toolbar.ol.title", localizationId: 'pad.toolbar.ol.title',
class: "buttonicon buttonicon-insertorderedlist" class: 'buttonicon buttonicon-insertorderedlist',
}, },
unorderedlist: { unorderedlist: {
command: "insertunorderedlist", command: 'insertunorderedlist',
localizationId: "pad.toolbar.ul.title", localizationId: 'pad.toolbar.ul.title',
class: "buttonicon buttonicon-insertunorderedlist" class: 'buttonicon buttonicon-insertunorderedlist',
}, },
indent: defaultButtonAttributes("indent"), indent: defaultButtonAttributes('indent'),
outdent: { outdent: {
command: "outdent", command: 'outdent',
localizationId: "pad.toolbar.unindent.title", localizationId: 'pad.toolbar.unindent.title',
class: "buttonicon buttonicon-outdent" class: 'buttonicon buttonicon-outdent',
}, },
undo: defaultButtonAttributes("undo"), undo: defaultButtonAttributes('undo'),
redo: defaultButtonAttributes("redo"), redo: defaultButtonAttributes('redo'),
clearauthorship: { clearauthorship: {
command: "clearauthorship", command: 'clearauthorship',
localizationId: "pad.toolbar.clearAuthorship.title", localizationId: 'pad.toolbar.clearAuthorship.title',
class: "buttonicon buttonicon-clearauthorship" class: 'buttonicon buttonicon-clearauthorship',
}, },
importexport: { importexport: {
command: "import_export", command: 'import_export',
localizationId: "pad.toolbar.import_export.title", localizationId: 'pad.toolbar.import_export.title',
class: "buttonicon buttonicon-import_export" class: 'buttonicon buttonicon-import_export',
}, },
timeslider: { timeslider: {
command: "showTimeSlider", command: 'showTimeSlider',
localizationId: "pad.toolbar.timeslider.title", localizationId: 'pad.toolbar.timeslider.title',
class: "buttonicon buttonicon-history" class: 'buttonicon buttonicon-history',
}, },
savedrevision: defaultButtonAttributes("savedRevision"), savedrevision: defaultButtonAttributes('savedRevision'),
settings: defaultButtonAttributes("settings"), settings: defaultButtonAttributes('settings'),
embed: defaultButtonAttributes("embed"), embed: defaultButtonAttributes('embed'),
showusers: defaultButtonAttributes("showusers"), showusers: defaultButtonAttributes('showusers'),
timeslider_export: { timeslider_export: {
command: "import_export", command: 'import_export',
localizationId: "timeslider.toolbar.exportlink.title", localizationId: 'timeslider.toolbar.exportlink.title',
class: "buttonicon buttonicon-import_export" class: 'buttonicon buttonicon-import_export',
}, },
timeslider_settings: { timeslider_settings: {
command: "settings", command: 'settings',
localizationId: "pad.toolbar.settings.title", localizationId: 'pad.toolbar.settings.title',
class: "buttonicon buttonicon-settings" class: 'buttonicon buttonicon-settings',
}, },
timeslider_returnToPad: { timeslider_returnToPad: {
command: "timeslider_returnToPad", command: 'timeslider_returnToPad',
localizationId: "timeslider.toolbar.returnbutton", localizationId: 'timeslider.toolbar.returnbutton',
class: "buttontext" class: 'buttontext',
} },
}, },
registerButton: function (buttonName, buttonInfo) { registerButton(buttonName, buttonInfo) {
this.availableButtons[buttonName] = buttonInfo; this.availableButtons[buttonName] = buttonInfo;
}, },
button: function (attributes) { button(attributes) {
return new Button(attributes); return new Button(attributes);
}, },
separator: function () { separator() {
return (new Separator).render(); return (new Separator()).render();
}, },
selectButton: function (attributes) { selectButton(attributes) {
return new SelectButton(attributes); return new SelectButton(attributes);
}, },
@ -255,15 +249,15 @@ module.exports = {
* Valid values for whichMenu: 'left' | 'right' | 'timeslider-right' * Valid values for whichMenu: 'left' | 'right' | 'timeslider-right'
* Valid values for page: 'pad' | 'timeslider' * Valid values for page: 'pad' | 'timeslider'
*/ */
menu: function (buttons, isReadOnly, whichMenu, page) { menu(buttons, isReadOnly, whichMenu, page) {
if (isReadOnly) { if (isReadOnly) {
// The best way to detect if it's the left editbar is to check for a bold button // The best way to detect if it's the left editbar is to check for a bold button
if (buttons[0].indexOf("bold") !== -1) { if (buttons[0].indexOf('bold') !== -1) {
// Clear all formatting buttons // Clear all formatting buttons
buttons = []; buttons = [];
} else { } else {
// Remove Save Revision from the right menu // Remove Save Revision from the right menu
removeItem(buttons[0],"savedrevision"); removeItem(buttons[0], 'savedrevision');
} }
} else { } else {
/* /*
@ -277,14 +271,12 @@ module.exports = {
* sufficient to visit a single read only pad to cause the disappearence * sufficient to visit a single read only pad to cause the disappearence
* of the star button from all the pads. * of the star button from all the pads.
*/ */
if ((buttons[0].indexOf("savedrevision") === -1) && (whichMenu === "right") && (page === "pad")) { if ((buttons[0].indexOf('savedrevision') === -1) && (whichMenu === 'right') && (page === 'pad')) {
buttons[0].push("savedrevision"); buttons[0].push('savedrevision');
} }
} }
var groups = _.map(buttons, function (group) { const groups = _.map(buttons, (group) => ButtonsGroup.fromArray(group).render());
return ButtonsGroup.fromArray(group).render();
});
return groups.join(this.separator()); return groups.join(this.separator());
} },
}; };

View file

@ -1,18 +1,18 @@
var Changeset = require('./Changeset'); const Changeset = require('./Changeset');
var ChangesetUtils = require('./ChangesetUtils'); const ChangesetUtils = require('./ChangesetUtils');
var _ = require('./underscore'); const _ = require('./underscore');
var lineMarkerAttribute = 'lmkr'; const lineMarkerAttribute = 'lmkr';
// Some of these attributes are kept for compatibility purposes. // Some of these attributes are kept for compatibility purposes.
// Not sure if we need all of them // Not sure if we need all of them
var DEFAULT_LINE_ATTRIBUTES = ['author', 'lmkr', 'insertorder', 'start']; const DEFAULT_LINE_ATTRIBUTES = ['author', 'lmkr', 'insertorder', 'start'];
// If one of these attributes are set to the first character of a // If one of these attributes are set to the first character of a
// line it is considered as a line attribute marker i.e. attributes // line it is considered as a line attribute marker i.e. attributes
// set on this marker are applied to the whole line. // set on this marker are applied to the whole line.
// The list attribute is only maintained for compatibility reasons // The list attribute is only maintained for compatibility reasons
var lineAttributes = [lineMarkerAttribute,'list']; const lineAttributes = [lineMarkerAttribute, 'list'];
/* /*
The Attribute manager builds changesets based on a document The Attribute manager builds changesets based on a document
@ -29,7 +29,7 @@ var lineAttributes = [lineMarkerAttribute,'list'];
- a SkipList `lines` containing the text lines of the document. - a SkipList `lines` containing the text lines of the document.
*/ */
var AttributeManager = function(rep, applyChangesetCallback) { const AttributeManager = function (rep, applyChangesetCallback) {
this.rep = rep; this.rep = rep;
this.applyChangesetCallback = applyChangesetCallback; this.applyChangesetCallback = applyChangesetCallback;
this.author = ''; this.author = '';
@ -43,12 +43,11 @@ AttributeManager.lineAttributes = lineAttributes;
AttributeManager.prototype = _(AttributeManager.prototype).extend({ AttributeManager.prototype = _(AttributeManager.prototype).extend({
applyChangeset: function(changeset){ applyChangeset(changeset) {
if(!this.applyChangesetCallback) return changeset; if (!this.applyChangesetCallback) return changeset;
var cs = changeset.toString(); const cs = changeset.toString();
if (!Changeset.isIdentity(cs)) if (!Changeset.isIdentity(cs)) {
{
this.applyChangesetCallback(cs); this.applyChangesetCallback(cs);
} }
@ -61,17 +60,17 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
@param end [row, col] tuple pointing to the end of the range @param end [row, col] tuple pointing to the end of the range
@param attribs: an array of attributes @param attribs: an array of attributes
*/ */
setAttributesOnRange: function(start, end, attribs) { setAttributesOnRange(start, end, attribs) {
// instead of applying the attributes to the whole range at once, we need to apply them // instead of applying the attributes to the whole range at once, we need to apply them
// line by line, to be able to disregard the "*" used as line marker. For more details, // line by line, to be able to disregard the "*" used as line marker. For more details,
// see https://github.com/ether/etherpad-lite/issues/2772 // see https://github.com/ether/etherpad-lite/issues/2772
var allChangesets; let allChangesets;
for(var row = start[0]; row <= end[0]; row++) { for (let row = start[0]; row <= end[0]; row++) {
var rowRange = this._findRowRange(row, start, end); const rowRange = this._findRowRange(row, start, end);
var startCol = rowRange[0]; const startCol = rowRange[0];
var endCol = rowRange[1]; const endCol = rowRange[1];
var rowChangeset = this._setAttributesOnRangeByLine(row, startCol, endCol, attribs); const rowChangeset = this._setAttributesOnRangeByLine(row, startCol, endCol, attribs);
// compose changesets of all rows into a single changeset, as the range might not be continuous // compose changesets of all rows into a single changeset, as the range might not be continuous
// due to the presence of line markers on the rows // due to the presence of line markers on the rows
@ -85,12 +84,12 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
return this.applyChangeset(allChangesets); return this.applyChangeset(allChangesets);
}, },
_findRowRange: function(row, start, end) { _findRowRange(row, start, end) {
var startCol, endCol; let startCol, endCol;
var startLineOffset = this.rep.lines.offsetOfIndex(row); const startLineOffset = this.rep.lines.offsetOfIndex(row);
var endLineOffset = this.rep.lines.offsetOfIndex(row+1); const endLineOffset = this.rep.lines.offsetOfIndex(row + 1);
var lineLength = endLineOffset - startLineOffset; const lineLength = endLineOffset - startLineOffset;
// find column where range on this row starts // find column where range on this row starts
if (row === start[0]) { // are we on the first row of range? if (row === start[0]) { // are we on the first row of range?
@ -116,8 +115,8 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
@param endCol column where range ends @param endCol column where range ends
@param attribs: an array of attributes @param attribs: an array of attributes
*/ */
_setAttributesOnRangeByLine: function(row, startCol, endCol, attribs) { _setAttributesOnRangeByLine(row, startCol, endCol, attribs) {
var builder = Changeset.builder(this.rep.lines.totalWidth()); const builder = Changeset.builder(this.rep.lines.totalWidth());
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [row, startCol]); ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [row, startCol]);
ChangesetUtils.buildKeepRange(this.rep, builder, [row, startCol], [row, endCol], attribs, this.rep.apool); ChangesetUtils.buildKeepRange(this.rep, builder, [row, startCol], [row, endCol], attribs, this.rep.apool);
return builder; return builder;
@ -127,12 +126,10 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
Returns if the line already has a line marker Returns if the line already has a line marker
@param lineNum: the number of the line @param lineNum: the number of the line
*/ */
lineHasMarker: function(lineNum){ lineHasMarker(lineNum) {
var that = this; const that = this;
return _.find(lineAttributes, function(attribute){ return _.find(lineAttributes, (attribute) => that.getAttributeOnLine(lineNum, attribute) != '') !== undefined;
return that.getAttributeOnLine(lineNum, attribute) != '';
}) !== undefined;
}, },
/* /*
@ -140,14 +137,12 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
@param lineNum: the number of the line to set the attribute for @param lineNum: the number of the line to set the attribute for
@param attributeKey: the name of the attribute to get, e.g. list @param attributeKey: the name of the attribute to get, e.g. list
*/ */
getAttributeOnLine: function(lineNum, attributeName){ getAttributeOnLine(lineNum, attributeName) {
// get `attributeName` attribute of first char of line // get `attributeName` attribute of first char of line
var aline = this.rep.alines[lineNum]; const aline = this.rep.alines[lineNum];
if (aline) if (aline) {
{ const opIter = Changeset.opIterator(aline);
var opIter = Changeset.opIterator(aline); if (opIter.hasNext()) {
if (opIter.hasNext())
{
return Changeset.opAttributeValue(opIter.next(), attributeName, this.rep.apool) || ''; return Changeset.opAttributeValue(opIter.next(), attributeName, this.rep.apool) || '';
} }
} }
@ -158,96 +153,94 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
Gets all attributes on a line Gets all attributes on a line
@param lineNum: the number of the line to get the attribute for @param lineNum: the number of the line to get the attribute for
*/ */
getAttributesOnLine: function(lineNum){ getAttributesOnLine(lineNum) {
// get attributes of first char of line // get attributes of first char of line
var aline = this.rep.alines[lineNum]; const aline = this.rep.alines[lineNum];
var attributes = [] const attributes = [];
if (aline) if (aline) {
{ const opIter = Changeset.opIterator(aline);
var opIter = Changeset.opIterator(aline) let op;
, op if (opIter.hasNext()) {
if (opIter.hasNext()) op = opIter.next();
{ if (!op.attribs) return [];
op = opIter.next()
if(!op.attribs) return []
Changeset.eachAttribNumber(op.attribs, function(n) { Changeset.eachAttribNumber(op.attribs, (n) => {
attributes.push([this.rep.apool.getAttribKey(n), this.rep.apool.getAttribValue(n)]) attributes.push([this.rep.apool.getAttribKey(n), this.rep.apool.getAttribValue(n)]);
}.bind(this)) });
return attributes; return attributes;
} }
} }
return []; return [];
}, },
/* /*
Gets a given attribute on a selection Gets a given attribute on a selection
@param attributeName @param attributeName
@param prevChar @param prevChar
returns true or false if an attribute is visible in range returns true or false if an attribute is visible in range
*/ */
getAttributeOnSelection: function(attributeName, prevChar){ getAttributeOnSelection(attributeName, prevChar) {
var rep = this.rep; const rep = this.rep;
if (!(rep.selStart && rep.selEnd)) return if (!(rep.selStart && rep.selEnd)) return;
// If we're looking for the caret attribute not the selection // If we're looking for the caret attribute not the selection
// has the user already got a selection or is this purely a caret location? // has the user already got a selection or is this purely a caret location?
var isNotSelection = (rep.selStart[0] == rep.selEnd[0] && rep.selEnd[1] === rep.selStart[1]); const isNotSelection = (rep.selStart[0] == rep.selEnd[0] && rep.selEnd[1] === rep.selStart[1]);
if(isNotSelection){ if (isNotSelection) {
if(prevChar){ if (prevChar) {
// If it's not the start of the line // If it's not the start of the line
if(rep.selStart[1] !== 0){ if (rep.selStart[1] !== 0) {
rep.selStart[1]--; rep.selStart[1]--;
} }
} }
} }
var withIt = Changeset.makeAttribsString('+', [ const withIt = Changeset.makeAttribsString('+', [
[attributeName, 'true'] [attributeName, 'true'],
], rep.apool); ], rep.apool);
var withItRegex = new RegExp(withIt.replace(/\*/g, '\\*') + "(\\*|$)"); const withItRegex = new RegExp(`${withIt.replace(/\*/g, '\\*')}(\\*|$)`);
function hasIt(attribs) { function hasIt(attribs) {
return withItRegex.test(attribs); return withItRegex.test(attribs);
} }
return rangeHasAttrib(rep.selStart, rep.selEnd) return rangeHasAttrib(rep.selStart, rep.selEnd);
function rangeHasAttrib(selStart, selEnd) { function rangeHasAttrib(selStart, selEnd) {
// if range is collapsed -> no attribs in range // if range is collapsed -> no attribs in range
if(selStart[1] == selEnd[1] && selStart[0] == selEnd[0]) return false if (selStart[1] == selEnd[1] && selStart[0] == selEnd[0]) return false;
if(selStart[0] != selEnd[0]) { // -> More than one line selected if (selStart[0] != selEnd[0]) { // -> More than one line selected
var hasAttrib = true var hasAttrib = true;
// from selStart to the end of the first line // from selStart to the end of the first line
hasAttrib = hasAttrib && rangeHasAttrib(selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length]) hasAttrib = hasAttrib && rangeHasAttrib(selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length]);
// for all lines in between // for all lines in between
for(var n=selStart[0]+1; n < selEnd[0]; n++) { for (let n = selStart[0] + 1; n < selEnd[0]; n++) {
hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length]) hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length]);
} }
// for the last, potentially partial, line // for the last, potentially partial, line
hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]]) hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]]);
return hasAttrib return hasAttrib;
} }
// Logic tells us we now have a range on a single line // Logic tells us we now have a range on a single line
var lineNum = selStart[0] const lineNum = selStart[0];
, start = selStart[1] const start = selStart[1];
, end = selEnd[1] const end = selEnd[1];
, hasAttrib = true var hasAttrib = true;
// Iterate over attribs on this line // Iterate over attribs on this line
var opIter = Changeset.opIterator(rep.alines[lineNum]) const opIter = Changeset.opIterator(rep.alines[lineNum]);
, indexIntoLine = 0 let indexIntoLine = 0;
while (opIter.hasNext()) { while (opIter.hasNext()) {
var op = opIter.next(); const op = opIter.next();
var opStartInLine = indexIntoLine; const opStartInLine = indexIntoLine;
var opEndInLine = opStartInLine + op.chars; const opEndInLine = opStartInLine + op.chars;
if (!hasIt(op.attribs)) { if (!hasIt(op.attribs)) {
// does op overlap selection? // does op overlap selection?
if (!(opEndInLine <= start || opStartInLine >= end)) { if (!(opEndInLine <= start || opStartInLine >= end)) {
@ -258,7 +251,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
indexIntoLine = opEndInLine; indexIntoLine = opEndInLine;
} }
return hasAttrib return hasAttrib;
} }
}, },
@ -269,40 +262,39 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
returns a list of attributes in the format returns a list of attributes in the format
[ ["key","value"], ["key","value"], ... ] [ ["key","value"], ["key","value"], ... ]
*/ */
getAttributesOnPosition: function(lineNumber, column){ getAttributesOnPosition(lineNumber, column) {
// get all attributes of the line // get all attributes of the line
var aline = this.rep.alines[lineNumber]; const aline = this.rep.alines[lineNumber];
if (!aline) { if (!aline) {
return []; return [];
} }
// iterate through all operations of a line // iterate through all operations of a line
var opIter = Changeset.opIterator(aline); const opIter = Changeset.opIterator(aline);
// we need to sum up how much characters each operations take until the wanted position // we need to sum up how much characters each operations take until the wanted position
var currentPointer = 0; let currentPointer = 0;
var attributes = []; const attributes = [];
var currentOperation; let currentOperation;
while (opIter.hasNext()) { while (opIter.hasNext()) {
currentOperation = opIter.next(); currentOperation = opIter.next();
currentPointer = currentPointer + currentOperation.chars; currentPointer += currentOperation.chars;
if (currentPointer > column) { if (currentPointer > column) {
// we got the operation of the wanted position, now collect all its attributes // we got the operation of the wanted position, now collect all its attributes
Changeset.eachAttribNumber(currentOperation.attribs, function (n) { Changeset.eachAttribNumber(currentOperation.attribs, (n) => {
attributes.push([ attributes.push([
this.rep.apool.getAttribKey(n), this.rep.apool.getAttribKey(n),
this.rep.apool.getAttribValue(n) this.rep.apool.getAttribValue(n),
]); ]);
}.bind(this)); });
// skip the loop // skip the loop
return attributes; return attributes;
} }
} }
return attributes; return attributes;
}, },
/* /*
@ -311,7 +303,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
returns a list of attributes in the format returns a list of attributes in the format
[ ["key","value"], ["key","value"], ... ] [ ["key","value"], ["key","value"], ... ]
*/ */
getAttributesOnCaret: function(){ getAttributesOnCaret() {
return this.getAttributesOnPosition(this.rep.selStart[0], this.rep.selStart[1]); return this.getAttributesOnPosition(this.rep.selStart[0], this.rep.selStart[1]);
}, },
@ -322,72 +314,72 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
@param attributeValue: an optional parameter to pass to the attribute (e.g. indention level) @param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
*/ */
setAttributeOnLine: function(lineNum, attributeName, attributeValue){ setAttributeOnLine(lineNum, attributeName, attributeValue) {
var loc = [0,0]; let loc = [0, 0];
var builder = Changeset.builder(this.rep.lines.totalWidth()); const builder = Changeset.builder(this.rep.lines.totalWidth());
var hasMarker = this.lineHasMarker(lineNum); const hasMarker = this.lineHasMarker(lineNum);
ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0])); ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
if(hasMarker){ if (hasMarker) {
ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), [ ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), [
[attributeName, attributeValue] [attributeName, attributeValue],
], this.rep.apool);
} else {
// add a line marker
builder.insert('*', [
['author', this.author],
['insertorder', 'first'],
[lineMarkerAttribute, '1'],
[attributeName, attributeValue],
], this.rep.apool); ], this.rep.apool);
}else{
// add a line marker
builder.insert('*', [
['author', this.author],
['insertorder', 'first'],
[lineMarkerAttribute, '1'],
[attributeName, attributeValue]
], this.rep.apool);
} }
return this.applyChangeset(builder); return this.applyChangeset(builder);
}, },
/** /**
* Removes a specified attribute on a line * Removes a specified attribute on a line
* @param lineNum the number of the affected line * @param lineNum the number of the affected line
* @param attributeName the name of the attribute to remove, e.g. list * @param attributeName the name of the attribute to remove, e.g. list
* @param attributeValue if given only attributes with equal value will be removed * @param attributeValue if given only attributes with equal value will be removed
*/ */
removeAttributeOnLine: function(lineNum, attributeName, attributeValue){ removeAttributeOnLine(lineNum, attributeName, attributeValue) {
var builder = Changeset.builder(this.rep.lines.totalWidth()); const builder = Changeset.builder(this.rep.lines.totalWidth());
var hasMarker = this.lineHasMarker(lineNum); const hasMarker = this.lineHasMarker(lineNum);
var found = false; let found = false;
var attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) { const attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) {
if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)){ if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)) {
found = true; found = true;
return [attributeName, '']; return [attributeName, ''];
}else if (attrib[0] === 'author'){ } else if (attrib[0] === 'author') {
// update last author to make changes to line attributes on this line // update last author to make changes to line attributes on this line
return [attributeName, this.author]; return [attributeName, this.author];
} }
return attrib; return attrib;
}); });
if (!found) { if (!found) {
return; return;
} }
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]); ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]);
var countAttribsWithMarker = _.chain(attribs).filter(function(a){return !!a[1];}) const countAttribsWithMarker = _.chain(attribs).filter((a) => !!a[1])
.map(function(a){return a[0];}).difference(DEFAULT_LINE_ATTRIBUTES).size().value(); .map((a) => a[0]).difference(DEFAULT_LINE_ATTRIBUTES).size().value();
//if we have marker and any of attributes don't need to have marker. we need delete it // if we have marker and any of attributes don't need to have marker. we need delete it
if(hasMarker && !countAttribsWithMarker){ if (hasMarker && !countAttribsWithMarker) {
ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]); ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]);
}else{ } else {
ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool); ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool);
} }
return this.applyChangeset(builder); return this.applyChangeset(builder);
}, },
/* /*
Toggles a line attribute for the specified line number Toggles a line attribute for the specified line number
If a line attribute with the specified name exists with any value it will be removed If a line attribute with the specified name exists with any value it will be removed
Otherwise it will be set to the given value Otherwise it will be set to the given value
@ -395,20 +387,19 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
@param attributeKey: the name of the attribute to toggle, e.g. list @param attributeKey: the name of the attribute to toggle, e.g. list
@param attributeValue: the value to pass to the attribute (e.g. indention level) @param attributeValue: the value to pass to the attribute (e.g. indention level)
*/ */
toggleAttributeOnLine: function(lineNum, attributeName, attributeValue) { toggleAttributeOnLine(lineNum, attributeName, attributeValue) {
return this.getAttributeOnLine(lineNum, attributeName) ? return this.getAttributeOnLine(lineNum, attributeName)
this.removeAttributeOnLine(lineNum, attributeName) : ? this.removeAttributeOnLine(lineNum, attributeName)
this.setAttributeOnLine(lineNum, attributeName, attributeValue); : this.setAttributeOnLine(lineNum, attributeName, attributeValue);
}, },
hasAttributeOnSelectionOrCaretPosition: function(attributeName) { hasAttributeOnSelectionOrCaretPosition(attributeName) {
var hasSelection = ((this.rep.selStart[0] !== this.rep.selEnd[0]) || (this.rep.selEnd[1] !== this.rep.selStart[1])); const hasSelection = ((this.rep.selStart[0] !== this.rep.selEnd[0]) || (this.rep.selEnd[1] !== this.rep.selStart[1]));
var hasAttrib; let hasAttrib;
if (hasSelection) { if (hasSelection) {
hasAttrib = this.getAttributeOnSelection(attributeName); hasAttrib = this.getAttributeOnSelection(attributeName);
}else { } else {
var attributesOnCaretPosition = this.getAttributesOnCaret(); const attributesOnCaretPosition = this.getAttributesOnCaret();
hasAttrib = _.contains(_.flatten(attributesOnCaretPosition), attributeName); hasAttrib = _.contains(_.flatten(attributesOnCaretPosition), attributeName);
} }
return hasAttrib; return hasAttrib;

View file

@ -28,28 +28,28 @@
used to reference Attributes in Changesets. used to reference Attributes in Changesets.
*/ */
var AttributePool = function () { const AttributePool = function () {
this.numToAttrib = {}; // e.g. {0: ['foo','bar']} this.numToAttrib = {}; // e.g. {0: ['foo','bar']}
this.attribToNum = {}; // e.g. {'foo,bar': 0} this.attribToNum = {}; // e.g. {'foo,bar': 0}
this.nextNum = 0; this.nextNum = 0;
}; };
AttributePool.prototype.putAttrib = function (attrib, dontAddIfAbsent) { AttributePool.prototype.putAttrib = function (attrib, dontAddIfAbsent) {
var str = String(attrib); const str = String(attrib);
if (str in this.attribToNum) { if (str in this.attribToNum) {
return this.attribToNum[str]; return this.attribToNum[str];
} }
if (dontAddIfAbsent) { if (dontAddIfAbsent) {
return -1; return -1;
} }
var num = this.nextNum++; const num = this.nextNum++;
this.attribToNum[str] = num; this.attribToNum[str] = num;
this.numToAttrib[num] = [String(attrib[0] || ''), String(attrib[1] || '')]; this.numToAttrib[num] = [String(attrib[0] || ''), String(attrib[1] || '')];
return num; return num;
}; };
AttributePool.prototype.getAttrib = function (num) { AttributePool.prototype.getAttrib = function (num) {
var pair = this.numToAttrib[num]; const pair = this.numToAttrib[num];
if (!pair) { if (!pair) {
return pair; return pair;
} }
@ -57,20 +57,20 @@ AttributePool.prototype.getAttrib = function (num) {
}; };
AttributePool.prototype.getAttribKey = function (num) { AttributePool.prototype.getAttribKey = function (num) {
var pair = this.numToAttrib[num]; const pair = this.numToAttrib[num];
if (!pair) return ''; if (!pair) return '';
return pair[0]; return pair[0];
}; };
AttributePool.prototype.getAttribValue = function (num) { AttributePool.prototype.getAttribValue = function (num) {
var pair = this.numToAttrib[num]; const pair = this.numToAttrib[num];
if (!pair) return ''; if (!pair) return '';
return pair[1]; return pair[1];
}; };
AttributePool.prototype.eachAttrib = function (func) { AttributePool.prototype.eachAttrib = function (func) {
for (var n in this.numToAttrib) { for (const n in this.numToAttrib) {
var pair = this.numToAttrib[n]; const pair = this.numToAttrib[n];
func(pair[0], pair[1]); func(pair[0], pair[1]);
} }
}; };
@ -78,7 +78,7 @@ AttributePool.prototype.eachAttrib = function (func) {
AttributePool.prototype.toJsonable = function () { AttributePool.prototype.toJsonable = function () {
return { return {
numToAttrib: this.numToAttrib, numToAttrib: this.numToAttrib,
nextNum: this.nextNum nextNum: this.nextNum,
}; };
}; };
@ -86,7 +86,7 @@ AttributePool.prototype.fromJsonable = function (obj) {
this.numToAttrib = obj.numToAttrib; this.numToAttrib = obj.numToAttrib;
this.nextNum = obj.nextNum; this.nextNum = obj.nextNum;
this.attribToNum = {}; this.attribToNum = {};
for (var n in this.numToAttrib) { for (const n in this.numToAttrib) {
this.attribToNum[String(this.numToAttrib[n])] = Number(n); this.attribToNum[String(this.numToAttrib[n])] = Number(n);
} }
return this; return this;

File diff suppressed because it is too large Load diff

View file

@ -18,40 +18,33 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
exports.buildRemoveRange = function(rep, builder, start, end) { exports.buildRemoveRange = function (rep, builder, start, end) {
var startLineOffset = rep.lines.offsetOfIndex(start[0]); const startLineOffset = rep.lines.offsetOfIndex(start[0]);
var endLineOffset = rep.lines.offsetOfIndex(end[0]); const endLineOffset = rep.lines.offsetOfIndex(end[0]);
if (end[0] > start[0]) if (end[0] > start[0]) {
{
builder.remove(endLineOffset - startLineOffset - start[1], end[0] - start[0]); builder.remove(endLineOffset - startLineOffset - start[1], end[0] - start[0]);
builder.remove(end[1]); builder.remove(end[1]);
} } else {
else
{
builder.remove(end[1] - start[1]); builder.remove(end[1] - start[1]);
} }
} };
exports.buildKeepRange = function(rep, builder, start, end, attribs, pool) { exports.buildKeepRange = function (rep, builder, start, end, attribs, pool) {
var startLineOffset = rep.lines.offsetOfIndex(start[0]); const startLineOffset = rep.lines.offsetOfIndex(start[0]);
var endLineOffset = rep.lines.offsetOfIndex(end[0]); const endLineOffset = rep.lines.offsetOfIndex(end[0]);
if (end[0] > start[0]) if (end[0] > start[0]) {
{
builder.keep(endLineOffset - startLineOffset - start[1], end[0] - start[0], attribs, pool); builder.keep(endLineOffset - startLineOffset - start[1], end[0] - start[0], attribs, pool);
builder.keep(end[1], 0, attribs, pool); builder.keep(end[1], 0, attribs, pool);
} } else {
else
{
builder.keep(end[1] - start[1], 0, attribs, pool); builder.keep(end[1] - start[1], 0, attribs, pool);
} }
} };
exports.buildKeepToStartOfRange = function(rep, builder, start) { exports.buildKeepToStartOfRange = function (rep, builder, start) {
var startLineOffset = rep.lines.offsetOfIndex(start[0]); const startLineOffset = rep.lines.offsetOfIndex(start[0]);
builder.keep(startLineOffset, start[0]); builder.keep(startLineOffset, start[0]);
builder.keep(start[1]); builder.keep(start[1]);
} };

View file

@ -23,61 +23,57 @@
// requires: top // requires: top
// requires: undefined // requires: undefined
var KERNEL_SOURCE = '../static/js/require-kernel.js'; const KERNEL_SOURCE = '../static/js/require-kernel.js';
Ace2Editor.registry = { Ace2Editor.registry = {
nextId: 1 nextId: 1,
}; };
var hooks = require('./pluginfw/hooks'); const hooks = require('./pluginfw/hooks');
var pluginUtils = require('./pluginfw/shared'); const pluginUtils = require('./pluginfw/shared');
var _ = require('./underscore'); const _ = require('./underscore');
function scriptTag(source) { function scriptTag(source) {
return ( return (
'<script type="text/javascript">\n' `<script type="text/javascript">\n${
+ source.replace(/<\//g, '<\\/') + source.replace(/<\//g, '<\\/')
'</script>' }</script>`
) );
} }
function Ace2Editor() { function Ace2Editor() {
var ace2 = Ace2Editor; const ace2 = Ace2Editor;
var editor = {}; const editor = {};
var info = { let info = {
editor: editor, editor,
id: (ace2.registry.nextId++) id: (ace2.registry.nextId++),
}; };
var loaded = false; let loaded = false;
var actionsPendingInit = []; let actionsPendingInit = [];
function pendingInit(func, optDoNow) { function pendingInit(func, optDoNow) {
return function() { return function () {
var that = this; const that = this;
var args = arguments; const args = arguments;
var action = function() { const action = function () {
func.apply(that, args); func.apply(that, args);
} };
if (optDoNow) if (optDoNow) {
{
optDoNow.apply(that, args); optDoNow.apply(that, args);
} }
if (loaded) if (loaded) {
{
action(); action();
} } else {
else
{
actionsPendingInit.push(action); actionsPendingInit.push(action);
} }
}; };
} }
function doActionsPendingInit() { function doActionsPendingInit() {
_.each(actionsPendingInit, function(fn,i){ _.each(actionsPendingInit, (fn, i) => {
fn() fn();
}); });
actionsPendingInit = []; actionsPendingInit = [];
} }
@ -86,43 +82,56 @@ function Ace2Editor() {
// The following functions (prefixed by 'ace_') are exposed by editor, but // The following functions (prefixed by 'ace_') are exposed by editor, but
// execution is delayed until init is complete // execution is delayed until init is complete
var aceFunctionsPendingInit = ['importText', 'importAText', 'focus', const aceFunctionsPendingInit = ['importText',
'setEditable', 'getFormattedCode', 'setOnKeyPress', 'setOnKeyDown', 'importAText',
'setNotifyDirty', 'setProperty', 'setBaseText', 'setBaseAttributedText', 'focus',
'applyChangesToBase', 'applyPreparedChangesetToBase', 'setEditable',
'setUserChangeNotificationCallback', 'setAuthorInfo', 'getFormattedCode',
'setAuthorSelectionRange', 'callWithAce', 'execCommand', 'replaceRange']; 'setOnKeyPress',
'setOnKeyDown',
'setNotifyDirty',
'setProperty',
'setBaseText',
'setBaseAttributedText',
'applyChangesToBase',
'applyPreparedChangesetToBase',
'setUserChangeNotificationCallback',
'setAuthorInfo',
'setAuthorSelectionRange',
'callWithAce',
'execCommand',
'replaceRange'];
_.each(aceFunctionsPendingInit, function(fnName,i){ _.each(aceFunctionsPendingInit, (fnName, i) => {
var prefix = 'ace_'; const prefix = 'ace_';
var name = prefix + fnName; const name = prefix + fnName;
editor[fnName] = pendingInit(function(){ editor[fnName] = pendingInit(function () {
if(fnName === "setAuthorInfo"){ if (fnName === 'setAuthorInfo') {
if(!arguments[0]){ if (!arguments[0]) {
// setAuthorInfo AuthorId not set for some reason // setAuthorInfo AuthorId not set for some reason
}else{ } else {
info[prefix + fnName].apply(this, arguments); info[prefix + fnName].apply(this, arguments);
} }
}else{ } else {
info[prefix + fnName].apply(this, arguments); info[prefix + fnName].apply(this, arguments);
} }
}); });
}); });
editor.exportText = function() { editor.exportText = function () {
if (!loaded) return "(awaiting init)\n"; if (!loaded) return '(awaiting init)\n';
return info.ace_exportText(); return info.ace_exportText();
}; };
editor.getFrame = function() { editor.getFrame = function () {
return info.frame || null; return info.frame || null;
}; };
editor.getDebugProperty = function(prop) { editor.getDebugProperty = function (prop) {
return info.ace_getDebugProperty(prop); return info.ace_getDebugProperty(prop);
}; };
editor.getInInternationalComposition = function() { editor.getInInternationalComposition = function () {
if (!loaded) return false; if (!loaded) return false;
return info.ace_getInInternationalComposition(); return info.ace_getInInternationalComposition();
}; };
@ -136,26 +145,25 @@ function Ace2Editor() {
// to prepareUserChangeset will return an updated changeset that takes into account the // to prepareUserChangeset will return an updated changeset that takes into account the
// latest user changes, and modify the changeset to be applied by applyPreparedChangesetToBase // latest user changes, and modify the changeset to be applied by applyPreparedChangesetToBase
// accordingly. // accordingly.
editor.prepareUserChangeset = function() { editor.prepareUserChangeset = function () {
if (!loaded) return null; if (!loaded) return null;
return info.ace_prepareUserChangeset(); return info.ace_prepareUserChangeset();
}; };
editor.getUnhandledErrors = function() { editor.getUnhandledErrors = function () {
if (!loaded) return []; if (!loaded) return [];
// returns array of {error: <browser Error object>, time: +new Date()} // returns array of {error: <browser Error object>, time: +new Date()}
return info.ace_getUnhandledErrors(); return info.ace_getUnhandledErrors();
}; };
function sortFilesByEmbeded(files) { function sortFilesByEmbeded(files) {
var embededFiles = []; const embededFiles = [];
var remoteFiles = []; let remoteFiles = [];
if (Ace2Editor.EMBEDED) { if (Ace2Editor.EMBEDED) {
for (var i = 0, ii = files.length; i < ii; i++) { for (let i = 0, ii = files.length; i < ii; i++) {
var file = files[i]; const file = files[i];
if (Object.prototype.hasOwnProperty.call(Ace2Editor.EMBEDED, file)) { if (Object.prototype.hasOwnProperty.call(Ace2Editor.EMBEDED, file)) {
embededFiles.push(file); embededFiles.push(file);
} else { } else {
@ -169,9 +177,9 @@ function Ace2Editor() {
return {embeded: embededFiles, remote: remoteFiles}; return {embeded: embededFiles, remote: remoteFiles};
} }
function pushStyleTagsFor(buffer, files) { function pushStyleTagsFor(buffer, files) {
var sorted = sortFilesByEmbeded(files); const sorted = sortFilesByEmbeded(files);
var embededFiles = sorted.embeded; const embededFiles = sorted.embeded;
var remoteFiles = sorted.remote; const remoteFiles = sorted.remote;
if (embededFiles.length > 0) { if (embededFiles.length > 0) {
buffer.push('<style type="text/css">'); buffer.push('<style type="text/css">');
@ -183,67 +191,66 @@ function Ace2Editor() {
} }
for (var i = 0, ii = remoteFiles.length; i < ii; i++) { for (var i = 0, ii = remoteFiles.length; i < ii; i++) {
var file = remoteFiles[i]; var file = remoteFiles[i];
buffer.push('<link rel="stylesheet" type="text/css" href="' + encodeURI(file) + '"\/>'); buffer.push(`<link rel="stylesheet" type="text/css" href="${encodeURI(file)}"\/>`);
} }
} }
editor.destroy = pendingInit(function() { editor.destroy = pendingInit(() => {
info.ace_dispose(); info.ace_dispose();
info.frame.parentNode.removeChild(info.frame); info.frame.parentNode.removeChild(info.frame);
delete ace2.registry[info.id]; delete ace2.registry[info.id];
info = null; // prevent IE 6 closure memory leaks info = null; // prevent IE 6 closure memory leaks
}); });
editor.init = function(containerId, initialCode, doneFunc) { editor.init = function (containerId, initialCode, doneFunc) {
editor.importText(initialCode); editor.importText(initialCode);
info.onEditorReady = function() { info.onEditorReady = function () {
loaded = true; loaded = true;
doActionsPendingInit(); doActionsPendingInit();
doneFunc(); doneFunc();
}; };
(function() { (function () {
var doctype = "<!doctype html>"; const doctype = '<!doctype html>';
var iframeHTML = []; const iframeHTML = [];
iframeHTML.push(doctype); iframeHTML.push(doctype);
iframeHTML.push("<html class='inner-editor " + clientVars.skinVariants + "'><head>"); iframeHTML.push(`<html class='inner-editor ${clientVars.skinVariants}'><head>`);
// calls to these functions ($$INCLUDE_...) are replaced when this file is processed // calls to these functions ($$INCLUDE_...) are replaced when this file is processed
// and compressed, putting the compressed code from the named file directly into the // and compressed, putting the compressed code from the named file directly into the
// source here. // source here.
// these lines must conform to a specific format because they are passed by the build script: // these lines must conform to a specific format because they are passed by the build script:
var includedCSS = []; var includedCSS = [];
var $$INCLUDE_CSS = function(filename) {includedCSS.push(filename)}; var $$INCLUDE_CSS = function (filename) { includedCSS.push(filename); };
$$INCLUDE_CSS("../static/css/iframe_editor.css"); $$INCLUDE_CSS('../static/css/iframe_editor.css');
// disableCustomScriptsAndStyles can be used to disable loading of custom scripts // disableCustomScriptsAndStyles can be used to disable loading of custom scripts
if(!clientVars.disableCustomScriptsAndStyles){ if (!clientVars.disableCustomScriptsAndStyles) {
$$INCLUDE_CSS("../static/css/pad.css?v=" + clientVars.randomVersionString); $$INCLUDE_CSS(`../static/css/pad.css?v=${clientVars.randomVersionString}`);
} }
var additionalCSS = _(hooks.callAll("aceEditorCSS")).map(function(path){ var additionalCSS = _(hooks.callAll('aceEditorCSS')).map((path) => {
if (path.match(/\/\//)) { // Allow urls to external CSS - http(s):// and //some/path.css if (path.match(/\/\//)) { // Allow urls to external CSS - http(s):// and //some/path.css
return path; return path;
} }
return '../static/plugins/' + path; return `../static/plugins/${path}`;
}); });
includedCSS = includedCSS.concat(additionalCSS); includedCSS = includedCSS.concat(additionalCSS);
$$INCLUDE_CSS("../static/skins/" + clientVars.skinName + "/pad.css?v=" + clientVars.randomVersionString); $$INCLUDE_CSS(`../static/skins/${clientVars.skinName}/pad.css?v=${clientVars.randomVersionString}`);
pushStyleTagsFor(iframeHTML, includedCSS); pushStyleTagsFor(iframeHTML, includedCSS);
if (!Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) { if (!Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) {
// Remotely src'd script tag will not work in IE; it must be embedded, so // Remotely src'd script tag will not work in IE; it must be embedded, so
// throw an error if it is not. // throw an error if it is not.
throw new Error("Require kernel could not be found."); throw new Error('Require kernel could not be found.');
} }
iframeHTML.push(scriptTag( iframeHTML.push(scriptTag(
Ace2Editor.EMBEDED[KERNEL_SOURCE] + '\n\ `${Ace2Editor.EMBEDED[KERNEL_SOURCE]}\n\
require.setRootURI("../javascripts/src");\n\ require.setRootURI("../javascripts/src");\n\
require.setLibraryURI("../javascripts/lib");\n\ require.setLibraryURI("../javascripts/lib");\n\
require.setGlobalKeyPath("require");\n\ require.setGlobalKeyPath("require");\n\
@ -257,23 +264,23 @@ var Ace2Inner = require("ep_etherpad-lite/static/js/ace2_inner");\n\
plugins.ensure(function () {\n\ plugins.ensure(function () {\n\
Ace2Inner.init();\n\ Ace2Inner.init();\n\
});\n\ });\n\
')); `));
iframeHTML.push('<style type="text/css" title="dynamicsyntax"></style>'); iframeHTML.push('<style type="text/css" title="dynamicsyntax"></style>');
hooks.callAll("aceInitInnerdocbodyHead", { hooks.callAll('aceInitInnerdocbodyHead', {
iframeHTML: iframeHTML iframeHTML,
}); });
iframeHTML.push('</head><body id="innerdocbody" class="innerdocbody" role="application" class="syntax" spellcheck="false">&nbsp;</body></html>'); iframeHTML.push('</head><body id="innerdocbody" class="innerdocbody" role="application" class="syntax" spellcheck="false">&nbsp;</body></html>');
// Expose myself to global for my child frame. // Expose myself to global for my child frame.
var thisFunctionsName = "ChildAccessibleAce2Editor"; const thisFunctionsName = 'ChildAccessibleAce2Editor';
(function () {return this}())[thisFunctionsName] = Ace2Editor; (function () { return this; }())[thisFunctionsName] = Ace2Editor;
var outerScript = '\ const outerScript = `\
editorId = ' + JSON.stringify(info.id) + ';\n\ editorId = ${JSON.stringify(info.id)};\n\
editorInfo = parent[' + JSON.stringify(thisFunctionsName) + '].registry[editorId];\n\ editorInfo = parent[${JSON.stringify(thisFunctionsName)}].registry[editorId];\n\
window.onload = function () {\n\ window.onload = function () {\n\
window.onload = null;\n\ window.onload = null;\n\
setTimeout(function () {\n\ setTimeout(function () {\n\
@ -293,34 +300,35 @@ window.onload = function () {\n\
};\n\ };\n\
var doc = iframe.contentWindow.document;\n\ var doc = iframe.contentWindow.document;\n\
doc.open();\n\ doc.open();\n\
var text = (' + JSON.stringify(iframeHTML.join('\n')) + ');\n\ var text = (${JSON.stringify(iframeHTML.join('\n'))});\n\
doc.write(text);\n\ doc.write(text);\n\
doc.close();\n\ doc.close();\n\
}, 0);\n\ }, 0);\n\
}'; }`;
var outerHTML = [doctype, '<html class="inner-editor outerdoc ' + clientVars.skinVariants + '"><head>'] const outerHTML = [doctype, `<html class="inner-editor outerdoc ${clientVars.skinVariants}"><head>`];
var includedCSS = []; var includedCSS = [];
var $$INCLUDE_CSS = function(filename) {includedCSS.push(filename)}; var $$INCLUDE_CSS = function (filename) { includedCSS.push(filename); };
$$INCLUDE_CSS("../static/css/iframe_editor.css"); $$INCLUDE_CSS('../static/css/iframe_editor.css');
$$INCLUDE_CSS("../static/css/pad.css?v=" + clientVars.randomVersionString); $$INCLUDE_CSS(`../static/css/pad.css?v=${clientVars.randomVersionString}`);
var additionalCSS = _(hooks.callAll("aceEditorCSS")).map(function(path){ var additionalCSS = _(hooks.callAll('aceEditorCSS')).map((path) => {
if (path.match(/\/\//)) { // Allow urls to external CSS - http(s):// and //some/path.css if (path.match(/\/\//)) { // Allow urls to external CSS - http(s):// and //some/path.css
return path; return path;
} }
return '../static/plugins/' + path } return `../static/plugins/${path}`;
},
); );
includedCSS = includedCSS.concat(additionalCSS); includedCSS = includedCSS.concat(additionalCSS);
$$INCLUDE_CSS("../static/skins/" + clientVars.skinName + "/pad.css?v=" + clientVars.randomVersionString); $$INCLUDE_CSS(`../static/skins/${clientVars.skinName}/pad.css?v=${clientVars.randomVersionString}`);
pushStyleTagsFor(outerHTML, includedCSS); pushStyleTagsFor(outerHTML, includedCSS);
// bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly // bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly
// (throbs busy while typing) // (throbs busy while typing)
var pluginNames = pluginUtils.clientPluginNames(); const pluginNames = pluginUtils.clientPluginNames();
outerHTML.push( outerHTML.push(
'<style type="text/css" title="dynamicsyntax"></style>', '<style type="text/css" title="dynamicsyntax"></style>',
'<link rel="stylesheet" type="text/css" href="data:text/css,"/>', '<link rel="stylesheet" type="text/css" href="data:text/css,"/>',
@ -331,14 +339,14 @@ window.onload = function () {\n\
'<div id="linemetricsdiv">x</div>', '<div id="linemetricsdiv">x</div>',
'</body></html>'); '</body></html>');
var outerFrame = document.createElement("IFRAME"); const outerFrame = document.createElement('IFRAME');
outerFrame.name = "ace_outer"; outerFrame.name = 'ace_outer';
outerFrame.frameBorder = 0; // for IE outerFrame.frameBorder = 0; // for IE
outerFrame.title = "Ether"; outerFrame.title = 'Ether';
info.frame = outerFrame; info.frame = outerFrame;
document.getElementById(containerId).appendChild(outerFrame); document.getElementById(containerId).appendChild(outerFrame);
var editorDocument = outerFrame.contentWindow.document; const editorDocument = outerFrame.contentWindow.document;
editorDocument.open(); editorDocument.open();
editorDocument.write(outerHTML.join('')); editorDocument.write(outerHTML.join(''));

View file

@ -20,27 +20,27 @@
* limitations under the License. * limitations under the License.
*/ */
var Security = require('./security'); const Security = require('./security');
function isNodeText(node) { function isNodeText(node) {
return (node.nodeType == 3); return (node.nodeType == 3);
} }
function object(o) { function object(o) {
var f = function(){}; const f = function () {};
f.prototype = o; f.prototype = o;
return new f(); return new f();
} }
function getAssoc(obj, name) { function getAssoc(obj, name) {
return obj["_magicdom_" + name]; return obj[`_magicdom_${name}`];
} }
function setAssoc(obj, name, value) { function setAssoc(obj, name, value) {
// note that in IE designMode, properties of a node can get // note that in IE designMode, properties of a node can get
// copied to new nodes that are spawned during editing; also, // copied to new nodes that are spawned during editing; also,
// properties representable in HTML text can survive copy-and-paste // properties representable in HTML text can survive copy-and-paste
obj["_magicdom_" + name] = value; obj[`_magicdom_${name}`] = value;
} }
// "func" is a function over 0..(numItems-1) that is monotonically // "func" is a function over 0..(numItems-1) that is monotonically
@ -52,11 +52,10 @@ function binarySearch(numItems, func) {
if (numItems < 1) return 0; if (numItems < 1) return 0;
if (func(0)) return 0; if (func(0)) return 0;
if (!func(numItems - 1)) return numItems; if (!func(numItems - 1)) return numItems;
var low = 0; // func(low) is always false let low = 0; // func(low) is always false
var high = numItems - 1; // func(high) is always true let high = numItems - 1; // func(high) is always true
while ((high - low) > 1) while ((high - low) > 1) {
{ const x = Math.floor((low + high) / 2); // x != low, x != high
var x = Math.floor((low + high) / 2); // x != low, x != high
if (func(x)) high = x; if (func(x)) high = x;
else low = x; else low = x;
} }
@ -64,7 +63,7 @@ function binarySearch(numItems, func) {
} }
function binarySearchInfinite(expectedLength, func) { function binarySearchInfinite(expectedLength, func) {
var i = 0; let i = 0;
while (!func(i)) i += expectedLength; while (!func(i)) i += expectedLength;
return binarySearch(i, func); return binarySearch(i, func);
} }
@ -73,7 +72,7 @@ function htmlPrettyEscape(str) {
return Security.escapeHTML(str).replace(/\r?\n/g, '\\n'); return Security.escapeHTML(str).replace(/\r?\n/g, '\\n');
} }
var noop = function(){}; const noop = function () {};
exports.isNodeText = isNodeText; exports.isNodeText = isNodeText;
exports.object = object; exports.object = object;

File diff suppressed because it is too large Load diff

View file

@ -1,271 +1,262 @@
$(document).ready(function () { $(document).ready(() => {
let socket;
const loc = document.location;
const port = loc.port == '' ? (loc.protocol == 'https:' ? 443 : 80) : loc.port;
const url = `${loc.protocol}//${loc.hostname}:${port}/`;
const pathComponents = location.pathname.split('/');
// Strip admin/plugins
const baseURL = `${pathComponents.slice(0, pathComponents.length - 2).join('/')}/`;
const resource = `${baseURL.substring(1)}socket.io`;
var socket, // connect
loc = document.location, const room = `${url}pluginfw/installer`;
port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port, socket = io.connect(room, {path: `${baseURL}socket.io`, resource});
url = loc.protocol + "//" + loc.hostname + ":" + port + "/",
pathComponents = location.pathname.split('/'),
// Strip admin/plugins
baseURL = pathComponents.slice(0,pathComponents.length-2).join('/') + '/',
resource = baseURL.substring(1) + "socket.io";
//connect
var room = url + "pluginfw/installer";
socket = io.connect(room, {path: baseURL + "socket.io", resource : resource});
function search(searchTerm, limit) { function search(searchTerm, limit) {
if(search.searchTerm != searchTerm) { if (search.searchTerm != searchTerm) {
search.offset = 0 search.offset = 0;
search.results = [] search.results = [];
search.end = false search.end = false;
} }
limit = limit? limit : search.limit limit = limit ? limit : search.limit;
search.searchTerm = searchTerm; search.searchTerm = searchTerm;
socket.emit("search", {searchTerm: searchTerm, offset:search.offset, limit: limit, sortBy: search.sortBy, sortDir: search.sortDir}); socket.emit('search', {searchTerm, offset: search.offset, limit, sortBy: search.sortBy, sortDir: search.sortDir});
search.offset += limit; search.offset += limit;
$('#search-progress').show() $('#search-progress').show();
search.messages.show('fetching') search.messages.show('fetching');
search.searching = true search.searching = true;
} }
search.searching = false; search.searching = false;
search.offset = 0; search.offset = 0;
search.limit = 999; search.limit = 999;
search.results = []; search.results = [];
search.sortBy = 'name'; search.sortBy = 'name';
search.sortDir = /*DESC?*/true; search.sortDir = /* DESC?*/true;
search.end = true;// have we received all results already? search.end = true;// have we received all results already?
search.messages = { search.messages = {
show: function(msg) { show(msg) {
//$('.search-results .messages').show() // $('.search-results .messages').show()
$('.search-results .messages .'+msg+'').show() $(`.search-results .messages .${msg}`).show();
$('.search-results .messages .'+msg+' *').show() $(`.search-results .messages .${msg} *`).show();
}, },
hide: function(msg) { hide(msg) {
$('.search-results .messages').hide() $('.search-results .messages').hide();
$('.search-results .messages .'+msg+'').hide() $(`.search-results .messages .${msg}`).hide();
$('.search-results .messages .'+msg+' *').hide() $(`.search-results .messages .${msg} *`).hide();
} },
} };
var installed = { const installed = {
progress: { progress: {
show: function(plugin, msg) { show(plugin, msg) {
$('.installed-results .'+plugin+' .progress').show() $(`.installed-results .${plugin} .progress`).show();
$('.installed-results .'+plugin+' .progress .message').text(msg) $(`.installed-results .${plugin} .progress .message`).text(msg);
if($(window).scrollTop() > $('.'+plugin).offset().top)$(window).scrollTop($('.'+plugin).offset().top-100) if ($(window).scrollTop() > $(`.${plugin}`).offset().top)$(window).scrollTop($(`.${plugin}`).offset().top - 100);
},
hide(plugin) {
$(`.installed-results .${plugin} .progress`).hide();
$(`.installed-results .${plugin} .progress .message`).text('');
}, },
hide: function(plugin) {
$('.installed-results .'+plugin+' .progress').hide()
$('.installed-results .'+plugin+' .progress .message').text('')
}
}, },
messages: { messages: {
show: function(msg) { show(msg) {
$('.installed-results .messages').show() $('.installed-results .messages').show();
$('.installed-results .messages .'+msg+'').show() $(`.installed-results .messages .${msg}`).show();
},
hide(msg) {
$('.installed-results .messages').hide();
$(`.installed-results .messages .${msg}`).hide();
}, },
hide: function(msg) {
$('.installed-results .messages').hide()
$('.installed-results .messages .'+msg+'').hide()
}
}, },
list: [] list: [],
} };
function displayPluginList(plugins, container, template) { function displayPluginList(plugins, container, template) {
plugins.forEach(function(plugin) { plugins.forEach((plugin) => {
var row = template.clone(); const row = template.clone();
for (attr in plugin) { for (attr in plugin) {
if(attr == "name"){ // Hack to rewrite URLS into name if (attr == 'name') { // Hack to rewrite URLS into name
var link = $('<a>'); const link = $('<a>');
link.attr('href', 'https://npmjs.org/package/'+plugin['name']); link.attr('href', `https://npmjs.org/package/${plugin.name}`);
link.attr('plugin', 'Plugin details'); link.attr('plugin', 'Plugin details');
link.attr('target', '_blank'); link.attr('target', '_blank');
link.text(plugin['name'].substr(3)); link.text(plugin.name.substr(3));
row.find('.name').append(link); row.find('.name').append(link);
} else { } else {
row.find("." + attr).text(plugin[attr]); row.find(`.${attr}`).text(plugin[attr]);
} }
} }
row.find(".version").text(plugin.version); row.find('.version').text(plugin.version);
row.addClass(plugin.name) row.addClass(plugin.name);
row.data('plugin', plugin.name) row.data('plugin', plugin.name);
container.append(row); container.append(row);
}) });
updateHandlers(); updateHandlers();
} }
function sortPluginList(plugins, property, /*ASC?*/dir) { function sortPluginList(plugins, property, /* ASC?*/dir) {
return plugins.sort(function(a, b) { return plugins.sort((a, b) => {
if (a[property] < b[property]) if (a[property] < b[property]) return dir ? -1 : 1;
return dir? -1 : 1; if (a[property] > b[property]) return dir ? 1 : -1;
if (a[property] > b[property])
return dir? 1 : -1;
// a must be equal to b // a must be equal to b
return 0; return 0;
}) });
} }
function updateHandlers() { function updateHandlers() {
// Search // Search
$("#search-query").unbind('keyup').keyup(function () { $('#search-query').unbind('keyup').keyup(() => {
search($("#search-query").val()); search($('#search-query').val());
}); });
// Prevent form submit // Prevent form submit
$('#search-query').parent().bind('submit', function() { $('#search-query').parent().bind('submit', () => false);
return false;
});
// update & install // update & install
$(".do-install, .do-update").unbind('click').click(function (e) { $('.do-install, .do-update').unbind('click').click(function (e) {
var $row = $(e.target).closest("tr") const $row = $(e.target).closest('tr');
, plugin = $row.data('plugin'); const plugin = $row.data('plugin');
if($(this).hasClass('do-install')) { if ($(this).hasClass('do-install')) {
$row.remove().appendTo('#installed-plugins') $row.remove().appendTo('#installed-plugins');
installed.progress.show(plugin, 'Installing') installed.progress.show(plugin, 'Installing');
}else{ } else {
installed.progress.show(plugin, 'Updating') installed.progress.show(plugin, 'Updating');
} }
socket.emit("install", plugin); socket.emit('install', plugin);
installed.messages.hide("nothing-installed") installed.messages.hide('nothing-installed');
}); });
// uninstall // uninstall
$(".do-uninstall").unbind('click').click(function (e) { $('.do-uninstall').unbind('click').click((e) => {
var $row = $(e.target).closest("tr") const $row = $(e.target).closest('tr');
, pluginName = $row.data('plugin'); const pluginName = $row.data('plugin');
socket.emit("uninstall", pluginName); socket.emit('uninstall', pluginName);
installed.progress.show(pluginName, 'Uninstalling') installed.progress.show(pluginName, 'Uninstalling');
installed.list = installed.list.filter(function(plugin) { installed.list = installed.list.filter((plugin) => plugin.name != pluginName);
return plugin.name != pluginName
})
}); });
// Sort // Sort
$('.sort.up').unbind('click').click(function() { $('.sort.up').unbind('click').click(function () {
search.sortBy = $(this).attr('data-label').toLowerCase(); search.sortBy = $(this).attr('data-label').toLowerCase();
search.sortDir = false; search.sortDir = false;
search.offset = 0; search.offset = 0;
search(search.searchTerm, search.results.length); search(search.searchTerm, search.results.length);
search.results = []; search.results = [];
}) });
$('.sort.down, .sort.none').unbind('click').click(function() { $('.sort.down, .sort.none').unbind('click').click(function () {
search.sortBy = $(this).attr('data-label').toLowerCase(); search.sortBy = $(this).attr('data-label').toLowerCase();
search.sortDir = true; search.sortDir = true;
search.offset = 0; search.offset = 0;
search(search.searchTerm, search.results.length); search(search.searchTerm, search.results.length);
search.results = []; search.results = [];
}) });
} }
socket.on('results:search', function (data) { socket.on('results:search', (data) => {
if(!data.results.length) search.end = true; if (!data.results.length) search.end = true;
if(data.query.offset == 0) search.results = []; if (data.query.offset == 0) search.results = [];
search.messages.hide('nothing-found') search.messages.hide('nothing-found');
search.messages.hide('fetching') search.messages.hide('fetching');
$("#search-query").removeAttr('disabled') $('#search-query').removeAttr('disabled');
console.log('got search results', data) console.log('got search results', data);
// add to results // add to results
search.results = search.results.concat(data.results); search.results = search.results.concat(data.results);
// Update sorting head // Update sorting head
$('.sort') $('.sort')
.removeClass('up down') .removeClass('up down')
.addClass('none'); .addClass('none');
$('.search-results thead th[data-label='+data.query.sortBy+']') $(`.search-results thead th[data-label=${data.query.sortBy}]`)
.removeClass('none') .removeClass('none')
.addClass(data.query.sortDir? 'up' : 'down'); .addClass(data.query.sortDir ? 'up' : 'down');
// re-render search results // re-render search results
var searchWidget = $(".search-results"); const searchWidget = $('.search-results');
searchWidget.find(".results *").remove(); searchWidget.find('.results *').remove();
if(search.results.length > 0) { if (search.results.length > 0) {
displayPluginList(search.results, searchWidget.find(".results"), searchWidget.find(".template tr")) displayPluginList(search.results, searchWidget.find('.results'), searchWidget.find('.template tr'));
}else { } else {
search.messages.show('nothing-found') search.messages.show('nothing-found');
} }
search.messages.hide('fetching') search.messages.hide('fetching');
$('#search-progress').hide() $('#search-progress').hide();
search.searching = false search.searching = false;
}); });
socket.on('results:installed', function (data) { socket.on('results:installed', (data) => {
installed.messages.hide("fetching") installed.messages.hide('fetching');
installed.messages.hide("nothing-installed") installed.messages.hide('nothing-installed');
installed.list = data.installed installed.list = data.installed;
sortPluginList(installed.list, 'name', /*ASC?*/true); sortPluginList(installed.list, 'name', /* ASC?*/true);
// filter out epl // filter out epl
installed.list = installed.list.filter(function(plugin) { installed.list = installed.list.filter((plugin) => plugin.name != 'ep_etherpad-lite');
return plugin.name != 'ep_etherpad-lite'
})
// remove all installed plugins (leave plugins that are still being installed) // remove all installed plugins (leave plugins that are still being installed)
installed.list.forEach(function(plugin) { installed.list.forEach((plugin) => {
$('#installed-plugins .'+plugin.name).remove() $(`#installed-plugins .${plugin.name}`).remove();
}) });
if(installed.list.length > 0) { if (installed.list.length > 0) {
displayPluginList(installed.list, $("#installed-plugins"), $("#installed-plugin-template")); displayPluginList(installed.list, $('#installed-plugins'), $('#installed-plugin-template'));
socket.emit('checkUpdates'); socket.emit('checkUpdates');
}else { } else {
installed.messages.show("nothing-installed") installed.messages.show('nothing-installed');
} }
}); });
socket.on('results:updatable', function(data) { socket.on('results:updatable', (data) => {
data.updatable.forEach(function(pluginName) { data.updatable.forEach((pluginName) => {
var $row = $('#installed-plugins > tr.'+pluginName) const $row = $(`#installed-plugins > tr.${pluginName}`);
, actions = $row.find('.actions') const actions = $row.find('.actions');
actions.append('<input class="do-update" type="button" value="Update" />') actions.append('<input class="do-update" type="button" value="Update" />');
}) });
updateHandlers(); updateHandlers();
}) });
socket.on('finished:install', function(data) { socket.on('finished:install', (data) => {
if(data.error) { if (data.error) {
if(data.code === "EPEERINVALID"){ if (data.code === 'EPEERINVALID') {
alert("This plugin requires that you update Etherpad so it can operate in it's true glory"); alert("This plugin requires that you update Etherpad so it can operate in it's true glory");
} }
alert('An error occurred while installing '+data.plugin+' \n'+data.error) alert(`An error occurred while installing ${data.plugin} \n${data.error}`);
$('#installed-plugins .'+data.plugin).remove() $(`#installed-plugins .${data.plugin}`).remove();
} }
socket.emit("getInstalled"); socket.emit('getInstalled');
// update search results // update search results
search.offset = 0; search.offset = 0;
search(search.searchTerm, search.results.length); search(search.searchTerm, search.results.length);
search.results = []; search.results = [];
}) });
socket.on('finished:uninstall', function(data) { socket.on('finished:uninstall', (data) => {
if(data.error) alert('An error occurred while uninstalling the '+data.plugin+' \n'+data.error) if (data.error) alert(`An error occurred while uninstalling the ${data.plugin} \n${data.error}`);
// remove plugin from installed list // remove plugin from installed list
$('#installed-plugins .'+data.plugin).remove() $(`#installed-plugins .${data.plugin}`).remove();
socket.emit("getInstalled"); socket.emit('getInstalled');
// update search results // update search results
search.offset = 0; search.offset = 0;
search(search.searchTerm, search.results.length); search(search.searchTerm, search.results.length);
search.results = []; search.results = [];
}) });
// init // init
updateHandlers(); updateHandlers();
socket.emit("getInstalled"); socket.emit('getInstalled');
search(''); search('');
// check for updates every 5mins // check for updates every 5mins
setInterval(function() { setInterval(() => {
socket.emit('checkUpdates'); socket.emit('checkUpdates');
}, 1000*60*5) }, 1000 * 60 * 5);
}); });

View file

@ -1,81 +1,75 @@
$(document).ready(function () { $(document).ready(() => {
var socket, let socket;
loc = document.location, const loc = document.location;
port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port, const port = loc.port == '' ? (loc.protocol == 'https:' ? 443 : 80) : loc.port;
url = loc.protocol + "//" + loc.hostname + ":" + port + "/", const url = `${loc.protocol}//${loc.hostname}:${port}/`;
pathComponents = location.pathname.split('/'), const pathComponents = location.pathname.split('/');
// Strip admin/plugins // Strip admin/plugins
baseURL = pathComponents.slice(0,pathComponents.length-2).join('/') + '/', const baseURL = `${pathComponents.slice(0, pathComponents.length - 2).join('/')}/`;
resource = baseURL.substring(1) + "socket.io"; const resource = `${baseURL.substring(1)}socket.io`;
//connect // connect
var room = url + "settings"; const room = `${url}settings`;
socket = io.connect(room, {path: baseURL + "socket.io", resource : resource}); socket = io.connect(room, {path: `${baseURL}socket.io`, resource});
socket.on('settings', function (settings) {
socket.on('settings', (settings) => {
/* Check whether the settings.json is authorized to be viewed */ /* Check whether the settings.json is authorized to be viewed */
if(settings.results === 'NOT_ALLOWED') { if (settings.results === 'NOT_ALLOWED') {
$('.innerwrapper').hide(); $('.innerwrapper').hide();
$('.innerwrapper-err').show(); $('.innerwrapper-err').show();
$('.err-message').html("Settings json is not authorized to be viewed in Admin page!!"); $('.err-message').html('Settings json is not authorized to be viewed in Admin page!!');
return; return;
} }
/* Check to make sure the JSON is clean before proceeding */ /* Check to make sure the JSON is clean before proceeding */
if(isJSONClean(settings.results)) if (isJSONClean(settings.results)) {
{
$('.settings').append(settings.results); $('.settings').append(settings.results);
$('.settings').focus(); $('.settings').focus();
$('.settings').autosize(); $('.settings').autosize();
} } else {
else{ alert('YOUR JSON IS BAD AND YOU SHOULD FEEL BAD');
alert("YOUR JSON IS BAD AND YOU SHOULD FEEL BAD");
} }
}); });
/* When the admin clicks save Settings check the JSON then send the JSON back to the server */ /* When the admin clicks save Settings check the JSON then send the JSON back to the server */
$('#saveSettings').on('click', function(){ $('#saveSettings').on('click', () => {
var editedSettings = $('.settings').val(); const editedSettings = $('.settings').val();
if(isJSONClean(editedSettings)){ if (isJSONClean(editedSettings)) {
// JSON is clean so emit it to the server // JSON is clean so emit it to the server
socket.emit("saveSettings", $('.settings').val()); socket.emit('saveSettings', $('.settings').val());
}else{ } else {
alert("YOUR JSON IS BAD AND YOU SHOULD FEEL BAD") alert('YOUR JSON IS BAD AND YOU SHOULD FEEL BAD');
$('.settings').focus(); $('.settings').focus();
} }
}); });
/* Tell Etherpad Server to restart */ /* Tell Etherpad Server to restart */
$('#restartEtherpad').on('click', function(){ $('#restartEtherpad').on('click', () => {
socket.emit("restartServer"); socket.emit('restartServer');
}); });
socket.on('saveprogress', function(progress){ socket.on('saveprogress', (progress) => {
$('#response').show(); $('#response').show();
$('#response').text(progress); $('#response').text(progress);
$('#response').fadeOut('slow'); $('#response').fadeOut('slow');
}); });
socket.emit("load"); // Load the JSON from the server socket.emit('load'); // Load the JSON from the server
}); });
function isJSONClean(data){ function isJSONClean(data) {
var cleanSettings = JSON.minify(data); let cleanSettings = JSON.minify(data);
// this is a bit naive. In theory some key/value might contain the sequences ',]' or ',}' // this is a bit naive. In theory some key/value might contain the sequences ',]' or ',}'
cleanSettings = cleanSettings.replace(",]","]").replace(",}","}"); cleanSettings = cleanSettings.replace(',]', ']').replace(',}', '}');
try{ try {
var response = jQuery.parseJSON(cleanSettings); var response = jQuery.parseJSON(cleanSettings);
} } catch (e) {
catch(e){
return false; // the JSON failed to be parsed return false; // the JSON failed to be parsed
} }
if(typeof response !== 'object'){ if (typeof response !== 'object') {
return false; return false;
}else{ } else {
return true; return true;
} }
} }

View file

@ -20,32 +20,30 @@
* limitations under the License. * limitations under the License.
*/ */
var makeCSSManager = require('./cssmanager').makeCSSManager; const makeCSSManager = require('./cssmanager').makeCSSManager;
var domline = require('./domline').domline; const domline = require('./domline').domline;
var AttribPool = require('./AttributePool'); const AttribPool = require('./AttributePool');
var Changeset = require('./Changeset'); const Changeset = require('./Changeset');
var linestylefilter = require('./linestylefilter').linestylefilter; const linestylefilter = require('./linestylefilter').linestylefilter;
var colorutils = require('./colorutils').colorutils; const colorutils = require('./colorutils').colorutils;
var _ = require('./underscore'); const _ = require('./underscore');
var hooks = require('./pluginfw/hooks'); 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) {
var 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 // 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) if (!Array.prototype.indexOf) {
{ Array.prototype.indexOf = function (elt /* , from*/) {
Array.prototype.indexOf = function(elt /*, from*/ ) { const len = this.length >>> 0;
var len = this.length >>> 0;
var from = Number(arguments[1]) || 0; let from = Number(arguments[1]) || 0;
from = (from < 0) ? Math.ceil(from) : Math.floor(from); from = (from < 0) ? Math.ceil(from) : Math.floor(from);
if (from < 0) from += len; if (from < 0) from += len;
for (; from < len; from++) for (; from < len; from++) {
{
if (from in this && this[from] === elt) return from; if (from in this && this[from] === elt) return from;
} }
return -1; return -1;
@ -53,22 +51,19 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
} }
function debugLog() { function debugLog() {
try try {
{
if (window.console) console.log.apply(console, arguments); if (window.console) console.log.apply(console, arguments);
} } catch (e) {
catch (e) if (window.console) console.log('error printing: ', e);
{
if (window.console) console.log("error printing: ", e);
} }
} }
//var socket; // var socket;
var channelState = "DISCONNECTED"; const channelState = 'DISCONNECTED';
var appLevelDisconnectReason = null; const appLevelDisconnectReason = null;
var 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),
@ -76,13 +71,13 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
// 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: function(line, aline) { lineToElement(line, aline) {
var element = document.createElement("div"); const element = document.createElement('div');
var emptyLine = (line == '\n'); const emptyLine = (line == '\n');
var 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();
element.className = domInfo.node.className; element.className = domInfo.node.className;
@ -91,35 +86,29 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
return $(element); return $(element);
}, },
applySpliceToDivs: function(start, numRemoved, newLines) { applySpliceToDivs(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 (var i = start; i < start + numRemoved && i < this.currentDivs.length; i++) {
{
this.currentDivs[i].remove(); this.currentDivs[i].remove();
} }
// remove spliced-out line divs from currentDivs array // remove spliced-out line divs from currentDivs array
this.currentDivs.splice(start, numRemoved); this.currentDivs.splice(start, numRemoved);
var newDivs = []; const newDivs = [];
for (var i = 0; i < newLines.length; i++) for (var 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]));
} }
// grab the div just before the first one // grab the div just before the first one
var 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 (var i = 0; i < newDivs.length; i++) {
{ if (startDiv) {
if (startDiv)
{
startDiv.after(newDivs[i]); startDiv.after(newDivs[i]);
} } else {
else $('#innerdocbody').prepend(newDivs[i]);
{
$("#innerdocbody").prepend(newDivs[i]);
} }
startDiv = newDivs[i]; startDiv = newDivs[i];
} }
@ -132,10 +121,8 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
}, },
// splice the lines // splice the lines
splice: function(start, numRemoved, newLinesVA) { splice(start, numRemoved, newLinesVA) {
var newLines = _.map(Array.prototype.slice.call(arguments, 2), function(s) { const newLines = _.map(Array.prototype.slice.call(arguments, 2), (s) => s);
return s;
});
// apply this splice to the divs // apply this splice to the divs
this.applySpliceToDivs(start, numRemoved, newLines); this.applySpliceToDivs(start, numRemoved, newLines);
@ -146,30 +133,26 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
this.currentLines.splice.apply(this.currentLines, arguments); this.currentLines.splice.apply(this.currentLines, arguments);
}, },
// returns the contents of the specified line I // returns the contents of the specified line I
get: function(i) { get(i) {
return this.currentLines[i]; return this.currentLines[i];
}, },
// returns the number of lines in the document // returns the number of lines in the document
length: function() { length() {
return this.currentLines.length; return this.currentLines.length;
}, },
getActiveAuthors: function() { getActiveAuthors() {
var self = this; const self = this;
var authors = []; const authors = [];
var seenNums = {}; const seenNums = {};
var alines = self.alines; const alines = self.alines;
for (var i = 0; i < alines.length; i++) for (let i = 0; i < alines.length; i++) {
{ Changeset.eachAttribNumber(alines[i], (n) => {
Changeset.eachAttribNumber(alines[i], function(n) { if (!seenNums[n]) {
if (!seenNums[n])
{
seenNums[n] = true; seenNums[n] = true;
if (self.apool.getAttribKey(n) == 'author') if (self.apool.getAttribKey(n) == 'author') {
{ const a = self.apool.getAttribValue(n);
var a = self.apool.getAttribValue(n); if (a) {
if (a)
{
authors.push(a); authors.push(a);
} }
} }
@ -178,27 +161,21 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
} }
authors.sort(); authors.sort();
return authors; return authors;
} },
}; };
function callCatchingErrors(catcher, func) { function callCatchingErrors(catcher, func) {
try try {
{
wrapRecordingErrors(catcher, func)(); wrapRecordingErrors(catcher, func)();
} } catch (e) { /* absorb*/
catch (e)
{ /*absorb*/
} }
} }
function wrapRecordingErrors(catcher, func) { function wrapRecordingErrors(catcher, func) {
return function() { return function () {
try try {
{
return func.apply(this, Array.prototype.slice.call(arguments)); return func.apply(this, Array.prototype.slice.call(arguments));
} } catch (e) {
catch (e)
{
// caughtErrors.push(e); // caughtErrors.push(e);
// caughtErrorCatchers.push(catcher); // caughtErrorCatchers.push(catcher);
// caughtErrorTimes.push(+new Date()); // caughtErrorTimes.push(+new Date());
@ -210,13 +187,13 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
} }
function loadedNewChangeset(changesetForward, changesetBackward, revision, timeDelta) { function loadedNewChangeset(changesetForward, changesetBackward, revision, timeDelta) {
var broadcasting = (BroadcastSlider.getSliderPosition() == revisionInfo.latest); const broadcasting = (BroadcastSlider.getSliderPosition() == revisionInfo.latest);
revisionInfo.addChangeset(revision, revision + 1, changesetForward, changesetBackward, timeDelta); revisionInfo.addChangeset(revision, revision + 1, changesetForward, changesetBackward, timeDelta);
BroadcastSlider.setSliderLength(revisionInfo.latest); BroadcastSlider.setSliderLength(revisionInfo.latest);
if (broadcasting) applyChangeset(changesetForward, revision + 1, false, timeDelta); if (broadcasting) applyChangeset(changesetForward, revision + 1, false, timeDelta);
} }
/* /*
At this point, we must be certain that the changeset really does map from 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 the current revision to the specified revision. Any mistakes here will
cause the whole slider to get out of sync. cause the whole slider to get out of sync.
@ -224,38 +201,34 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
function applyChangeset(changeset, revision, preventSliderMovement, timeDelta) { 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++;
BroadcastSlider.setSliderPosition(revision); BroadcastSlider.setSliderPosition(revision);
} }
let oldAlines = padContents.alines.slice(); const oldAlines = padContents.alines.slice();
try try {
{
// must mutate attribution lines before text lines // must mutate attribution lines before text lines
Changeset.mutateAttributionLines(changeset, padContents.alines, padContents.apool); Changeset.mutateAttributionLines(changeset, padContents.alines, padContents.apool);
} } catch (e) {
catch (e)
{
debugLog(e); debugLog(e);
} }
// 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
// the bottom of a pad // the bottom of a pad
let lineChanged; let lineChanged;
_.some(oldAlines, function(line, index){ _.some(oldAlines, (line, index) => {
if(line !== padContents.alines[index]){ if (line !== padContents.alines[index]) {
lineChanged = index; lineChanged = index;
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;
} }
@ -268,106 +241,94 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
updateTimer(); updateTimer();
var authors = _.map(padContents.getActiveAuthors(), function(name) { const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
return authorData[name];
});
BroadcastSlider.setAuthors(authors); BroadcastSlider.setAuthors(authors);
} }
function updateTimer() { function updateTimer() {
var zpad = function(str, length) { const zpad = function (str, length) {
str = str + ""; str = `${str}`;
while (str.length < length) while (str.length < length) str = `0${str}`;
str = '0' + str; return 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
}));
}
const date = new Date(padContents.currentTime);
const dateFormat = function () {
const month = zpad(date.getMonth() + 1, 2);
const day = zpad(date.getDate(), 2);
const year = (date.getFullYear());
const hours = zpad(date.getHours(), 2);
const minutes = zpad(date.getMinutes(), 2);
const seconds = zpad(date.getSeconds(), 2);
return (html10n.get('timeslider.dateformat', {
day,
month,
year,
hours,
minutes,
seconds,
}));
};
$('#timer').html(dateFormat()); $('#timer').html(dateFormat());
var revisionDate = html10n.get("timeslider.saved", { const revisionDate = html10n.get('timeslider.saved', {
"day": date.getDate(), day: date.getDate(),
"month": [ month: [
html10n.get("timeslider.month.january"), html10n.get('timeslider.month.january'),
html10n.get("timeslider.month.february"), html10n.get('timeslider.month.february'),
html10n.get("timeslider.month.march"), html10n.get('timeslider.month.march'),
html10n.get("timeslider.month.april"), html10n.get('timeslider.month.april'),
html10n.get("timeslider.month.may"), html10n.get('timeslider.month.may'),
html10n.get("timeslider.month.june"), html10n.get('timeslider.month.june'),
html10n.get("timeslider.month.july"), html10n.get('timeslider.month.july'),
html10n.get("timeslider.month.august"), html10n.get('timeslider.month.august'),
html10n.get("timeslider.month.september"), html10n.get('timeslider.month.september'),
html10n.get("timeslider.month.october"), html10n.get('timeslider.month.october'),
html10n.get("timeslider.month.november"), html10n.get('timeslider.month.november'),
html10n.get("timeslider.month.december") html10n.get('timeslider.month.december'),
][date.getMonth()], ][date.getMonth()],
"year": date.getFullYear() year: date.getFullYear(),
}); });
$('#revision_date').html(revisionDate) $('#revision_date').html(revisionDate);
} }
updateTimer(); updateTimer();
function goToRevision(newRevision) { function goToRevision(newRevision) {
padContents.targetRevision = newRevision; padContents.targetRevision = newRevision;
var self = this; const self = this;
var path = 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; var cs = path.changesets;
var changeset = cs[0]; var changeset = cs[0];
var timeDelta = path.times[0]; var timeDelta = path.times[0];
for (var i = 1; i < cs.length; i++) for (var 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;
{
var 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
var update = function(start, end) { const update = function (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; var cs = path.changesets;
var changeset = cs[0]; var changeset = cs[0];
var timeDelta = path.times[0]; var timeDelta = path.times[0];
for (var i = 1; i < cs.length; i++) for (var 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];
} }
@ -379,21 +340,17 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
loadChangesetsForRevision(padContents.currentRevision - 1); loadChangesetsForRevision(padContents.currentRevision - 1);
} }
var authors = _.map(padContents.getActiveAuthors(), function(name){ const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
return authorData[name];
});
BroadcastSlider.setAuthors(authors); BroadcastSlider.setAuthors(authors);
} }
function loadChangesetsForRevision(revision, callback) { function loadChangesetsForRevision(revision, callback) {
if (BroadcastSlider.getSliderLength() > 10000) if (BroadcastSlider.getSliderLength() > 10000) {
{
var start = (Math.floor((revision) / 10000) * 10000); // revision 0 to 10 var 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 var start = (Math.floor((revision) / 1000) * 1000); // (start from -1, go to 19) + 1
changesetLoader.queueUp(start, 10); changesetLoader.queueUp(start, 10);
} }
@ -410,167 +367,146 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
requestQueue2: [], requestQueue2: [],
requestQueue3: [], requestQueue3: [],
reqCallbacks: [], reqCallbacks: [],
queueUp: function(revision, width, callback) { queueUp(revision, width, callback) {
if (revision < 0) revision = 0; if (revision < 0) revision = 0;
// if(changesetLoader.requestQueue.indexOf(revision) != -1) // if(changesetLoader.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 (changesetLoader.resolved.indexOf(`${revision}_${width}`) != -1) return; // already loaded from the server
changesetLoader.resolved.push(revision + "_" + width); changesetLoader.resolved.push(`${revision}_${width}`);
var requestQueue = width == 1 ? changesetLoader.requestQueue3 : width == 10 ? changesetLoader.requestQueue2 : changesetLoader.requestQueue1; const requestQueue = width == 1 ? changesetLoader.requestQueue3 : width == 10 ? changesetLoader.requestQueue2 : changesetLoader.requestQueue1;
requestQueue.push( requestQueue.push(
{ {
'rev': revision, rev: revision,
'res': width, res: width,
'callback': callback callback,
}); });
if (!changesetLoader.running) if (!changesetLoader.running) {
{
changesetLoader.running = true; changesetLoader.running = true;
setTimeout(changesetLoader.loadFromQueue, 10); setTimeout(changesetLoader.loadFromQueue, 10);
} }
}, },
loadFromQueue: function() { loadFromQueue() {
var self = changesetLoader; const self = changesetLoader;
var requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : self.requestQueue2.length > 0 ? self.requestQueue2 : self.requestQueue3.length > 0 ? self.requestQueue3 : null; const requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : self.requestQueue2.length > 0 ? self.requestQueue2 : self.requestQueue3.length > 0 ? self.requestQueue3 : null;
if (!requestQueue) if (!requestQueue) {
{
self.running = false; self.running = false;
return; return;
} }
var request = requestQueue.pop(); const request = requestQueue.pop();
var granularity = request.res; const granularity = request.res;
var callback = request.callback; const callback = request.callback;
var start = request.rev; const start = request.rev;
var requestID = Math.floor(Math.random() * 100000); const requestID = Math.floor(Math.random() * 100000);
sendSocketMsg("CHANGESET_REQ", { sendSocketMsg('CHANGESET_REQ', {
"start": start, start,
"granularity": granularity, granularity,
"requestID": requestID requestID,
}); });
self.reqCallbacks[requestID] = callback; self.reqCallbacks[requestID] = callback;
}, },
handleSocketResponse: function(message) { handleSocketResponse(message) {
var self = changesetLoader; const self = changesetLoader;
var start = message.data.start; const start = message.data.start;
var granularity = message.data.granularity; const granularity = message.data.granularity;
var callback = self.reqCallbacks[message.data.requestID]; const callback = self.reqCallbacks[message.data.requestID];
delete self.reqCallbacks[message.data.requestID]; delete self.reqCallbacks[message.data.requestID];
self.handleResponse(message.data, start, granularity, callback); self.handleResponse(message.data, start, granularity, callback);
setTimeout(self.loadFromQueue, 10); setTimeout(self.loadFromQueue, 10);
}, },
handleResponse: function(data, start, granularity, callback) { handleResponse(data, start, granularity, callback) {
var pool = (new AttribPool()).fromJsonable(data.apool); const pool = (new AttribPool()).fromJsonable(data.apool);
for (var 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
var 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
var 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);
var forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool); const forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool);
var backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool); const backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool);
revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]); 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: function (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(
var 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( var changesetBack = Changeset.inverse(
obj.changeset, padContents.currentLines, padContents.alines, padContents.apool); obj.changeset, padContents.currentLines, padContents.alines, padContents.apool);
var changesetBack = Changeset.moveOpsToNewPool( var 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 = {};
{
var authorMap = {};
authorMap[obj.author] = obj.data; authorMap[obj.author] = obj.data;
receiveAuthorData(authorMap); receiveAuthorData(authorMap);
var authors = _.map(padContents.getActiveAuthors(), function(name) { const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
return authorData[name];
});
BroadcastSlider.setAuthors(authors); BroadcastSlider.setAuthors(authors);
} } else if (obj.type == 'NEW_SAVEDREV') {
else if (obj.type == "NEW_SAVEDREV") const savedRev = obj.savedRev;
{
var 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); changesetLoader.handleSocketResponse(obj);
} else {
debugLog(`Unknown message type: ${obj.type}`);
} }
else },
{
debugLog("Unknown message type: " + obj.type);
}
}
}; };
// to start upon window load, just push a function onto this array // to start upon window load, just push a function onto this array
//window['onloadFuncts'].push(setUpSocket); // window['onloadFuncts'].push(setUpSocket);
//window['onloadFuncts'].push(function () // window['onloadFuncts'].push(function ()
fireWhenAllScriptsAreLoaded.push(function() { fireWhenAllScriptsAreLoaded.push(() => {
// set up the currentDivs and DOM // set up the currentDivs and DOM
padContents.currentDivs = []; padContents.currentDivs = [];
$("#innerdocbody").html(""); $('#innerdocbody').html('');
for (var i = 0; i < padContents.currentLines.length; i++) for (let i = 0; i < padContents.currentLines.length; i++) {
{ const div = padContents.lineToElement(padContents.currentLines[i], padContents.alines[i]);
var div = padContents.lineToElement(padContents.currentLines[i], padContents.alines[i]);
padContents.currentDivs.push(div); padContents.currentDivs.push(div);
$("#innerdocbody").append(div); $('#innerdocbody').append(div);
} }
}); });
// 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; var goToRevisionIfEnabledCount = 0;
var goToRevisionIfEnabled = function() { const goToRevisionIfEnabled = function () {
if (goToRevisionIfEnabledCount > 0) if (goToRevisionIfEnabledCount > 0) {
{
goToRevisionIfEnabledCount--; goToRevisionIfEnabledCount--;
} } else {
else
{
goToRevision.apply(goToRevision, arguments); goToRevision.apply(goToRevision, arguments);
} }
} };
BroadcastSlider.onSlider(goToRevisionIfEnabled); BroadcastSlider.onSlider(goToRevisionIfEnabled);
var dynamicCSS = makeCSSManager('dynamicsyntax'); const dynamicCSS = makeCSSManager('dynamicsyntax');
var authorData = {}; var authorData = {};
function receiveAuthorData(newAuthorData) { function receiveAuthorData(newAuthorData) {
for (var author in newAuthorData) for (const author in newAuthorData) {
{ const data = newAuthorData[author];
var data = newAuthorData[author]; const bgcolor = typeof data.colorId === 'number' ? clientVars.colorPalette[data.colorId] : data.colorId;
var bgcolor = typeof data.colorId == "number" ? clientVars.colorPalette[data.colorId] : data.colorId; if (bgcolor && dynamicCSS) {
if (bgcolor && dynamicCSS) const selector = dynamicCSS.selectorStyle(`.${linestylefilter.getAuthorClassName(author)}`);
{ selector.backgroundColor = bgcolor;
var selector = dynamicCSS.selectorStyle('.' + linestylefilter.getAuthorClassName(author)); selector.color = (colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5) ? '#ffffff' : '#000000'; // see ace2_inner.js for the other part
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;
} }
@ -580,15 +516,15 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
return changesetLoader; return changesetLoader;
function goToLineNumber(lineNumber){ function goToLineNumber(lineNumber) {
// Sets the Y scrolling of the browser to go to this line // Sets the Y scrolling of the browser to go to this line
var line = $('#innerdocbody').find("div:nth-child("+(lineNumber+1)+")"); const line = $('#innerdocbody').find(`div:nth-child(${lineNumber + 1})`);
var newY = $(line)[0].offsetTop; const newY = $(line)[0].offsetTop;
var ecb = document.getElementById('editorcontainerbox'); const ecb = document.getElementById('editorcontainerbox');
// Chrome 55 - 59 bugfix // Chrome 55 - 59 bugfix
if(ecb.scrollTo){ if (ecb.scrollTo) {
ecb.scrollTo({top: newY, behavior: 'smooth'}); ecb.scrollTo({top: newY, behavior: 'smooth'});
}else{ } else {
$('#editorcontainerbox').scrollTop(newY); $('#editorcontainerbox').scrollTop(newY);
} }
} }

View file

@ -29,68 +29,60 @@ function loadBroadcastRevisionsJS() {
this.changesets = []; this.changesets = [];
} }
Revision.prototype.addChangeset = function(destIndex, changeset, timeDelta) { Revision.prototype.addChangeset = function (destIndex, changeset, timeDelta) {
var changesetWrapper = { const changesetWrapper = {
deltaRev: destIndex - this.rev, deltaRev: destIndex - this.rev,
deltaTime: timeDelta, deltaTime: timeDelta,
getValue: function() { getValue() {
return changeset; return changeset;
} },
}; };
this.changesets.push(changesetWrapper); this.changesets.push(changesetWrapper);
this.changesets.sort(function(a, b) { this.changesets.sort((a, b) => (b.deltaRev - a.deltaRev));
return (b.deltaRev - a.deltaRev) };
});
}
revisionInfo = {}; revisionInfo = {};
revisionInfo.addChangeset = function(fromIndex, toIndex, changeset, backChangeset, timeDelta) { revisionInfo.addChangeset = function (fromIndex, toIndex, changeset, backChangeset, timeDelta) {
var startRevision = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex); const startRevision = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
var endRevision = revisionInfo[toIndex] || revisionInfo.createNew(toIndex); const endRevision = revisionInfo[toIndex] || revisionInfo.createNew(toIndex);
startRevision.addChangeset(toIndex, changeset, timeDelta); startRevision.addChangeset(toIndex, changeset, timeDelta);
endRevision.addChangeset(fromIndex, backChangeset, -1 * timeDelta); endRevision.addChangeset(fromIndex, backChangeset, -1 * timeDelta);
} };
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); revisionInfo[index] = new Revision(index);
if (index > revisionInfo.latest) if (index > revisionInfo.latest) {
{
revisionInfo.latest = index; revisionInfo.latest = index;
} }
return revisionInfo[index]; return revisionInfo[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
// are laid out in a skip-list format // are laid out in a skip-list format
revisionInfo.getPath = function(fromIndex, toIndex) { revisionInfo.getPath = function (fromIndex, toIndex) {
var changesets = []; const changesets = [];
var spans = []; const spans = [];
var times = []; const times = [];
var elem = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex); let elem = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
if (elem.changesets.length != 0 && fromIndex != toIndex) if (elem.changesets.length != 0 && fromIndex != toIndex) {
{ const reverse = !(fromIndex < toIndex);
var reverse = !(fromIndex < toIndex) while (((elem.rev < toIndex) && !reverse) || ((elem.rev > toIndex) && reverse)) {
while (((elem.rev < toIndex) && !reverse) || ((elem.rev > toIndex) && reverse)) let couldNotContinue = false;
{ const oldRev = elem.rev;
var couldNotContinue = false;
var oldRev = elem.rev;
for (var 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];
var 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);
@ -103,18 +95,18 @@ function loadBroadcastRevisionsJS() {
} }
} }
var status = 'partial'; let status = 'partial';
if (elem.rev == toIndex) status = 'complete'; if (elem.rev == toIndex) status = 'complete';
return { return {
'fromRev': fromIndex, fromRev: fromIndex,
'rev': elem.rev, rev: elem.rev,
'status': status, status,
'changesets': changesets, changesets,
'spans': spans, spans,
'times': times times,
}; };
} };
} }
exports.loadBroadcastRevisionsJS = loadBroadcastRevisionsJS; exports.loadBroadcastRevisionsJS = loadBroadcastRevisionsJS;

View file

@ -20,62 +20,60 @@
* limitations under the License. * limitations under the License.
*/ */
// 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.
var _ = require('./underscore'); const _ = require('./underscore');
var padmodals = require('./pad_modals').padmodals; const padmodals = require('./pad_modals').padmodals;
var colorutils = require('./colorutils').colorutils; const colorutils = require('./colorutils').colorutils;
function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) { function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
var 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 (function () { // wrap this code in its own namespace
var sliderLength = 1000; let sliderLength = 1000;
var sliderPos = 0; let sliderPos = 0;
var sliderActive = false; let sliderActive = false;
var slidercallbacks = []; const slidercallbacks = [];
var savedRevisions = []; const savedRevisions = [];
var sliderPlaying = false; let sliderPlaying = false;
var _callSliderCallbacks = function(newval) { const _callSliderCallbacks = function (newval) {
sliderPos = newval; sliderPos = newval;
for (var i = 0; i < slidercallbacks.length; i++) for (let i = 0; i < slidercallbacks.length; i++) {
{ slidercallbacks[i](newval);
slidercallbacks[i](newval);
}
} }
};
var updateSliderElements = function() { const updateSliderElements = function () {
for (var i = 0; i < savedRevisions.length; i++) for (let i = 0; i < savedRevisions.length; i++) {
{ const position = parseInt(savedRevisions[i].attr('pos'));
var 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));
};
var addSavedRevision = function(position, info) { const addSavedRevision = function (position, info) {
var 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(function(evt) { newSavedRevision.mouseup((evt) => {
BroadcastSlider.setSliderPosition(position); BroadcastSlider.setSliderPosition(position);
}); });
savedRevisions.push(newSavedRevision); savedRevisions.push(newSavedRevision);
}; };
var removeSavedRevision = function(position) { const removeSavedRevision = function (position) {
var element = $("div.star [pos=" + position + "]"); const element = $(`div.star [pos=${position}]`);
savedRevisions.remove(element); savedRevisions.remove(element);
element.remove(); element.remove();
return element; return element;
}; };
/* Begin small 'API' */ /* Begin small 'API' */
@ -90,19 +88,19 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
function setSliderPosition(newpos) { function 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);
@ -120,89 +118,80 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
// just take over the whole slider screen with a reconnect message // just take over the whole slider screen with a reconnect message
function showReconnectUI() { function showReconnectUI() {
padmodals.showModal("disconnected"); padmodals.showModal('disconnected');
} }
function setAuthors(authors) { function setAuthors(authors) {
var authorsList = $("#authorsList"); const authorsList = $('#authorsList');
authorsList.empty(); authorsList.empty();
var numAnonymous = 0; let numAnonymous = 0;
var numNamed = 0; let numNamed = 0;
var colorsAnonymous = []; const colorsAnonymous = [];
_.each(authors, function(author) { _.each(authors, (author) => {
if(author) if (author) {
{ const authorColor = clientVars.colorPalette[author.colorId] || author.colorId;
var authorColor = clientVars.colorPalette[author.colorId] || author.colorId; if (author.name) {
if (author.name)
{
if (numNamed !== 0) authorsList.append(', '); if (numNamed !== 0) authorsList.append(', ');
var 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)
.css('color', textColor) .css('color', textColor)
.addClass('author') .addClass('author')
.appendTo(authorsList); .appendTo(authorsList);
numNamed++; numNamed++;
} } else {
else
{
numAnonymous++; numAnonymous++;
if(authorColor) colorsAnonymous.push(authorColor); if (authorColor) colorsAnonymous.push(authorColor);
} }
} }
}); });
if (numAnonymous > 0) if (numAnonymous > 0) {
{ const anonymousAuthorString = html10n.get('timeslider.unnamedauthors', {num: numAnonymous});
var anonymousAuthorString = html10n.get("timeslider.unnamedauthors", { num: numAnonymous });
if (numNamed !== 0){ if (numNamed !== 0) {
authorsList.append(' + ' + anonymousAuthorString); authorsList.append(` + ${anonymousAuthorString}`);
} else { } else {
authorsList.append(anonymousAuthorString); authorsList.append(anonymousAuthorString);
} }
if(colorsAnonymous.length > 0){ if (colorsAnonymous.length > 0) {
authorsList.append(' ('); authorsList.append(' (');
_.each(colorsAnonymous, function(color, i){ _.each(colorsAnonymous, (color, i) => {
if( i > 0 ) authorsList.append(' '); if (i > 0) authorsList.append(' ');
$('<span>&nbsp;</span>') $('<span>&nbsp;</span>')
.css('background-color', color) .css('background-color', color)
.addClass('author author-anonymous') .addClass('author author-anonymous')
.appendTo(authorsList); .appendTo(authorsList);
}); });
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 = { BroadcastSlider = {
onSlider: onSlider, onSlider,
getSliderPosition: getSliderPosition, getSliderPosition,
setSliderPosition: setSliderPosition, setSliderPosition,
getSliderLength: getSliderLength, getSliderLength,
setSliderLength: setSliderLength, setSliderLength,
isSliderActive: function() { isSliderActive() {
return sliderActive; return sliderActive;
}, },
playpause: playpause, playpause,
addSavedRevision: addSavedRevision, addSavedRevision,
showReconnectUI: showReconnectUI, showReconnectUI,
setAuthors: setAuthors setAuthors,
} };
function playButtonUpdater() { function playButtonUpdater() {
if (sliderPlaying) if (sliderPlaying) {
{ if (getSliderPosition() + 1 > sliderLength) {
if (getSliderPosition() + 1 > sliderLength) $('#playpause_button_icon').toggleClass('pause');
{
$("#playpause_button_icon").toggleClass('pause');
sliderPlaying = false; sliderPlaying = false;
return; return;
} }
@ -213,156 +202,142 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
} }
function playpause() { function 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;
} }
} }
// assign event handlers to html UI elements after page load // assign event handlers to html UI elements after page load
fireWhenAllScriptsAreLoaded.push(function() { fireWhenAllScriptsAreLoaded.push(() => {
$(document).keyup(function(e) { $(document).keyup((e) => {
if (!e) var e = window.event; if (!e) var e = window.event;
var code = e.keyCode || e.which; const code = e.keyCode || e.which;
if (code == 37) if (code == 37) { // left
{ // 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) $('#playpause_button_icon').trigger('click');
{ // spacebar
$("#playpause_button_icon").trigger('click');
} }
}); });
// Resize // Resize
$(window).resize(function() { $(window).resize(() => {
updateSliderElements(); updateSliderElements();
}); });
// Slider click // Slider click
$("#ui-slider-bar").mousedown(function(evt) { $('#ui-slider-bar').mousedown((evt) => {
$("#ui-slider-handle").css('left', (evt.clientX - $("#ui-slider-bar").offset().left)); $('#ui-slider-handle').css('left', (evt.clientX - $('#ui-slider-bar').offset().left));
$("#ui-slider-handle").trigger(evt); $('#ui-slider-handle').trigger(evt);
}); });
// Slider dragging // Slider dragging
$("#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'));
var self = this; const self = this;
sliderActive = true; sliderActive = true;
$(document).mousemove(function(evt2) { $(document).mousemove((evt2) => {
$(self).css('pointer', 'move') $(self).css('pointer', 'move');
var newloc = self.currentLoc + (evt2.clientX - self.startLoc); let newloc = self.currentLoc + (evt2.clientX - self.startLoc);
if (newloc < 0) newloc = 0; if (newloc < 0) newloc = 0;
if (newloc > ($("#ui-slider-bar").width() - 2)) newloc = ($("#ui-slider-bar").width() - 2); 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))})); $('#revision_label').html(html10n.get('timeslider.version', {version: Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2))}));
$(self).css('left', newloc); $(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))) if (getSliderPosition() != Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2))) _callSliderCallbacks(Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2)));
}); });
$(document).mouseup(function(evt2) { $(document).mouseup((evt2) => {
$(document).unbind('mousemove'); $(document).unbind('mousemove');
$(document).unbind('mouseup'); $(document).unbind('mouseup');
sliderActive = false; sliderActive = false;
var newloc = self.currentLoc + (evt2.clientX - self.startLoc); let newloc = self.currentLoc + (evt2.clientX - self.startLoc);
if (newloc < 0) newloc = 0; if (newloc < 0) newloc = 0;
if (newloc > ($("#ui-slider-bar").width() - 2)) newloc = ($("#ui-slider-bar").width() - 2); if (newloc > ($('#ui-slider-bar').width() - 2)) newloc = ($('#ui-slider-bar').width() - 2);
$(self).css('left', newloc); $(self).css('left', newloc);
// if(getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2))) // if(getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2)))
setSliderPosition(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){ if (parseInt($(self).css('left')) < 2) {
$(self).css('left', '2px'); $(self).css('left', '2px');
}else{ } else {
self.currentLoc = parseInt($(self).css('left')); self.currentLoc = parseInt($(self).css('left'));
} }
}); });
}) });
// play/pause toggling // play/pause toggling
$("#playpause_button_icon").click(function(evt) { $('#playpause_button_icon').click((evt) => {
BroadcastSlider.playpause(); BroadcastSlider.playpause();
}); });
// next/prev saved revision and changeset // next/prev saved revision and changeset
$('.stepper').click(function(evt) { $('.stepper').click(function (evt) {
switch ($(this).attr("id")) { switch ($(this).attr('id')) {
case "leftstep": case 'leftstep':
setSliderPosition(getSliderPosition() - 1); setSliderPosition(getSliderPosition() - 1);
break; break;
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 var nextStar = 0; // default to first revision in document
for (var i = 0; i < savedRevisions.length; i++) for (var i = 0; i < savedRevisions.length; i++) {
{
var pos = parseInt(savedRevisions[i].attr('pos')); var 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": case 'rightstar':
var nextStar = sliderLength; // default to last revision in document var nextStar = sliderLength; // default to last revision in document
for (var i = 0; i < savedRevisions.length; i++) for (var i = 0; i < savedRevisions.length; i++) {
{
var pos = parseInt(savedRevisions[i].attr('pos')); var 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();
var startPos = clientVars.collab_client_vars.rev; 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));
var hashRev = Number(window.location.hash.substr(1)); if (!isNaN(hashRev)) {
if(!isNaN(hashRev))
{
// this is necessary because of the socket.io-event which loads the changesets // this is necessary because of the socket.io-event which loads the changesets
setTimeout(function() { setSliderPosition(hashRev); }, 1); setTimeout(() => { setSliderPosition(hashRev); }, 1);
} }
} }
setSliderLength(clientVars.collab_client_vars.rev); setSliderLength(clientVars.collab_client_vars.rev);
setSliderPosition(clientVars.collab_client_vars.rev); setSliderPosition(clientVars.collab_client_vars.rev);
_.each(clientVars.savedRevisions, function(revision) { _.each(clientVars.savedRevisions, (revision) => {
addSavedRevision(revision.revNum, revision); addSavedRevision(revision.revNum, revision);
}) });
} }
}); });
})(); })();
BroadcastSlider.onSlider(function(loc) { BroadcastSlider.onSlider((loc) => {
$("#viewlatest").html(loc == BroadcastSlider.getSliderLength() ? "Viewing latest content" : "View latest content"); $('#viewlatest').html(loc == BroadcastSlider.getSliderLength() ? 'Viewing latest content' : 'View latest content');
}) });
return BroadcastSlider; return BroadcastSlider;
} }

View file

@ -2,39 +2,39 @@
// This function is useful to get the caret position of the line as // This function is useful to get the caret position of the line as
// is represented by the browser // is represented by the browser
exports.getPosition = function () { exports.getPosition = function () {
var rect, line; let rect, line;
var editor = $('#innerdocbody')[0]; const editor = $('#innerdocbody')[0];
var range = getSelectionRange(); const range = getSelectionRange();
var isSelectionInsideTheEditor = range && $(range.endContainer).closest('body')[0].id === 'innerdocbody'; const isSelectionInsideTheEditor = range && $(range.endContainer).closest('body')[0].id === 'innerdocbody';
if(isSelectionInsideTheEditor){ if (isSelectionInsideTheEditor) {
// when we have the caret in an empty line, e.g. a line with only a <br>, // when we have the caret in an empty line, e.g. a line with only a <br>,
// getBoundingClientRect() returns all dimensions value as 0 // getBoundingClientRect() returns all dimensions value as 0
var selectionIsInTheBeginningOfLine = range.endOffset > 0; const selectionIsInTheBeginningOfLine = range.endOffset > 0;
if (selectionIsInTheBeginningOfLine) { if (selectionIsInTheBeginningOfLine) {
var clonedRange = createSelectionRange(range); var clonedRange = createSelectionRange(range);
line = getPositionOfElementOrSelection(clonedRange); line = getPositionOfElementOrSelection(clonedRange);
clonedRange.detach() clonedRange.detach();
} }
// when there's a <br> or any element that has no height, we can't get // when there's a <br> or any element that has no height, we can't get
// the dimension of the element where the caret is // the dimension of the element where the caret is
if(!rect || rect.height === 0){ if (!rect || rect.height === 0) {
var clonedRange = createSelectionRange(range); var clonedRange = createSelectionRange(range);
// as we can't get the element height, we create a text node to get the dimensions // as we can't get the element height, we create a text node to get the dimensions
// on the position // on the position
var shadowCaret = $(document.createTextNode("|")); const shadowCaret = $(document.createTextNode('|'));
clonedRange.insertNode(shadowCaret[0]); clonedRange.insertNode(shadowCaret[0]);
clonedRange.selectNode(shadowCaret[0]); clonedRange.selectNode(shadowCaret[0]);
line = getPositionOfElementOrSelection(clonedRange); line = getPositionOfElementOrSelection(clonedRange);
clonedRange.detach() clonedRange.detach();
shadowCaret.remove(); shadowCaret.remove();
} }
} }
return line; return line;
} };
var createSelectionRange = function (range) { var createSelectionRange = function (range) {
clonedRange = range.cloneRange(); clonedRange = range.cloneRange();
@ -46,9 +46,9 @@ var createSelectionRange = function (range) {
clonedRange.setStart(range.endContainer, range.endOffset); clonedRange.setStart(range.endContainer, range.endOffset);
clonedRange.setEnd(range.endContainer, range.endOffset); clonedRange.setEnd(range.endContainer, range.endOffset);
return clonedRange; return clonedRange;
} };
var getPositionOfRepLineAtOffset = function (node, offset) { const getPositionOfRepLineAtOffset = function (node, offset) {
// it is not a text node, so we cannot make a selection // it is not a text node, so we cannot make a selection
if (node.tagName === 'BR' || node.tagName === 'EMPTY') { if (node.tagName === 'BR' || node.tagName === 'EMPTY') {
return getPositionOfElementOrSelection(node); return getPositionOfElementOrSelection(node);
@ -58,21 +58,21 @@ var getPositionOfRepLineAtOffset = function (node, offset) {
node = node.nextSibling; node = node.nextSibling;
} }
var newRange = new Range(); const newRange = new Range();
newRange.setStart(node, offset); newRange.setStart(node, offset);
newRange.setEnd(node, offset); newRange.setEnd(node, offset);
var linePosition = getPositionOfElementOrSelection(newRange); const linePosition = getPositionOfElementOrSelection(newRange);
newRange.detach(); // performance sake newRange.detach(); // performance sake
return linePosition; return linePosition;
} };
function getPositionOfElementOrSelection(element) { function getPositionOfElementOrSelection(element) {
var rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
var linePosition = { const linePosition = {
bottom: rect.bottom, bottom: rect.bottom,
height: rect.height, height: rect.height,
top: rect.top top: rect.top,
} };
return linePosition; return linePosition;
} }
@ -82,67 +82,66 @@ function getPositionOfElementOrSelection(element) {
// of the previous line // of the previous line
// [2] the line before is part of another rep line. It's possible this line has different margins // [2] the line before is part of another rep line. It's possible this line has different margins
// height. So we have to get the exactly position of the line // height. So we have to get the exactly position of the line
exports.getPositionTopOfPreviousBrowserLine = function(caretLinePosition, rep) { exports.getPositionTopOfPreviousBrowserLine = function (caretLinePosition, rep) {
var previousLineTop = caretLinePosition.top - caretLinePosition.height; // [1] let previousLineTop = caretLinePosition.top - caretLinePosition.height; // [1]
var isCaretLineFirstBrowserLine = caretLineIsFirstBrowserLine(caretLinePosition.top, rep); const isCaretLineFirstBrowserLine = caretLineIsFirstBrowserLine(caretLinePosition.top, rep);
// the caret is in the beginning of a rep line, so the previous browser line // the caret is in the beginning of a rep line, so the previous browser line
// is the last line browser line of the a rep line // is the last line browser line of the a rep line
if (isCaretLineFirstBrowserLine) { //[2] if (isCaretLineFirstBrowserLine) { // [2]
var lineBeforeCaretLine = rep.selStart[0] - 1; const lineBeforeCaretLine = rep.selStart[0] - 1;
var firstLineVisibleBeforeCaretLine = getPreviousVisibleLine(lineBeforeCaretLine, rep); const firstLineVisibleBeforeCaretLine = getPreviousVisibleLine(lineBeforeCaretLine, rep);
var linePosition = getDimensionOfLastBrowserLineOfRepLine(firstLineVisibleBeforeCaretLine, rep); const linePosition = getDimensionOfLastBrowserLineOfRepLine(firstLineVisibleBeforeCaretLine, rep);
previousLineTop = linePosition.top; previousLineTop = linePosition.top;
} }
return previousLineTop; return previousLineTop;
} };
function caretLineIsFirstBrowserLine(caretLineTop, rep) { function caretLineIsFirstBrowserLine(caretLineTop, rep) {
var caretRepLine = rep.selStart[0]; const caretRepLine = rep.selStart[0];
var lineNode = rep.lines.atIndex(caretRepLine).lineNode; const lineNode = rep.lines.atIndex(caretRepLine).lineNode;
var firstRootNode = getFirstRootChildNode(lineNode); const firstRootNode = getFirstRootChildNode(lineNode);
// to get the position of the node we get the position of the first char // to get the position of the node we get the position of the first char
var positionOfFirstRootNode = getPositionOfRepLineAtOffset(firstRootNode, 1); const positionOfFirstRootNode = getPositionOfRepLineAtOffset(firstRootNode, 1);
return positionOfFirstRootNode.top === caretLineTop; return positionOfFirstRootNode.top === caretLineTop;
} }
// find the first root node, usually it is a text node // find the first root node, usually it is a text node
function getFirstRootChildNode(node) { function getFirstRootChildNode(node) {
if(!node.firstChild){ if (!node.firstChild) {
return node; return node;
}else{ } else {
return getFirstRootChildNode(node.firstChild); return getFirstRootChildNode(node.firstChild);
} }
} }
function getPreviousVisibleLine(line, rep) { function getPreviousVisibleLine(line, rep) {
if (line < 0) { if (line < 0) {
return 0; return 0;
}else if (isLineVisible(line, rep)) { } else if (isLineVisible(line, rep)) {
return line; return line;
}else{ } else {
return getPreviousVisibleLine(line - 1, rep); return getPreviousVisibleLine(line - 1, rep);
} }
} }
function getDimensionOfLastBrowserLineOfRepLine(line, rep) { function getDimensionOfLastBrowserLineOfRepLine(line, rep) {
var lineNode = rep.lines.atIndex(line).lineNode; const lineNode = rep.lines.atIndex(line).lineNode;
var lastRootChildNode = getLastRootChildNode(lineNode); const lastRootChildNode = getLastRootChildNode(lineNode);
// we get the position of the line in the last char of it // we get the position of the line in the last char of it
var lastRootChildNodePosition = getPositionOfRepLineAtOffset(lastRootChildNode.node, lastRootChildNode.length); const lastRootChildNodePosition = getPositionOfRepLineAtOffset(lastRootChildNode.node, lastRootChildNode.length);
return lastRootChildNodePosition; return lastRootChildNodePosition;
} }
function getLastRootChildNode(node) { function getLastRootChildNode(node) {
if(!node.lastChild){ if (!node.lastChild) {
return { return {
node: node, node,
length: node.length length: node.length,
}; };
}else{ } else {
return getLastRootChildNode(node.lastChild); return getLastRootChildNode(node.lastChild);
} }
} }
@ -152,50 +151,50 @@ function getLastRootChildNode(node) {
// So, we can use the caret line to calculate the bottom of the line. // So, we can use the caret line to calculate the bottom of the line.
// [2] the next line is part of another rep line. It's possible this line has different dimensions, so we // [2] the next line is part of another rep line. It's possible this line has different dimensions, so we
// have to get the exactly dimension of it // have to get the exactly dimension of it
exports.getBottomOfNextBrowserLine = function(caretLinePosition, rep) { exports.getBottomOfNextBrowserLine = function (caretLinePosition, rep) {
var nextLineBottom = caretLinePosition.bottom + caretLinePosition.height; //[1] let nextLineBottom = caretLinePosition.bottom + caretLinePosition.height; // [1]
var isCaretLineLastBrowserLine = caretLineIsLastBrowserLineOfRepLine(caretLinePosition.top, rep); const isCaretLineLastBrowserLine = caretLineIsLastBrowserLineOfRepLine(caretLinePosition.top, rep);
// the caret is at the end of a rep line, so we can get the next browser line dimension // the caret is at the end of a rep line, so we can get the next browser line dimension
// using the position of the first char of the next rep line // using the position of the first char of the next rep line
if(isCaretLineLastBrowserLine){ //[2] if (isCaretLineLastBrowserLine) { // [2]
var nextLineAfterCaretLine = rep.selStart[0] + 1; const nextLineAfterCaretLine = rep.selStart[0] + 1;
var firstNextLineVisibleAfterCaretLine = getNextVisibleLine(nextLineAfterCaretLine, rep); const firstNextLineVisibleAfterCaretLine = getNextVisibleLine(nextLineAfterCaretLine, rep);
var linePosition = getDimensionOfFirstBrowserLineOfRepLine(firstNextLineVisibleAfterCaretLine, rep); const linePosition = getDimensionOfFirstBrowserLineOfRepLine(firstNextLineVisibleAfterCaretLine, rep);
nextLineBottom = linePosition.bottom; nextLineBottom = linePosition.bottom;
} }
return nextLineBottom; return nextLineBottom;
} };
function caretLineIsLastBrowserLineOfRepLine(caretLineTop, rep) { function caretLineIsLastBrowserLineOfRepLine(caretLineTop, rep) {
var caretRepLine = rep.selStart[0]; const caretRepLine = rep.selStart[0];
var lineNode = rep.lines.atIndex(caretRepLine).lineNode; const lineNode = rep.lines.atIndex(caretRepLine).lineNode;
var lastRootChildNode = getLastRootChildNode(lineNode); const lastRootChildNode = getLastRootChildNode(lineNode);
// we take a rep line and get the position of the last char of it // we take a rep line and get the position of the last char of it
var lastRootChildNodePosition = getPositionOfRepLineAtOffset(lastRootChildNode.node, lastRootChildNode.length); const lastRootChildNodePosition = getPositionOfRepLineAtOffset(lastRootChildNode.node, lastRootChildNode.length);
return lastRootChildNodePosition.top === caretLineTop; return lastRootChildNodePosition.top === caretLineTop;
} }
function getPreviousVisibleLine(line, rep) { function getPreviousVisibleLine(line, rep) {
var firstLineOfPad = 0; const firstLineOfPad = 0;
if (line <= firstLineOfPad) { if (line <= firstLineOfPad) {
return firstLineOfPad; return firstLineOfPad;
}else if (isLineVisible(line,rep)) { } else if (isLineVisible(line, rep)) {
return line; return line;
}else{ } else {
return getPreviousVisibleLine(line - 1, rep); return getPreviousVisibleLine(line - 1, rep);
} }
} }
exports.getPreviousVisibleLine = getPreviousVisibleLine; exports.getPreviousVisibleLine = getPreviousVisibleLine;
function getNextVisibleLine(line, rep) { function getNextVisibleLine(line, rep) {
var lastLineOfThePad = rep.lines.length() - 1; const lastLineOfThePad = rep.lines.length() - 1;
if (line >= lastLineOfThePad) { if (line >= lastLineOfThePad) {
return lastLineOfThePad; return lastLineOfThePad;
}else if (isLineVisible(line,rep)) { } else if (isLineVisible(line, rep)) {
return line; return line;
}else{ } else {
return getNextVisibleLine(line + 1, rep); return getNextVisibleLine(line + 1, rep);
} }
} }
@ -206,23 +205,23 @@ function isLineVisible(line, rep) {
} }
function getDimensionOfFirstBrowserLineOfRepLine(line, rep) { function getDimensionOfFirstBrowserLineOfRepLine(line, rep) {
var lineNode = rep.lines.atIndex(line).lineNode; const lineNode = rep.lines.atIndex(line).lineNode;
var firstRootChildNode = getFirstRootChildNode(lineNode); const firstRootChildNode = getFirstRootChildNode(lineNode);
// we can get the position of the line, getting the position of the first char of the rep line // we can get the position of the line, getting the position of the first char of the rep line
var firstRootChildNodePosition = getPositionOfRepLineAtOffset(firstRootChildNode, 1); const firstRootChildNodePosition = getPositionOfRepLineAtOffset(firstRootChildNode, 1);
return firstRootChildNodePosition; return firstRootChildNodePosition;
} }
function getSelectionRange() { function getSelectionRange() {
var selection; let selection;
if (!window.getSelection) { if (!window.getSelection) {
return; return;
} }
selection = window.getSelection(); selection = window.getSelection();
if (selection.rangeCount > 0) { if (selection.rangeCount > 0) {
return selection.getRangeAt(0); return selection.getRangeAt(0);
} else { } else {
return null; return null;
} }
} }

View file

@ -20,80 +20,70 @@
* limitations under the License. * limitations under the License.
*/ */
var AttributePool = require('./AttributePool'); const AttributePool = require('./AttributePool');
var Changeset = require('./Changeset'); const Changeset = require('./Changeset');
function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) { function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) {
// latest official text from server // latest official text from server
var baseAText = Changeset.makeAText("\n"); let baseAText = Changeset.makeAText('\n');
// changes applied to baseText that have been submitted // changes applied to baseText that have been submitted
var submittedChangeset = null; let submittedChangeset = null;
// changes applied to submittedChangeset since it was prepared // changes applied to submittedChangeset since it was prepared
var userChangeset = Changeset.identity(1); let userChangeset = Changeset.identity(1);
// is the changesetTracker enabled // is the changesetTracker enabled
var tracking = false; let tracking = false;
// stack state flag so that when we change the rep we don't // stack state flag so that when we change the rep we don't
// handle the notification recursively. When setting, always // handle the notification recursively. When setting, always
// unset in a "finally" block. When set to true, the setter // unset in a "finally" block. When set to true, the setter
// takes change of userChangeset. // takes change of userChangeset.
var applyingNonUserChanges = false; let applyingNonUserChanges = false;
var changeCallback = null; let changeCallback = null;
var changeCallbackTimeout = null; let changeCallbackTimeout = null;
function setChangeCallbackTimeout() { function setChangeCallbackTimeout() {
// can call this multiple times per call-stack, because // can call this multiple times per call-stack, because
// we only schedule a call to changeCallback if it exists // we only schedule a call to changeCallback if it exists
// and if there isn't a timeout already scheduled. // and if there isn't a timeout already scheduled.
if (changeCallback && changeCallbackTimeout === null) if (changeCallback && changeCallbackTimeout === null) {
{ changeCallbackTimeout = scheduler.setTimeout(() => {
changeCallbackTimeout = scheduler.setTimeout(function() { try {
try
{
changeCallback(); changeCallback();
} } catch (pseudoError) {} finally {
catch(pseudoError) {}
finally
{
changeCallbackTimeout = null; changeCallbackTimeout = null;
} }
}, 0); }, 0);
} }
} }
var self; let self;
return self = { return self = {
isTracking: function() { isTracking() {
return tracking; return tracking;
}, },
setBaseText: function(text) { setBaseText(text) {
self.setBaseAttributedText(Changeset.makeAText(text), null); self.setBaseAttributedText(Changeset.makeAText(text), null);
}, },
setBaseAttributedText: function(atext, apoolJsonObj) { setBaseAttributedText(atext, apoolJsonObj) {
aceCallbacksProvider.withCallbacks("setBaseText", function(callbacks) { aceCallbacksProvider.withCallbacks('setBaseText', (callbacks) => {
tracking = true; tracking = true;
baseAText = Changeset.cloneAText(atext); baseAText = Changeset.cloneAText(atext);
if (apoolJsonObj) if (apoolJsonObj) {
{ const wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
var wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
baseAText.attribs = Changeset.moveOpsToNewPool(baseAText.attribs, wireApool, apool); baseAText.attribs = Changeset.moveOpsToNewPool(baseAText.attribs, wireApool, apool);
} }
submittedChangeset = null; submittedChangeset = null;
userChangeset = Changeset.identity(atext.text.length); userChangeset = Changeset.identity(atext.text.length);
applyingNonUserChanges = true; applyingNonUserChanges = true;
try try {
{
callbacks.setDocumentAttributedText(atext); callbacks.setDocumentAttributedText(atext);
} } finally {
finally
{
applyingNonUserChanges = false; applyingNonUserChanges = false;
} }
}); });
}, },
composeUserChangeset: function(c) { composeUserChangeset(c) {
if (!tracking) return; if (!tracking) return;
if (applyingNonUserChanges) return; if (applyingNonUserChanges) return;
if (Changeset.isIdentity(c)) return; if (Changeset.isIdentity(c)) return;
@ -101,149 +91,132 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) {
setChangeCallbackTimeout(); setChangeCallbackTimeout();
}, },
applyChangesToBase: function(c, optAuthor, apoolJsonObj) { applyChangesToBase(c, optAuthor, apoolJsonObj) {
if (!tracking) return; if (!tracking) return;
aceCallbacksProvider.withCallbacks("applyChangesToBase", function(callbacks) { aceCallbacksProvider.withCallbacks('applyChangesToBase', (callbacks) => {
if (apoolJsonObj) {
if (apoolJsonObj) const wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
{
var wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
c = Changeset.moveOpsToNewPool(c, wireApool, apool); c = Changeset.moveOpsToNewPool(c, wireApool, apool);
} }
baseAText = Changeset.applyToAText(c, baseAText, apool); baseAText = Changeset.applyToAText(c, baseAText, apool);
var c2 = c; let c2 = c;
if (submittedChangeset) if (submittedChangeset) {
{ const oldSubmittedChangeset = submittedChangeset;
var oldSubmittedChangeset = submittedChangeset;
submittedChangeset = Changeset.follow(c, oldSubmittedChangeset, false, apool); submittedChangeset = Changeset.follow(c, oldSubmittedChangeset, false, apool);
c2 = Changeset.follow(oldSubmittedChangeset, c, true, apool); c2 = Changeset.follow(oldSubmittedChangeset, c, true, apool);
} }
var preferInsertingAfterUserChanges = true; const preferInsertingAfterUserChanges = true;
var oldUserChangeset = userChangeset; const oldUserChangeset = userChangeset;
userChangeset = Changeset.follow(c2, oldUserChangeset, preferInsertingAfterUserChanges, apool); userChangeset = Changeset.follow(c2, oldUserChangeset, preferInsertingAfterUserChanges, apool);
var postChange = Changeset.follow(oldUserChangeset, c2, !preferInsertingAfterUserChanges, apool); const postChange = Changeset.follow(oldUserChangeset, c2, !preferInsertingAfterUserChanges, apool);
var preferInsertionAfterCaret = true; //(optAuthor && optAuthor > thisAuthor); const preferInsertionAfterCaret = true; // (optAuthor && optAuthor > thisAuthor);
applyingNonUserChanges = true; applyingNonUserChanges = true;
try try {
{
callbacks.applyChangesetToDocument(postChange, preferInsertionAfterCaret); callbacks.applyChangesetToDocument(postChange, preferInsertionAfterCaret);
} } finally {
finally
{
applyingNonUserChanges = false; applyingNonUserChanges = false;
} }
}); });
}, },
prepareUserChangeset: function() { prepareUserChangeset() {
// If there are user changes to submit, 'changeset' will be the // If there are user changes to submit, 'changeset' will be the
// changeset, else it will be null. // changeset, else it will be null.
var toSubmit; let toSubmit;
if (submittedChangeset) if (submittedChangeset) {
{
// submission must have been canceled, prepare new changeset // submission must have been canceled, prepare new changeset
// that includes old submittedChangeset // that includes old submittedChangeset
toSubmit = Changeset.compose(submittedChangeset, userChangeset, apool); toSubmit = Changeset.compose(submittedChangeset, userChangeset, apool);
} } else {
else
{
// add forEach function to Array.prototype for IE8 // add forEach function to Array.prototype for IE8
if (!('forEach' in Array.prototype)) { if (!('forEach' in Array.prototype)) {
Array.prototype.forEach= function(action, that /*opt*/) { Array.prototype.forEach = function (action, that /* opt*/) {
for (var i= 0, n= this.length; i<n; i++) for (let i = 0, n = this.length; i < n; i++) if (i in this) action.call(that, this[i], i, this);
if (i in this)
action.call(that, this[i], i, this);
}; };
} }
// Get my authorID // Get my authorID
var authorId = parent.parent.pad.myUserInfo.userId; const authorId = parent.parent.pad.myUserInfo.userId;
// Sanitize authorship // Sanitize authorship
// We need to replace all author attribs with thisSession.author, in case they copy/pasted or otherwise inserted other peoples changes // We need to replace all author attribs with thisSession.author, in case they copy/pasted or otherwise inserted other peoples changes
if(apool.numToAttrib){ if (apool.numToAttrib) {
for (var attr in apool.numToAttrib){ for (const attr in apool.numToAttrib) {
if (apool.numToAttrib[attr][0] == 'author' && apool.numToAttrib[attr][1] == authorId) var authorAttr = Number(attr).toString(36) if (apool.numToAttrib[attr][0] == 'author' && apool.numToAttrib[attr][1] == authorId) var authorAttr = Number(attr).toString(36);
} }
// Replace all added 'author' attribs with the value of the current user // Replace all added 'author' attribs with the value of the current user
var cs = Changeset.unpack(userChangeset) var cs = Changeset.unpack(userChangeset);
, iterator = Changeset.opIterator(cs.ops) const iterator = Changeset.opIterator(cs.ops);
, op let op;
, assem = Changeset.mergingOpAssembler(); const assem = Changeset.mergingOpAssembler();
while(iterator.hasNext()) { while (iterator.hasNext()) {
op = iterator.next() op = iterator.next();
if(op.opcode == '+') { if (op.opcode == '+') {
var newAttrs = '' var newAttrs = '';
op.attribs.split('*').forEach(function(attrNum) { op.attribs.split('*').forEach((attrNum) => {
if(!attrNum) return if (!attrNum) return;
var attr = apool.getAttrib(parseInt(attrNum, 36)) const attr = apool.getAttrib(parseInt(attrNum, 36));
if(!attr) return if (!attr) return;
if('author' == attr[0]) { if ('author' == attr[0]) {
// replace that author with the current one // replace that author with the current one
newAttrs += '*'+authorAttr; newAttrs += `*${authorAttr}`;
} } else { newAttrs += `*${attrNum}`; } // overtake all other attribs as is
else newAttrs += '*'+attrNum // overtake all other attribs as is });
}) op.attribs = newAttrs;
op.attribs = newAttrs
} }
assem.append(op) assem.append(op);
} }
assem.endDocument(); assem.endDocument();
userChangeset = Changeset.pack(cs.oldLen, cs.newLen, assem.toString(), cs.charBank) userChangeset = Changeset.pack(cs.oldLen, cs.newLen, assem.toString(), cs.charBank);
Changeset.checkRep(userChangeset) Changeset.checkRep(userChangeset);
} }
if (Changeset.isIdentity(userChangeset)) toSubmit = null; if (Changeset.isIdentity(userChangeset)) toSubmit = null;
else toSubmit = userChangeset; else toSubmit = userChangeset;
} }
var cs = null; var cs = null;
if (toSubmit) if (toSubmit) {
{
submittedChangeset = toSubmit; submittedChangeset = toSubmit;
userChangeset = Changeset.identity(Changeset.newLen(toSubmit)); userChangeset = Changeset.identity(Changeset.newLen(toSubmit));
cs = toSubmit; cs = toSubmit;
} }
var wireApool = null; let wireApool = null;
if (cs) if (cs) {
{ const forWire = Changeset.prepareForWire(cs, apool);
var forWire = Changeset.prepareForWire(cs, apool);
wireApool = forWire.pool.toJsonable(); wireApool = forWire.pool.toJsonable();
cs = forWire.translated; cs = forWire.translated;
} }
var data = { const data = {
changeset: cs, changeset: cs,
apool: wireApool apool: wireApool,
}; };
return data; return data;
}, },
applyPreparedChangesetToBase: function() { applyPreparedChangesetToBase() {
if (!submittedChangeset) if (!submittedChangeset) {
{
// violation of protocol; use prepareUserChangeset first // violation of protocol; use prepareUserChangeset first
throw new Error("applySubmittedChangesToBase: no submitted changes to apply"); throw new Error('applySubmittedChangesToBase: no submitted changes to apply');
} }
//bumpDebug("applying committed changeset: "+submittedChangeset.encodeToString(false)); // bumpDebug("applying committed changeset: "+submittedChangeset.encodeToString(false));
baseAText = Changeset.applyToAText(submittedChangeset, baseAText, apool); baseAText = Changeset.applyToAText(submittedChangeset, baseAText, apool);
submittedChangeset = null; submittedChangeset = null;
}, },
setUserChangeNotificationCallback: function(callback) { setUserChangeNotificationCallback(callback) {
changeCallback = callback; changeCallback = callback;
}, },
hasUncommittedChanges: function() { hasUncommittedChanges() {
return !!(submittedChangeset || (!Changeset.isIdentity(userChangeset))); return !!(submittedChangeset || (!Changeset.isIdentity(userChangeset)));
} },
}; };
} }
exports.makeChangesetTracker = makeChangesetTracker; exports.makeChangesetTracker = makeChangesetTracker;

View file

@ -14,174 +14,165 @@
* limitations under the License. * limitations under the License.
*/ */
var padutils = require('./pad_utils').padutils; const padutils = require('./pad_utils').padutils;
var padcookie = require('./pad_cookie').padcookie; const padcookie = require('./pad_cookie').padcookie;
var Tinycon = require('tinycon/tinycon'); const Tinycon = require('tinycon/tinycon');
var hooks = require('./pluginfw/hooks'); const hooks = require('./pluginfw/hooks');
var padeditor = require('./pad_editor').padeditor; const padeditor = require('./pad_editor').padeditor;
var chat = (function() { var chat = (function () {
var isStuck = false; let isStuck = false;
var userAndChat = false; let userAndChat = false;
var gotInitialMessages = false; const gotInitialMessages = false;
var historyPointer = 0; const historyPointer = 0;
var chatMentions = 0; let chatMentions = 0;
var self = { var self = {
show: function () { show() {
$("#chaticon").removeClass('visible'); $('#chaticon').removeClass('visible');
$("#chatbox").addClass('visible'); $('#chatbox').addClass('visible');
self.scrollDown(true); self.scrollDown(true);
chatMentions = 0; chatMentions = 0;
Tinycon.setBubble(0); Tinycon.setBubble(0);
$('.chat-gritter-msg').each(function() { $('.chat-gritter-msg').each(function () {
$.gritter.remove(this.id); $.gritter.remove(this.id);
}); });
}, },
focus: function () { focus() {
setTimeout(function(){ setTimeout(() => {
$("#chatinput").focus(); $('#chatinput').focus();
},100); }, 100);
}, },
stickToScreen: function(fromInitialCall) { // Make chat stick to right hand side of screen stickToScreen(fromInitialCall) { // Make chat stick to right hand side of screen
if(pad.settings.hideChat){ if (pad.settings.hideChat) {
return; return;
} }
chat.show(); chat.show();
isStuck = (!isStuck || fromInitialCall); isStuck = (!isStuck || fromInitialCall);
$('#chatbox').hide(); $('#chatbox').hide();
// Add timeout to disable the chatbox animations // Add timeout to disable the chatbox animations
setTimeout(function() { setTimeout(() => {
$('#chatbox, .sticky-container').toggleClass("stickyChat", isStuck); $('#chatbox, .sticky-container').toggleClass('stickyChat', isStuck);
$('#chatbox').css('display', 'flex'); $('#chatbox').css('display', 'flex');
}, 0); }, 0);
padcookie.setPref("chatAlwaysVisible", isStuck); padcookie.setPref('chatAlwaysVisible', isStuck);
$('#options-stickychat').prop('checked', isStuck); $('#options-stickychat').prop('checked', isStuck);
}, },
chatAndUsers: function(fromInitialCall) { chatAndUsers(fromInitialCall) {
var toEnable = $('#options-chatandusers').is(":checked"); const toEnable = $('#options-chatandusers').is(':checked');
if(toEnable || !userAndChat || fromInitialCall){ if (toEnable || !userAndChat || fromInitialCall) {
chat.stickToScreen(true); chat.stickToScreen(true);
$('#options-stickychat').prop('checked', true) $('#options-stickychat').prop('checked', true);
$('#options-chatandusers').prop('checked', true) $('#options-chatandusers').prop('checked', true);
$('#options-stickychat').prop("disabled", "disabled"); $('#options-stickychat').prop('disabled', 'disabled');
userAndChat = true; userAndChat = true;
}else{ } else {
$('#options-stickychat').prop("disabled", false); $('#options-stickychat').prop('disabled', false);
userAndChat = false; userAndChat = false;
} }
padcookie.setPref("chatAndUsers", userAndChat); padcookie.setPref('chatAndUsers', userAndChat);
$('#users, .sticky-container').toggleClass("chatAndUsers popup-show stickyUsers", userAndChat); $('#users, .sticky-container').toggleClass('chatAndUsers popup-show stickyUsers', userAndChat);
$("#chatbox").toggleClass("chatAndUsersChat", userAndChat); $('#chatbox').toggleClass('chatAndUsersChat', userAndChat);
}, },
hide: function () { hide() {
// decide on hide logic based on chat window being maximized or not // decide on hide logic based on chat window being maximized or not
if ($('#options-stickychat').prop('checked')) { if ($('#options-stickychat').prop('checked')) {
chat.stickToScreen(); chat.stickToScreen();
$('#options-stickychat').prop('checked', false); $('#options-stickychat').prop('checked', false);
} } else {
else { $('#chatcounter').text('0');
$("#chatcounter").text("0"); $('#chaticon').addClass('visible');
$("#chaticon").addClass('visible'); $('#chatbox').removeClass('visible');
$("#chatbox").removeClass('visible');
} }
}, },
scrollDown: function(force) { scrollDown(force) {
if ($('#chatbox').hasClass('visible')) { if ($('#chatbox').hasClass('visible')) {
if (force || !self.lastMessage || !self.lastMessage.position() || self.lastMessage.position().top < ($('#chattext').outerHeight() + 20)) { if (force || !self.lastMessage || !self.lastMessage.position() || self.lastMessage.position().top < ($('#chattext').outerHeight() + 20)) {
// if we use a slow animate here we can have a race condition when a users focus can not be moved away // if we use a slow animate here we can have a race condition when a users focus can not be moved away
// from the last message recieved. // from the last message recieved.
$('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, { duration: 400, queue: false }); $('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, {duration: 400, queue: false});
self.lastMessage = $('#chattext > p').eq(-1); self.lastMessage = $('#chattext > p').eq(-1);
} }
} }
}, },
send: function() { send() {
var text = $("#chatinput").val(); const text = $('#chatinput').val();
if(text.replace(/\s+/,'').length == 0) if (text.replace(/\s+/, '').length == 0) return;
return; this._pad.collabClient.sendMessage({type: 'CHAT_MESSAGE', text});
this._pad.collabClient.sendMessage({"type": "CHAT_MESSAGE", "text": text}); $('#chatinput').val('');
$("#chatinput").val("");
}, },
addMessage: function(msg, increment, isHistoryAdd) { addMessage(msg, increment, isHistoryAdd) {
//correct the time // correct the time
msg.time += this._pad.clientTimeOffset; msg.time += this._pad.clientTimeOffset;
//create the time string // create the time string
var minutes = "" + new Date(msg.time).getMinutes(); let minutes = `${new Date(msg.time).getMinutes()}`;
var hours = "" + new Date(msg.time).getHours(); let hours = `${new Date(msg.time).getHours()}`;
if(minutes.length == 1) if (minutes.length == 1) minutes = `0${minutes}`;
minutes = "0" + minutes ; if (hours.length == 1) hours = `0${hours}`;
if(hours.length == 1) const timeStr = `${hours}:${minutes}`;
hours = "0" + hours ;
var timeStr = hours + ":" + minutes;
//create the authorclass // create the authorclass
if (!msg.userId) { if (!msg.userId) {
/* /*
* If, for a bug or a database corruption, the message coming from the * If, for a bug or a database corruption, the message coming from the
* server does not contain the userId field (see for example #3731), * server does not contain the userId field (see for example #3731),
* let's be defensive and replace it with "unknown". * let's be defensive and replace it with "unknown".
*/ */
msg.userId = "unknown"; msg.userId = 'unknown';
console.warn('The "userId" field of a chat message coming from the server was not present. Replacing with "unknown". This may be a bug or a database corruption.'); console.warn('The "userId" field of a chat message coming from the server was not present. Replacing with "unknown". This may be a bug or a database corruption.');
} }
var authorClass = "author-" + msg.userId.replace(/[^a-y0-9]/g, function(c) { const authorClass = `author-${msg.userId.replace(/[^a-y0-9]/g, (c) => {
if (c == ".") return "-"; if (c == '.') return '-';
return 'z' + c.charCodeAt(0) + 'z'; return `z${c.charCodeAt(0)}z`;
}); })}`;
var text = padutils.escapeHtmlWithClickableLinks(msg.text, "_blank"); const text = padutils.escapeHtmlWithClickableLinks(msg.text, '_blank');
var authorName = msg.userName == null ? _('pad.userlist.unnamed') : padutils.escapeHtml(msg.userName); const authorName = msg.userName == null ? _('pad.userlist.unnamed') : padutils.escapeHtml(msg.userName);
// the hook args // the hook args
var ctx = { const ctx = {
"authorName" : authorName, authorName,
"author" : msg.userId, author: msg.userId,
"text" : text, text,
"sticky" : false, sticky: false,
"timestamp" : msg.time, timestamp: msg.time,
"timeStr" : timeStr, timeStr,
"duration" : 4000 duration: 4000,
} };
// is the users focus already in the chatbox? // is the users focus already in the chatbox?
var alreadyFocused = $("#chatinput").is(":focus"); const alreadyFocused = $('#chatinput').is(':focus');
// does the user already have the chatbox open? // does the user already have the chatbox open?
var chatOpen = $("#chatbox").hasClass("visible"); const chatOpen = $('#chatbox').hasClass('visible');
// does this message contain this user's name? (is the curretn user mentioned?) // does this message contain this user's name? (is the curretn user mentioned?)
var myName = $('#myusernameedit').val(); const myName = $('#myusernameedit').val();
var wasMentioned = (text.toLowerCase().indexOf(myName.toLowerCase()) !== -1 && myName != "undefined"); const wasMentioned = (text.toLowerCase().indexOf(myName.toLowerCase()) !== -1 && myName != 'undefined');
if(wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen) if (wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen) { // If the user was mentioned, make the message sticky
{ // If the user was mentioned, make the message sticky
chatMentions++; chatMentions++;
Tinycon.setBubble(chatMentions); Tinycon.setBubble(chatMentions);
ctx.sticky = true; ctx.sticky = true;
} }
// Call chat message hook // Call chat message hook
hooks.aCallAll("chatNewMessage", ctx, function() { hooks.aCallAll('chatNewMessage', ctx, () => {
const html = `<p data-authorId='${msg.userId}' class='${authorClass}'><b>${authorName}:</b><span class='time ${authorClass}'>${ctx.timeStr}</span> ${ctx.text}</p>`;
if (isHistoryAdd) $(html).insertAfter('#chatloadmessagesbutton');
else $('#chattext').append(html);
var html = "<p data-authorId='" + msg.userId + "' class='" + authorClass + "'><b>" + authorName + ":</b><span class='time " + authorClass + "'>" + ctx.timeStr + "</span> " + ctx.text + "</p>"; // should we increment the counter??
if(isHistoryAdd) if (increment && !isHistoryAdd) {
$(html).insertAfter('#chatloadmessagesbutton');
else
$("#chattext").append(html);
//should we increment the counter??
if(increment && !isHistoryAdd)
{
// Update the counter of unread messages // Update the counter of unread messages
var count = Number($("#chatcounter").text()); let count = Number($('#chatcounter').text());
count++; count++;
$("#chatcounter").text(count); $('#chatcounter').text(count);
if(!chatOpen && ctx.duration > 0) { if (!chatOpen && ctx.duration > 0) {
$.gritter.add({ $.gritter.add({
// Note: ctx.authorName and ctx.text are already HTML-escaped. // Note: ctx.authorName and ctx.text are already HTML-escaped.
text: $('<p>') text: $('<p>')
@ -190,26 +181,25 @@ var chat = (function() {
sticky: ctx.sticky, sticky: ctx.sticky,
time: 5000, time: 5000,
position: 'bottom', position: 'bottom',
class_name: 'chat-gritter-msg' class_name: 'chat-gritter-msg',
}); });
} }
} }
}); });
// Clear the chat mentions when the user clicks on the chat input box // Clear the chat mentions when the user clicks on the chat input box
$('#chatinput').click(function(){ $('#chatinput').click(() => {
chatMentions = 0; chatMentions = 0;
Tinycon.setBubble(0); Tinycon.setBubble(0);
}); });
if(!isHistoryAdd) if (!isHistoryAdd) self.scrollDown();
self.scrollDown();
}, },
init: function(pad) { init(pad) {
this._pad = pad; this._pad = pad;
$("#chatinput").on("keydown", function(evt){ $('#chatinput').on('keydown', (evt) => {
// If the event is Alt C or Escape & we're already in the chat menu // If the event is Alt C or Escape & we're already in the chat menu
// Send the users focus back to the pad // Send the users focus back to the pad
if((evt.altKey == true && evt.which === 67) || evt.which === 27){ if ((evt.altKey == true && evt.which === 67) || evt.which === 27) {
// If we're in chat already.. // If we're in chat already..
$(':focus').blur(); // required to do not try to remove! $(':focus').blur(); // required to do not try to remove!
padeditor.ace.focus(); // Sends focus back to pad padeditor.ace.focus(); // Sends focus back to pad
@ -218,20 +208,19 @@ var chat = (function() {
} }
}); });
$('body:not(#chatinput)').on("keypress", function(evt){ $('body:not(#chatinput)').on('keypress', function (evt) {
if (evt.altKey && evt.which == 67){ if (evt.altKey && evt.which == 67) {
// Alt c focuses on the Chat window // Alt c focuses on the Chat window
$(this).blur(); $(this).blur();
chat.show(); chat.show();
$("#chatinput").focus(); $('#chatinput').focus();
evt.preventDefault(); evt.preventDefault();
} }
}); });
$("#chatinput").keypress(function(evt){ $('#chatinput').keypress((evt) => {
//if the user typed enter, fire the send // if the user typed enter, fire the send
if(evt.which == 13 || evt.which == 10) if (evt.which == 13 || evt.which == 10) {
{
evt.preventDefault(); evt.preventDefault();
self.send(); self.send();
} }
@ -239,25 +228,24 @@ var chat = (function() {
// initial messages are loaded in pad.js' _afterHandshake // initial messages are loaded in pad.js' _afterHandshake
$("#chatcounter").text(0); $('#chatcounter').text(0);
$("#chatloadmessagesbutton").click(function() { $('#chatloadmessagesbutton').click(() => {
var start = Math.max(self.historyPointer - 20, 0); const start = Math.max(self.historyPointer - 20, 0);
var end = self.historyPointer; const end = self.historyPointer;
if(start == end) // nothing to load if (start == end) // nothing to load
return; { return; }
$("#chatloadmessagesbutton").css("display", "none"); $('#chatloadmessagesbutton').css('display', 'none');
$("#chatloadmessagesball").css("display", "block"); $('#chatloadmessagesball').css('display', 'block');
pad.collabClient.sendMessage({"type": "GET_CHAT_MESSAGES", "start": start, "end": end}); pad.collabClient.sendMessage({type: 'GET_CHAT_MESSAGES', start, end});
self.historyPointer = start; self.historyPointer = start;
}); });
} },
} };
return self; return self;
}()); }());
exports.chat = chat; exports.chat = chat;

View file

@ -20,12 +20,12 @@
* limitations under the License. * limitations under the License.
*/ */
var chat = require('./chat').chat; const chat = require('./chat').chat;
var hooks = require('./pluginfw/hooks'); const hooks = require('./pluginfw/hooks');
// Dependency fill on init. This exists for `pad.socket` only. // Dependency fill on init. This exists for `pad.socket` only.
// TODO: bind directly to the socket. // TODO: bind directly to the socket.
var pad = undefined; let pad = undefined;
function getSocket() { function getSocket() {
return pad && pad.socket; return pad && pad.socket;
} }
@ -34,134 +34,118 @@ function getSocket() {
ACE's ready callback does not need to have fired yet. ACE's ready callback does not need to have fired yet.
"serverVars" are from calling doc.getCollabClientVars() on the server. */ "serverVars" are from calling doc.getCollabClientVars() on the server. */
function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) { function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) {
var editor = ace2editor; const editor = ace2editor;
pad = _pad; // Inject pad to avoid a circular dependency. pad = _pad; // Inject pad to avoid a circular dependency.
var rev = serverVars.rev; let rev = serverVars.rev;
var padId = serverVars.padId; const padId = serverVars.padId;
var state = "IDLE"; let state = 'IDLE';
var stateMessage; let stateMessage;
var channelState = "CONNECTING"; let channelState = 'CONNECTING';
var appLevelDisconnectReason = null; let appLevelDisconnectReason = null;
var lastCommitTime = 0; let lastCommitTime = 0;
var initialStartConnectTime = 0; let initialStartConnectTime = 0;
var userId = initialUserInfo.userId; const userId = initialUserInfo.userId;
//var socket; // var socket;
var userSet = {}; // userId -> userInfo const userSet = {}; // userId -> userInfo
userSet[userId] = initialUserInfo; userSet[userId] = initialUserInfo;
var caughtErrors = []; const caughtErrors = [];
var caughtErrorCatchers = []; const caughtErrorCatchers = [];
var caughtErrorTimes = []; const caughtErrorTimes = [];
var debugMessages = []; const debugMessages = [];
var msgQueue = []; const msgQueue = [];
var isPendingRevision = false; let isPendingRevision = false;
tellAceAboutHistoricalAuthors(serverVars.historicalAuthorData); tellAceAboutHistoricalAuthors(serverVars.historicalAuthorData);
tellAceActiveAuthorInfo(initialUserInfo); tellAceActiveAuthorInfo(initialUserInfo);
var callbacks = { const callbacks = {
onUserJoin: function() {}, onUserJoin() {},
onUserLeave: function() {}, onUserLeave() {},
onUpdateUserInfo: function() {}, onUpdateUserInfo() {},
onChannelStateChange: function() {}, onChannelStateChange() {},
onClientMessage: function() {}, onClientMessage() {},
onInternalAction: function() {}, onInternalAction() {},
onConnectionTrouble: function() {}, onConnectionTrouble() {},
onServerMessage: function() {} onServerMessage() {},
}; };
if (browser.firefox) if (browser.firefox) {
{
// Prevent "escape" from taking effect and canceling a comet connection; // Prevent "escape" from taking effect and canceling a comet connection;
// doesn't work if focus is on an iframe. // doesn't work if focus is on an iframe.
$(window).bind("keydown", function(evt) { $(window).bind('keydown', (evt) => {
if (evt.which == 27) if (evt.which == 27) {
{ evt.preventDefault();
evt.preventDefault()
} }
}); });
} }
editor.setProperty("userAuthor", userId); editor.setProperty('userAuthor', userId);
editor.setBaseAttributedText(serverVars.initialAttributedText, serverVars.apool); editor.setBaseAttributedText(serverVars.initialAttributedText, serverVars.apool);
editor.setUserChangeNotificationCallback(wrapRecordingErrors("handleUserChanges", handleUserChanges)); editor.setUserChangeNotificationCallback(wrapRecordingErrors('handleUserChanges', handleUserChanges));
function dmesg(str) { function dmesg(str) {
if (typeof window.ajlog == "string") window.ajlog += str + '\n'; if (typeof window.ajlog === 'string') window.ajlog += `${str}\n`;
debugMessages.push(str); debugMessages.push(str);
} }
function handleUserChanges() { function handleUserChanges() {
if (editor.getInInternationalComposition()) return; if (editor.getInInternationalComposition()) return;
if ((!getSocket()) || channelState == "CONNECTING") if ((!getSocket()) || channelState == 'CONNECTING') {
{ if (channelState == 'CONNECTING' && (((+new Date()) - initialStartConnectTime) > 20000)) {
if (channelState == "CONNECTING" && (((+new Date()) - initialStartConnectTime) > 20000)) setChannelState('DISCONNECTED', 'initsocketfail');
{ } else {
setChannelState("DISCONNECTED", "initsocketfail");
}
else
{
// check again in a bit // check again in a bit
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 1000); setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), 1000);
} }
return; return;
} }
var t = (+new Date()); const t = (+new Date());
if (state != "IDLE") if (state != 'IDLE') {
{ if (state == 'COMMITTING' && msgQueue.length == 0 && (t - lastCommitTime) > 20000) {
if (state == "COMMITTING" && msgQueue.length == 0 && (t - lastCommitTime) > 20000)
{
// a commit is taking too long // a commit is taking too long
setChannelState("DISCONNECTED", "slowcommit"); setChannelState('DISCONNECTED', 'slowcommit');
} } else if (state == 'COMMITTING' && msgQueue.length == 0 && (t - lastCommitTime) > 5000) {
else if (state == "COMMITTING" && msgQueue.length == 0 && (t - lastCommitTime) > 5000) callbacks.onConnectionTrouble('SLOW');
{ } else {
callbacks.onConnectionTrouble("SLOW");
}
else
{
// run again in a few seconds, to detect a disconnect // run again in a few seconds, to detect a disconnect
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000); setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), 3000);
} }
return; return;
} }
var earliestCommit = lastCommitTime + 500; const earliestCommit = lastCommitTime + 500;
if (t < earliestCommit) if (t < earliestCommit) {
{ setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), earliestCommit - t);
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), earliestCommit - t);
return; return;
} }
// apply msgQueue changeset. // apply msgQueue changeset.
if (msgQueue.length != 0) { if (msgQueue.length != 0) {
var msg; let msg;
while (msg = msgQueue.shift()) { while (msg = msgQueue.shift()) {
var newRev = msg.newRev; const newRev = msg.newRev;
rev=newRev; rev = newRev;
if (msg.type == "ACCEPT_COMMIT") if (msg.type == 'ACCEPT_COMMIT') {
{
editor.applyPreparedChangesetToBase(); editor.applyPreparedChangesetToBase();
setStateIdle(); setStateIdle();
callCatchingErrors("onInternalAction", function() { callCatchingErrors('onInternalAction', () => {
callbacks.onInternalAction("commitAcceptedByServer"); callbacks.onInternalAction('commitAcceptedByServer');
}); });
callCatchingErrors("onConnectionTrouble", function() { callCatchingErrors('onConnectionTrouble', () => {
callbacks.onConnectionTrouble("OK"); callbacks.onConnectionTrouble('OK');
}); });
handleUserChanges(); handleUserChanges();
} } else if (msg.type == 'NEW_CHANGES') {
else if (msg.type == "NEW_CHANGES") const changeset = msg.changeset;
{ const author = (msg.author || '');
var changeset = msg.changeset; const apool = msg.apool;
var author = (msg.author || '');
var apool = msg.apool;
editor.applyChangesToBase(changeset, author, apool); editor.applyChangesToBase(changeset, author, apool);
} }
@ -171,43 +155,38 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
} }
} }
var sentMessage = false; let sentMessage = false;
// Check if there are any pending revisions to be received from server. // Check if there are any pending revisions to be received from server.
// Allow only if there are no pending revisions to be received from server // Allow only if there are no pending revisions to be received from server
if (!isPendingRevision) if (!isPendingRevision) {
{ const userChangesData = editor.prepareUserChangeset();
var userChangesData = editor.prepareUserChangeset(); if (userChangesData.changeset) {
if (userChangesData.changeset) lastCommitTime = t;
{ state = 'COMMITTING';
lastCommitTime = t; stateMessage = {
state = "COMMITTING"; type: 'USER_CHANGES',
stateMessage = { baseRev: rev,
type: "USER_CHANGES", changeset: userChangesData.changeset,
baseRev: rev, apool: userChangesData.apool,
changeset: userChangesData.changeset, };
apool: userChangesData.apool sendMessage(stateMessage);
}; sentMessage = true;
sendMessage(stateMessage); callbacks.onInternalAction('commitPerformed');
sentMessage = true; }
callbacks.onInternalAction("commitPerformed"); } else {
} // run again in a few seconds, to check if there was a reconnection attempt
} setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), 3000);
else
{
// run again in a few seconds, to check if there was a reconnection attempt
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000);
} }
if (sentMessage) if (sentMessage) {
{
// run again in a few seconds, to detect a disconnect // run again in a few seconds, to detect a disconnect
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000); setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), 3000);
} }
} }
function setUpSocket() { function setUpSocket() {
hiccupCount = 0; hiccupCount = 0;
setChannelState("CONNECTED"); setChannelState('CONNECTED');
doDeferredActions(); doDeferredActions();
initialStartConnectTime = +new Date(); initialStartConnectTime = +new Date();
@ -217,49 +196,42 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
function sendMessage(msg) { function sendMessage(msg) {
getSocket().json.send( getSocket().json.send(
{ {
type: "COLLABROOM", type: 'COLLABROOM',
component: "pad", component: 'pad',
data: msg data: msg,
}); });
} }
function wrapRecordingErrors(catcher, func) { function wrapRecordingErrors(catcher, func) {
return function() { return function () {
try try {
{
return func.apply(this, Array.prototype.slice.call(arguments)); return func.apply(this, Array.prototype.slice.call(arguments));
} } catch (e) {
catch (e)
{
caughtErrors.push(e); caughtErrors.push(e);
caughtErrorCatchers.push(catcher); caughtErrorCatchers.push(catcher);
caughtErrorTimes.push(+new Date()); caughtErrorTimes.push(+new Date());
//console.dir({catcher: catcher, e: e}); // console.dir({catcher: catcher, e: e});
throw e; throw e;
} }
}; };
} }
function callCatchingErrors(catcher, func) { function callCatchingErrors(catcher, func) {
try try {
{
wrapRecordingErrors(catcher, func)(); wrapRecordingErrors(catcher, func)();
} } catch (e) { /* absorb*/
catch (e)
{ /*absorb*/
} }
} }
function handleMessageFromServer(evt) { function handleMessageFromServer(evt) {
if (!getSocket()) return; if (!getSocket()) return;
if (!evt.data) return; if (!evt.data) return;
var wrapper = evt; const wrapper = evt;
if (wrapper.type != "COLLABROOM" && wrapper.type != "CUSTOM") return; if (wrapper.type != 'COLLABROOM' && wrapper.type != 'CUSTOM') return;
var msg = wrapper.data; const msg = wrapper.data;
if (msg.type == "NEW_CHANGES") if (msg.type == 'NEW_CHANGES') {
{
var newRev = msg.newRev; var newRev = msg.newRev;
var changeset = msg.changeset; var changeset = msg.changeset;
var author = (msg.author || ''); var author = (msg.author || '');
@ -270,9 +242,8 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
if (msgQueue.length > 0) var oldRev = msgQueue[msgQueue.length - 1].newRev; if (msgQueue.length > 0) var oldRev = msgQueue[msgQueue.length - 1].newRev;
else oldRev = rev; else oldRev = rev;
if (newRev != (oldRev + 1)) if (newRev != (oldRev + 1)) {
{ window.console.warn(`bad message revision on NEW_CHANGES: ${newRev} not ${oldRev + 1}`);
window.console.warn("bad message revision on NEW_CHANGES: " + newRev + " not " + (oldRev + 1));
// setChannelState("DISCONNECTED", "badmessage_newchanges"); // setChannelState("DISCONNECTED", "badmessage_newchanges");
return; return;
} }
@ -280,24 +251,19 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
return; return;
} }
if (newRev != (rev + 1)) if (newRev != (rev + 1)) {
{ window.console.warn(`bad message revision on NEW_CHANGES: ${newRev} not ${rev + 1}`);
window.console.warn("bad message revision on NEW_CHANGES: " + newRev + " not " + (rev + 1));
// setChannelState("DISCONNECTED", "badmessage_newchanges"); // setChannelState("DISCONNECTED", "badmessage_newchanges");
return; return;
} }
rev = newRev; rev = newRev;
editor.applyChangesToBase(changeset, author, apool); editor.applyChangesToBase(changeset, author, apool);
} } else if (msg.type == 'ACCEPT_COMMIT') {
else if (msg.type == "ACCEPT_COMMIT")
{
var newRev = msg.newRev; var newRev = msg.newRev;
if (msgQueue.length > 0) if (msgQueue.length > 0) {
{ if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1)) {
if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1)) window.console.warn(`bad message revision on ACCEPT_COMMIT: ${newRev} not ${msgQueue[msgQueue.length - 1][0] + 1}`);
{
window.console.warn("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (msgQueue[msgQueue.length - 1][0] + 1));
// setChannelState("DISCONNECTED", "badmessage_acceptcommit"); // setChannelState("DISCONNECTED", "badmessage_acceptcommit");
return; return;
} }
@ -305,178 +271,140 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
return; return;
} }
if (newRev != (rev + 1)) if (newRev != (rev + 1)) {
{ window.console.warn(`bad message revision on ACCEPT_COMMIT: ${newRev} not ${rev + 1}`);
window.console.warn("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (rev + 1));
// setChannelState("DISCONNECTED", "badmessage_acceptcommit"); // setChannelState("DISCONNECTED", "badmessage_acceptcommit");
return; return;
} }
rev = newRev; rev = newRev;
editor.applyPreparedChangesetToBase(); editor.applyPreparedChangesetToBase();
setStateIdle(); setStateIdle();
callCatchingErrors("onInternalAction", function() { callCatchingErrors('onInternalAction', () => {
callbacks.onInternalAction("commitAcceptedByServer"); callbacks.onInternalAction('commitAcceptedByServer');
}); });
callCatchingErrors("onConnectionTrouble", function() { callCatchingErrors('onConnectionTrouble', () => {
callbacks.onConnectionTrouble("OK"); callbacks.onConnectionTrouble('OK');
}); });
handleUserChanges(); handleUserChanges();
} } else if (msg.type == 'CLIENT_RECONNECT') {
else if (msg.type == 'CLIENT_RECONNECT')
{
// Server sends a CLIENT_RECONNECT message when there is a client reconnect. Server also returns // Server sends a CLIENT_RECONNECT message when there is a client reconnect. Server also returns
// all pending revisions along with this CLIENT_RECONNECT message // all pending revisions along with this CLIENT_RECONNECT message
if (msg.noChanges) if (msg.noChanges) {
{
// If no revisions are pending, just make everything normal // If no revisions are pending, just make everything normal
setIsPendingRevision(false); setIsPendingRevision(false);
return; return;
} }
var headRev = msg.headRev; const headRev = msg.headRev;
var newRev = msg.newRev; var newRev = msg.newRev;
var changeset = msg.changeset; var changeset = msg.changeset;
var author = (msg.author || ''); var author = (msg.author || '');
var apool = msg.apool; var apool = msg.apool;
if (msgQueue.length > 0) if (msgQueue.length > 0) {
{ if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1)) {
if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1)) window.console.warn(`bad message revision on CLIENT_RECONNECT: ${newRev} not ${msgQueue[msgQueue.length - 1][0] + 1}`);
{
window.console.warn("bad message revision on CLIENT_RECONNECT: " + newRev + " not " + (msgQueue[msgQueue.length - 1][0] + 1));
// setChannelState("DISCONNECTED", "badmessage_acceptcommit"); // setChannelState("DISCONNECTED", "badmessage_acceptcommit");
return; return;
} }
msg.type = "NEW_CHANGES"; msg.type = 'NEW_CHANGES';
msgQueue.push(msg); msgQueue.push(msg);
return; return;
} }
if (newRev != (rev + 1)) if (newRev != (rev + 1)) {
{ window.console.warn(`bad message revision on CLIENT_RECONNECT: ${newRev} not ${rev + 1}`);
window.console.warn("bad message revision on CLIENT_RECONNECT: " + newRev + " not " + (rev + 1));
// setChannelState("DISCONNECTED", "badmessage_acceptcommit"); // setChannelState("DISCONNECTED", "badmessage_acceptcommit");
return; return;
} }
rev = newRev; rev = newRev;
if (author == pad.getUserId()) if (author == pad.getUserId()) {
{
editor.applyPreparedChangesetToBase(); editor.applyPreparedChangesetToBase();
setStateIdle(); setStateIdle();
callCatchingErrors("onInternalAction", function() { callCatchingErrors('onInternalAction', () => {
callbacks.onInternalAction("commitAcceptedByServer"); callbacks.onInternalAction('commitAcceptedByServer');
}); });
callCatchingErrors("onConnectionTrouble", function() { callCatchingErrors('onConnectionTrouble', () => {
callbacks.onConnectionTrouble("OK"); callbacks.onConnectionTrouble('OK');
}); });
handleUserChanges(); handleUserChanges();
} } else {
else
{
editor.applyChangesToBase(changeset, author, apool); editor.applyChangesToBase(changeset, author, apool);
} }
if (newRev == headRev) if (newRev == headRev) {
{
// Once we have applied all pending revisions, make everything normal // Once we have applied all pending revisions, make everything normal
setIsPendingRevision(false); setIsPendingRevision(false);
} }
} } else if (msg.type == 'NO_COMMIT_PENDING') {
else if (msg.type == "NO_COMMIT_PENDING") if (state == 'COMMITTING') {
{
if (state == "COMMITTING")
{
// server missed our commit message; abort that commit // server missed our commit message; abort that commit
setStateIdle(); setStateIdle();
handleUserChanges(); handleUserChanges();
} }
} } else if (msg.type == 'USER_NEWINFO') {
else if (msg.type == "USER_NEWINFO")
{
var userInfo = msg.userInfo; var userInfo = msg.userInfo;
var id = userInfo.userId; var id = userInfo.userId;
// Avoid a race condition when setting colors. If our color was set by a // Avoid a race condition when setting colors. If our color was set by a
// query param, ignore our own "new user" message's color value. // query param, ignore our own "new user" message's color value.
if (id === initialUserInfo.userId && initialUserInfo.globalUserColor) if (id === initialUserInfo.userId && initialUserInfo.globalUserColor) {
{
msg.userInfo.colorId = initialUserInfo.globalUserColor; msg.userInfo.colorId = initialUserInfo.globalUserColor;
} }
if (userSet[id]) if (userSet[id]) {
{
userSet[id] = userInfo; userSet[id] = userInfo;
callbacks.onUpdateUserInfo(userInfo); callbacks.onUpdateUserInfo(userInfo);
} } else {
else
{
userSet[id] = userInfo; userSet[id] = userInfo;
callbacks.onUserJoin(userInfo); callbacks.onUserJoin(userInfo);
} }
tellAceActiveAuthorInfo(userInfo); tellAceActiveAuthorInfo(userInfo);
} } else if (msg.type == 'USER_LEAVE') {
else if (msg.type == "USER_LEAVE")
{
var userInfo = msg.userInfo; var userInfo = msg.userInfo;
var id = userInfo.userId; var id = userInfo.userId;
if (userSet[id]) if (userSet[id]) {
{
delete userSet[userInfo.userId]; delete userSet[userInfo.userId];
fadeAceAuthorInfo(userInfo); fadeAceAuthorInfo(userInfo);
callbacks.onUserLeave(userInfo); callbacks.onUserLeave(userInfo);
} }
} } else if (msg.type == 'DISCONNECT_REASON') {
else if (msg.type == "DISCONNECT_REASON")
{
appLevelDisconnectReason = msg.reason; appLevelDisconnectReason = msg.reason;
} } else if (msg.type == 'CLIENT_MESSAGE') {
else if (msg.type == "CLIENT_MESSAGE")
{
callbacks.onClientMessage(msg.payload); callbacks.onClientMessage(msg.payload);
} } else if (msg.type == 'CHAT_MESSAGE') {
else if (msg.type == "CHAT_MESSAGE")
{
chat.addMessage(msg, true, false); chat.addMessage(msg, true, false);
} } else if (msg.type == 'CHAT_MESSAGES') {
else if (msg.type == "CHAT_MESSAGES") for (let i = msg.messages.length - 1; i >= 0; i--) {
{
for(var i = msg.messages.length - 1; i >= 0; i--)
{
chat.addMessage(msg.messages[i], true, true); chat.addMessage(msg.messages[i], true, true);
} }
if(!chat.gotInitalMessages) if (!chat.gotInitalMessages) {
{
chat.scrollDown(); chat.scrollDown();
chat.gotInitalMessages = true; chat.gotInitalMessages = true;
chat.historyPointer = clientVars.chatHead - msg.messages.length; chat.historyPointer = clientVars.chatHead - msg.messages.length;
} }
// messages are loaded, so hide the loading-ball // messages are loaded, so hide the loading-ball
$("#chatloadmessagesball").css("display", "none"); $('#chatloadmessagesball').css('display', 'none');
// there are less than 100 messages or we reached the top // there are less than 100 messages or we reached the top
if(chat.historyPointer <= 0) if (chat.historyPointer <= 0) { $('#chatloadmessagesbutton').css('display', 'none'); } else // there are still more messages, re-show the load-button
$("#chatloadmessagesbutton").css("display", "none"); { $('#chatloadmessagesbutton').css('display', 'block'); }
else // there are still more messages, re-show the load-button } else if (msg.type == 'SERVER_MESSAGE') {
$("#chatloadmessagesbutton").css("display", "block");
}
else if (msg.type == "SERVER_MESSAGE")
{
callbacks.onServerMessage(msg.payload); callbacks.onServerMessage(msg.payload);
} }
//HACKISH: User messages do not have "payload" but "userInfo", so that all "handleClientMessage_USER_" hooks would work, populate payload // HACKISH: User messages do not have "payload" but "userInfo", so that all "handleClientMessage_USER_" hooks would work, populate payload
//FIXME: USER_* messages to have "payload" property instead of "userInfo", seems like a quite a big work // FIXME: USER_* messages to have "payload" property instead of "userInfo", seems like a quite a big work
if(msg.type.indexOf("USER_") > -1) { if (msg.type.indexOf('USER_') > -1) {
msg.payload = msg.userInfo; msg.payload = msg.userInfo;
} }
// Similar for NEW_CHANGES // Similar for NEW_CHANGES
if(msg.type === "NEW_CHANGES") msg.payload = msg; if (msg.type === 'NEW_CHANGES') msg.payload = msg;
hooks.callAll('handleClientMessage_' + msg.type, {payload: msg.payload}); hooks.callAll(`handleClientMessage_${msg.type}`, {payload: msg.payload});
} }
function updateUserInfo(userInfo) { function updateUserInfo(userInfo) {
@ -485,10 +413,10 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
tellAceActiveAuthorInfo(userInfo); tellAceActiveAuthorInfo(userInfo);
if (!getSocket()) return; if (!getSocket()) return;
sendMessage( sendMessage(
{ {
type: "USERINFO_UPDATE", type: 'USERINFO_UPDATE',
userInfo: userInfo userInfo,
}); });
} }
function tellAceActiveAuthorInfo(userInfo) { function tellAceActiveAuthorInfo(userInfo) {
@ -496,23 +424,19 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
} }
function tellAceAuthorInfo(userId, colorId, inactive) { function tellAceAuthorInfo(userId, colorId, inactive) {
if(typeof colorId == "number") if (typeof colorId === 'number') {
{
colorId = clientVars.colorPalette[colorId]; colorId = clientVars.colorPalette[colorId];
} }
var cssColor = colorId; const cssColor = colorId;
if (inactive) if (inactive) {
{
editor.setAuthorInfo(userId, { editor.setAuthorInfo(userId, {
bgcolor: cssColor, bgcolor: cssColor,
fade: 0.5 fade: 0.5,
}); });
} } else {
else
{
editor.setAuthorInfo(userId, { editor.setAuthorInfo(userId, {
bgcolor: cssColor bgcolor: cssColor,
}); });
} }
} }
@ -526,27 +450,24 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
} }
function tellAceAboutHistoricalAuthors(hadata) { function tellAceAboutHistoricalAuthors(hadata) {
for (var author in hadata) for (const author in hadata) {
{ const data = hadata[author];
var data = hadata[author]; if (!userSet[author]) {
if (!userSet[author])
{
tellAceAuthorInfo(author, data.colorId, true); tellAceAuthorInfo(author, data.colorId, true);
} }
} }
} }
function setChannelState(newChannelState, moreInfo) { function setChannelState(newChannelState, moreInfo) {
if (newChannelState != channelState) if (newChannelState != channelState) {
{
channelState = newChannelState; channelState = newChannelState;
callbacks.onChannelStateChange(channelState, moreInfo); callbacks.onChannelStateChange(channelState, moreInfo);
} }
} }
function valuesArray(obj) { function valuesArray(obj) {
var array = []; const array = [];
$.each(obj, function(k, v) { $.each(obj, (k, v) => {
array.push(v); array.push(v);
}); });
return array; return array;
@ -554,39 +475,32 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
// We need to present a working interface even before the socket // We need to present a working interface even before the socket
// is connected for the first time. // is connected for the first time.
var deferredActions = []; let deferredActions = [];
function defer(func, tag) { function defer(func, tag) {
return function() { return function () {
var that = this; const that = this;
var args = arguments; const args = arguments;
function action() { function action() {
func.apply(that, args); func.apply(that, args);
} }
action.tag = tag; action.tag = tag;
if (channelState == "CONNECTING") if (channelState == 'CONNECTING') {
{
deferredActions.push(action); deferredActions.push(action);
} } else {
else
{
action(); action();
} }
} };
} }
function doDeferredActions(tag) { function doDeferredActions(tag) {
var newArray = []; const newArray = [];
for (var i = 0; i < deferredActions.length; i++) for (let i = 0; i < deferredActions.length; i++) {
{ const a = deferredActions[i];
var a = deferredActions[i]; if ((!tag) || (tag == a.tag)) {
if ((!tag) || (tag == a.tag))
{
a(); a();
} } else {
else
{
newArray.push(a); newArray.push(a);
} }
} }
@ -595,10 +509,10 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
function sendClientMessage(msg) { function sendClientMessage(msg) {
sendMessage( sendMessage(
{ {
type: "CLIENT_MESSAGE", type: 'CLIENT_MESSAGE',
payload: msg payload: msg,
}); });
} }
function getCurrentRevisionNumber() { function getCurrentRevisionNumber() {
@ -606,18 +520,16 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
} }
function getMissedChanges() { function getMissedChanges() {
var obj = {}; const obj = {};
obj.userInfo = userSet[userId]; obj.userInfo = userSet[userId];
obj.baseRev = rev; obj.baseRev = rev;
if (state == "COMMITTING" && stateMessage) if (state == 'COMMITTING' && stateMessage) {
{
obj.committedChangeset = stateMessage.changeset; obj.committedChangeset = stateMessage.changeset;
obj.committedChangesetAPool = stateMessage.apool; obj.committedChangesetAPool = stateMessage.apool;
editor.applyPreparedChangesetToBase(); editor.applyPreparedChangesetToBase();
} }
var userChangesData = editor.prepareUserChangeset(); const userChangesData = editor.prepareUserChangeset();
if (userChangesData.changeset) if (userChangesData.changeset) {
{
obj.furtherChangeset = userChangesData.changeset; obj.furtherChangeset = userChangesData.changeset;
obj.furtherChangesetAPool = userChangesData.apool; obj.furtherChangesetAPool = userChangesData.apool;
} }
@ -625,8 +537,8 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
} }
function setStateIdle() { function setStateIdle() {
state = "IDLE"; state = 'IDLE';
callbacks.onInternalAction("newlyIdle"); callbacks.onInternalAction('newlyIdle');
schedulePerhapsCallIdleFuncs(); schedulePerhapsCallIdleFuncs();
} }
@ -642,55 +554,53 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
var idleFuncs = []; var idleFuncs = [];
function schedulePerhapsCallIdleFuncs() { function schedulePerhapsCallIdleFuncs() {
setTimeout(function() { setTimeout(() => {
if (state == "IDLE") if (state == 'IDLE') {
{ while (idleFuncs.length > 0) {
while (idleFuncs.length > 0) const f = idleFuncs.shift();
{
var f = idleFuncs.shift();
f(); f();
} }
} }
}, 0); }, 0);
} }
var self = { const self = {
setOnUserJoin: function(cb) { setOnUserJoin(cb) {
callbacks.onUserJoin = cb; callbacks.onUserJoin = cb;
}, },
setOnUserLeave: function(cb) { setOnUserLeave(cb) {
callbacks.onUserLeave = cb; callbacks.onUserLeave = cb;
}, },
setOnUpdateUserInfo: function(cb) { setOnUpdateUserInfo(cb) {
callbacks.onUpdateUserInfo = cb; callbacks.onUpdateUserInfo = cb;
}, },
setOnChannelStateChange: function(cb) { setOnChannelStateChange(cb) {
callbacks.onChannelStateChange = cb; callbacks.onChannelStateChange = cb;
}, },
setOnClientMessage: function(cb) { setOnClientMessage(cb) {
callbacks.onClientMessage = cb; callbacks.onClientMessage = cb;
}, },
setOnInternalAction: function(cb) { setOnInternalAction(cb) {
callbacks.onInternalAction = cb; callbacks.onInternalAction = cb;
}, },
setOnConnectionTrouble: function(cb) { setOnConnectionTrouble(cb) {
callbacks.onConnectionTrouble = cb; callbacks.onConnectionTrouble = cb;
}, },
setOnServerMessage: function(cb) { setOnServerMessage(cb) {
callbacks.onServerMessage = cb; callbacks.onServerMessage = cb;
}, },
updateUserInfo: defer(updateUserInfo), updateUserInfo: defer(updateUserInfo),
handleMessageFromServer: handleMessageFromServer, handleMessageFromServer,
getConnectedUsers: getConnectedUsers, getConnectedUsers,
sendClientMessage: sendClientMessage, sendClientMessage,
sendMessage: sendMessage, sendMessage,
getCurrentRevisionNumber: getCurrentRevisionNumber, getCurrentRevisionNumber,
getMissedChanges: getMissedChanges, getMissedChanges,
callWhenNotCommitting: callWhenNotCommitting, callWhenNotCommitting,
addHistoricalAuthors: tellAceAboutHistoricalAuthors, addHistoricalAuthors: tellAceAboutHistoricalAuthors,
setChannelState: setChannelState, setChannelState,
setStateIdle: setStateIdle, setStateIdle,
setIsPendingRevision: setIsPendingRevision setIsPendingRevision,
}; };
setUpSocket(); setUpSocket();

View file

@ -22,111 +22,110 @@
* limitations under the License. * limitations under the License.
*/ */
var colorutils = {}; const colorutils = {};
// Check that a given value is a css hex color value, e.g. // Check that a given value is a css hex color value, e.g.
// "#ffffff" or "#fff" // "#ffffff" or "#fff"
colorutils.isCssHex = function(cssColor) { colorutils.isCssHex = function (cssColor) {
return /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(cssColor); return /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(cssColor);
} };
// "#ffffff" or "#fff" or "ffffff" or "fff" to [1.0, 1.0, 1.0] // "#ffffff" or "#fff" or "ffffff" or "fff" to [1.0, 1.0, 1.0]
colorutils.css2triple = function(cssColor) { colorutils.css2triple = function (cssColor) {
var sixHex = colorutils.css2sixhex(cssColor); const sixHex = colorutils.css2sixhex(cssColor);
function hexToFloat(hh) { function hexToFloat(hh) {
return Number("0x" + hh) / 255; return Number(`0x${hh}`) / 255;
} }
return [hexToFloat(sixHex.substr(0, 2)), hexToFloat(sixHex.substr(2, 2)), hexToFloat(sixHex.substr(4, 2))]; return [hexToFloat(sixHex.substr(0, 2)), hexToFloat(sixHex.substr(2, 2)), hexToFloat(sixHex.substr(4, 2))];
} };
// "#ffffff" or "#fff" or "ffffff" or "fff" to "ffffff" // "#ffffff" or "#fff" or "ffffff" or "fff" to "ffffff"
colorutils.css2sixhex = function(cssColor) { colorutils.css2sixhex = function (cssColor) {
var h = /[0-9a-fA-F]+/.exec(cssColor)[0]; let h = /[0-9a-fA-F]+/.exec(cssColor)[0];
if (h.length != 6) if (h.length != 6) {
{ const a = h.charAt(0);
var a = h.charAt(0); const b = h.charAt(1);
var b = h.charAt(1); const c = h.charAt(2);
var c = h.charAt(2);
h = a + a + b + b + c + c; h = a + a + b + b + c + c;
} }
return h; return h;
} };
// [1.0, 1.0, 1.0] -> "#ffffff" // [1.0, 1.0, 1.0] -> "#ffffff"
colorutils.triple2css = function(triple) { colorutils.triple2css = function (triple) {
function floatToHex(n) { function floatToHex(n) {
var n2 = colorutils.clamp(Math.round(n * 255), 0, 255); const n2 = colorutils.clamp(Math.round(n * 255), 0, 255);
return ("0" + n2.toString(16)).slice(-2); return (`0${n2.toString(16)}`).slice(-2);
} }
return "#" + floatToHex(triple[0]) + floatToHex(triple[1]) + floatToHex(triple[2]); return `#${floatToHex(triple[0])}${floatToHex(triple[1])}${floatToHex(triple[2])}`;
} };
colorutils.clamp = function(v, bot, top) { colorutils.clamp = function (v, bot, top) {
return v < bot ? bot : (v > top ? top : v); return v < bot ? bot : (v > top ? top : v);
}; };
colorutils.min3 = function(a, b, c) { colorutils.min3 = function (a, b, c) {
return (a < b) ? (a < c ? a : c) : (b < c ? b : c); return (a < b) ? (a < c ? a : c) : (b < c ? b : c);
}; };
colorutils.max3 = function(a, b, c) { colorutils.max3 = function (a, b, c) {
return (a > b) ? (a > c ? a : c) : (b > c ? b : c); return (a > b) ? (a > c ? a : c) : (b > c ? b : c);
}; };
colorutils.colorMin = function(c) { colorutils.colorMin = function (c) {
return colorutils.min3(c[0], c[1], c[2]); return colorutils.min3(c[0], c[1], c[2]);
}; };
colorutils.colorMax = function(c) { colorutils.colorMax = function (c) {
return colorutils.max3(c[0], c[1], c[2]); return colorutils.max3(c[0], c[1], c[2]);
}; };
colorutils.scale = function(v, bot, top) { colorutils.scale = function (v, bot, top) {
return colorutils.clamp(bot + v * (top - bot), 0, 1); return colorutils.clamp(bot + v * (top - bot), 0, 1);
}; };
colorutils.unscale = function(v, bot, top) { colorutils.unscale = function (v, bot, top) {
return colorutils.clamp((v - bot) / (top - bot), 0, 1); return colorutils.clamp((v - bot) / (top - bot), 0, 1);
}; };
colorutils.scaleColor = function(c, bot, top) { colorutils.scaleColor = function (c, bot, top) {
return [colorutils.scale(c[0], bot, top), colorutils.scale(c[1], bot, top), colorutils.scale(c[2], bot, top)]; return [colorutils.scale(c[0], bot, top), colorutils.scale(c[1], bot, top), colorutils.scale(c[2], bot, top)];
} };
colorutils.unscaleColor = function(c, bot, top) { colorutils.unscaleColor = function (c, bot, top) {
return [colorutils.unscale(c[0], bot, top), colorutils.unscale(c[1], bot, top), colorutils.unscale(c[2], bot, top)]; return [colorutils.unscale(c[0], bot, top), colorutils.unscale(c[1], bot, top), colorutils.unscale(c[2], bot, top)];
} };
colorutils.luminosity = function(c) { colorutils.luminosity = function (c) {
// rule of thumb for RGB brightness; 1.0 is white // rule of thumb for RGB brightness; 1.0 is white
return c[0] * 0.30 + c[1] * 0.59 + c[2] * 0.11; return c[0] * 0.30 + c[1] * 0.59 + c[2] * 0.11;
} };
colorutils.saturate = function(c) { colorutils.saturate = function (c) {
var min = colorutils.colorMin(c); const min = colorutils.colorMin(c);
var max = colorutils.colorMax(c); const max = colorutils.colorMax(c);
if (max - min <= 0) return [1.0, 1.0, 1.0]; if (max - min <= 0) return [1.0, 1.0, 1.0];
return colorutils.unscaleColor(c, min, max); return colorutils.unscaleColor(c, min, max);
} };
colorutils.blend = function(c1, c2, t) { colorutils.blend = function (c1, c2, t) {
return [colorutils.scale(t, c1[0], c2[0]), colorutils.scale(t, c1[1], c2[1]), colorutils.scale(t, c1[2], c2[2])]; return [colorutils.scale(t, c1[0], c2[0]), colorutils.scale(t, c1[1], c2[1]), colorutils.scale(t, c1[2], c2[2])];
} };
colorutils.invert = function(c) { colorutils.invert = function (c) {
return [1 - c[0], 1 - c[1], 1- c[2]]; return [1 - c[0], 1 - c[1], 1 - c[2]];
} };
colorutils.complementary = function(c) { colorutils.complementary = function (c) {
var inv = colorutils.invert(c); const inv = colorutils.invert(c);
return [ return [
(inv[0] >= c[0]) ? Math.min(inv[0] * 1.30, 1) : (c[0] * 0.30), (inv[0] >= c[0]) ? Math.min(inv[0] * 1.30, 1) : (c[0] * 0.30),
(inv[1] >= c[1]) ? Math.min(inv[1] * 1.59, 1) : (c[1] * 0.59), (inv[1] >= c[1]) ? Math.min(inv[1] * 1.59, 1) : (c[1] * 0.59),
(inv[2] >= c[2]) ? Math.min(inv[2] * 1.11, 1) : (c[2] * 0.11) (inv[2] >= c[2]) ? Math.min(inv[2] * 1.11, 1) : (c[2] * 0.11),
]; ];
} };
colorutils.textColorFromBackgroundColor = function(bgcolor, skinName) { colorutils.textColorFromBackgroundColor = function (bgcolor, skinName) {
var white = skinName == 'colibris' ? 'var(--super-light-color)' : '#fff'; const white = skinName == 'colibris' ? 'var(--super-light-color)' : '#fff';
var black = skinName == 'colibris' ? 'var(--super-dark-color)' : '#222'; const black = skinName == 'colibris' ? 'var(--super-dark-color)' : '#222';
return colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5 ? white : black; return colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5 ? white : black;
} };
exports.colorutils = colorutils; exports.colorutils = colorutils;

File diff suppressed because it is too large Load diff

View file

@ -21,41 +21,34 @@
*/ */
function makeCSSManager(emptyStylesheetTitle, doc) { function makeCSSManager(emptyStylesheetTitle, doc) {
if (doc === true) if (doc === true) {
{
doc = 'parent'; doc = 'parent';
} else if (!doc) { } else if (!doc) {
doc = 'inner'; doc = 'inner';
} }
function getSheetByTitle(title) { function getSheetByTitle(title) {
if (doc === 'parent') if (doc === 'parent') {
{
win = window.parent.parent; win = window.parent.parent;
} } else if (doc === 'inner') {
else if (doc === 'inner') {
win = window; win = window;
} } else if (doc === 'outer') {
else if (doc === 'outer') {
win = window.parent; win = window.parent;
} else {
throw 'Unknown dynamic style container';
} }
else { const allSheets = win.document.styleSheets;
throw "Unknown dynamic style container";
}
var allSheets = win.document.styleSheets;
for (var i = 0; i < allSheets.length; i++) for (let i = 0; i < allSheets.length; i++) {
{ const s = allSheets[i];
var s = allSheets[i]; if (s.title == title) {
if (s.title == title)
{
return s; return s;
} }
} }
return null; return null;
} }
var browserSheet = getSheetByTitle(emptyStylesheetTitle); const browserSheet = getSheetByTitle(emptyStylesheetTitle);
function browserRules() { function browserRules() {
return (browserSheet.cssRules || browserSheet.rules); return (browserSheet.cssRules || browserSheet.rules);
@ -67,16 +60,14 @@ function makeCSSManager(emptyStylesheetTitle, doc) {
} }
function browserInsertRule(i, selector) { function browserInsertRule(i, selector) {
if (browserSheet.insertRule) browserSheet.insertRule(selector + ' {}', i); if (browserSheet.insertRule) browserSheet.insertRule(`${selector} {}`, i);
else browserSheet.addRule(selector, null, i); else browserSheet.addRule(selector, null, i);
} }
var selectorList = []; const selectorList = [];
function indexOfSelector(selector) { function indexOfSelector(selector) {
for (var i = 0; i < selectorList.length; i++) for (let i = 0; i < selectorList.length; i++) {
{ if (selectorList[i] == selector) {
if (selectorList[i] == selector)
{
return i; return i;
} }
} }
@ -84,9 +75,8 @@ function makeCSSManager(emptyStylesheetTitle, doc) {
} }
function selectorStyle(selector) { function selectorStyle(selector) {
var i = indexOfSelector(selector); let i = indexOfSelector(selector);
if (i < 0) if (i < 0) {
{
// add selector // add selector
browserInsertRule(0, selector); browserInsertRule(0, selector);
selectorList.splice(0, 0, selector); selectorList.splice(0, 0, selector);
@ -96,20 +86,19 @@ function makeCSSManager(emptyStylesheetTitle, doc) {
} }
function removeSelectorStyle(selector) { function removeSelectorStyle(selector) {
var i = indexOfSelector(selector); const i = indexOfSelector(selector);
if (i >= 0) if (i >= 0) {
{
browserDeleteRule(i); browserDeleteRule(i);
selectorList.splice(i, 1); selectorList.splice(i, 1);
} }
} }
return { return {
selectorStyle: selectorStyle, selectorStyle,
removeSelectorStyle: removeSelectorStyle, removeSelectorStyle,
info: function() { info() {
return selectorList.length + ":" + browserRules().length; return `${selectorList.length}:${browserRules().length}`;
} },
}; };
} }

View file

@ -26,167 +26,150 @@
// requires: plugins // requires: plugins
// requires: undefined // requires: undefined
var Security = require('./security'); const Security = require('./security');
var hooks = require('./pluginfw/hooks'); const hooks = require('./pluginfw/hooks');
var _ = require('./underscore'); const _ = require('./underscore');
var lineAttributeMarker = require('./linestylefilter').lineAttributeMarker; const lineAttributeMarker = require('./linestylefilter').lineAttributeMarker;
var noop = function(){}; const noop = function () {};
var domline = {}; const domline = {};
domline.addToLineClass = function(lineClass, cls) { domline.addToLineClass = function (lineClass, cls) {
// an "empty span" at any point can be used to add classes to // an "empty span" at any point can be used to add classes to
// the line, using line:className. otherwise, we ignore // the line, using line:className. otherwise, we ignore
// the span. // the span.
cls.replace(/\S+/g, function(c) { cls.replace(/\S+/g, (c) => {
if (c.indexOf("line:") == 0) if (c.indexOf('line:') == 0) {
{
// add class to line // add class to line
lineClass = (lineClass ? lineClass + ' ' : '') + c.substring(5); lineClass = (lineClass ? `${lineClass} ` : '') + c.substring(5);
} }
}); });
return lineClass; return lineClass;
} };
// if "document" is falsy we don't create a DOM node, just // if "document" is falsy we don't create a DOM node, just
// an object with innerHTML and className // an object with innerHTML and className
domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) { domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
var result = { const result = {
node: null, node: null,
appendSpan: noop, appendSpan: noop,
prepareForAdd: noop, prepareForAdd: noop,
notifyAdded: noop, notifyAdded: noop,
clearSpans: noop, clearSpans: noop,
finishUpdate: noop, finishUpdate: noop,
lineMarker: 0 lineMarker: 0,
}; };
var document = optDocument; const document = optDocument;
if (document) if (document) {
{ result.node = document.createElement('div');
result.node = document.createElement("div"); } else {
}
else
{
result.node = { result.node = {
innerHTML: '', innerHTML: '',
className: '' className: '',
}; };
} }
var html = []; let html = [];
var preHtml = '', let preHtml = '';
postHtml = ''; let postHtml = '';
var curHTML = null; let curHTML = null;
function processSpaces(s) { function processSpaces(s) {
return domline.processSpaces(s, doesWrap); return domline.processSpaces(s, doesWrap);
} }
var perTextNodeProcess = (doesWrap ? _.identity : processSpaces); const perTextNodeProcess = (doesWrap ? _.identity : processSpaces);
var perHtmlLineProcess = (doesWrap ? processSpaces : _.identity); const perHtmlLineProcess = (doesWrap ? processSpaces : _.identity);
var lineClass = 'ace-line'; let lineClass = 'ace-line';
result.appendSpan = function(txt, cls) { result.appendSpan = function (txt, cls) {
let processedMarker = false;
var processedMarker = false;
// Handle lineAttributeMarker, if present // Handle lineAttributeMarker, if present
if (cls.indexOf(lineAttributeMarker) >= 0) if (cls.indexOf(lineAttributeMarker) >= 0) {
{ let listType = /(?:^| )list:(\S+)/.exec(cls);
var listType = /(?:^| )list:(\S+)/.exec(cls); const start = /(?:^| )start:(\S+)/.exec(cls);
var start = /(?:^| )start:(\S+)/.exec(cls);
_.map(hooks.callAll("aceDomLinePreProcessLineAttributes", { _.map(hooks.callAll('aceDomLinePreProcessLineAttributes', {
domline: domline, domline,
cls: cls cls,
}), function(modifier) { }), (modifier) => {
preHtml += modifier.preHtml; preHtml += modifier.preHtml;
postHtml += modifier.postHtml; postHtml += modifier.postHtml;
processedMarker |= modifier.processedMarker; processedMarker |= modifier.processedMarker;
}); });
if (listType) if (listType) {
{
listType = listType[1]; listType = listType[1];
if (listType) if (listType) {
{ if (listType.indexOf('number') < 0) {
if(listType.indexOf("number") < 0) preHtml += `<ul class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
{ postHtml = `</li></ul>${postHtml}`;
preHtml += '<ul class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>'; } else {
postHtml = '</li></ul>' + postHtml; if (start) { // is it a start of a list with more than one item in?
} if (start[1] == 1) { // if its the first one at this level?
else lineClass = `${lineClass} ` + `list-start-${listType}`; // Add start class to DIV node
{
if(start){ // is it a start of a list with more than one item in?
if(start[1] == 1){ // if its the first one at this level?
lineClass = lineClass + " " + "list-start-" + listType; // Add start class to DIV node
} }
preHtml += '<ol start='+start[1]+' class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>'; preHtml += `<ol start=${start[1]} class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
}else{ } else {
preHtml += '<ol class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>'; // Handles pasted contents into existing lists preHtml += `<ol class="list-${Security.escapeHTMLAttribute(listType)}"><li>`; // Handles pasted contents into existing lists
} }
postHtml += '</li></ol>'; postHtml += '</li></ol>';
} }
} }
processedMarker = true; processedMarker = true;
} }
_.map(hooks.callAll("aceDomLineProcessLineAttributes", { _.map(hooks.callAll('aceDomLineProcessLineAttributes', {
domline: domline, domline,
cls: cls cls,
}), function(modifier) { }), (modifier) => {
preHtml += modifier.preHtml; preHtml += modifier.preHtml;
postHtml += modifier.postHtml; postHtml += modifier.postHtml;
processedMarker |= modifier.processedMarker; processedMarker |= modifier.processedMarker;
}); });
if( processedMarker ){ if (processedMarker) {
result.lineMarker += txt.length; result.lineMarker += txt.length;
return; // don't append any text return; // don't append any text
} }
} }
var href = null; let href = null;
var simpleTags = null; let simpleTags = null;
if (cls.indexOf('url') >= 0) if (cls.indexOf('url') >= 0) {
{ cls = cls.replace(/(^| )url:(\S+)/g, (x0, space, url) => {
cls = cls.replace(/(^| )url:(\S+)/g, function(x0, space, url) {
href = url; href = url;
return space + "url"; return `${space}url`;
}); });
} }
if (cls.indexOf('tag') >= 0) if (cls.indexOf('tag') >= 0) {
{ cls = cls.replace(/(^| )tag:(\S+)/g, (x0, space, tag) => {
cls = cls.replace(/(^| )tag:(\S+)/g, function(x0, space, tag) {
if (!simpleTags) simpleTags = []; if (!simpleTags) simpleTags = [];
simpleTags.push(tag.toLowerCase()); simpleTags.push(tag.toLowerCase());
return space + tag; return space + tag;
}); });
} }
var extraOpenTags = ""; let extraOpenTags = '';
var extraCloseTags = ""; let extraCloseTags = '';
_.map(hooks.callAll("aceCreateDomLine", { _.map(hooks.callAll('aceCreateDomLine', {
domline: domline, domline,
cls: cls cls,
}), function(modifier) { }), (modifier) => {
cls = modifier.cls; cls = modifier.cls;
extraOpenTags = extraOpenTags + modifier.extraOpenTags; extraOpenTags += modifier.extraOpenTags;
extraCloseTags = modifier.extraCloseTags + extraCloseTags; extraCloseTags = modifier.extraCloseTags + extraCloseTags;
}); });
if ((!txt) && cls) if ((!txt) && cls) {
{
lineClass = domline.addToLineClass(lineClass, cls); lineClass = domline.addToLineClass(lineClass, cls);
} } else if (txt) {
else if (txt) if (href) {
{ urn_schemes = new RegExp('^(about|geo|mailto|tel):');
if (href) if (!~href.indexOf('://') && !urn_schemes.test(href)) // if the url doesn't include a protocol prefix, assume http
{
urn_schemes = new RegExp("^(about|geo|mailto|tel):");
if(!~href.indexOf("://") && !urn_schemes.test(href)) // if the url doesn't include a protocol prefix, assume http
{ {
href = "http://"+href; href = `http://${href}`;
} }
// Using rel="noreferrer" stops leaking the URL/location of the pad when clicking links in the document. // Using rel="noreferrer" stops leaking the URL/location of the pad when clicking links in the document.
// Not all browsers understand this attribute, but it's part of the HTML5 standard. // Not all browsers understand this attribute, but it's part of the HTML5 standard.
@ -195,115 +178,94 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) {
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener // https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
// https://mathiasbynens.github.io/rel-noopener/ // https://mathiasbynens.github.io/rel-noopener/
// https://github.com/ether/etherpad-lite/pull/3636 // https://github.com/ether/etherpad-lite/pull/3636
extraOpenTags = extraOpenTags + '<a href="' + Security.escapeHTMLAttribute(href) + '" rel="noreferrer noopener">'; extraOpenTags = `${extraOpenTags}<a href="${Security.escapeHTMLAttribute(href)}" rel="noreferrer noopener">`;
extraCloseTags = '</a>' + extraCloseTags; extraCloseTags = `</a>${extraCloseTags}`;
} }
if (simpleTags) if (simpleTags) {
{
simpleTags.sort(); simpleTags.sort();
extraOpenTags = extraOpenTags + '<' + simpleTags.join('><') + '>'; extraOpenTags = `${extraOpenTags}<${simpleTags.join('><')}>`;
simpleTags.reverse(); simpleTags.reverse();
extraCloseTags = '</' + simpleTags.join('></') + '>' + extraCloseTags; extraCloseTags = `</${simpleTags.join('></')}>${extraCloseTags}`;
} }
html.push('<span class="', Security.escapeHTMLAttribute(cls || ''), '">', extraOpenTags, perTextNodeProcess(Security.escapeHTML(txt)), extraCloseTags, '</span>'); html.push('<span class="', Security.escapeHTMLAttribute(cls || ''), '">', extraOpenTags, perTextNodeProcess(Security.escapeHTML(txt)), extraCloseTags, '</span>');
} }
}; };
result.clearSpans = function() { result.clearSpans = function () {
html = []; html = [];
lineClass = 'ace-line'; lineClass = 'ace-line';
result.lineMarker = 0; result.lineMarker = 0;
}; };
function writeHTML() { function writeHTML() {
var newHTML = perHtmlLineProcess(html.join('')); let newHTML = perHtmlLineProcess(html.join(''));
if (!newHTML) if (!newHTML) {
{ if ((!document) || (!optBrowser)) {
if ((!document) || (!optBrowser))
{
newHTML += '&nbsp;'; newHTML += '&nbsp;';
} } else if (!optBrowser.msie) {
else if (!optBrowser.msie)
{
newHTML += '<br/>'; newHTML += '<br/>';
} }
} }
if (nonEmpty) if (nonEmpty) {
{
newHTML = (preHtml || '') + newHTML + (postHtml || ''); newHTML = (preHtml || '') + newHTML + (postHtml || '');
} }
html = preHtml = postHtml = ''; // free memory html = preHtml = postHtml = ''; // free memory
if (newHTML !== curHTML) if (newHTML !== curHTML) {
{
curHTML = newHTML; curHTML = newHTML;
result.node.innerHTML = curHTML; result.node.innerHTML = curHTML;
} }
if (lineClass !== null) result.node.className = lineClass; if (lineClass !== null) result.node.className = lineClass;
hooks.callAll("acePostWriteDomLineHTML", { hooks.callAll('acePostWriteDomLineHTML', {
node: result.node node: result.node,
}); });
} }
result.prepareForAdd = writeHTML; result.prepareForAdd = writeHTML;
result.finishUpdate = writeHTML; result.finishUpdate = writeHTML;
result.getInnerHTML = function() { result.getInnerHTML = function () {
return curHTML || ''; return curHTML || '';
}; };
return result; return result;
}; };
domline.processSpaces = function(s, doesWrap) { domline.processSpaces = function (s, doesWrap) {
if (s.indexOf("<") < 0 && !doesWrap) if (s.indexOf('<') < 0 && !doesWrap) {
{
// short-cut // short-cut
return s.replace(/ /g, '&nbsp;'); return s.replace(/ /g, '&nbsp;');
} }
var parts = []; const parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, function(m) { s.replace(/<[^>]*>?| |[^ <]+/g, (m) => {
parts.push(m); parts.push(m);
}); });
if (doesWrap) if (doesWrap) {
{ let endOfLine = true;
var endOfLine = true; let beforeSpace = false;
var beforeSpace = false;
// last space in a run is normal, others are nbsp, // last space in a run is normal, others are nbsp,
// end of line is nbsp // end of line is nbsp
for (var i = parts.length - 1; i >= 0; i--) for (var i = parts.length - 1; i >= 0; i--) {
{
var p = parts[i]; var p = parts[i];
if (p == " ") if (p == ' ') {
{
if (endOfLine || beforeSpace) parts[i] = '&nbsp;'; if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false; endOfLine = false;
beforeSpace = true; beforeSpace = true;
} } else if (p.charAt(0) != '<') {
else if (p.charAt(0) != "<")
{
endOfLine = false; endOfLine = false;
beforeSpace = false; beforeSpace = false;
} }
} }
// beginning of line is nbsp // beginning of line is nbsp
for (var i = 0; i < parts.length; i++) for (var i = 0; i < parts.length; i++) {
{
var p = parts[i]; var p = parts[i];
if (p == " ") if (p == ' ') {
{
parts[i] = '&nbsp;'; parts[i] = '&nbsp;';
break; break;
} } else if (p.charAt(0) != '<') {
else if (p.charAt(0) != "<")
{
break; break;
} }
} }
} } else {
else for (var i = 0; i < parts.length; i++) {
{
for (var i = 0; i < parts.length; i++)
{
var p = parts[i]; var p = parts[i];
if (p == " ") if (p == ' ') {
{
parts[i] = '&nbsp;'; parts[i] = '&nbsp;';
} }
} }

View file

@ -33,25 +33,25 @@ function randomPadName() {
for (let i = 0; i < string_length; i++) { for (let i = 0; i < string_length; i++) {
// instead of writing "Math.floor(randomarray[i]/256*64)" // instead of writing "Math.floor(randomarray[i]/256*64)"
// we can save some cycles. // we can save some cycles.
const rnum = Math.floor(randomarray[i]/4); const rnum = Math.floor(randomarray[i] / 4);
randomstring += chars.substring(rnum, rnum + 1); randomstring += chars.substring(rnum, rnum + 1);
} }
return randomstring; return randomstring;
} }
$(() => { $(() => {
$('#go2Name').submit(function() { $('#go2Name').submit(() => {
const padname = $('#padname').val(); const padname = $('#padname').val();
if (padname.length > 0) { if (padname.length > 0) {
window.location = 'p/' + encodeURIComponent(padname.trim()); window.location = `p/${encodeURIComponent(padname.trim())}`;
} else { } else {
alert('Please enter a name'); alert('Please enter a name');
} }
return false; return false;
}); });
$('#button').click(function() { $('#button').click(() => {
window.location = 'p/' + randomPadName(); window.location = `p/${randomPadName()}`;
}); });
// start the custom js // start the custom js

View file

@ -1,14 +1,14 @@
(function(document) { (function (document) {
// Set language for l10n // Set language for l10n
var language = document.cookie.match(/language=((\w{2,3})(-\w+)?)/); let language = document.cookie.match(/language=((\w{2,3})(-\w+)?)/);
if(language) language = language[1]; if (language) language = language[1];
html10n.bind('indexed', function() { html10n.bind('indexed', () => {
html10n.localize([language, navigator.language, navigator.userLanguage, 'en']) html10n.localize([language, navigator.language, navigator.userLanguage, 'en']);
}) });
html10n.bind('localized', function() { html10n.bind('localized', () => {
document.documentElement.lang = html10n.getLanguage() document.documentElement.lang = html10n.getLanguage();
document.documentElement.dir = html10n.getDirection() document.documentElement.dir = html10n.getDirection();
}) });
})(document) })(document);

View file

@ -28,33 +28,32 @@
// requires: plugins // requires: plugins
// requires: undefined // requires: undefined
var Changeset = require('./Changeset'); const Changeset = require('./Changeset');
var hooks = require('./pluginfw/hooks'); const hooks = require('./pluginfw/hooks');
var linestylefilter = {}; const linestylefilter = {};
var _ = require('./underscore'); const _ = require('./underscore');
var AttributeManager = require('./AttributeManager'); const AttributeManager = require('./AttributeManager');
linestylefilter.ATTRIB_CLASSES = { linestylefilter.ATTRIB_CLASSES = {
'bold': 'tag:b', bold: 'tag:b',
'italic': 'tag:i', italic: 'tag:i',
'underline': 'tag:u', underline: 'tag:u',
'strikethrough': 'tag:s' strikethrough: 'tag:s',
}; };
var lineAttributeMarker = 'lineAttribMarker'; const lineAttributeMarker = 'lineAttribMarker';
exports.lineAttributeMarker = lineAttributeMarker; exports.lineAttributeMarker = lineAttributeMarker;
linestylefilter.getAuthorClassName = function(author) { linestylefilter.getAuthorClassName = function (author) {
return "author-" + author.replace(/[^a-y0-9]/g, function(c) { return `author-${author.replace(/[^a-y0-9]/g, (c) => {
if (c == ".") return "-"; if (c == '.') return '-';
return 'z' + c.charCodeAt(0) + 'z'; return `z${c.charCodeAt(0)}z`;
}); })}`;
}; };
// lineLength is without newline; aline includes newline, // lineLength is without newline; aline includes newline,
// but may be falsy if lineLength == 0 // but may be falsy if lineLength == 0
linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFunc, apool) { linestylefilter.getLineStyleFilter = function (lineLength, aline, textAndClassFunc, apool) {
// Plugin Hook to add more Attrib Classes // Plugin Hook to add more Attrib Classes
for (const attribClasses of hooks.callAll('aceAttribClasses', linestylefilter.ATTRIB_CLASSES)) { for (const attribClasses of hooks.callAll('aceAttribClasses', linestylefilter.ATTRIB_CLASSES)) {
Object.assign(linestylefilter.ATTRIB_CLASSES, attribClasses); Object.assign(linestylefilter.ATTRIB_CLASSES, attribClasses);
@ -62,64 +61,54 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
if (lineLength == 0) return textAndClassFunc; if (lineLength == 0) return textAndClassFunc;
var nextAfterAuthorColors = textAndClassFunc; const nextAfterAuthorColors = textAndClassFunc;
var authorColorFunc = (function() { const authorColorFunc = (function () {
var lineEnd = lineLength; const lineEnd = lineLength;
var curIndex = 0; let curIndex = 0;
var extraClasses; let extraClasses;
var leftInAuthor; let leftInAuthor;
function attribsToClasses(attribs) { function attribsToClasses(attribs) {
var classes = ''; let classes = '';
var isLineAttribMarker = false; let isLineAttribMarker = false;
// For each attribute number // For each attribute number
Changeset.eachAttribNumber(attribs, function(n) { Changeset.eachAttribNumber(attribs, (n) => {
// Give us this attributes key // Give us this attributes key
var key = apool.getAttribKey(n); const key = apool.getAttribKey(n);
if (key) if (key) {
{ const value = apool.getAttribValue(n);
var value = apool.getAttribValue(n); if (value) {
if (value) if (!isLineAttribMarker && _.indexOf(AttributeManager.lineAttributes, key) >= 0) {
{
if (!isLineAttribMarker && _.indexOf(AttributeManager.lineAttributes, key) >= 0){
isLineAttribMarker = true; isLineAttribMarker = true;
} }
if (key == 'author') if (key == 'author') {
{ classes += ` ${linestylefilter.getAuthorClassName(value)}`;
classes += ' ' + linestylefilter.getAuthorClassName(value); } else if (key == 'list') {
} classes += ` list:${value}`;
else if (key == 'list') } else if (key == 'start') {
{
classes += ' list:' + value;
}
else if (key == 'start'){
// Needed to introduce the correct Ordered list item start number on import // Needed to introduce the correct Ordered list item start number on import
classes += ' start:' + value; classes += ` start:${value}`;
} } else if (linestylefilter.ATTRIB_CLASSES[key]) {
else if (linestylefilter.ATTRIB_CLASSES[key]) classes += ` ${linestylefilter.ATTRIB_CLASSES[key]}`;
{ } else {
classes += ' ' + linestylefilter.ATTRIB_CLASSES[key]; classes += hooks.callAllStr('aceAttribsToClasses', {
} linestylefilter,
else key,
{ value,
classes += hooks.callAllStr("aceAttribsToClasses", { }, ' ', ' ', '');
linestylefilter: linestylefilter,
key: key,
value: value
}, " ", " ", "");
} }
} }
} }
}); });
if(isLineAttribMarker) classes += ' ' + lineAttributeMarker; if (isLineAttribMarker) classes += ` ${lineAttributeMarker}`;
return classes.substring(1); return classes.substring(1);
} }
var attributionIter = Changeset.opIterator(aline); const attributionIter = Changeset.opIterator(aline);
var nextOp, nextOpClasses; let nextOp, nextOpClasses;
function goNextOp() { function goNextOp() {
nextOp = attributionIter.next(); nextOp = attributionIter.next();
@ -128,13 +117,11 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
goNextOp(); goNextOp();
function nextClasses() { function nextClasses() {
if (curIndex < lineEnd) if (curIndex < lineEnd) {
{
extraClasses = nextOpClasses; extraClasses = nextOpClasses;
leftInAuthor = nextOp.chars; leftInAuthor = nextOp.chars;
goNextOp(); goNextOp();
while (nextOp.opcode && nextOpClasses == extraClasses) while (nextOp.opcode && nextOpClasses == extraClasses) {
{
leftInAuthor += nextOp.chars; leftInAuthor += nextOp.chars;
goNextOp(); goNextOp();
} }
@ -142,33 +129,28 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
} }
nextClasses(); nextClasses();
return function(txt, cls) { return function (txt, cls) {
const disableAuthColorForThisLine = hooks.callAll('disableAuthorColorsForThisLine', {
var disableAuthColorForThisLine = hooks.callAll("disableAuthorColorsForThisLine", { linestylefilter,
linestylefilter: linestylefilter,
text: txt, text: txt,
"class": cls class: cls,
}, " ", " ", ""); }, ' ', ' ', '');
var disableAuthors = (disableAuthColorForThisLine==null||disableAuthColorForThisLine.length==0)?false:disableAuthColorForThisLine[0]; const disableAuthors = (disableAuthColorForThisLine == null || disableAuthColorForThisLine.length == 0) ? false : disableAuthColorForThisLine[0];
while (txt.length > 0) while (txt.length > 0) {
{ if (leftInAuthor <= 0 || disableAuthors) {
if (leftInAuthor <= 0 || disableAuthors)
{
// prevent infinite loop if something funny's going on // prevent infinite loop if something funny's going on
return nextAfterAuthorColors(txt, cls); return nextAfterAuthorColors(txt, cls);
} }
var spanSize = txt.length; let spanSize = txt.length;
if (spanSize > leftInAuthor) if (spanSize > leftInAuthor) {
{
spanSize = leftInAuthor; spanSize = leftInAuthor;
} }
var curTxt = txt.substring(0, spanSize); const curTxt = txt.substring(0, spanSize);
txt = txt.substring(spanSize); txt = txt.substring(spanSize);
nextAfterAuthorColors(curTxt, (cls && cls + " ") + extraClasses); nextAfterAuthorColors(curTxt, (cls && `${cls} `) + extraClasses);
curIndex += spanSize; curIndex += spanSize;
leftInAuthor -= spanSize; leftInAuthor -= spanSize;
if (leftInAuthor == 0) if (leftInAuthor == 0) {
{
nextClasses(); nextClasses();
} }
} }
@ -177,15 +159,13 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
return authorColorFunc; return authorColorFunc;
}; };
linestylefilter.getAtSignSplitterFilter = function(lineText, textAndClassFunc) { linestylefilter.getAtSignSplitterFilter = function (lineText, textAndClassFunc) {
var at = /@/g; const at = /@/g;
at.lastIndex = 0; at.lastIndex = 0;
var splitPoints = null; let splitPoints = null;
var execResult; let execResult;
while ((execResult = at.exec(lineText))) while ((execResult = at.exec(lineText))) {
{ if (!splitPoints) {
if (!splitPoints)
{
splitPoints = []; splitPoints = [];
} }
splitPoints.push(execResult.index); splitPoints.push(execResult.index);
@ -196,21 +176,19 @@ linestylefilter.getAtSignSplitterFilter = function(lineText, textAndClassFunc) {
return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints); return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
}; };
linestylefilter.getRegexpFilter = function(regExp, tag) { linestylefilter.getRegexpFilter = function (regExp, tag) {
return function(lineText, textAndClassFunc) { return function (lineText, textAndClassFunc) {
regExp.lastIndex = 0; regExp.lastIndex = 0;
var regExpMatchs = null; let regExpMatchs = null;
var splitPoints = null; let splitPoints = null;
var execResult; let execResult;
while ((execResult = regExp.exec(lineText))) while ((execResult = regExp.exec(lineText))) {
{ if (!regExpMatchs) {
if (!regExpMatchs)
{
regExpMatchs = []; regExpMatchs = [];
splitPoints = []; splitPoints = [];
} }
var startIndex = execResult.index; const startIndex = execResult.index;
var regExpMatch = execResult[0]; const regExpMatch = execResult[0];
regExpMatchs.push([startIndex, regExpMatch]); regExpMatchs.push([startIndex, regExpMatch]);
splitPoints.push(startIndex, startIndex + regExpMatch.length); splitPoints.push(startIndex, startIndex + regExpMatch.length);
} }
@ -218,26 +196,23 @@ linestylefilter.getRegexpFilter = function(regExp, tag) {
if (!regExpMatchs) return textAndClassFunc; if (!regExpMatchs) return textAndClassFunc;
function regExpMatchForIndex(idx) { function regExpMatchForIndex(idx) {
for (var k = 0; k < regExpMatchs.length; k++) for (let k = 0; k < regExpMatchs.length; k++) {
{ const u = regExpMatchs[k];
var u = regExpMatchs[k]; if (idx >= u[0] && idx < u[0] + u[1].length) {
if (idx >= u[0] && idx < u[0] + u[1].length)
{
return u[1]; return u[1];
} }
} }
return false; return false;
} }
var handleRegExpMatchsAfterSplit = (function() { const handleRegExpMatchsAfterSplit = (function () {
var curIndex = 0; let curIndex = 0;
return function(txt, cls) { return function (txt, cls) {
var txtlen = txt.length; const txtlen = txt.length;
var newCls = cls; let newCls = cls;
var regExpMatch = regExpMatchForIndex(curIndex); const regExpMatch = regExpMatchForIndex(curIndex);
if (regExpMatch) if (regExpMatch) {
{ newCls += ` ${tag}:${regExpMatch}`;
newCls += " " + tag + ":" + regExpMatch;
} }
textAndClassFunc(txt, newCls); textAndClassFunc(txt, newCls);
curIndex += txtlen; curIndex += txtlen;
@ -250,45 +225,36 @@ linestylefilter.getRegexpFilter = function(regExp, tag) {
linestylefilter.REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; linestylefilter.REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
linestylefilter.REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#!;()$]/.source + '|' + linestylefilter.REGEX_WORDCHAR.source + ')'); linestylefilter.REGEX_URLCHAR = new RegExp(`(${/[-:@a-zA-Z0-9_.,~%+\/\\?=&#!;()$]/.source}|${linestylefilter.REGEX_WORDCHAR.source})`);
linestylefilter.REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|(about|geo|mailto|tel):|www\.)/.source + linestylefilter.REGEX_URLCHAR.source + '*(?![:.,;])' + linestylefilter.REGEX_URLCHAR.source, 'g'); linestylefilter.REGEX_URL = new RegExp(`${/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|(about|geo|mailto|tel):|www\.)/.source + linestylefilter.REGEX_URLCHAR.source}*(?![:.,;])${linestylefilter.REGEX_URLCHAR.source}`, 'g');
linestylefilter.getURLFilter = linestylefilter.getRegexpFilter( linestylefilter.getURLFilter = linestylefilter.getRegexpFilter(
linestylefilter.REGEX_URL, 'url'); linestylefilter.REGEX_URL, 'url');
linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt) { linestylefilter.textAndClassFuncSplitter = function (func, splitPointsOpt) {
var nextPointIndex = 0; let nextPointIndex = 0;
var idx = 0; let idx = 0;
// don't split at 0 // don't split at 0
while (splitPointsOpt && nextPointIndex < splitPointsOpt.length && splitPointsOpt[nextPointIndex] == 0) while (splitPointsOpt && nextPointIndex < splitPointsOpt.length && splitPointsOpt[nextPointIndex] == 0) {
{
nextPointIndex++; nextPointIndex++;
} }
function spanHandler(txt, cls) { function spanHandler(txt, cls) {
if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length) if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length) {
{
func(txt, cls); func(txt, cls);
idx += txt.length; idx += txt.length;
} } else {
else const splitPoints = splitPointsOpt;
{ const pointLocInSpan = splitPoints[nextPointIndex] - idx;
var splitPoints = splitPointsOpt; const txtlen = txt.length;
var pointLocInSpan = splitPoints[nextPointIndex] - idx; if (pointLocInSpan >= txtlen) {
var txtlen = txt.length;
if (pointLocInSpan >= txtlen)
{
func(txt, cls); func(txt, cls);
idx += txt.length; idx += txt.length;
if (pointLocInSpan == txtlen) if (pointLocInSpan == txtlen) {
{
nextPointIndex++; nextPointIndex++;
} }
} } else {
else if (pointLocInSpan > 0) {
{
if (pointLocInSpan > 0)
{
func(txt.substring(0, pointLocInSpan), cls); func(txt.substring(0, pointLocInSpan), cls);
idx += pointLocInSpan; idx += pointLocInSpan;
} }
@ -301,34 +267,32 @@ linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt) {
return spanHandler; return spanHandler;
}; };
linestylefilter.getFilterStack = function(lineText, textAndClassFunc, abrowser) { linestylefilter.getFilterStack = function (lineText, textAndClassFunc, abrowser) {
var func = linestylefilter.getURLFilter(lineText, textAndClassFunc); let func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
var hookFilters = hooks.callAll("aceGetFilterStack", { const hookFilters = hooks.callAll('aceGetFilterStack', {
linestylefilter: linestylefilter, linestylefilter,
browser: abrowser browser: abrowser,
}); });
_.map(hookFilters ,function(hookFilter) { _.map(hookFilters, (hookFilter) => {
func = hookFilter(lineText, func); func = hookFilter(lineText, func);
}); });
if (abrowser !== undefined && abrowser.msie) if (abrowser !== undefined && abrowser.msie) {
{
// IE7+ will take an e-mail address like <foo@bar.com> and linkify it to foo@bar.com. // IE7+ will take an e-mail address like <foo@bar.com> and linkify it to foo@bar.com.
// We then normalize it back to text with no angle brackets. It's weird. So always // We then normalize it back to text with no angle brackets. It's weird. So always
// break spans at an "at" sign. // break spans at an "at" sign.
func = linestylefilter.getAtSignSplitterFilter( func = linestylefilter.getAtSignSplitterFilter(
lineText, func); lineText, func);
} }
return func; return func;
}; };
// domLineObj is like that returned by domline.createDomLine // domLineObj is like that returned by domline.createDomLine
linestylefilter.populateDomLine = function(textLine, aline, apool, domLineObj) { linestylefilter.populateDomLine = function (textLine, aline, apool, domLineObj) {
// remove final newline from text if any // remove final newline from text if any
var text = textLine; let text = textLine;
if (text.slice(-1) == '\n') if (text.slice(-1) == '\n') {
{
text = text.substring(0, text.length - 1); text = text.substring(0, text.length - 1);
} }
@ -336,7 +300,7 @@ linestylefilter.populateDomLine = function(textLine, aline, apool, domLineObj) {
domLineObj.appendSpan(tokenText, tokenClass); domLineObj.appendSpan(tokenText, tokenClass);
} }
var func = linestylefilter.getFilterStack(text, textAndClassFunc); let func = linestylefilter.getFilterStack(text, textAndClassFunc);
func = linestylefilter.getLineStyleFilter(text.length, aline, func, apool); func = linestylefilter.getLineStyleFilter(text.length, aline, func, apool);
func(text, ''); func(text, '');
}; };

File diff suppressed because it is too large Load diff

View file

@ -1,24 +1,23 @@
exports.showCountDownTimerToReconnectOnModal = function ($modal, pad) {
exports.showCountDownTimerToReconnectOnModal = function($modal, pad) {
if (clientVars.automaticReconnectionTimeout && $modal.is('.with_reconnect_timer')) { if (clientVars.automaticReconnectionTimeout && $modal.is('.with_reconnect_timer')) {
createCountDownElementsIfNecessary($modal); createCountDownElementsIfNecessary($modal);
var timer = createTimerForModal($modal, pad); const timer = createTimerForModal($modal, pad);
$modal.find('#cancelreconnect').one('click', function() { $modal.find('#cancelreconnect').one('click', () => {
timer.cancel(); timer.cancel();
disableAutomaticReconnection($modal); disableAutomaticReconnection($modal);
}); });
enableAutomaticReconnection($modal); enableAutomaticReconnection($modal);
} }
} };
var createCountDownElementsIfNecessary = function($modal) { var createCountDownElementsIfNecessary = function ($modal) {
var elementsDoNotExist = $modal.find('#cancelreconnect').length === 0; const elementsDoNotExist = $modal.find('#cancelreconnect').length === 0;
if (elementsDoNotExist) { if (elementsDoNotExist) {
var $defaultMessage = $modal.find('#defaulttext'); const $defaultMessage = $modal.find('#defaulttext');
var $reconnectButton = $modal.find('#forcereconnect'); const $reconnectButton = $modal.find('#forcereconnect');
// create extra DOM elements, if they don't exist // create extra DOM elements, if they don't exist
const $reconnectTimerMessage = const $reconnectTimerMessage =
@ -44,100 +43,100 @@ var createCountDownElementsIfNecessary = function($modal) {
$reconnectTimerMessage.insertAfter($defaultMessage); $reconnectTimerMessage.insertAfter($defaultMessage);
$cancelReconnect.insertAfter($reconnectButton); $cancelReconnect.insertAfter($reconnectButton);
} }
} };
var localize = function($element) { var localize = function ($element) {
html10n.translateElement(html10n.translations, $element.get(0)); html10n.translateElement(html10n.translations, $element.get(0));
}; };
var createTimerForModal = function($modal, pad) { var createTimerForModal = function ($modal, pad) {
var timeUntilReconnection = clientVars.automaticReconnectionTimeout * reconnectionTries.nextTry(); const timeUntilReconnection = clientVars.automaticReconnectionTimeout * reconnectionTries.nextTry();
var timer = new CountDownTimer(timeUntilReconnection); const timer = new CountDownTimer(timeUntilReconnection);
timer.onTick(function(minutes, seconds) { timer.onTick((minutes, seconds) => {
updateCountDownTimerMessage($modal, minutes, seconds); updateCountDownTimerMessage($modal, minutes, seconds);
}).onExpire(function() { }).onExpire(() => {
var wasANetworkError = $modal.is('.disconnected'); const wasANetworkError = $modal.is('.disconnected');
if (wasANetworkError) { if (wasANetworkError) {
// cannot simply reconnect, client is having issues to establish connection to server // cannot simply reconnect, client is having issues to establish connection to server
waitUntilClientCanConnectToServerAndThen(function() { forceReconnection($modal); }, pad); waitUntilClientCanConnectToServerAndThen(() => { forceReconnection($modal); }, pad);
} else { } else {
forceReconnection($modal); forceReconnection($modal);
} }
}).start(); }).start();
return timer; return timer;
} };
var disableAutomaticReconnection = function($modal) { var disableAutomaticReconnection = function ($modal) {
toggleAutomaticReconnectionOption($modal, true); toggleAutomaticReconnectionOption($modal, true);
} };
var enableAutomaticReconnection = function($modal) { var enableAutomaticReconnection = function ($modal) {
toggleAutomaticReconnectionOption($modal, false); toggleAutomaticReconnectionOption($modal, false);
} };
var toggleAutomaticReconnectionOption = function($modal, disableAutomaticReconnect) { var toggleAutomaticReconnectionOption = function ($modal, disableAutomaticReconnect) {
$modal.find('#cancelreconnect, .reconnecttimer').toggleClass('hidden', disableAutomaticReconnect); $modal.find('#cancelreconnect, .reconnecttimer').toggleClass('hidden', disableAutomaticReconnect);
$modal.find('#defaulttext').toggleClass('hidden', !disableAutomaticReconnect); $modal.find('#defaulttext').toggleClass('hidden', !disableAutomaticReconnect);
} };
var waitUntilClientCanConnectToServerAndThen = function(callback, pad) { var waitUntilClientCanConnectToServerAndThen = function (callback, pad) {
whenConnectionIsRestablishedWithServer(callback, pad); whenConnectionIsRestablishedWithServer(callback, pad);
pad.socket.connect(); pad.socket.connect();
} };
var whenConnectionIsRestablishedWithServer = function(callback, pad) { var whenConnectionIsRestablishedWithServer = function (callback, pad) {
// only add listener for the first try, don't need to add another listener // only add listener for the first try, don't need to add another listener
// on every unsuccessful try // on every unsuccessful try
if (reconnectionTries.counter === 1) { if (reconnectionTries.counter === 1) {
pad.socket.once('connect', callback); pad.socket.once('connect', callback);
} }
} };
var forceReconnection = function($modal) { var forceReconnection = function ($modal) {
$modal.find('#forcereconnect').click(); $modal.find('#forcereconnect').click();
} };
var updateCountDownTimerMessage = function($modal, minutes, seconds) { var updateCountDownTimerMessage = function ($modal, minutes, seconds) {
minutes = minutes < 10 ? '0' + minutes : minutes; minutes = minutes < 10 ? `0${minutes}` : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds; seconds = seconds < 10 ? `0${seconds}` : seconds;
$modal.find('.timetoexpire').text(minutes + ':' + seconds); $modal.find('.timetoexpire').text(`${minutes}:${seconds}`);
} };
// store number of tries to reconnect to server, in order to increase time to wait // store number of tries to reconnect to server, in order to increase time to wait
// until next try // until next try
var reconnectionTries = { var reconnectionTries = {
counter: 0, counter: 0,
nextTry: function() { nextTry() {
// double the time to try to reconnect on every time reconnection fails // double the time to try to reconnect on every time reconnection fails
var nextCounterFactor = Math.pow(2, this.counter); const nextCounterFactor = Math.pow(2, this.counter);
this.counter++; this.counter++;
return nextCounterFactor; return nextCounterFactor;
} },
} };
// Timer based on http://stackoverflow.com/a/20618517. // Timer based on http://stackoverflow.com/a/20618517.
// duration: how many **seconds** until the timer ends // duration: how many **seconds** until the timer ends
// granularity (optional): how many **milliseconds** between each 'tick' of timer. Default: 1000ms (1s) // granularity (optional): how many **milliseconds** between each 'tick' of timer. Default: 1000ms (1s)
var CountDownTimer = function(duration, granularity) { var CountDownTimer = function (duration, granularity) {
this.duration = duration; this.duration = duration;
this.granularity = granularity || 1000; this.granularity = granularity || 1000;
this.running = false; this.running = false;
this.onTickCallbacks = []; this.onTickCallbacks = [];
this.onExpireCallbacks = []; this.onExpireCallbacks = [];
} };
CountDownTimer.prototype.start = function() { CountDownTimer.prototype.start = function () {
if (this.running) { if (this.running) {
return; return;
} }
this.running = true; this.running = true;
var start = Date.now(), const start = Date.now();
that = this, const that = this;
diff; let diff;
(function timer() { (function timer() {
diff = that.duration - Math.floor((Date.now() - start) / 1000); diff = that.duration - Math.floor((Date.now() - start) / 1000);
@ -153,41 +152,41 @@ CountDownTimer.prototype.start = function() {
}()); }());
}; };
CountDownTimer.prototype.tick = function(diff) { CountDownTimer.prototype.tick = function (diff) {
var obj = CountDownTimer.parse(diff); const obj = CountDownTimer.parse(diff);
this.onTickCallbacks.forEach(function(callback) { this.onTickCallbacks.forEach(function (callback) {
callback.call(this, obj.minutes, obj.seconds); callback.call(this, obj.minutes, obj.seconds);
}, this); }, this);
} };
CountDownTimer.prototype.expire = function() { CountDownTimer.prototype.expire = function () {
this.onExpireCallbacks.forEach(function(callback) { this.onExpireCallbacks.forEach(function (callback) {
callback.call(this); callback.call(this);
}, this); }, this);
} };
CountDownTimer.prototype.onTick = function(callback) { CountDownTimer.prototype.onTick = function (callback) {
if (typeof callback === 'function') { if (typeof callback === 'function') {
this.onTickCallbacks.push(callback); this.onTickCallbacks.push(callback);
} }
return this; return this;
}; };
CountDownTimer.prototype.onExpire = function(callback) { CountDownTimer.prototype.onExpire = function (callback) {
if (typeof callback === 'function') { if (typeof callback === 'function') {
this.onExpireCallbacks.push(callback); this.onExpireCallbacks.push(callback);
} }
return this; return this;
}; };
CountDownTimer.prototype.cancel = function() { CountDownTimer.prototype.cancel = function () {
this.running = false; this.running = false;
clearTimeout(this.timeoutId); clearTimeout(this.timeoutId);
return this; return this;
}; };
CountDownTimer.parse = function(seconds) { CountDownTimer.parse = function (seconds) {
return { return {
'minutes': (seconds / 60) | 0, minutes: (seconds / 60) | 0,
'seconds': (seconds % 60) | 0 seconds: (seconds % 60) | 0,
}; };
}; };

View file

@ -20,42 +20,40 @@
* limitations under the License. * limitations under the License.
*/ */
var padmodals = require('./pad_modals').padmodals; const padmodals = require('./pad_modals').padmodals;
var padconnectionstatus = (function() { const padconnectionstatus = (function () {
let status = {
var status = { what: 'connecting',
what: 'connecting'
}; };
var self = { const self = {
init: function() { init() {
$('button#forcereconnect').click(function() { $('button#forcereconnect').click(() => {
window.location.reload(); window.location.reload();
}); });
}, },
connected: function() { connected() {
status = { status = {
what: 'connected' what: 'connected',
}; };
padmodals.showModal('connected'); padmodals.showModal('connected');
padmodals.hideOverlay(); padmodals.hideOverlay();
}, },
reconnecting: function() { reconnecting() {
status = { status = {
what: 'reconnecting' what: 'reconnecting',
}; };
padmodals.showModal('reconnecting'); padmodals.showModal('reconnecting');
padmodals.showOverlay(); padmodals.showOverlay();
}, },
disconnected: function(msg) { disconnected(msg) {
if(status.what == "disconnected") if (status.what == 'disconnected') return;
return;
status = { status = {
what: 'disconnected', what: 'disconnected',
why: msg why: msg,
}; };
// These message IDs correspond to localized strings that are presented to the user. If a new // These message IDs correspond to localized strings that are presented to the user. If a new
@ -83,12 +81,12 @@ var padconnectionstatus = (function() {
padmodals.showModal(k); padmodals.showModal(k);
padmodals.showOverlay(); padmodals.showOverlay();
}, },
isFullyConnected: function() { isFullyConnected() {
return status.what == 'connected'; return status.what == 'connected';
}, },
getStatus: function() { getStatus() {
return status; return status;
} },
}; };
return self; return self;
}()); }());

View file

@ -20,150 +20,139 @@
* limitations under the License. * limitations under the License.
*/ */
var browser = require('./browser'); const browser = require('./browser');
var hooks = require('./pluginfw/hooks'); const hooks = require('./pluginfw/hooks');
var padutils = require('./pad_utils').padutils; const padutils = require('./pad_utils').padutils;
var padeditor = require('./pad_editor').padeditor; const padeditor = require('./pad_editor').padeditor;
var padsavedrevs = require('./pad_savedrevs'); const padsavedrevs = require('./pad_savedrevs');
var _ = require('ep_etherpad-lite/static/js/underscore'); const _ = require('ep_etherpad-lite/static/js/underscore');
require('ep_etherpad-lite/static/js/vendors/nice-select'); require('ep_etherpad-lite/static/js/vendors/nice-select');
var ToolbarItem = function (element) { const ToolbarItem = function (element) {
this.$el = element; this.$el = element;
}; };
ToolbarItem.prototype.getCommand = function () { ToolbarItem.prototype.getCommand = function () {
return this.$el.attr("data-key"); return this.$el.attr('data-key');
}; };
ToolbarItem.prototype.getValue = function () { ToolbarItem.prototype.getValue = function () {
if (this.isSelect()) { if (this.isSelect()) {
return this.$el.find("select").val(); return this.$el.find('select').val();
} }
}; };
ToolbarItem.prototype.setValue = function (val) { ToolbarItem.prototype.setValue = function (val) {
if (this.isSelect()) { if (this.isSelect()) {
return this.$el.find("select").val(val); return this.$el.find('select').val(val);
} }
}; };
ToolbarItem.prototype.getType = function () { ToolbarItem.prototype.getType = function () {
return this.$el.attr("data-type"); return this.$el.attr('data-type');
}; };
ToolbarItem.prototype.isSelect = function () { ToolbarItem.prototype.isSelect = function () {
return this.getType() == "select"; return this.getType() == 'select';
}; };
ToolbarItem.prototype.isButton = function () { ToolbarItem.prototype.isButton = function () {
return this.getType() == "button"; return this.getType() == 'button';
}; };
ToolbarItem.prototype.bind = function (callback) { ToolbarItem.prototype.bind = function (callback) {
var self = this; const self = this;
if (self.isButton()) { if (self.isButton()) {
self.$el.click(function (event) { self.$el.click((event) => {
$(':focus').blur(); $(':focus').blur();
callback(self.getCommand(), self); callback(self.getCommand(), self);
event.preventDefault(); event.preventDefault();
}); });
} } else if (self.isSelect()) {
else if (self.isSelect()) { self.$el.find('select').change(() => {
self.$el.find("select").change(function () {
callback(self.getCommand(), self); callback(self.getCommand(), self);
}); });
} }
}; };
var padeditbar = (function() { var padeditbar = (function () {
const syncAnimation = (function () {
var syncAnimation = (function() { const SYNCING = -100;
var SYNCING = -100; const DONE = 100;
var DONE = 100; let state = DONE;
var state = DONE; const fps = 25;
var fps = 25; const step = 1 / fps;
var step = 1 / fps; const T_START = -0.5;
var T_START = -0.5; const T_FADE = 1.0;
var T_FADE = 1.0; const T_GONE = 1.5;
var T_GONE = 1.5; const animator = padutils.makeAnimationScheduler(() => {
var animator = padutils.makeAnimationScheduler(function() { if (state == SYNCING || state == DONE) {
if (state == SYNCING || state == DONE)
{
return false; return false;
} } else if (state >= T_GONE) {
else if (state >= T_GONE)
{
state = DONE; state = DONE;
$("#syncstatussyncing").css('display', 'none'); $('#syncstatussyncing').css('display', 'none');
$("#syncstatusdone").css('display', 'none'); $('#syncstatusdone').css('display', 'none');
return false; return false;
} } else if (state < 0) {
else if (state < 0)
{
state += step; state += step;
if (state >= 0) if (state >= 0) {
{ $('#syncstatussyncing').css('display', 'none');
$("#syncstatussyncing").css('display', 'none'); $('#syncstatusdone').css('display', 'block').css('opacity', 1);
$("#syncstatusdone").css('display', 'block').css('opacity', 1);
} }
return true; return true;
} } else {
else
{
state += step; state += step;
if (state >= T_FADE) if (state >= T_FADE) {
{ $('#syncstatusdone').css('opacity', (T_GONE - state) / (T_GONE - T_FADE));
$("#syncstatusdone").css('opacity', (T_GONE - state) / (T_GONE - T_FADE));
} }
return true; return true;
} }
}, step * 1000); }, step * 1000);
return { return {
syncing: function() { syncing() {
state = SYNCING; state = SYNCING;
$("#syncstatussyncing").css('display', 'block'); $('#syncstatussyncing').css('display', 'block');
$("#syncstatusdone").css('display', 'none'); $('#syncstatusdone').css('display', 'none');
}, },
done: function() { done() {
state = T_START; state = T_START;
animator.scheduleAnimation(); animator.scheduleAnimation();
} },
}; };
}()); }());
var self = { var self = {
init: function() { init() {
var self = this; const self = this;
self.dropdowns = []; self.dropdowns = [];
$("#editbar .editbarbutton").attr("unselectable", "on"); // for IE $('#editbar .editbarbutton').attr('unselectable', 'on'); // for IE
this.enable(); this.enable();
$("#editbar [data-key]").each(function () { $('#editbar [data-key]').each(function () {
$(this).unbind("click"); $(this).unbind('click');
(new ToolbarItem($(this))).bind(function (command, item) { (new ToolbarItem($(this))).bind((command, item) => {
self.triggerCommand(command, item); self.triggerCommand(command, item);
}); });
}); });
$('body:not(#editorcontainerbox)').on("keydown", function(evt){ $('body:not(#editorcontainerbox)').on('keydown', (evt) => {
bodyKeyEvent(evt); bodyKeyEvent(evt);
}); });
$('.show-more-icon-btn').click(function() { $('.show-more-icon-btn').click(() => {
$('.toolbar').toggleClass('full-icons'); $('.toolbar').toggleClass('full-icons');
}); });
self.checkAllIconsAreDisplayedInToolbar(); self.checkAllIconsAreDisplayedInToolbar();
$(window).resize(_.debounce( self.checkAllIconsAreDisplayedInToolbar, 100 ) ); $(window).resize(_.debounce(self.checkAllIconsAreDisplayedInToolbar, 100));
registerDefaultCommands(self); registerDefaultCommands(self);
hooks.callAll("postToolbarInit", { hooks.callAll('postToolbarInit', {
toolbar: self, toolbar: self,
ace: padeditor.ace ace: padeditor.ace,
}); });
/* /*
@ -173,101 +162,91 @@ var padeditbar = (function() {
* overflow:hidden on parent * overflow:hidden on parent
*/ */
if (!browser.safari) { if (!browser.safari) {
$('select').niceSelect(); $('select').niceSelect();
} }
// When editor is scrolled, we add a class to style the editbar differently // When editor is scrolled, we add a class to style the editbar differently
$('iframe[name="ace_outer"]').contents().scroll(function() { $('iframe[name="ace_outer"]').contents().scroll(function () {
$('#editbar').toggleClass('editor-scrolled', $(this).scrollTop() > 2); $('#editbar').toggleClass('editor-scrolled', $(this).scrollTop() > 2);
}) });
}, },
isEnabled: function() { isEnabled() {
return true; return true;
}, },
disable: function() { disable() {
$("#editbar").addClass('disabledtoolbar').removeClass("enabledtoolbar"); $('#editbar').addClass('disabledtoolbar').removeClass('enabledtoolbar');
}, },
enable: function() { enable() {
$('#editbar').addClass('enabledtoolbar').removeClass('disabledtoolbar'); $('#editbar').addClass('enabledtoolbar').removeClass('disabledtoolbar');
}, },
commands: {}, commands: {},
registerCommand: function (cmd, callback) { registerCommand(cmd, callback) {
this.commands[cmd] = callback; this.commands[cmd] = callback;
return this; return this;
}, },
registerDropdownCommand: function (cmd, dropdown) { registerDropdownCommand(cmd, dropdown) {
dropdown = dropdown || cmd; dropdown = dropdown || cmd;
self.dropdowns.push(dropdown) self.dropdowns.push(dropdown);
this.registerCommand(cmd, function () { this.registerCommand(cmd, () => {
self.toggleDropDown(dropdown); self.toggleDropDown(dropdown);
}); });
}, },
registerAceCommand: function (cmd, callback) { registerAceCommand(cmd, callback) {
this.registerCommand(cmd, function (cmd, ace, item) { this.registerCommand(cmd, (cmd, ace, item) => {
ace.callWithAce(function (ace) { ace.callWithAce((ace) => {
callback(cmd, ace, item); callback(cmd, ace, item);
}, cmd, true); }, cmd, true);
}); });
}, },
triggerCommand: function (cmd, item) { triggerCommand(cmd, item) {
if (self.isEnabled() && this.commands[cmd]) { if (self.isEnabled() && this.commands[cmd]) {
this.commands[cmd](cmd, padeditor.ace, item); this.commands[cmd](cmd, padeditor.ace, item);
} }
if(padeditor.ace) padeditor.ace.focus(); if (padeditor.ace) padeditor.ace.focus();
}, },
toggleDropDown: function(moduleName, cb) { toggleDropDown(moduleName, cb) {
// do nothing if users are sticked // do nothing if users are sticked
if (moduleName === "users" && $('#users').hasClass('stickyUsers')) { if (moduleName === 'users' && $('#users').hasClass('stickyUsers')) {
return; return;
} }
$('.nice-select').removeClass('open'); $('.nice-select').removeClass('open');
$('.toolbar-popup').removeClass("popup-show"); $('.toolbar-popup').removeClass('popup-show');
// hide all modules and remove highlighting of all buttons // hide all modules and remove highlighting of all buttons
if(moduleName == "none") if (moduleName == 'none') {
{ const returned = false;
var returned = false; for (var i = 0; i < self.dropdowns.length; i++) {
for(var i=0;i<self.dropdowns.length;i++)
{
var thisModuleName = self.dropdowns[i]; var thisModuleName = self.dropdowns[i];
//skip the userlist // skip the userlist
if(thisModuleName == "users") if (thisModuleName == 'users') continue;
continue;
var module = $("#" + thisModuleName); var module = $(`#${thisModuleName}`);
//skip any "force reconnect" message // skip any "force reconnect" message
var isAForceReconnectMessage = module.find('button#forcereconnect:visible').length > 0; const isAForceReconnectMessage = module.find('button#forcereconnect:visible').length > 0;
if(isAForceReconnectMessage) if (isAForceReconnectMessage) continue;
continue;
if (module.hasClass('popup-show')) { if (module.hasClass('popup-show')) {
$("li[data-key=" + thisModuleName + "] > a").removeClass("selected"); $(`li[data-key=${thisModuleName}] > a`).removeClass('selected');
module.removeClass("popup-show"); module.removeClass('popup-show');
} }
} }
if(!returned && cb) return cb(); if (!returned && cb) return cb();
} } else {
else
{
// hide all modules that are not selected and remove highlighting // hide all modules that are not selected and remove highlighting
// respectively add highlighting to the corresponding button // respectively add highlighting to the corresponding button
for(var i=0;i<self.dropdowns.length;i++) for (var i = 0; i < self.dropdowns.length; i++) {
{
var thisModuleName = self.dropdowns[i]; var thisModuleName = self.dropdowns[i];
var module = $("#" + thisModuleName); var module = $(`#${thisModuleName}`);
if(module.hasClass('popup-show')) if (module.hasClass('popup-show')) {
{ $(`li[data-key=${thisModuleName}] > a`).removeClass('selected');
$("li[data-key=" + thisModuleName + "] > a").removeClass("selected"); module.removeClass('popup-show');
module.removeClass("popup-show"); } else if (thisModuleName == moduleName) {
} $(`li[data-key=${thisModuleName}] > a`).addClass('selected');
else if(thisModuleName==moduleName) module.addClass('popup-show');
{
$("li[data-key=" + thisModuleName + "] > a").addClass("selected");
module.addClass("popup-show");
if (cb) { if (cb) {
cb(); cb();
} }
@ -275,113 +254,105 @@ var padeditbar = (function() {
} }
} }
}, },
setSyncStatus: function(status) { setSyncStatus(status) {
if (status == "syncing") if (status == 'syncing') {
{
syncAnimation.syncing(); syncAnimation.syncing();
} } else if (status == 'done') {
else if (status == "done")
{
syncAnimation.done(); syncAnimation.done();
} }
}, },
setEmbedLinks: function() { setEmbedLinks() {
var padUrl = window.location.href.split("?")[0]; const padUrl = window.location.href.split('?')[0];
if ($('#readonlyinput').is(':checked')) if ($('#readonlyinput').is(':checked')) {
{ const urlParts = padUrl.split('/');
var urlParts = padUrl.split("/");
urlParts.pop(); urlParts.pop();
var readonlyLink = urlParts.join("/") + "/" + clientVars.readOnlyId; const readonlyLink = `${urlParts.join('/')}/${clientVars.readOnlyId}`;
$('#embedinput').val('<iframe name="embed_readonly" src="' + readonlyLink + '?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false" width="100%" height="600" frameborder="0"></iframe>'); $('#embedinput').val(`<iframe name="embed_readonly" src="${readonlyLink}?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false" width="100%" height="600" frameborder="0"></iframe>`);
$('#linkinput').val(readonlyLink); $('#linkinput').val(readonlyLink);
} } else {
else $('#embedinput').val(`<iframe name="embed_readwrite" src="${padUrl}?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false" width="100%" height="600" frameborder="0"></iframe>`);
{
$('#embedinput').val('<iframe name="embed_readwrite" src="' + padUrl + '?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false" width="100%" height="600" frameborder="0"></iframe>');
$('#linkinput').val(padUrl); $('#linkinput').val(padUrl);
} }
}, },
checkAllIconsAreDisplayedInToolbar: function() { checkAllIconsAreDisplayedInToolbar() {
// reset style // reset style
$('.toolbar').removeClass('cropped') $('.toolbar').removeClass('cropped');
$('body').removeClass('mobile-layout'); $('body').removeClass('mobile-layout');
var menu_left = $('.toolbar .menu_left')[0]; const menu_left = $('.toolbar .menu_left')[0];
var menuRightWidth = 280; // this is approximate, we cannot measure it because on mobileLayour it takes the full width on the bottom of the page const menuRightWidth = 280; // this is approximate, we cannot measure it because on mobileLayour it takes the full width on the bottom of the page
if (menu_left && menu_left.scrollWidth > $('.toolbar').width() - menuRightWidth || $('.toolbar').width() < 1000) { if (menu_left && menu_left.scrollWidth > $('.toolbar').width() - menuRightWidth || $('.toolbar').width() < 1000) {
$('body').addClass('mobile-layout'); $('body').addClass('mobile-layout');
} }
if (menu_left && menu_left.scrollWidth > $('.toolbar').width()) { if (menu_left && menu_left.scrollWidth > $('.toolbar').width()) {
$('.toolbar').addClass('cropped'); $('.toolbar').addClass('cropped');
} }
} },
}; };
var editbarPosition = 0; let editbarPosition = 0;
function bodyKeyEvent(evt){
function bodyKeyEvent(evt) {
// If the event is Alt F9 or Escape & we're already in the editbar menu // If the event is Alt F9 or Escape & we're already in the editbar menu
// Send the users focus back to the pad // Send the users focus back to the pad
if((evt.keyCode === 120 && evt.altKey) || evt.keyCode === 27){ if ((evt.keyCode === 120 && evt.altKey) || evt.keyCode === 27) {
if($(':focus').parents(".toolbar").length === 1){ if ($(':focus').parents('.toolbar').length === 1) {
// If we're in the editbar already.. // If we're in the editbar already..
// Close any dropdowns we have open.. // Close any dropdowns we have open..
padeditbar.toggleDropDown("none"); padeditbar.toggleDropDown('none');
// Check we're on a pad and not on the timeslider // Check we're on a pad and not on the timeslider
// Or some other window I haven't thought about! // Or some other window I haven't thought about!
if(typeof pad === 'undefined'){ if (typeof pad === 'undefined') {
// Timeslider probably.. // Timeslider probably..
// Shift focus away from any drop downs // Shift focus away from any drop downs
$(':focus').blur(); // required to do not try to remove! $(':focus').blur(); // required to do not try to remove!
$('#editorcontainerbox').focus(); // Focus back onto the pad $('#editorcontainerbox').focus(); // Focus back onto the pad
}else{ } else {
// Shift focus away from any drop downs // Shift focus away from any drop downs
$(':focus').blur(); // required to do not try to remove! $(':focus').blur(); // required to do not try to remove!
padeditor.ace.focus(); // Sends focus back to pad padeditor.ace.focus(); // Sends focus back to pad
// The above focus doesn't always work in FF, you have to hit enter afterwards // The above focus doesn't always work in FF, you have to hit enter afterwards
evt.preventDefault(); evt.preventDefault();
} }
}else{ } else {
// Focus on the editbar :) // Focus on the editbar :)
var firstEditbarElement = parent.parent.$('#editbar').children("ul").first().children().first().children().first().children().first(); const firstEditbarElement = parent.parent.$('#editbar').children('ul').first().children().first().children().first().children().first();
$(this).blur(); $(this).blur();
firstEditbarElement.focus(); firstEditbarElement.focus();
evt.preventDefault(); evt.preventDefault();
} }
} }
// Are we in the toolbar?? // Are we in the toolbar??
if($(':focus').parents(".toolbar").length === 1){ if ($(':focus').parents('.toolbar').length === 1) {
// On arrow keys go to next/previous button item in editbar // On arrow keys go to next/previous button item in editbar
if(evt.keyCode !== 39 && evt.keyCode !== 37) return; if (evt.keyCode !== 39 && evt.keyCode !== 37) return;
// Get all the focusable items in the editbar // Get all the focusable items in the editbar
var focusItems = $('#editbar').find('button, select'); const focusItems = $('#editbar').find('button, select');
// On left arrow move to next button in editbar // On left arrow move to next button in editbar
if(evt.keyCode === 37){ if (evt.keyCode === 37) {
// If a dropdown is visible or we're in an input don't move to the next button // If a dropdown is visible or we're in an input don't move to the next button
if($('.popup').is(":visible") || evt.target.localName === "input") return; if ($('.popup').is(':visible') || evt.target.localName === 'input') return;
editbarPosition--; editbarPosition--;
// Allow focus to shift back to end of row and start of row // Allow focus to shift back to end of row and start of row
if(editbarPosition === -1) editbarPosition = focusItems.length -1; if (editbarPosition === -1) editbarPosition = focusItems.length - 1;
$(focusItems[editbarPosition]).focus() $(focusItems[editbarPosition]).focus();
} }
// On right arrow move to next button in editbar // On right arrow move to next button in editbar
if(evt.keyCode === 39){ if (evt.keyCode === 39) {
// If a dropdown is visible or we're in an input don't move to the next button // If a dropdown is visible or we're in an input don't move to the next button
if($('.popup').is(":visible") || evt.target.localName === "input") return; if ($('.popup').is(':visible') || evt.target.localName === 'input') return;
editbarPosition++; editbarPosition++;
// Allow focus to shift back to end of row and start of row // Allow focus to shift back to end of row and start of row
if(editbarPosition >= focusItems.length) editbarPosition = 0; if (editbarPosition >= focusItems.length) editbarPosition = 0;
$(focusItems[editbarPosition]).focus(); $(focusItems[editbarPosition]).focus();
} }
} }
} }
function aceAttributeCommand(cmd, ace) { function aceAttributeCommand(cmd, ace) {
@ -389,91 +360,91 @@ var padeditbar = (function() {
} }
function registerDefaultCommands(toolbar) { function registerDefaultCommands(toolbar) {
toolbar.registerDropdownCommand("showusers", "users"); toolbar.registerDropdownCommand('showusers', 'users');
toolbar.registerDropdownCommand("settings"); toolbar.registerDropdownCommand('settings');
toolbar.registerDropdownCommand("connectivity"); toolbar.registerDropdownCommand('connectivity');
toolbar.registerDropdownCommand("import_export"); toolbar.registerDropdownCommand('import_export');
toolbar.registerDropdownCommand("embed"); toolbar.registerDropdownCommand('embed');
toolbar.registerCommand("settings", function () { toolbar.registerCommand('settings', () => {
toolbar.toggleDropDown("settings", function(){ toolbar.toggleDropDown('settings', () => {
$('#options-stickychat').focus(); $('#options-stickychat').focus();
}); });
}); });
toolbar.registerCommand("import_export", function () { toolbar.registerCommand('import_export', () => {
toolbar.toggleDropDown("import_export", function(){ toolbar.toggleDropDown('import_export', () => {
// If Import file input exists then focus on it.. // If Import file input exists then focus on it..
if($('#importfileinput').length !== 0){ if ($('#importfileinput').length !== 0) {
setTimeout(function(){ setTimeout(() => {
$('#importfileinput').focus(); $('#importfileinput').focus();
}, 100); }, 100);
}else{ } else {
$('.exportlink').first().focus(); $('.exportlink').first().focus();
} }
}); });
}); });
toolbar.registerCommand("showusers", function () { toolbar.registerCommand('showusers', () => {
toolbar.toggleDropDown("users", function(){ toolbar.toggleDropDown('users', () => {
$('#myusernameedit').focus(); $('#myusernameedit').focus();
}); });
}); });
toolbar.registerCommand("embed", function () { toolbar.registerCommand('embed', () => {
toolbar.setEmbedLinks(); toolbar.setEmbedLinks();
toolbar.toggleDropDown("embed", function(){ toolbar.toggleDropDown('embed', () => {
$('#linkinput').focus().select(); $('#linkinput').focus().select();
}); });
}); });
toolbar.registerCommand("savedRevision", function () { toolbar.registerCommand('savedRevision', () => {
padsavedrevs.saveNow(); padsavedrevs.saveNow();
}); });
toolbar.registerCommand("showTimeSlider", function () { toolbar.registerCommand('showTimeSlider', () => {
document.location = document.location.pathname+ '/timeslider'; document.location = `${document.location.pathname}/timeslider`;
}); });
toolbar.registerAceCommand("bold", aceAttributeCommand); toolbar.registerAceCommand('bold', aceAttributeCommand);
toolbar.registerAceCommand("italic", aceAttributeCommand); toolbar.registerAceCommand('italic', aceAttributeCommand);
toolbar.registerAceCommand("underline", aceAttributeCommand); toolbar.registerAceCommand('underline', aceAttributeCommand);
toolbar.registerAceCommand("strikethrough", aceAttributeCommand); toolbar.registerAceCommand('strikethrough', aceAttributeCommand);
toolbar.registerAceCommand("undo", function (cmd, ace) { toolbar.registerAceCommand('undo', (cmd, ace) => {
ace.ace_doUndoRedo(cmd); ace.ace_doUndoRedo(cmd);
}); });
toolbar.registerAceCommand("redo", function (cmd, ace) { toolbar.registerAceCommand('redo', (cmd, ace) => {
ace.ace_doUndoRedo(cmd); ace.ace_doUndoRedo(cmd);
}); });
toolbar.registerAceCommand("insertunorderedlist", function (cmd, ace) { toolbar.registerAceCommand('insertunorderedlist', (cmd, ace) => {
ace.ace_doInsertUnorderedList(); ace.ace_doInsertUnorderedList();
}); });
toolbar.registerAceCommand("insertorderedlist", function (cmd, ace) { toolbar.registerAceCommand('insertorderedlist', (cmd, ace) => {
ace.ace_doInsertOrderedList(); ace.ace_doInsertOrderedList();
}); });
toolbar.registerAceCommand("indent", function (cmd, ace) { toolbar.registerAceCommand('indent', (cmd, ace) => {
if (!ace.ace_doIndentOutdent(false)) { if (!ace.ace_doIndentOutdent(false)) {
ace.ace_doInsertUnorderedList(); ace.ace_doInsertUnorderedList();
} }
}); });
toolbar.registerAceCommand("outdent", function (cmd, ace) { toolbar.registerAceCommand('outdent', (cmd, ace) => {
ace.ace_doIndentOutdent(true); ace.ace_doIndentOutdent(true);
}); });
toolbar.registerAceCommand("clearauthorship", function (cmd, ace) { toolbar.registerAceCommand('clearauthorship', (cmd, ace) => {
// If we have the whole document selected IE control A has been hit // If we have the whole document selected IE control A has been hit
var rep = ace.ace_getRep(); const rep = ace.ace_getRep();
var lastChar = rep.lines.atIndex(rep.lines.length()-1).width-1; const lastChar = rep.lines.atIndex(rep.lines.length() - 1).width - 1;
var lastLineIndex = rep.lines.length()-1; const lastLineIndex = rep.lines.length() - 1;
if(rep.selStart[0] === 0 && rep.selStart[1] === 0){ if (rep.selStart[0] === 0 && rep.selStart[1] === 0) {
// nesting intentionally here to make things readable // nesting intentionally here to make things readable
if(rep.selEnd[0] === lastLineIndex && rep.selEnd[1] === lastChar){ if (rep.selEnd[0] === lastLineIndex && rep.selEnd[1] === lastChar) {
var doPrompt = true; var doPrompt = true;
} }
} }
@ -488,22 +459,21 @@ var padeditbar = (function() {
// if we don't have any text selected, we have a caret or we have already said to prompt // if we don't have any text selected, we have a caret or we have already said to prompt
if ((!(rep.selStart && rep.selEnd)) || ace.ace_isCaret() || doPrompt) { if ((!(rep.selStart && rep.selEnd)) || ace.ace_isCaret() || doPrompt) {
if (window.confirm(html10n.get("pad.editbar.clearcolors"))) { if (window.confirm(html10n.get('pad.editbar.clearcolors'))) {
ace.ace_performDocumentApplyAttributesToCharRange(0, ace.ace_getRep().alltext.length, [ ace.ace_performDocumentApplyAttributesToCharRange(0, ace.ace_getRep().alltext.length, [
['author', ''] ['author', ''],
]); ]);
} }
} } else {
else {
ace.ace_setAttributeOnSelection('author', ''); ace.ace_setAttributeOnSelection('author', '');
} }
}); });
toolbar.registerCommand('timeslider_returnToPad', function(cmd) { toolbar.registerCommand('timeslider_returnToPad', (cmd) => {
if( document.referrer.length > 0 && document.referrer.substring(document.referrer.lastIndexOf("/")-1, document.referrer.lastIndexOf("/")) === "p") { if (document.referrer.length > 0 && document.referrer.substring(document.referrer.lastIndexOf('/') - 1, document.referrer.lastIndexOf('/')) === 'p') {
document.location = document.referrer; document.location = document.referrer;
} else { } else {
document.location = document.location.href.substring(0,document.location.href.lastIndexOf("/")); document.location = document.location.href.substring(0, document.location.href.lastIndexOf('/'));
} }
}); });
} }

View file

@ -21,145 +21,140 @@
*/ */
const Cookies = require('./pad_utils').Cookies; const Cookies = require('./pad_utils').Cookies;
var padcookie = require('./pad_cookie').padcookie; const padcookie = require('./pad_cookie').padcookie;
var padutils = require('./pad_utils').padutils; const padutils = require('./pad_utils').padutils;
var padeditor = (function() { const padeditor = (function () {
var Ace2Editor = undefined; let Ace2Editor = undefined;
var pad = undefined; let pad = undefined;
var settings = undefined; let settings = undefined;
var self = { var self = {
ace: null, ace: null,
// this is accessed directly from other files // this is accessed directly from other files
viewZoom: 100, viewZoom: 100,
init: function(readyFunc, initialViewOptions, _pad) { init(readyFunc, initialViewOptions, _pad) {
Ace2Editor = require('./ace').Ace2Editor; Ace2Editor = require('./ace').Ace2Editor;
pad = _pad; pad = _pad;
settings = pad.settings; settings = pad.settings;
function aceReady() { function aceReady() {
$("#editorloadingbox").hide(); $('#editorloadingbox').hide();
if (readyFunc) if (readyFunc) {
{
readyFunc(); readyFunc();
} }
} }
self.ace = new Ace2Editor(); self.ace = new Ace2Editor();
self.ace.init("editorcontainer", "", aceReady); self.ace.init('editorcontainer', '', aceReady);
self.ace.setProperty("wraps", true); self.ace.setProperty('wraps', true);
if (pad.getIsDebugEnabled()) if (pad.getIsDebugEnabled()) {
{ self.ace.setProperty('dmesg', pad.dmesg);
self.ace.setProperty("dmesg", pad.dmesg);
} }
self.initViewOptions(); self.initViewOptions();
self.setViewOptions(initialViewOptions); self.setViewOptions(initialViewOptions);
// view bar // view bar
$("#viewbarcontents").show(); $('#viewbarcontents').show();
}, },
initViewOptions: function() { initViewOptions() {
// Line numbers // Line numbers
padutils.bindCheckboxChange($("#options-linenoscheck"), function() { padutils.bindCheckboxChange($('#options-linenoscheck'), () => {
pad.changeViewOption('showLineNumbers', padutils.getCheckbox($("#options-linenoscheck"))); pad.changeViewOption('showLineNumbers', padutils.getCheckbox($('#options-linenoscheck')));
}); });
// Author colors // Author colors
padutils.bindCheckboxChange($("#options-colorscheck"), function() { padutils.bindCheckboxChange($('#options-colorscheck'), () => {
padcookie.setPref('showAuthorshipColors', padutils.getCheckbox("#options-colorscheck")); padcookie.setPref('showAuthorshipColors', padutils.getCheckbox('#options-colorscheck'));
pad.changeViewOption('showAuthorColors', padutils.getCheckbox("#options-colorscheck")); pad.changeViewOption('showAuthorColors', padutils.getCheckbox('#options-colorscheck'));
}); });
// Right to left // Right to left
padutils.bindCheckboxChange($("#options-rtlcheck"), function() { padutils.bindCheckboxChange($('#options-rtlcheck'), () => {
pad.changeViewOption('rtlIsTrue', padutils.getCheckbox($("#options-rtlcheck"))) pad.changeViewOption('rtlIsTrue', padutils.getCheckbox($('#options-rtlcheck')));
}); });
html10n.bind('localized', function() { html10n.bind('localized', () => {
pad.changeViewOption('rtlIsTrue', ('rtl' == html10n.getDirection())); pad.changeViewOption('rtlIsTrue', ('rtl' == html10n.getDirection()));
padutils.setCheckbox($("#options-rtlcheck"), ('rtl' == html10n.getDirection())); padutils.setCheckbox($('#options-rtlcheck'), ('rtl' == html10n.getDirection()));
}) });
// font family change // font family change
$("#viewfontmenu").change(function() { $('#viewfontmenu').change(() => {
pad.changeViewOption('padFontFamily', $("#viewfontmenu").val()); pad.changeViewOption('padFontFamily', $('#viewfontmenu').val());
}); });
// Language // Language
html10n.bind('localized', function() { html10n.bind('localized', () => {
$("#languagemenu").val(html10n.getLanguage()); $('#languagemenu').val(html10n.getLanguage());
// translate the value of 'unnamed' and 'Enter your name' textboxes in the userlist // translate the value of 'unnamed' and 'Enter your name' textboxes in the userlist
// this does not interfere with html10n's normal value-setting because html10n just ingores <input>s // this does not interfere with html10n's normal value-setting because html10n just ingores <input>s
// also, a value which has been set by the user will be not overwritten since a user-edited <input> // also, a value which has been set by the user will be not overwritten since a user-edited <input>
// does *not* have the editempty-class // does *not* have the editempty-class
$('input[data-l10n-id]').each(function(key, input){ $('input[data-l10n-id]').each((key, input) => {
input = $(input); input = $(input);
if(input.hasClass("editempty")){ if (input.hasClass('editempty')) {
input.val(html10n.get(input.attr("data-l10n-id"))); input.val(html10n.get(input.attr('data-l10n-id')));
} }
}); });
}) });
$("#languagemenu").val(html10n.getLanguage()); $('#languagemenu').val(html10n.getLanguage());
$("#languagemenu").change(function() { $('#languagemenu').change(() => {
Cookies.set('language', $('#languagemenu').val()); Cookies.set('language', $('#languagemenu').val());
window.html10n.localize([$("#languagemenu").val(), 'en']); window.html10n.localize([$('#languagemenu').val(), 'en']);
}); });
}, },
setViewOptions: function(newOptions) { setViewOptions(newOptions) {
function getOption(key, defaultValue) { function getOption(key, defaultValue) {
var value = String(newOptions[key]); const value = String(newOptions[key]);
if (value == "true") return true; if (value == 'true') return true;
if (value == "false") return false; if (value == 'false') return false;
return defaultValue; return defaultValue;
} }
var v; let v;
v = getOption('rtlIsTrue', ('rtl' == html10n.getDirection())); v = getOption('rtlIsTrue', ('rtl' == html10n.getDirection()));
self.ace.setProperty("rtlIsTrue", v); self.ace.setProperty('rtlIsTrue', v);
padutils.setCheckbox($("#options-rtlcheck"), v); padutils.setCheckbox($('#options-rtlcheck'), v);
v = getOption('showLineNumbers', true); v = getOption('showLineNumbers', true);
self.ace.setProperty("showslinenumbers", v); self.ace.setProperty('showslinenumbers', v);
padutils.setCheckbox($("#options-linenoscheck"), v); padutils.setCheckbox($('#options-linenoscheck'), v);
v = getOption('showAuthorColors', true); v = getOption('showAuthorColors', true);
self.ace.setProperty("showsauthorcolors", v); self.ace.setProperty('showsauthorcolors', v);
$('#chattext').toggleClass('authorColors', v); $('#chattext').toggleClass('authorColors', v);
$('iframe[name="ace_outer"]').contents().find('#sidedivinner').toggleClass('authorColors', v); $('iframe[name="ace_outer"]').contents().find('#sidedivinner').toggleClass('authorColors', v);
padutils.setCheckbox($("#options-colorscheck"), v); padutils.setCheckbox($('#options-colorscheck'), v);
// Override from parameters if true // Override from parameters if true
if (settings.noColors !== false){ if (settings.noColors !== false) {
self.ace.setProperty("showsauthorcolors", !settings.noColors); self.ace.setProperty('showsauthorcolors', !settings.noColors);
} }
self.ace.setProperty("textface", newOptions['padFontFamily'] || ""); self.ace.setProperty('textface', newOptions.padFontFamily || '');
}, },
dispose: function() { dispose() {
if (self.ace) if (self.ace) {
{
self.ace.destroy(); self.ace.destroy();
self.ace = null; self.ace = null;
} }
}, },
enable: function() { enable() {
if (self.ace) if (self.ace) {
{
self.ace.setEditable(true); self.ace.setEditable(true);
} }
}, },
disable: function() { disable() {
if (self.ace) if (self.ace) {
{ self.ace.setProperty('grayedOut', true);
self.ace.setProperty("grayedOut", true);
self.ace.setEditable(false); self.ace.setEditable(false);
} }
}, },
restoreRevisionText: function(dataFromServer) { restoreRevisionText(dataFromServer) {
pad.addHistoricalAuthors(dataFromServer.historicalAuthorData); pad.addHistoricalAuthors(dataFromServer.historicalAuthorData);
self.ace.importAText(dataFromServer.atext, dataFromServer.apool, true); self.ace.importAText(dataFromServer.atext, dataFromServer.apool, true);
} },
}; };
return self; return self;
}()); }());

View file

@ -20,14 +20,13 @@
* limitations under the License. * limitations under the License.
*/ */
var padimpexp = (function() { const padimpexp = (function () {
// /// import
///// import let currentImportTimer = null;
var currentImportTimer = null;
function addImportFrames() { function addImportFrames() {
$("#import .importframe").remove(); $('#import .importframe').remove();
var iframe = $('<iframe style="display: none;" name="importiframe" class="importframe"></iframe>'); const iframe = $('<iframe style="display: none;" name="importiframe" class="importframe"></iframe>');
$('#import').append(iframe); $('#import').append(iframe);
} }
@ -39,29 +38,27 @@ var padimpexp = (function() {
} }
function fileInputSubmit() { function fileInputSubmit() {
$('#importmessagefail').fadeOut("fast"); $('#importmessagefail').fadeOut('fast');
var ret = window.confirm(html10n.get("pad.impexp.confirmimport")); const ret = window.confirm(html10n.get('pad.impexp.confirmimport'));
if (ret) if (ret) {
{ currentImportTimer = window.setTimeout(() => {
currentImportTimer = window.setTimeout(function() { if (!currentImportTimer) {
if (!currentImportTimer)
{
return; return;
} }
currentImportTimer = null; currentImportTimer = null;
importFailed("Request timed out."); importFailed('Request timed out.');
importDone(); importDone();
}, 25000); // time out after some number of seconds }, 25000); // time out after some number of seconds
$('#importsubmitinput').attr( $('#importsubmitinput').attr(
{ {
disabled: true disabled: true,
}).val(html10n.get("pad.impexp.importing")); }).val(html10n.get('pad.impexp.importing'));
window.setTimeout(function() { window.setTimeout(() => {
$('#importfileinput').attr( $('#importfileinput').attr(
{ {
disabled: true disabled: true,
}); });
}, 0); }, 0);
$('#importarrow').stop(true, true).hide(); $('#importarrow').stop(true, true).hide();
$('#importstatusball').show(); $('#importstatusball').show();
@ -74,8 +71,8 @@ var padimpexp = (function() {
} }
function importDone() { function importDone() {
$('#importsubmitinput').removeAttr('disabled').val(html10n.get("pad.impexp.importbutton")); $('#importsubmitinput').removeAttr('disabled').val(html10n.get('pad.impexp.importbutton'));
window.setTimeout(function() { window.setTimeout(() => {
$('#importfileinput').removeAttr('disabled'); $('#importfileinput').removeAttr('disabled');
}, 0); }, 0);
$('#importstatusball').hide(); $('#importstatusball').hide();
@ -84,155 +81,136 @@ var padimpexp = (function() {
} }
function importClearTimeout() { function importClearTimeout() {
if (currentImportTimer) if (currentImportTimer) {
{
window.clearTimeout(currentImportTimer); window.clearTimeout(currentImportTimer);
currentImportTimer = null; currentImportTimer = null;
} }
} }
function importErrorMessage(status) { function importErrorMessage(status) {
var msg=""; let msg = '';
if(status === "convertFailed"){ if (status === 'convertFailed') {
msg = html10n.get("pad.impexp.convertFailed"); msg = html10n.get('pad.impexp.convertFailed');
} else if(status === "uploadFailed"){ } else if (status === 'uploadFailed') {
msg = html10n.get("pad.impexp.uploadFailed"); msg = html10n.get('pad.impexp.uploadFailed');
} else if(status === "padHasData"){ } else if (status === 'padHasData') {
msg = html10n.get("pad.impexp.padHasData"); msg = html10n.get('pad.impexp.padHasData');
} else if(status === "maxFileSize"){ } else if (status === 'maxFileSize') {
msg = html10n.get("pad.impexp.maxFileSize"); msg = html10n.get('pad.impexp.maxFileSize');
} else if(status === "permission"){ } else if (status === 'permission') {
msg = html10n.get("pad.impexp.permission"); msg = html10n.get('pad.impexp.permission');
} }
function showError(fade) { function showError(fade) {
$('#importmessagefail').html('<strong style="color: red">'+html10n.get('pad.impexp.importfailed')+':</strong> ' + (msg || html10n.get('pad.impexp.copypaste','')))[(fade ? "fadeIn" : "show")](); $('#importmessagefail').html(`<strong style="color: red">${html10n.get('pad.impexp.importfailed')}:</strong> ${msg || html10n.get('pad.impexp.copypaste', '')}`)[(fade ? 'fadeIn' : 'show')]();
} }
if ($('#importexport .importmessage').is(':visible')) if ($('#importexport .importmessage').is(':visible')) {
{ $('#importmessagesuccess').fadeOut('fast');
$('#importmessagesuccess').fadeOut("fast"); $('#importmessagefail').fadeOut('fast', () => {
$('#importmessagefail').fadeOut("fast", function() {
showError(true); showError(true);
}); });
} } else {
else
{
showError(); showError();
} }
} }
function importSuccessful(token) { function importSuccessful(token) {
$.ajax( $.ajax(
{ {
type: 'post', type: 'post',
url: '/ep/pad/impexp/import2', url: '/ep/pad/impexp/import2',
data: { data: {
token: token, token,
padId: pad.getPadId() padId: pad.getPadId(),
}, },
success: importApplicationSuccessful, success: importApplicationSuccessful,
error: importApplicationFailed, error: importApplicationFailed,
timeout: 25000 timeout: 25000,
}); });
addImportFrames(); addImportFrames();
} }
function importApplicationFailed(xhr, textStatus, errorThrown) { function importApplicationFailed(xhr, textStatus, errorThrown) {
importErrorMessage("Error during conversion."); importErrorMessage('Error during conversion.');
importDone(); importDone();
} }
///// export // /// export
function cantExport() { function cantExport() {
var type = $(this); let type = $(this);
if (type.hasClass("exporthrefpdf")) if (type.hasClass('exporthrefpdf')) {
{ type = 'PDF';
type = "PDF"; } else if (type.hasClass('exporthrefdoc')) {
type = 'Microsoft Word';
} else if (type.hasClass('exporthrefodt')) {
type = 'OpenDocument';
} else {
type = 'this file';
} }
else if (type.hasClass("exporthrefdoc")) alert(html10n.get('pad.impexp.exportdisabled', {type}));
{
type = "Microsoft Word";
}
else if (type.hasClass("exporthrefodt"))
{
type = "OpenDocument";
}
else
{
type = "this file";
}
alert(html10n.get("pad.impexp.exportdisabled", {type:type}));
return false; return false;
} }
///// // ///
var pad = undefined; var pad = undefined;
var self = { const self = {
init: function(_pad) { init(_pad) {
pad = _pad; pad = _pad;
//get /p/padname // get /p/padname
// if /p/ isn't available due to a rewrite we use the clientVars padId // if /p/ isn't available due to a rewrite we use the clientVars padId
var pad_root_path = new RegExp(/.*\/p\/[^\/]+/).exec(document.location.pathname) || clientVars.padId; const pad_root_path = new RegExp(/.*\/p\/[^\/]+/).exec(document.location.pathname) || clientVars.padId;
//get http://example.com/p/padname without Params // get http://example.com/p/padname without Params
var pad_root_url = document.location.protocol + '//' + document.location.host + document.location.pathname; const pad_root_url = `${document.location.protocol}//${document.location.host}${document.location.pathname}`;
//i10l buttom import // i10l buttom import
$('#importsubmitinput').val(html10n.get("pad.impexp.importbutton")); $('#importsubmitinput').val(html10n.get('pad.impexp.importbutton'));
html10n.bind('localized', function() { html10n.bind('localized', () => {
$('#importsubmitinput').val(html10n.get("pad.impexp.importbutton")); $('#importsubmitinput').val(html10n.get('pad.impexp.importbutton'));
}) });
// build the export links // build the export links
$("#exporthtmla").attr("href", pad_root_path + "/export/html"); $('#exporthtmla').attr('href', `${pad_root_path}/export/html`);
$("#exportetherpada").attr("href", pad_root_path + "/export/etherpad"); $('#exportetherpada').attr('href', `${pad_root_path}/export/etherpad`);
$("#exportplaina").attr("href", pad_root_path + "/export/txt"); $('#exportplaina').attr('href', `${pad_root_path}/export/txt`);
// activate action to import in the form // activate action to import in the form
$("#importform").attr('action', pad_root_url + "/import"); $('#importform').attr('action', `${pad_root_url}/import`);
//hide stuff thats not avaible if abiword/soffice is disabled // hide stuff thats not avaible if abiword/soffice is disabled
if(clientVars.exportAvailable == "no") if (clientVars.exportAvailable == 'no') {
{ $('#exportworda').remove();
$("#exportworda").remove(); $('#exportpdfa').remove();
$("#exportpdfa").remove(); $('#exportopena').remove();
$("#exportopena").remove();
$("#importmessageabiword").show(); $('#importmessageabiword').show();
} } else if (clientVars.exportAvailable == 'withoutPDF') {
else if(clientVars.exportAvailable == "withoutPDF") $('#exportpdfa').remove();
{
$("#exportpdfa").remove();
$("#exportworda").attr("href", pad_root_path + "/export/doc"); $('#exportworda').attr('href', `${pad_root_path}/export/doc`);
$("#exportopena").attr("href", pad_root_path + "/export/odt"); $('#exportopena').attr('href', `${pad_root_path}/export/odt`);
$("#importexport").css({"height":"142px"}); $('#importexport').css({height: '142px'});
$("#importexportline").css({"height":"142px"}); $('#importexportline').css({height: '142px'});
} } else {
else $('#exportworda').attr('href', `${pad_root_path}/export/doc`);
{ $('#exportpdfa').attr('href', `${pad_root_path}/export/pdf`);
$("#exportworda").attr("href", pad_root_path + "/export/doc"); $('#exportopena').attr('href', `${pad_root_path}/export/odt`);
$("#exportpdfa").attr("href", pad_root_path + "/export/pdf");
$("#exportopena").attr("href", pad_root_path + "/export/odt");
} }
addImportFrames(); addImportFrames();
$("#importfileinput").change(fileInputUpdated); $('#importfileinput').change(fileInputUpdated);
$('#importform').unbind("submit").submit(fileInputSubmit); $('#importform').unbind('submit').submit(fileInputSubmit);
$('.disabledexport').click(cantExport); $('.disabledexport').click(cantExport);
}, },
handleFrameCall: function(directDatabaseAccess, status) { handleFrameCall(directDatabaseAccess, status) {
if(directDatabaseAccess === "undefined") directDatabaseAccess = false; if (directDatabaseAccess === 'undefined') directDatabaseAccess = false;
if (status !== "ok") if (status !== 'ok') {
{
importFailed(status); importFailed(status);
} } else {
else
{
$('#import_export').removeClass('popup-show'); $('#import_export').removeClass('popup-show');
} }
@ -244,16 +222,16 @@ var padimpexp = (function() {
importDone(); importDone();
}, },
disable: function() { disable() {
$("#impexp-disabled-clickcatcher").show(); $('#impexp-disabled-clickcatcher').show();
$("#import").css('opacity', 0.5); $('#import').css('opacity', 0.5);
$("#impexp-export").css('opacity', 0.5); $('#impexp-export').css('opacity', 0.5);
},
enable() {
$('#impexp-disabled-clickcatcher').hide();
$('#import').css('opacity', 1);
$('#impexp-export').css('opacity', 1);
}, },
enable: function() {
$("#impexp-disabled-clickcatcher").hide();
$("#import").css('opacity', 1);
$("#impexp-export").css('opacity', 1);
}
}; };
return self; return self;
}()); }());

View file

@ -20,33 +20,33 @@
* limitations under the License. * limitations under the License.
*/ */
var padeditbar = require('./pad_editbar').padeditbar; const padeditbar = require('./pad_editbar').padeditbar;
var automaticReconnect = require('./pad_automatic_reconnect'); const automaticReconnect = require('./pad_automatic_reconnect');
var padmodals = (function() { const padmodals = (function () {
var pad = undefined; let pad = undefined;
var self = { const self = {
init: function(_pad) { init(_pad) {
pad = _pad; pad = _pad;
}, },
showModal: function(messageId) { showModal(messageId) {
padeditbar.toggleDropDown("none", function() { padeditbar.toggleDropDown('none', () => {
$("#connectivity .visible").removeClass('visible'); $('#connectivity .visible').removeClass('visible');
$("#connectivity ."+messageId).addClass('visible'); $(`#connectivity .${messageId}`).addClass('visible');
var $modal = $('#connectivity .' + messageId); const $modal = $(`#connectivity .${messageId}`);
automaticReconnect.showCountDownTimerToReconnectOnModal($modal, pad); automaticReconnect.showCountDownTimerToReconnectOnModal($modal, pad);
padeditbar.toggleDropDown("connectivity"); padeditbar.toggleDropDown('connectivity');
}); });
}, },
showOverlay: function() { showOverlay() {
// Prevent the user to interact with the toolbar. Useful when user is disconnected for example // Prevent the user to interact with the toolbar. Useful when user is disconnected for example
$("#toolbar-overlay").show(); $('#toolbar-overlay').show();
},
hideOverlay() {
$('#toolbar-overlay').hide();
}, },
hideOverlay: function() {
$("#toolbar-overlay").hide();
}
}; };
return self; return self;
}()); }());

View file

@ -14,22 +14,22 @@
* limitations under the License. * limitations under the License.
*/ */
var pad; let pad;
exports.saveNow = function(){ exports.saveNow = function () {
pad.collabClient.sendMessage({"type": "SAVE_REVISION"}); pad.collabClient.sendMessage({type: 'SAVE_REVISION'});
$.gritter.add({ $.gritter.add({
// (string | mandatory) the heading of the notification // (string | mandatory) the heading of the notification
title: _("pad.savedrevs.marked"), title: _('pad.savedrevs.marked'),
// (string | mandatory) the text inside the notification // (string | mandatory) the text inside the notification
text: _("pad.savedrevs.timeslider") || "You can view saved revisions in the timeslider", text: _('pad.savedrevs.timeslider') || 'You can view saved revisions in the timeslider',
// (bool | optional) if you want it to fade out on its own or just sit there // (bool | optional) if you want it to fade out on its own or just sit there
sticky: false, sticky: false,
time: 3000, time: 3000,
class_name: "saved-revision", class_name: 'saved-revision',
}); });
} };
exports.init = function(_pad){ exports.init = function (_pad) {
pad = _pad; pad = _pad;
} };

View file

@ -20,56 +20,55 @@
* limitations under the License. * limitations under the License.
*/ */
var padutils = require('./pad_utils').padutils; const padutils = require('./pad_utils').padutils;
var hooks = require('./pluginfw/hooks'); const hooks = require('./pluginfw/hooks');
var myUserInfo = {}; let myUserInfo = {};
var colorPickerOpen = false; let colorPickerOpen = false;
var colorPickerSetup = false; let colorPickerSetup = false;
var previousColorId = 0; let previousColorId = 0;
var paduserlist = (function() { const paduserlist = (function () {
const rowManager = (function () {
var rowManager = (function() {
// The row manager handles rendering rows of the user list and animating // The row manager handles rendering rows of the user list and animating
// their insertion, removal, and reordering. It manipulates TD height // their insertion, removal, and reordering. It manipulates TD height
// and TD opacity. // and TD opacity.
function nextRowId() { function nextRowId() {
return "usertr" + (nextRowId.counter++); return `usertr${nextRowId.counter++}`;
} }
nextRowId.counter = 1; nextRowId.counter = 1;
// objects are shared; fields are "domId","data","animationStep" // objects are shared; fields are "domId","data","animationStep"
var rowsFadingOut = []; // unordered set const rowsFadingOut = []; // unordered set
var rowsFadingIn = []; // unordered set const rowsFadingIn = []; // unordered set
var rowsPresent = []; // in order const rowsPresent = []; // in order
var ANIMATION_START = -12; // just starting to fade in const ANIMATION_START = -12; // just starting to fade in
var ANIMATION_END = 12; // just finishing fading out const ANIMATION_END = 12; // just finishing fading out
function getAnimationHeight(step, power) { function getAnimationHeight(step, power) {
var a = Math.abs(step / 12); let a = Math.abs(step / 12);
if (power == 2) a = a * a; if (power == 2) a *= a;
else if (power == 3) a = a * a * a; else if (power == 3) a = a * a * a;
else if (power == 4) a = a * a * a * a; else if (power == 4) a = a * a * a * a;
else if (power >= 5) a = a * a * a * a * a; else if (power >= 5) a = a * a * a * a * a;
return Math.round(26 * (1 - a)); return Math.round(26 * (1 - a));
} }
var OPACITY_STEPS = 6; const OPACITY_STEPS = 6;
var ANIMATION_STEP_TIME = 20; const ANIMATION_STEP_TIME = 20;
var LOWER_FRAMERATE_FACTOR = 2; const LOWER_FRAMERATE_FACTOR = 2;
var scheduleAnimation = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME, LOWER_FRAMERATE_FACTOR).scheduleAnimation; const scheduleAnimation = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME, LOWER_FRAMERATE_FACTOR).scheduleAnimation;
var NUMCOLS = 4; const NUMCOLS = 4;
// we do lots of manipulation of table rows and stuff that JQuery makes ok, despite // we do lots of manipulation of table rows and stuff that JQuery makes ok, despite
// IE's poor handling when manipulating the DOM directly. // IE's poor handling when manipulating the DOM directly.
function getEmptyRowHtml(height) { function getEmptyRowHtml(height) {
return '<td colspan="' + NUMCOLS + '" style="border:0;height:' + height + 'px"><!-- --></td>'; return `<td colspan="${NUMCOLS}" style="border:0;height:${height}px"><!-- --></td>`;
} }
function isNameEditable(data) { function isNameEditable(data) {
@ -77,84 +76,68 @@ var paduserlist = (function() {
} }
function replaceUserRowContents(tr, height, data) { function replaceUserRowContents(tr, height, data) {
var tds = getUserRowHtml(height, data).match(/<td.*?<\/td>/gi); const tds = getUserRowHtml(height, data).match(/<td.*?<\/td>/gi);
if (isNameEditable(data) && tr.find("td.usertdname input:enabled").length > 0) if (isNameEditable(data) && tr.find('td.usertdname input:enabled').length > 0) {
{
// preserve input field node // preserve input field node
for (var i = 0; i < tds.length; i++) for (let i = 0; i < tds.length; i++) {
{ const oldTd = $(tr.find('td').get(i));
var oldTd = $(tr.find("td").get(i)); if (!oldTd.hasClass('usertdname')) {
if (!oldTd.hasClass('usertdname'))
{
oldTd.replaceWith(tds[i]); oldTd.replaceWith(tds[i]);
} }
} }
} } else {
else
{
tr.html(tds.join('')); tr.html(tds.join(''));
} }
return tr; return tr;
} }
function getUserRowHtml(height, data) { function getUserRowHtml(height, data) {
var nameHtml; let nameHtml;
if (data.name) if (data.name) {
{
nameHtml = padutils.escapeHtml(data.name); nameHtml = padutils.escapeHtml(data.name);
} } else {
else nameHtml = `<input data-l10n-id="pad.userlist.unnamed" type="text" class="editempty newinput" value="${_('pad.userlist.unnamed')}" ${isNameEditable(data) ? '' : 'disabled="disabled" '}/>`;
{
nameHtml = '<input data-l10n-id="pad.userlist.unnamed" type="text" class="editempty newinput" value="'+_('pad.userlist.unnamed')+'" ' + (isNameEditable(data) ? '' : 'disabled="disabled" ') + '/>';
} }
return ['<td style="height:', height, 'px" class="usertdswatch"><div class="swatch" style="background:' + padutils.escapeHtml(data.color) + '">&nbsp;</div></td>', '<td style="height:', height, 'px" class="usertdname">', nameHtml, '</td>', '<td style="height:', height, 'px" class="activity">', padutils.escapeHtml(data.activity), '</td>'].join(''); return ['<td style="height:', height, `px" class="usertdswatch"><div class="swatch" style="background:${padutils.escapeHtml(data.color)}">&nbsp;</div></td>`, '<td style="height:', height, 'px" class="usertdname">', nameHtml, '</td>', '<td style="height:', height, 'px" class="activity">', padutils.escapeHtml(data.activity), '</td>'].join('');
} }
function getRowHtml(id, innerHtml, authorId) { function getRowHtml(id, innerHtml, authorId) {
return '<tr data-authorId="'+authorId+'" id="' + id + '">' + innerHtml + '</tr>'; return `<tr data-authorId="${authorId}" id="${id}">${innerHtml}</tr>`;
} }
function rowNode(row) { function rowNode(row) {
return $("#" + row.domId); return $(`#${row.domId}`);
} }
function handleRowData(row) { function handleRowData(row) {
if (row.data && row.data.status == 'Disconnected') if (row.data && row.data.status == 'Disconnected') {
{
row.opacity = 0.5; row.opacity = 0.5;
} } else {
else
{
delete row.opacity; delete row.opacity;
} }
} }
function handleRowNode(tr, data) { function handleRowNode(tr, data) {
if (data.titleText) if (data.titleText) {
{ const titleText = data.titleText;
var titleText = data.titleText; window.setTimeout(() => {
window.setTimeout(function() {
/* tr.attr('title', titleText)*/ /* tr.attr('title', titleText)*/
}, 0); }, 0);
} } else {
else
{
tr.removeAttr('title'); tr.removeAttr('title');
} }
} }
function handleOtherUserInputs() { function handleOtherUserInputs() {
// handle 'INPUT' elements for naming other unnamed users // handle 'INPUT' elements for naming other unnamed users
$("#otheruserstable input.newinput").each(function() { $('#otheruserstable input.newinput').each(function () {
var input = $(this); const input = $(this);
var tr = input.closest("tr"); const tr = input.closest('tr');
if (tr.length > 0) if (tr.length > 0) {
{ const index = tr.parent().children().index(tr);
var index = tr.parent().children().index(tr); if (index >= 0) {
if (index >= 0) const userId = rowsPresent[index].data.id;
{
var userId = rowsPresent[index].data.id;
rowManagerMakeNameEditor($(this), userId); rowManagerMakeNameEditor($(this), userId);
} }
} }
@ -168,41 +151,34 @@ var paduserlist = (function() {
position = Math.max(0, Math.min(rowsPresent.length, position)); position = Math.max(0, Math.min(rowsPresent.length, position));
animationPower = (animationPower === undefined ? 4 : animationPower); animationPower = (animationPower === undefined ? 4 : animationPower);
var domId = nextRowId(); const domId = nextRowId();
var row = { const row = {
data: data, data,
animationStep: ANIMATION_START, animationStep: ANIMATION_START,
domId: domId, domId,
animationPower: animationPower animationPower,
}; };
var authorId = data.id; const authorId = data.id;
handleRowData(row); handleRowData(row);
rowsPresent.splice(position, 0, row); rowsPresent.splice(position, 0, row);
var tr; let tr;
if (animationPower == 0) if (animationPower == 0) {
{
tr = $(getRowHtml(domId, getUserRowHtml(getAnimationHeight(0), data), authorId)); tr = $(getRowHtml(domId, getUserRowHtml(getAnimationHeight(0), data), authorId));
row.animationStep = 0; row.animationStep = 0;
} } else {
else
{
rowsFadingIn.push(row); rowsFadingIn.push(row);
tr = $(getRowHtml(domId, getEmptyRowHtml(getAnimationHeight(ANIMATION_START)), authorId)); tr = $(getRowHtml(domId, getEmptyRowHtml(getAnimationHeight(ANIMATION_START)), authorId));
} }
handleRowNode(tr, data); handleRowNode(tr, data);
$("table#otheruserstable").show(); $('table#otheruserstable').show();
if (position == 0) if (position == 0) {
{ $('table#otheruserstable').prepend(tr);
$("table#otheruserstable").prepend(tr); } else {
}
else
{
rowNode(rowsPresent[position - 1]).after(tr); rowNode(rowsPresent[position - 1]).after(tr);
} }
if (animationPower != 0) if (animationPower != 0) {
{
scheduleAnimation(); scheduleAnimation();
} }
@ -212,16 +188,14 @@ var paduserlist = (function() {
} }
function updateRow(position, data) { function updateRow(position, data) {
var row = rowsPresent[position]; const row = rowsPresent[position];
if (row) if (row) {
{
row.data = data; row.data = data;
handleRowData(row); handleRowData(row);
if (row.animationStep == 0) if (row.animationStep == 0) {
{
// not currently animating // not currently animating
var tr = rowNode(row); const tr = rowNode(row);
replaceUserRowContents(tr, getAnimationHeight(0), row.data).find("td").css('opacity', (row.opacity === undefined ? 1 : row.opacity)); replaceUserRowContents(tr, getAnimationHeight(0), row.data).find('td').css('opacity', (row.opacity === undefined ? 1 : row.opacity));
handleRowNode(tr, data); handleRowNode(tr, data);
handleOtherUserInputs(); handleOtherUserInputs();
} }
@ -230,16 +204,12 @@ var paduserlist = (function() {
function removeRow(position, animationPower) { function removeRow(position, animationPower) {
animationPower = (animationPower === undefined ? 4 : animationPower); animationPower = (animationPower === undefined ? 4 : animationPower);
var row = rowsPresent[position]; const row = rowsPresent[position];
if (row) if (row) {
{
rowsPresent.splice(position, 1); // remove rowsPresent.splice(position, 1); // remove
if (animationPower == 0) if (animationPower == 0) {
{
rowNode(row).remove(); rowNode(row).remove();
} } else {
else
{
row.animationStep = -row.animationStep; // use symmetry row.animationStep = -row.animationStep; // use symmetry
row.animationPower = animationPower; row.animationPower = animationPower;
rowsFadingOut.push(row); rowsFadingOut.push(row);
@ -247,7 +217,7 @@ var paduserlist = (function() {
} }
} }
if (rowsPresent.length === 0) { if (rowsPresent.length === 0) {
$("table#otheruserstable").hide(); $('table#otheruserstable').hide();
} }
} }
@ -256,10 +226,9 @@ var paduserlist = (function() {
function moveRow(oldPosition, newPosition, animationPower) { function moveRow(oldPosition, newPosition, animationPower) {
animationPower = (animationPower === undefined ? 1 : animationPower); // linear is best animationPower = (animationPower === undefined ? 1 : animationPower); // linear is best
var row = rowsPresent[oldPosition]; const row = rowsPresent[oldPosition];
if (row && oldPosition != newPosition) if (row && oldPosition != newPosition) {
{ const rowData = row.data;
var rowData = row.data;
removeRow(oldPosition, animationPower); removeRow(oldPosition, animationPower);
insertRow(newPosition, rowData, animationPower); insertRow(newPosition, rowData, animationPower);
} }
@ -267,55 +236,39 @@ var paduserlist = (function() {
function animateStep() { function animateStep() {
// animation must be symmetrical // animation must be symmetrical
for (var i = rowsFadingIn.length - 1; i >= 0; i--) for (var i = rowsFadingIn.length - 1; i >= 0; i--) { // backwards to allow removal
{ // backwards to allow removal
var row = rowsFadingIn[i]; var row = rowsFadingIn[i];
var step = ++row.animationStep; var step = ++row.animationStep;
var animHeight = getAnimationHeight(step, row.animationPower); var animHeight = getAnimationHeight(step, row.animationPower);
var node = rowNode(row); var node = rowNode(row);
var baseOpacity = (row.opacity === undefined ? 1 : row.opacity); var baseOpacity = (row.opacity === undefined ? 1 : row.opacity);
if (step <= -OPACITY_STEPS) if (step <= -OPACITY_STEPS) {
{ node.find('td').height(animHeight);
node.find("td").height(animHeight); } else if (step == -OPACITY_STEPS + 1) {
} node.html(getUserRowHtml(animHeight, row.data)).find('td').css('opacity', baseOpacity * 1 / OPACITY_STEPS);
else if (step == -OPACITY_STEPS + 1)
{
node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1 / OPACITY_STEPS);
handleRowNode(node, row.data); handleRowNode(node, row.data);
} } else if (step < 0) {
else if (step < 0) node.find('td').css('opacity', baseOpacity * (OPACITY_STEPS - (-step)) / OPACITY_STEPS).height(animHeight);
{ } else if (step == 0) {
node.find("td").css('opacity', baseOpacity * (OPACITY_STEPS - (-step)) / OPACITY_STEPS).height(animHeight);
}
else if (step == 0)
{
// set HTML in case modified during animation // set HTML in case modified during animation
node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1).height(animHeight); node.html(getUserRowHtml(animHeight, row.data)).find('td').css('opacity', baseOpacity * 1).height(animHeight);
handleRowNode(node, row.data); handleRowNode(node, row.data);
rowsFadingIn.splice(i, 1); // remove from set rowsFadingIn.splice(i, 1); // remove from set
} }
} }
for (var i = rowsFadingOut.length - 1; i >= 0; i--) for (var i = rowsFadingOut.length - 1; i >= 0; i--) { // backwards to allow removal
{ // backwards to allow removal
var row = rowsFadingOut[i]; var row = rowsFadingOut[i];
var step = ++row.animationStep; var step = ++row.animationStep;
var node = rowNode(row); var node = rowNode(row);
var animHeight = getAnimationHeight(step, row.animationPower); var animHeight = getAnimationHeight(step, row.animationPower);
var baseOpacity = (row.opacity === undefined ? 1 : row.opacity); var baseOpacity = (row.opacity === undefined ? 1 : row.opacity);
if (step < OPACITY_STEPS) if (step < OPACITY_STEPS) {
{ node.find('td').css('opacity', baseOpacity * (OPACITY_STEPS - step) / OPACITY_STEPS).height(animHeight);
node.find("td").css('opacity', baseOpacity * (OPACITY_STEPS - step) / OPACITY_STEPS).height(animHeight); } else if (step == OPACITY_STEPS) {
}
else if (step == OPACITY_STEPS)
{
node.html(getEmptyRowHtml(animHeight)); node.html(getEmptyRowHtml(animHeight));
} } else if (step <= ANIMATION_END) {
else if (step <= ANIMATION_END) node.find('td').height(animHeight);
{ } else {
node.find("td").height(animHeight);
}
else
{
rowsFadingOut.splice(i, 1); // remove from set rowsFadingOut.splice(i, 1); // remove from set
node.remove(); node.remove();
} }
@ -326,36 +279,30 @@ var paduserlist = (function() {
return (rowsFadingIn.length > 0) || (rowsFadingOut.length > 0); // is more to do return (rowsFadingIn.length > 0) || (rowsFadingOut.length > 0); // is more to do
} }
var self = { const self = {
insertRow: insertRow, insertRow,
removeRow: removeRow, removeRow,
moveRow: moveRow, moveRow,
updateRow: updateRow updateRow,
}; };
return self; return self;
}()); ////////// rowManager }()); // //////// rowManager
var otherUsersInfo = []; const otherUsersInfo = [];
var otherUsersData = []; const otherUsersData = [];
function rowManagerMakeNameEditor(jnode, userId) { function rowManagerMakeNameEditor(jnode, userId) {
setUpEditable(jnode, function() { setUpEditable(jnode, () => {
var existingIndex = findExistingIndex(userId); const existingIndex = findExistingIndex(userId);
if (existingIndex >= 0) if (existingIndex >= 0) {
{
return otherUsersInfo[existingIndex].name || ''; return otherUsersInfo[existingIndex].name || '';
} } else {
else
{
return ''; return '';
} }
}, function(newName) { }, (newName) => {
if (!newName) if (!newName) {
{ jnode.addClass('editempty');
jnode.addClass("editempty");
jnode.val(_('pad.userlist.unnamed')); jnode.val(_('pad.userlist.unnamed'));
} } else {
else
{
jnode.attr('disabled', 'disabled'); jnode.attr('disabled', 'disabled');
pad.suggestUserName(userId, newName); pad.suggestUserName(userId, newName);
} }
@ -363,11 +310,9 @@ var paduserlist = (function() {
} }
function findExistingIndex(userId) { function findExistingIndex(userId) {
var existingIndex = -1; let existingIndex = -1;
for (var i = 0; i < otherUsersInfo.length; i++) for (let i = 0; i < otherUsersInfo.length; i++) {
{ if (otherUsersInfo[i].userId == userId) {
if (otherUsersInfo[i].userId == userId)
{
existingIndex = i; existingIndex = i;
break; break;
} }
@ -376,144 +321,134 @@ var paduserlist = (function() {
} }
function setUpEditable(jqueryNode, valueGetter, valueSetter) { function setUpEditable(jqueryNode, valueGetter, valueSetter) {
jqueryNode.bind('focus', function(evt) { jqueryNode.bind('focus', (evt) => {
var oldValue = valueGetter(); const oldValue = valueGetter();
if (jqueryNode.val() !== oldValue) if (jqueryNode.val() !== oldValue) {
{
jqueryNode.val(oldValue); jqueryNode.val(oldValue);
} }
jqueryNode.addClass("editactive").removeClass("editempty"); jqueryNode.addClass('editactive').removeClass('editempty');
}); });
jqueryNode.bind('blur', function(evt) { jqueryNode.bind('blur', (evt) => {
var newValue = jqueryNode.removeClass("editactive").val(); const newValue = jqueryNode.removeClass('editactive').val();
valueSetter(newValue); valueSetter(newValue);
}); });
padutils.bindEnterAndEscape(jqueryNode, function onEnter() { padutils.bindEnterAndEscape(jqueryNode, () => {
jqueryNode.blur(); jqueryNode.blur();
}, function onEscape() { }, () => {
jqueryNode.val(valueGetter()).blur(); jqueryNode.val(valueGetter()).blur();
}); });
jqueryNode.removeAttr('disabled').addClass('editable'); jqueryNode.removeAttr('disabled').addClass('editable');
} }
var knocksToIgnore = {}; const knocksToIgnore = {};
var guestPromptFlashState = 0; let guestPromptFlashState = 0;
var guestPromptFlash = padutils.makeAnimationScheduler( const guestPromptFlash = padutils.makeAnimationScheduler(
function() { () => {
var prompts = $("#guestprompts .guestprompt"); const prompts = $('#guestprompts .guestprompt');
if (prompts.length == 0) if (prompts.length == 0) {
{ return false; // no more to do
return false; // no more to do }
}
guestPromptFlashState = 1 - guestPromptFlashState; guestPromptFlashState = 1 - guestPromptFlashState;
if (guestPromptFlashState) if (guestPromptFlashState) {
{ prompts.css('background', '#ffa');
prompts.css('background', '#ffa'); } else {
} prompts.css('background', '#ffe');
else }
{
prompts.css('background', '#ffe');
}
return true; return true;
}, 1000); }, 1000);
var pad = undefined; var pad = undefined;
var self = { var self = {
init: function(myInitialUserInfo, _pad) { init(myInitialUserInfo, _pad) {
pad = _pad; pad = _pad;
self.setMyUserInfo(myInitialUserInfo); self.setMyUserInfo(myInitialUserInfo);
if($('#online_count').length === 0) $('#editbar [data-key=showusers] > a').append('<span id="online_count">1</span>'); if ($('#online_count').length === 0) $('#editbar [data-key=showusers] > a').append('<span id="online_count">1</span>');
$("#otheruserstable tr").remove(); $('#otheruserstable tr').remove();
if (pad.getUserIsGuest()) if (pad.getUserIsGuest()) {
{ $('#myusernameedit').addClass('myusernameedithoverable');
$("#myusernameedit").addClass('myusernameedithoverable'); setUpEditable($('#myusernameedit'), () => myUserInfo.name || '', (newValue) => {
setUpEditable($("#myusernameedit"), function() {
return myUserInfo.name || '';
}, function(newValue) {
myUserInfo.name = newValue; myUserInfo.name = newValue;
pad.notifyChangeName(newValue); pad.notifyChangeName(newValue);
// wrap with setTimeout to do later because we get // wrap with setTimeout to do later because we get
// a double "blur" fire in IE... // a double "blur" fire in IE...
window.setTimeout(function() { window.setTimeout(() => {
self.renderMyUserInfo(); self.renderMyUserInfo();
}, 0); }, 0);
}); });
} }
// color picker // color picker
$("#myswatchbox").click(showColorPicker); $('#myswatchbox').click(showColorPicker);
$("#mycolorpicker .pickerswatchouter").click(function() { $('#mycolorpicker .pickerswatchouter').click(function () {
$("#mycolorpicker .pickerswatchouter").removeClass('picked'); $('#mycolorpicker .pickerswatchouter').removeClass('picked');
$(this).addClass('picked'); $(this).addClass('picked');
}); });
$("#mycolorpickersave").click(function() { $('#mycolorpickersave').click(() => {
closeColorPicker(true); closeColorPicker(true);
}); });
$("#mycolorpickercancel").click(function() { $('#mycolorpickercancel').click(() => {
closeColorPicker(false); closeColorPicker(false);
}); });
// //
}, },
usersOnline: function() { usersOnline() {
// Returns an object of users who are currently online on this pad // Returns an object of users who are currently online on this pad
var userList = [].concat(otherUsersInfo); // Make a copy of the otherUsersInfo, otherwise every call to users modifies the referenced array const userList = [].concat(otherUsersInfo); // Make a copy of the otherUsersInfo, otherwise every call to users modifies the referenced array
// Now we need to add ourselves.. // Now we need to add ourselves..
userList.push(myUserInfo); userList.push(myUserInfo);
return userList; return userList;
}, },
users: function(){ users() {
// Returns an object of users who have been on this pad // Returns an object of users who have been on this pad
var userList = self.usersOnline(); const userList = self.usersOnline();
// Now we add historical authors // Now we add historical authors
var historical = clientVars.collab_client_vars.historicalAuthorData; const historical = clientVars.collab_client_vars.historicalAuthorData;
for (var key in historical){ for (const key in historical) {
var userId = historical[key].userId; var userId = historical[key].userId;
// Check we don't already have this author in our array // Check we don't already have this author in our array
var exists = false; var exists = false;
userList.forEach(function(user){ userList.forEach((user) => {
if(user.userId === userId) exists = true; if (user.userId === userId) exists = true;
}); });
if(exists === false){ if (exists === false) {
userList.push(historical[key]); userList.push(historical[key]);
} }
} }
return userList; return userList;
}, },
setMyUserInfo: function(info) { setMyUserInfo(info) {
//translate the colorId // translate the colorId
if(typeof info.colorId == "number") if (typeof info.colorId === 'number') {
{
info.colorId = clientVars.colorPalette[info.colorId]; info.colorId = clientVars.colorPalette[info.colorId];
} }
myUserInfo = $.extend( myUserInfo = $.extend(
{}, info); {}, info);
self.renderMyUserInfo(); self.renderMyUserInfo();
}, },
userJoinOrUpdate: function(info) { userJoinOrUpdate(info) {
if ((!info.userId) || (info.userId == myUserInfo.userId)) if ((!info.userId) || (info.userId == myUserInfo.userId)) {
{
// not sure how this would happen // not sure how this would happen
return; return;
} }
hooks.callAll('userJoinOrUpdate', { hooks.callAll('userJoinOrUpdate', {
userInfo: info userInfo: info,
}); });
var userData = {}; const userData = {};
userData.color = typeof info.colorId == "number" ? clientVars.colorPalette[info.colorId] : info.colorId; userData.color = typeof info.colorId === 'number' ? clientVars.colorPalette[info.colorId] : info.colorId;
userData.name = info.name; userData.name = info.name;
userData.status = ''; userData.status = '';
userData.activity = ''; userData.activity = '';
@ -521,38 +456,32 @@ var paduserlist = (function() {
// Firefox ignores \n in title text; Safari does a linebreak // Firefox ignores \n in title text; Safari does a linebreak
userData.titleText = [info.userAgent || '', info.ip || ''].join(' \n'); userData.titleText = [info.userAgent || '', info.ip || ''].join(' \n');
var existingIndex = findExistingIndex(info.userId); const existingIndex = findExistingIndex(info.userId);
var numUsersBesides = otherUsersInfo.length; let numUsersBesides = otherUsersInfo.length;
if (existingIndex >= 0) if (existingIndex >= 0) {
{
numUsersBesides--; numUsersBesides--;
} }
var newIndex = padutils.binarySearch(numUsersBesides, function(n) { const newIndex = padutils.binarySearch(numUsersBesides, (n) => {
if (existingIndex >= 0 && n >= existingIndex) if (existingIndex >= 0 && n >= existingIndex) {
{
// pretend existingIndex isn't there // pretend existingIndex isn't there
n++; n++;
} }
var infoN = otherUsersInfo[n]; const infoN = otherUsersInfo[n];
var nameN = (infoN.name || '').toLowerCase(); const nameN = (infoN.name || '').toLowerCase();
var nameThis = (info.name || '').toLowerCase(); const nameThis = (info.name || '').toLowerCase();
var idN = infoN.userId; const idN = infoN.userId;
var idThis = info.userId; const idThis = info.userId;
return (nameN > nameThis) || (nameN == nameThis && idN > idThis); return (nameN > nameThis) || (nameN == nameThis && idN > idThis);
}); });
if (existingIndex >= 0) if (existingIndex >= 0) {
{
// update // update
if (existingIndex == newIndex) if (existingIndex == newIndex) {
{
otherUsersInfo[existingIndex] = info; otherUsersInfo[existingIndex] = info;
otherUsersData[existingIndex] = userData; otherUsersData[existingIndex] = userData;
rowManager.updateRow(existingIndex, userData); rowManager.updateRow(existingIndex, userData);
} } else {
else
{
otherUsersInfo.splice(existingIndex, 1); otherUsersInfo.splice(existingIndex, 1);
otherUsersData.splice(existingIndex, 1); otherUsersData.splice(existingIndex, 1);
otherUsersInfo.splice(newIndex, 0, info); otherUsersInfo.splice(newIndex, 0, info);
@ -560,9 +489,7 @@ var paduserlist = (function() {
rowManager.updateRow(existingIndex, userData); rowManager.updateRow(existingIndex, userData);
rowManager.moveRow(existingIndex, newIndex); rowManager.moveRow(existingIndex, newIndex);
} }
} } else {
else
{
otherUsersInfo.splice(newIndex, 0, info); otherUsersInfo.splice(newIndex, 0, info);
otherUsersData.splice(newIndex, 0, userData); otherUsersData.splice(newIndex, 0, userData);
rowManager.insertRow(newIndex, userData); rowManager.insertRow(newIndex, userData);
@ -570,12 +497,10 @@ var paduserlist = (function() {
self.updateNumberOfOnlineUsers(); self.updateNumberOfOnlineUsers();
}, },
updateNumberOfOnlineUsers: function() { updateNumberOfOnlineUsers() {
var online = 1; // you are always online! let online = 1; // you are always online!
for (var i = 0; i < otherUsersData.length; i++) for (let i = 0; i < otherUsersData.length; i++) {
{ if (otherUsersData[i].status == '') {
if (otherUsersData[i].status == "")
{
online++; online++;
} }
} }
@ -584,33 +509,29 @@ var paduserlist = (function() {
return online; return online;
}, },
userLeave: function(info) { userLeave(info) {
var existingIndex = findExistingIndex(info.userId); const existingIndex = findExistingIndex(info.userId);
if (existingIndex >= 0) if (existingIndex >= 0) {
{ const userData = otherUsersData[existingIndex];
var userData = otherUsersData[existingIndex];
userData.status = 'Disconnected'; userData.status = 'Disconnected';
rowManager.updateRow(existingIndex, userData); rowManager.updateRow(existingIndex, userData);
if (userData.leaveTimer) if (userData.leaveTimer) {
{
window.clearTimeout(userData.leaveTimer); window.clearTimeout(userData.leaveTimer);
} }
// set up a timer that will only fire if no leaves, // set up a timer that will only fire if no leaves,
// joins, or updates happen for this user in the // joins, or updates happen for this user in the
// next N seconds, to remove the user from the list. // next N seconds, to remove the user from the list.
var thisUserId = info.userId; const thisUserId = info.userId;
var thisLeaveTimer = window.setTimeout(function() { var thisLeaveTimer = window.setTimeout(() => {
var newExistingIndex = findExistingIndex(thisUserId); const newExistingIndex = findExistingIndex(thisUserId);
if (newExistingIndex >= 0) if (newExistingIndex >= 0) {
{ const newUserData = otherUsersData[newExistingIndex];
var newUserData = otherUsersData[newExistingIndex]; if (newUserData.status == 'Disconnected' && newUserData.leaveTimer == thisLeaveTimer) {
if (newUserData.status == 'Disconnected' && newUserData.leaveTimer == thisLeaveTimer)
{
otherUsersInfo.splice(newExistingIndex, 1); otherUsersInfo.splice(newExistingIndex, 1);
otherUsersData.splice(newExistingIndex, 1); otherUsersData.splice(newExistingIndex, 1);
rowManager.removeRow(newExistingIndex); rowManager.removeRow(newExistingIndex);
hooks.callAll('userLeave', { hooks.callAll('userLeave', {
userInfo: info userInfo: info,
}); });
} }
} }
@ -620,163 +541,143 @@ var paduserlist = (function() {
self.updateNumberOfOnlineUsers(); self.updateNumberOfOnlineUsers();
}, },
showGuestPrompt: function(userId, displayName) { showGuestPrompt(userId, displayName) {
if (knocksToIgnore[userId]) if (knocksToIgnore[userId]) {
{
return; return;
} }
var encodedUserId = padutils.encodeUserId(userId); const encodedUserId = padutils.encodeUserId(userId);
var actionName = 'hide-guest-prompt-' + encodedUserId; const actionName = `hide-guest-prompt-${encodedUserId}`;
padutils.cancelActions(actionName); padutils.cancelActions(actionName);
var box = $("#guestprompt-" + encodedUserId); let box = $(`#guestprompt-${encodedUserId}`);
if (box.length == 0) if (box.length == 0) {
{
// make guest prompt box // make guest prompt box
box = $('<div id="'+padutils.escapeHtml('guestprompt-' + encodedUserId) + '" class="guestprompt"><div class="choices"><a href="' + padutils.escapeHtml('javascript:void(require('+JSON.stringify(module.id)+').paduserlist.answerGuestPrompt(' + JSON.stringify(encodedUserId) + ',false))')+'">'+_('pad.userlist.deny')+'</a> <a href="' + padutils.escapeHtml('javascript:void(require('+JSON.stringify(module.id)+').paduserlist.answerGuestPrompt(' + JSON.stringify(encodedUserId) + ',true))') + '">'+_('pad.userlist.approve')+'</a></div><div class="guestname"><strong>'+_('pad.userlist.guest')+':</strong> ' + padutils.escapeHtml(displayName) + '</div></div>'); box = $(`<div id="${padutils.escapeHtml(`guestprompt-${encodedUserId}`)}" class="guestprompt"><div class="choices"><a href="${padutils.escapeHtml(`javascript:void(require(${JSON.stringify(module.id)}).paduserlist.answerGuestPrompt(${JSON.stringify(encodedUserId)},false))`)}">${_('pad.userlist.deny')}</a> <a href="${padutils.escapeHtml(`javascript:void(require(${JSON.stringify(module.id)}).paduserlist.answerGuestPrompt(${JSON.stringify(encodedUserId)},true))`)}">${_('pad.userlist.approve')}</a></div><div class="guestname"><strong>${_('pad.userlist.guest')}:</strong> ${padutils.escapeHtml(displayName)}</div></div>`);
$("#guestprompts").append(box); $('#guestprompts').append(box);
} } else {
else
{
// update display name // update display name
box.find(".guestname").html('<strong>'+_('pad.userlist.guest')+':</strong> ' + padutils.escapeHtml(displayName)); box.find('.guestname').html(`<strong>${_('pad.userlist.guest')}:</strong> ${padutils.escapeHtml(displayName)}`);
} }
var hideLater = padutils.getCancellableAction(actionName, function() { const hideLater = padutils.getCancellableAction(actionName, () => {
self.removeGuestPrompt(userId); self.removeGuestPrompt(userId);
}); });
window.setTimeout(hideLater, 15000); // time-out with no knock window.setTimeout(hideLater, 15000); // time-out with no knock
guestPromptFlash.scheduleAnimation(); guestPromptFlash.scheduleAnimation();
}, },
removeGuestPrompt: function(userId) { removeGuestPrompt(userId) {
var box = $("#guestprompt-" + padutils.encodeUserId(userId)); const box = $(`#guestprompt-${padutils.encodeUserId(userId)}`);
// remove ID now so a new knock by same user gets new, unfaded box // remove ID now so a new knock by same user gets new, unfaded box
box.removeAttr('id').fadeOut("fast", function() { box.removeAttr('id').fadeOut('fast', () => {
box.remove(); box.remove();
}); });
knocksToIgnore[userId] = true; knocksToIgnore[userId] = true;
window.setTimeout(function() { window.setTimeout(() => {
delete knocksToIgnore[userId]; delete knocksToIgnore[userId];
}, 5000); }, 5000);
}, },
answerGuestPrompt: function(encodedUserId, approve) { answerGuestPrompt(encodedUserId, approve) {
var guestId = padutils.decodeUserId(encodedUserId); const guestId = padutils.decodeUserId(encodedUserId);
var msg = { const msg = {
type: 'guestanswer', type: 'guestanswer',
authId: pad.getUserId(), authId: pad.getUserId(),
guestId: guestId, guestId,
answer: (approve ? "approved" : "denied") answer: (approve ? 'approved' : 'denied'),
}; };
pad.sendClientMessage(msg); pad.sendClientMessage(msg);
self.removeGuestPrompt(guestId); self.removeGuestPrompt(guestId);
}, },
renderMyUserInfo: function() { renderMyUserInfo() {
if (myUserInfo.name) if (myUserInfo.name) {
{ $('#myusernameedit').removeClass('editempty').val(myUserInfo.name);
$("#myusernameedit").removeClass("editempty").val(myUserInfo.name); } else {
$('#myusernameedit').attr('placeholder', html10n.get('pad.userlist.entername'));
} }
else if (colorPickerOpen) {
{ $('#myswatchbox').addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable');
$("#myusernameedit").attr("placeholder", html10n.get("pad.userlist.entername")); } else {
} $('#myswatchbox').addClass('myswatchboxhoverable').removeClass('myswatchboxunhoverable');
if (colorPickerOpen)
{
$("#myswatchbox").addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable');
}
else
{
$("#myswatchbox").addClass('myswatchboxhoverable').removeClass('myswatchboxunhoverable');
} }
$("#myswatch").css({'background-color': myUserInfo.colorId}); $('#myswatch').css({'background-color': myUserInfo.colorId});
if (browser.msie && parseInt(browser.version) <= 8) { if (browser.msie && parseInt(browser.version) <= 8) {
$("li[data-key=showusers] > a").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId,'background-color': myUserInfo.colorId}); $('li[data-key=showusers] > a').css({'box-shadow': `inset 0 0 30px ${myUserInfo.colorId}`, 'background-color': myUserInfo.colorId});
} else {
$('li[data-key=showusers] > a').css({'box-shadow': `inset 0 0 30px ${myUserInfo.colorId}`});
} }
else },
{
$("li[data-key=showusers] > a").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId});
}
}
}; };
return self; return self;
}()); }());
function getColorPickerSwatchIndex(jnode) { function getColorPickerSwatchIndex(jnode) {
// return Number(jnode.get(0).className.match(/\bn([0-9]+)\b/)[1])-1; // return Number(jnode.get(0).className.match(/\bn([0-9]+)\b/)[1])-1;
return $("#colorpickerswatches li").index(jnode); return $('#colorpickerswatches li').index(jnode);
} }
function closeColorPicker(accept) { function closeColorPicker(accept) {
if (accept) if (accept) {
{ var newColor = $('#mycolorpickerpreview').css('background-color');
var newColor = $("#mycolorpickerpreview").css("background-color"); const parts = newColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
var parts = newColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
// parts now should be ["rgb(0, 70, 255", "0", "70", "255"] // parts now should be ["rgb(0, 70, 255", "0", "70", "255"]
if (parts) { if (parts) {
delete (parts[0]); delete (parts[0]);
for (var i = 1; i <= 3; ++i) { for (let i = 1; i <= 3; ++i) {
parts[i] = parseInt(parts[i]).toString(16); parts[i] = parseInt(parts[i]).toString(16);
if (parts[i].length == 1) parts[i] = '0' + parts[i]; if (parts[i].length == 1) parts[i] = `0${parts[i]}`;
} }
var newColor = "#" +parts.join(''); // "0070ff" var newColor = `#${parts.join('')}`; // "0070ff"
} }
myUserInfo.colorId = newColor; myUserInfo.colorId = newColor;
pad.notifyChangeColor(newColor); pad.notifyChangeColor(newColor);
paduserlist.renderMyUserInfo(); paduserlist.renderMyUserInfo();
} } else {
else // pad.notifyChangeColor(previousColorId);
{ // paduserlist.renderMyUserInfo();
//pad.notifyChangeColor(previousColorId);
//paduserlist.renderMyUserInfo();
} }
colorPickerOpen = false; colorPickerOpen = false;
$("#mycolorpicker").removeClass('popup-show'); $('#mycolorpicker').removeClass('popup-show');
} }
function showColorPicker() { function showColorPicker() {
previousColorId = myUserInfo.colorId; previousColorId = myUserInfo.colorId;
$.farbtastic('#colorpicker').setColor(myUserInfo.colorId) $.farbtastic('#colorpicker').setColor(myUserInfo.colorId);
if (!colorPickerOpen) if (!colorPickerOpen) {
{ const palette = pad.getColorPalette();
var palette = pad.getColorPalette();
if (!colorPickerSetup) if (!colorPickerSetup) {
{ const colorsList = $('#colorpickerswatches');
var colorsList = $("#colorpickerswatches") for (let i = 0; i < palette.length; i++) {
for (var i = 0; i < palette.length; i++) const li = $('<li>', {
{ style: `background: ${palette[i]};`,
var li = $('<li>', {
style: 'background: ' + palette[i] + ';'
}); });
li.appendTo(colorsList); li.appendTo(colorsList);
li.bind('click', function(event) { li.bind('click', (event) => {
$("#colorpickerswatches li").removeClass('picked'); $('#colorpickerswatches li').removeClass('picked');
$(event.target).addClass("picked"); $(event.target).addClass('picked');
var newColorId = getColorPickerSwatchIndex($("#colorpickerswatches .picked")); const newColorId = getColorPickerSwatchIndex($('#colorpickerswatches .picked'));
pad.notifyChangeColor(newColorId); pad.notifyChangeColor(newColorId);
}); });
} }
colorPickerSetup = true; colorPickerSetup = true;
} }
$("#mycolorpicker").addClass('popup-show') $('#mycolorpicker').addClass('popup-show');
colorPickerOpen = true; colorPickerOpen = true;
$("#colorpickerswatches li").removeClass('picked'); $('#colorpickerswatches li').removeClass('picked');
$($("#colorpickerswatches li")[myUserInfo.colorId]).addClass("picked"); //seems weird $($('#colorpickerswatches li')[myUserInfo.colorId]).addClass('picked'); // seems weird
} }
} }

View file

@ -20,129 +20,119 @@
* limitations under the License. * limitations under the License.
*/ */
var Security = require('./security'); const Security = require('./security');
/** /**
* Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids * Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
*/ */
function randomString(len) { function randomString(len) {
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
var randomstring = ''; let randomstring = '';
len = len || 20 len = len || 20;
for (var i = 0; i < len; i++) for (let i = 0; i < len; i++) {
{ const rnum = Math.floor(Math.random() * chars.length);
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1); randomstring += chars.substring(rnum, rnum + 1);
} }
return randomstring; return randomstring;
} }
var padutils = { var padutils = {
escapeHtml: function(x) { escapeHtml(x) {
return Security.escapeHTML(String(x)); return Security.escapeHTML(String(x));
}, },
uniqueId: function() { uniqueId() {
var pad = require('./pad').pad; // Sidestep circular dependency const pad = require('./pad').pad; // Sidestep circular dependency
function encodeNum(n, width) { function encodeNum(n, width) {
// returns string that is exactly 'width' chars, padding with zeros // returns string that is exactly 'width' chars, padding with zeros
// and taking rightmost digits // and taking rightmost digits
return (Array(width + 1).join('0') + Number(n).toString(35)).slice(-width); return (Array(width + 1).join('0') + Number(n).toString(35)).slice(-width);
} }
return [pad.getClientIp(), encodeNum(+new Date, 7), encodeNum(Math.floor(Math.random() * 1e9), 4)].join('.'); return [pad.getClientIp(), encodeNum(+new Date(), 7), encodeNum(Math.floor(Math.random() * 1e9), 4)].join('.');
}, },
uaDisplay: function(ua) { uaDisplay(ua) {
var m; let m;
function clean(a) { function clean(a) {
var maxlen = 16; const maxlen = 16;
a = a.replace(/[^a-zA-Z0-9\.]/g, ''); a = a.replace(/[^a-zA-Z0-9\.]/g, '');
if (a.length > maxlen) if (a.length > maxlen) {
{
a = a.substr(0, maxlen); a = a.substr(0, maxlen);
} }
return a; return a;
} }
function checkver(name) { function checkver(name) {
var m = ua.match(RegExp(name + '\\/([\\d\\.]+)')); const m = ua.match(RegExp(`${name}\\/([\\d\\.]+)`));
if (m && m.length > 1) if (m && m.length > 1) {
{
return clean(name + m[1]); return clean(name + m[1]);
} }
return null; return null;
} }
// firefox // firefox
if (checkver('Firefox')) if (checkver('Firefox')) {
{
return checkver('Firefox'); return checkver('Firefox');
} }
// misc browsers, including IE // misc browsers, including IE
m = ua.match(/compatible; ([^;]+);/); m = ua.match(/compatible; ([^;]+);/);
if (m && m.length > 1) if (m && m.length > 1) {
{
return clean(m[1]); return clean(m[1]);
} }
// iphone // iphone
if (ua.match(/\(iPhone;/)) if (ua.match(/\(iPhone;/)) {
{
return 'iPhone'; return 'iPhone';
} }
// chrome // chrome
if (checkver('Chrome')) if (checkver('Chrome')) {
{
return checkver('Chrome'); return checkver('Chrome');
} }
// safari // safari
m = ua.match(/Safari\/[\d\.]+/); m = ua.match(/Safari\/[\d\.]+/);
if (m) if (m) {
{ let v = '?';
var v = '?';
m = ua.match(/Version\/([\d\.]+)/); m = ua.match(/Version\/([\d\.]+)/);
if (m && m.length > 1) if (m && m.length > 1) {
{
v = m[1]; v = m[1];
} }
return clean('Safari' + v); return clean(`Safari${v}`);
} }
// everything else // everything else
var x = ua.split(' ')[0]; const x = ua.split(' ')[0];
return clean(x); return clean(x);
}, },
// e.g. "Thu Jun 18 2009 13:09" // e.g. "Thu Jun 18 2009 13:09"
simpleDateTime: function(date) { simpleDateTime(date) {
var d = new Date(+date); // accept either number or date const d = new Date(+date); // accept either number or date
var dayOfWeek = (['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])[d.getDay()]; const dayOfWeek = (['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])[d.getDay()];
var month = (['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])[d.getMonth()]; const month = (['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])[d.getMonth()];
var dayOfMonth = d.getDate(); const dayOfMonth = d.getDate();
var year = d.getFullYear(); const year = d.getFullYear();
var hourmin = d.getHours() + ":" + ("0" + d.getMinutes()).slice(-2); const hourmin = `${d.getHours()}:${(`0${d.getMinutes()}`).slice(-2)}`;
return dayOfWeek + ' ' + month + ' ' + dayOfMonth + ' ' + year + ' ' + hourmin; return `${dayOfWeek} ${month} ${dayOfMonth} ${year} ${hourmin}`;
}, },
findURLs: function(text) { findURLs(text) {
// copied from ACE // copied from ACE
var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/; const _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')'); const _REGEX_URLCHAR = new RegExp(`(${/[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source}|${_REGEX_WORDCHAR.source})`);
var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|(about|geo|mailto|tel):)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g'); const _REGEX_URL = new RegExp(`${/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|(about|geo|mailto|tel):)/.source + _REGEX_URLCHAR.source}*(?![:.,;])${_REGEX_URLCHAR.source}`, 'g');
// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...] // returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
function _findURLs(text) { function _findURLs(text) {
_REGEX_URL.lastIndex = 0; _REGEX_URL.lastIndex = 0;
var urls = null; let urls = null;
var execResult; let execResult;
while ((execResult = _REGEX_URL.exec(text))) while ((execResult = _REGEX_URL.exec(text))) {
{
urls = (urls || []); urls = (urls || []);
var startIndex = execResult.index; const startIndex = execResult.index;
var url = execResult[0]; const url = execResult[0];
urls.push([startIndex, url]); urls.push([startIndex, url]);
} }
@ -151,24 +141,21 @@ var padutils = {
return _findURLs(text); return _findURLs(text);
}, },
escapeHtmlWithClickableLinks: function(text, target) { escapeHtmlWithClickableLinks(text, target) {
var idx = 0; let idx = 0;
var pieces = []; const pieces = [];
var urls = padutils.findURLs(text); const urls = padutils.findURLs(text);
function advanceTo(i) { function advanceTo(i) {
if (i > idx) if (i > idx) {
{
pieces.push(Security.escapeHTML(text.substring(idx, i))); pieces.push(Security.escapeHTML(text.substring(idx, i)));
idx = i; idx = i;
} }
} }
if (urls) if (urls) {
{ for (let j = 0; j < urls.length; j++) {
for (var j = 0; j < urls.length; j++) const startIndex = urls[j][0];
{ const href = urls[j][1];
var startIndex = urls[j][0];
var href = urls[j][1];
advanceTo(startIndex); advanceTo(startIndex);
// Using rel="noreferrer" stops leaking the URL/location of the pad when clicking links in the document. // Using rel="noreferrer" stops leaking the URL/location of the pad when clicking links in the document.
// Not all browsers understand this attribute, but it's part of the HTML5 standard. // Not all browsers understand this attribute, but it's part of the HTML5 standard.
@ -177,7 +164,7 @@ var padutils = {
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener // https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
// https://mathiasbynens.github.io/rel-noopener/ // https://mathiasbynens.github.io/rel-noopener/
// https://github.com/ether/etherpad-lite/pull/3636 // https://github.com/ether/etherpad-lite/pull/3636
pieces.push('<a ', (target ? 'target="' + Security.escapeHTMLAttribute(target) + '" ' : ''), 'href="', Security.escapeHTMLAttribute(href), '" rel="noreferrer noopener">'); pieces.push('<a ', (target ? `target="${Security.escapeHTMLAttribute(target)}" ` : ''), 'href="', Security.escapeHTMLAttribute(href), '" rel="noreferrer noopener">');
advanceTo(startIndex + href.length); advanceTo(startIndex + href.length);
pieces.push('</a>'); pieces.push('</a>');
} }
@ -185,78 +172,66 @@ var padutils = {
advanceTo(text.length); advanceTo(text.length);
return pieces.join(''); return pieces.join('');
}, },
bindEnterAndEscape: function(node, onEnter, onEscape) { bindEnterAndEscape(node, onEnter, onEscape) {
// Use keypress instead of keyup in bindEnterAndEscape // Use keypress instead of keyup in bindEnterAndEscape
// Keyup event is fired on enter in IME (Input Method Editor), But // Keyup event is fired on enter in IME (Input Method Editor), But
// keypress is not. So, I changed to use keypress instead of keyup. // keypress is not. So, I changed to use keypress instead of keyup.
// It is work on Windows (IE8, Chrome 6.0.472), CentOs (Firefox 3.0) and Mac OSX (Firefox 3.6.10, Chrome 6.0.472, Safari 5.0). // It is work on Windows (IE8, Chrome 6.0.472), CentOs (Firefox 3.0) and Mac OSX (Firefox 3.6.10, Chrome 6.0.472, Safari 5.0).
if (onEnter) if (onEnter) {
{ node.keypress((evt) => {
node.keypress(function(evt) { if (evt.which == 13) {
if (evt.which == 13)
{
onEnter(evt); onEnter(evt);
} }
}); });
} }
if (onEscape) if (onEscape) {
{ node.keydown((evt) => {
node.keydown(function(evt) { if (evt.which == 27) {
if (evt.which == 27)
{
onEscape(evt); onEscape(evt);
} }
}); });
} }
}, },
timediff: function(d) { timediff(d) {
var pad = require('./pad').pad; // Sidestep circular dependency const pad = require('./pad').pad; // Sidestep circular dependency
function format(n, word) { function format(n, word) {
n = Math.round(n); n = Math.round(n);
return ('' + n + ' ' + word + (n != 1 ? 's' : '') + ' ago'); return (`${n} ${word}${n != 1 ? 's' : ''} ago`);
} }
d = Math.max(0, (+(new Date) - (+d) - pad.clientTimeOffset) / 1000); d = Math.max(0, (+(new Date()) - (+d) - pad.clientTimeOffset) / 1000);
if (d < 60) if (d < 60) {
{
return format(d, 'second'); return format(d, 'second');
} }
d /= 60; d /= 60;
if (d < 60) if (d < 60) {
{
return format(d, 'minute'); return format(d, 'minute');
} }
d /= 60; d /= 60;
if (d < 24) if (d < 24) {
{
return format(d, 'hour'); return format(d, 'hour');
} }
d /= 24; d /= 24;
return format(d, 'day'); return format(d, 'day');
}, },
makeAnimationScheduler: function(funcToAnimateOneStep, stepTime, stepsAtOnce) { makeAnimationScheduler(funcToAnimateOneStep, stepTime, stepsAtOnce) {
if (stepsAtOnce === undefined) if (stepsAtOnce === undefined) {
{
stepsAtOnce = 1; stepsAtOnce = 1;
} }
var animationTimer = null; let animationTimer = null;
function scheduleAnimation() { function scheduleAnimation() {
if (!animationTimer) if (!animationTimer) {
{ animationTimer = window.setTimeout(() => {
animationTimer = window.setTimeout(function() {
animationTimer = null; animationTimer = null;
var n = stepsAtOnce; let n = stepsAtOnce;
var moreToDo = true; let moreToDo = true;
while (moreToDo && n > 0) while (moreToDo && n > 0) {
{
moreToDo = funcToAnimateOneStep(); moreToDo = funcToAnimateOneStep();
n--; n--;
} }
if (moreToDo) if (moreToDo) {
{
// more to do // more to do
scheduleAnimation(); scheduleAnimation();
} }
@ -264,15 +239,15 @@ var padutils = {
} }
} }
return { return {
scheduleAnimation: scheduleAnimation scheduleAnimation,
}; };
}, },
makeShowHideAnimator: function(funcToArriveAtState, initiallyShown, fps, totalMs) { makeShowHideAnimator(funcToArriveAtState, initiallyShown, fps, totalMs) {
var animationState = (initiallyShown ? 0 : -2); // -2 hidden, -1 to 0 fade in, 0 to 1 fade out let animationState = (initiallyShown ? 0 : -2); // -2 hidden, -1 to 0 fade in, 0 to 1 fade out
var animationFrameDelay = 1000 / fps; const animationFrameDelay = 1000 / fps;
var animationStep = animationFrameDelay / totalMs; const animationStep = animationFrameDelay / totalMs;
var scheduleAnimation = padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation; const scheduleAnimation = padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation;
function doShow() { function doShow() {
animationState = -1; animationState = -1;
@ -281,12 +256,9 @@ var padutils = {
} }
function doQuickShow() { // start showing without losing any fade-in progress function doQuickShow() { // start showing without losing any fade-in progress
if (animationState < -1) if (animationState < -1) {
{
animationState = -1; animationState = -1;
} } else if (animationState > 0) {
else if (animationState > 0)
{
animationState = Math.max(-1, Math.min(0, -animationState)); animationState = Math.max(-1, Math.min(0, -animationState));
} }
funcToArriveAtState(animationState); funcToArriveAtState(animationState);
@ -294,47 +266,35 @@ var padutils = {
} }
function doHide() { function doHide() {
if (animationState >= -1 && animationState <= 0) if (animationState >= -1 && animationState <= 0) {
{
animationState = 1e-6; animationState = 1e-6;
scheduleAnimation(); scheduleAnimation();
} }
} }
function animateOneStep() { function animateOneStep() {
if (animationState < -1 || animationState == 0) if (animationState < -1 || animationState == 0) {
{
return false; return false;
} } else if (animationState < 0) {
else if (animationState < 0)
{
// animate show // animate show
animationState += animationStep; animationState += animationStep;
if (animationState >= 0) if (animationState >= 0) {
{
animationState = 0; animationState = 0;
funcToArriveAtState(animationState); funcToArriveAtState(animationState);
return false; return false;
} } else {
else
{
funcToArriveAtState(animationState); funcToArriveAtState(animationState);
return true; return true;
} }
} } else if (animationState > 0) {
else if (animationState > 0)
{
// animate hide // animate hide
animationState += animationStep; animationState += animationStep;
if (animationState >= 1) if (animationState >= 1) {
{
animationState = 1; animationState = 1;
funcToArriveAtState(animationState); funcToArriveAtState(animationState);
animationState = -2; animationState = -2;
return false; return false;
} } else {
else
{
funcToArriveAtState(animationState); funcToArriveAtState(animationState);
return true; return true;
} }
@ -344,95 +304,83 @@ var padutils = {
return { return {
show: doShow, show: doShow,
hide: doHide, hide: doHide,
quickShow: doQuickShow quickShow: doQuickShow,
}; };
}, },
_nextActionId: 1, _nextActionId: 1,
uncanceledActions: {}, uncanceledActions: {},
getCancellableAction: function(actionType, actionFunc) { getCancellableAction(actionType, actionFunc) {
var o = padutils.uncanceledActions[actionType]; let o = padutils.uncanceledActions[actionType];
if (!o) if (!o) {
{
o = {}; o = {};
padutils.uncanceledActions[actionType] = o; padutils.uncanceledActions[actionType] = o;
} }
var actionId = (padutils._nextActionId++); const actionId = (padutils._nextActionId++);
o[actionId] = true; o[actionId] = true;
return function() { return function () {
var p = padutils.uncanceledActions[actionType]; const p = padutils.uncanceledActions[actionType];
if (p && p[actionId]) if (p && p[actionId]) {
{
actionFunc(); actionFunc();
} }
}; };
}, },
cancelActions: function(actionType) { cancelActions(actionType) {
var o = padutils.uncanceledActions[actionType]; const o = padutils.uncanceledActions[actionType];
if (o) if (o) {
{
// clear it // clear it
delete padutils.uncanceledActions[actionType]; delete padutils.uncanceledActions[actionType];
} }
}, },
makeFieldLabeledWhenEmpty: function(field, labelText) { makeFieldLabeledWhenEmpty(field, labelText) {
field = $(field); field = $(field);
function clear() { function clear() {
field.addClass('editempty'); field.addClass('editempty');
field.val(labelText); field.val(labelText);
} }
field.focus(function() { field.focus(() => {
if (field.hasClass('editempty')) if (field.hasClass('editempty')) {
{
field.val(''); field.val('');
} }
field.removeClass('editempty'); field.removeClass('editempty');
}); });
field.blur(function() { field.blur(() => {
if (!field.val()) if (!field.val()) {
{
clear(); clear();
} }
}); });
return { return {
clear: clear clear,
}; };
}, },
getCheckbox: function(node) { getCheckbox(node) {
return $(node).is(':checked'); return $(node).is(':checked');
}, },
setCheckbox: function(node, value) { setCheckbox(node, value) {
if (value) if (value) {
{
$(node).attr('checked', 'checked'); $(node).attr('checked', 'checked');
} } else {
else
{
$(node).removeAttr('checked'); $(node).removeAttr('checked');
} }
}, },
bindCheckboxChange: function(node, func) { bindCheckboxChange(node, func) {
$(node).change(func); $(node).change(func);
}, },
encodeUserId: function(userId) { encodeUserId(userId) {
return userId.replace(/[^a-y0-9]/g, function(c) { return userId.replace(/[^a-y0-9]/g, (c) => {
if (c == ".") return "-"; if (c == '.') return '-';
return 'z' + c.charCodeAt(0) + 'z'; return `z${c.charCodeAt(0)}z`;
}); });
}, },
decodeUserId: function(encodedUserId) { decodeUserId(encodedUserId) {
return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, function(cc) { return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, (cc) => {
if (cc == '-') return '.'; if (cc == '-') { return '.'; } else if (cc.charAt(0) == 'z') {
else if (cc.charAt(0) == 'z')
{
return String.fromCharCode(Number(cc.slice(1, -1))); return String.fromCharCode(Number(cc.slice(1, -1)));
} } else {
else
{
return cc; return cc;
} }
}); });
} },
}; };
let globalExceptionHandler = null; let globalExceptionHandler = null;
@ -453,12 +401,12 @@ padutils.setupGlobalExceptionHandler = () => {
} else { } else {
throw new Error(`unknown event: ${e.toString()}`); throw new Error(`unknown event: ${e.toString()}`);
} }
var errorId = randomString(20); const errorId = randomString(20);
var msgAlreadyVisible = false; let msgAlreadyVisible = false;
$('.gritter-item .error-msg').each(function() { $('.gritter-item .error-msg').each(function () {
if ($(this).text() === msg) { if ($(this).text() === msg) {
msgAlreadyVisible = true; msgAlreadyVisible = true;
} }
}); });
@ -479,9 +427,9 @@ padutils.setupGlobalExceptionHandler = () => {
]; ];
$.gritter.add({ $.gritter.add({
title: "An error occurred", title: 'An error occurred',
text: errorMsg, text: errorMsg,
class_name: "error", class_name: 'error',
position: 'bottom', position: 'bottom',
sticky: true, sticky: true,
}); });
@ -505,7 +453,7 @@ padutils.setupGlobalExceptionHandler = () => {
window.addEventListener('error', globalExceptionHandler); window.addEventListener('error', globalExceptionHandler);
window.addEventListener('unhandledrejection', globalExceptionHandler); window.addEventListener('unhandledrejection', globalExceptionHandler);
} }
} };
padutils.binarySearch = require('./ace2_common').binarySearch; padutils.binarySearch = require('./ace2_common').binarySearch;

View file

@ -1,43 +1,41 @@
var $, jQuery; let $, jQuery;
$ = jQuery = require("ep_etherpad-lite/static/js/rjquery").$; $ = jQuery = require('ep_etherpad-lite/static/js/rjquery').$;
var _ = require("underscore"); const _ = require('underscore');
var pluginUtils = require('./shared'); const pluginUtils = require('./shared');
var defs = require('./plugin_defs'); const defs = require('./plugin_defs');
exports.baseURL = ''; exports.baseURL = '';
exports.ensure = function (cb) { exports.ensure = function (cb) {
if (!defs.loaded) if (!defs.loaded) exports.update(cb);
exports.update(cb); else cb();
else
cb();
}; };
exports.update = function (cb) { exports.update = function (cb) {
// It appears that this response (see #620) may interrupt the current thread // It appears that this response (see #620) may interrupt the current thread
// of execution on Firefox. This schedules the response in the run-loop, // of execution on Firefox. This schedules the response in the run-loop,
// which appears to fix the issue. // which appears to fix the issue.
var callback = function () {setTimeout(cb, 0);}; const callback = function () { setTimeout(cb, 0); };
$.ajaxSetup({ cache: false }); $.ajaxSetup({cache: false});
jQuery.getJSON(exports.baseURL + 'pluginfw/plugin-definitions.json').done(function(data) { jQuery.getJSON(`${exports.baseURL}pluginfw/plugin-definitions.json`).done((data) => {
defs.plugins = data.plugins; defs.plugins = data.plugins;
defs.parts = data.parts; defs.parts = data.parts;
defs.hooks = pluginUtils.extractHooks(defs.parts, "client_hooks"); defs.hooks = pluginUtils.extractHooks(defs.parts, 'client_hooks');
defs.loaded = true; defs.loaded = true;
callback(); callback();
}).fail(function(e){ }).fail((e) => {
console.error("Failed to load plugin-definitions: " + err); console.error(`Failed to load plugin-definitions: ${err}`);
callback(); callback();
}); });
}; };
function adoptPluginsFromAncestorsOf(frame) { function adoptPluginsFromAncestorsOf(frame) {
// Bind plugins with parent; // Bind plugins with parent;
var parentRequire = null; let parentRequire = null;
try { try {
while (frame = frame.parent) { while (frame = frame.parent) {
if (typeof (frame.require) !== "undefined") { if (typeof (frame.require) !== 'undefined') {
parentRequire = frame.require; parentRequire = frame.require;
break; break;
} }
@ -46,17 +44,17 @@ function adoptPluginsFromAncestorsOf(frame) {
// Silence (this can only be a XDomain issue). // Silence (this can only be a XDomain issue).
} }
if (parentRequire) { if (parentRequire) {
var ancestorPluginDefs = parentRequire("ep_etherpad-lite/static/js/pluginfw/plugin_defs"); const ancestorPluginDefs = parentRequire('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
defs.hooks = ancestorPluginDefs.hooks; defs.hooks = ancestorPluginDefs.hooks;
defs.loaded = ancestorPluginDefs.loaded; defs.loaded = ancestorPluginDefs.loaded;
defs.parts = ancestorPluginDefs.parts; defs.parts = ancestorPluginDefs.parts;
defs.plugins = ancestorPluginDefs.plugins; defs.plugins = ancestorPluginDefs.plugins;
var ancestorPlugins = parentRequire("ep_etherpad-lite/static/js/pluginfw/client_plugins"); const ancestorPlugins = parentRequire('ep_etherpad-lite/static/js/pluginfw/client_plugins');
exports.baseURL = ancestorPlugins.baseURL; exports.baseURL = ancestorPlugins.baseURL;
exports.ensure = ancestorPlugins.ensure; exports.ensure = ancestorPlugins.ensure;
exports.update = ancestorPlugins.update; exports.update = ancestorPlugins.update;
} else { } else {
throw new Error("Parent plugins could not be found.") throw new Error('Parent plugins could not be found.');
} }
} }

View file

@ -1,7 +1,7 @@
/* global exports, require */ /* global exports, require */
var _ = require("underscore"); const _ = require('underscore');
var pluginDefs = require('./plugin_defs'); const pluginDefs = require('./plugin_defs');
// Maps the name of a server-side hook to a string explaining the deprecation // Maps the name of a server-side hook to a string explaining the deprecation
// (e.g., 'use the foo hook instead'). // (e.g., 'use the foo hook instead').
@ -24,26 +24,24 @@ function checkDeprecation(hook) {
deprecationWarned[hook.hook_fn_name] = true; deprecationWarned[hook.hook_fn_name] = true;
} }
exports.bubbleExceptions = true exports.bubbleExceptions = true;
var hookCallWrapper = function (hook, hook_name, args, cb) { const hookCallWrapper = function (hook, hook_name, args, cb) {
if (cb === undefined) cb = function (x) { return x; }; if (cb === undefined) cb = function (x) { return x; };
checkDeprecation(hook); checkDeprecation(hook);
// Normalize output to list for both sync and async cases // Normalize output to list for both sync and async cases
var normalize = function(x) { const normalize = function (x) {
if (x === undefined) return []; if (x === undefined) return [];
return x; return x;
} };
var normalizedhook = function () { const normalizedhook = function () {
return normalize(hook.hook_fn(hook_name, args, function (x) { return normalize(hook.hook_fn(hook_name, args, (x) => cb(normalize(x))));
return cb(normalize(x)); };
}));
}
if (exports.bubbleExceptions) { if (exports.bubbleExceptions) {
return normalizedhook(); return normalizedhook();
} else { } else {
try { try {
return normalizedhook(); return normalizedhook();
@ -51,32 +49,32 @@ var hookCallWrapper = function (hook, hook_name, args, cb) {
console.error([hook_name, hook.part.full_name, ex.stack || ex]); console.error([hook_name, hook.part.full_name, ex.stack || ex]);
} }
} }
} };
exports.syncMapFirst = function (lst, fn) { exports.syncMapFirst = function (lst, fn) {
var i; let i;
var result; let result;
for (i = 0; i < lst.length; i++) { for (i = 0; i < lst.length; i++) {
result = fn(lst[i]) result = fn(lst[i]);
if (result.length) return result; if (result.length) return result;
} }
return []; return [];
} };
exports.mapFirst = function (lst, fn, cb, predicate) { exports.mapFirst = function (lst, fn, cb, predicate) {
if (predicate == null) predicate = (x) => (x != null && x.length > 0); if (predicate == null) predicate = (x) => (x != null && x.length > 0);
var i = 0; let i = 0;
var next = function () { var next = function () {
if (i >= lst.length) return cb(null, []); if (i >= lst.length) return cb(null, []);
fn(lst[i++], function (err, result) { fn(lst[i++], (err, result) => {
if (err) return cb(err); if (err) return cb(err);
if (predicate(result)) return cb(null, result); if (predicate(result)) return cb(null, result);
next(); next();
}); });
} };
next(); next();
} };
// Calls the hook function synchronously and returns the value provided by the hook function (via // Calls the hook function synchronously and returns the value provided by the hook function (via
// callback or return value). // callback or return value).
@ -350,11 +348,9 @@ async function callHookFnAsync(hook, context) {
exports.aCallAll = async (hookName, context, cb) => { exports.aCallAll = async (hookName, context, cb) => {
if (context == null) context = {}; if (context == null) context = {};
const hooks = pluginDefs.hooks[hookName] || []; const hooks = pluginDefs.hooks[hookName] || [];
let resultsPromise = Promise.all(hooks.map((hook) => { let resultsPromise = Promise.all(hooks.map((hook) => callHookFnAsync(hook, context)
return callHookFnAsync(hook, context) // `undefined` (but not `null`!) is treated the same as [].
// `undefined` (but not `null`!) is treated the same as []. .then((result) => (result === undefined) ? [] : result))).then((results) => _.flatten(results, 1));
.then((result) => (result === undefined) ? [] : result);
})).then((results) => _.flatten(results, 1));
if (cb != null) resultsPromise = resultsPromise.then((val) => cb(null, val), cb); if (cb != null) resultsPromise = resultsPromise.then((val) => cb(null, val), cb);
return await resultsPromise; return await resultsPromise;
}; };
@ -362,49 +358,45 @@ exports.aCallAll = async (hookName, context, cb) => {
exports.callFirst = function (hook_name, args) { exports.callFirst = function (hook_name, args) {
if (!args) args = {}; if (!args) args = {};
if (pluginDefs.hooks[hook_name] === undefined) return []; if (pluginDefs.hooks[hook_name] === undefined) return [];
return exports.syncMapFirst(pluginDefs.hooks[hook_name], function(hook) { return exports.syncMapFirst(pluginDefs.hooks[hook_name], (hook) => hookCallWrapper(hook, hook_name, args));
return hookCallWrapper(hook, hook_name, args); };
});
}
function aCallFirst(hook_name, args, cb, predicate) { function aCallFirst(hook_name, args, cb, predicate) {
if (!args) args = {}; if (!args) args = {};
if (!cb) cb = function () {}; if (!cb) cb = function () {};
if (pluginDefs.hooks[hook_name] === undefined) return cb(null, []); if (pluginDefs.hooks[hook_name] === undefined) return cb(null, []);
exports.mapFirst( exports.mapFirst(
pluginDefs.hooks[hook_name], pluginDefs.hooks[hook_name],
function (hook, cb) { (hook, cb) => {
hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); }); hookCallWrapper(hook, hook_name, args, (res) => { cb(null, res); });
}, },
cb, cb,
predicate predicate,
); );
} }
/* return a Promise if cb is not supplied */ /* return a Promise if cb is not supplied */
exports.aCallFirst = function (hook_name, args, cb, predicate) { exports.aCallFirst = function (hook_name, args, cb, predicate) {
if (cb === undefined) { if (cb === undefined) {
return new Promise(function(resolve, reject) { return new Promise((resolve, reject) => {
aCallFirst(hook_name, args, function(err, res) { aCallFirst(hook_name, args, (err, res) => err ? reject(err) : resolve(res), predicate);
return err ? reject(err) : resolve(res);
}, predicate);
}); });
} else { } else {
return aCallFirst(hook_name, args, cb, predicate); return aCallFirst(hook_name, args, cb, predicate);
} }
} };
exports.callAllStr = function(hook_name, args, sep, pre, post) { exports.callAllStr = function (hook_name, args, sep, pre, post) {
if (sep == undefined) sep = ''; if (sep == undefined) sep = '';
if (pre == undefined) pre = ''; if (pre == undefined) pre = '';
if (post == undefined) post = ''; if (post == undefined) post = '';
var newCallhooks = []; const newCallhooks = [];
var callhooks = exports.callAll(hook_name, args); const callhooks = exports.callAll(hook_name, args);
for (var i = 0, ii = callhooks.length; i < ii; i++) { for (let i = 0, ii = callhooks.length; i < ii; i++) {
newCallhooks[i] = pre + callhooks[i] + post; newCallhooks[i] = pre + callhooks[i] + post;
} }
return newCallhooks.join(sep || ""); return newCallhooks.join(sep || '');
} };
exports.exportedForTestingOnly = { exports.exportedForTestingOnly = {
callHookFnAsync, callHookFnAsync,

View file

@ -1,8 +1,8 @@
const log4js = require('log4js'); const log4js = require('log4js');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins"); const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var npm = require("npm"); const npm = require('npm');
var request = require("request"); const request = require('request');
const util = require('util'); const util = require('util');
let npmIsLoaded = false; let npmIsLoaded = false;
@ -13,20 +13,20 @@ const loadNpm = async () => {
npm.on('log', log4js.getLogger('npm').log); npm.on('log', log4js.getLogger('npm').log);
}; };
var tasks = 0 let tasks = 0;
function wrapTaskCb(cb) { function wrapTaskCb(cb) {
tasks++; tasks++;
return function() { return function () {
cb && cb.apply(this, arguments); cb && cb.apply(this, arguments);
tasks--; tasks--;
if (tasks == 0) onAllTasksFinished(); if (tasks == 0) onAllTasksFinished();
} };
} }
function onAllTasksFinished() { function onAllTasksFinished() {
hooks.aCallAll("restartServer", {}, function() {}); hooks.aCallAll('restartServer', {}, () => {});
} }
exports.uninstall = async (pluginName, cb = null) => { exports.uninstall = async (pluginName, cb = null) => {
@ -58,18 +58,18 @@ exports.install = async (pluginName, cb = null) => {
}; };
exports.availablePlugins = null; exports.availablePlugins = null;
var cacheTimestamp = 0; let cacheTimestamp = 0;
exports.getAvailablePlugins = function(maxCacheAge) { exports.getAvailablePlugins = function (maxCacheAge) {
var nowTimestamp = Math.round(Date.now() / 1000); const nowTimestamp = Math.round(Date.now() / 1000);
return new Promise(function(resolve, reject) { return new Promise((resolve, reject) => {
// check cache age before making any request // check cache age before making any request
if (exports.availablePlugins && maxCacheAge && (nowTimestamp - cacheTimestamp) <= maxCacheAge) { if (exports.availablePlugins && maxCacheAge && (nowTimestamp - cacheTimestamp) <= maxCacheAge) {
return resolve(exports.availablePlugins); return resolve(exports.availablePlugins);
} }
request("https://static.etherpad.org/plugins.json", function(er, response, plugins) { request('https://static.etherpad.org/plugins.json', (er, response, plugins) => {
if (er) return reject(er); if (er) return reject(er);
try { try {
@ -87,26 +87,26 @@ exports.getAvailablePlugins = function(maxCacheAge) {
}; };
exports.search = function(searchTerm, maxCacheAge) { exports.search = function (searchTerm, maxCacheAge) {
return exports.getAvailablePlugins(maxCacheAge).then(function(results) { return exports.getAvailablePlugins(maxCacheAge).then((results) => {
var res = {}; const res = {};
if (searchTerm) { if (searchTerm) {
searchTerm = searchTerm.toLowerCase(); searchTerm = searchTerm.toLowerCase();
} }
for (var pluginName in results) { for (const pluginName in results) {
// for every available plugin // for every available plugin
if (pluginName.indexOf(plugins.prefix) != 0) continue; // TODO: Also search in keywords here! if (pluginName.indexOf(plugins.prefix) != 0) continue; // TODO: Also search in keywords here!
if (searchTerm && !~results[pluginName].name.toLowerCase().indexOf(searchTerm) if (searchTerm && !~results[pluginName].name.toLowerCase().indexOf(searchTerm) &&
&& (typeof results[pluginName].description != "undefined" && !~results[pluginName].description.toLowerCase().indexOf(searchTerm) ) (typeof results[pluginName].description !== 'undefined' && !~results[pluginName].description.toLowerCase().indexOf(searchTerm))
) { ) {
if (typeof results[pluginName].description === "undefined") { if (typeof results[pluginName].description === 'undefined') {
console.debug('plugin without Description: %s', results[pluginName].name); console.debug('plugin without Description: %s', results[pluginName].name);
} }
continue; continue;
} }
res[pluginName] = results[pluginName]; res[pluginName] = results[pluginName];

View file

@ -1,61 +1,61 @@
const fs = require('fs').promises; const fs = require('fs').promises;
const hooks = require('./hooks'); const hooks = require('./hooks');
var npm = require("npm/lib/npm.js"); const npm = require('npm/lib/npm.js');
var readInstalled = require("./read-installed.js"); const readInstalled = require('./read-installed.js');
var path = require("path"); const path = require('path');
var tsort = require("./tsort"); const tsort = require('./tsort');
var util = require("util"); const util = require('util');
var _ = require("underscore"); const _ = require('underscore');
var settings = require('../../../node/utils/Settings'); const settings = require('../../../node/utils/Settings');
var pluginUtils = require('./shared'); const pluginUtils = require('./shared');
var defs = require('./plugin_defs'); const defs = require('./plugin_defs');
exports.prefix = 'ep_'; exports.prefix = 'ep_';
exports.formatPlugins = function () { exports.formatPlugins = function () {
return _.keys(defs.plugins).join(", "); return _.keys(defs.plugins).join(', ');
}; };
exports.formatPluginsWithVersion = function () { exports.formatPluginsWithVersion = function () {
var plugins = []; const plugins = [];
_.forEach(defs.plugins, function(plugin) { _.forEach(defs.plugins, (plugin) => {
if(plugin.package.name !== "ep_etherpad-lite"){ if (plugin.package.name !== 'ep_etherpad-lite') {
var pluginStr = plugin.package.name + "@" + plugin.package.version; const pluginStr = `${plugin.package.name}@${plugin.package.version}`;
plugins.push(pluginStr); plugins.push(pluginStr);
} }
}); });
return plugins.join(", "); return plugins.join(', ');
}; };
exports.formatParts = function () { exports.formatParts = function () {
return _.map(defs.parts, function(part) { return part.full_name; }).join('\n'); return _.map(defs.parts, (part) => part.full_name).join('\n');
}; };
exports.formatHooks = function (hook_set_name) { exports.formatHooks = function (hook_set_name) {
var res = []; const res = [];
var hooks = pluginUtils.extractHooks(defs.parts, hook_set_name || 'hooks'); const hooks = pluginUtils.extractHooks(defs.parts, hook_set_name || 'hooks');
_.chain(hooks).keys().forEach(function (hook_name) { _.chain(hooks).keys().forEach((hook_name) => {
_.forEach(hooks[hook_name], function (hook) { _.forEach(hooks[hook_name], (hook) => {
res.push("<dt>" + hook.hook_name + "</dt><dd>" + hook.hook_fn_name + " from " + hook.part.full_name + "</dd>"); res.push(`<dt>${hook.hook_name}</dt><dd>${hook.hook_fn_name} from ${hook.part.full_name}</dd>`);
}); });
}); });
return "<dl>" + res.join("\n") + "</dl>"; return `<dl>${res.join('\n')}</dl>`;
}; };
const callInit = async () => { const callInit = async () => {
await Promise.all(Object.keys(defs.plugins).map(async (plugin_name) => { await Promise.all(Object.keys(defs.plugins).map(async (plugin_name) => {
let plugin = defs.plugins[plugin_name]; const plugin = defs.plugins[plugin_name];
let ep_init = path.normalize(path.join(plugin.package.path, ".ep_initialized")); const ep_init = path.normalize(path.join(plugin.package.path, '.ep_initialized'));
try { try {
await fs.stat(ep_init); await fs.stat(ep_init);
} catch (err) { } catch (err) {
await fs.writeFile(ep_init, 'done'); await fs.writeFile(ep_init, 'done');
await hooks.aCallAll("init_" + plugin_name, {}); await hooks.aCallAll(`init_${plugin_name}`, {});
} }
})); }));
} };
exports.pathNormalization = function (part, hook_fn_name, hook_name) { exports.pathNormalization = function (part, hook_fn_name, hook_name) {
const tmp = hook_fn_name.split(':'); // hook_fn_name might be something like 'C:\\foo.js:myFunc'. const tmp = hook_fn_name.split(':'); // hook_fn_name might be something like 'C:\\foo.js:myFunc'.
@ -65,32 +65,32 @@ exports.pathNormalization = function (part, hook_fn_name, hook_name) {
const packageDir = path.dirname(defs.plugins[part.plugin].package.path); const packageDir = path.dirname(defs.plugins[part.plugin].package.path);
const fileName = path.normalize(path.join(packageDir, moduleName)); const fileName = path.normalize(path.join(packageDir, moduleName));
return `${fileName}:${functionName}`; return `${fileName}:${functionName}`;
} };
exports.update = async function () { exports.update = async function () {
let packages = await exports.getPackages(); const packages = await exports.getPackages();
var parts = {}; // Key is full name. sortParts converts this into a topologically sorted array. const parts = {}; // Key is full name. sortParts converts this into a topologically sorted array.
var plugins = {}; const plugins = {};
// Load plugin metadata ep.json // Load plugin metadata ep.json
await Promise.all(Object.keys(packages).map( await Promise.all(Object.keys(packages).map(
async (pluginName) => await loadPlugin(packages, pluginName, plugins, parts))); async (pluginName) => await loadPlugin(packages, pluginName, plugins, parts)));
defs.plugins = plugins; defs.plugins = plugins;
defs.parts = sortParts(parts); defs.parts = sortParts(parts);
defs.hooks = pluginUtils.extractHooks(defs.parts, 'hooks', exports.pathNormalization); defs.hooks = pluginUtils.extractHooks(defs.parts, 'hooks', exports.pathNormalization);
defs.loaded = true; defs.loaded = true;
await callInit(); await callInit();
} };
exports.getPackages = async function () { exports.getPackages = async function () {
// Load list of installed NPM packages, flatten it to a list, and filter out only packages with names that // Load list of installed NPM packages, flatten it to a list, and filter out only packages with names that
var dir = settings.root; const dir = settings.root;
let data = await util.promisify(readInstalled)(dir); const data = await util.promisify(readInstalled)(dir);
var packages = {}; const packages = {};
function flatten(deps) { function flatten(deps) {
_.chain(deps).keys().each(function (name) { _.chain(deps).keys().each((name) => {
if (name.indexOf(exports.prefix) === 0) { if (name.indexOf(exports.prefix) === 0) {
packages[name] = _.clone(deps[name]); packages[name] = _.clone(deps[name]);
// Delete anything that creates loops so that the plugin // Delete anything that creates loops so that the plugin
@ -100,48 +100,48 @@ exports.getPackages = async function () {
} }
// I don't think we need recursion // I don't think we need recursion
//if (deps[name].dependencies !== undefined) flatten(deps[name].dependencies); // if (deps[name].dependencies !== undefined) flatten(deps[name].dependencies);
}); });
} }
var tmp = {}; const tmp = {};
tmp[data.name] = data; tmp[data.name] = data;
flatten(tmp[data.name].dependencies); flatten(tmp[data.name].dependencies);
return packages; return packages;
}; };
async function loadPlugin(packages, plugin_name, plugins, parts) { async function loadPlugin(packages, plugin_name, plugins, parts) {
var plugin_path = path.resolve(packages[plugin_name].path, "ep.json"); const plugin_path = path.resolve(packages[plugin_name].path, 'ep.json');
try { try {
let data = await fs.readFile(plugin_path); const data = await fs.readFile(plugin_path);
try { try {
var plugin = JSON.parse(data); const plugin = JSON.parse(data);
plugin['package'] = packages[plugin_name]; plugin.package = packages[plugin_name];
plugins[plugin_name] = plugin; plugins[plugin_name] = plugin;
_.each(plugin.parts, function (part) { _.each(plugin.parts, (part) => {
part.plugin = plugin_name; part.plugin = plugin_name;
part.full_name = plugin_name + "/" + part.name; part.full_name = `${plugin_name}/${part.name}`;
parts[part.full_name] = part; parts[part.full_name] = part;
}); });
} catch (ex) { } catch (ex) {
console.error("Unable to parse plugin definition file " + plugin_path + ": " + ex.toString()); console.error(`Unable to parse plugin definition file ${plugin_path}: ${ex.toString()}`);
} }
} catch (er) { } catch (er) {
console.error("Unable to load plugin definition file " + plugin_path); console.error(`Unable to load plugin definition file ${plugin_path}`);
} }
} }
function partsToParentChildList(parts) { function partsToParentChildList(parts) {
var res = []; const res = [];
_.chain(parts).keys().forEach(function (name) { _.chain(parts).keys().forEach((name) => {
_.each(parts[name].post || [], function (child_name) { _.each(parts[name].post || [], (child_name) => {
res.push([name, child_name]); res.push([name, child_name]);
}); });
_.each(parts[name].pre || [], function (parent_name) { _.each(parts[name].pre || [], (parent_name) => {
res.push([parent_name, name]); res.push([parent_name, name]);
}); });
if (!parts[name].pre && !parts[name].post) { if (!parts[name].pre && !parts[name].post) {
res.push([name, ":" + name]); // Include apps with no dependency info res.push([name, `:${name}`]); // Include apps with no dependency info
} }
}); });
return res; return res;
@ -150,10 +150,10 @@ function partsToParentChildList(parts) {
// Used only in Node, so no need for _ // Used only in Node, so no need for _
function sortParts(parts) { function sortParts(parts) {
return tsort( return tsort(
partsToParentChildList(parts) partsToParentChildList(parts),
).filter( ).filter(
function (name) { return parts[name] !== undefined; } (name) => parts[name] !== undefined,
).map( ).map(
function (name) { return parts[name]; } (name) => parts[name],
); );
} }

View file

@ -89,248 +89,251 @@ as far as the left-most node_modules folder.
*/ */
var npm = require("npm/lib/npm.js") const npm = require('npm/lib/npm.js');
, fs = require("graceful-fs") const fs = require('graceful-fs');
, path = require("path") const path = require('path');
, asyncMap = require("slide").asyncMap const asyncMap = require('slide').asyncMap;
, semver = require("semver") const semver = require('semver');
, log = require("log4js").getLogger('pluginfw') const log = require('log4js').getLogger('pluginfw');
function readJson(file, callback) { function readJson(file, callback) {
fs.readFile(file, function(er, buf) { fs.readFile(file, (er, buf) => {
if(er) { if (er) {
callback(er); callback(er);
return; return;
} }
try { try {
callback( null, JSON.parse(buf.toString()) ) callback(null, JSON.parse(buf.toString()));
} catch(er) { } catch (er) {
callback(er) callback(er);
} }
}) });
} }
module.exports = readInstalled module.exports = readInstalled;
function readInstalled (folder, cb) { function readInstalled(folder, cb) {
/* This is where we clear the cache, these three lines are all the /* This is where we clear the cache, these three lines are all the
* new code there is */ * new code there is */
rpSeen = {}; rpSeen = {};
riSeen = []; riSeen = [];
var fuSeen = []; const fuSeen = [];
var d = npm.config.get("depth") const d = npm.config.get('depth');
readInstalled_(folder, null, null, null, 0, d, function (er, obj) { readInstalled_(folder, null, null, null, 0, d, (er, obj) => {
if (er) return cb(er) if (er) return cb(er);
// now obj has all the installed things, where they're installed // now obj has all the installed things, where they're installed
// figure out the inheritance links, now that the object is built. // figure out the inheritance links, now that the object is built.
resolveInheritance(obj) resolveInheritance(obj);
cb(null, obj) cb(null, obj);
}) });
} }
var rpSeen = {} var rpSeen = {};
function readInstalled_ (folder, parent, name, reqver, depth, maxDepth, cb) { function readInstalled_(folder, parent, name, reqver, depth, maxDepth, cb) {
//console.error(folder, name) // console.error(folder, name)
var installed let installed,
, obj obj,
, real real,
, link link;
fs.readdir(path.resolve(folder, "node_modules"), function (er, i) { fs.readdir(path.resolve(folder, 'node_modules'), (er, i) => {
// error indicates that nothing is installed here // error indicates that nothing is installed here
if (er) i = [] if (er) i = [];
installed = i.filter(function (f) { return f.charAt(0) !== "." }) installed = i.filter((f) => f.charAt(0) !== '.');
next() next();
}) });
readJson(path.resolve(folder, "package.json"), function (er, data) { readJson(path.resolve(folder, 'package.json'), (er, data) => {
obj = copy(data) obj = copy(data);
if (!parent) { if (!parent) {
obj = obj || true obj = obj || true;
er = null er = null;
} }
return next(er) return next(er);
}) });
fs.lstat(folder, function (er, st) { fs.lstat(folder, (er, st) => {
if (er) { if (er) {
if (!parent) real = true if (!parent) real = true;
return next(er) return next(er);
} }
fs.realpath(folder, function (er, rp) { fs.realpath(folder, (er, rp) => {
//console.error("realpath(%j) = %j", folder, rp) // console.error("realpath(%j) = %j", folder, rp)
real = rp real = rp;
if (st.isSymbolicLink()) link = rp if (st.isSymbolicLink()) link = rp;
next(er) next(er);
}) });
}) });
var errState = null let errState = null;
, called = false let called = false;
function next (er) { function next(er) {
if (errState) return if (errState) return;
if (er) { if (er) {
errState = er errState = er;
return cb(null, []) return cb(null, []);
} }
//console.error('next', installed, obj && typeof obj, name, real) // console.error('next', installed, obj && typeof obj, name, real)
if (!installed || !obj || !real || called) return if (!installed || !obj || !real || called) return;
called = true called = true;
if (rpSeen[real]) return cb(null, rpSeen[real]) if (rpSeen[real]) return cb(null, rpSeen[real]);
if (obj === true) { if (obj === true) {
obj = {dependencies:{}, path:folder} obj = {dependencies: {}, path: folder};
installed.forEach(function (i) { obj.dependencies[i] = "*" }) installed.forEach((i) => { obj.dependencies[i] = '*'; });
} }
if (name && obj.name !== name) obj.invalid = true if (name && obj.name !== name) obj.invalid = true;
obj.realName = name || obj.name obj.realName = name || obj.name;
obj.dependencies = obj.dependencies || {} obj.dependencies = obj.dependencies || {};
// "foo":"http://blah" is always presumed valid // "foo":"http://blah" is always presumed valid
if (reqver if (reqver &&
&& semver.validRange(reqver) semver.validRange(reqver) &&
&& !semver.satisfies(obj.version, reqver)) { !semver.satisfies(obj.version, reqver)) {
obj.invalid = true obj.invalid = true;
} }
if (parent if (parent &&
&& !(name in parent.dependencies) !(name in parent.dependencies) &&
&& !(name in (parent.devDependencies || {}))) { !(name in (parent.devDependencies || {}))) {
obj.extraneous = true obj.extraneous = true;
} }
obj.path = obj.path || folder obj.path = obj.path || folder;
obj.realPath = real obj.realPath = real;
obj.link = link obj.link = link;
if (parent && !obj.link) obj.parent = parent if (parent && !obj.link) obj.parent = parent;
rpSeen[real] = obj rpSeen[real] = obj;
obj.depth = depth obj.depth = depth;
if (depth >= maxDepth) return cb(null, obj) if (depth >= maxDepth) return cb(null, obj);
asyncMap(installed, function (pkg, cb) { asyncMap(installed, (pkg, cb) => {
var rv = obj.dependencies[pkg] let rv = obj.dependencies[pkg];
if (!rv && obj.devDependencies) rv = obj.devDependencies[pkg] if (!rv && obj.devDependencies) rv = obj.devDependencies[pkg];
readInstalled_( path.resolve(folder, "node_modules/"+pkg) readInstalled_(path.resolve(folder, `node_modules/${pkg}`)
, obj, pkg, obj.dependencies[pkg], depth + 1, maxDepth , obj, pkg, obj.dependencies[pkg], depth + 1, maxDepth
, cb ) , cb);
}, function (er, installedData) { }, (er, installedData) => {
if (er) return cb(er) if (er) return cb(er);
installedData.forEach(function (dep) { installedData.forEach((dep) => {
obj.dependencies[dep.realName] = dep obj.dependencies[dep.realName] = dep;
}) });
// any strings here are unmet things. however, if it's // any strings here are unmet things. however, if it's
// optional, then that's fine, so just delete it. // optional, then that's fine, so just delete it.
if (obj.optionalDependencies) { if (obj.optionalDependencies) {
Object.keys(obj.optionalDependencies).forEach(function (dep) { Object.keys(obj.optionalDependencies).forEach((dep) => {
if (typeof obj.dependencies[dep] === "string") { if (typeof obj.dependencies[dep] === 'string') {
delete obj.dependencies[dep] delete obj.dependencies[dep];
} }
}) });
} }
return cb(null, obj) return cb(null, obj);
}) });
} }
} }
// starting from a root object, call findUnmet on each layer of children // starting from a root object, call findUnmet on each layer of children
var riSeen = [] var riSeen = [];
function resolveInheritance (obj) { function resolveInheritance(obj) {
if (typeof obj !== "object") return if (typeof obj !== 'object') return;
if (riSeen.indexOf(obj) !== -1) return if (riSeen.indexOf(obj) !== -1) return;
riSeen.push(obj) riSeen.push(obj);
if (typeof obj.dependencies !== "object") { if (typeof obj.dependencies !== 'object') {
obj.dependencies = {} obj.dependencies = {};
} }
Object.keys(obj.dependencies).forEach(function (dep) { Object.keys(obj.dependencies).forEach((dep) => {
findUnmet(obj.dependencies[dep]) findUnmet(obj.dependencies[dep]);
}) });
Object.keys(obj.dependencies).forEach(function (dep) { Object.keys(obj.dependencies).forEach((dep) => {
resolveInheritance(obj.dependencies[dep]) resolveInheritance(obj.dependencies[dep]);
}) });
} }
// find unmet deps by walking up the tree object. // find unmet deps by walking up the tree object.
// No I/O // No I/O
var fuSeen = [] const fuSeen = [];
function findUnmet (obj) { function findUnmet(obj) {
if (fuSeen.indexOf(obj) !== -1) return if (fuSeen.indexOf(obj) !== -1) return;
fuSeen.push(obj) fuSeen.push(obj);
//console.error("find unmet", obj.name, obj.parent && obj.parent.name) // console.error("find unmet", obj.name, obj.parent && obj.parent.name)
var deps = obj.dependencies = obj.dependencies || {} const deps = obj.dependencies = obj.dependencies || {};
//console.error(deps) // console.error(deps)
Object.keys(deps) Object.keys(deps)
.filter(function (d) { return typeof deps[d] === "string" }) .filter((d) => typeof deps[d] === 'string')
.forEach(function (d) { .forEach((d) => {
//console.error("find unmet", obj.name, d, deps[d]) // console.error("find unmet", obj.name, d, deps[d])
var r = obj.parent let r = obj.parent;
, found = null let found = null;
while (r && !found && typeof deps[d] === "string") { while (r && !found && typeof deps[d] === 'string') {
// if r is a valid choice, then use that. // if r is a valid choice, then use that.
found = r.dependencies[d] found = r.dependencies[d];
if (!found && r.realName === d) found = r if (!found && r.realName === d) found = r;
if (!found) { if (!found) {
r = r.link ? null : r.parent r = r.link ? null : r.parent;
continue continue;
} }
if ( typeof deps[d] === "string" if (typeof deps[d] === 'string' &&
&& !semver.satisfies(found.version, deps[d])) { !semver.satisfies(found.version, deps[d])) {
// the bad thing will happen // the bad thing will happen
log.warn(obj.path + " requires "+d+"@'"+deps[d] log.warn(`${obj.path} requires ${d}@'${deps[d]
+"' but will load\n" }' but will load\n${
+found.path+",\nwhich is version "+found.version found.path},\nwhich is version ${found.version}`
,"unmet dependency") , 'unmet dependency');
found.invalid = true found.invalid = true;
}
deps[d] = found;
} }
deps[d] = found });
} return obj;
})
return obj
} }
function copy (obj) { function copy(obj) {
if (!obj || typeof obj !== 'object') return obj if (!obj || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) return obj.map(copy) if (Array.isArray(obj)) return obj.map(copy);
var o = {} const o = {};
for (var i in obj) o[i] = copy(obj[i]) for (const i in obj) o[i] = copy(obj[i]);
return o return o;
} }
if (module === require.main) { if (module === require.main) {
var util = require("util") const util = require('util');
console.error("testing") console.error('testing');
var called = 0 let called = 0;
readInstalled(process.cwd(), function (er, map) { readInstalled(process.cwd(), (er, map) => {
console.error(called ++) console.error(called++);
if (er) return console.error(er.stack || er.message) if (er) return console.error(er.stack || er.message);
cleanup(map) cleanup(map);
console.error(util.inspect(map, true, 10, true)) console.error(util.inspect(map, true, 10, true));
}) });
var seen = [] const seen = [];
function cleanup (map) { function cleanup(map) {
if (seen.indexOf(map) !== -1) return if (seen.indexOf(map) !== -1) return;
seen.push(map) seen.push(map);
for (var i in map) switch (i) { for (var i in map) {
case "_id": switch (i) {
case "path": case '_id':
case "extraneous": case "invalid": case 'path':
case "dependencies": case "name": case 'extraneous': case 'invalid':
continue case 'dependencies': case 'name':
default: delete map[i] continue;
} default: delete map[i];
var dep = map.dependencies
// delete map.dependencies
if (dep) {
// map.dependencies = dep
for (var i in dep) if (typeof dep[i] === "object") {
cleanup(dep[i])
} }
} }
return map const dep = map.dependencies;
// delete map.dependencies
if (dep) {
// map.dependencies = dep
for (var i in dep) {
if (typeof dep[i] === 'object') {
cleanup(dep[i]);
}
}
}
return map;
} }
} }

View file

@ -1,5 +1,5 @@
var _ = require("underscore"); const _ = require('underscore');
var defs = require('./plugin_defs'); const defs = require('./plugin_defs');
const disabledHookReasons = { const disabledHookReasons = {
hooks: { hooks: {
@ -9,70 +9,70 @@ const disabledHookReasons = {
}; };
function loadFn(path, hookName) { function loadFn(path, hookName) {
var functionName let functionName;
, parts = path.split(":"); const parts = path.split(':');
// on windows: C:\foo\bar:xyz // on windows: C:\foo\bar:xyz
if (parts[0].length == 1) { if (parts[0].length == 1) {
if (parts.length == 3) { if (parts.length == 3) {
functionName = parts.pop(); functionName = parts.pop();
} }
path = parts.join(":"); path = parts.join(':');
} else { } else {
path = parts[0]; path = parts[0];
functionName = parts[1]; functionName = parts[1];
} }
var fn = require(path); let fn = require(path);
functionName = functionName ? functionName : hookName; functionName = functionName ? functionName : hookName;
_.each(functionName.split("."), function (name) { _.each(functionName.split('.'), (name) => {
fn = fn[name]; fn = fn[name];
}); });
return fn; return fn;
}; }
function extractHooks(parts, hook_set_name, normalizer) { function extractHooks(parts, hook_set_name, normalizer) {
var hooks = {}; const hooks = {};
_.each(parts,function (part) { _.each(parts, (part) => {
_.chain(part[hook_set_name] || {}) _.chain(part[hook_set_name] || {})
.keys() .keys()
.each(function (hook_name) { .each((hook_name) => {
var hook_fn_name = part[hook_set_name][hook_name]; let hook_fn_name = part[hook_set_name][hook_name];
/* On the server side, you can't just /* On the server side, you can't just
* require("pluginname/whatever") if the plugin is installed as * require("pluginname/whatever") if the plugin is installed as
* a dependency of another plugin! Bah, pesky little details of * a dependency of another plugin! Bah, pesky little details of
* npm... */ * npm... */
if (normalizer) { if (normalizer) {
hook_fn_name = normalizer(part, hook_fn_name, hook_name); hook_fn_name = normalizer(part, hook_fn_name, hook_name);
} }
const disabledReason = (disabledHookReasons[hook_set_name] || {})[hook_name]; const disabledReason = (disabledHookReasons[hook_set_name] || {})[hook_name];
if (disabledReason) { if (disabledReason) {
console.error(`Hook ${hook_set_name}/${hook_name} is disabled. Reason: ${disabledReason}`); console.error(`Hook ${hook_set_name}/${hook_name} is disabled. Reason: ${disabledReason}`);
console.error(`The hook function ${hook_fn_name} from plugin ${part.plugin} ` + console.error(`The hook function ${hook_fn_name} from plugin ${part.plugin} ` +
'will never be called, which may cause the plugin to fail'); 'will never be called, which may cause the plugin to fail');
console.error(`Please update the ${part.plugin} plugin to not use the ${hook_name} hook`); console.error(`Please update the ${part.plugin} plugin to not use the ${hook_name} hook`);
return; return;
} }
try { try {
var hook_fn = loadFn(hook_fn_name, hook_name); var hook_fn = loadFn(hook_fn_name, hook_name);
if (!hook_fn) { if (!hook_fn) {
throw "Not a function"; throw 'Not a function';
} }
} catch (exc) { } catch (exc) {
console.error("Failed to load '" + hook_fn_name + "' for '" + part.full_name + "/" + hook_set_name + "/" + hook_name + "': " + exc.toString()) console.error(`Failed to load '${hook_fn_name}' for '${part.full_name}/${hook_set_name}/${hook_name}': ${exc.toString()}`);
} }
if (hook_fn) { if (hook_fn) {
if (hooks[hook_name] == null) hooks[hook_name] = []; if (hooks[hook_name] == null) hooks[hook_name] = [];
hooks[hook_name].push({"hook_name": hook_name, "hook_fn": hook_fn, "hook_fn_name": hook_fn_name, "part": part}); hooks[hook_name].push({hook_name, hook_fn, hook_fn_name, part});
} }
}); });
}); });
return hooks; return hooks;
}; }
exports.extractHooks = extractHooks; exports.extractHooks = extractHooks;
@ -88,12 +88,12 @@ exports.extractHooks = extractHooks;
* No plugins: [] * No plugins: []
* Some plugins: [ 'ep_adminpads', 'ep_add_buttons', 'ep_activepads' ] * Some plugins: [ 'ep_adminpads', 'ep_add_buttons', 'ep_activepads' ]
*/ */
exports.clientPluginNames = function() { exports.clientPluginNames = function () {
var client_plugin_names = _.uniq( const client_plugin_names = _.uniq(
defs.parts defs.parts
.filter(function(part) { return part.hasOwnProperty('client_hooks'); }) .filter((part) => part.hasOwnProperty('client_hooks'))
.map(function(part) { return 'plugin-' + part['plugin']; }) .map((part) => `plugin-${part.plugin}`),
); );
return client_plugin_names; return client_plugin_names;
} };

View file

@ -8,27 +8,28 @@
**/ **/
function tsort(edges) { function tsort(edges) {
var nodes = {}, // hash: stringified id of the node => { id: id, afters: lisf of ids } const nodes = {}; // hash: stringified id of the node => { id: id, afters: lisf of ids }
sorted = [], // sorted list of IDs ( returned value ) const sorted = []; // sorted list of IDs ( returned value )
visited = {}; // hash: id of already visited node => true const visited = {}; // hash: id of already visited node => true
var Node = function(id) { const Node = function (id) {
this.id = id; this.id = id;
this.afters = []; this.afters = [];
} };
// 1. build data structures // 1. build data structures
edges.forEach(function(v) { edges.forEach((v) => {
var from = v[0], to = v[1]; const from = v[0]; const
to = v[1];
if (!nodes[from]) nodes[from] = new Node(from); if (!nodes[from]) nodes[from] = new Node(from);
if (!nodes[to]) nodes[to] = new Node(to); if (!nodes[to]) nodes[to] = new Node(to);
nodes[from].afters.push(to); nodes[from].afters.push(to);
}); });
// 2. topological sort // 2. topological sort
Object.keys(nodes).forEach(function visit(idstr, ancestors) { Object.keys(nodes).forEach(function visit(idstr, ancestors) {
var node = nodes[idstr], const node = nodes[idstr];
id = node.id; const id = node.id;
// if already exists, do nothing // if already exists, do nothing
if (visited[idstr]) return; if (visited[idstr]) return;
@ -39,11 +40,11 @@ function tsort(edges) {
visited[idstr] = true; visited[idstr] = true;
node.afters.forEach(function(afterID) { node.afters.forEach((afterID) => {
if (ancestors.indexOf(afterID) >= 0) // if already in ancestors, a closed chain exists. if (ancestors.indexOf(afterID) >= 0) // if already in ancestors, a closed chain exists.
throw new Error('closed chain : ' + afterID + ' is in ' + id); { throw new Error(`closed chain : ${afterID} is in ${id}`); }
visit(afterID.toString(), ancestors.map(function(v) { return v })); // recursive call visit(afterID.toString(), ancestors.map((v) => v)); // recursive call
}); });
sorted.unshift(id); sorted.unshift(id);
@ -56,57 +57,55 @@ function tsort(edges) {
* TEST * TEST
**/ **/
function tsortTest() { function tsortTest() {
// example 1: success // example 1: success
var edges = [ let edges = [
[1, 2], [1, 2],
[1, 3], [1, 3],
[2, 4], [2, 4],
[3, 4] [3, 4],
]; ];
var sorted = tsort(edges); let sorted = tsort(edges);
console.log(sorted); console.log(sorted);
// example 2: failure ( A > B > C > A ) // example 2: failure ( A > B > C > A )
edges = [ edges = [
['A', 'B'], ['A', 'B'],
['B', 'C'], ['B', 'C'],
['C', 'A'] ['C', 'A'],
]; ];
try { try {
sorted = tsort(edges); sorted = tsort(edges);
} } catch (e) {
catch (e) {
console.log(e.message); console.log(e.message);
} }
// example 3: generate random edges // example 3: generate random edges
var max = 100, iteration = 30; const max = 100; const
iteration = 30;
function randomInt(max) { function randomInt(max) {
return Math.floor(Math.random() * max) + 1; return Math.floor(Math.random() * max) + 1;
} }
edges = (function() { edges = (function () {
var ret = [], i = 0; const ret = []; let
while (i++ < iteration) ret.push( [randomInt(max), randomInt(max)] ); i = 0;
while (i++ < iteration) ret.push([randomInt(max), randomInt(max)]);
return ret; return ret;
})(); })();
try { try {
sorted = tsort(edges); sorted = tsort(edges);
console.log("succeeded", sorted); console.log('succeeded', sorted);
} catch (e) {
console.log('failed', e.message);
} }
catch (e) {
console.log("failed", e.message);
}
} }
// for node.js // for node.js
if (typeof exports == 'object' && exports === this) { if (typeof exports === 'object' && exports === this) {
module.exports = tsort; module.exports = tsort;
if (process.argv[1] === __filename) tsortTest(); if (process.argv[1] === __filename) tsortTest();
} }

View file

@ -1,5 +1,4 @@
// Proviedes a require'able version of jQuery without leaking $ and jQuery; // Proviedes a require'able version of jQuery without leaking $ and jQuery;
window.$ = require('./jquery'); window.$ = require('./jquery');
var jq = window.$.noConflict(true); const jq = window.$.noConflict(true);
exports.jQuery = exports.$ = jq; exports.jQuery = exports.$ = jq;

View file

@ -5,7 +5,7 @@
Browser Line = each vertical line. A <div> can be break into more than one Browser Line = each vertical line. A <div> can be break into more than one
browser line. browser line.
*/ */
var caretPosition = require('./caretPosition'); const caretPosition = require('./caretPosition');
function Scroll(outerWin) { function Scroll(outerWin) {
// scroll settings // scroll settings
@ -20,317 +20,313 @@ function Scroll(outerWin) {
Scroll.prototype.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary = function (rep, isScrollableEvent, innerHeight) { Scroll.prototype.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary = function (rep, isScrollableEvent, innerHeight) {
// are we placing the caret on the line at the bottom of viewport? // are we placing the caret on the line at the bottom of viewport?
// And if so, do we need to scroll the editor, as defined on the settings.json? // And if so, do we need to scroll the editor, as defined on the settings.json?
var shouldScrollWhenCaretIsAtBottomOfViewport = this.scrollSettings.scrollWhenCaretIsInTheLastLineOfViewport; const shouldScrollWhenCaretIsAtBottomOfViewport = this.scrollSettings.scrollWhenCaretIsInTheLastLineOfViewport;
if (shouldScrollWhenCaretIsAtBottomOfViewport) { if (shouldScrollWhenCaretIsAtBottomOfViewport) {
// avoid scrolling when selection includes multiple lines -- user can potentially be selecting more lines // avoid scrolling when selection includes multiple lines -- user can potentially be selecting more lines
// than it fits on viewport // than it fits on viewport
var multipleLinesSelected = rep.selStart[0] !== rep.selEnd[0]; const multipleLinesSelected = rep.selStart[0] !== rep.selEnd[0];
// avoid scrolling when pad loads // avoid scrolling when pad loads
if (isScrollableEvent && !multipleLinesSelected && this._isCaretAtTheBottomOfViewport(rep)) { if (isScrollableEvent && !multipleLinesSelected && this._isCaretAtTheBottomOfViewport(rep)) {
// when scrollWhenFocusLineIsOutOfViewport.percentage is 0, pixelsToScroll is 0 // when scrollWhenFocusLineIsOutOfViewport.percentage is 0, pixelsToScroll is 0
var pixelsToScroll = this._getPixelsRelativeToPercentageOfViewport(innerHeight); const pixelsToScroll = this._getPixelsRelativeToPercentageOfViewport(innerHeight);
this._scrollYPage(pixelsToScroll); this._scrollYPage(pixelsToScroll);
} }
} }
} };
Scroll.prototype.scrollWhenPressArrowKeys = function(arrowUp, rep, innerHeight) { Scroll.prototype.scrollWhenPressArrowKeys = function (arrowUp, rep, innerHeight) {
// if percentageScrollArrowUp is 0, let the scroll to be handled as default, put the previous // if percentageScrollArrowUp is 0, let the scroll to be handled as default, put the previous
// rep line on the top of the viewport // rep line on the top of the viewport
if(this._arrowUpWasPressedInTheFirstLineOfTheViewport(arrowUp, rep)){ if (this._arrowUpWasPressedInTheFirstLineOfTheViewport(arrowUp, rep)) {
var pixelsToScroll = this._getPixelsToScrollWhenUserPressesArrowUp(innerHeight); const pixelsToScroll = this._getPixelsToScrollWhenUserPressesArrowUp(innerHeight);
// by default, the browser scrolls to the middle of the viewport. To avoid the twist made // by default, the browser scrolls to the middle of the viewport. To avoid the twist made
// when we apply a second scroll, we made it immediately (without animation) // when we apply a second scroll, we made it immediately (without animation)
this._scrollYPageWithoutAnimation(-pixelsToScroll); this._scrollYPageWithoutAnimation(-pixelsToScroll);
}else{ } else {
this.scrollNodeVerticallyIntoView(rep, innerHeight); this.scrollNodeVerticallyIntoView(rep, innerHeight);
} }
} };
// Some plugins might set a minimum height to the editor (ex: ep_page_view), so checking // Some plugins might set a minimum height to the editor (ex: ep_page_view), so checking
// if (caretLine() === rep.lines.length() - 1) is not enough. We need to check if there are // if (caretLine() === rep.lines.length() - 1) is not enough. We need to check if there are
// other lines after caretLine(), and all of them are out of viewport. // other lines after caretLine(), and all of them are out of viewport.
Scroll.prototype._isCaretAtTheBottomOfViewport = function(rep) { Scroll.prototype._isCaretAtTheBottomOfViewport = function (rep) {
// computing a line position using getBoundingClientRect() is expensive. // computing a line position using getBoundingClientRect() is expensive.
// (obs: getBoundingClientRect() is called on caretPosition.getPosition()) // (obs: getBoundingClientRect() is called on caretPosition.getPosition())
// To avoid that, we only call this function when it is possible that the // To avoid that, we only call this function when it is possible that the
// caret is in the bottom of viewport // caret is in the bottom of viewport
var caretLine = rep.selStart[0]; const caretLine = rep.selStart[0];
var lineAfterCaretLine = caretLine + 1; const lineAfterCaretLine = caretLine + 1;
var firstLineVisibleAfterCaretLine = caretPosition.getNextVisibleLine(lineAfterCaretLine, rep); const firstLineVisibleAfterCaretLine = caretPosition.getNextVisibleLine(lineAfterCaretLine, rep);
var caretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(caretLine, rep); const caretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(caretLine, rep);
var lineAfterCaretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(firstLineVisibleAfterCaretLine, rep); const lineAfterCaretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(firstLineVisibleAfterCaretLine, rep);
if (caretLineIsPartiallyVisibleOnViewport || lineAfterCaretLineIsPartiallyVisibleOnViewport) { if (caretLineIsPartiallyVisibleOnViewport || lineAfterCaretLineIsPartiallyVisibleOnViewport) {
// check if the caret is in the bottom of the viewport // check if the caret is in the bottom of the viewport
var caretLinePosition = caretPosition.getPosition(); const caretLinePosition = caretPosition.getPosition();
var viewportBottom = this._getViewPortTopBottom().bottom; const viewportBottom = this._getViewPortTopBottom().bottom;
var nextLineBottom = caretPosition.getBottomOfNextBrowserLine(caretLinePosition, rep); const nextLineBottom = caretPosition.getBottomOfNextBrowserLine(caretLinePosition, rep);
var nextLineIsBelowViewportBottom = nextLineBottom > viewportBottom; const nextLineIsBelowViewportBottom = nextLineBottom > viewportBottom;
return nextLineIsBelowViewportBottom; return nextLineIsBelowViewportBottom;
} }
return false; return false;
} };
Scroll.prototype._isLinePartiallyVisibleOnViewport = function(lineNumber, rep) { Scroll.prototype._isLinePartiallyVisibleOnViewport = function (lineNumber, rep) {
var lineNode = rep.lines.atIndex(lineNumber); const lineNode = rep.lines.atIndex(lineNumber);
var linePosition = this._getLineEntryTopBottom(lineNode); const linePosition = this._getLineEntryTopBottom(lineNode);
var lineTop = linePosition.top; const lineTop = linePosition.top;
var lineBottom = linePosition.bottom; const lineBottom = linePosition.bottom;
var viewport = this._getViewPortTopBottom(); const viewport = this._getViewPortTopBottom();
var viewportBottom = viewport.bottom; const viewportBottom = viewport.bottom;
var viewportTop = viewport.top; const viewportTop = viewport.top;
var topOfLineIsAboveOfViewportBottom = lineTop < viewportBottom; const topOfLineIsAboveOfViewportBottom = lineTop < viewportBottom;
var bottomOfLineIsOnOrBelowOfViewportBottom = lineBottom >= viewportBottom; const bottomOfLineIsOnOrBelowOfViewportBottom = lineBottom >= viewportBottom;
var topOfLineIsBelowViewportTop = lineTop >= viewportTop; const topOfLineIsBelowViewportTop = lineTop >= viewportTop;
var topOfLineIsAboveViewportBottom = lineTop <= viewportBottom; const topOfLineIsAboveViewportBottom = lineTop <= viewportBottom;
var bottomOfLineIsAboveViewportBottom = lineBottom <= viewportBottom; const bottomOfLineIsAboveViewportBottom = lineBottom <= viewportBottom;
var bottomOfLineIsBelowViewportTop = lineBottom >= viewportTop; const bottomOfLineIsBelowViewportTop = lineBottom >= viewportTop;
return (topOfLineIsAboveOfViewportBottom && bottomOfLineIsOnOrBelowOfViewportBottom) || return (topOfLineIsAboveOfViewportBottom && bottomOfLineIsOnOrBelowOfViewportBottom) ||
(topOfLineIsBelowViewportTop && topOfLineIsAboveViewportBottom) || (topOfLineIsBelowViewportTop && topOfLineIsAboveViewportBottom) ||
(bottomOfLineIsAboveViewportBottom && bottomOfLineIsBelowViewportTop); (bottomOfLineIsAboveViewportBottom && bottomOfLineIsBelowViewportTop);
} };
Scroll.prototype._getViewPortTopBottom = function() { Scroll.prototype._getViewPortTopBottom = function () {
var theTop = this.getScrollY(); const theTop = this.getScrollY();
var doc = this.doc; const doc = this.doc;
var height = doc.documentElement.clientHeight; // includes padding const height = doc.documentElement.clientHeight; // includes padding
// we have to get the exactly height of the viewport. So it has to subtract all the values which changes // we have to get the exactly height of the viewport. So it has to subtract all the values which changes
// the viewport height (E.g. padding, position top) // the viewport height (E.g. padding, position top)
var viewportExtraSpacesAndPosition = this._getEditorPositionTop() + this._getPaddingTopAddedWhenPageViewIsEnable(); const viewportExtraSpacesAndPosition = this._getEditorPositionTop() + this._getPaddingTopAddedWhenPageViewIsEnable();
return { return {
top: theTop, top: theTop,
bottom: (theTop + height - viewportExtraSpacesAndPosition) bottom: (theTop + height - viewportExtraSpacesAndPosition),
}; };
} };
Scroll.prototype._getEditorPositionTop = function() { Scroll.prototype._getEditorPositionTop = function () {
var editor = parent.document.getElementsByTagName('iframe'); const editor = parent.document.getElementsByTagName('iframe');
var editorPositionTop = editor[0].offsetTop; const editorPositionTop = editor[0].offsetTop;
return editorPositionTop; return editorPositionTop;
} };
// ep_page_view adds padding-top, which makes the viewport smaller // ep_page_view adds padding-top, which makes the viewport smaller
Scroll.prototype._getPaddingTopAddedWhenPageViewIsEnable = function() { Scroll.prototype._getPaddingTopAddedWhenPageViewIsEnable = function () {
var aceOuter = this.rootDocument.getElementsByName("ace_outer"); const aceOuter = this.rootDocument.getElementsByName('ace_outer');
var aceOuterPaddingTop = parseInt($(aceOuter).css("padding-top")); const aceOuterPaddingTop = parseInt($(aceOuter).css('padding-top'));
return aceOuterPaddingTop; return aceOuterPaddingTop;
} };
Scroll.prototype._getScrollXY = function() { Scroll.prototype._getScrollXY = function () {
var win = this.outerWin; const win = this.outerWin;
var odoc = this.doc; const odoc = this.doc;
if (typeof(win.pageYOffset) == "number") if (typeof (win.pageYOffset) === 'number') {
{
return { return {
x: win.pageXOffset, x: win.pageXOffset,
y: win.pageYOffset y: win.pageYOffset,
}; };
} }
var docel = odoc.documentElement; const docel = odoc.documentElement;
if (docel && typeof(docel.scrollTop) == "number") if (docel && typeof (docel.scrollTop) === 'number') {
{
return { return {
x: docel.scrollLeft, x: docel.scrollLeft,
y: docel.scrollTop y: docel.scrollTop,
}; };
} }
} };
Scroll.prototype.getScrollX = function() { Scroll.prototype.getScrollX = function () {
return this._getScrollXY().x; return this._getScrollXY().x;
} };
Scroll.prototype.getScrollY = function() { Scroll.prototype.getScrollY = function () {
return this._getScrollXY().y; return this._getScrollXY().y;
} };
Scroll.prototype.setScrollX = function(x) { Scroll.prototype.setScrollX = function (x) {
this.outerWin.scrollTo(x, this.getScrollY()); this.outerWin.scrollTo(x, this.getScrollY());
} };
Scroll.prototype.setScrollY = function(y) { Scroll.prototype.setScrollY = function (y) {
this.outerWin.scrollTo(this.getScrollX(), y); this.outerWin.scrollTo(this.getScrollX(), y);
} };
Scroll.prototype.setScrollXY = function(x, y) { Scroll.prototype.setScrollXY = function (x, y) {
this.outerWin.scrollTo(x, y); this.outerWin.scrollTo(x, y);
} };
Scroll.prototype._isCaretAtTheTopOfViewport = function(rep) { Scroll.prototype._isCaretAtTheTopOfViewport = function (rep) {
var caretLine = rep.selStart[0]; const caretLine = rep.selStart[0];
var linePrevCaretLine = caretLine - 1; const linePrevCaretLine = caretLine - 1;
var firstLineVisibleBeforeCaretLine = caretPosition.getPreviousVisibleLine(linePrevCaretLine, rep); const firstLineVisibleBeforeCaretLine = caretPosition.getPreviousVisibleLine(linePrevCaretLine, rep);
var caretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(caretLine, rep); const caretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(caretLine, rep);
var lineBeforeCaretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(firstLineVisibleBeforeCaretLine, rep); const lineBeforeCaretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(firstLineVisibleBeforeCaretLine, rep);
if (caretLineIsPartiallyVisibleOnViewport || lineBeforeCaretLineIsPartiallyVisibleOnViewport) { if (caretLineIsPartiallyVisibleOnViewport || lineBeforeCaretLineIsPartiallyVisibleOnViewport) {
var caretLinePosition = caretPosition.getPosition(); // get the position of the browser line const caretLinePosition = caretPosition.getPosition(); // get the position of the browser line
var viewportPosition = this._getViewPortTopBottom(); const viewportPosition = this._getViewPortTopBottom();
var viewportTop = viewportPosition.top; const viewportTop = viewportPosition.top;
var viewportBottom = viewportPosition.bottom; const viewportBottom = viewportPosition.bottom;
var caretLineIsBelowViewportTop = caretLinePosition.bottom >= viewportTop; const caretLineIsBelowViewportTop = caretLinePosition.bottom >= viewportTop;
var caretLineIsAboveViewportBottom = caretLinePosition.top < viewportBottom; const caretLineIsAboveViewportBottom = caretLinePosition.top < viewportBottom;
var caretLineIsInsideOfViewport = caretLineIsBelowViewportTop && caretLineIsAboveViewportBottom; const caretLineIsInsideOfViewport = caretLineIsBelowViewportTop && caretLineIsAboveViewportBottom;
if (caretLineIsInsideOfViewport) { if (caretLineIsInsideOfViewport) {
var prevLineTop = caretPosition.getPositionTopOfPreviousBrowserLine(caretLinePosition, rep); const prevLineTop = caretPosition.getPositionTopOfPreviousBrowserLine(caretLinePosition, rep);
var previousLineIsAboveViewportTop = prevLineTop < viewportTop; const previousLineIsAboveViewportTop = prevLineTop < viewportTop;
return previousLineIsAboveViewportTop; return previousLineIsAboveViewportTop;
} }
} }
return false; return false;
} };
// By default, when user makes an edition in a line out of viewport, this line goes // By default, when user makes an edition in a line out of viewport, this line goes
// to the edge of viewport. This function gets the extra pixels necessary to get the // to the edge of viewport. This function gets the extra pixels necessary to get the
// caret line in a position X relative to Y% viewport. // caret line in a position X relative to Y% viewport.
Scroll.prototype._getPixelsRelativeToPercentageOfViewport = function(innerHeight, aboveOfViewport) { Scroll.prototype._getPixelsRelativeToPercentageOfViewport = function (innerHeight, aboveOfViewport) {
var pixels = 0; let pixels = 0;
var scrollPercentageRelativeToViewport = this._getPercentageToScroll(aboveOfViewport); const scrollPercentageRelativeToViewport = this._getPercentageToScroll(aboveOfViewport);
if(scrollPercentageRelativeToViewport > 0 && scrollPercentageRelativeToViewport <= 1){ if (scrollPercentageRelativeToViewport > 0 && scrollPercentageRelativeToViewport <= 1) {
pixels = parseInt(innerHeight * scrollPercentageRelativeToViewport); pixels = parseInt(innerHeight * scrollPercentageRelativeToViewport);
} }
return pixels; return pixels;
} };
// we use different percentages when change selection. It depends on if it is // we use different percentages when change selection. It depends on if it is
// either above the top or below the bottom of the page // either above the top or below the bottom of the page
Scroll.prototype._getPercentageToScroll = function(aboveOfViewport) { Scroll.prototype._getPercentageToScroll = function (aboveOfViewport) {
var percentageToScroll = this.scrollSettings.percentage.editionBelowViewport; let percentageToScroll = this.scrollSettings.percentage.editionBelowViewport;
if(aboveOfViewport){ if (aboveOfViewport) {
percentageToScroll = this.scrollSettings.percentage.editionAboveViewport; percentageToScroll = this.scrollSettings.percentage.editionAboveViewport;
} }
return percentageToScroll; return percentageToScroll;
} };
Scroll.prototype._getPixelsToScrollWhenUserPressesArrowUp = function(innerHeight) { Scroll.prototype._getPixelsToScrollWhenUserPressesArrowUp = function (innerHeight) {
var pixels = 0; let pixels = 0;
var percentageToScrollUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp; const percentageToScrollUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp;
if(percentageToScrollUp > 0 && percentageToScrollUp <= 1){ if (percentageToScrollUp > 0 && percentageToScrollUp <= 1) {
pixels = parseInt(innerHeight * percentageToScrollUp); pixels = parseInt(innerHeight * percentageToScrollUp);
} }
return pixels; return pixels;
} };
Scroll.prototype._scrollYPage = function(pixelsToScroll) { Scroll.prototype._scrollYPage = function (pixelsToScroll) {
var durationOfAnimationToShowFocusline = this.scrollSettings.duration; const durationOfAnimationToShowFocusline = this.scrollSettings.duration;
if(durationOfAnimationToShowFocusline){ if (durationOfAnimationToShowFocusline) {
this._scrollYPageWithAnimation(pixelsToScroll, durationOfAnimationToShowFocusline); this._scrollYPageWithAnimation(pixelsToScroll, durationOfAnimationToShowFocusline);
}else{ } else {
this._scrollYPageWithoutAnimation(pixelsToScroll); this._scrollYPageWithoutAnimation(pixelsToScroll);
} }
} };
Scroll.prototype._scrollYPageWithoutAnimation = function(pixelsToScroll) { Scroll.prototype._scrollYPageWithoutAnimation = function (pixelsToScroll) {
this.outerWin.scrollBy(0, pixelsToScroll); this.outerWin.scrollBy(0, pixelsToScroll);
} };
Scroll.prototype._scrollYPageWithAnimation = function(pixelsToScroll, durationOfAnimationToShowFocusline) { Scroll.prototype._scrollYPageWithAnimation = function (pixelsToScroll, durationOfAnimationToShowFocusline) {
var outerDocBody = this.doc.getElementById("outerdocbody"); const outerDocBody = this.doc.getElementById('outerdocbody');
// it works on later versions of Chrome // it works on later versions of Chrome
var $outerDocBody = $(outerDocBody); const $outerDocBody = $(outerDocBody);
this._triggerScrollWithAnimation($outerDocBody, pixelsToScroll, durationOfAnimationToShowFocusline); this._triggerScrollWithAnimation($outerDocBody, pixelsToScroll, durationOfAnimationToShowFocusline);
// it works on Firefox and earlier versions of Chrome // it works on Firefox and earlier versions of Chrome
var $outerDocBodyParent = $outerDocBody.parent(); const $outerDocBodyParent = $outerDocBody.parent();
this._triggerScrollWithAnimation($outerDocBodyParent, pixelsToScroll, durationOfAnimationToShowFocusline); this._triggerScrollWithAnimation($outerDocBodyParent, pixelsToScroll, durationOfAnimationToShowFocusline);
} };
// using a custom queue and clearing it, we avoid creating a queue of scroll animations. So if this function // using a custom queue and clearing it, we avoid creating a queue of scroll animations. So if this function
// is called twice quickly, only the last one runs. // is called twice quickly, only the last one runs.
Scroll.prototype._triggerScrollWithAnimation = function($elem, pixelsToScroll, durationOfAnimationToShowFocusline) { Scroll.prototype._triggerScrollWithAnimation = function ($elem, pixelsToScroll, durationOfAnimationToShowFocusline) {
// clear the queue of animation // clear the queue of animation
$elem.stop("scrollanimation"); $elem.stop('scrollanimation');
$elem.animate({ $elem.animate({
scrollTop: '+=' + pixelsToScroll scrollTop: `+=${pixelsToScroll}`,
}, { }, {
duration: durationOfAnimationToShowFocusline, duration: durationOfAnimationToShowFocusline,
queue: "scrollanimation" queue: 'scrollanimation',
}).dequeue("scrollanimation"); }).dequeue('scrollanimation');
} };
// scrollAmountWhenFocusLineIsOutOfViewport is set to 0 (default), scroll it the minimum distance // scrollAmountWhenFocusLineIsOutOfViewport is set to 0 (default), scroll it the minimum distance
// needed to be completely in view. If the value is greater than 0 and less than or equal to 1, // needed to be completely in view. If the value is greater than 0 and less than or equal to 1,
// besides of scrolling the minimum needed to be visible, it scrolls additionally // besides of scrolling the minimum needed to be visible, it scrolls additionally
// (viewport height * scrollAmountWhenFocusLineIsOutOfViewport) pixels // (viewport height * scrollAmountWhenFocusLineIsOutOfViewport) pixels
Scroll.prototype.scrollNodeVerticallyIntoView = function(rep, innerHeight) { Scroll.prototype.scrollNodeVerticallyIntoView = function (rep, innerHeight) {
var viewport = this._getViewPortTopBottom(); const viewport = this._getViewPortTopBottom();
var isPartOfRepLineOutOfViewport = this._partOfRepLineIsOutOfViewport(viewport, rep); const isPartOfRepLineOutOfViewport = this._partOfRepLineIsOutOfViewport(viewport, rep);
// when the selection changes outside of the viewport the browser automatically scrolls the line // when the selection changes outside of the viewport the browser automatically scrolls the line
// to inside of the viewport. Tested on IE, Firefox, Chrome in releases from 2015 until now // to inside of the viewport. Tested on IE, Firefox, Chrome in releases from 2015 until now
// So, when the line scrolled gets outside of the viewport we let the browser handle it. // So, when the line scrolled gets outside of the viewport we let the browser handle it.
var linePosition = caretPosition.getPosition(); const linePosition = caretPosition.getPosition();
if(linePosition){ if (linePosition) {
var distanceOfTopOfViewport = linePosition.top - viewport.top; const distanceOfTopOfViewport = linePosition.top - viewport.top;
var distanceOfBottomOfViewport = viewport.bottom - linePosition.bottom; const distanceOfBottomOfViewport = viewport.bottom - linePosition.bottom;
var caretIsAboveOfViewport = distanceOfTopOfViewport < 0; const caretIsAboveOfViewport = distanceOfTopOfViewport < 0;
var caretIsBelowOfViewport = distanceOfBottomOfViewport < 0; const caretIsBelowOfViewport = distanceOfBottomOfViewport < 0;
if(caretIsAboveOfViewport){ if (caretIsAboveOfViewport) {
var pixelsToScroll = distanceOfTopOfViewport - this._getPixelsRelativeToPercentageOfViewport(innerHeight, true); var pixelsToScroll = distanceOfTopOfViewport - this._getPixelsRelativeToPercentageOfViewport(innerHeight, true);
this._scrollYPage(pixelsToScroll); this._scrollYPage(pixelsToScroll);
}else if(caretIsBelowOfViewport){ } else if (caretIsBelowOfViewport) {
var pixelsToScroll = -distanceOfBottomOfViewport + this._getPixelsRelativeToPercentageOfViewport(innerHeight); var pixelsToScroll = -distanceOfBottomOfViewport + this._getPixelsRelativeToPercentageOfViewport(innerHeight);
this._scrollYPage(pixelsToScroll); this._scrollYPage(pixelsToScroll);
}else{ } else {
this.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary(rep, true, innerHeight); this.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary(rep, true, innerHeight);
} }
} }
} };
Scroll.prototype._partOfRepLineIsOutOfViewport = function(viewportPosition, rep) { Scroll.prototype._partOfRepLineIsOutOfViewport = function (viewportPosition, rep) {
var focusLine = (rep.selFocusAtStart ? rep.selStart[0] : rep.selEnd[0]); const focusLine = (rep.selFocusAtStart ? rep.selStart[0] : rep.selEnd[0]);
var line = rep.lines.atIndex(focusLine); const line = rep.lines.atIndex(focusLine);
var linePosition = this._getLineEntryTopBottom(line); const linePosition = this._getLineEntryTopBottom(line);
var lineIsAboveOfViewport = linePosition.top < viewportPosition.top; const lineIsAboveOfViewport = linePosition.top < viewportPosition.top;
var lineIsBelowOfViewport = linePosition.bottom > viewportPosition.bottom; const lineIsBelowOfViewport = linePosition.bottom > viewportPosition.bottom;
return lineIsBelowOfViewport || lineIsAboveOfViewport; return lineIsBelowOfViewport || lineIsAboveOfViewport;
} };
Scroll.prototype._getLineEntryTopBottom = function(entry, destObj) { Scroll.prototype._getLineEntryTopBottom = function (entry, destObj) {
var dom = entry.lineNode; const dom = entry.lineNode;
var top = dom.offsetTop; const top = dom.offsetTop;
var height = dom.offsetHeight; const height = dom.offsetHeight;
var obj = (destObj || {}); const obj = (destObj || {});
obj.top = top; obj.top = top;
obj.bottom = (top + height); obj.bottom = (top + height);
return obj; return obj;
} };
Scroll.prototype._arrowUpWasPressedInTheFirstLineOfTheViewport = function(arrowUp, rep) { Scroll.prototype._arrowUpWasPressedInTheFirstLineOfTheViewport = function (arrowUp, rep) {
var percentageScrollArrowUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp; const percentageScrollArrowUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp;
return percentageScrollArrowUp && arrowUp && this._isCaretAtTheTopOfViewport(rep); return percentageScrollArrowUp && arrowUp && this._isCaretAtTheTopOfViewport(rep);
} };
Scroll.prototype.getVisibleLineRange = function(rep) { Scroll.prototype.getVisibleLineRange = function (rep) {
var viewport = this._getViewPortTopBottom(); const viewport = this._getViewPortTopBottom();
//console.log("viewport top/bottom: %o", viewport); // console.log("viewport top/bottom: %o", viewport);
var obj = {}; const obj = {};
var self = this; const self = this;
var start = rep.lines.search(function(e) { const start = rep.lines.search((e) => self._getLineEntryTopBottom(e, obj).bottom > viewport.top);
return self._getLineEntryTopBottom(e, obj).bottom > viewport.top; let end = rep.lines.search((e) =>
});
var end = rep.lines.search(function(e) {
// return the first line that the top position is greater or equal than // return the first line that the top position is greater or equal than
// the viewport. That is the first line that is below the viewport bottom. // the viewport. That is the first line that is below the viewport bottom.
// So the line that is in the bottom of the viewport is the very previous one. // So the line that is in the bottom of the viewport is the very previous one.
return self._getLineEntryTopBottom(e, obj).top >= viewport.bottom; self._getLineEntryTopBottom(e, obj).top >= viewport.bottom,
}); );
if (end < start) end = start; // unlikely if (end < start) end = start; // unlikely
// top.console.log(start+","+(end -1)); // top.console.log(start+","+(end -1));
return [start, end - 1]; return [start, end - 1];
} };
Scroll.prototype.getVisibleCharRange = function(rep) { Scroll.prototype.getVisibleCharRange = function (rep) {
var lineRange = this.getVisibleLineRange(rep); const lineRange = this.getVisibleLineRange(rep);
return [rep.lines.offsetOfIndex(lineRange[0]), rep.lines.offsetOfIndex(lineRange[1])]; return [rep.lines.offsetOfIndex(lineRange[0]), rep.lines.offsetOfIndex(lineRange[1])];
} };
exports.init = function(outerWin) { exports.init = function (outerWin) {
return new Scroll(outerWin); return new Scroll(outerWin);
} };

Some files were not shown because too many files have changed in this diff Show more