diff --git a/src/node/db/API.js b/src/node/db/API.js index 724c545aa..2c58a69a3 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -18,19 +18,19 @@ * limitations under the License. */ -var Changeset = require("ep_etherpad-lite/static/js/Changeset"); -var customError = require("../utils/customError"); -var padManager = require("./PadManager"); -var padMessageHandler = require("../handler/PadMessageHandler"); -var readOnlyManager = require("./ReadOnlyManager"); -var groupManager = require("./GroupManager"); -var authorManager = require("./AuthorManager"); -var sessionManager = require("./SessionManager"); -var exportHtml = require("../utils/ExportHtml"); -var exportTxt = require("../utils/ExportTxt"); -var importHtml = require("../utils/ImportHtml"); -var cleanText = require("./Pad").cleanText; -var PadDiff = require("../utils/padDiff"); +const Changeset = require('ep_etherpad-lite/static/js/Changeset'); +const customError = require('../utils/customError'); +const padManager = require('./PadManager'); +const padMessageHandler = require('../handler/PadMessageHandler'); +const readOnlyManager = require('./ReadOnlyManager'); +const groupManager = require('./GroupManager'); +const authorManager = require('./AuthorManager'); +const sessionManager = require('./SessionManager'); +const exportHtml = require('../utils/ExportHtml'); +const exportTxt = require('../utils/ExportTxt'); +const importHtml = require('../utils/ImportHtml'); +const cleanText = require('./Pad').cleanText; +const PadDiff = require('../utils/padDiff'); /* ******************** * GROUP FUNCTIONS **** @@ -101,10 +101,10 @@ Example returns: } */ -exports.getAttributePool = async function(padID) { - let pad = await getPadSafe(padID, true); - return { pool: pad.pool }; -} +exports.getAttributePool = async function (padID) { + const pad = await getPadSafe(padID, true); + return {pool: pad.pool}; +}; /** 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 if (rev !== undefined) { rev = checkValidRev(rev); } // get the pad - let pad = await getPadSafe(padID, true); - let head = pad.getHeadRevisionNumber(); + const pad = await getPadSafe(padID, true); + const head = pad.getHeadRevisionNumber(); // the client asked for a special revision if (rev !== undefined) { - // check if this is a valid revision 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 @@ -143,7 +142,7 @@ exports.getRevisionChangeset = async function(padID, rev) { // the client wants the latest changeset, lets return it to him return pad.getRevisionChangeset(head); -} +}; /** getText(padID, [rev]) returns the text of a pad @@ -153,33 +152,32 @@ Example returns: {code: 0, message:"ok", data: {text:"Welcome Text"}} {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 if (rev !== undefined) { rev = checkValidRev(rev); } // get the pad - let pad = await getPadSafe(padID, true); - let head = pad.getHeadRevisionNumber(); + const pad = await getPadSafe(padID, true); + const head = pad.getHeadRevisionNumber(); // the client asked for a special revision if (rev !== undefined) { - // check if this is a valid revision 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 - let text = await pad.getInternalRevisionAText(rev); - return { text }; + const text = await pad.getInternalRevisionAText(rev); + return {text}; } // the client wants the latest text, lets return it to him - let text = exportTxt.getTXTFromAtext(pad, pad.atext); - return { text }; -} + const text = exportTxt.getTXTFromAtext(pad, pad.atext); + return {text}; +}; /** 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:"text too long", data: null} */ -exports.setText = async function(padID, text) { +exports.setText = async function (padID, text) { // text is required - if (typeof text !== "string") { - throw new customError("text is not a string", "apierror"); + if (typeof text !== 'string') { + throw new customError('text is not a string', 'apierror'); } // get the pad - let pad = await getPadSafe(padID, true); + const pad = await getPadSafe(padID, true); await Promise.all([ pad.setText(text), padMessageHandler.updatePadClients(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:"text too long", data: null} */ -exports.appendText = async function(padID, text) { +exports.appendText = async function (padID, text) { // text is required - if (typeof text !== "string") { - throw new customError("text is not a string", "apierror"); + if (typeof text !== 'string') { + throw new customError('text is not a string', 'apierror'); } - let pad = await getPadSafe(padID, true); + const pad = await getPadSafe(padID, true); await Promise.all([ pad.appendText(text), padMessageHandler.updatePadClients(pad), ]); -} +}; /** getHTML(padID, [rev]) returns the html of a pad @@ -235,19 +233,19 @@ Example returns: {code: 0, message:"ok", data: {text:"Welcome Text"}} {code: 1, message:"padID does not exist", data: null} */ -exports.getHTML = async function(padID, rev) { +exports.getHTML = async function (padID, rev) { if (rev !== undefined) { rev = checkValidRev(rev); } - let pad = await getPadSafe(padID, true); + const pad = await getPadSafe(padID, true); // the client asked for a special revision if (rev !== undefined) { // check if this is a valid revision - let head = pad.getHeadRevisionNumber(); + const head = pad.getHeadRevisionNumber(); 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); // wrap the HTML - html = "" + html + ""; - return { html }; -} + html = `${html}`; + return {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: 1, message:"padID does not exist", data: null} */ -exports.setHTML = async function(padID, html) { +exports.setHTML = async function (padID, html) { // html string is required - if (typeof html !== "string") { - throw new customError("html is not a string", "apierror"); + if (typeof html !== 'string') { + throw new customError('html is not a string', 'apierror'); } // 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 try { await importHtml.setPadHTML(pad, cleanText(html)); } catch (e) { - throw new customError("HTML is malformed", "apierror"); + throw new customError('HTML is malformed', 'apierror'); } // update the clients on the pad @@ -303,23 +301,23 @@ Example returns: {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 < 0) { - throw new customError("start is below zero", "apierror"); + throw new customError('start is below zero', 'apierror'); } if (end < 0) { - throw new customError("end is below zero", "apierror"); + throw new customError('end is below zero', 'apierror'); } if (start > end) { - throw new customError("start is higher than end", "apierror"); + throw new customError('start is higher than end', 'apierror'); } } // 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 if (!start || !end) { @@ -328,17 +326,17 @@ exports.getChatHistory = async function(padID, start, end) { } 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) { - 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 - 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 @@ -348,10 +346,10 @@ Example returns: {code: 0, message:"ok", 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 - if (typeof text !== "string") { - throw new customError("text is not a string", "apierror"); + if (typeof text !== 'string') { + throw new customError('text is not a string', 'apierror'); } // 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 await padMessageHandler.sendChatMessageToPadClients(time, authorID, text, padID); -} +}; /* *************** * PAD FUNCTIONS * @@ -377,11 +375,11 @@ Example returns: {code: 0, message:"ok", data: {revisions: 56}} {code: 1, message:"padID does not exist", data: null} */ -exports.getRevisionsCount = async function(padID) { +exports.getRevisionsCount = async function (padID) { // get the pad - let pad = await getPadSafe(padID, true); - return { revisions: pad.getHeadRevisionNumber() }; -} + const pad = await getPadSafe(padID, true); + return {revisions: pad.getHeadRevisionNumber()}; +}; /** 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: 1, message:"padID does not exist", data: null} */ -exports.getSavedRevisionsCount = async function(padID) { +exports.getSavedRevisionsCount = async function (padID) { // get the pad - let pad = await getPadSafe(padID, true); - return { savedRevisions: pad.getSavedRevisionsNumber() }; -} + const pad = await getPadSafe(padID, true); + return {savedRevisions: pad.getSavedRevisionsNumber()}; +}; /** 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: 1, message:"padID does not exist", data: null} */ -exports.listSavedRevisions = async function(padID) { +exports.listSavedRevisions = async function (padID) { // get the pad - let pad = await getPadSafe(padID, true); - return { savedRevisions: pad.getSavedRevisionsList() }; -} + const pad = await getPadSafe(padID, true); + return {savedRevisions: pad.getSavedRevisionsList()}; +}; /** saveRevision(padID) returns the list of saved revisions of this pad @@ -419,28 +417,28 @@ Example returns: {code: 0, message:"ok", 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 if (rev !== undefined) { rev = checkValidRev(rev); } // get the pad - let pad = await getPadSafe(padID, true); - let head = pad.getHeadRevisionNumber(); + const pad = await getPadSafe(padID, true); + const head = pad.getHeadRevisionNumber(); // the client asked for a special revision if (rev !== undefined) { 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 { 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'); -} +}; /** 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: 1, message:"padID does not exist", data: null} */ -exports.getLastEdited = async function(padID) { +exports.getLastEdited = async function (padID) { // get the pad - let pad = await getPadSafe(padID, true); - let lastEdited = await pad.getLastEdit(); - return { lastEdited }; -} + const pad = await getPadSafe(padID, true); + const lastEdited = await pad.getLastEdit(); + return {lastEdited}; +}; /** createPad(padName [, text]) creates a new pad in this group @@ -465,22 +463,22 @@ Example returns: {code: 0, message:"ok", 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) { // ensure there is no $ in the padID - if (padID.indexOf("$") !== -1) { - throw new customError("createPad can't create group pads", "apierror"); + if (padID.indexOf('$') !== -1) { + throw new customError("createPad can't create group pads", 'apierror'); } // check for url special characters if (padID.match(/(\/|\?|&|#)/)) { - throw new customError("malformed padID: Remove special characters", "apierror"); + throw new customError('malformed padID: Remove special characters', 'apierror'); } } // create pad await getPadSafe(padID, false, text); -} +}; /** deletePad(padID) deletes a pad @@ -490,10 +488,10 @@ Example returns: {code: 0, message:"ok", data: null} {code: 1, message:"padID does not exist", data: null} */ -exports.deletePad = async function(padID) { - let pad = await getPadSafe(padID, true); +exports.deletePad = async function (padID) { + const pad = await getPadSafe(padID, true); await pad.remove(); -} +}; /** 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: 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 if (rev === undefined) { - throw new customError("rev is not defined", "apierror"); + throw new customError('rev is not defined', 'apierror'); } rev = checkValidRev(rev); // get the pad - let pad = await getPadSafe(padID, true); + const pad = await getPadSafe(padID, true); // check if this is a valid revision 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(); - atext.text += "\n"; + const oldText = pad.text(); + atext.text += '\n'; function eachAttribRun(attribs, func) { - var attribsIter = Changeset.opIterator(attribs); - var textIndex = 0; - var newTextStart = 0; - var newTextEnd = atext.text.length; + const attribsIter = Changeset.opIterator(attribs); + let textIndex = 0; + const newTextStart = 0; + const newTextEnd = atext.text.length; while (attribsIter.hasNext()) { - var op = attribsIter.next(); - var nextIndex = textIndex + op.chars; + const op = attribsIter.next(); + const nextIndex = textIndex + op.chars; if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) { 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 - var builder = Changeset.builder(oldText.length); + const builder = Changeset.builder(oldText.length); // 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); }); - var lastNewlinePos = oldText.lastIndexOf('\n'); + const lastNewlinePos = oldText.lastIndexOf('\n'); if (lastNewlinePos < 0) { builder.remove(oldText.length - 1, 0); } else { @@ -554,13 +552,13 @@ exports.restoreRevision = async function(padID, rev) { builder.remove(oldText.length - lastNewlinePos - 1, 0); } - var changeset = builder.toString(); + const changeset = builder.toString(); await Promise.all([ pad.appendRevision(changeset), padMessageHandler.updatePadClients(pad), ]); -} +}; /** 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: 1, message:"padID does not exist", data: null} */ -exports.copyPad = async function(sourceID, destinationID, force) { - let pad = await getPadSafe(sourceID, true); +exports.copyPad = async function (sourceID, destinationID, force) { + const pad = await getPadSafe(sourceID, true); await pad.copy(destinationID, force); -} +}; /** 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: 1, message:"padID does not exist", data: null} */ -exports.copyPadWithoutHistory = async function(sourceID, destinationID, force) { - let pad = await getPadSafe(sourceID, true); +exports.copyPadWithoutHistory = async function (sourceID, destinationID, force) { + const pad = await getPadSafe(sourceID, true); await pad.copyPadWithoutHistory(destinationID, force); -} +}; /** 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: 1, message:"padID does not exist", data: null} */ -exports.movePad = async function(sourceID, destinationID, force) { - let pad = await getPadSafe(sourceID, true); +exports.movePad = async function (sourceID, destinationID, force) { + const pad = await getPadSafe(sourceID, true); await pad.copy(destinationID, force); await pad.remove(); -} +}; /** getReadOnlyLink(padID) returns the read only link of a pad @@ -613,15 +611,15 @@ Example returns: {code: 0, message:"ok", 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 await getPadSafe(padID, true); // 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) @@ -631,15 +629,15 @@ Example returns: {code: 0, message:"ok", data: {padID: padID}} {code: 1, message:"padID does not exist", data: null} */ -exports.getPadID = async function(roID) { +exports.getPadID = async function (roID) { // get the PadId - let padID = await readOnlyManager.getPadId(roID); + const padID = await readOnlyManager.getPadId(roID); 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 @@ -649,20 +647,20 @@ Example returns: {code: 0, message:"ok", 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 - checkGroupPad(padID, "publicStatus"); + checkGroupPad(padID, 'publicStatus'); // get the pad - let pad = await getPadSafe(padID, true); + const pad = await getPadSafe(padID, true); // convert string to boolean - if (typeof publicStatus === "string") { - publicStatus = (publicStatus.toLowerCase() === "true"); + if (typeof publicStatus === 'string') { + publicStatus = (publicStatus.toLowerCase() === 'true'); } await pad.setPublicStatus(publicStatus); -} +}; /** getPublicStatus(padID) return true of false @@ -672,14 +670,14 @@ Example returns: {code: 0, message:"ok", data: {publicStatus: true}} {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 - checkGroupPad(padID, "publicStatus"); + checkGroupPad(padID, 'publicStatus'); // get the pad - let pad = await getPadSafe(padID, true); - return { publicStatus: pad.getPublicStatus() }; -} + const pad = await getPadSafe(padID, true); + return {publicStatus: pad.getPublicStatus()}; +}; /** 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: 1, message:"padID does not exist", data: null} */ -exports.listAuthorsOfPad = async function(padID) { +exports.listAuthorsOfPad = async function (padID) { // get the pad - let pad = await getPadSafe(padID, true); - let authorIDs = pad.getAllAuthors(); - return { authorIDs }; -} + const pad = await getPadSafe(padID, true); + const authorIDs = pad.getAllAuthors(); + return {authorIDs}; +}; /** 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"} */ -exports.sendClientsMessage = async function(padID, msg) { - let pad = await getPadSafe(padID, true); +exports.sendClientsMessage = async function (padID, msg) { + const pad = await getPadSafe(padID, true); padMessageHandler.handleCustomMessage(padID, msg); -} +}; /** checkToken() returns ok when the current api token is valid @@ -732,8 +730,8 @@ Example returns: {"code":0,"message":"ok","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 @@ -743,11 +741,11 @@ Example returns: {code: 0, message:"ok", data: {chatHead: 42}} {code: 1, message:"padID does not exist", data: null} */ -exports.getChatHead = async function(padID) { +exports.getChatHead = async function (padID) { // get the pad - let pad = await getPadSafe(padID, true); - return { chatHead: pad.chatHead }; -} + const pad = await getPadSafe(padID, true); + return {chatHead: pad.chatHead}; +}; /** 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":"Welcome to Etherpad!

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!

Get involved with Etherpad at http://etherpad.org
aw

","authors":["a.HKIv23mEbachFYfH",""]}} {"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 if (startRev !== undefined) { startRev = checkValidRev(startRev); @@ -770,18 +767,18 @@ exports.createDiffHTML = async function(padID, startRev, endRev) { } // get the pad - let pad = await getPadSafe(padID, true); + const pad = await getPadSafe(padID, true); try { var padDiff = new PadDiff(pad, startRev, endRev); } catch (e) { - throw { stop: e.message }; + throw {stop: e.message}; } - let html = await padDiff.getHtml(); - let authors = await padDiff.getAuthors(); + const html = await padDiff.getHtml(); + const authors = await padDiff.getAuthors(); - return { html, authors }; -} + return {html, authors}; +}; /* ******************** ** GLOBAL FUNCTIONS ** @@ -796,20 +793,20 @@ exports.createDiffHTML = async function(padID, startRev, endRev) { {"code":4,"message":"no or wrong API Key","data":null} */ -exports.getStats = async function() { +exports.getStats = async function () { const sessionInfos = padMessageHandler.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 { totalPads: padIDs.length, totalSessions: sessionKeys.length, totalActivePads: activePads.size, - } -} + }; +}; /* **************************** ** INTERNAL HELPER FUNCTIONS * @@ -817,32 +814,32 @@ exports.getStats = async function() { // checks if a number is an int function is_int(value) { - return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value) + return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value); } // gets a pad safe async function getPadSafe(padID, shouldExist, text) { // check if padID is a string - if (typeof padID !== "string") { - throw new customError("padID is not a string", "apierror"); + if (typeof padID !== 'string') { + throw new customError('padID is not a string', 'apierror'); } // check if the padID maches the requirements 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 - let exists = await padManager.doesPadExists(padID); + const exists = await padManager.doesPadExists(padID); if (!exists && shouldExist) { // does not exist, but should - throw new customError("padID does not exist", "apierror"); + throw new customError('padID does not exist', 'apierror'); } if (exists && !shouldExist) { // 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 @@ -852,23 +849,23 @@ async function getPadSafe(padID, shouldExist, text) { // checks if a rev is a legal number // pre-condition is that `rev` is not undefined function checkValidRev(rev) { - if (typeof rev !== "number") { + if (typeof rev !== 'number') { rev = parseInt(rev, 10); } // check if rev is a number 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 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 if (!is_int(rev)) { - throw new customError("rev is a float value", "apierror"); + throw new customError('rev is a float value', 'apierror'); } return rev; @@ -877,7 +874,7 @@ function checkValidRev(rev) { // checks if a padID is part of a group function checkGroupPad(padID, field) { // ensure this is a group pad - if (padID && padID.indexOf("$") === -1) { - throw new customError(`You can only get/set the ${field} of pads that belong to a group`, "apierror"); + if (padID && padID.indexOf('$') === -1) { + throw new customError(`You can only get/set the ${field} of pads that belong to a group`, 'apierror'); } } diff --git a/src/node/db/AuthorManager.js b/src/node/db/AuthorManager.js index 382cf3ee5..f4ef903cf 100644 --- a/src/node/db/AuthorManager.js +++ b/src/node/db/AuthorManager.js @@ -18,31 +18,87 @@ * limitations under the License. */ -var db = require("./DB"); -var customError = require("../utils/customError"); -var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; +const db = require('./DB'); +const customError = require('../utils/customError'); +const randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; -exports.getColorPalette = function() { +exports.getColorPalette = function () { return [ - "#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#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" + '#ffc7c7', + '#fff1c7', + '#e3ffc7', + '#c7ffd5', + '#c7ffff', + '#c7d5ff', + '#e3c7ff', + '#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 */ -exports.doesAuthorExist = async function(authorID) { - let author = await db.get("globalAuthor:" + authorID); +exports.doesAuthorExist = async function (authorID) { + const author = await db.get(`globalAuthor:${authorID}`); return author !== null; -} +}; /* exported for backwards compatibility */ exports.doesAuthorExists = exports.doesAuthorExist; @@ -51,20 +107,20 @@ exports.doesAuthorExists = exports.doesAuthorExist; * Returns the AuthorID for a token. * @param {String} token The token */ -exports.getAuthor4Token = async function(token) { - let author = await mapAuthorWithDBKey("token2author", token); +exports.getAuthor4Token = async function (token) { + const author = await mapAuthorWithDBKey('token2author', token); // return only the sub value authorID return author ? author.authorID : author; -} +}; /** * Returns the AuthorID for a mapper. * @param {String} token The mapper * @param {String} name The name of the author (optional) */ -exports.createAuthorIfNotExistsFor = async function(authorMapper, name) { - let author = await mapAuthorWithDBKey("mapper2author", authorMapper); +exports.createAuthorIfNotExistsFor = async function (authorMapper, name) { + const author = await mapAuthorWithDBKey('mapper2author', authorMapper); if (name) { // 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} mapper The mapper */ -async function mapAuthorWithDBKey (mapperkey, mapper) { +async function mapAuthorWithDBKey(mapperkey, mapper) { // try to map to an author - let author = await db.get(mapperkey + ":" + mapper); + const author = await db.get(`${mapperkey}:${mapper}`); if (author === null) { // 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 - await db.set(mapperkey + ":" + mapper, author.authorID); + await db.set(`${mapperkey}:${mapper}`, author.authorID); // return the author return author; @@ -97,109 +153,109 @@ async function mapAuthorWithDBKey (mapperkey, mapper) { // there is an author with this mapper // 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 { authorID: author}; + return {authorID: author}; } /** * Internal function that creates the database entry for an author * @param {String} name The name of the author */ -exports.createAuthor = function(name) { +exports.createAuthor = function (name) { // create the new author name - let author = "a." + randomString(16); + const author = `a.${randomString(16)}`; // create the globalAuthors db entry - let authorObj = { - "colorId": Math.floor(Math.random() * (exports.getColorPalette().length)), - "name": name, - "timestamp": Date.now() + const authorObj = { + colorId: Math.floor(Math.random() * (exports.getColorPalette().length)), + name, + timestamp: Date.now(), }; // set the global author db entry // 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 * @param {String} author The id of the author */ -exports.getAuthor = function(author) { +exports.getAuthor = function (author) { // NB: result is already a Promise - return db.get("globalAuthor:" + author); -} + return db.get(`globalAuthor:${author}`); +}; /** * Returns the color Id of the author * @param {String} author The id of the author */ -exports.getAuthorColorId = function(author) { - return db.getSub("globalAuthor:" + author, ["colorId"]); -} +exports.getAuthorColorId = function (author) { + return db.getSub(`globalAuthor:${author}`, ['colorId']); +}; /** * Sets the color Id of the author * @param {String} author The id of the author * @param {String} colorId The color id of the author */ -exports.setAuthorColorId = function(author, colorId) { - return db.setSub("globalAuthor:" + author, ["colorId"], colorId); -} +exports.setAuthorColorId = function (author, colorId) { + return db.setSub(`globalAuthor:${author}`, ['colorId'], colorId); +}; /** * Returns the name of the author * @param {String} author The id of the author */ -exports.getAuthorName = function(author) { - return db.getSub("globalAuthor:" + author, ["name"]); -} +exports.getAuthorName = function (author) { + return db.getSub(`globalAuthor:${author}`, ['name']); +}; /** * Sets the name of the author * @param {String} author The id of the author * @param {String} name The name of the author */ -exports.setAuthorName = function(author, name) { - return db.setSub("globalAuthor:" + author, ["name"], name); -} +exports.setAuthorName = function (author, name) { + return db.setSub(`globalAuthor:${author}`, ['name'], name); +}; /** * Returns an array of all pads this author contributed to * @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: * (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 */ // get the globalAuthor - let author = await db.get("globalAuthor:" + authorID); + const author = await db.get(`globalAuthor:${authorID}`); if (author === null) { // 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 - 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 * @param {String} author The id of the author * @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 - let author = await db.get("globalAuthor:" + authorID); + const author = await db.get(`globalAuthor:${authorID}`); if (author === null) return; @@ -216,22 +272,22 @@ exports.addPad = async function(authorID, padID) { author.padIDs[padID] = 1; // anything, because value is not used // save the new element back - db.set("globalAuthor:" + authorID, author); -} + db.set(`globalAuthor:${authorID}`, author); +}; /** * Removes a pad from the list of contributions * @param {String} author The id of the author * @param {String} padID The id of the pad the author contributes to */ -exports.removePad = async function(authorID, padID) { - let author = await db.get("globalAuthor:" + authorID); +exports.removePad = async function (authorID, padID) { + const author = await db.get(`globalAuthor:${authorID}`); if (author === null) return; if (author.padIDs !== null) { // remove pad from author delete author.padIDs[padID]; - await db.set('globalAuthor:' + authorID, author); + await db.set(`globalAuthor:${authorID}`, author); } -} +}; diff --git a/src/node/db/DB.js b/src/node/db/DB.js index 22c3635ea..aca8d7432 100644 --- a/src/node/db/DB.js +++ b/src/node/db/DB.js @@ -19,13 +19,13 @@ * limitations under the License. */ -var ueberDB = require("ueberdb2"); -var settings = require("../utils/Settings"); -var log4js = require('log4js'); -const util = require("util"); +const ueberDB = require('ueberdb2'); +const settings = require('../utils/Settings'); +const log4js = require('log4js'); +const util = require('util'); // 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 @@ -36,32 +36,32 @@ exports.db = null; * Initalizes the database with the settings provided by the settings module * @param {Function} callback */ -exports.init = function() { +exports.init = function () { // initalize the database async return new Promise((resolve, reject) => { - db.init(function(err) { + db.init((err) => { if (err) { // 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); process.exit(1); } // 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)); }); // set up wrappers for get and getSub that can't return "undefined" - let get = exports.get; - exports.get = async function(key) { - let result = await get(key); + const get = exports.get; + exports.get = async function (key) { + const result = await get(key); return (result === undefined) ? null : result; }; - let getSub = exports.getSub; - exports.getSub = async function(key, sub) { - let result = await getSub(key, sub); + const getSub = exports.getSub; + exports.getSub = async function (key, sub) { + const result = await getSub(key, sub); return (result === undefined) ? null : result; }; @@ -70,7 +70,7 @@ exports.init = function() { resolve(); }); }); -} +}; exports.shutdown = async (hookName, context) => { await exports.doShutdown(); diff --git a/src/node/db/GroupManager.js b/src/node/db/GroupManager.js index 3e9d11676..1330acc43 100644 --- a/src/node/db/GroupManager.js +++ b/src/node/db/GroupManager.js @@ -18,52 +18,48 @@ * limitations under the License. */ -var customError = require("../utils/customError"); -var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; -var db = require("./DB"); -var padManager = require("./PadManager"); -var sessionManager = require("./SessionManager"); +const customError = require('../utils/customError'); +const randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; +const db = require('./DB'); +const padManager = require('./PadManager'); +const sessionManager = require('./SessionManager'); -exports.listAllGroups = async function() { - let groups = await db.get("groups"); +exports.listAllGroups = async function () { + let groups = await db.get('groups'); groups = groups || {}; - let groupIDs = Object.keys(groups); - return { groupIDs }; -} + const groupIDs = Object.keys(groups); + return {groupIDs}; +}; -exports.deleteGroup = async function(groupID) { - let group = await db.get("group:" + groupID); +exports.deleteGroup = async function (groupID) { + const group = await db.get(`group:${groupID}`); // ensure group exists if (group == null) { // 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) - await Promise.all(Object.keys(group.pads).map(padID => { - return padManager.getPad(padID).then(pad => pad.remove()); - })); + await Promise.all(Object.keys(group.pads).map((padID) => padManager.getPad(padID).then((pad) => pad.remove()))); // iterate through group2sessions and delete all sessions - let group2sessions = await db.get("group2sessions:" + groupID); - let sessions = group2sessions ? group2sessions.sessionIDs : {}; + const group2sessions = await db.get(`group2sessions:${groupID}`); + const sessions = group2sessions ? group2sessions.sessionIDs : {}; // loop through all sessions and delete them (in parallel) - await Promise.all(Object.keys(sessions).map(session => { - return sessionManager.deleteSession(session); - })); + await Promise.all(Object.keys(sessions).map((session) => sessionManager.deleteSession(session))); // remove group and group2sessions entry - await db.remove("group2sessions:" + groupID); - await db.remove("group:" + groupID); + await db.remove(`group2sessions:${groupID}`); + await db.remove(`group:${groupID}`); // unlist the group let groups = await exports.listAllGroups(); groups = groups ? groups.groupIDs : []; - let index = groups.indexOf(groupID); + const index = groups.indexOf(groupID); if (index === -1) { // it's not listed @@ -75,102 +71,102 @@ exports.deleteGroup = async function(groupID) { groups.splice(index, 1); // regenerate group list - var newGroups = {}; - groups.forEach(group => newGroups[group] = 1); - await db.set("groups", newGroups); -} + const newGroups = {}; + groups.forEach((group) => newGroups[group] = 1); + await db.set('groups', newGroups); +}; -exports.doesGroupExist = async function(groupID) { +exports.doesGroupExist = async function (groupID) { // try to get the group entry - let group = await db.get("group:" + groupID); + const group = await db.get(`group:${groupID}`); return (group != null); -} +}; -exports.createGroup = async function() { +exports.createGroup = async function () { // search for non existing groupID - var groupID = "g." + randomString(16); + const groupID = `g.${randomString(16)}`; // create the group - await db.set("group:" + groupID, {pads: {}}); + await db.set(`group:${groupID}`, {pads: {}}); // list the group let groups = await exports.listAllGroups(); - groups = groups? groups.groupIDs : []; + groups = groups ? groups.groupIDs : []; groups.push(groupID); // regenerate group list - var newGroups = {}; - groups.forEach(group => newGroups[group] = 1); - await db.set("groups", newGroups); + const newGroups = {}; + groups.forEach((group) => newGroups[group] = 1); + await db.set('groups', newGroups); - return { groupID }; -} + return {groupID}; +}; -exports.createGroupIfNotExistsFor = async function(groupMapper) { +exports.createGroupIfNotExistsFor = async function (groupMapper) { // ensure mapper is optional - if (typeof groupMapper !== "string") { - throw new customError("groupMapper is not a string", "apierror"); + if (typeof groupMapper !== 'string') { + throw new customError('groupMapper is not a string', 'apierror'); } // try to get a group for this mapper - let groupID = await db.get("mapper2group:" + groupMapper); + const groupID = await db.get(`mapper2group:${groupMapper}`); if (groupID) { // 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 - let result = await exports.createGroup(); + const result = await exports.createGroup(); // create the mapper entry for this group - await db.set("mapper2group:" + groupMapper, result.groupID); + await db.set(`mapper2group:${groupMapper}`, result.groupID); return result; -} +}; -exports.createGroupPad = async function(groupID, padName, text) { +exports.createGroupPad = async function (groupID, padName, text) { // create the padID - let padID = groupID + "$" + padName; + const padID = `${groupID}$${padName}`; // ensure group exists - let groupExists = await exports.doesGroupExist(groupID); + const groupExists = await exports.doesGroupExist(groupID); if (!groupExists) { - throw new customError("groupID does not exist", "apierror"); + throw new customError('groupID does not exist', 'apierror'); } // ensure pad doesn't exist already - let padExists = await padManager.doesPadExists(padID); + const padExists = await padManager.doesPadExists(padID); if (padExists) { // pad exists already - throw new customError("padName does already exist", "apierror"); + throw new customError('padName does already exist', 'apierror'); } // create the pad await padManager.getPad(padID, text); - //create an entry in the group for this pad - await db.setSub("group:" + groupID, ["pads", padID], 1); + // create an entry in the group for this pad + await db.setSub(`group:${groupID}`, ['pads', padID], 1); - return { padID }; -} + return {padID}; +}; -exports.listPads = async function(groupID) { - let exists = await exports.doesGroupExist(groupID); +exports.listPads = async function (groupID) { + const exists = await exports.doesGroupExist(groupID); // ensure the group 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 - let result = await db.getSub("group:" + groupID, ["pads"]); - let padIDs = Object.keys(result); + const result = await db.getSub(`group:${groupID}`, ['pads']); + const padIDs = Object.keys(result); - return { padIDs }; -} + return {padIDs}; +}; diff --git a/src/node/db/Pad.js b/src/node/db/Pad.js index b147f40e0..c39b3c69e 100644 --- a/src/node/db/Pad.js +++ b/src/node/db/Pad.js @@ -3,36 +3,36 @@ */ -var Changeset = require("ep_etherpad-lite/static/js/Changeset"); -var AttributePool = require("ep_etherpad-lite/static/js/AttributePool"); -var db = require("./DB"); -var settings = require('../utils/Settings'); -var authorManager = require("./AuthorManager"); -var padManager = require("./PadManager"); -var padMessageHandler = require("../handler/PadMessageHandler"); -var groupManager = require("./GroupManager"); -var customError = require("../utils/customError"); -var readOnlyManager = require("./ReadOnlyManager"); -var crypto = require("crypto"); -var randomString = require("../utils/randomstring"); -var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); -var promises = require('../utils/promises') +const Changeset = require('ep_etherpad-lite/static/js/Changeset'); +const AttributePool = require('ep_etherpad-lite/static/js/AttributePool'); +const db = require('./DB'); +const settings = require('../utils/Settings'); +const authorManager = require('./AuthorManager'); +const padManager = require('./PadManager'); +const padMessageHandler = require('../handler/PadMessageHandler'); +const groupManager = require('./GroupManager'); +const customError = require('../utils/customError'); +const readOnlyManager = require('./ReadOnlyManager'); +const crypto = require('crypto'); +const randomString = require('../utils/randomstring'); +const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); +const promises = require('../utils/promises'); // serialization/deserialization attributes -var attributeBlackList = ["id"]; -var jsonableList = ["pool"]; +const attributeBlackList = ['id']; +const jsonableList = ['pool']; /** * Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces * @param 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) { - this.atext = Changeset.makeAText("\n"); +const Pad = function Pad(id) { + this.atext = Changeset.makeAText('\n'); this.pool = new AttributePool(); this.head = -1; this.chatHead = -1; @@ -56,13 +56,11 @@ Pad.prototype.getSavedRevisionsNumber = function getSavedRevisionsNumber() { }; Pad.prototype.getSavedRevisionsList = function getSavedRevisionsList() { - var savedRev = new Array(); - for (var rev in this.savedRevisions) { + const savedRev = new Array(); + for (const rev in this.savedRevisions) { savedRev.push(this.savedRevisions[rev].revNum); } - savedRev.sort(function(a, b) { - return a - b; - }); + savedRev.sort((a, b) => a - b); return savedRev; }; @@ -75,12 +73,12 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, 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); - var newRev = ++this.head; + const newRev = ++this.head; - var newRevData = {}; + const newRevData = {}; newRevData.changeset = aChangeset; newRevData.meta = {}; newRevData.meta.author = author; @@ -97,7 +95,7 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author) } const p = [ - db.set('pad:' + this.id + ':revs:' + newRev, newRevData), + db.set(`pad:${this.id}:revs:${newRev}`, newRevData), this.saveToDatabase(), ]; @@ -107,9 +105,9 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author) } if (this.head == 0) { - hooks.callAll("padCreate", {'pad':this, 'author': author}); + hooks.callAll('padCreate', {pad: this, author}); } 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); @@ -117,10 +115,10 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author) // save all attributes to the database Pad.prototype.saveToDatabase = async function saveToDatabase() { - var dbObject = {}; + const dbObject = {}; - for (var attr in this) { - if (typeof this[attr] === "function") continue; + for (const attr in this) { + if (typeof this[attr] === 'function') continue; if (attributeBlackList.indexOf(attr) !== -1) continue; 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) Pad.prototype.getLastEdit = function getLastEdit() { - var revNum = this.getHeadRevisionNumber(); - return db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "timestamp"]); -} + const revNum = this.getHeadRevisionNumber(); + return db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'timestamp']); +}; 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) { - 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) { - 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() { - var authors = []; + const authors = []; - for(var key in this.pool.numToAttrib) { - if (this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "") { + for (const key in this.pool.numToAttrib) { + if (this.pool.numToAttrib[key][0] == 'author' && 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) { - let keyRev = this.getKeyRevisionNumber(targetRev); + const keyRev = this.getKeyRevisionNumber(targetRev); // find out which changesets are needed - let neededChangesets = []; - for (let curRev = keyRev; curRev < targetRev; ) { + const neededChangesets = []; + for (let curRev = keyRev; curRev < targetRev;) { neededChangesets.push(++curRev); } // get all needed data out of the database // 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 - let changesets = []; - await Promise.all(neededChangesets.map(item => { - return this.getRevisionChangeset(item).then(changeset => { - changesets[item] = changeset; - }); - })); + const changesets = []; + await Promise.all(neededChangesets.map((item) => this.getRevisionChangeset(item).then((changeset) => { + changesets[item] = changeset; + }))); // we should have the atext by now let atext = await p_atext; atext = Changeset.cloneAText(atext); // apply all changesets to the key changeset - let apool = this.apool(); - for (let curRev = keyRev; curRev < targetRev; ) { - let cs = changesets[++curRev]; + const apool = this.apool(); + for (let curRev = keyRev; curRev < targetRev;) { + const cs = changesets[++curRev]; atext = Changeset.applyToAText(cs, atext, apool); } return atext; -} +}; 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() { - let authors = this.getAllAuthors(); - let returnTable = {}; - let colorPalette = authorManager.getColorPalette(); + const authors = this.getAllAuthors(); + const returnTable = {}; + const colorPalette = authorManager.getColorPalette(); - await Promise.all(authors.map(author => { - return authorManager.getAuthorColorId(author).then(colorId => { - // colorId might be a hex color or an number out of the palette - returnTable[author] = colorPalette[colorId] || colorId; - }); - })); + await Promise.all(authors.map((author) => authorManager.getAuthorColorId(author).then((colorId) => { + // colorId might be a hex color or an number out of the palette + returnTable[author] = colorPalette[colorId] || colorId; + }))); return returnTable; -} +}; Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, endRev) { startRev = parseInt(startRev, 10); - var head = this.getHeadRevisionNumber(); + const head = this.getHeadRevisionNumber(); endRev = endRev ? parseInt(endRev, 10) : head; if (isNaN(startRev) || startRev < 0 || startRev > head) { @@ -234,7 +228,7 @@ Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, e } if (startRev !== null && endRev !== null) { - return { startRev: startRev , endRev: endRev } + return {startRev, endRev}; } return null; }; @@ -251,16 +245,16 @@ Pad.prototype.setText = async function setText(newText) { // clean the new text newText = exports.cleanText(newText); - var oldText = this.text(); + const oldText = this.text(); // create the changeset // We want to ensure the pad still ends with a \n, but otherwise keep // getText() and setText() consistent. - var changeset; + let changeset; if (newText[newText.length - 1] == '\n') { changeset = Changeset.makeSplice(oldText, 0, oldText.length, newText); } else { - changeset = Changeset.makeSplice(oldText, 0, oldText.length-1, newText); + changeset = Changeset.makeSplice(oldText, 0, oldText.length - 1, newText); } // append the changeset @@ -271,10 +265,10 @@ Pad.prototype.appendText = async function appendText(newText) { // clean the new text newText = exports.cleanText(newText); - var oldText = this.text(); + const oldText = this.text(); // create the changeset - var changeset = Changeset.makeSplice(oldText, oldText.length, 0, newText); + const changeset = Changeset.makeSplice(oldText, oldText.length, 0, newText); // append the changeset await this.appendRevision(changeset); @@ -284,14 +278,14 @@ Pad.prototype.appendChatMessage = async function appendChatMessage(text, userId, this.chatHead++; // save the chat entry in the database 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(), ]); }; Pad.prototype.getChatMessage = async function getChatMessage(entryNum) { // 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 if (entry != null) { @@ -302,49 +296,45 @@ Pad.prototype.getChatMessage = async function getChatMessage(entryNum) { }; Pad.prototype.getChatMessages = async function getChatMessages(start, end) { - // 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) { - neededEntries.push({ entryNum, order }); + neededEntries.push({entryNum, order}); } // get all entries out of the database - let entries = []; - await Promise.all(neededEntries.map(entryObject => { - return this.getChatMessage(entryObject.entryNum).then(entry => { - entries[entryObject.order] = entry; - }); - })); + const entries = []; + await Promise.all(neededEntries.map((entryObject) => this.getChatMessage(entryObject.entryNum).then((entry) => { + entries[entryObject.order] = entry; + }))); // sort out broken chat entries // it looks like in happened in the past that the chat head was // incremented, but the chat message wasn't added - let cleanedEntries = entries.filter(entry => { - let pass = (entry != null); + const cleanedEntries = entries.filter((entry) => { + const pass = (entry != null); 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 cleanedEntries; -} +}; Pad.prototype.init = async function init(text) { - // replace text with default text if text isn't set if (text == null) { text = settings.defaultPadText; } // 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 (value != null) { // 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) { this[attr] = this[attr].fromJsonable(value[attr]); } else { @@ -353,17 +343,17 @@ Pad.prototype.init = async function init(text) { } } else { // 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, ''); } - hooks.callAll("padLoad", { 'pad': this }); -} + hooks.callAll('padLoad', {pad: this}); +}; Pad.prototype.copy = async function copy(destinationID, force) { let destGroupID; - let sourceID = this.id; + const sourceID = this.id; // Kick everyone from this pad. // 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 await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force); - } catch(err) { + } catch (err) { throw err; } // copy the 'pad' entry - let pad = await db.get("pad:" + sourceID); - db.set("pad:" + destinationID, pad); + const pad = await db.get(`pad:${sourceID}`); + db.set(`pad:${destinationID}`, pad); // copy all relations in parallel - let promises = []; + const promises = []; // copy all chat messages - let chatHead = this.chatHead; + const chatHead = this.chatHead; for (let i = 0; i <= chatHead; ++i) { - let p = db.get("pad:" + sourceID + ":chat:" + i).then(chat => { - return db.set("pad:" + destinationID + ":chat:" + i, chat); - }); + const p = db.get(`pad:${sourceID}:chat:${i}`).then((chat) => db.set(`pad:${destinationID}:chat:${i}`, chat)); promises.push(p); } // copy all revisions - let revHead = this.head; + const revHead = this.head; for (let i = 0; i <= revHead; ++i) { - let p = db.get("pad:" + sourceID + ":revs:" + i).then(rev => { - return db.set("pad:" + destinationID + ":revs:" + i, rev); - }); + const p = db.get(`pad:${sourceID}:revs:${i}`).then((rev) => db.set(`pad:${destinationID}:revs:${i}`, rev)); promises.push(p); } @@ -416,70 +402,69 @@ Pad.prototype.copy = async function copy(destinationID, force) { // Group pad? Add it to the group's list if (destGroupID) { - await db.setSub("group:" + destGroupID, ["pads", destinationID], 1); + await db.setSub(`group:${destGroupID}`, ['pads', destinationID], 1); } // 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) await padManager.getPad(destinationID, null); // this runs too early. // 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) { let destGroupID = false; - if (destinationID.indexOf("$") >= 0) { - - destGroupID = destinationID.split("$")[0] - let groupExists = await groupManager.doesGroupExist(destGroupID); + if (destinationID.indexOf('$') >= 0) { + destGroupID = destinationID.split('$')[0]; + const groupExists = await groupManager.doesGroupExist(destGroupID); // group does not exist if (!groupExists) { - throw new customError("groupID does not exist for destinationID", "apierror"); + throw new customError('groupID does not exist for destinationID', 'apierror'); } } return destGroupID; -} +}; Pad.prototype.removePadIfForceIsTrueAndAlreadyExist = async function removePadIfForceIsTrueAndAlreadyExist(destinationID, force) { // 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 - if (typeof force === "string") { - force = (force.toLowerCase() === "true"); + if (typeof force === 'string') { + force = (force.toLowerCase() === 'true'); } else { force = !!force; } if (exists) { if (!force) { - console.error("erroring out without force"); - throw new customError("destinationID already exists", "apierror"); + console.error('erroring out without force'); + throw new customError('destinationID already exists', 'apierror'); } // exists and forcing - let pad = await padManager.getPad(destinationID); + const pad = await padManager.getPad(destinationID); await pad.remove(); } -} +}; Pad.prototype.copyAuthorInfoToDestinationPad = function copyAuthorInfoToDestinationPad(destinationID) { // 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); }); -} +}; Pad.prototype.copyPadWithoutHistory = async function copyPadWithoutHistory(destinationID, force) { let destGroupID; - let sourceID = this.id; + const sourceID = this.id; // flush the source pad 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 await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force); - } catch(err) { + } catch (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 this.copyAuthorInfoToDestinationPad(destinationID); // Group pad? Add it to the group's list 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 - let newPad = await padManager.getPad(destinationID, '\n'); + const newPad = await padManager.getPad(destinationID, '\n'); - let oldAText = this.atext; - let newPool = newPad.pool; + const oldAText = this.atext; + const newPool = newPad.pool; newPool.fromJsonable(sourcePad.pool.toJsonable()); // copy that sourceId pool to the new pad // based on Changeset.makeSplice - let assem = Changeset.smartOpAssembler(); + const assem = Changeset.smartOpAssembler(); assem.appendOpWithText('=', ''); Changeset.appendATextToAssembler(oldAText, assem); assem.endDocument(); // 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" - let oldLength = 2; + const oldLength = 2; - let newLength = assem.getLengthChange(); - let newText = oldAText.text; + const newLength = assem.getLengthChange(); + const newText = oldAText.text; // create a changeset that removes the previous text and add the newText with // 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); - 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() { - var padID = this.id; + const padID = this.id; const p = []; // kick everyone from this pad @@ -548,45 +533,44 @@ Pad.prototype.remove = async function remove() { // run to completion // 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 - let groupID = padID.substring(0, padID.indexOf("$")); - let group = await db.get("group:" + groupID); + const groupID = padID.substring(0, padID.indexOf('$')); + const group = await db.get(`group:${groupID}`); // remove the pad entry delete group.pads[padID]; // set the new value - p.push(db.set('group:' + groupID, group)); + p.push(db.set(`group:${groupID}`, group)); } // remove the readonly entries 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 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 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 - this.getAllAuthors().forEach(authorID => { + this.getAllAuthors().forEach((authorID) => { p.push(authorManager.removePad(authorID, padID)); }); // delete the pad entry and delete pad from padManager p.push(padManager.removePad(padID)); - hooks.callAll("padRemove", { padID }); + hooks.callAll('padRemove', {padID}); await Promise.all(p); -} +}; // set in db 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) { // 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) { return; } } // build the saved revision object - var savedRevision = {}; + const savedRevision = {}; savedRevision.revNum = revNum; savedRevision.savedById = savedById; - savedRevision.label = label || "Revision " + revNum; + savedRevision.label = label || `Revision ${revNum}`; savedRevision.timestamp = Date.now(); savedRevision.id = randomString(10); @@ -618,4 +602,3 @@ Pad.prototype.addSavedRevision = async function addSavedRevision(revNum, savedBy Pad.prototype.getSavedRevisions = function getSavedRevisions() { return this.savedRevisions; }; - diff --git a/src/node/db/PadManager.js b/src/node/db/PadManager.js index ec198b36a..9334b92a4 100644 --- a/src/node/db/PadManager.js +++ b/src/node/db/PadManager.js @@ -18,9 +18,9 @@ * limitations under the License. */ -var customError = require("../utils/customError"); -var Pad = require("../db/Pad").Pad; -var db = require("./DB"); +const customError = require('../utils/customError'); +const Pad = require('../db/Pad').Pad; +const db = require('./DB'); /** * 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 * that's defined somewhere more sensible. */ -var globalPads = { - get: function(name) { return this[':'+name]; }, - set: function(name, value) { - this[':'+name] = value; - }, - remove: function(name) { - delete this[':'+name]; - } +const globalPads = { + get(name) { return this[`:${name}`]; }, + set(name, value) { + this[`:${name}`] = value; + }, + remove(name) { + delete this[`:${name}`]; + }, }; /** @@ -48,24 +48,24 @@ var globalPads = { * * Updated without db access as new pads are created/old ones removed. */ -let padList = { +const padList = { list: new Set(), cachedList: undefined, initiated: false, - init: async function() { - let dbData = await db.findKeys("pad:*", "*:*:*"); + async init() { + const dbData = await db.findKeys('pad:*', '*:*:*'); if (dbData != null) { this.initiated = true; - for (let val of dbData) { - this.addPad(val.replace(/^pad:/,""), false); + for (const val of dbData) { + this.addPad(val.replace(/^pad:/, ''), false); } } return this; }, - load: async function() { + async load() { if (!this.initiated) { return this.init(); } @@ -75,7 +75,7 @@ let padList = { /** * Returns all pads in alphabetical order as array. */ - getPads: async function() { + async getPads() { await this.load(); if (!this.cachedList) { @@ -84,7 +84,7 @@ let padList = { return this.cachedList; }, - addPad: function(name) { + addPad(name) { if (!this.initiated) return; if (!this.list.has(name)) { @@ -92,14 +92,14 @@ let padList = { this.cachedList = undefined; } }, - removePad: function(name) { + removePad(name) { if (!this.initiated) return; if (this.list.has(name)) { this.list.delete(name); this.cachedList = undefined; } - } + }, }; // initialises the all-knowing data structure @@ -109,22 +109,22 @@ let padList = { * @param id A String with the id of the pad * @param {Function} callback */ -exports.getPad = async function(id, text) { +exports.getPad = async function (id, text) { // check if this is a valid padId 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 if (text != null) { // check if text is a string - if (typeof text != "string") { - throw new customError("text is not a string", "apierror"); + if (typeof text !== 'string') { + throw new customError('text is not a string', 'apierror'); } // check if text is less than 100k chars 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); return pad; -} +}; -exports.listAllPads = async function() { - let padIDs = await padList.getPads(); +exports.listAllPads = async function () { + const padIDs = await padList.getPads(); - return { padIDs }; -} + return {padIDs}; +}; // checks if a pad exists -exports.doesPadExist = async function(padId) { - let value = await db.get("pad:" + padId); +exports.doesPadExist = async function (padId) { + const value = await db.get(`pad:${padId}`); return (value != null && value.atext); -} +}; // alias for backwards compatibility exports.doesPadExists = exports.doesPadExist; @@ -168,42 +168,42 @@ exports.doesPadExists = exports.doesPadExist; */ const padIdTransforms = [ [/\s+/g, '_'], - [/:+/g, '_'] + [/:+/g, '_'], ]; // returns a sanitized padId, respecting legacy pad id formats exports.sanitizePadId = async function sanitizePadId(padId) { for (let i = 0, n = padIdTransforms.length; i < n; ++i) { - let exists = await exports.doesPadExist(padId); + const exists = await exports.doesPadExist(padId); if (exists) { return padId; } - let [from, to] = padIdTransforms[i]; + const [from, to] = padIdTransforms[i]; padId = padId.replace(from, to); } // we're out of possible transformations, so just return it return padId; -} +}; -exports.isValidPadId = function(padId) { +exports.isValidPadId = function (padId) { return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId); -} +}; /** * Removes the pad from database and unloads it. */ exports.removePad = async (padId) => { - const p = db.remove('pad:' + padId); + const p = db.remove(`pad:${padId}`); exports.unloadPad(padId); padList.removePad(padId); await p; -} +}; // removes a pad from the cache -exports.unloadPad = function(padId) { +exports.unloadPad = function (padId) { globalPads.remove(padId); -} +}; diff --git a/src/node/db/ReadOnlyManager.js b/src/node/db/ReadOnlyManager.js index 094f996cd..54f8c592e 100644 --- a/src/node/db/ReadOnlyManager.js +++ b/src/node/db/ReadOnlyManager.js @@ -19,17 +19,17 @@ */ -var db = require("./DB"); -var randomString = require("../utils/randomstring"); +const db = require('./DB'); +const randomString = require('../utils/randomstring'); /** * checks if the id pattern matches a read-only pad id * @param {String} the pad's id */ -exports.isReadOnlyId = function(id) { - return id.indexOf("r.") === 0; -} +exports.isReadOnlyId = function (id) { + return id.indexOf('r.') === 0; +}; /** * returns a read only id for a pad @@ -37,36 +37,36 @@ exports.isReadOnlyId = function(id) { */ exports.getReadOnlyId = async function (padId) { // 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 if (readOnlyId == null) { - readOnlyId = "r." + randomString(16); - db.set("pad2readonly:" + padId, readOnlyId); - db.set("readonly2pad:" + readOnlyId, padId); + readOnlyId = `r.${randomString(16)}`; + db.set(`pad2readonly:${padId}`, readOnlyId); + db.set(`readonly2pad:${readOnlyId}`, padId); } return readOnlyId; -} +}; /** * returns the padId for a read only id * @param {String} readOnlyId read only id */ -exports.getPadId = function(readOnlyId) { - return db.get("readonly2pad:" + readOnlyId); -} +exports.getPadId = function (readOnlyId) { + return db.get(`readonly2pad:${readOnlyId}`); +}; /** * returns the padId and readonlyPadId in an object for any id * @param {String} padIdOrReadonlyPadId read only id or real pad id */ -exports.getIds = async function(id) { - let readonly = (id.indexOf("r.") === 0); +exports.getIds = async function (id) { + const readonly = (id.indexOf('r.') === 0); // Might be null, if this is an unknown read-only id - let readOnlyPadId = readonly ? id : await exports.getReadOnlyId(id); - let padId = readonly ? await exports.getPadId(id) : id; + const readOnlyPadId = readonly ? id : await exports.getReadOnlyId(id); + const padId = readonly ? await exports.getPadId(id) : id; - return { readOnlyPadId, padId, readonly }; -} + return {readOnlyPadId, padId, readonly}; +}; diff --git a/src/node/db/SecurityManager.js b/src/node/db/SecurityManager.js index 331b395a6..64091dbdc 100644 --- a/src/node/db/SecurityManager.js +++ b/src/node/db/SecurityManager.js @@ -18,14 +18,14 @@ * limitations under the License. */ -var authorManager = require("./AuthorManager"); -var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js"); -var padManager = require("./PadManager"); -var sessionManager = require("./SessionManager"); -var settings = require("../utils/Settings"); +const authorManager = require('./AuthorManager'); +const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js'); +const padManager = require('./PadManager'); +const sessionManager = require('./SessionManager'); +const settings = require('../utils/Settings'); const webaccess = require('../hooks/express/webaccess'); -var log4js = require('log4js'); -var authLogger = log4js.getLogger("auth"); +const log4js = require('log4js'); +const authLogger = log4js.getLogger('auth'); 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 * 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) { authLogger.debug('access denied: missing padID'); return DENY; diff --git a/src/node/db/SessionManager.js b/src/node/db/SessionManager.js index c43ce91bf..8f1daab52 100644 --- a/src/node/db/SessionManager.js +++ b/src/node/db/SessionManager.js @@ -18,12 +18,12 @@ * limitations under the License. */ -var customError = require("../utils/customError"); +const customError = require('../utils/customError'); const promises = require('../utils/promises'); -var randomString = require("../utils/randomstring"); -var db = require("./DB"); -var groupManager = require("./GroupManager"); -var authorManager = require("./AuthorManager"); +const randomString = require('../utils/randomstring'); +const db = require('./DB'); +const groupManager = require('./GroupManager'); +const authorManager = require('./AuthorManager'); /** * Finds the author ID for a session with matching ID and group. @@ -78,61 +78,61 @@ exports.findAuthorID = async (groupID, sessionCookie) => { return sessionInfo.authorID; }; -exports.doesSessionExist = async function(sessionID) { - //check if the database entry of this session exists - let session = await db.get("session:" + sessionID); +exports.doesSessionExist = async function (sessionID) { + // check if the database entry of this session exists + const session = await db.get(`session:${sessionID}`); return (session !== null); -} +}; /** * 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 - let groupExists = await groupManager.doesGroupExist(groupID); + const groupExists = await groupManager.doesGroupExist(groupID); if (!groupExists) { - throw new customError("groupID does not exist", "apierror"); + throw new customError('groupID does not exist', 'apierror'); } // check if the author exists - let authorExists = await authorManager.doesAuthorExist(authorID); + const authorExists = await authorManager.doesAuthorExist(authorID); 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 - if (typeof validUntil !== "number") { + if (typeof validUntil !== 'number') { validUntil = parseInt(validUntil); } // check it's a valid number 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 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 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 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 - let sessionID = "s." + randomString(16); + const sessionID = `s.${randomString(16)}`; // 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 - 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". @@ -144,115 +144,115 @@ exports.createSession = async function(groupID, authorID, validUntil) { */ if (!group2sessions || !group2sessions.sessionIDs) { // the entry doesn't exist so far, let's create it - group2sessions = {sessionIDs : {}}; + group2sessions = {sessionIDs: {}}; } // add the entry for this session group2sessions.sessionIDs[sessionID] = 1; // save the new element back - await db.set("group2sessions:" + groupID, group2sessions); + await db.set(`group2sessions:${groupID}`, group2sessions); // get the author2sessions entry - let author2sessions = await db.get("author2sessions:" + authorID); + let author2sessions = await db.get(`author2sessions:${authorID}`); if (author2sessions == null || author2sessions.sessionIDs == null) { // the entry doesn't exist so far, let's create it - author2sessions = {sessionIDs : {}}; + author2sessions = {sessionIDs: {}}; } // add the entry for this session author2sessions.sessionIDs[sessionID] = 1; - //save the new element back - await db.set("author2sessions:" + authorID, author2sessions); + // save the new element back + 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 - let session = await db.get("session:" + sessionID); + const session = await db.get(`session:${sessionID}`); if (session == null) { // 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 return session; -} +}; /** * Deletes a session */ -exports.deleteSession = async function(sessionID) { +exports.deleteSession = async function (sessionID) { // ensure that the session exists - let session = await db.get("session:" + sessionID); + const session = await db.get(`session:${sessionID}`); 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 - let groupID = session.groupID; - let authorID = session.authorID; + const groupID = session.groupID; + const authorID = session.authorID; // get the group2sessions and author2sessions entries - let group2sessions = await db.get("group2sessions:" + groupID); - let author2sessions = await db.get("author2sessions:" + authorID); + const group2sessions = await db.get(`group2sessions:${groupID}`); + const author2sessions = await db.get(`author2sessions:${authorID}`); // remove the session - await db.remove("session:" + sessionID); + await db.remove(`session:${sessionID}`); // remove session from group2sessions if (group2sessions != null) { // Maybe the group was already deleted delete group2sessions.sessionIDs[sessionID]; - await db.set("group2sessions:" + groupID, group2sessions); + await db.set(`group2sessions:${groupID}`, group2sessions); } // remove session from author2sessions if (author2sessions != null) { // Maybe the author was already deleted 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 - let exists = await groupManager.doesGroupExist(groupID); + const exists = await groupManager.doesGroupExist(groupID); 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; -} +}; -exports.listSessionsOfAuthor = async function(authorID) { +exports.listSessionsOfAuthor = async function (authorID) { // check that the author exists - let exists = await authorManager.doesAuthorExist(authorID) + const exists = await authorManager.doesAuthorExist(authorID); 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; -} +}; // 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 async function listSessionsWithDBKey(dbkey) { // get the group2sessions entry - let sessionObject = await db.get(dbkey); - let sessions = sessionObject ? sessionObject.sessionIDs : null; + const sessionObject = await db.get(dbkey); + const sessions = sessionObject ? sessionObject.sessionIDs : null; // iterate through the sessions and get the sessioninfos - for (let sessionID in sessions) { + for (const sessionID in sessions) { try { - let sessionInfo = await exports.getSessionInfo(sessionID); + const sessionInfo = await exports.getSessionInfo(sessionID); sessions[sessionID] = sessionInfo; } catch (err) { - if (err == "apierror: sessionID does not exist") { + if (err == 'apierror: sessionID does not exist') { console.warn(`Found bad session ${sessionID} in ${dbkey}`); sessions[sessionID] = null; } else { diff --git a/src/node/db/SessionStore.js b/src/node/db/SessionStore.js index e265ee68e..91bd75561 100644 --- a/src/node/db/SessionStore.js +++ b/src/node/db/SessionStore.js @@ -15,11 +15,11 @@ const logger = log4js.getLogger('SessionStore'); module.exports = class SessionStore extends Store { get(sid, fn) { - logger.debug('GET ' + sid); - DB.db.get('sessionstorage:' + sid, (err, sess) => { + logger.debug(`GET ${sid}`); + DB.db.get(`sessionstorage:${sid}`, (err, sess) => { if (sess) { - sess.cookie.expires = ('string' == typeof sess.cookie.expires - ? new Date(sess.cookie.expires) : sess.cookie.expires); + sess.cookie.expires = ('string' === typeof sess.cookie.expires + ? new Date(sess.cookie.expires) : sess.cookie.expires); if (!sess.cookie.expires || new Date() < sess.cookie.expires) { fn(null, sess); } else { @@ -32,12 +32,12 @@ module.exports = class SessionStore extends Store { } set(sid, sess, fn) { - logger.debug('SET ' + sid); - DB.db.set('sessionstorage:' + sid, sess, fn); + logger.debug(`SET ${sid}`); + DB.db.set(`sessionstorage:${sid}`, sess, fn); } destroy(sid, fn) { - logger.debug('DESTROY ' + sid); - DB.db.remove('sessionstorage:' + sid, fn); + logger.debug(`DESTROY ${sid}`); + DB.db.remove(`sessionstorage:${sid}`, fn); } }; diff --git a/src/node/easysync_tests.js b/src/node/easysync_tests.js index 374e949fd..c8a5c9853 100644 --- a/src/node/easysync_tests.js +++ b/src/node/easysync_tests.js @@ -21,47 +21,45 @@ */ -var Changeset = require("ep_etherpad-lite/static/js/Changeset"); -var AttributePool = require("ep_etherpad-lite/static/js/AttributePool"); +const Changeset = require('ep_etherpad-lite/static/js/Changeset'); +const AttributePool = require('ep_etherpad-lite/static/js/AttributePool'); function random() { this.nextInt = function (maxValue) { return Math.floor(Math.random() * maxValue); - } + }; this.nextDouble = function (maxValue) { return Math.random(); - } + }; } function runTests() { - function print(str) { console.log(str); } function assert(code, optMsg) { - if (!eval(code)) throw new Error("FALSE: " + (optMsg || code)); + if (!eval(code)) throw new Error(`FALSE: ${optMsg || code}`); } function literal(v) { - if ((typeof v) == "string") { - return '"' + v.replace(/[\\\"]/g, '\\$1').replace(/\n/g, '\\n') + '"'; - } else - return JSON.stringify(v); + if ((typeof v) === 'string') { + return `"${v.replace(/[\\\"]/g, '\\$1').replace(/\n/g, '\\n')}"`; + } else { return JSON.stringify(v); } } function assertEqualArrays(a, b) { - assert("JSON.stringify(" + literal(a) + ") == JSON.stringify(" + literal(b) + ")"); + assert(`JSON.stringify(${literal(a)}) == JSON.stringify(${literal(b)})`); } function assertEqualStrings(a, b) { - assert(literal(a) + " == " + literal(b)); + assert(`${literal(a)} == ${literal(b)}`); } function throughIterator(opsStr) { - var iter = Changeset.opIterator(opsStr); - var assem = Changeset.opAssembler(); + const iter = Changeset.opIterator(opsStr); + const assem = Changeset.opAssembler(); while (iter.hasNext()) { assem.append(iter.next()); } @@ -69,8 +67,8 @@ function runTests() { } function throughSmartAssembler(opsStr) { - var iter = Changeset.opIterator(opsStr); - var assem = Changeset.smartOpAssembler(); + const iter = Changeset.opIterator(opsStr); + const assem = Changeset.smartOpAssembler(); while (iter.hasNext()) { assem.append(iter.next()); } @@ -79,20 +77,20 @@ function runTests() { } (function () { - print("> throughIterator"); - var x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1'; - assert("throughIterator(" + literal(x) + ") == " + literal(x)); + print('> throughIterator'); + const x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1'; + assert(`throughIterator(${literal(x)}) == ${literal(x)}`); })(); (function () { - print("> throughSmartAssembler"); - var x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1'; - assert("throughSmartAssembler(" + literal(x) + ") == " + literal(x)); + print('> throughSmartAssembler'); + const x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1'; + assert(`throughSmartAssembler(${literal(x)}) == ${literal(x)}`); })(); function applyMutations(mu, arrayOfArrays) { - arrayOfArrays.forEach(function (a) { - var result = mu[a[0]].apply(mu, a.slice(1)); + arrayOfArrays.forEach((a) => { + const result = mu[a[0]].apply(mu, a.slice(1)); if (a[0] == 'remove' && a[3]) { assertEqualStrings(a[3], result); } @@ -100,12 +98,12 @@ function runTests() { } function mutationsToChangeset(oldLen, arrayOfArrays) { - var assem = Changeset.smartOpAssembler(); - var op = Changeset.newOp(); - var bank = Changeset.stringAssembler(); - var oldPos = 0; - var newLen = 0; - arrayOfArrays.forEach(function (a) { + const assem = Changeset.smartOpAssembler(); + const op = Changeset.newOp(); + const bank = Changeset.stringAssembler(); + let oldPos = 0; + let newLen = 0; + arrayOfArrays.forEach((a) => { if (a[0] == 'skip') { op.opcode = '='; op.chars = a[1]; @@ -134,101 +132,101 @@ function runTests() { } function runMutationTest(testId, origLines, muts, correct) { - print("> runMutationTest#" + testId); - var lines = origLines.slice(); - var mu = Changeset.textLinesMutator(lines); + print(`> runMutationTest#${testId}`); + let lines = origLines.slice(); + const mu = Changeset.textLinesMutator(lines); applyMutations(mu, muts); mu.close(); assertEqualArrays(correct, lines); - var inText = origLines.join(''); - var cs = mutationsToChangeset(inText.length, muts); + const inText = origLines.join(''); + const cs = mutationsToChangeset(inText.length, muts); lines = origLines.slice(); Changeset.mutateTextLines(cs, lines); assertEqualArrays(correct, lines); - var correctText = correct.join(''); - //print(literal(cs)); - var outText = Changeset.applyToText(cs, inText); + const correctText = correct.join(''); + // print(literal(cs)); + const outText = Changeset.applyToText(cs, inText); assertEqualStrings(correctText, outText); } - runMutationTest(1, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"], [ - ['remove', 1, 0, "a"], - ['insert', "tu"], - ['remove', 1, 0, "p"], + runMutationTest(1, ['apple\n', 'banana\n', 'cabbage\n', 'duffle\n', 'eggplant\n'], [ + ['remove', 1, 0, 'a'], + ['insert', 'tu'], + ['remove', 1, 0, 'p'], ['skip', 4, 1], ['skip', 7, 1], - ['insert', "cream\npie\n", 2], + ['insert', 'cream\npie\n', 2], ['skip', 2], - ['insert', "bot"], - ['insert', "\n", 1], - ['insert', "bu"], + ['insert', 'bot'], + ['insert', '\n', 1], + ['insert', 'bu'], ['skip', 3], - ['remove', 3, 1, "ge\n"], - ['remove', 6, 0, "duffle"] - ], ["tuple\n", "banana\n", "cream\n", "pie\n", "cabot\n", "bubba\n", "eggplant\n"]); + ['remove', 3, 1, 'ge\n'], + ['remove', 6, 0, 'duffle'], + ], ['tuple\n', 'banana\n', 'cream\n', 'pie\n', 'cabot\n', 'bubba\n', 'eggplant\n']); - runMutationTest(2, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"], [ - ['remove', 1, 0, "a"], - ['remove', 1, 0, "p"], - ['insert', "tu"], + runMutationTest(2, ['apple\n', 'banana\n', 'cabbage\n', 'duffle\n', 'eggplant\n'], [ + ['remove', 1, 0, 'a'], + ['remove', 1, 0, 'p'], + ['insert', 'tu'], ['skip', 11, 2], - ['insert', "cream\npie\n", 2], + ['insert', 'cream\npie\n', 2], ['skip', 2], - ['insert', "bot"], - ['insert', "\n", 1], - ['insert', "bu"], + ['insert', 'bot'], + ['insert', '\n', 1], + ['insert', 'bu'], ['skip', 3], - ['remove', 3, 1, "ge\n"], - ['remove', 6, 0, "duffle"] - ], ["tuple\n", "banana\n", "cream\n", "pie\n", "cabot\n", "bubba\n", "eggplant\n"]); + ['remove', 3, 1, 'ge\n'], + ['remove', 6, 0, 'duffle'], + ], ['tuple\n', 'banana\n', 'cream\n', 'pie\n', 'cabot\n', 'bubba\n', 'eggplant\n']); - runMutationTest(3, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"], [ - ['remove', 6, 1, "apple\n"], + runMutationTest(3, ['apple\n', 'banana\n', 'cabbage\n', 'duffle\n', 'eggplant\n'], [ + ['remove', 6, 1, 'apple\n'], ['skip', 15, 2], ['skip', 6], - ['remove', 1, 1, "\n"], - ['remove', 8, 0, "eggplant"], - ['skip', 1, 1] - ], ["banana\n", "cabbage\n", "duffle\n"]); + ['remove', 1, 1, '\n'], + ['remove', 8, 0, 'eggplant'], + ['skip', 1, 1], + ], ['banana\n', 'cabbage\n', 'duffle\n']); - runMutationTest(4, ["15\n"], [ + runMutationTest(4, ['15\n'], [ ['skip', 1], - ['insert', "\n2\n3\n4\n", 4], - ['skip', 2, 1] - ], ["1\n", "2\n", "3\n", "4\n", "5\n"]); + ['insert', '\n2\n3\n4\n', 4], + ['skip', 2, 1], + ], ['1\n', '2\n', '3\n', '4\n', '5\n']); - runMutationTest(5, ["1\n", "2\n", "3\n", "4\n", "5\n"], [ + runMutationTest(5, ['1\n', '2\n', '3\n', '4\n', '5\n'], [ ['skip', 1], - ['remove', 7, 4, "\n2\n3\n4\n"], - ['skip', 2, 1] - ], ["15\n"]); + ['remove', 7, 4, '\n2\n3\n4\n'], + ['skip', 2, 1], + ], ['15\n']); - runMutationTest(6, ["123\n", "abc\n", "def\n", "ghi\n", "xyz\n"], [ - ['insert', "0"], + runMutationTest(6, ['123\n', 'abc\n', 'def\n', 'ghi\n', 'xyz\n'], [ + ['insert', '0'], ['skip', 4, 1], ['skip', 4, 1], - ['remove', 8, 2, "def\nghi\n"], - ['skip', 4, 1] - ], ["0123\n", "abc\n", "xyz\n"]); + ['remove', 8, 2, 'def\nghi\n'], + ['skip', 4, 1], + ], ['0123\n', 'abc\n', 'xyz\n']); - runMutationTest(7, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"], [ - ['remove', 6, 1, "apple\n"], + runMutationTest(7, ['apple\n', 'banana\n', 'cabbage\n', 'duffle\n', 'eggplant\n'], [ + ['remove', 6, 1, 'apple\n'], ['skip', 15, 2, true], ['skip', 6, 0, true], - ['remove', 1, 1, "\n"], - ['remove', 8, 0, "eggplant"], - ['skip', 1, 1, true] - ], ["banana\n", "cabbage\n", "duffle\n"]); + ['remove', 1, 1, '\n'], + ['remove', 8, 0, 'eggplant'], + ['skip', 1, 1, true], + ], ['banana\n', 'cabbage\n', 'duffle\n']); function poolOrArray(attribs) { if (attribs.getAttrib) { return attribs; // it's already an attrib pool } else { // assume it's an array of attrib strings to be split and added - var p = new AttributePool(); - attribs.forEach(function (kv) { + const p = new AttributePool(); + attribs.forEach((kv) => { p.putAttrib(kv.split(',')); }); return p; @@ -236,98 +234,97 @@ function runTests() { } function runApplyToAttributionTest(testId, attribs, cs, inAttr, outCorrect) { - print("> applyToAttribution#" + testId); - var p = poolOrArray(attribs); - var result = Changeset.applyToAttribution( - Changeset.checkRep(cs), inAttr, p); + print(`> applyToAttribution#${testId}`); + const p = poolOrArray(attribs); + const result = Changeset.applyToAttribution( + Changeset.checkRep(cs), inAttr, p); assertEqualStrings(outCorrect, result); } // turn cactus\n into actusabcd\n - runApplyToAttributionTest(1, ['bold,', 'bold,true'], "Z:7>3-1*0=1*1=1=3+4$abcd", "+1*1+1|1+5", "+1*1+1|1+8"); + runApplyToAttributionTest(1, ['bold,', 'bold,true'], 'Z:7>3-1*0=1*1=1=3+4$abcd', '+1*1+1|1+5', '+1*1+1|1+8'); // turn "david\ngreenspan\n" into "david\ngreen\n" - runApplyToAttributionTest(2, ['bold,', 'bold,true'], "Z:g<4*1|1=6*1=5-4$", "|2+g", "*1|1+6*1+5|1+1"); + runApplyToAttributionTest(2, ['bold,', 'bold,true'], 'Z:g<4*1|1=6*1=5-4$', '|2+g', '*1|1+6*1+5|1+1'); (function () { - print("> mutatorHasMore"); - var lines = ["1\n", "2\n", "3\n", "4\n"]; - var mu; + print('> mutatorHasMore'); + const lines = ['1\n', '2\n', '3\n', '4\n']; + let mu; mu = Changeset.textLinesMutator(lines); - assert(mu.hasMore() + ' == true'); + assert(`${mu.hasMore()} == true`); mu.skip(8, 4); - assert(mu.hasMore() + ' == false'); + assert(`${mu.hasMore()} == false`); mu.close(); - assert(mu.hasMore() + ' == false'); + assert(`${mu.hasMore()} == false`); // still 1,2,3,4 mu = Changeset.textLinesMutator(lines); - assert(mu.hasMore() + ' == true'); + assert(`${mu.hasMore()} == true`); mu.remove(2, 1); - assert(mu.hasMore() + ' == true'); + assert(`${mu.hasMore()} == true`); mu.skip(2, 1); - assert(mu.hasMore() + ' == true'); + assert(`${mu.hasMore()} == true`); mu.skip(2, 1); - assert(mu.hasMore() + ' == true'); + assert(`${mu.hasMore()} == true`); mu.skip(2, 1); - assert(mu.hasMore() + ' == false'); - mu.insert("5\n", 1); - assert(mu.hasMore() + ' == false'); + assert(`${mu.hasMore()} == false`); + mu.insert('5\n', 1); + assert(`${mu.hasMore()} == false`); mu.close(); - assert(mu.hasMore() + ' == false'); + assert(`${mu.hasMore()} == false`); // 2,3,4,5 now mu = Changeset.textLinesMutator(lines); - assert(mu.hasMore() + ' == true'); + assert(`${mu.hasMore()} == true`); mu.remove(6, 3); - assert(mu.hasMore() + ' == true'); + assert(`${mu.hasMore()} == true`); mu.remove(2, 1); - assert(mu.hasMore() + ' == false'); - mu.insert("hello\n", 1); - assert(mu.hasMore() + ' == false'); + assert(`${mu.hasMore()} == false`); + mu.insert('hello\n', 1); + assert(`${mu.hasMore()} == false`); mu.close(); - assert(mu.hasMore() + ' == false'); - + assert(`${mu.hasMore()} == false`); })(); function runMutateAttributionTest(testId, attribs, cs, alines, outCorrect) { - print("> runMutateAttributionTest#" + testId); - var p = poolOrArray(attribs); - var alines2 = Array.prototype.slice.call(alines); - var result = Changeset.mutateAttributionLines( - Changeset.checkRep(cs), alines2, p); + print(`> runMutateAttributionTest#${testId}`); + const p = poolOrArray(attribs); + const alines2 = Array.prototype.slice.call(alines); + const result = Changeset.mutateAttributionLines( + Changeset.checkRep(cs), alines2, p); assertEqualArrays(outCorrect, alines2); - print("> runMutateAttributionTest#" + testId + ".applyToAttribution"); + print(`> runMutateAttributionTest#${testId}.applyToAttribution`); function removeQuestionMarks(a) { return a.replace(/\?/g, ''); } - var inMerged = Changeset.joinAttributionLines(alines.map(removeQuestionMarks)); - var correctMerged = Changeset.joinAttributionLines(outCorrect.map(removeQuestionMarks)); - var mergedResult = Changeset.applyToAttribution(cs, inMerged, p); + const inMerged = Changeset.joinAttributionLines(alines.map(removeQuestionMarks)); + const correctMerged = Changeset.joinAttributionLines(outCorrect.map(removeQuestionMarks)); + const mergedResult = Changeset.applyToAttribution(cs, inMerged, p); assertEqualStrings(correctMerged, mergedResult); } // turn 123\n 456\n 789\n into 123\n 456\n 789\n - runMutateAttributionTest(1, ["bold,true"], "Z:c>0|1=4=1*0=1$", ["|1+4", "|1+4", "|1+4"], ["|1+4", "+1*0+1|1+2", "|1+4"]); + runMutateAttributionTest(1, ['bold,true'], 'Z:c>0|1=4=1*0=1$', ['|1+4', '|1+4', '|1+4'], ['|1+4', '+1*0+1|1+2', '|1+4']); // make a document bold - runMutateAttributionTest(2, ["bold,true"], "Z:c>0*0|3=c$", ["|1+4", "|1+4", "|1+4"], ["*0|1+4", "*0|1+4", "*0|1+4"]); + runMutateAttributionTest(2, ['bold,true'], 'Z:c>0*0|3=c$', ['|1+4', '|1+4', '|1+4'], ['*0|1+4', '*0|1+4', '*0|1+4']); // clear bold on document - runMutateAttributionTest(3, ["bold,", "bold,true"], "Z:c>0*0|3=c$", ["*1+1+1*1+1|1+1", "+1*1+1|1+2", "*1+1+1*1+1|1+1"], ["|1+4", "|1+4", "|1+4"]); + runMutateAttributionTest(3, ['bold,', 'bold,true'], 'Z:c>0*0|3=c$', ['*1+1+1*1+1|1+1', '+1*1+1|1+2', '*1+1+1*1+1|1+1'], ['|1+4', '|1+4', '|1+4']); // add a character on line 3 of a document with 5 blank lines, and make sure // the optimization that skips purely-kept lines is working; if any attribution string // with a '?' is parsed it will cause an error. - runMutateAttributionTest(4, ['foo,bar', 'line,1', 'line,2', 'line,3', 'line,4', 'line,5'], "Z:5>1|2=2+1$x", ["?*1|1+1", "?*2|1+1", "*3|1+1", "?*4|1+1", "?*5|1+1"], ["?*1|1+1", "?*2|1+1", "+1*3|1+1", "?*4|1+1", "?*5|1+1"]); + runMutateAttributionTest(4, ['foo,bar', 'line,1', 'line,2', 'line,3', 'line,4', 'line,5'], 'Z:5>1|2=2+1$x', ['?*1|1+1', '?*2|1+1', '*3|1+1', '?*4|1+1', '?*5|1+1'], ['?*1|1+1', '?*2|1+1', '+1*3|1+1', '?*4|1+1', '?*5|1+1']); - var testPoolWithChars = (function () { - var p = new AttributePool(); + const testPoolWithChars = (function () { + const p = new AttributePool(); p.putAttrib(['char', 'newline']); - for (var i = 1; i < 36; i++) { + for (let i = 1; i < 36; i++) { p.putAttrib(['char', Changeset.numToString(i)]); } p.putAttrib(['char', '']); @@ -335,38 +332,38 @@ function runTests() { })(); // based on runMutationTest#1 - runMutateAttributionTest(5, testPoolWithChars, "Z:11>7-2*t+1*u+1|2=b|2+a=2*b+1*o+1*t+1*0|1+1*b+1*u+1=3|1-3-6$" + "tucream\npie\nbot\nbu", ["*a+1*p+2*l+1*e+1*0|1+1", "*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1", "*c+1*a+1*b+2*a+1*g+1*e+1*0|1+1", "*d+1*u+1*f+2*l+1*e+1*0|1+1", "*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1"], ["*t+1*u+1*p+1*l+1*e+1*0|1+1", "*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1", "|1+6", "|1+4", "*c+1*a+1*b+1*o+1*t+1*0|1+1", "*b+1*u+1*b+2*a+1*0|1+1", "*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1"]); + runMutateAttributionTest(5, testPoolWithChars, 'Z:11>7-2*t+1*u+1|2=b|2+a=2*b+1*o+1*t+1*0|1+1*b+1*u+1=3|1-3-6$' + 'tucream\npie\nbot\nbu', ['*a+1*p+2*l+1*e+1*0|1+1', '*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1', '*c+1*a+1*b+2*a+1*g+1*e+1*0|1+1', '*d+1*u+1*f+2*l+1*e+1*0|1+1', '*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1'], ['*t+1*u+1*p+1*l+1*e+1*0|1+1', '*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1', '|1+6', '|1+4', '*c+1*a+1*b+1*o+1*t+1*0|1+1', '*b+1*u+1*b+2*a+1*0|1+1', '*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1']); // based on runMutationTest#3 - runMutateAttributionTest(6, testPoolWithChars, "Z:117=1|4+7$\n2\n3\n4\n", ["*1+1*5|1+2"], ["*1+1|1+1", "|1+2", "|1+2", "|1+2", "*5|1+2"]); + runMutateAttributionTest(7, testPoolWithChars, 'Z:3>7=1|4+7$\n2\n3\n4\n', ['*1+1*5|1+2'], ['*1+1|1+1', '|1+2', '|1+2', '|1+2', '*5|1+2']); // based on runMutationTest#5 - runMutateAttributionTest(8, testPoolWithChars, "Z:a<7=1|4-7$", ["*1|1+2", "*2|1+2", "*3|1+2", "*4|1+2", "*5|1+2"], ["*1+1*5|1+2"]); + runMutateAttributionTest(8, testPoolWithChars, 'Z:a<7=1|4-7$', ['*1|1+2', '*2|1+2', '*3|1+2', '*4|1+2', '*5|1+2'], ['*1+1*5|1+2']); // based on runMutationTest#6 - runMutateAttributionTest(9, testPoolWithChars, "Z:k<7*0+1*10|2=8|2-8$0", ["*1+1*2+1*3+1|1+1", "*a+1*b+1*c+1|1+1", "*d+1*e+1*f+1|1+1", "*g+1*h+1*i+1|1+1", "?*x+1*y+1*z+1|1+1"], ["*0+1|1+4", "|1+4", "?*x+1*y+1*z+1|1+1"]); + runMutateAttributionTest(9, testPoolWithChars, 'Z:k<7*0+1*10|2=8|2-8$0', ['*1+1*2+1*3+1|1+1', '*a+1*b+1*c+1|1+1', '*d+1*e+1*f+1|1+1', '*g+1*h+1*i+1|1+1', '?*x+1*y+1*z+1|1+1'], ['*0+1|1+4', '|1+4', '?*x+1*y+1*z+1|1+1']); - runMutateAttributionTest(10, testPoolWithChars, "Z:6>4=1+1=1+1|1=1+1=1*0+1$abcd", ["|1+3", "|1+3"], ["|1+5", "+2*0+1|1+2"]); + runMutateAttributionTest(10, testPoolWithChars, 'Z:6>4=1+1=1+1|1=1+1=1*0+1$abcd', ['|1+3', '|1+3'], ['|1+5', '+2*0+1|1+2']); - runMutateAttributionTest(11, testPoolWithChars, "Z:s>1|1=4=6|1+1$\n", ["*0|1+4", "*0|1+8", "*0+5|1+1", "*0|1+1", "*0|1+5", "*0|1+1", "*0|1+1", "*0|1+1", "|1+1"], ["*0|1+4", "*0+6|1+1", "*0|1+2", "*0+5|1+1", "*0|1+1", "*0|1+5", "*0|1+1", "*0|1+1", "*0|1+1", "|1+1"]); + runMutateAttributionTest(11, testPoolWithChars, 'Z:s>1|1=4=6|1+1$\n', ['*0|1+4', '*0|1+8', '*0+5|1+1', '*0|1+1', '*0|1+5', '*0|1+1', '*0|1+1', '*0|1+1', '|1+1'], ['*0|1+4', '*0+6|1+1', '*0|1+2', '*0+5|1+1', '*0|1+1', '*0|1+5', '*0|1+1', '*0|1+1', '*0|1+1', '|1+1']); function randomInlineString(len, rand) { - var assem = Changeset.stringAssembler(); - for (var i = 0; i < len; i++) { + const assem = Changeset.stringAssembler(); + for (let i = 0; i < len; i++) { assem.append(String.fromCharCode(rand.nextInt(26) + 97)); } return assem.toString(); } function randomMultiline(approxMaxLines, approxMaxCols, rand) { - var numParts = rand.nextInt(approxMaxLines * 2) + 1; - var txt = Changeset.stringAssembler(); + const numParts = rand.nextInt(approxMaxLines * 2) + 1; + const txt = Changeset.stringAssembler(); txt.append(rand.nextInt(2) ? '\n' : ''); - for (var i = 0; i < numParts; i++) { + for (let i = 0; i < numParts; i++) { if ((i % 2) == 0) { if (rand.nextInt(10)) { txt.append(randomInlineString(rand.nextInt(approxMaxCols) + 1, rand)); @@ -381,98 +378,98 @@ function runTests() { } function randomStringOperation(numCharsLeft, rand) { - var result; + let result; switch (rand.nextInt(9)) { - case 0: + case 0: { // insert char result = { - insert: randomInlineString(1, rand) + insert: randomInlineString(1, rand), }; break; } - case 1: + case 1: { // delete char result = { - remove: 1 + remove: 1, }; break; } - case 2: + case 2: { // skip char result = { - skip: 1 + skip: 1, }; break; } - case 3: + case 3: { // insert small result = { - insert: randomInlineString(rand.nextInt(4) + 1, rand) + insert: randomInlineString(rand.nextInt(4) + 1, rand), }; break; } - case 4: + case 4: { // delete small result = { - remove: rand.nextInt(4) + 1 + remove: rand.nextInt(4) + 1, }; break; } - case 5: + case 5: { // skip small result = { - skip: rand.nextInt(4) + 1 + skip: rand.nextInt(4) + 1, }; break; } - case 6: + case 6: { // insert multiline; result = { - insert: randomMultiline(5, 20, rand) + insert: randomMultiline(5, 20, rand), }; break; } - case 7: + case 7: { // delete multiline result = { - remove: Math.round(numCharsLeft * rand.nextDouble() * rand.nextDouble()) + remove: Math.round(numCharsLeft * rand.nextDouble() * rand.nextDouble()), }; break; } - case 8: + case 8: { // skip multiline result = { - skip: Math.round(numCharsLeft * rand.nextDouble() * rand.nextDouble()) + skip: Math.round(numCharsLeft * rand.nextDouble() * rand.nextDouble()), }; break; } - case 9: + case 9: { // delete to end result = { - remove: numCharsLeft + remove: numCharsLeft, }; break; } - case 10: + case 10: { // skip to end result = { - skip: numCharsLeft + skip: numCharsLeft, }; break; } } - var maxOrig = numCharsLeft - 1; + const maxOrig = numCharsLeft - 1; if ('remove' in result) { result.remove = Math.min(result.remove, maxOrig); } else if ('skip' in result) { @@ -487,34 +484,32 @@ function runTests() { return ''; } else if (rand.nextInt(3)) { if (opcode == '+' || rand.nextInt(2)) { - return '*' + Changeset.numToString(rand.nextInt(2) * 2 + 1); + return `*${Changeset.numToString(rand.nextInt(2) * 2 + 1)}`; } else { - return '*' + Changeset.numToString(rand.nextInt(2) * 2); + return `*${Changeset.numToString(rand.nextInt(2) * 2)}`; } + } else if (opcode == '+' || rand.nextInt(4) == 0) { + return '*1*3'; } else { - if (opcode == '+' || rand.nextInt(4) == 0) { - return '*1*3'; - } else { - return ['*0*2', '*0*3', '*1*2'][rand.nextInt(3)]; - } + return ['*0*2', '*0*3', '*1*2'][rand.nextInt(3)]; } } function randomTestChangeset(origText, rand, withAttribs) { - var charBank = Changeset.stringAssembler(); - var textLeft = origText; // always keep final newline - var outTextAssem = Changeset.stringAssembler(); - var opAssem = Changeset.smartOpAssembler(); - var oldLen = origText.length; + const charBank = Changeset.stringAssembler(); + let textLeft = origText; // always keep final newline + const outTextAssem = Changeset.stringAssembler(); + const opAssem = Changeset.smartOpAssembler(); + const oldLen = origText.length; - var nextOp = Changeset.newOp(); + const nextOp = Changeset.newOp(); function appendMultilineOp(opcode, txt) { nextOp.opcode = opcode; if (withAttribs) { nextOp.attribs = randomTwoPropAttribs(opcode, rand); } - txt.replace(/\n|[^\n]+/g, function (t) { + txt.replace(/\n|[^\n]+/g, (t) => { if (t == '\n') { nextOp.chars = 1; nextOp.lines = 1; @@ -529,7 +524,7 @@ function runTests() { } function doOp() { - var o = randomStringOperation(textLeft.length, rand); + const o = randomStringOperation(textLeft.length, rand); if (o.insert) { var txt = o.insert; charBank.append(txt); @@ -548,41 +543,41 @@ function runTests() { } while (textLeft.length > 1) doOp(); - for (var i = 0; i < 5; i++) doOp(); // do some more (only insertions will happen) - var outText = outTextAssem.toString() + '\n'; + for (let i = 0; i < 5; i++) doOp(); // do some more (only insertions will happen) + const outText = `${outTextAssem.toString()}\n`; opAssem.endDocument(); - var cs = Changeset.pack(oldLen, outText.length, opAssem.toString(), charBank.toString()); + const cs = Changeset.pack(oldLen, outText.length, opAssem.toString(), charBank.toString()); Changeset.checkRep(cs); return [cs, outText]; } function testCompose(randomSeed) { - var rand = new random(); - print("> testCompose#" + randomSeed); + const rand = new random(); + print(`> testCompose#${randomSeed}`); - var p = new AttributePool(); + const p = new AttributePool(); - var startText = randomMultiline(10, 20, rand) + '\n'; + const startText = `${randomMultiline(10, 20, rand)}\n`; - var x1 = randomTestChangeset(startText, rand); - var change1 = x1[0]; - var text1 = x1[1]; + const x1 = randomTestChangeset(startText, rand); + const change1 = x1[0]; + const text1 = x1[1]; - var x2 = randomTestChangeset(text1, rand); - var change2 = x2[0]; - var text2 = x2[1]; + const x2 = randomTestChangeset(text1, rand); + const change2 = x2[0]; + const text2 = x2[1]; - var x3 = randomTestChangeset(text2, rand); - var change3 = x3[0]; - var text3 = x3[1]; + const x3 = randomTestChangeset(text2, rand); + const change3 = x3[0]; + const text3 = x3[1]; - //print(literal(Changeset.toBaseTen(startText))); - //print(literal(Changeset.toBaseTen(change1))); - //print(literal(Changeset.toBaseTen(change2))); - var change12 = Changeset.checkRep(Changeset.compose(change1, change2, p)); - var change23 = Changeset.checkRep(Changeset.compose(change2, change3, p)); - var change123 = Changeset.checkRep(Changeset.compose(change12, change3, p)); - var change123a = Changeset.checkRep(Changeset.compose(change1, change23, p)); + // print(literal(Changeset.toBaseTen(startText))); + // print(literal(Changeset.toBaseTen(change1))); + // print(literal(Changeset.toBaseTen(change2))); + const change12 = Changeset.checkRep(Changeset.compose(change1, change2, p)); + const change23 = Changeset.checkRep(Changeset.compose(change2, change3, p)); + const change123 = Changeset.checkRep(Changeset.compose(change12, change3, p)); + const change123a = Changeset.checkRep(Changeset.compose(change1, change23, p)); assertEqualStrings(change123, change123a); assertEqualStrings(text2, Changeset.applyToText(change12, startText)); @@ -593,18 +588,18 @@ function runTests() { for (var i = 0; i < 30; i++) testCompose(i); (function simpleComposeAttributesTest() { - print("> simpleComposeAttributesTest"); - var p = new AttributePool(); + print('> simpleComposeAttributesTest'); + const p = new AttributePool(); p.putAttrib(['bold', '']); p.putAttrib(['bold', 'true']); - var cs1 = Changeset.checkRep("Z:2>1*1+1*1=1$x"); - var cs2 = Changeset.checkRep("Z:3>0*0|1=3$"); - var cs12 = Changeset.checkRep(Changeset.compose(cs1, cs2, p)); - assertEqualStrings("Z:2>1+1*0|1=2$x", cs12); + const cs1 = Changeset.checkRep('Z:2>1*1+1*1=1$x'); + const cs2 = Changeset.checkRep('Z:3>0*0|1=3$'); + const cs12 = Changeset.checkRep(Changeset.compose(cs1, cs2, p)); + assertEqualStrings('Z:2>1+1*0|1=2$x', cs12); })(); (function followAttributesTest() { - var p = new AttributePool(); + const p = new AttributePool(); p.putAttrib(['x', '']); p.putAttrib(['x', 'abc']); p.putAttrib(['x', 'def']); @@ -630,21 +625,21 @@ function runTests() { })(); function testFollow(randomSeed) { - var rand = new random(); - print("> testFollow#" + randomSeed); + const rand = new random(); + print(`> testFollow#${randomSeed}`); - var p = new AttributePool(); + const p = new AttributePool(); - var startText = randomMultiline(10, 20, rand) + '\n'; + const startText = `${randomMultiline(10, 20, rand)}\n`; - var cs1 = randomTestChangeset(startText, rand)[0]; - var cs2 = randomTestChangeset(startText, rand)[0]; + const cs1 = randomTestChangeset(startText, rand)[0]; + const cs2 = randomTestChangeset(startText, rand)[0]; - var afb = Changeset.checkRep(Changeset.follow(cs1, cs2, false, p)); - var bfa = Changeset.checkRep(Changeset.follow(cs2, cs1, true, p)); + const afb = Changeset.checkRep(Changeset.follow(cs1, cs2, false, p)); + const bfa = Changeset.checkRep(Changeset.follow(cs2, cs1, true, p)); - var merge1 = Changeset.checkRep(Changeset.compose(cs1, afb)); - var merge2 = Changeset.checkRep(Changeset.compose(cs2, bfa)); + const merge1 = Changeset.checkRep(Changeset.compose(cs1, afb)); + const merge2 = Changeset.checkRep(Changeset.compose(cs2, bfa)); assertEqualStrings(merge1, merge2); } @@ -652,26 +647,26 @@ function runTests() { for (var i = 0; i < 30; i++) testFollow(i); function testSplitJoinAttributionLines(randomSeed) { - var rand = new random(); - print("> testSplitJoinAttributionLines#" + randomSeed); + const rand = new random(); + print(`> testSplitJoinAttributionLines#${randomSeed}`); - var doc = randomMultiline(10, 20, rand) + '\n'; + const doc = `${randomMultiline(10, 20, rand)}\n`; function stringToOps(str) { - var assem = Changeset.mergingOpAssembler(); - var o = Changeset.newOp('+'); + const assem = Changeset.mergingOpAssembler(); + const o = Changeset.newOp('+'); o.chars = 1; - for (var i = 0; i < str.length; i++) { - var c = str.charAt(i); + for (let i = 0; i < str.length; i++) { + const c = str.charAt(i); o.lines = (c == '\n' ? 1 : 0); - o.attribs = (c == 'a' || c == 'b' ? '*' + c : ''); + o.attribs = (c == 'a' || c == 'b' ? `*${c}` : ''); assem.append(o); } return assem.toString(); } - var theJoined = stringToOps(doc); - var theSplit = doc.match(/[^\n]*\n/g).map(stringToOps); + const theJoined = stringToOps(doc); + const theSplit = doc.match(/[^\n]*\n/g).map(stringToOps); assertEqualArrays(theSplit, Changeset.splitAttributionLines(theJoined, doc)); assertEqualStrings(theJoined, Changeset.joinAttributionLines(theSplit)); @@ -680,10 +675,10 @@ function runTests() { for (var i = 0; i < 10; i++) testSplitJoinAttributionLines(i); (function testMoveOpsToNewPool() { - print("> testMoveOpsToNewPool"); + print('> testMoveOpsToNewPool'); - var pool1 = new AttributePool(); - var pool2 = new AttributePool(); + const pool1 = new AttributePool(); + const pool2 = new AttributePool(); pool1.putAttrib(['baz', 'qux']); pool1.putAttrib(['foo', 'bar']); @@ -696,249 +691,241 @@ function runTests() { (function testMakeSplice() { - print("> testMakeSplice"); - - var t = "a\nb\nc\n"; - var t2 = Changeset.applyToText(Changeset.makeSplice(t, 5, 0, "def"), t); - assertEqualStrings("a\nb\ncdef\n", t2); + print('> testMakeSplice'); + const t = 'a\nb\nc\n'; + const t2 = Changeset.applyToText(Changeset.makeSplice(t, 5, 0, 'def'), t); + assertEqualStrings('a\nb\ncdef\n', t2); })(); (function testToSplices() { - print("> testToSplices"); + print('> testToSplices'); - var cs = Changeset.checkRep('Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk'); - var correctSplices = [ - [5, 8, "123456789"], - [9, 17, "abcdefghijk"] + const cs = Changeset.checkRep('Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk'); + const correctSplices = [ + [5, 8, '123456789'], + [9, 17, 'abcdefghijk'], ]; assertEqualArrays(correctSplices, Changeset.toSplices(cs)); })(); function testCharacterRangeFollow(testId, cs, oldRange, insertionsAfter, correctNewRange) { - print("> testCharacterRangeFollow#" + testId); + print(`> testCharacterRangeFollow#${testId}`); var cs = Changeset.checkRep(cs); assertEqualArrays(correctNewRange, Changeset.characterRangeFollow(cs, oldRange[0], oldRange[1], insertionsAfter)); - } testCharacterRangeFollow(1, 'Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk', [7, 10], false, [14, 15]); - testCharacterRangeFollow(2, "Z:bc<6|x=b4|2-6$", [400, 407], false, [400, 401]); - testCharacterRangeFollow(3, "Z:4>0-3+3$abc", [0, 3], false, [3, 3]); - testCharacterRangeFollow(4, "Z:4>0-3+3$abc", [0, 3], true, [0, 0]); - testCharacterRangeFollow(5, "Z:5>1+1=1-3+3$abcd", [1, 4], false, [5, 5]); - testCharacterRangeFollow(6, "Z:5>1+1=1-3+3$abcd", [1, 4], true, [2, 2]); - testCharacterRangeFollow(7, "Z:5>1+1=1-3+3$abcd", [0, 6], false, [1, 7]); - testCharacterRangeFollow(8, "Z:5>1+1=1-3+3$abcd", [0, 3], false, [1, 2]); - testCharacterRangeFollow(9, "Z:5>1+1=1-3+3$abcd", [2, 5], false, [5, 6]); - testCharacterRangeFollow(10, "Z:2>1+1$a", [0, 0], false, [1, 1]); - testCharacterRangeFollow(11, "Z:2>1+1$a", [0, 0], true, [0, 0]); + testCharacterRangeFollow(2, 'Z:bc<6|x=b4|2-6$', [400, 407], false, [400, 401]); + testCharacterRangeFollow(3, 'Z:4>0-3+3$abc', [0, 3], false, [3, 3]); + testCharacterRangeFollow(4, 'Z:4>0-3+3$abc', [0, 3], true, [0, 0]); + testCharacterRangeFollow(5, 'Z:5>1+1=1-3+3$abcd', [1, 4], false, [5, 5]); + testCharacterRangeFollow(6, 'Z:5>1+1=1-3+3$abcd', [1, 4], true, [2, 2]); + testCharacterRangeFollow(7, 'Z:5>1+1=1-3+3$abcd', [0, 6], false, [1, 7]); + testCharacterRangeFollow(8, 'Z:5>1+1=1-3+3$abcd', [0, 3], false, [1, 2]); + testCharacterRangeFollow(9, 'Z:5>1+1=1-3+3$abcd', [2, 5], false, [5, 6]); + testCharacterRangeFollow(10, 'Z:2>1+1$a', [0, 0], false, [1, 1]); + testCharacterRangeFollow(11, 'Z:2>1+1$a', [0, 0], true, [0, 0]); (function testOpAttributeValue() { - print("> testOpAttributeValue"); + print('> testOpAttributeValue'); - var p = new AttributePool(); + const p = new AttributePool(); p.putAttrib(['name', 'david']); p.putAttrib(['color', 'green']); - assertEqualStrings("david", Changeset.opAttributeValue(Changeset.stringOp('*0*1+1'), 'name', p)); - assertEqualStrings("david", Changeset.opAttributeValue(Changeset.stringOp('*0+1'), 'name', p)); - assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('*1+1'), 'name', p)); - assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('+1'), 'name', p)); - assertEqualStrings("green", Changeset.opAttributeValue(Changeset.stringOp('*0*1+1'), 'color', p)); - assertEqualStrings("green", Changeset.opAttributeValue(Changeset.stringOp('*1+1'), 'color', p)); - assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('*0+1'), 'color', p)); - assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('+1'), 'color', p)); + assertEqualStrings('david', Changeset.opAttributeValue(Changeset.stringOp('*0*1+1'), 'name', p)); + assertEqualStrings('david', Changeset.opAttributeValue(Changeset.stringOp('*0+1'), 'name', p)); + assertEqualStrings('', Changeset.opAttributeValue(Changeset.stringOp('*1+1'), 'name', p)); + assertEqualStrings('', Changeset.opAttributeValue(Changeset.stringOp('+1'), 'name', p)); + assertEqualStrings('green', Changeset.opAttributeValue(Changeset.stringOp('*0*1+1'), 'color', p)); + assertEqualStrings('green', Changeset.opAttributeValue(Changeset.stringOp('*1+1'), 'color', p)); + assertEqualStrings('', Changeset.opAttributeValue(Changeset.stringOp('*0+1'), 'color', p)); + assertEqualStrings('', Changeset.opAttributeValue(Changeset.stringOp('+1'), 'color', p)); })(); function testAppendATextToAssembler(testId, atext, correctOps) { - print("> testAppendATextToAssembler#" + testId); + print(`> testAppendATextToAssembler#${testId}`); - var assem = Changeset.smartOpAssembler(); + const assem = Changeset.smartOpAssembler(); Changeset.appendATextToAssembler(atext, assem); assertEqualStrings(correctOps, assem.toString()); } testAppendATextToAssembler(1, { - text: "\n", - attribs: "|1+1" - }, ""); + text: '\n', + attribs: '|1+1', + }, ''); testAppendATextToAssembler(2, { - text: "\n\n", - attribs: "|2+2" - }, "|1+1"); + text: '\n\n', + attribs: '|2+2', + }, '|1+1'); testAppendATextToAssembler(3, { - text: "\n\n", - attribs: "*x|2+2" - }, "*x|1+1"); + text: '\n\n', + attribs: '*x|2+2', + }, '*x|1+1'); testAppendATextToAssembler(4, { - text: "\n\n", - attribs: "*x|1+1|1+1" - }, "*x|1+1"); + text: '\n\n', + attribs: '*x|1+1|1+1', + }, '*x|1+1'); testAppendATextToAssembler(5, { - text: "foo\n", - attribs: "|1+4" - }, "+3"); + text: 'foo\n', + attribs: '|1+4', + }, '+3'); testAppendATextToAssembler(6, { - text: "\nfoo\n", - attribs: "|2+5" - }, "|1+1+3"); + text: '\nfoo\n', + attribs: '|2+5', + }, '|1+1+3'); testAppendATextToAssembler(7, { - text: "\nfoo\n", - attribs: "*x|2+5" - }, "*x|1+1*x+3"); + text: '\nfoo\n', + attribs: '*x|2+5', + }, '*x|1+1*x+3'); testAppendATextToAssembler(8, { - text: "\n\n\nfoo\n", - attribs: "|2+2*x|2+5" - }, "|2+2*x|1+1*x+3"); + text: '\n\n\nfoo\n', + attribs: '|2+2*x|2+5', + }, '|2+2*x|1+1*x+3'); function testMakeAttribsString(testId, pool, opcode, attribs, correctString) { - print("> testMakeAttribsString#" + testId); + print(`> testMakeAttribsString#${testId}`); - var p = poolOrArray(pool); - var str = Changeset.makeAttribsString(opcode, attribs, p); + const p = poolOrArray(pool); + const str = Changeset.makeAttribsString(opcode, attribs, p); assertEqualStrings(correctString, str); } testMakeAttribsString(1, ['bold,'], '+', [ - ['bold', ''] + ['bold', ''], ], ''); testMakeAttribsString(2, ['abc,def', 'bold,'], '=', [ - ['bold', ''] + ['bold', ''], ], '*1'); testMakeAttribsString(3, ['abc,def', 'bold,true'], '+', [ ['abc', 'def'], - ['bold', 'true'] + ['bold', 'true'], ], '*0*1'); testMakeAttribsString(4, ['abc,def', 'bold,true'], '+', [ ['bold', 'true'], - ['abc', 'def'] + ['abc', 'def'], ], '*0*1'); function testSubattribution(testId, astr, start, end, correctOutput) { - print("> testSubattribution#" + testId); + print(`> testSubattribution#${testId}`); - var str = Changeset.subattribution(astr, start, end); + const str = Changeset.subattribution(astr, start, end); assertEqualStrings(correctOutput, str); } - testSubattribution(1, "+1", 0, 0, ""); - testSubattribution(2, "+1", 0, 1, "+1"); - testSubattribution(3, "+1", 0, undefined, "+1"); - testSubattribution(4, "|1+1", 0, 0, ""); - testSubattribution(5, "|1+1", 0, 1, "|1+1"); - testSubattribution(6, "|1+1", 0, undefined, "|1+1"); - testSubattribution(7, "*0+1", 0, 0, ""); - testSubattribution(8, "*0+1", 0, 1, "*0+1"); - testSubattribution(9, "*0+1", 0, undefined, "*0+1"); - testSubattribution(10, "*0|1+1", 0, 0, ""); - testSubattribution(11, "*0|1+1", 0, 1, "*0|1+1"); - testSubattribution(12, "*0|1+1", 0, undefined, "*0|1+1"); - testSubattribution(13, "*0+2+1*1+3", 0, 1, "*0+1"); - testSubattribution(14, "*0+2+1*1+3", 0, 2, "*0+2"); - testSubattribution(15, "*0+2+1*1+3", 0, 3, "*0+2+1"); - testSubattribution(16, "*0+2+1*1+3", 0, 4, "*0+2+1*1+1"); - testSubattribution(17, "*0+2+1*1+3", 0, 5, "*0+2+1*1+2"); - testSubattribution(18, "*0+2+1*1+3", 0, 6, "*0+2+1*1+3"); - testSubattribution(19, "*0+2+1*1+3", 0, 7, "*0+2+1*1+3"); - testSubattribution(20, "*0+2+1*1+3", 0, undefined, "*0+2+1*1+3"); - testSubattribution(21, "*0+2+1*1+3", 1, undefined, "*0+1+1*1+3"); - testSubattribution(22, "*0+2+1*1+3", 2, undefined, "+1*1+3"); - testSubattribution(23, "*0+2+1*1+3", 3, undefined, "*1+3"); - testSubattribution(24, "*0+2+1*1+3", 4, undefined, "*1+2"); - testSubattribution(25, "*0+2+1*1+3", 5, undefined, "*1+1"); - testSubattribution(26, "*0+2+1*1+3", 6, undefined, ""); - testSubattribution(27, "*0+2+1*1|1+3", 0, 1, "*0+1"); - testSubattribution(28, "*0+2+1*1|1+3", 0, 2, "*0+2"); - testSubattribution(29, "*0+2+1*1|1+3", 0, 3, "*0+2+1"); - testSubattribution(30, "*0+2+1*1|1+3", 0, 4, "*0+2+1*1+1"); - testSubattribution(31, "*0+2+1*1|1+3", 0, 5, "*0+2+1*1+2"); - testSubattribution(32, "*0+2+1*1|1+3", 0, 6, "*0+2+1*1|1+3"); - testSubattribution(33, "*0+2+1*1|1+3", 0, 7, "*0+2+1*1|1+3"); - testSubattribution(34, "*0+2+1*1|1+3", 0, undefined, "*0+2+1*1|1+3"); - testSubattribution(35, "*0+2+1*1|1+3", 1, undefined, "*0+1+1*1|1+3"); - testSubattribution(36, "*0+2+1*1|1+3", 2, undefined, "+1*1|1+3"); - testSubattribution(37, "*0+2+1*1|1+3", 3, undefined, "*1|1+3"); - testSubattribution(38, "*0+2+1*1|1+3", 4, undefined, "*1|1+2"); - testSubattribution(39, "*0+2+1*1|1+3", 5, undefined, "*1|1+1"); - testSubattribution(40, "*0+2+1*1|1+3", 1, 5, "*0+1+1*1+2"); - testSubattribution(41, "*0+2+1*1|1+3", 2, 6, "+1*1|1+3"); - testSubattribution(42, "*0+2+1*1+3", 2, 6, "+1*1+3"); + testSubattribution(1, '+1', 0, 0, ''); + testSubattribution(2, '+1', 0, 1, '+1'); + testSubattribution(3, '+1', 0, undefined, '+1'); + testSubattribution(4, '|1+1', 0, 0, ''); + testSubattribution(5, '|1+1', 0, 1, '|1+1'); + testSubattribution(6, '|1+1', 0, undefined, '|1+1'); + testSubattribution(7, '*0+1', 0, 0, ''); + testSubattribution(8, '*0+1', 0, 1, '*0+1'); + testSubattribution(9, '*0+1', 0, undefined, '*0+1'); + testSubattribution(10, '*0|1+1', 0, 0, ''); + testSubattribution(11, '*0|1+1', 0, 1, '*0|1+1'); + testSubattribution(12, '*0|1+1', 0, undefined, '*0|1+1'); + testSubattribution(13, '*0+2+1*1+3', 0, 1, '*0+1'); + testSubattribution(14, '*0+2+1*1+3', 0, 2, '*0+2'); + testSubattribution(15, '*0+2+1*1+3', 0, 3, '*0+2+1'); + testSubattribution(16, '*0+2+1*1+3', 0, 4, '*0+2+1*1+1'); + testSubattribution(17, '*0+2+1*1+3', 0, 5, '*0+2+1*1+2'); + testSubattribution(18, '*0+2+1*1+3', 0, 6, '*0+2+1*1+3'); + testSubattribution(19, '*0+2+1*1+3', 0, 7, '*0+2+1*1+3'); + testSubattribution(20, '*0+2+1*1+3', 0, undefined, '*0+2+1*1+3'); + testSubattribution(21, '*0+2+1*1+3', 1, undefined, '*0+1+1*1+3'); + testSubattribution(22, '*0+2+1*1+3', 2, undefined, '+1*1+3'); + testSubattribution(23, '*0+2+1*1+3', 3, undefined, '*1+3'); + testSubattribution(24, '*0+2+1*1+3', 4, undefined, '*1+2'); + testSubattribution(25, '*0+2+1*1+3', 5, undefined, '*1+1'); + testSubattribution(26, '*0+2+1*1+3', 6, undefined, ''); + testSubattribution(27, '*0+2+1*1|1+3', 0, 1, '*0+1'); + testSubattribution(28, '*0+2+1*1|1+3', 0, 2, '*0+2'); + testSubattribution(29, '*0+2+1*1|1+3', 0, 3, '*0+2+1'); + testSubattribution(30, '*0+2+1*1|1+3', 0, 4, '*0+2+1*1+1'); + testSubattribution(31, '*0+2+1*1|1+3', 0, 5, '*0+2+1*1+2'); + testSubattribution(32, '*0+2+1*1|1+3', 0, 6, '*0+2+1*1|1+3'); + testSubattribution(33, '*0+2+1*1|1+3', 0, 7, '*0+2+1*1|1+3'); + testSubattribution(34, '*0+2+1*1|1+3', 0, undefined, '*0+2+1*1|1+3'); + testSubattribution(35, '*0+2+1*1|1+3', 1, undefined, '*0+1+1*1|1+3'); + testSubattribution(36, '*0+2+1*1|1+3', 2, undefined, '+1*1|1+3'); + testSubattribution(37, '*0+2+1*1|1+3', 3, undefined, '*1|1+3'); + testSubattribution(38, '*0+2+1*1|1+3', 4, undefined, '*1|1+2'); + testSubattribution(39, '*0+2+1*1|1+3', 5, undefined, '*1|1+1'); + testSubattribution(40, '*0+2+1*1|1+3', 1, 5, '*0+1+1*1+2'); + testSubattribution(41, '*0+2+1*1|1+3', 2, 6, '+1*1|1+3'); + testSubattribution(42, '*0+2+1*1+3', 2, 6, '+1*1+3'); function testFilterAttribNumbers(testId, cs, filter, correctOutput) { - print("> testFilterAttribNumbers#" + testId); + print(`> testFilterAttribNumbers#${testId}`); - var str = Changeset.filterAttribNumbers(cs, filter); + const str = Changeset.filterAttribNumbers(cs, filter); assertEqualStrings(correctOutput, str); } - testFilterAttribNumbers(1, "*0*1+1+2+3*1+4*2+5*0*2*1*b*c+6", function (n) { - return (n % 2) == 0; - }, "*0+1+2+3+4*2+5*0*2*c+6"); - testFilterAttribNumbers(2, "*0*1+1+2+3*1+4*2+5*0*2*1*b*c+6", function (n) { - return (n % 2) == 1; - }, "*1+1+2+3*1+4+5*1*b+6"); + testFilterAttribNumbers(1, '*0*1+1+2+3*1+4*2+5*0*2*1*b*c+6', (n) => (n % 2) == 0, '*0+1+2+3+4*2+5*0*2*c+6'); + testFilterAttribNumbers(2, '*0*1+1+2+3*1+4*2+5*0*2*1*b*c+6', (n) => (n % 2) == 1, '*1+1+2+3*1+4+5*1*b+6'); function testInverse(testId, cs, lines, alines, pool, correctOutput) { - print("> testInverse#" + testId); + print(`> testInverse#${testId}`); pool = poolOrArray(pool); - var str = Changeset.inverse(Changeset.checkRep(cs), lines, alines, pool); + const str = Changeset.inverse(Changeset.checkRep(cs), lines, alines, pool); assertEqualStrings(correctOutput, str); } // take "FFFFTTTTT" and apply "-FT--FFTT", the inverse of which is "--F--TT--" - testInverse(1, "Z:9>0=1*0=1*1=1=2*0=2*1|1=2$", null, ["+4*1+5"], ['bold,', 'bold,true'], "Z:9>0=2*0=1=2*1=2$"); + testInverse(1, 'Z:9>0=1*0=1*1=1=2*0=2*1|1=2$', null, ['+4*1+5'], ['bold,', 'bold,true'], 'Z:9>0=2*0=1=2*1=2$'); function testMutateTextLines(testId, cs, lines, correctLines) { - print("> testMutateTextLines#" + testId); + print(`> testMutateTextLines#${testId}`); - var a = lines.slice(); + const a = lines.slice(); Changeset.mutateTextLines(cs, a); assertEqualArrays(correctLines, a); } - testMutateTextLines(1, "Z:4<1|1-2-1|1+1+1$\nc", ["a\n", "b\n"], ["\n", "c\n"]); - testMutateTextLines(2, "Z:4>0|1-2-1|2+3$\nc\n", ["a\n", "b\n"], ["\n", "c\n", "\n"]); + testMutateTextLines(1, 'Z:4<1|1-2-1|1+1+1$\nc', ['a\n', 'b\n'], ['\n', 'c\n']); + testMutateTextLines(2, 'Z:4>0|1-2-1|2+3$\nc\n', ['a\n', 'b\n'], ['\n', 'c\n', '\n']); function testInverseRandom(randomSeed) { - var rand = new random(); - print("> testInverseRandom#" + randomSeed); + const rand = new random(); + print(`> testInverseRandom#${randomSeed}`); - var p = poolOrArray(['apple,', 'apple,true', 'banana,', 'banana,true']); + const p = poolOrArray(['apple,', 'apple,true', 'banana,', 'banana,true']); - var startText = randomMultiline(10, 20, rand) + '\n'; - var alines = Changeset.splitAttributionLines(Changeset.makeAttribution(startText), startText); - var lines = startText.slice(0, -1).split('\n').map(function (s) { - return s + '\n'; - }); + const startText = `${randomMultiline(10, 20, rand)}\n`; + const alines = Changeset.splitAttributionLines(Changeset.makeAttribution(startText), startText); + const lines = startText.slice(0, -1).split('\n').map((s) => `${s}\n`); - var stylifier = randomTestChangeset(startText, rand, true)[0]; + const stylifier = randomTestChangeset(startText, rand, true)[0]; - //print(alines.join('\n')); + // print(alines.join('\n')); Changeset.mutateAttributionLines(stylifier, alines, p); - //print(stylifier); - //print(alines.join('\n')); + // print(stylifier); + // print(alines.join('\n')); Changeset.mutateTextLines(stylifier, lines); - var changeset = randomTestChangeset(lines.join(''), rand, true)[0]; - var inverseChangeset = Changeset.inverse(changeset, lines, alines, p); + const changeset = randomTestChangeset(lines.join(''), rand, true)[0]; + const inverseChangeset = Changeset.inverse(changeset, lines, alines, p); - var origLines = lines.slice(); - var origALines = alines.slice(); + const origLines = lines.slice(); + const origALines = alines.slice(); Changeset.mutateTextLines(changeset, lines); Changeset.mutateAttributionLines(changeset, alines, p); - //print(origALines.join('\n')); - //print(changeset); - //print(inverseChangeset); - //print(origLines.map(function(s) { return '1: '+s.slice(0,-1); }).join('\n')); - //print(lines.map(function(s) { return '2: '+s.slice(0,-1); }).join('\n')); - //print(alines.join('\n')); + // print(origALines.join('\n')); + // print(changeset); + // print(inverseChangeset); + // print(origLines.map(function(s) { return '1: '+s.slice(0,-1); }).join('\n')); + // print(lines.map(function(s) { return '2: '+s.slice(0,-1); }).join('\n')); + // print(alines.join('\n')); Changeset.mutateTextLines(inverseChangeset, lines); Changeset.mutateAttributionLines(inverseChangeset, alines, p); - //print(lines.map(function(s) { return '3: '+s.slice(0,-1); }).join('\n')); + // print(lines.map(function(s) { return '3: '+s.slice(0,-1); }).join('\n')); assertEqualArrays(origLines, lines); assertEqualArrays(origALines, alines); } diff --git a/src/node/eejs/index.js b/src/node/eejs/index.js index 057c24c75..3e30200f2 100644 --- a/src/node/eejs/index.js +++ b/src/node/eejs/index.js @@ -19,84 +19,84 @@ * require("./index").require("./examples/foo.ejs") */ -var ejs = require("ejs"); -var fs = require("fs"); -var path = require("path"); -var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js"); -var resolve = require("resolve"); -var settings = require('../utils/Settings'); +const ejs = require('ejs'); +const fs = require('fs'); +const path = require('path'); +const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js'); +const resolve = require('resolve'); +const settings = require('../utils/Settings'); -const templateCache = new Map() +const templateCache = new Map(); exports.info = { __output_stack: [], block_stack: [], file_stack: [], - args: [] + args: [], }; 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) { - return getCurrentFile().path + '|' + name; + return `${getCurrentFile().path}|${name}`; } exports._init = function (b, recursive) { exports.info.__output_stack.push(exports.info.__output); exports.info.__output = b; -} +}; exports._exit = function (b, recursive) { - getCurrentFile().inherit.forEach(function (item) { + getCurrentFile().inherit.forEach((item) => { exports._require(item.name, item.args); }); 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.splice(0, exports.info.__output.length); -} +}; exports.end_capture = function () { - var res = exports.info.__output.join(""); + const res = exports.info.__output.join(''); exports.info.__output.splice.apply( - exports.info.__output, - [0, exports.info.__output.length].concat(exports.info.__output_stack.pop())); + exports.info.__output, + [0, exports.info.__output.length].concat(exports.info.__output_stack.pop())); return res; -} +}; exports.begin_define_block = function (name) { exports.info.block_stack.push(name); exports.begin_capture(); -} +}; exports.end_define_block = function () { - var content = exports.end_capture(); + const content = exports.end_capture(); return content; -} +}; exports.end_block = function () { - var name = exports.info.block_stack.pop(); - var renderContext = exports.info.args[exports.info.args.length-1]; - var args = {content: exports.end_define_block(), renderContext: renderContext}; - hooks.callAll("eejsBlock_" + name, args); + const name = exports.info.block_stack.pop(); + const renderContext = exports.info.args[exports.info.args.length - 1]; + const args = {content: exports.end_define_block(), renderContext}; + hooks.callAll(`eejsBlock_${name}`, args); exports.info.__output.push(args.content); -} +}; exports.begin_block = exports.begin_define_block; exports.inherit = function (name, args) { - getCurrentFile().inherit.push({name:name, args:args}); -} + getCurrentFile().inherit.push({name, args}); +}; exports.require = function (name, args, mod) { if (args == undefined) args = {}; - var basedir = __dirname; - var paths = []; + let basedir = __dirname; + let paths = []; if (exports.info.file_stack.length) { basedir = path.dirname(getCurrentFile().path); @@ -106,43 +106,43 @@ exports.require = function (name, args, mod) { paths = mod.paths; } - var ejspath = resolve.sync( - name, - { - paths : paths, - basedir : basedir, - extensions : [ '.html', '.ejs' ], - } - ) + const ejspath = resolve.sync( + name, + { + paths, + basedir, + extensions: ['.html', '.ejs'], + }, + ); args.e = exports; args.require = require; - let template - if (settings.maxAge !== 0){ // don't cache if maxAge is 0 + let template; + if (settings.maxAge !== 0) { // don't cache if maxAge is 0 if (!templateCache.has(ejspath)) { - template = '<% e._init(__output); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>'; - templateCache.set(ejspath, template) + template = `<% e._init(__output); %>${fs.readFileSync(ejspath).toString()}<% e._exit(); %>`; + templateCache.set(ejspath, template); } else { - template = templateCache.get(ejspath) + template = templateCache.get(ejspath); } - }else{ - template = '<% e._init(__output); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>'; + } else { + template = `<% e._init(__output); %>${fs.readFileSync(ejspath).toString()}<% e._exit(); %>`; } exports.info.args.push(args); exports.info.file_stack.push({path: ejspath, inherit: []}); - if(settings.maxAge !== 0){ - var res = ejs.render(template, args, { cache: true, filename: ejspath }); - }else{ - var res = ejs.render(template, args, { cache: false, filename: ejspath }); + if (settings.maxAge !== 0) { + var res = ejs.render(template, args, {cache: true, filename: ejspath}); + } else { + var res = ejs.render(template, args, {cache: false, filename: ejspath}); } exports.info.file_stack.pop(); exports.info.args.pop(); return res; -} +}; exports._require = function (name, args) { exports.info.__output.push(exports.require(name, args)); -} +}; diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index 6a134553a..242e4e873 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -18,131 +18,118 @@ * limitations under the License. */ -var absolutePaths = require('../utils/AbsolutePaths'); -var fs = require("fs"); -var api = require("../db/API"); -var log4js = require('log4js'); -var padManager = require("../db/PadManager"); -var randomString = require("../utils/randomstring"); -var argv = require('../utils/Cli').argv; -var createHTTPError = require('http-errors'); +const absolutePaths = require('../utils/AbsolutePaths'); +const fs = require('fs'); +const api = require('../db/API'); +const log4js = require('log4js'); +const padManager = require('../db/PadManager'); +const randomString = require('../utils/randomstring'); +const argv = require('../utils/Cli').argv; +const createHTTPError = require('http-errors'); -var apiHandlerLogger = log4js.getLogger('APIHandler'); +const apiHandlerLogger = log4js.getLogger('APIHandler'); -//ensure we have an apikey -var apikey = null; -var apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || "./APIKEY.txt"); +// ensure we have an apikey +let apikey = null; +const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt'); try { - apikey = fs.readFileSync(apikeyFilename,"utf8"); + apikey = fs.readFileSync(apikeyFilename, 'utf8'); apiHandlerLogger.info(`Api key file read from: "${apikeyFilename}"`); -} catch(e) { +} catch (e) { apiHandlerLogger.info(`Api key file "${apikeyFilename}" not found. Creating with random contents.`); apikey = randomString(32); - fs.writeFileSync(apikeyFilename,apikey,"utf8"); + fs.writeFileSync(apikeyFilename, apikey, 'utf8'); } // a list of all functions -var version = {}; +const version = {}; -version["1"] = Object.assign({}, - { "createGroup" : [] - , "createGroupIfNotExistsFor" : ["groupMapper"] - , "deleteGroup" : ["groupID"] - , "listPads" : ["groupID"] - , "createPad" : ["padID", "text"] - , "createGroupPad" : ["groupID", "padName", "text"] - , "createAuthor" : ["name"] - , "createAuthorIfNotExistsFor": ["authorMapper" , "name"] - , "listPadsOfAuthor" : ["authorID"] - , "createSession" : ["groupID", "authorID", "validUntil"] - , "deleteSession" : ["sessionID"] - , "getSessionInfo" : ["sessionID"] - , "listSessionsOfGroup" : ["groupID"] - , "listSessionsOfAuthor" : ["authorID"] - , "getText" : ["padID", "rev"] - , "setText" : ["padID", "text"] - , "getHTML" : ["padID", "rev"] - , "setHTML" : ["padID", "html"] - , "getRevisionsCount" : ["padID"] - , "getLastEdited" : ["padID"] - , "deletePad" : ["padID"] - , "getReadOnlyID" : ["padID"] - , "setPublicStatus" : ["padID", "publicStatus"] - , "getPublicStatus" : ["padID"] - , "listAuthorsOfPad" : ["padID"] - , "padUsersCount" : ["padID"] - } +version['1'] = Object.assign({}, + {createGroup: [], + createGroupIfNotExistsFor: ['groupMapper'], + deleteGroup: ['groupID'], + listPads: ['groupID'], + createPad: ['padID', 'text'], + createGroupPad: ['groupID', 'padName', 'text'], + createAuthor: ['name'], + createAuthorIfNotExistsFor: ['authorMapper', 'name'], + listPadsOfAuthor: ['authorID'], + createSession: ['groupID', 'authorID', 'validUntil'], + deleteSession: ['sessionID'], + getSessionInfo: ['sessionID'], + listSessionsOfGroup: ['groupID'], + listSessionsOfAuthor: ['authorID'], + getText: ['padID', 'rev'], + setText: ['padID', 'text'], + getHTML: ['padID', 'rev'], + setHTML: ['padID', 'html'], + getRevisionsCount: ['padID'], + getLastEdited: ['padID'], + deletePad: ['padID'], + getReadOnlyID: ['padID'], + setPublicStatus: ['padID', 'publicStatus'], + getPublicStatus: ['padID'], + listAuthorsOfPad: ['padID'], + padUsersCount: ['padID']}, ); -version["1.1"] = Object.assign({}, version["1"], - { "getAuthorName" : ["authorID"] - , "padUsers" : ["padID"] - , "sendClientsMessage" : ["padID", "msg"] - , "listAllGroups" : [] - } +version['1.1'] = Object.assign({}, version['1'], + {getAuthorName: ['authorID'], + padUsers: ['padID'], + sendClientsMessage: ['padID', 'msg'], + listAllGroups: []}, ); -version["1.2"] = Object.assign({}, version["1.1"], - { "checkToken" : [] - } +version['1.2'] = Object.assign({}, version['1.1'], + {checkToken: []}, ); -version["1.2.1"] = Object.assign({}, version["1.2"], - { "listAllPads" : [] - } +version['1.2.1'] = Object.assign({}, version['1.2'], + {listAllPads: []}, ); -version["1.2.7"] = Object.assign({}, version["1.2.1"], - { "createDiffHTML" : ["padID", "startRev", "endRev"] - , "getChatHistory" : ["padID", "start", "end"] - , "getChatHead" : ["padID"] - } +version['1.2.7'] = Object.assign({}, version['1.2.1'], + {createDiffHTML: ['padID', 'startRev', 'endRev'], + getChatHistory: ['padID', 'start', 'end'], + getChatHead: ['padID']}, ); -version["1.2.8"] = Object.assign({}, version["1.2.7"], - { "getAttributePool" : ["padID"] - , "getRevisionChangeset" : ["padID", "rev"] - } +version['1.2.8'] = Object.assign({}, version['1.2.7'], + {getAttributePool: ['padID'], + getRevisionChangeset: ['padID', 'rev']}, ); -version["1.2.9"] = Object.assign({}, version["1.2.8"], - { "copyPad" : ["sourceID", "destinationID", "force"] - , "movePad" : ["sourceID", "destinationID", "force"] - } +version['1.2.9'] = Object.assign({}, version['1.2.8'], + {copyPad: ['sourceID', 'destinationID', 'force'], + movePad: ['sourceID', 'destinationID', 'force']}, ); -version["1.2.10"] = Object.assign({}, version["1.2.9"], - { "getPadID" : ["roID"] - } +version['1.2.10'] = Object.assign({}, version['1.2.9'], + {getPadID: ['roID']}, ); -version["1.2.11"] = Object.assign({}, version["1.2.10"], - { "getSavedRevisionsCount" : ["padID"] - , "listSavedRevisions" : ["padID"] - , "saveRevision" : ["padID", "rev"] - , "restoreRevision" : ["padID", "rev"] - } +version['1.2.11'] = Object.assign({}, version['1.2.10'], + {getSavedRevisionsCount: ['padID'], + listSavedRevisions: ['padID'], + saveRevision: ['padID', 'rev'], + restoreRevision: ['padID', 'rev']}, ); -version["1.2.12"] = Object.assign({}, version["1.2.11"], - { "appendChatMessage" : ["padID", "text", "authorID", "time"] - } +version['1.2.12'] = Object.assign({}, version['1.2.11'], + {appendChatMessage: ['padID', 'text', 'authorID', 'time']}, ); -version["1.2.13"] = Object.assign({}, version["1.2.12"], - { "appendText" : ["padID", "text"] - } +version['1.2.13'] = Object.assign({}, version['1.2.12'], + {appendText: ['padID', 'text']}, ); -version["1.2.14"] = Object.assign({}, version["1.2.13"], - { "getStats" : [] - } +version['1.2.14'] = Object.assign({}, version['1.2.13'], + {getStats: []}, ); -version["1.2.15"] = Object.assign({}, version["1.2.14"], - { "copyPadWithoutHistory" : ["sourceID", "destinationID", "force"] - } +version['1.2.15'] = Object.assign({}, version['1.2.14'], + {copyPadWithoutHistory: ['sourceID', 'destinationID', 'force']}, ); // set the latest available API version here @@ -158,7 +145,7 @@ exports.version = version; * @req express request 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 if (!(apiVersion in 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! - 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'); } // sanitize any padIDs before continuing - if (fields["padID"]) { - fields["padID"] = await padManager.sanitizePadId(fields["padID"]); + if (fields.padID) { + fields.padID = await padManager.sanitizePadId(fields.padID); } // there was an 'else' here before - removed it to ensure // that this sanitize step can't be circumvented by forcing // the first branch to be taken - if (fields["padName"]) { - fields["padName"] = await padManager.sanitizePadId(fields["padName"]); + if (fields.padName) { + fields.padName = await padManager.sanitizePadId(fields.padName); } // put the function parameters in an array - var functionParams = version[apiVersion][functionName].map(function (field) { - return fields[field] - }); + const functionParams = version[apiVersion][functionName].map((field) => fields[field]); // call the api function return api[functionName].apply(this, functionParams); -} +}; exports.exportedForTestingOnly = { apiKey: apikey, diff --git a/src/node/handler/ExportHandler.js b/src/node/handler/ExportHandler.js index 990426dbb..0a92633f7 100644 --- a/src/node/handler/ExportHandler.js +++ b/src/node/handler/ExportHandler.js @@ -19,15 +19,15 @@ * limitations under the License. */ -var exporthtml = require("../utils/ExportHtml"); -var exporttxt = require("../utils/ExportTxt"); -var exportEtherpad = require("../utils/ExportEtherpad"); -var fs = require("fs"); -var settings = require('../utils/Settings'); -var os = require('os'); -var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); -var TidyHtml = require('../utils/TidyHtml'); -const util = require("util"); +const exporthtml = require('../utils/ExportHtml'); +const exporttxt = require('../utils/ExportTxt'); +const exportEtherpad = require('../utils/ExportEtherpad'); +const fs = require('fs'); +const settings = require('../utils/Settings'); +const os = require('os'); +const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); +const TidyHtml = require('../utils/TidyHtml'); +const util = require('util'); const fsp_writeFile = util.promisify(fs.writeFile); const fsp_unlink = util.promisify(fs.unlink); @@ -36,12 +36,12 @@ let convertor = null; // load abiword only if it is enabled if (settings.abiword != null) { - convertor = require("../utils/Abiword"); + convertor = require('../utils/Abiword'); } // Use LibreOffice if an executable has been defined in the settings if (settings.soffice != null) { - convertor = require("../utils/LibreOffice"); + convertor = require('../utils/LibreOffice'); } const tempDirectory = os.tmpdir(); @@ -51,10 +51,10 @@ const tempDirectory = os.tmpdir(); */ async function doExport(req, res, padId, readOnlyId, type) { // 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 - 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 (hookFileName.length) { @@ -62,15 +62,15 @@ async function doExport(req, res, padId, readOnlyId, type) { } // 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 // We have to over engineer this because tabs are stored as attributes and not plain text - if (type === "etherpad") { - let pad = await exportEtherpad.getPadRaw(padId); + if (type === 'etherpad') { + const pad = await exportEtherpad.getPadRaw(padId); res.send(pad); - } else if (type === "txt") { - let txt = await exporttxt.getPadTXTDocument(padId, req.params.rev); + } else if (type === 'txt') { + const txt = await exporttxt.getPadTXTDocument(padId, req.params.rev); res.send(txt); } else { // render the html document @@ -79,17 +79,17 @@ async function doExport(req, res, padId, readOnlyId, type) { // decide what to do with the html export // 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 - let newHTML = await hooks.aCallFirst("exportHTMLSend", html); + const newHTML = await hooks.aCallFirst('exportHTMLSend', html); if (newHTML.length) html = newHTML; res.send(html); - throw "stop"; + throw 'stop'; } // else write the html export to a file - let randNum = Math.floor(Math.random()*0xFFFFFFFF); - let srcFile = tempDirectory + "/etherpad_export_" + randNum + ".html"; + const randNum = Math.floor(Math.random() * 0xFFFFFFFF); + const srcFile = `${tempDirectory}/etherpad_export_${randNum}.html`; await fsp_writeFile(srcFile, html); // Tidy up the exported HTML @@ -98,42 +98,42 @@ async function doExport(req, res, padId, readOnlyId, type) { await TidyHtml.tidy(srcFile); // 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 - let result = await hooks.aCallAll("exportConvert", { srcFile, destFile, req, res }); + const result = await hooks.aCallAll('exportConvert', {srcFile, destFile, req, res}); if (result.length > 0) { // console.log("export handled by plugin", destFile); handledByPlugin = true; } else { // @TODO no Promise interface for convertors (yet) await new Promise((resolve, reject) => { - convertor.convertFile(srcFile, destFile, type, function(err) { - err ? reject("convertFailed") : resolve(); + convertor.convertFile(srcFile, destFile, type, (err) => { + err ? reject('convertFailed') : resolve(); }); }); } // send the file - let sendFile = util.promisify(res.sendFile); + const sendFile = util.promisify(res.sendFile); await res.sendFile(destFile, null); // clean up temporary files await fsp_unlink(srcFile); // 100ms delay to accommodate for slow windows fs - if (os.type().indexOf("Windows") > -1) { - await new Promise(resolve => setTimeout(resolve, 100)); + if (os.type().indexOf('Windows') > -1) { + await new Promise((resolve) => setTimeout(resolve, 100)); } await fsp_unlink(destFile); } } -exports.doExport = function(req, res, padId, readOnlyId, type) { - doExport(req, res, padId, readOnlyId, type).catch(err => { - if (err !== "stop") { +exports.doExport = function (req, res, padId, readOnlyId, type) { + doExport(req, res, padId, readOnlyId, type).catch((err) => { + if (err !== 'stop') { throw err; } }); -} +}; diff --git a/src/node/handler/ImportHandler.js b/src/node/handler/ImportHandler.js index c7af6a99b..719cb9f88 100644 --- a/src/node/handler/ImportHandler.js +++ b/src/node/handler/ImportHandler.js @@ -20,36 +20,36 @@ * limitations under the License. */ -var padManager = require("../db/PadManager") - , padMessageHandler = require("./PadMessageHandler") - , fs = require("fs") - , path = require("path") - , settings = require('../utils/Settings') - , formidable = require('formidable') - , os = require("os") - , importHtml = require("../utils/ImportHtml") - , importEtherpad = require("../utils/ImportEtherpad") - , log4js = require("log4js") - , hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js") - , util = require("util"); +const padManager = require('../db/PadManager'); +const padMessageHandler = require('./PadMessageHandler'); +const fs = require('fs'); +const path = require('path'); +const settings = require('../utils/Settings'); +const formidable = require('formidable'); +const os = require('os'); +const importHtml = require('../utils/ImportHtml'); +const importEtherpad = require('../utils/ImportEtherpad'); +const log4js = require('log4js'); +const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js'); +const util = require('util'); -let fsp_exists = util.promisify(fs.exists); -let fsp_rename = util.promisify(fs.rename); -let fsp_readFile = util.promisify(fs.readFile); -let fsp_unlink = util.promisify(fs.unlink) +const fsp_exists = util.promisify(fs.exists); +const fsp_rename = util.promisify(fs.rename); +const fsp_readFile = util.promisify(fs.readFile); +const fsp_unlink = util.promisify(fs.unlink); let convertor = null; -let exportExtension = "htm"; +let exportExtension = 'htm'; // load abiword only if it is enabled and if soffice is disabled if (settings.abiword != null && settings.soffice === null) { - convertor = require("../utils/Abiword"); + convertor = require('../utils/Abiword'); } // load soffice only if it is enabled if (settings.soffice != null) { - convertor = require("../utils/LibreOffice"); - exportExtension = "html"; + convertor = require('../utils/LibreOffice'); + exportExtension = 'html'; } const tmpDirectory = os.tmpdir(); @@ -58,28 +58,28 @@ const tmpDirectory = os.tmpdir(); * do a requested import */ async function doImport(req, res, padId) { - var apiLogger = log4js.getLogger("ImportHandler"); + const apiLogger = log4js.getLogger('ImportHandler'); // pipe to a file // convert file to html via abiword or soffice // 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 let useConvertor = (convertor != null); - let form = new formidable.IncomingForm(); + const form = new formidable.IncomingForm(); form.keepExtensions = true; form.uploadDir = tmpDirectory; form.maxFileSize = settings.importMaxFileSize; - + // Ref: https://github.com/node-formidable/formidable/issues/469 // Crash in Etherpad was Uploading Error: Error: Request aborted // [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed - form.onPart = part => { + form.onPart = (part) => { form.handlePart(part); 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); }); } @@ -87,23 +87,23 @@ async function doImport(req, res, padId) { // locally wrapped Promise, since form.parse requires a callback 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) { // the upload failed, stop at this point 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... - if (err && err.stack && err.stack.indexOf("maxFileSize") !== -1) { - reject("maxFileSize"); + if (err && err.stack && err.stack.indexOf('maxFileSize') !== -1) { + reject('maxFileSize'); } - reject("uploadFailed"); + reject('uploadFailed'); } - if(!files.file){ // might not be a graceful fix but it works - reject("uploadFailed"); - }else{ + if (!files.file) { // might not be a graceful fix but it works + reject('uploadFailed'); + } else { 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 // this allows us to accept source code files like .c or .java - let fileEnding = path.extname(srcFile).toLowerCase() - , knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm", ".etherpad", ".rtf"] - , fileEndingUnknown = (knownFileEndings.indexOf(fileEnding) < 0); + const fileEnding = path.extname(srcFile).toLowerCase(); + const knownFileEndings = ['.txt', '.doc', '.docx', '.pdf', '.odt', '.html', '.htm', '.etherpad', '.rtf']; + const fileEndingUnknown = (knownFileEndings.indexOf(fileEnding) < 0); if (fileEndingUnknown) { // the file ending is not known if (settings.allowUnknownFileEnds === true) { // 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); } else { - console.warn("Not allowing unknown file type to be imported", fileEnding); - throw "uploadFailed"; + console.warn('Not allowing unknown file type to be imported', fileEnding); + 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 - let result = await hooks.aCallAll("import", { srcFile, destFile, fileEnding }); - let importHandledByPlugin = (result.length > 0); // This feels hacky and wrong.. + const result = await hooks.aCallAll('import', {srcFile, destFile, fileEnding}); + const importHandledByPlugin = (result.length > 0); // This feels hacky and wrong.. - let fileIsEtherpad = (fileEnding === ".etherpad"); - let fileIsHTML = (fileEnding === ".html" || fileEnding === ".htm"); - let fileIsTXT = (fileEnding === ".txt"); + const fileIsEtherpad = (fileEnding === '.etherpad'); + const fileIsHTML = (fileEnding === '.html' || fileEnding === '.htm'); + const fileIsTXT = (fileEnding === '.txt'); if (fileIsEtherpad) { // we do this here so we can see if the pad has quite a few edits - let _pad = await padManager.getPad(padId); - let headCount = _pad.head; + const _pad = await padManager.getPad(padId); + const headCount = _pad.head; if (headCount >= 10) { 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); - let _text = await fsp_readFile(srcFile, "utf8"); + const _text = await fsp_readFile(srcFile, 'utf8'); req.directDatabaseAccess = true; await importEtherpad.setPadRaw(padId, _text); } @@ -170,11 +170,11 @@ async function doImport(req, res, padId) { } else { // @TODO - no Promise interface for convertors (yet) await new Promise((resolve, reject) => { - convertor.convertFile(srcFile, destFile, exportExtension, function(err) { + convertor.convertFile(srcFile, destFile, exportExtension, (err) => { // catch convert errors if (err) { - console.warn("Converting Error:", err); - reject("convertFailed"); + console.warn('Converting Error:', err); + reject('convertFailed'); } resolve(); }); @@ -184,13 +184,13 @@ async function doImport(req, res, padId) { if (!useConvertor && !req.directDatabaseAccess) { // 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 - let isAscii = ! Array.prototype.some.call(buf, c => (c > 240)); + const isAscii = !Array.prototype.some.call(buf, (c) => (c > 240)); if (!isAscii) { - throw "uploadFailed"; + throw 'uploadFailed'; } } @@ -201,7 +201,7 @@ async function doImport(req, res, padId) { let text; if (!req.directDatabaseAccess) { - text = await fsp_readFile(destFile, "utf8"); + text = await fsp_readFile(destFile, 'utf8'); /* * The 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 * title and comment it out independently on the classes that are set on it. */ - text = text.replace("<title", "<!-- <title"); - text = text.replace("","-->"); + text = text.replace('', '-->'); // node on windows has a delay on releasing of the file lock. // We add a 100ms delay to work around this - if (os.type().indexOf("Windows") > -1){ - await new Promise(resolve => setTimeout(resolve, 100)); + if (os.type().indexOf('Windows') > -1) { + await new Promise((resolve) => setTimeout(resolve, 100)); } } @@ -227,7 +227,7 @@ async function doImport(req, res, padId) { try { await importHtml.setPadHTML(pad, text); } catch (e) { - apiLogger.warn("Error importing, possibly caused by malformed HTML"); + apiLogger.warn('Error importing, possibly caused by malformed HTML'); } } else { await pad.setText(text); @@ -274,16 +274,16 @@ exports.doImport = function (req, res, padId) { * the function above there's no other way to return * a value to the caller. */ - let status = "ok"; - doImport(req, res, padId).catch(err => { + let status = 'ok'; + doImport(req, res, padId).catch((err) => { // 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; } else { throw err; } }).then(() => { // close the connection - res.send(""); + res.send(``); }); -} +}; diff --git a/src/node/handler/PadMessageHandler.js b/src/node/handler/PadMessageHandler.js index c3e3ac272..214571044 100644 --- a/src/node/handler/PadMessageHandler.js +++ b/src/node/handler/PadMessageHandler.js @@ -20,30 +20,30 @@ /* global exports, process, require */ -var padManager = require("../db/PadManager"); -var Changeset = require("ep_etherpad-lite/static/js/Changeset"); -var AttributePool = require("ep_etherpad-lite/static/js/AttributePool"); -var AttributeManager = require("ep_etherpad-lite/static/js/AttributeManager"); -var authorManager = require("../db/AuthorManager"); -var readOnlyManager = require("../db/ReadOnlyManager"); -var settings = require('../utils/Settings'); -var securityManager = require("../db/SecurityManager"); -var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugin_defs.js"); -var log4js = require('log4js'); -var messageLogger = log4js.getLogger("message"); -var accessLogger = log4js.getLogger("access"); -var _ = require('underscore'); -var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js"); -var channels = require("channels"); -var stats = require('../stats'); +const padManager = require('../db/PadManager'); +const Changeset = require('ep_etherpad-lite/static/js/Changeset'); +const AttributePool = require('ep_etherpad-lite/static/js/AttributePool'); +const AttributeManager = require('ep_etherpad-lite/static/js/AttributeManager'); +const authorManager = require('../db/AuthorManager'); +const readOnlyManager = require('../db/ReadOnlyManager'); +const settings = require('../utils/Settings'); +const securityManager = require('../db/SecurityManager'); +const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs.js'); +const log4js = require('log4js'); +const messageLogger = log4js.getLogger('message'); +const accessLogger = log4js.getLogger('access'); +const _ = require('underscore'); +const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js'); +const channels = require('channels'); +const stats = require('../stats'); const assert = require('assert').strict; -const nodeify = require("nodeify"); -const { RateLimiterMemory } = require('rate-limiter-flexible'); +const nodeify = require('nodeify'); +const {RateLimiterMemory} = require('rate-limiter-flexible'); const webaccess = require('../hooks/express/webaccess'); const rateLimiter = new RateLimiterMemory({ points: settings.commitRateLimiting.points, - duration: settings.commitRateLimiting.duration + duration: settings.commitRateLimiting.duration, }); /** @@ -56,20 +56,16 @@ const rateLimiter = new RateLimiterMemory({ * rev = That last revision that was send to this client * author = the author ID used for this session */ -var sessioninfos = {}; +const sessioninfos = {}; exports.sessioninfos = sessioninfos; // Measure total amount of users -stats.gauge('totalUsers', function() { - return Object.keys(socketio.sockets.sockets).length; -}); +stats.gauge('totalUsers', () => Object.keys(socketio.sockets.sockets).length); /** * A changeset queue per pad that is processed by handleUserChanges() */ -const padChannels = new channels.channels(({socket, message}, callback) => { - return nodeify(handleUserChanges(socket, message), callback); -}); +const padChannels = new channels.channels(({socket, message}, callback) => nodeify(handleUserChanges(socket, message), callback)); /** * Saves the Socket class we need to send and receive data from the client @@ -80,9 +76,9 @@ let socketio; * This Method is called by server.js to tell the message handler on which socket it should send * @param socket_io The Socket */ -exports.setSocketIO = function(socket_io) { - socketio=socket_io; -} +exports.setSocketIO = function (socket_io) { + socketio = socket_io; +}; /** * Handles the connection of a new user @@ -98,17 +94,15 @@ exports.handleConnect = (socket) => { /** * Kicks all sessions from a pad */ -exports.kickSessionsFromPad = function(padID) { - if(typeof socketio.sockets['clients'] !== 'function') - return; +exports.kickSessionsFromPad = function (padID) { + if (typeof socketio.sockets.clients !== 'function') return; // skip if there is nobody on this pad - if(_getRoomSockets(padID).length === 0) - return; + if (_getRoomSockets(padID).length === 0) return; // disconnect everyone from this pad - socketio.sockets.in(padID).json.send({disconnect:"deleted"}); -} + socketio.sockets.in(padID).json.send({disconnect: 'deleted'}); +}; /** * Handles the disconnection of a user @@ -123,35 +117,35 @@ exports.handleDisconnect = async (socket) => { // if this connection was already etablished with a handshake, send a disconnect message to the others if (session && session.author) { const {session: {user} = {}} = socket.client.request; - accessLogger.info('[LEAVE]' + + accessLogger.info(`${'[LEAVE]' + ` pad:${session.padId}` + ` socket:${socket.id}` + ` IP:${settings.disableIPlogging ? 'ANONYMOUS' : socket.request.ip}` + - ` authorID:${session.author}` + - ((user && user.username) ? ` username:${user.username}` : '')); + ` authorID:${session.author}`}${ + (user && user.username) ? ` username:${user.username}` : ''}`); // get the author color out of the db - let color = await authorManager.getAuthorColorId(session.author); + const color = await authorManager.getAuthorColorId(session.author); // prepare the notification for the other users on the pad, that this user left - let messageToTheOtherUsers = { - "type": "COLLABROOM", - "data": { - type: "USER_LEAVE", + const messageToTheOtherUsers = { + type: 'COLLABROOM', + data: { + type: 'USER_LEAVE', userInfo: { - "ip": "127.0.0.1", - "colorId": color, - "userAgent": "Anonymous", - "userId": session.author - } - } + ip: '127.0.0.1', + colorId: color, + userAgent: 'Anonymous', + userId: session.author, + }, + }, }; // Go through all user that are still on the pad, and send them the USER_LEAVE message socket.broadcast.to(session.padId).json.send(messageToTheOtherUsers); // Allow plugins to hook into users leaving the pad - hooks.callAll("userLeave", session); + hooks.callAll('userLeave', session); } // Delete the sessioninfos entrys of this session @@ -164,7 +158,7 @@ exports.handleDisconnect = async (socket) => { * @param message the message from the client */ exports.handleMessage = async (socket, message) => { - var env = process.env.NODE_ENV || 'development'; + const env = process.env.NODE_ENV || 'development'; if (env === 'production') { try { @@ -189,25 +183,25 @@ exports.handleMessage = async (socket, message) => { const thisSession = sessioninfos[socket.id]; if (!thisSession) { - messageLogger.warn("Dropped message from an unknown connection.") + messageLogger.warn('Dropped message from an unknown connection.'); return; } - if (message.type === "CLIENT_READY") { + if (message.type === 'CLIENT_READY') { // client tried to auth for the first time (first msg from the client) createSessionInfoAuth(thisSession, message); } const auth = thisSession.auth; if (!auth) { - console.error("Auth was never applied to a session. If you are using the stress-test tool then restart Etherpad and the Stress test tool.") + console.error('Auth was never applied to a session. If you are using the stress-test tool then restart Etherpad and the Stress test tool.'); return; } // check if pad is requested via readOnly let padId = auth.padID; - if (padId.indexOf("r.") === 0) { + if (padId.indexOf('r.') === 0) { // Pad is readOnly, first get the real Pad ID padId = await readOnlyManager.getPadId(padId); } @@ -222,14 +216,14 @@ exports.handleMessage = async (socket, message) => { } if (thisSession.author != null && thisSession.author !== authorID) { messageLogger.warn( - 'Rejecting message from client because the author ID changed mid-session.' + + `${'Rejecting message from client because the author ID changed mid-session.' + ' Bad or missing token or sessionID?' + ` socket:${socket.id}` + ` IP:${settings.disableIPlogging ? 'ANONYMOUS' : socket.request.ip}` + ` originalAuthorID:${thisSession.author}` + - ` newAuthorID:${authorID}` + - ((user && user.username) ? ` username:${user.username}` : '') + - ` message:${message}`); + ` newAuthorID:${authorID}`}${ + (user && user.username) ? ` username:${user.username}` : '' + } message:${message}`); socket.json.send({disconnect: 'rejected'}); return; } @@ -248,42 +242,42 @@ exports.handleMessage = async (socket, message) => { // Drop the message if the client disconnected during the above processing. if (sessioninfos[socket.id] !== thisSession) { - messageLogger.warn('Dropping message from a connection that has gone away.') + messageLogger.warn('Dropping message from a connection that has gone away.'); return; } // Check what type of message we get and delegate to the other methods - if (message.type === "CLIENT_READY") { + if (message.type === 'CLIENT_READY') { await handleClientReady(socket, message, authorID); - } else if (message.type === "CHANGESET_REQ") { + } else if (message.type === 'CHANGESET_REQ') { await handleChangesetRequest(socket, message); - } else if(message.type === "COLLABROOM") { + } else if (message.type === 'COLLABROOM') { if (thisSession.readonly) { - messageLogger.warn("Dropped message, COLLABROOM for readonly pad"); - } else if (message.data.type === "USER_CHANGES") { - stats.counter('pendingEdits').inc() + messageLogger.warn('Dropped message, COLLABROOM for readonly pad'); + } else if (message.data.type === 'USER_CHANGES') { + stats.counter('pendingEdits').inc(); padChannels.emit(message.padId, {socket, message}); // add to pad queue - } else if (message.data.type === "USERINFO_UPDATE") { + } else if (message.data.type === 'USERINFO_UPDATE') { await handleUserInfoUpdate(socket, message); - } else if (message.data.type === "CHAT_MESSAGE") { + } else if (message.data.type === 'CHAT_MESSAGE') { await handleChatMessage(socket, message); - } else if (message.data.type === "GET_CHAT_MESSAGES") { + } else if (message.data.type === 'GET_CHAT_MESSAGES') { await handleGetChatMessages(socket, message); - } else if (message.data.type === "SAVE_REVISION") { + } else if (message.data.type === 'SAVE_REVISION') { await handleSaveRevisionMessage(socket, message); - } else if (message.data.type === "CLIENT_MESSAGE" && + } else if (message.data.type === 'CLIENT_MESSAGE' && message.data.payload != null && - message.data.payload.type === "suggestUserName") { + message.data.payload.type === 'suggestUserName') { handleSuggestUserName(socket, message); } else { - messageLogger.warn("Dropped message, unknown COLLABROOM Data Type " + message.data.type); + messageLogger.warn(`Dropped message, unknown COLLABROOM Data Type ${message.data.type}`); } - } else if(message.type === "SWITCH_TO_PAD") { + } else if (message.type === 'SWITCH_TO_PAD') { await handleSwitchToPad(socket, message, authorID); } else { - messageLogger.warn("Dropped message, unknown Message Type " + message.type); + messageLogger.warn(`Dropped message, unknown Message Type ${message.type}`); } -} +}; /** @@ -304,9 +298,9 @@ async function handleSaveRevisionMessage(socket, message) { * @param msg {Object} the message we're sending * @param sessionID {string} the socketIO session to which we're sending this message */ -exports.handleCustomObjectMessage = function(msg, sessionID) { - if (msg.data.type === "CUSTOM") { - if (sessionID){ +exports.handleCustomObjectMessage = function (msg, sessionID) { + if (msg.data.type === 'CUSTOM') { + if (sessionID) { // a sessionID is targeted: directly to this sessionID socketio.sockets.socket(sessionID).json.send(msg); } else { @@ -314,7 +308,7 @@ exports.handleCustomObjectMessage = function(msg, sessionID) { socketio.sockets.in(msg.data.payload.padId).json.send(msg); } } -} +}; /** * Handles a custom message (sent via HTTP API request) @@ -322,17 +316,17 @@ exports.handleCustomObjectMessage = function(msg, sessionID) { * @param padID {Pad} the pad to which we're sending this message * @param msgString {String} the message we're sending */ -exports.handleCustomMessage = function(padID, msgString) { - let time = Date.now(); - let msg = { +exports.handleCustomMessage = function (padID, msgString) { + const time = Date.now(); + const msg = { type: 'COLLABROOM', data: { type: msgString, - time: time - } + time, + }, }; socketio.sockets.in(padID).json.send(msg); -} +}; /** * Handles a Chat Message @@ -340,8 +334,8 @@ exports.handleCustomMessage = function(padID, msgString) { * @param message the message from the client */ async function handleChatMessage(socket, message) { - var time = Date.now(); - var text = message.data.text; + const time = Date.now(); + const text = message.data.text; const {padId, author: authorId} = sessioninfos[socket.id]; await exports.sendChatMessageToPadClients(time, authorId, text, padId); } @@ -353,26 +347,26 @@ async function handleChatMessage(socket, message) { * @param text the text of the chat message * @param padId the padId to send the chat message to */ -exports.sendChatMessageToPadClients = async function(time, userId, text, padId) { +exports.sendChatMessageToPadClients = async function (time, userId, text, padId) { // get the pad - let pad = await padManager.getPad(padId); + const pad = await padManager.getPad(padId); // get the author - let userName = await authorManager.getAuthorName(userId); + const userName = await authorManager.getAuthorName(userId); // save the chat message const promise = pad.appendChatMessage(text, userId, time); - let msg = { - type: "COLLABROOM", - data: { type: "CHAT_MESSAGE", userId, userName, time, text } + const msg = { + type: 'COLLABROOM', + data: {type: 'CHAT_MESSAGE', userId, userName, time, text}, }; // broadcast the chat message to everyone on the pad socketio.sockets.in(padId).json.send(msg); await promise; -} +}; /** * Handles the clients request for more chat-messages @@ -381,34 +375,34 @@ exports.sendChatMessageToPadClients = async function(time, userId, text, padId) */ async function handleGetChatMessages(socket, message) { if (message.data.start == null) { - messageLogger.warn("Dropped message, GetChatMessages Message has no start!"); + messageLogger.warn('Dropped message, GetChatMessages Message has no start!'); return; } if (message.data.end == null) { - messageLogger.warn("Dropped message, GetChatMessages Message has no start!"); + messageLogger.warn('Dropped message, GetChatMessages Message has no start!'); return; } - let start = message.data.start; - let end = message.data.end; - let count = end - start; + const start = message.data.start; + const end = message.data.end; + const count = end - start; if (count < 0 || count > 100) { - messageLogger.warn("Dropped message, GetChatMessages Message, client requested invalid amount of messages!"); + messageLogger.warn('Dropped message, GetChatMessages Message, client requested invalid amount of messages!'); return; } const padId = sessioninfos[socket.id].padId; - let pad = await padManager.getPad(padId); + const pad = await padManager.getPad(padId); - let chatMessages = await pad.getChatMessages(start, end); - let infoMsg = { - type: "COLLABROOM", + const chatMessages = await pad.getChatMessages(start, end); + const infoMsg = { + type: 'COLLABROOM', data: { - type: "CHAT_MESSAGES", - messages: chatMessages - } + type: 'CHAT_MESSAGES', + messages: chatMessages, + }, }; // send the messages back to the client @@ -423,12 +417,12 @@ async function handleGetChatMessages(socket, message) { function handleSuggestUserName(socket, message) { // check if all ok if (message.data.payload.newName == null) { - messageLogger.warn("Dropped message, suggestUserName Message has no newName!"); + messageLogger.warn('Dropped message, suggestUserName Message has no newName!'); return; } if (message.data.payload.unnamedId == null) { - messageLogger.warn("Dropped message, suggestUserName Message has no unnamedId!"); + messageLogger.warn('Dropped message, suggestUserName Message has no unnamedId!'); return; } @@ -451,29 +445,29 @@ function handleSuggestUserName(socket, message) { async function handleUserInfoUpdate(socket, message) { // check if all ok if (message.data.userInfo == null) { - messageLogger.warn("Dropped message, USERINFO_UPDATE Message has no userInfo!"); + messageLogger.warn('Dropped message, USERINFO_UPDATE Message has no userInfo!'); return; } if (message.data.userInfo.colorId == null) { - messageLogger.warn("Dropped message, USERINFO_UPDATE Message has no colorId!"); + messageLogger.warn('Dropped message, USERINFO_UPDATE Message has no colorId!'); return; } // Check that we have a valid session and author to update. const session = sessioninfos[socket.id]; if (!session || !session.author || !session.padId) { - messageLogger.warn("Dropped message, USERINFO_UPDATE Session not ready." + message.data); + messageLogger.warn(`Dropped message, USERINFO_UPDATE Session not ready.${message.data}`); return; } // Find out the author name of this session - var author = session.author; + const author = session.author; // Check colorId is a Hex color - var isColor = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(message.data.userInfo.colorId) // for #f00 (Thanks Smamatti) + const isColor = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(message.data.userInfo.colorId); // for #f00 (Thanks Smamatti) if (!isColor) { - messageLogger.warn("Dropped message, USERINFO_UPDATE Color is malformed." + message.data); + messageLogger.warn(`Dropped message, USERINFO_UPDATE Color is malformed.${message.data}`); return; } @@ -483,22 +477,22 @@ async function handleUserInfoUpdate(socket, message) { authorManager.setAuthorName(author, message.data.userInfo.name), ]); - var padId = session.padId; + const padId = session.padId; - var infoMsg = { - type: "COLLABROOM", + const infoMsg = { + type: 'COLLABROOM', data: { // The Client doesn't know about USERINFO_UPDATE, use USER_NEWINFO - type: "USER_NEWINFO", + type: 'USER_NEWINFO', userInfo: { userId: author, // set a null name, when there is no name set. cause the client wants it null name: message.data.userInfo.name || null, colorId: message.data.userInfo.colorId, - userAgent: "Anonymous", - ip: "127.0.0.1", - } - } + userAgent: 'Anonymous', + ip: '127.0.0.1', + }, + }, }; // Send the other clients on the pad the update message @@ -524,21 +518,21 @@ async function handleUserInfoUpdate(socket, message) { */ async function handleUserChanges(socket, message) { // This one's no longer pending, as we're gonna process it now - stats.counter('pendingEdits').dec() + stats.counter('pendingEdits').dec(); // Make sure all required fields are present if (message.data.baseRev == null) { - messageLogger.warn("Dropped message, USER_CHANGES Message has no baseRev!"); + messageLogger.warn('Dropped message, USER_CHANGES Message has no baseRev!'); return; } if (message.data.apool == null) { - messageLogger.warn("Dropped message, USER_CHANGES Message has no apool!"); + messageLogger.warn('Dropped message, USER_CHANGES Message has no apool!'); return; } if (message.data.changeset == null) { - messageLogger.warn("Dropped message, USER_CHANGES Message has no changeset!"); + messageLogger.warn('Dropped message, USER_CHANGES Message has no changeset!'); return; } @@ -550,20 +544,20 @@ async function handleUserChanges(socket, message) { // and always use the copy. atm a message will be ignored if the session is gone even // if the session was valid when the message arrived in the first place if (!thisSession) { - messageLogger.warn("Dropped message, disconnect happened in the mean time"); + messageLogger.warn('Dropped message, disconnect happened in the mean time'); return; } // get all Vars we need - var baseRev = message.data.baseRev; - var wireApool = (new AttributePool()).fromJsonable(message.data.apool); - var changeset = message.data.changeset; + const baseRev = message.data.baseRev; + const wireApool = (new AttributePool()).fromJsonable(message.data.apool); + let changeset = message.data.changeset; // Measure time to process edit - var stopWatch = stats.timer('edits').start(); + const stopWatch = stats.timer('edits').start(); // get the pad - let pad = await padManager.getPad(thisSession.padId); + const pad = await padManager.getPad(thisSession.padId); // create the changeset try { @@ -573,24 +567,24 @@ async function handleUserChanges(socket, message) { // Verify that the attribute indexes used in the changeset are all // defined in the accompanying attribute pool. - Changeset.eachAttribNumber(changeset, function(n) { + Changeset.eachAttribNumber(changeset, (n) => { if (!wireApool.getAttrib(n)) { - throw new Error("Attribute pool is missing attribute " + n + " for changeset " + changeset); + throw new Error(`Attribute pool is missing attribute ${n} for changeset ${changeset}`); } }); // Validate all added 'author' attribs to be the same value as the current user - var iterator = Changeset.opIterator(Changeset.unpack(changeset).ops) - , op; + const iterator = Changeset.opIterator(Changeset.unpack(changeset).ops); + let op; while (iterator.hasNext()) { - op = iterator.next() + op = iterator.next(); // + can add text with attribs // = can change or add attribs // - can have attribs, but they are discarded and don't show up in the attribs - but do show up in the pool - op.attribs.split('*').forEach(function(attr) { + op.attribs.split('*').forEach((attr) => { if (!attr) return; attr = wireApool.getAttrib(attr); @@ -607,8 +601,7 @@ async function handleUserChanges(socket, message) { // Afaik, it copies the new attributes from the changeset, to the global Attribute Pool changeset = Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool); - - } catch(e) { + } catch (e) { // There is an error in this changeset, so just refuse it socket.json.send({disconnect: 'badChangeset'}); stats.meter('failedChangesets').mark(); @@ -616,7 +609,7 @@ async function handleUserChanges(socket, message) { } // ex. applyUserChanges - let apool = pad.pool; + const apool = pad.pool; let r = baseRev; // The client's changeset might not be based on the latest revision, @@ -625,7 +618,7 @@ async function handleUserChanges(socket, message) { while (r < pad.getHeadRevisionNumber()) { r++; - let c = await pad.getRevisionChangeset(r); + const c = await pad.getRevisionChangeset(r); // At this point, both "c" (from the pad) and "changeset" (from the // client) are relative to revision r - 1. The follow function @@ -643,37 +636,37 @@ async function handleUserChanges(socket, message) { } changeset = Changeset.follow(c, changeset, false, apool); - } catch(e) { + } catch (e) { socket.json.send({disconnect: 'badChangeset'}); stats.meter('failedChangesets').mark(); - throw new Error("Can't apply USER_CHANGES, because " + e.message); + throw new Error(`Can't apply USER_CHANGES, because ${e.message}`); } } - let prevText = pad.text(); + const prevText = pad.text(); if (Changeset.oldLen(changeset) !== prevText.length) { socket.json.send({disconnect: 'badChangeset'}); stats.meter('failedChangesets').mark(); - throw new Error("Can't apply USER_CHANGES "+changeset+" with oldLen " + Changeset.oldLen(changeset) + " to document of length " + prevText.length); + throw new Error(`Can't apply USER_CHANGES ${changeset} with oldLen ${Changeset.oldLen(changeset)} to document of length ${prevText.length}`); } try { await pad.appendRevision(changeset, thisSession.author); - } catch(e) { + } catch (e) { socket.json.send({disconnect: 'badChangeset'}); stats.meter('failedChangesets').mark(); throw e; } - let correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool); + const correctionChangeset = _correctMarkersInPad(pad.atext, pad.pool); if (correctionChangeset) { await pad.appendRevision(correctionChangeset); } // Make sure the pad always ends with an empty line. - if (pad.text().lastIndexOf("\n") !== pad.text().length-1) { - var nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length - 1, 0, "\n"); + if (pad.text().lastIndexOf('\n') !== pad.text().length - 1) { + const nlChangeset = Changeset.makeSplice(pad.text(), pad.text().length - 1, 0, '\n'); await pad.appendRevision(nlChangeset); } @@ -685,7 +678,7 @@ async function handleUserChanges(socket, message) { stopWatch.end(); } -exports.updatePadClients = async function(pad) { +exports.updatePadClients = async function (pad) { // skip this if no-one is on this pad const roomSockets = _getRoomSockets(pad.id); if (roomSockets.length === 0) return; @@ -697,7 +690,7 @@ exports.updatePadClients = async function(pad) { // BEFORE first result will be landed to our cache object. The solution is to replace parallel processing // via async.forEach with sequential for() loop. There is no real benefits of running this in parallel, // but benefit of reusing cached revision object is HUGE - let revCache = {}; + const revCache = {}; // go through all sessions on this pad for (const socket of roomSockets) { @@ -705,16 +698,16 @@ exports.updatePadClients = async function(pad) { // send them all new changesets while (sessioninfos[sid] && sessioninfos[sid].rev < pad.getHeadRevisionNumber()) { - let r = sessioninfos[sid].rev + 1; + const r = sessioninfos[sid].rev + 1; let revision = revCache[r]; if (!revision) { revision = await pad.getRevision(r); revCache[r] = revision; } - let author = revision.meta.author, - revChangeset = revision.changeset, - currentTime = revision.meta.timestamp; + const author = revision.meta.author; + const revChangeset = revision.changeset; + const currentTime = revision.meta.timestamp; // next if session has not been deleted if (sessioninfos[sid] == null) { @@ -724,16 +717,15 @@ exports.updatePadClients = async function(pad) { if (author === sessioninfos[sid].author) { socket.json.send({type: 'COLLABROOM', data: {type: 'ACCEPT_COMMIT', newRev: r}}); } else { - let forWire = Changeset.prepareForWire(revChangeset, pad.pool); - let wireMsg = {"type": "COLLABROOM", - "data": { type:"NEW_CHANGES", - newRev:r, - changeset: forWire.translated, - apool: forWire.pool, - author: author, - currentTime: currentTime, - timeDelta: currentTime - sessioninfos[sid].time - }}; + const forWire = Changeset.prepareForWire(revChangeset, pad.pool); + const wireMsg = {type: 'COLLABROOM', + data: {type: 'NEW_CHANGES', + newRev: r, + changeset: forWire.translated, + apool: forWire.pool, + author, + currentTime, + timeDelta: currentTime - sessioninfos[sid].time}}; socket.json.send(wireMsg); } @@ -744,29 +736,27 @@ exports.updatePadClients = async function(pad) { } } } -} +}; /** * Copied from the Etherpad Source Code. Don't know what this method does excatly... */ function _correctMarkersInPad(atext, apool) { - var text = atext.text; + const text = atext.text; // collect char positions of line markers (e.g. bullets) in new atext // that aren't at the start of a line - var badMarkers = []; - var iter = Changeset.opIterator(atext.attribs); - var offset = 0; + const badMarkers = []; + const iter = Changeset.opIterator(atext.attribs); + let offset = 0; while (iter.hasNext()) { var op = iter.next(); - var hasMarker = _.find(AttributeManager.lineAttributes, function(attribute) { - return Changeset.opAttributeValue(op, attribute, apool); - }) !== undefined; + const hasMarker = _.find(AttributeManager.lineAttributes, (attribute) => Changeset.opAttributeValue(op, attribute, apool)) !== undefined; if (hasMarker) { - for (var i = 0; i < op.chars; i++) { - if (offset > 0 && text.charAt(offset-1) !== '\n') { + for (let i = 0; i < op.chars; i++) { + if (offset > 0 && text.charAt(offset - 1) !== '\n') { badMarkers.push(offset); } offset++; @@ -783,12 +773,12 @@ function _correctMarkersInPad(atext, apool) { // create changeset that removes these bad markers offset = 0; - var builder = Changeset.builder(text.length); + const builder = Changeset.builder(text.length); - badMarkers.forEach(function(pos) { + badMarkers.forEach((pos) => { builder.keepText(text.substring(offset, pos)); builder.remove(1); - offset = pos+1; + offset = pos + 1; }); return builder.toString(); @@ -855,56 +845,54 @@ function createSessionInfoAuth(sessionInfo, message) { async function handleClientReady(socket, message, authorID) { // check if all ok if (!message.token) { - messageLogger.warn("Dropped message, CLIENT_READY Message has no token!"); + messageLogger.warn('Dropped message, CLIENT_READY Message has no token!'); return; } if (!message.padId) { - messageLogger.warn("Dropped message, CLIENT_READY Message has no padId!"); + messageLogger.warn('Dropped message, CLIENT_READY Message has no padId!'); return; } if (!message.protocolVersion) { - messageLogger.warn("Dropped message, CLIENT_READY Message has no protocolVersion!"); + messageLogger.warn('Dropped message, CLIENT_READY Message has no protocolVersion!'); return; } if (message.protocolVersion !== 2) { - messageLogger.warn("Dropped message, CLIENT_READY Message has a unknown protocolVersion '" + message.protocolVersion + "'!"); + messageLogger.warn(`Dropped message, CLIENT_READY Message has a unknown protocolVersion '${message.protocolVersion}'!`); return; } - hooks.callAll("clientReady", message); + hooks.callAll('clientReady', message); // Get ro/rw id:s - let padIds = await readOnlyManager.getIds(message.padId); + const padIds = await readOnlyManager.getIds(message.padId); // get all authordata of this new user assert(authorID); - let value = await authorManager.getAuthor(authorID); - let authorColorId = value.colorId; - let authorName = value.name; + const value = await authorManager.getAuthor(authorID); + const authorColorId = value.colorId; + const authorName = value.name; // load the pad-object from the database - let pad = await padManager.getPad(padIds.padId); + const pad = await padManager.getPad(padIds.padId); // these db requests all need the pad object (timestamp of latest revision, author data) - let authors = pad.getAllAuthors(); + const authors = pad.getAllAuthors(); // get timestamp of latest revision needed for timeslider - let currentTime = await pad.getRevisionDate(pad.getHeadRevisionNumber()); + const currentTime = await pad.getRevisionDate(pad.getHeadRevisionNumber()); // get all author data out of the database (in parallel) - let historicalAuthorData = {}; - await Promise.all(authors.map(authorId => { - return authorManager.getAuthor(authorId).then(author => { - if (!author) { - messageLogger.error("There is no author for authorId: ", authorId, ". This is possibly related to https://github.com/ether/etherpad-lite/issues/2802"); - } else { - historicalAuthorData[authorId] = { name: author.name, colorId: author.colorId }; // Filter author attribs (e.g. don't send author's pads to all clients) - } - }); - })); + const historicalAuthorData = {}; + await Promise.all(authors.map((authorId) => authorManager.getAuthor(authorId).then((author) => { + if (!author) { + messageLogger.error('There is no author for authorId: ', authorId, '. This is possibly related to https://github.com/ether/etherpad-lite/issues/2802'); + } else { + historicalAuthorData[authorId] = {name: author.name, colorId: author.colorId}; // Filter author attribs (e.g. don't send author's pads to all clients) + } + }))); // glue the clientVars together, send them and tell the other clients that a new one is there @@ -932,12 +920,12 @@ async function handleClientReady(socket, message, authorID) { padIds.readonly || !webaccess.userCanModify(message.padId, socket.client.request); const {session: {user} = {}} = socket.client.request; - accessLogger.info(`[${pad.head > 0 ? 'ENTER' : 'CREATE'}]` + + accessLogger.info(`${`[${pad.head > 0 ? 'ENTER' : 'CREATE'}]` + ` pad:${padIds.padId}` + ` socket:${socket.id}` + ` IP:${settings.disableIPlogging ? 'ANONYMOUS' : socket.request.ip}` + - ` authorID:${authorID}` + - ((user && user.username) ? ` username:${user.username}` : '')); + ` authorID:${authorID}`}${ + (user && user.username) ? ` username:${user.username}` : ''}`); if (message.reconnect) { // If this is a reconnect, we don't have to send the client the ClientVars again @@ -949,13 +937,13 @@ async function handleClientReady(socket, message, authorID) { // During the client reconnect, client might miss some revisions from other clients. By using client revision, // this below code sends all the revisions missed during the client reconnect - var revisionsNeeded = []; - var changesets = {}; + const revisionsNeeded = []; + const changesets = {}; - var startNum = message.client_rev + 1; - var endNum = pad.getHeadRevisionNumber() + 1; + let startNum = message.client_rev + 1; + let endNum = pad.getHeadRevisionNumber() + 1; - var headNum = pad.getHeadRevisionNumber(); + const headNum = pad.getHeadRevisionNumber(); if (endNum > headNum + 1) { endNum = headNum + 1; @@ -971,51 +959,47 @@ async function handleClientReady(socket, message, authorID) { } // get changesets, author and timestamp needed for pending revisions (in parallel) - let promises = []; - for (let revNum of revisionsNeeded) { - let cs = changesets[revNum]; - promises.push( pad.getRevisionChangeset(revNum).then(result => cs.changeset = result )); - promises.push( pad.getRevisionAuthor(revNum).then(result => cs.author = result )); - promises.push( pad.getRevisionDate(revNum).then(result => cs.timestamp = result )); + const promises = []; + for (const revNum of revisionsNeeded) { + const cs = changesets[revNum]; + promises.push(pad.getRevisionChangeset(revNum).then((result) => cs.changeset = result)); + promises.push(pad.getRevisionAuthor(revNum).then((result) => cs.author = result)); + promises.push(pad.getRevisionDate(revNum).then((result) => cs.timestamp = result)); } await Promise.all(promises); // return pending changesets - for (let r of revisionsNeeded) { - - let forWire = Changeset.prepareForWire(changesets[r]['changeset'], pad.pool); - let wireMsg = {"type":"COLLABROOM", - "data":{type:"CLIENT_RECONNECT", - headRev:pad.getHeadRevisionNumber(), - newRev:r, - changeset:forWire.translated, - apool: forWire.pool, - author: changesets[r]['author'], - currentTime: changesets[r]['timestamp'] - }}; + for (const r of revisionsNeeded) { + const forWire = Changeset.prepareForWire(changesets[r].changeset, pad.pool); + const wireMsg = {type: 'COLLABROOM', + data: {type: 'CLIENT_RECONNECT', + headRev: pad.getHeadRevisionNumber(), + newRev: r, + changeset: forWire.translated, + apool: forWire.pool, + author: changesets[r].author, + currentTime: changesets[r].timestamp}}; socket.json.send(wireMsg); } if (startNum === endNum) { - var Msg = {"type":"COLLABROOM", - "data":{type:"CLIENT_RECONNECT", - noChanges: true, - newRev: pad.getHeadRevisionNumber() - }}; + const Msg = {type: 'COLLABROOM', + data: {type: 'CLIENT_RECONNECT', + noChanges: true, + newRev: pad.getHeadRevisionNumber()}}; socket.json.send(Msg); } - } else { // This is a normal first connect // prepare all values for the wire, there's a chance that this throws, if the pad is corrupted try { var atext = Changeset.cloneAText(pad.atext); - var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool); + const attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool); var apool = attribsForWire.pool.toJsonable(); atext.attribs = attribsForWire.translated; - } catch(e) { - console.error(e.stack || e) + } catch (e) { + console.error(e.stack || e); socket.json.send({disconnect: 'corruptPad'}); // pull the brakes return; @@ -1024,64 +1008,64 @@ async function handleClientReady(socket, message, authorID) { // Warning: never ever send padIds.padId to the client. If the // client is read only you would open a security hole 1 swedish // mile wide... - var clientVars = { - "skinName": settings.skinName, - "skinVariants": settings.skinVariants, - "randomVersionString": settings.randomVersionString, - "accountPrivs": { - "maxRevisions": 100 + const clientVars = { + skinName: settings.skinName, + skinVariants: settings.skinVariants, + randomVersionString: settings.randomVersionString, + accountPrivs: { + maxRevisions: 100, }, - "automaticReconnectionTimeout": settings.automaticReconnectionTimeout, - "initialRevisionList": [], - "initialOptions": { - "guestPolicy": "deny" + automaticReconnectionTimeout: settings.automaticReconnectionTimeout, + initialRevisionList: [], + initialOptions: { + guestPolicy: 'deny', }, - "savedRevisions": pad.getSavedRevisions(), - "collab_client_vars": { - "initialAttributedText": atext, - "clientIp": "127.0.0.1", - "padId": message.padId, - "historicalAuthorData": historicalAuthorData, - "apool": apool, - "rev": pad.getHeadRevisionNumber(), - "time": currentTime, + savedRevisions: pad.getSavedRevisions(), + collab_client_vars: { + initialAttributedText: atext, + clientIp: '127.0.0.1', + padId: message.padId, + historicalAuthorData, + apool, + rev: pad.getHeadRevisionNumber(), + time: currentTime, }, - "colorPalette": authorManager.getColorPalette(), - "clientIp": "127.0.0.1", - "userIsGuest": true, - "userColor": authorColorId, - "padId": message.padId, - "padOptions": settings.padOptions, - "padShortcutEnabled": settings.padShortcutEnabled, - "initialTitle": "Pad: " + message.padId, - "opts": {}, + colorPalette: authorManager.getColorPalette(), + clientIp: '127.0.0.1', + userIsGuest: true, + userColor: authorColorId, + padId: message.padId, + padOptions: settings.padOptions, + padShortcutEnabled: settings.padShortcutEnabled, + initialTitle: `Pad: ${message.padId}`, + opts: {}, // tell the client the number of the latest chat-message, which will be // used to request the latest 100 chat-messages later (GET_CHAT_MESSAGES) - "chatHead": pad.chatHead, - "numConnectedUsers": roomSockets.length, - "readOnlyId": padIds.readOnlyPadId, - "readonly": sessionInfo.readonly, - "serverTimestamp": Date.now(), - "userId": authorID, - "abiwordAvailable": settings.abiwordAvailable(), - "sofficeAvailable": settings.sofficeAvailable(), - "exportAvailable": settings.exportAvailable(), - "plugins": { - "plugins": plugins.plugins, - "parts": plugins.parts, + chatHead: pad.chatHead, + numConnectedUsers: roomSockets.length, + readOnlyId: padIds.readOnlyPadId, + readonly: sessionInfo.readonly, + serverTimestamp: Date.now(), + userId: authorID, + abiwordAvailable: settings.abiwordAvailable(), + sofficeAvailable: settings.sofficeAvailable(), + exportAvailable: settings.exportAvailable(), + plugins: { + plugins: plugins.plugins, + parts: plugins.parts, }, - "indentationOnNewLine": settings.indentationOnNewLine, - "scrollWhenFocusLineIsOutOfViewport": { - "percentage" : { - "editionAboveViewport": settings.scrollWhenFocusLineIsOutOfViewport.percentage.editionAboveViewport, - "editionBelowViewport": settings.scrollWhenFocusLineIsOutOfViewport.percentage.editionBelowViewport, + indentationOnNewLine: settings.indentationOnNewLine, + scrollWhenFocusLineIsOutOfViewport: { + percentage: { + editionAboveViewport: settings.scrollWhenFocusLineIsOutOfViewport.percentage.editionAboveViewport, + editionBelowViewport: settings.scrollWhenFocusLineIsOutOfViewport.percentage.editionBelowViewport, }, - "duration": settings.scrollWhenFocusLineIsOutOfViewport.duration, - "scrollWhenCaretIsInTheLastLineOfViewport": settings.scrollWhenFocusLineIsOutOfViewport.scrollWhenCaretIsInTheLastLineOfViewport, - "percentageToScrollWhenUserPressesArrowUp": settings.scrollWhenFocusLineIsOutOfViewport.percentageToScrollWhenUserPressesArrowUp, + duration: settings.scrollWhenFocusLineIsOutOfViewport.duration, + scrollWhenCaretIsInTheLastLineOfViewport: settings.scrollWhenFocusLineIsOutOfViewport.scrollWhenCaretIsInTheLastLineOfViewport, + percentageToScrollWhenUserPressesArrowUp: settings.scrollWhenFocusLineIsOutOfViewport.percentageToScrollWhenUserPressesArrowUp, }, - "initialChangesets": [], // FIXME: REMOVE THIS SHIT - } + initialChangesets: [], // FIXME: REMOVE THIS SHIT + }; // Add a username to the clientVars if one avaiable if (authorName != null) { @@ -1092,7 +1076,7 @@ async function handleClientReady(socket, message, authorID) { const messages = await hooks.aCallAll('clientVars', {clientVars, pad, socket}); // combine our old object with the new attributes from the hook - for (let msg of messages) { + for (const msg of messages) { Object.assign(clientVars, msg); } @@ -1106,17 +1090,17 @@ async function handleClientReady(socket, message, authorID) { sessionInfo.rev = pad.getHeadRevisionNumber(); // prepare the notification for the other users on the pad, that this user joined - let messageToTheOtherUsers = { - "type": "COLLABROOM", - "data": { - type: "USER_NEWINFO", + const messageToTheOtherUsers = { + type: 'COLLABROOM', + data: { + type: 'USER_NEWINFO', userInfo: { - "ip": "127.0.0.1", - "colorId": authorColorId, - "userAgent": "Anonymous", - "userId": authorID, - } - } + ip: '127.0.0.1', + colorId: authorColorId, + userAgent: 'Anonymous', + userId: authorID, + }, + }, }; // Add the authorname of this new User, if avaiable @@ -1129,7 +1113,6 @@ async function handleClientReady(socket, message, authorID) { // Get sessions for this pad and update them (in parallel) await Promise.all(_getRoomSockets(pad.id).map(async (roomSocket) => { - // Jump over, if this session is the connection session if (roomSocket.id === socket.id) { return; @@ -1141,25 +1124,25 @@ async function handleClientReady(socket, message, authorID) { if (sessionInfo == null) return; // get the authorname & colorId - let author = sessionInfo.author; - let cached = historicalAuthorData[author]; + const author = sessionInfo.author; + const cached = historicalAuthorData[author]; // reuse previously created cache of author's data let authorInfo = cached ? cached : (await authorManager.getAuthor(author)); // default fallback color to use if authorInfo.colorId is null - const defaultColor = "#daf0b2"; + const defaultColor = '#daf0b2'; if (!authorInfo) { - console.warn(`handleClientReady(): no authorInfo parameter was received. Default values are going to be used. See issue #3612. This can be caused by a user clicking undo after clearing all authorship colors see #2802`); + console.warn('handleClientReady(): no authorInfo parameter was received. Default values are going to be used. See issue #3612. This can be caused by a user clicking undo after clearing all authorship colors see #2802'); authorInfo = {}; } // For some reason sometimes name isn't set // Catch this issue here and use a fixed name. if (!authorInfo.name) { - console.warn(`handleClientReady(): client submitted no author name. Using "Anonymous". See: issue #3612`); - authorInfo.name = "Anonymous"; + console.warn('handleClientReady(): client submitted no author name. Using "Anonymous". See: issue #3612'); + authorInfo.name = 'Anonymous'; } // For some reason sometimes colorId isn't set @@ -1170,18 +1153,18 @@ async function handleClientReady(socket, message, authorID) { } // Send the new User a Notification about this other user - let msg = { - "type": "COLLABROOM", - "data": { - type: "USER_NEWINFO", + const msg = { + type: 'COLLABROOM', + data: { + type: 'USER_NEWINFO', userInfo: { - "ip": "127.0.0.1", - "colorId": authorInfo.colorId, - "name": authorInfo.name, - "userAgent": "Anonymous", - "userId": author - } - } + ip: '127.0.0.1', + colorId: authorInfo.colorId, + name: authorInfo.name, + userAgent: 'Anonymous', + userId: author, + }, + }, }; socket.json.send(msg); @@ -1195,49 +1178,49 @@ async function handleClientReady(socket, message, authorID) { async function handleChangesetRequest(socket, message) { // check if all ok if (message.data == null) { - messageLogger.warn("Dropped message, changeset request has no data!"); + messageLogger.warn('Dropped message, changeset request has no data!'); return; } if (message.padId == null) { - messageLogger.warn("Dropped message, changeset request has no padId!"); + messageLogger.warn('Dropped message, changeset request has no padId!'); return; } if (message.data.granularity == null) { - messageLogger.warn("Dropped message, changeset request has no granularity!"); + messageLogger.warn('Dropped message, changeset request has no granularity!'); return; } // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger#Polyfill if (Math.floor(message.data.granularity) !== message.data.granularity) { - messageLogger.warn("Dropped message, changeset request granularity is not an integer!"); + messageLogger.warn('Dropped message, changeset request granularity is not an integer!'); return; } if (message.data.start == null) { - messageLogger.warn("Dropped message, changeset request has no start!"); + messageLogger.warn('Dropped message, changeset request has no start!'); return; } if (message.data.requestID == null) { - messageLogger.warn("Dropped message, changeset request has no requestID!"); + messageLogger.warn('Dropped message, changeset request has no requestID!'); return; } - let granularity = message.data.granularity; - let start = message.data.start; - let end = start + (100 * granularity); + const granularity = message.data.granularity; + const start = message.data.start; + const end = start + (100 * granularity); - let padIds = await readOnlyManager.getIds(message.padId); + const padIds = await readOnlyManager.getIds(message.padId); // build the requested rough changesets and send them back try { - let data = await getChangesetInfo(padIds.padId, start, end, granularity); + const data = await getChangesetInfo(padIds.padId, start, end, granularity); data.requestID = message.data.requestID; socket.json.send({type: 'CHANGESET_REQ', data}); } catch (err) { - console.error('Error while handling a changeset request for ' + padIds.padId, err.toString(), message.data); + console.error(`Error while handling a changeset request for ${padIds.padId}`, err.toString(), message.data); } } @@ -1246,8 +1229,8 @@ async function handleChangesetRequest(socket, message) { * https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L144 */ async function getChangesetInfo(padId, startNum, endNum, granularity) { - let pad = await padManager.getPad(padId); - let head_revision = pad.getHeadRevisionNumber(); + const pad = await padManager.getPad(padId); + const head_revision = pad.getHeadRevisionNumber(); // calculate the last full endnum if (endNum > head_revision + 1) { @@ -1255,15 +1238,15 @@ async function getChangesetInfo(padId, startNum, endNum, granularity) { } endNum = Math.floor(endNum / granularity) * granularity; - let compositesChangesetNeeded = []; - let revTimesNeeded = []; + const compositesChangesetNeeded = []; + const revTimesNeeded = []; // figure out which composite Changeset and revTimes we need, to load them in bulk for (let start = startNum; start < endNum; start += granularity) { - let end = start + granularity; + const end = start + granularity; // add the composite Changeset we needed - compositesChangesetNeeded.push({ start, end }); + compositesChangesetNeeded.push({start, end}); // add the t1 time we need revTimesNeeded.push(start === 0 ? 0 : start - 1); @@ -1276,24 +1259,20 @@ async function getChangesetInfo(padId, startNum, endNum, granularity) { // it would make all the lookups run in series // get all needed composite Changesets - let composedChangesets = {}; - let p1 = Promise.all(compositesChangesetNeeded.map(item => { - return composePadChangesets(padId, item.start, item.end).then(changeset => { - composedChangesets[item.start + "/" + item.end] = changeset; - }); - })); + const composedChangesets = {}; + const p1 = Promise.all(compositesChangesetNeeded.map((item) => composePadChangesets(padId, item.start, item.end).then((changeset) => { + composedChangesets[`${item.start}/${item.end}`] = changeset; + }))); // get all needed revision Dates - let revisionDate = []; - let p2 = Promise.all(revTimesNeeded.map(revNum => { - return pad.getRevisionDate(revNum).then(revDate => { - revisionDate[revNum] = Math.floor(revDate / 1000); - }); - })); + const revisionDate = []; + const p2 = Promise.all(revTimesNeeded.map((revNum) => pad.getRevisionDate(revNum).then((revDate) => { + revisionDate[revNum] = Math.floor(revDate / 1000); + }))); // get the lines let lines; - let p3 = getPadLines(padId, startNum - 1).then(_lines => { + const p3 = getPadLines(padId, startNum - 1).then((_lines) => { lines = _lines; }); @@ -1301,37 +1280,37 @@ async function getChangesetInfo(padId, startNum, endNum, granularity) { await Promise.all([p1, p2, p3]); // doesn't know what happens here exactly :/ - let timeDeltas = []; - let forwardsChangesets = []; - let backwardsChangesets = []; - let apool = new AttributePool(); + const timeDeltas = []; + const forwardsChangesets = []; + const backwardsChangesets = []; + const apool = new AttributePool(); for (let compositeStart = startNum; compositeStart < endNum; compositeStart += granularity) { - let compositeEnd = compositeStart + granularity; + const compositeEnd = compositeStart + granularity; if (compositeEnd > endNum || compositeEnd > head_revision + 1) { break; } - let forwards = composedChangesets[compositeStart + "/" + compositeEnd]; - let backwards = Changeset.inverse(forwards, lines.textlines, lines.alines, pad.apool()); + const forwards = composedChangesets[`${compositeStart}/${compositeEnd}`]; + const backwards = Changeset.inverse(forwards, lines.textlines, lines.alines, pad.apool()); Changeset.mutateAttributionLines(forwards, lines.alines, pad.apool()); Changeset.mutateTextLines(forwards, lines.textlines); - let forwards2 = Changeset.moveOpsToNewPool(forwards, pad.apool(), apool); - let backwards2 = Changeset.moveOpsToNewPool(backwards, pad.apool(), apool); + const forwards2 = Changeset.moveOpsToNewPool(forwards, pad.apool(), apool); + const backwards2 = Changeset.moveOpsToNewPool(backwards, pad.apool(), apool); - let t1 = (compositeStart === 0) ? revisionDate[0] : revisionDate[compositeStart - 1]; - let t2 = revisionDate[compositeEnd - 1]; + const t1 = (compositeStart === 0) ? revisionDate[0] : revisionDate[compositeStart - 1]; + const t2 = revisionDate[compositeEnd - 1]; timeDeltas.push(t2 - t1); forwardsChangesets.push(forwards2); backwardsChangesets.push(backwards2); } - return { forwardsChangesets, backwardsChangesets, - apool: apool.toJsonable(), actualEndNum: endNum, - timeDeltas, start: startNum, granularity }; + return {forwardsChangesets, backwardsChangesets, + apool: apool.toJsonable(), actualEndNum: endNum, + timeDeltas, start: startNum, granularity}; } /** @@ -1339,7 +1318,7 @@ async function getChangesetInfo(padId, startNum, endNum, granularity) { * https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L263 */ async function getPadLines(padId, revNum) { - let pad = await padManager.getPad(padId); + const pad = await padManager.getPad(padId); // get the atext let atext; @@ -1347,12 +1326,12 @@ async function getPadLines(padId, revNum) { if (revNum >= 0) { atext = await pad.getInternalRevisionAText(revNum); } else { - atext = Changeset.makeAText("\n"); + atext = Changeset.makeAText('\n'); } return { textlines: Changeset.splitTextLines(atext.text), - alines: Changeset.splitAttributionLines(atext.attribs, atext.text) + alines: Changeset.splitAttributionLines(atext.attribs, atext.text), }; } @@ -1360,52 +1339,49 @@ async function getPadLines(padId, revNum) { * Tries to rebuild the composePadChangeset function of the original Etherpad * https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L241 */ -async function composePadChangesets (padId, startNum, endNum) { - let pad = await padManager.getPad(padId); +async function composePadChangesets(padId, startNum, endNum) { + const pad = await padManager.getPad(padId); // fetch all changesets we need - let headNum = pad.getHeadRevisionNumber(); + const headNum = pad.getHeadRevisionNumber(); endNum = Math.min(endNum, headNum + 1); startNum = Math.max(startNum, 0); // create an array for all changesets, we will // replace the values with the changeset later - let changesetsNeeded = []; - for (let r = startNum ; r < endNum; r++) { + const changesetsNeeded = []; + for (let r = startNum; r < endNum; r++) { changesetsNeeded.push(r); } // get all changesets - let changesets = {}; - await Promise.all(changesetsNeeded.map(revNum => { - return pad.getRevisionChangeset(revNum).then(changeset => changesets[revNum] = changeset); - })); + const changesets = {}; + await Promise.all(changesetsNeeded.map((revNum) => pad.getRevisionChangeset(revNum).then((changeset) => changesets[revNum] = changeset))); // compose Changesets let r; try { let changeset = changesets[startNum]; - let pool = pad.apool(); + const pool = pad.apool(); for (r = startNum + 1; r < endNum; r++) { - let cs = changesets[r]; + const cs = changesets[r]; changeset = Changeset.compose(changeset, cs, pool); } return changeset; - } catch (e) { // r-1 indicates the rev that was build starting with startNum, applying startNum+1, +2, +3 - console.warn("failed to compose cs in pad:", padId, " startrev:", startNum," current rev:", r); + console.warn('failed to compose cs in pad:', padId, ' startrev:', startNum, ' current rev:', r); throw e; } } function _getRoomSockets(padID) { const roomSockets = []; - var room = socketio.sockets.adapter.rooms[padID]; + const room = socketio.sockets.adapter.rooms[padID]; if (room) { - for (var id in room.sockets) { + for (const id in room.sockets) { roomSockets.push(socketio.sockets.sockets[id]); } } @@ -1416,27 +1392,26 @@ function _getRoomSockets(padID) { /** * Get the number of users in a pad */ -exports.padUsersCount = function(padID) { +exports.padUsersCount = function (padID) { return { - padUsersCount: _getRoomSockets(padID).length - } -} + padUsersCount: _getRoomSockets(padID).length, + }; +}; /** * Get the list of users in a pad */ -exports.padUsers = async function(padID) { - - let padUsers = []; +exports.padUsers = async function (padID) { + const padUsers = []; // iterate over all clients (in parallel) await Promise.all(_getRoomSockets(padID).map(async (roomSocket) => { const s = sessioninfos[roomSocket.id]; if (s) { - return authorManager.getAuthor(s.author).then(author => { + return authorManager.getAuthor(s.author).then((author) => { // Fixes: https://github.com/ether/etherpad-lite/issues/4120 // On restart author might not be populated? - if(author){ + if (author) { author.id = s.author; padUsers.push(author); } @@ -1444,7 +1419,7 @@ exports.padUsers = async function(padID) { } })); - return { padUsers }; -} + return {padUsers}; +}; exports.sessioninfos = sessioninfos; diff --git a/src/node/handler/SocketIORouter.js b/src/node/handler/SocketIORouter.js index 9b3c6f2dc..56e5c5be4 100644 --- a/src/node/handler/SocketIORouter.js +++ b/src/node/handler/SocketIORouter.js @@ -19,53 +19,53 @@ * limitations under the License. */ -var log4js = require('log4js'); -var messageLogger = log4js.getLogger("message"); -var securityManager = require("../db/SecurityManager"); -var readOnlyManager = require("../db/ReadOnlyManager"); -var settings = require('../utils/Settings'); +const log4js = require('log4js'); +const messageLogger = log4js.getLogger('message'); +const securityManager = require('../db/SecurityManager'); +const readOnlyManager = require('../db/ReadOnlyManager'); +const settings = require('../utils/Settings'); /** * Saves all components * key is the component name * value is the component module */ -var components = {}; +const components = {}; -var socket; +let socket; /** * adds a component */ -exports.addComponent = function(moduleName, module) { +exports.addComponent = function (moduleName, module) { // save the component components[moduleName] = module; // give the module the socket module.setSocketIO(socket); -} +}; /** * sets the socket.io and adds event functions for routing */ -exports.setSocketIO = function(_socket) { +exports.setSocketIO = function (_socket) { // save this socket internaly socket = _socket; - socket.sockets.on('connection', function(client) { + socket.sockets.on('connection', (client) => { // wrap the original send function to log the messages client._send = client.send; - client.send = function(message) { + client.send = function (message) { messageLogger.debug(`to ${client.id}: ${JSON.stringify(message)}`); client._send(message); - } + }; // tell all components about this connect - for (let i in components) { + for (const i in components) { components[i].handleConnect(client); } - client.on('message', async function(message) { + client.on('message', async (message) => { if (message.protocolVersion && message.protocolVersion != 2) { messageLogger.warn(`Protocolversion header is not correct: ${JSON.stringify(message)}`); return; @@ -78,11 +78,11 @@ exports.setSocketIO = function(_socket) { await components[message.component].handleMessage(client, message); }); - client.on('disconnect', function() { + client.on('disconnect', () => { // tell all components about this disconnect - for (let i in components) { + for (const i in components) { components[i].handleDisconnect(client); } }); }); -} +}; diff --git a/src/node/hooks/express.js b/src/node/hooks/express.js index 9bb4f2239..05283b5cb 100644 --- a/src/node/hooks/express.js +++ b/src/node/hooks/express.js @@ -18,7 +18,7 @@ let serverName; exports.server = null; 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)`; @@ -26,7 +26,7 @@ exports.createServer = async () => { await exports.restartServer(); - if (settings.ip === "") { + if (settings.ip === '') { // using Unix socket for connectivity console.log(`You can access your Etherpad instance using the Unix socket at ${settings.port}`); } else { @@ -42,26 +42,26 @@ exports.createServer = async () => { const env = process.env.NODE_ENV || 'development'; 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 () => { if (exports.server) { - console.log("Restarting express server"); + console.log('Restarting express server'); await util.promisify(exports.server.close).bind(exports.server)(); } const app = express(); // New syntax for express v3 if (settings.ssl) { - console.log("SSL -- enabled"); + console.log('SSL -- enabled'); console.log(`SSL -- server key file: ${settings.ssl.key}`); console.log(`SSL -- Certificate Authority's certificate file: ${settings.ssl.cert}`); const options = { - key: fs.readFileSync( settings.ssl.key ), - cert: fs.readFileSync( settings.ssl.cert ) + key: fs.readFileSync(settings.ssl.key), + cert: fs.readFileSync(settings.ssl.cert), }; if (settings.ssl.ca) { @@ -79,16 +79,16 @@ exports.restartServer = async () => { 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 if (settings.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 // 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 // loading local resources, but it will drop them when loading foreign resources. @@ -97,11 +97,11 @@ exports.restartServer = async () => { // marked with // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy // 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. if (settings.exposeVersion) { - res.header("Server", serverName); + res.header('Server', serverName); } next(); @@ -165,13 +165,13 @@ exports.restartServer = async () => { // // reference: https://github.com/expressjs/session/blob/v1.17.0/README.md#cookiesecure secure: 'auto', - } + }, }); app.use(exports.sessionMiddleware); app.use(cookieParser(settings.sessionKey, {})); - hooks.callAll("expressConfigure", {"app": app}); + hooks.callAll('expressConfigure', {app}); hooks.callAll('expressCreateServer', {app, server: exports.server}); await util.promisify(exports.server.listen).bind(exports.server)(settings.port, settings.ip); diff --git a/src/node/hooks/express/admin.js b/src/node/hooks/express/admin.js index 3971d1a32..417939600 100644 --- a/src/node/hooks/express/admin.js +++ b/src/node/hooks/express/admin.js @@ -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) { - args.app.get('/admin', function(req, res) { - if('/' != req.path[req.path.length-1]) return res.redirect('./admin/'); + args.app.get('/admin', (req, res) => { + if ('/' != req.path[req.path.length - 1]) return res.redirect('./admin/'); res.send(eejs.require('ep_etherpad-lite/templates/admin/index.html', {req})); }); return cb(); -} +}; diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index d1c6b389e..e01fc998d 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -1,13 +1,13 @@ -var eejs = require('ep_etherpad-lite/node/eejs'); -var settings = require('ep_etherpad-lite/node/utils/Settings'); -var installer = require('ep_etherpad-lite/static/js/pluginfw/installer'); -var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs'); -var _ = require('underscore'); -var semver = require('semver'); +const eejs = require('ep_etherpad-lite/node/eejs'); +const settings = require('ep_etherpad-lite/node/utils/Settings'); +const installer = require('ep_etherpad-lite/static/js/pluginfw/installer'); +const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs'); +const _ = require('underscore'); +const semver = require('semver'); const UpdateCheck = require('ep_etherpad-lite/node/utils/UpdateCheck'); -exports.expressCreateServer = function(hook_name, args, cb) { - args.app.get('/admin/plugins', function(req, res) { +exports.expressCreateServer = function (hook_name, args, cb) { + args.app.get('/admin/plugins', (req, res) => { res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins.html', { plugins: plugins.plugins, req, @@ -16,114 +16,108 @@ exports.expressCreateServer = function(hook_name, args, cb) { })); }); - args.app.get('/admin/plugins/info', function(req, res) { - var gitCommit = settings.getGitCommit(); - var epVersion = settings.getEpVersion(); + args.app.get('/admin/plugins/info', (req, res) => { + const gitCommit = settings.getGitCommit(); + const epVersion = settings.getEpVersion(); - res.send(eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", { - gitCommit: gitCommit, - epVersion: epVersion, + res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins-info.html', { + gitCommit, + epVersion, latestVersion: UpdateCheck.getLatestVersion(), req, })); }); return cb(); -} +}; -exports.socketio = function(hook_name, args, cb) { - var io = args.io.of("/pluginfw/installer"); - io.on('connection', function(socket) { +exports.socketio = function (hook_name, args, cb) { + const io = args.io.of('/pluginfw/installer'); + io.on('connection', (socket) => { 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 - var installed = Object.keys(plugins.plugins).map(function(plugin) { - return plugins.plugins[plugin].package - }); + const installed = Object.keys(plugins.plugins).map((plugin) => 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 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; - var latestVersion = results[plugin].version; - var currentVersion = plugins.plugins[plugin].package.version; + const latestVersion = results[plugin].version; + const currentVersion = plugins.plugins[plugin].package.version; return semver.gt(latestVersion, currentVersion); }); - socket.emit("results:updatable", {updatable: updatable}); + socket.emit('results:updatable', {updatable}); } catch (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 { - let results = await installer.getAvailablePlugins(/*maxCacheAge:*/ false); - socket.emit("results:available", results); + const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ false); + socket.emit('results:available', results); } catch (er) { console.error(er); - socket.emit("results:available", {}); + socket.emit('results:available', {}); } }); - socket.on("search", async function(query) { + socket.on('search', async (query) => { try { - let results = await installer.search(query.searchTerm, /*maxCacheAge:*/ 60 * 10); - var res = Object.keys(results) - .map(function(pluginName) { - return results[pluginName]; - }) - .filter(function(plugin) { - return !plugins.plugins[plugin.name]; - }); + const results = await installer.search(query.searchTerm, /* maxCacheAge:*/ 60 * 10); + let res = Object.keys(results) + .map((pluginName) => results[pluginName]) + .filter((plugin) => !plugins.plugins[plugin.name]); res = sortPluginList(res, query.sortBy, query.sortDir) - .slice(query.offset, query.offset+query.limit); - socket.emit("results:search", {results: res, query: query}); + .slice(query.offset, query.offset + query.limit); + socket.emit('results:search', {results: res, query}); } catch (er) { console.error(er); - socket.emit("results:search", {results: {}, query: query}); + socket.emit('results:search', {results: {}, query}); } }); - socket.on("install", function(plugin_name) { - installer.install(plugin_name, function(er) { + socket.on('install', (plugin_name) => { + installer.install(plugin_name, (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) { - installer.uninstall(plugin_name, function(er) { + socket.on('uninstall', (plugin_name) => { + installer.uninstall(plugin_name, (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(); -} +}; -function sortPluginList(plugins, property, /*ASC?*/dir) { - return plugins.sort(function(a, b) { +function sortPluginList(plugins, property, /* ASC?*/dir) { + return plugins.sort((a, b) => { if (a[property] < b[property]) { - return dir? -1 : 1; + return dir ? -1 : 1; } if (a[property] > b[property]) { - return dir? 1 : -1; + return dir ? 1 : -1; } // a must be equal to b diff --git a/src/node/hooks/express/adminsettings.js b/src/node/hooks/express/adminsettings.js index 201c7533f..8cfe41ca6 100644 --- a/src/node/hooks/express/adminsettings.js +++ b/src/node/hooks/express/adminsettings.js @@ -1,55 +1,52 @@ -var eejs = require('ep_etherpad-lite/node/eejs'); -var settings = require('ep_etherpad-lite/node/utils/Settings'); -var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); -var fs = require('fs'); +const eejs = require('ep_etherpad-lite/node/eejs'); +const settings = require('ep_etherpad-lite/node/utils/Settings'); +const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); +const fs = require('fs'); 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', { req, - settings: "", + settings: '', search_results: {}, - errors: [] + errors: [], })); }); return cb(); -} +}; exports.socketio = function (hook_name, args, cb) { - var io = args.io.of("/settings"); - io.on('connection', function (socket) { - + const io = args.io.of('/settings'); + io.on('connection', (socket) => { if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return; - socket.on("load", function (query) { - fs.readFile('settings.json', 'utf8', function (err,data) { + socket.on('load', (query) => { + fs.readFile('settings.json', 'utf8', (err, data) => { if (err) { return console.log(err); } // if showSettingsInAdminPage is set to false, then return NOT_ALLOWED in the result - if(settings.showSettingsInAdminPage === false) { - socket.emit("settings", {results: 'NOT_ALLOWED'}); - } - else { - socket.emit("settings", {results: data}); + if (settings.showSettingsInAdminPage === false) { + socket.emit('settings', {results: 'NOT_ALLOWED'}); + } else { + socket.emit('settings', {results: data}); } }); }); - socket.on("saveSettings", function (settings) { - fs.writeFile('settings.json', settings, function (err) { + socket.on('saveSettings', (settings) => { + fs.writeFile('settings.json', settings, (err) => { if (err) throw err; - socket.emit("saveprogress", "saved"); + socket.emit('saveprogress', 'saved'); }); }); 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(); await hooks.aCallAll('restartServer'); }); - }); return cb(); -} +}; diff --git a/src/node/hooks/express/apicalls.js b/src/node/hooks/express/apicalls.js index ed9f5a987..c87998e94 100644 --- a/src/node/hooks/express/apicalls.js +++ b/src/node/hooks/express/apicalls.js @@ -1,34 +1,34 @@ -var log4js = require('log4js'); -var clientLogger = log4js.getLogger("client"); -var formidable = require('formidable'); -var apiHandler = require('../../handler/APIHandler'); +const log4js = require('log4js'); +const clientLogger = log4js.getLogger('client'); +const formidable = require('formidable'); +const apiHandler = require('../../handler/APIHandler'); exports.expressCreateServer = function (hook_name, args, cb) { - //The Etherpad client side sends information about how a disconnect happened - args.app.post('/ep/pad/connection-diagnostic-info', function(req, res) { - new formidable.IncomingForm().parse(req, function(err, fields, files) { - clientLogger.info("DIAGNOSTIC-INFO: " + fields.diagnosticInfo); - res.end("OK"); + // The Etherpad client side sends information about how a disconnect happened + args.app.post('/ep/pad/connection-diagnostic-info', (req, res) => { + new formidable.IncomingForm().parse(req, (err, fields, files) => { + clientLogger.info(`DIAGNOSTIC-INFO: ${fields.diagnosticInfo}`); + res.end('OK'); }); }); - //The Etherpad client side sends information about client side javscript errors - args.app.post('/jserror', function(req, res) { - new formidable.IncomingForm().parse(req, function(err, fields, files) { + // The Etherpad client side sends information about client side javscript errors + args.app.post('/jserror', (req, res) => { + new formidable.IncomingForm().parse(req, (err, fields, files) => { try { - var data = JSON.parse(fields.errorInfo) - }catch(e){ - return res.end() + var data = JSON.parse(fields.errorInfo); + } catch (e) { + return res.end(); } - clientLogger.warn(data.msg+' --', data); - res.end("OK"); + clientLogger.warn(`${data.msg} --`, data); + res.end('OK'); }); }); - //Provide a possibility to query the latest available API version - args.app.get('/api', function (req, res) { - res.json({"currentVersion" : apiHandler.latestApiVersion}); + // Provide a possibility to query the latest available API version + args.app.get('/api', (req, res) => { + res.json({currentVersion: apiHandler.latestApiVersion}); }); return cb(); -} +}; diff --git a/src/node/hooks/express/errorhandling.js b/src/node/hooks/express/errorhandling.js index 9863c6a5b..4a20b70d2 100644 --- a/src/node/hooks/express/errorhandling.js +++ b/src/node/hooks/express/errorhandling.js @@ -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.app = args.app; // 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 // through these "error-handling" middleware // allowing you to respond however you like - res.status(500).send({ error: 'Sorry, something bad happened!' }); - console.error(err.stack? err.stack : err.toString()); - stats.meter('http500').mark() + res.status(500).send({error: 'Sorry, something bad happened!'}); + console.error(err.stack ? err.stack : err.toString()); + stats.meter('http500').mark(); }); return cb(); -} +}; diff --git a/src/node/hooks/express/importexport.js b/src/node/hooks/express/importexport.js index 5a0b72420..7a6c38655 100644 --- a/src/node/hooks/express/importexport.js +++ b/src/node/hooks/express/importexport.js @@ -1,44 +1,43 @@ const assert = require('assert').strict; -var hasPadAccess = require("../../padaccess"); -var settings = require('../../utils/Settings'); -var exportHandler = require('../../handler/ExportHandler'); -var importHandler = require('../../handler/ImportHandler'); -var padManager = require("../../db/PadManager"); -var readOnlyManager = require("../../db/ReadOnlyManager"); -var authorManager = require("../../db/AuthorManager"); -const rateLimit = require("express-rate-limit"); -const securityManager = require("../../db/SecurityManager"); -const webaccess = require("./webaccess"); +const hasPadAccess = require('../../padaccess'); +const settings = require('../../utils/Settings'); +const exportHandler = require('../../handler/ExportHandler'); +const importHandler = require('../../handler/ImportHandler'); +const padManager = require('../../db/PadManager'); +const readOnlyManager = require('../../db/ReadOnlyManager'); +const authorManager = require('../../db/AuthorManager'); +const rateLimit = require('express-rate-limit'); +const securityManager = require('../../db/SecurityManager'); +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 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) { - // handle export requests args.app.use('/p/:pad/:rev?/export/:type', limiter); - args.app.get('/p/:pad/:rev?/export/:type', async function(req, res, next) { - var types = ["pdf", "doc", "txt", "html", "odt", "etherpad"]; - //send a 404 if we don't support this filetype + args.app.get('/p/:pad/:rev?/export/:type', async (req, res, next) => { + const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad']; + // send a 404 if we don't support this filetype if (types.indexOf(req.params.type) == -1) { return next(); } // if abiword is disabled, and this is a format we only support with abiword, output a message - if (settings.exportAvailable() == "no" && - ["odt", "pdf", "doc"].indexOf(req.params.type) !== -1) { + if (settings.exportAvailable() == 'no' && + ['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`); // 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; } - res.header("Access-Control-Allow-Origin", "*"); + res.header('Access-Control-Allow-Origin', '*'); if (await hasPadAccess(req, res)) { let padId = req.params.pad; @@ -49,7 +48,7 @@ exports.expressCreateServer = function (hook_name, args, cb) { padId = await readOnlyManager.getPadId(readOnlyId); } - let exists = await padManager.doesPadExists(padId); + const exists = await padManager.doesPadExists(padId); if (!exists) { console.warn(`Someone tried to export a pad that doesn't exist (${padId})`); return next(); @@ -62,7 +61,7 @@ exports.expressCreateServer = function (hook_name, args, cb) { // handle import requests 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 {accessStatus} = await securityManager.checkAccess( req.params.pad, req.cookies.sessionID, req.cookies.token, user); @@ -73,4 +72,4 @@ exports.expressCreateServer = function (hook_name, args, cb) { }); return cb(); -} +}; diff --git a/src/node/hooks/express/isValidJSONPName.js b/src/node/hooks/express/isValidJSONPName.js index 47755ef86..442c963e9 100644 --- a/src/node/hooks/express/isValidJSONPName.js +++ b/src/node/hooks/express/isValidJSONPName.js @@ -62,14 +62,14 @@ const RESERVED_WORDS = [ 'volatile', 'while', 'with', - 'yield' + 'yield', ]; const regex = /^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:".+"|\'.+\'|\d+)\])*?$/; -module.exports.check = function(inputStr) { - var isValid = true; - inputStr.split(".").forEach(function(part) { +module.exports.check = function (inputStr) { + let isValid = true; + inputStr.split('.').forEach((part) => { if (!regex.test(part)) { isValid = false; } @@ -80,4 +80,4 @@ module.exports.check = function(inputStr) { }); return isValid; -} +}; diff --git a/src/node/hooks/express/openapi.js b/src/node/hooks/express/openapi.js index 0bfa871f1..8ea9529c7 100644 --- a/src/node/hooks/express/openapi.js +++ b/src/node/hooks/express/openapi.js @@ -14,7 +14,7 @@ const OpenAPIBackend = require('openapi-backend').default; const formidable = require('formidable'); -const { promisify } = require('util'); +const {promisify} = require('util'); const cloneDeep = require('lodash.clonedeep'); const createHTTPError = require('http-errors'); @@ -57,12 +57,12 @@ const resources = { create: { operationId: 'createGroup', summary: 'creates a new group', - responseSchema: { groupID: { type: 'string' } }, + responseSchema: {groupID: {type: 'string'}}, }, createIfNotExistsFor: { operationId: 'createGroupIfNotExistsFor', summary: 'this functions helps you to map your application group ids to Etherpad group ids', - responseSchema: { groupID: { type: 'string' } }, + responseSchema: {groupID: {type: 'string'}}, }, delete: { operationId: 'deleteGroup', @@ -71,7 +71,7 @@ const resources = { listPads: { operationId: 'listPads', summary: 'returns all pads of this group', - responseSchema: { padIDs: { type: 'array', items: { type: 'string' } } }, + responseSchema: {padIDs: {type: 'array', items: {type: 'string'}}}, }, createPad: { operationId: 'createGroupPad', @@ -80,12 +80,12 @@ const resources = { listSessions: { operationId: 'listSessionsOfGroup', summary: '', - responseSchema: { sessions: { type: 'array', items: { $ref: '#/components/schemas/SessionInfo' } } }, + responseSchema: {sessions: {type: 'array', items: {$ref: '#/components/schemas/SessionInfo'}}}, }, list: { operationId: 'listAllGroups', summary: '', - responseSchema: { groupIDs: { type: 'array', items: { type: 'string' } } }, + responseSchema: {groupIDs: {type: 'array', items: {type: 'string'}}}, }, }, @@ -94,28 +94,28 @@ const resources = { create: { operationId: 'createAuthor', summary: 'creates a new author', - responseSchema: { authorID: { type: 'string' } }, + responseSchema: {authorID: {type: 'string'}}, }, createIfNotExistsFor: { operationId: 'createAuthorIfNotExistsFor', summary: 'this functions helps you to map your application author ids to Etherpad author ids', - responseSchema: { authorID: { type: 'string' } }, + responseSchema: {authorID: {type: 'string'}}, }, listPads: { operationId: 'listPadsOfAuthor', 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: { operationId: 'listSessionsOfAuthor', 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 :( getName: { operationId: 'getAuthorName', 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: { operationId: 'createSession', summary: 'creates a new session. validUntil is an unix timestamp in seconds', - responseSchema: { sessionID: { type: 'string' } }, + responseSchema: {sessionID: {type: 'string'}}, }, delete: { operationId: 'deleteSession', @@ -134,7 +134,7 @@ const resources = { info: { operationId: 'getSessionInfo', 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: { operationId: 'listAllPads', summary: 'list all the pads', - responseSchema: { padIDs: { type: 'array', items: { type: 'string' } } }, + responseSchema: {padIDs: {type: 'array', items: {type: 'string'}}}, }, createDiffHTML: { operationId: 'createDiffHTML', @@ -158,7 +158,7 @@ const resources = { getText: { operationId: 'getText', summary: 'returns the text of a pad', - responseSchema: { text: { type: 'string' } }, + responseSchema: {text: {type: 'string'}}, }, setText: { operationId: 'setText', @@ -167,7 +167,7 @@ const resources = { getHTML: { operationId: 'getHTML', summary: 'returns the text of a pad formatted as HTML', - responseSchema: { html: { type: 'string' } }, + responseSchema: {html: {type: 'string'}}, }, setHTML: { operationId: 'setHTML', @@ -176,12 +176,12 @@ const resources = { getRevisionsCount: { operationId: 'getRevisionsCount', summary: 'returns the number of revisions of this pad', - responseSchema: { revisions: { type: 'integer' } }, + responseSchema: {revisions: {type: 'integer'}}, }, getLastEdited: { operationId: 'getLastEdited', summary: 'returns the timestamp of the last revision of the pad', - responseSchema: { lastEdited: { type: 'integer' } }, + responseSchema: {lastEdited: {type: 'integer'}}, }, delete: { operationId: 'deletePad', @@ -190,7 +190,7 @@ const resources = { getReadOnlyID: { operationId: 'getReadOnlyID', summary: 'returns the read only link of a pad', - responseSchema: { readOnlyID: { type: 'string' } }, + responseSchema: {readOnlyID: {type: 'string'}}, }, setPublicStatus: { operationId: 'setPublicStatus', @@ -199,22 +199,22 @@ const resources = { getPublicStatus: { operationId: 'getPublicStatus', summary: 'return true of false', - responseSchema: { publicStatus: { type: 'boolean' } }, + responseSchema: {publicStatus: {type: 'boolean'}}, }, authors: { operationId: 'listAuthorsOfPad', 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: { operationId: 'padUsersCount', summary: 'returns the number of user that are currently editing this pad', - responseSchema: { padUsersCount: { type: 'integer' } }, + responseSchema: {padUsersCount: {type: 'integer'}}, }, users: { operationId: 'padUsers', 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: { operationId: 'sendClientsMessage', @@ -227,13 +227,13 @@ const resources = { getChatHistory: { operationId: 'getChatHistory', 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 :( getChatHead: { operationId: 'getChatHead', summary: 'returns the chatHead (chat-message) of the pad', - responseSchema: { chatHead: { $ref: '#/components/schemas/Message' } }, + responseSchema: {chatHead: {$ref: '#/components/schemas/Message'}}, }, appendChatMessage: { operationId: 'appendChatMessage', @@ -384,10 +384,10 @@ const defaultResponseRefs = { const operations = {}; for (const resource in resources) { for (const action in resources[resource]) { - const { operationId, responseSchema, ...operation } = resources[resource][action]; + const {operationId, responseSchema, ...operation} = resources[resource][action]; // add response objects - const responses = { ...defaultResponseRefs }; + const responses = {...defaultResponseRefs}; if (responseSchema) { responses[200] = cloneDeep(defaultResponses.Success); responses[200].content['application/json'].schema.properties.data = { @@ -478,14 +478,14 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => { }, }, }, - security: [{ ApiKey: [] }], + security: [{ApiKey: []}], }; // build operations for (const funcName in apiHandler.version[version]) { let operation = {}; if (operations[funcName]) { - operation = { ...operations[funcName] }; + operation = {...operations[funcName]}; } else { // console.warn(`No operation found for function: ${funcName}`); operation = { @@ -497,7 +497,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => { // set parameters operation.parameters = operation.parameters || []; 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]) { definition.components.parameters[paramName] = { name: paramName, @@ -533,7 +533,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => { }; exports.expressCreateServer = (hookName, args, cb) => { - const { app } = args; + const {app} = args; // create openapi-backend handlers for each api version under /api/{version}/* for (const version in apiHandler.version) { @@ -550,7 +550,7 @@ exports.expressCreateServer = (hookName, args, cb) => { app.get(`${apiRoot}/openapi.json`, (req, res) => { // For openapi definitions, wide CORS is probably fine 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 @@ -558,7 +558,7 @@ exports.expressCreateServer = (hookName, args, cb) => { if (isLatestAPIVersion) { app.get(`/${style}/openapi.json`, (req, res) => { 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]) { const handler = async (c, req, res) => { // parse fields from request - const { header, params, query } = c.request; + const {header, params, query} = c.request; // read form data if method was POST let formData = {}; @@ -602,7 +602,7 @@ exports.expressCreateServer = (hookName, args, cb) => { apiLogger.info(`REQUEST, v${version}:${funcName}, ${JSON.stringify(fields)}`); // 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 if (createHTTPError.isHttpError(err)) { // pass http errors thrown by handler forward @@ -620,7 +620,7 @@ exports.expressCreateServer = (hookName, args, cb) => { }); // return in common format - let response = { code: 0, message: 'ok', data: data || null }; + const response = {code: 0, message: 'ok', data: data || null}; // log 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 switch (res.statusCode) { case 403: // forbidden - response = { code: 4, message: err.message, data: null }; + response = {code: 4, message: err.message, data: null}; break; 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; case 404: // not found (no such function) - response = { code: 3, message: err.message, data: null }; + response = {code: 3, message: err.message, data: null}; break; case 500: // server error (internal error) - response = { code: 2, message: err.message, data: null }; + response = {code: 2, message: err.message, data: null}; break; case 400: // bad request (wrong parameters) // respond with 200 OK to keep old behavior and pass tests 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; default: - response = { code: 1, message: err.message, data: null }; + response = {code: 1, message: err.message, data: null}; break; } } diff --git a/src/node/hooks/express/padreadonly.js b/src/node/hooks/express/padreadonly.js index 486806d80..f17f7f0d6 100644 --- a/src/node/hooks/express/padreadonly.js +++ b/src/node/hooks/express/padreadonly.js @@ -1,13 +1,12 @@ -var readOnlyManager = require("../../db/ReadOnlyManager"); -var hasPadAccess = require("../../padaccess"); -var exporthtml = require("../../utils/ExportHtml"); +const readOnlyManager = require('../../db/ReadOnlyManager'); +const hasPadAccess = require('../../padaccess'); +const exporthtml = require('../../utils/ExportHtml'); exports.expressCreateServer = function (hook_name, args, cb) { // 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 - let padId = await readOnlyManager.getPadId(req.params.id); + const padId = await readOnlyManager.getPadId(req.params.id); if (padId == null) { res.status(404).send('404 - Not Found'); return; @@ -18,9 +17,9 @@ exports.expressCreateServer = function (hook_name, args, cb) { if (await hasPadAccess(req, res)) { // render the html document - let html = await exporthtml.getPadHTMLDocument(padId, null); + const html = await exporthtml.getPadHTMLDocument(padId, null); res.send(html); } }); return cb(); -} +}; diff --git a/src/node/hooks/express/padurlsanitize.js b/src/node/hooks/express/padurlsanitize.js index 7c603b2b2..8a287a961 100644 --- a/src/node/hooks/express/padurlsanitize.js +++ b/src/node/hooks/express/padurlsanitize.js @@ -1,30 +1,29 @@ -var padManager = require('../../db/PadManager'); -var url = require('url'); +const padManager = require('../../db/PadManager'); +const url = require('url'); exports.expressCreateServer = function (hook_name, args, cb) { - // 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 / if (!padManager.isValidPadId(padId) || /\/$/.test(req.url)) { res.status(404).send('Such a padname is forbidden'); return; } - let sanitizedPadId = await padManager.sanitizePadId(padId); + const sanitizedPadId = await padManager.sanitizePadId(padId); if (sanitizedPadId === padId) { // the pad id was fine, so just render it next(); } else { // 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); - var query = url.parse(req.url).query; - if ( query ) real_url += '?' + query; + const query = url.parse(req.url).query; + if (query) real_url += `?${query}`; res.header('Location', real_url); - res.status(302).send('You should be redirected to ' + real_url + ''); + res.status(302).send(`You should be redirected to ${real_url}`); } }); return cb(); -} +}; diff --git a/src/node/hooks/express/socketio.js b/src/node/hooks/express/socketio.js index fbfea0989..3a0cd1bb0 100644 --- a/src/node/hooks/express/socketio.js +++ b/src/node/hooks/express/socketio.js @@ -1,19 +1,19 @@ -const express = require("../express"); +const express = require('../express'); const proxyaddr = require('proxy-addr'); -var settings = require('../../utils/Settings'); -var socketio = require('socket.io'); -var socketIORouter = require("../../handler/SocketIORouter"); -var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); +const settings = require('../../utils/Settings'); +const socketio = require('socket.io'); +const socketIORouter = require('../../handler/SocketIORouter'); +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) { - //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 // transports in this list at once // e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling - var io = socketio({ - transports: settings.socketTransportProtocols + const io = socketio({ + transports: settings.socketTransportProtocols, }).listen(args.server, { /* * 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 // 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 // no longer available, details available at: // http://stackoverflow.com/questions/23981741/minify-socket-io-socket-io-js-with-1-0 // if(settings.minify) io.enable('browser client minification'); - //Initalize the Socket.IO Router + // Initalize the Socket.IO Router 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(); -} +}; diff --git a/src/node/hooks/express/specialpages.js b/src/node/hooks/express/specialpages.js index b21d3910b..f53ce1ac7 100644 --- a/src/node/hooks/express/specialpages.js +++ b/src/node/hooks/express/specialpages.js @@ -1,83 +1,81 @@ -var path = require('path'); -var eejs = require('ep_etherpad-lite/node/eejs'); -var toolbar = require("ep_etherpad-lite/node/utils/toolbar"); -var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); -var settings = require('../../utils/Settings'); +const path = require('path'); +const eejs = require('ep_etherpad-lite/node/eejs'); +const toolbar = require('ep_etherpad-lite/node/utils/toolbar'); +const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); +const settings = require('../../utils/Settings'); const webaccess = require('./webaccess'); exports.expressCreateServer = function (hook_name, args, cb) { // expose current stats - args.app.get('/stats', function(req, res) { - res.json(require('ep_etherpad-lite/node/stats').toJSON()) - }) + args.app.get('/stats', (req, res) => { + res.json(require('ep_etherpad-lite/node/stats').toJSON()); + }); - //serve index.html under / - args.app.get('/', function(req, res) { + // serve index.html under / + args.app.get('/', (req, res) => { res.send(eejs.require('ep_etherpad-lite/templates/index.html', {req})); }); - //serve javascript.html - args.app.get('/javascript', function(req, res) { + // serve javascript.html + args.app.get('/javascript', (req, res) => { res.send(eejs.require('ep_etherpad-lite/templates/javascript.html', {req})); }); - //serve robots.txt - args.app.get('/robots.txt', function(req, res) { - var filePath = path.join(settings.root, "src", "static", "skins", settings.skinName, "robots.txt"); - res.sendFile(filePath, function(err) { - //there is no custom robots.txt, send the default robots.txt which dissallows all - if(err) - { - filePath = path.join(settings.root, "src", "static", "robots.txt"); + // serve robots.txt + args.app.get('/robots.txt', (req, res) => { + let filePath = path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'robots.txt'); + res.sendFile(filePath, (err) => { + // there is no custom robots.txt, send the default robots.txt which dissallows all + if (err) { + filePath = path.join(settings.root, 'src', 'static', 'robots.txt'); res.sendFile(filePath); } }); }); - //serve pad.html under /p - args.app.get('/p/:pad', function(req, res, next) { + // serve pad.html under /p + args.app.get('/p/:pad', (req, res, next) => { // The below might break for pads being rewritten 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", { - toolbar: toolbar, - isReadOnly: isReadOnly + hooks.callAll('padInitToolbar', { + toolbar, + isReadOnly, }); - res.send(eejs.require("ep_etherpad-lite/templates/pad.html", { - req: req, - toolbar: toolbar, - isReadOnly: isReadOnly + res.send(eejs.require('ep_etherpad-lite/templates/pad.html', { + req, + toolbar, + isReadOnly, })); }); - //serve timeslider.html under /p/$padname/timeslider - args.app.get('/p/:pad/timeslider', function(req, res, next) { - hooks.callAll("padInitToolbar", { - toolbar: toolbar + // serve timeslider.html under /p/$padname/timeslider + args.app.get('/p/:pad/timeslider', (req, res, next) => { + hooks.callAll('padInitToolbar', { + toolbar, }); - res.send(eejs.require("ep_etherpad-lite/templates/timeslider.html", { - req: req, - toolbar: toolbar + res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', { + req, + toolbar, })); }); - //serve favicon.ico from all path levels except as a pad name - args.app.get( /\/favicon.ico$/, function(req, res) { - var filePath = path.join(settings.root, "src", "static", "skins", settings.skinName, "favicon.ico"); + // serve favicon.ico from all path levels except as a pad name + args.app.get(/\/favicon.ico$/, (req, res) => { + let filePath = path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico'); - res.sendFile(filePath, function(err) { - //there is no custom favicon, send the default favicon - if(err) - { - filePath = path.join(settings.root, "src", "static", "favicon.ico"); + res.sendFile(filePath, (err) => { + // there is no custom favicon, send the default favicon + if (err) { + filePath = path.join(settings.root, 'src', 'static', 'favicon.ico'); res.sendFile(filePath); } }); }); return cb(); -} +}; diff --git a/src/node/hooks/express/static.js b/src/node/hooks/express/static.js index 2a925b26b..2df757e64 100644 --- a/src/node/hooks/express/static.js +++ b/src/node/hooks/express/static.js @@ -1,14 +1,13 @@ -var minify = require('../../utils/Minify'); -var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugin_defs"); -var CachingMiddleware = require('../../utils/caching_middleware'); -var settings = require("../../utils/Settings"); -var Yajsml = require('etherpad-yajsml'); -var _ = require("underscore"); +const minify = require('../../utils/Minify'); +const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs'); +const CachingMiddleware = require('../../utils/caching_middleware'); +const settings = require('../../utils/Settings'); +const Yajsml = require('etherpad-yajsml'); +const _ = require('underscore'); exports.expressCreateServer = function (hook_name, args, cb) { - // Cache both minified and static. - var assetCache = new CachingMiddleware; + const assetCache = new CachingMiddleware(); args.app.all(/\/javascripts\/(.*)/, assetCache.handle); // 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 // CommonJS loader on the client-side. // Hostname "invalid.invalid" is a dummy value to allow parsing as a URI. - var jsServer = new (Yajsml.Server)({ - rootPath: 'javascripts/src/' - , rootURI: 'http://invalid.invalid/static/js/' - , libraryPath: 'javascripts/lib/' - , libraryURI: 'http://invalid.invalid/static/plugins/' - , requestURIs: minify.requestURIs // Loop-back is causing problems, this is a workaround. + const jsServer = new (Yajsml.Server)({ + rootPath: 'javascripts/src/', + rootURI: 'http://invalid.invalid/static/js/', + libraryPath: 'javascripts/lib/', + libraryURI: 'http://invalid.invalid/static/plugins/', + requestURIs: minify.requestURIs, // Loop-back is causing problems, this is a workaround. }); - var StaticAssociator = Yajsml.associators.StaticAssociator; - var associations = + const StaticAssociator = Yajsml.associators.StaticAssociator; + const associations = Yajsml.associators.associationsForSimpleMapping(minify.tar); - var associator = new StaticAssociator(associations); + const associator = new StaticAssociator(associations); jsServer.setAssociator(associator); args.app.use(jsServer.handle.bind(jsServer)); // serve plugin definitions // 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) - .filter(function(part){ return _(part).has('client_hooks') }); - - var clientPlugins = {}; + const clientPlugins = {}; _(clientParts).chain() - .map(function(part){ return part.plugin }) - .uniq() - .each(function(name){ - clientPlugins[name] = _(plugins.plugins[name]).clone(); - delete clientPlugins[name]['package']; - }); + .map((part) => part.plugin) + .uniq() + .each((name) => { + clientPlugins[name] = _(plugins.plugins[name]).clone(); + delete clientPlugins[name].package; + }); - res.header("Content-Type","application/json; charset=utf-8"); - res.write(JSON.stringify({"plugins": clientPlugins, "parts": clientParts})); + res.header('Content-Type', 'application/json; charset=utf-8'); + res.write(JSON.stringify({plugins: clientPlugins, parts: clientParts})); res.end(); }); return cb(); -} +}; diff --git a/src/node/hooks/express/tests.js b/src/node/hooks/express/tests.js index 1ed67a5ca..7b32a322d 100644 --- a/src/node/hooks/express/tests.js +++ b/src/node/hooks/express/tests.js @@ -1,13 +1,13 @@ -var path = require("path") - , npm = require("npm") - , fs = require("fs") - , util = require("util"); +const path = require('path'); +const npm = require('npm'); +const fs = require('fs'); +const util = require('util'); exports.expressCreateServer = function (hook_name, args, cb) { - args.app.get('/tests/frontend/specs_list.js', async function(req, res) { - let [coreTests, pluginTests] = await Promise.all([ + args.app.get('/tests/frontend/specs_list.js', async (req, res) => { + const [coreTests, pluginTests] = await Promise.all([ exports.getCoreTests(), - exports.getPluginTests() + exports.getPluginTests(), ]); // merge the two sets of results @@ -16,79 +16,77 @@ exports.expressCreateServer = function (hook_name, args, cb) { // Keep only *.js files 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.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 - var rootTestFolder = path.normalize(path.join(npm.root, "../tests/frontend/")); + const rootTestFolder = path.normalize(path.join(npm.root, '../tests/frontend/')); - var url2FilePath = function(url) { - var subPath = url.substr("/tests/frontend".length); - if (subPath == "") { - subPath = "index.html" + const url2FilePath = function (url) { + let subPath = url.substr('/tests/frontend'.length); + if (subPath == '') { + 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 if (filePath.indexOf(rootTestFolder) !== 0) { - filePath = path.join(rootTestFolder, "index.html"); + filePath = path.join(rootTestFolder, 'index.html'); } return filePath; - } + }; - args.app.get('/tests/frontend/specs/*', function (req, res) { - var specFilePath = url2FilePath(req.url); - var specFileName = path.basename(specFilePath); + args.app.get('/tests/frontend/specs/*', (req, res) => { + const specFilePath = url2FilePath(req.url); + const specFileName = path.basename(specFilePath); - fs.readFile(specFilePath, function(err, content) { + fs.readFile(specFilePath, (err, content) => { if (err) { return res.send(500); } - content = "describe(" + JSON.stringify(specFileName) + ", function(){ " + content + " });"; + content = `describe(${JSON.stringify(specFileName)}, function(){ ${content} });`; res.send(content); }); }); - args.app.get('/tests/frontend/*', function (req, res) { - var filePath = url2FilePath(req.url); + args.app.get('/tests/frontend/*', (req, res) => { + const filePath = url2FilePath(req.url); res.sendFile(filePath); }); - args.app.get('/tests/frontend', function (req, res) { + args.app.get('/tests/frontend', (req, res) => { res.redirect('/tests/frontend/index.html'); }); return cb(); -} +}; const readdir = util.promisify(fs.readdir); -exports.getPluginTests = async function(callback) { - const moduleDir = "node_modules/"; - const specPath = "/static/tests/frontend/specs/"; - const staticDir = "/static/plugins/"; +exports.getPluginTests = async function (callback) { + const moduleDir = 'node_modules/'; + const specPath = '/static/tests/frontend/specs/'; + const staticDir = '/static/plugins/'; - let pluginSpecs = []; + const pluginSpecs = []; - let plugins = await readdir(moduleDir); - let promises = plugins - .map(plugin => [ plugin, moduleDir + plugin + specPath] ) - .filter(([plugin, specDir]) => fs.existsSync(specDir)) // check plugin exists - .map(([plugin, specDir]) => { - return readdir(specDir) - .then(specFiles => specFiles.map(spec => { - pluginSpecs.push(staticDir + plugin + specPath + spec); - })); - }); + const plugins = await readdir(moduleDir); + const promises = plugins + .map((plugin) => [plugin, moduleDir + plugin + specPath]) + .filter(([plugin, specDir]) => fs.existsSync(specDir)) // check plugin exists + .map(([plugin, specDir]) => readdir(specDir) + .then((specFiles) => specFiles.map((spec) => { + pluginSpecs.push(staticDir + plugin + specPath + spec); + }))); return Promise.all(promises).then(() => pluginSpecs); -} +}; -exports.getCoreTests = function() { +exports.getCoreTests = function () { // get the core test specs return readdir('tests/frontend/specs'); -} +}; diff --git a/src/node/hooks/i18n.js b/src/node/hooks/i18n.js index 98acb9de7..610c3f68f 100644 --- a/src/node/hooks/i18n.js +++ b/src/node/hooks/i18n.js @@ -1,58 +1,58 @@ -var languages = require('languages4translatewiki') - , fs = require('fs') - , path = require('path') - , _ = require('underscore') - , npm = require('npm') - , plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs.js').plugins - , semver = require('semver') - , existsSync = require('../utils/path_exists') - , settings = require('../utils/Settings') +const languages = require('languages4translatewiki'); +const fs = require('fs'); +const path = require('path'); +const _ = require('underscore'); +const npm = require('npm'); +const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs.js').plugins; +const semver = require('semver'); +const existsSync = require('../utils/path_exists'); +const settings = require('../utils/Settings') ; // returns all existing messages merged together and grouped by langcode // {es: {"foo": "string"}, en:...} function getAllLocales() { - var locales2paths = {}; + const locales2paths = {}; // Puts the paths of all locale files contained in a given directory // into `locales2paths` (files from various dirs are grouped by lang code) // (only json files with valid language code as name) function extractLangs(dir) { - if(!existsSync(dir)) return; - var stat = fs.lstatSync(dir); + if (!existsSync(dir)) return; + let stat = fs.lstatSync(dir); if (!stat.isDirectory() || stat.isSymbolicLink()) return; - fs.readdirSync(dir).forEach(function(file) { + fs.readdirSync(dir).forEach((file) => { file = path.resolve(dir, file); stat = fs.lstatSync(file); if (stat.isDirectory() || stat.isSymbolicLink()) return; - var ext = path.extname(file) - , locale = path.basename(file, ext).toLowerCase(); + const ext = path.extname(file); + const locale = path.basename(file, ext).toLowerCase(); if ((ext == '.json') && languages.isValid(locale)) { - if(!locales2paths[locale]) locales2paths[locale] = []; + if (!locales2paths[locale]) locales2paths[locale] = []; locales2paths[locale].push(file); } }); } - //add core supported languages first - extractLangs(npm.root+"/ep_etherpad-lite/locales"); + // add core supported languages first + extractLangs(`${npm.root}/ep_etherpad-lite/locales`); - //add plugins languages (if any) - for(var pluginName in plugins) extractLangs(path.join(npm.root, pluginName, 'locales')); + // add plugins languages (if any) + 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) - var locales = {} - _.each (locales2paths, function(files, langcode) { - locales[langcode]={}; + const locales = {}; + _.each(locales2paths, (files, langcode) => { + locales[langcode] = {}; - files.forEach(function(file) { + files.forEach((file) => { let fileContents; try { - fileContents = JSON.parse(fs.readFileSync(file,'utf8')); + fileContents = JSON.parse(fs.readFileSync(file, 'utf8')); } catch (err) { console.error(`failed to read JSON file ${file}: ${err}`); throw err; @@ -64,17 +64,17 @@ function getAllLocales() { // Add custom strings from settings.json // Since this is user-supplied, we'll do some extra sanity checks const wrongFormatErr = Error( - "customLocaleStrings in wrong format. See documentation " + - "for Customization for Administrators, under Localization.") + 'customLocaleStrings in wrong format. See documentation ' + + 'for Customization for Administrators, under Localization.'); if (settings.customLocaleStrings) { - if (typeof settings.customLocaleStrings !== "object") throw wrongFormatErr - _.each(settings.customLocaleStrings, function(overrides, langcode) { - if (typeof overrides !== "object") throw wrongFormatErr - _.each(overrides, function(localeString, key) { - if (typeof localeString !== "string") throw wrongFormatErr - locales[langcode][key] = localeString - }) - }) + if (typeof settings.customLocaleStrings !== 'object') throw wrongFormatErr; + _.each(settings.customLocaleStrings, (overrides, langcode) => { + if (typeof overrides !== 'object') throw wrongFormatErr; + _.each(overrides, (localeString, key) => { + if (typeof localeString !== 'string') throw wrongFormatErr; + locales[langcode][key] = localeString; + }); + }); } return locales; @@ -83,45 +83,44 @@ function getAllLocales() { // returns a hash of all available languages availables with nativeName and direction // e.g. { es: {nativeName: "español", direction: "ltr"}, ... } function getAvailableLangs(locales) { - var result = {}; - _.each(_.keys(locales), function(langcode) { + const result = {}; + _.each(_.keys(locales), (langcode) => { result[langcode] = languages.getLanguageInfo(langcode); }); return result; } // returns locale index that will be served in /locales.json -var generateLocaleIndex = function (locales) { - var result = _.clone(locales) // keep English strings - _.each(_.keys(locales), function(langcode) { - if (langcode != 'en') result[langcode]='locales/'+langcode+'.json'; +const generateLocaleIndex = function (locales) { + const result = _.clone(locales); // keep English strings + _.each(_.keys(locales), (langcode) => { + if (langcode != 'en') result[langcode] = `locales/${langcode}.json`; }); return JSON.stringify(result); -} +}; -exports.expressCreateServer = function(n, args, cb) { - - //regenerate locales on server restart - var locales = getAllLocales(); - var localeIndex = generateLocaleIndex(locales); +exports.expressCreateServer = function (n, args, cb) { + // regenerate locales on server restart + const locales = getAllLocales(); + const localeIndex = generateLocaleIndex(locales); exports.availableLangs = getAvailableLangs(locales); - args.app.get ('/locales/:locale', function(req, res) { - //works with /locale/en and /locale/en.json requests - var locale = req.params.locale.split('.')[0]; + args.app.get('/locales/:locale', (req, res) => { + // works with /locale/en and /locale/en.json requests + const locale = req.params.locale.split('.')[0]; if (exports.availableLangs.hasOwnProperty(locale)) { res.setHeader('Content-Type', 'application/json; charset=utf-8'); - res.send('{"'+locale+'":'+JSON.stringify(locales[locale])+'}'); + res.send(`{"${locale}":${JSON.stringify(locales[locale])}}`); } else { 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.send(localeIndex); - }) + }); return cb(); -} +}; diff --git a/src/node/padaccess.js b/src/node/padaccess.js index b30c43b40..617056a97 100644 --- a/src/node/padaccess.js +++ b/src/node/padaccess.js @@ -1,4 +1,4 @@ -var securityManager = require('./db/SecurityManager'); +const securityManager = require('./db/SecurityManager'); // checks for padAccess module.exports = async function (req, res) { @@ -7,7 +7,7 @@ module.exports = async function (req, res) { const accessObj = await securityManager.checkAccess( req.params.pad, req.cookies.sessionID, req.cookies.token, user); - if (accessObj.accessStatus === "grant") { + if (accessObj.accessStatus === 'grant') { // there is access, continue return true; } else { @@ -19,4 +19,4 @@ module.exports = async function (req, res) { // @TODO - send internal server error here? throw err; } -} +}; diff --git a/src/node/server.js b/src/node/server.js index a8a567179..5b3040bfc 100755 --- a/src/node/server.js +++ b/src/node/server.js @@ -61,13 +61,13 @@ exports.start = async () => { try { await db.init(); await plugins.update(); - console.info('Installed plugins: ' + plugins.formatPluginsWithVersion()); - console.debug('Installed parts:\n' + plugins.formatParts()); - console.debug('Installed hooks:\n' + plugins.formatHooks()); + console.info(`Installed plugins: ${plugins.formatPluginsWithVersion()}`); + console.debug(`Installed parts:\n${plugins.formatParts()}`); + console.debug(`Installed hooks:\n${plugins.formatHooks()}`); await hooks.aCallAll('loadSettings', {settings}); await hooks.aCallAll('createServer'); } catch (e) { - console.error('exception thrown: ' + e.message); + console.error(`exception thrown: ${e.message}`); if (e.stack) console.log(e.stack); process.exit(1); } diff --git a/src/node/stats.js b/src/node/stats.js index 13654bb7d..8f0d1f180 100644 --- a/src/node/stats.js +++ b/src/node/stats.js @@ -1,4 +1,4 @@ -var measured = require('measured-core') +const measured = require('measured-core'); module.exports = measured.createCollection(); diff --git a/src/node/utils/Abiword.js b/src/node/utils/Abiword.js index ee6b50781..b75487d75 100644 --- a/src/node/utils/Abiword.js +++ b/src/node/utils/Abiword.js @@ -18,41 +18,39 @@ * limitations under the License. */ -var spawn = require('child_process').spawn; -var async = require("async"); -var settings = require("./Settings"); -var os = require('os'); +const spawn = require('child_process').spawn; +const async = require('async'); +const settings = require('./Settings'); +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 -if(os.type().indexOf("Windows") > -1) -{ - var stdoutBuffer = ""; +// 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) { + let stdoutBuffer = ''; - doConvertTask = function(task, callback) { - //span an abiword process to perform the conversion - var abiword = spawn(settings.abiword, ["--to=" + task.destFile, task.srcFile]); + doConvertTask = function (task, callback) { + // span an abiword process to perform the conversion + const abiword = spawn(settings.abiword, [`--to=${task.destFile}`, task.srcFile]); - //delegate the processing of stdout to another function - abiword.stdout.on('data', function (data) { - //add data to buffer - stdoutBuffer+=data.toString(); - }); - - //append error messages to the buffer - abiword.stderr.on('data', function (data) { + // delegate the processing of stdout to another function + abiword.stdout.on('data', (data) => { + // add data to buffer stdoutBuffer += data.toString(); }); - //throw exceptions if abiword is dieing - abiword.on('exit', function (code) { - if(code != 0) { + // append error messages to the buffer + abiword.stderr.on('data', (data) => { + stdoutBuffer += data.toString(); + }); + + // throw exceptions if abiword is dieing + abiword.on('exit', (code) => { + if (code != 0) { return callback(`Abiword died with exit code ${code}`); } - if(stdoutBuffer != "") - { + if (stdoutBuffer != '') { console.log(stdoutBuffer); } @@ -60,51 +58,48 @@ if(os.type().indexOf("Windows") > -1) }); }; - exports.convertFile = function(srcFile, destFile, type, callback) { - doConvertTask({"srcFile": srcFile, "destFile": destFile, "type": type}, callback); + exports.convertFile = function (srcFile, destFile, type, callback) { + doConvertTask({srcFile, destFile, type}, callback); }; } -//on unix operating systems, we can start abiword with abicommand and communicate with it via stdin/stdout -//thats much faster, about factor 10 -else -{ - //spawn the abiword process - var abiword; - var stdoutCallback = null; - var spawnAbiword = function (){ - abiword = spawn(settings.abiword, ["--plugin", "AbiCommand"]); - var stdoutBuffer = ""; - var firstPrompt = true; +// on unix operating systems, we can start abiword with abicommand and communicate with it via stdin/stdout +// thats much faster, about factor 10 +else { + // spawn the abiword process + let abiword; + let stdoutCallback = null; + var spawnAbiword = function () { + abiword = spawn(settings.abiword, ['--plugin', 'AbiCommand']); + let stdoutBuffer = ''; + let firstPrompt = true; - //append error messages to the buffer - abiword.stderr.on('data', function (data) { + // append error messages to the buffer + abiword.stderr.on('data', (data) => { stdoutBuffer += data.toString(); }); - //abiword died, let's restart abiword and return an error with the callback - abiword.on('exit', function (code) { + // abiword died, let's restart abiword and return an error with the callback + abiword.on('exit', (code) => { spawnAbiword(); stdoutCallback(`Abiword died with exit code ${code}`); }); - //delegate the processing of stdout to a other function - abiword.stdout.on('data',function (data) { - //add data to buffer - stdoutBuffer+=data.toString(); + // delegate the processing of stdout to a other function + abiword.stdout.on('data', (data) => { + // add data to buffer + stdoutBuffer += data.toString(); - //we're searching for the prompt, cause this means everything we need is in the buffer - if(stdoutBuffer.search("AbiWord:>") != -1) - { - //filter the feedback message - var err = stdoutBuffer.search("OK") != -1 ? null : stdoutBuffer; + // we're searching for the prompt, cause this means everything we need is in the buffer + if (stdoutBuffer.search('AbiWord:>') != -1) { + // filter the feedback message + const err = stdoutBuffer.search('OK') != -1 ? null : stdoutBuffer; - //reset the buffer - stdoutBuffer = ""; + // reset the buffer + stdoutBuffer = ''; - //call the callback with the error message - //skip the first prompt - if(stdoutCallback != null && !firstPrompt) - { + // call the callback with the error message + // skip the first prompt + if (stdoutCallback != null && !firstPrompt) { stdoutCallback(err); stdoutCallback = null; } @@ -115,23 +110,23 @@ else }; spawnAbiword(); - doConvertTask = function(task, callback) { - abiword.stdin.write("convert " + task.srcFile + " " + task.destFile + " " + task.type + "\n"); - //create a callback that calls the task callback and the caller callback + doConvertTask = function (task, callback) { + abiword.stdin.write(`convert ${task.srcFile} ${task.destFile} ${task.type}\n`); + // create a callback that calls the task callback and the caller callback stdoutCallback = function (err) { callback(); - console.log("queue continue"); - try{ + console.log('queue continue'); + try { task.callback(err); - }catch(e){ - console.error("Abiword File failed to convert", e); + } catch (e) { + console.error('Abiword File failed to convert', e); } }; }; - //Queue with the converts we have to do - var queue = async.queue(doConvertTask, 1); - exports.convertFile = function(srcFile, destFile, type, callback) { - queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback}); + // Queue with the converts we have to do + const queue = async.queue(doConvertTask, 1); + exports.convertFile = function (srcFile, destFile, type, callback) { + queue.push({srcFile, destFile, type, callback}); }; } diff --git a/src/node/utils/AbsolutePaths.js b/src/node/utils/AbsolutePaths.js index 9d864c474..22294cfe2 100644 --- a/src/node/utils/AbsolutePaths.js +++ b/src/node/utils/AbsolutePaths.js @@ -18,17 +18,17 @@ * limitations under the License. */ -var log4js = require('log4js'); -var path = require('path'); -var _ = require('underscore'); +const log4js = require('log4js'); +const path = require('path'); +const _ = require('underscore'); -var absPathLogger = log4js.getLogger('AbsolutePaths'); +const absPathLogger = log4js.getLogger('AbsolutePaths'); /* * findEtherpadRoot() computes its value only on first invocation. * Subsequent invocations are served from this variable. */ -var etherpadRoot = null; +let etherpadRoot = null; /** * 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 * overlap. */ -var popIfEndsWith = function(stringArray, lastDesiredElements) { +const popIfEndsWith = function (stringArray, lastDesiredElements) { 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; } @@ -72,7 +72,7 @@ var popIfEndsWith = function(stringArray, lastDesiredElements) { * @return {string} The identified absolute base path. If such path cannot be * identified, prints a log and exits the application. */ -exports.findEtherpadRoot = function() { +exports.findEtherpadRoot = function () { if (etherpadRoot !== null) { return etherpadRoot; } @@ -87,7 +87,7 @@ exports.findEtherpadRoot = function() { * * \src */ - var maybeEtherpadRoot = popIfEndsWith(splitFoundRoot, ['src']); + let maybeEtherpadRoot = popIfEndsWith(splitFoundRoot, ['src']); if ((maybeEtherpadRoot === false) && (process.platform === 'win32')) { /* @@ -126,7 +126,7 @@ exports.findEtherpadRoot = function() { * it is returned unchanged. Otherwise it is interpreted * relative to exports.root. */ -exports.makeAbsolute = function(somePath) { +exports.makeAbsolute = function (somePath) { if (path.isAbsolute(somePath)) { return somePath; } @@ -145,7 +145,7 @@ exports.makeAbsolute = function(somePath) { * a subdirectory of the base one * @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 const relative = path.relative(parent, arbitraryDir); const isSubdir = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative); diff --git a/src/node/utils/Cli.js b/src/node/utils/Cli.js index 04c532fa0..6297a4f8c 100644 --- a/src/node/utils/Cli.js +++ b/src/node/utils/Cli.js @@ -22,30 +22,30 @@ // An object containing the parsed command-line options exports.argv = {}; -var argv = process.argv.slice(2); -var arg, prevArg; +const argv = process.argv.slice(2); +let arg, prevArg; // Loop through args -for ( var i = 0; i < argv.length; i++ ) { +for (let i = 0; i < argv.length; i++) { arg = argv[i]; // Override location of settings.json file - if ( prevArg == '--settings' || prevArg == '-s' ) { + if (prevArg == '--settings' || prevArg == '-s') { exports.argv.settings = arg; } // Override location of credentials.json file - if ( prevArg == '--credentials' ) { + if (prevArg == '--credentials') { exports.argv.credentials = arg; } // Override location of settings.json file - if ( prevArg == '--sessionkey' ) { + if (prevArg == '--sessionkey') { exports.argv.sessionkey = arg; } // Override location of settings.json file - if ( prevArg == '--apikey' ) { + if (prevArg == '--apikey') { exports.argv.apikey = arg; } diff --git a/src/node/utils/ExportEtherpad.js b/src/node/utils/ExportEtherpad.js index a92a91928..ace298ab7 100644 --- a/src/node/utils/ExportEtherpad.js +++ b/src/node/utils/ExportEtherpad.js @@ -15,41 +15,39 @@ */ -let db = require("../db/DB"); -let hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); +const db = require('../db/DB'); +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; - let padcontent = await db.get(padKey); - - let records = [ padKey ]; + const records = [padKey]; 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++) { - records.push(padKey + ":chat:" + i); + records.push(`${padKey}:chat:${i}`); } - let data = {}; - for (let key of records) { - + const data = {}; + for (const key of records) { // 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 if (entry.pool && entry.pool.numToAttrib) { - let authors = entry.pool.numToAttrib; + const authors = entry.pool.numToAttrib; - for (let k of Object.keys(authors)) { - if (authors[k][0] === "author") { - let authorId = authors[k][1]; + for (const k of Object.keys(authors)) { + if (authors[k][0] === 'author') { + const authorId = authors[k][1]; // Get the author info - let authorEntry = await db.get("globalAuthor:" + authorId); + const authorEntry = await db.get(`globalAuthor:${authorId}`); if (authorEntry) { - data["globalAuthor:" + authorId] = authorEntry; + data[`globalAuthor:${authorId}`] = authorEntry; if (authorEntry.padIDs) { authorEntry.padIDs = padId; } @@ -68,4 +66,4 @@ exports.getPadRaw = async function(padId) { })); return data; -} +}; diff --git a/src/node/utils/ExportHelper.js b/src/node/utils/ExportHelper.js index f6ec4486e..e498d4c42 100644 --- a/src/node/utils/ExportHelper.js +++ b/src/node/utils/ExportHelper.js @@ -18,24 +18,23 @@ * 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){ - var _analyzeLine = exports._analyzeLine; - var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext); - var textLines = atext.text.slice(0, -1).split('\n'); - var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); - var apool = pad.pool; +exports.getPadPlainText = function (pad, revNum) { + const _analyzeLine = exports._analyzeLine; + const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext); + const textLines = atext.text.slice(0, -1).split('\n'); + const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); + const apool = pad.pool; - var pieces = []; - for (var i = 0; i < textLines.length; i++){ - var line = _analyzeLine(textLines[i], attribLines[i], apool); - if (line.listLevel){ - var numSpaces = line.listLevel * 2 - 1; - var bullet = '*'; + const pieces = []; + for (let i = 0; i < textLines.length; i++) { + const line = _analyzeLine(textLines[i], attribLines[i], apool); + if (line.listLevel) { + const numSpaces = line.listLevel * 2 - 1; + const bullet = '*'; pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n'); - } - else{ + } else { pieces.push(line.text, '\n'); } } @@ -44,38 +43,37 @@ exports.getPadPlainText = function(pad, revNum){ }; -exports._analyzeLine = function(text, aline, apool){ - var line = {}; +exports._analyzeLine = function (text, aline, apool) { + const line = {}; // identify list - var lineMarker = 0; + let lineMarker = 0; line.listLevel = 0; - if (aline){ - var opIter = Changeset.opIterator(aline); - if (opIter.hasNext()){ - var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool); - if (listType){ + if (aline) { + const opIter = Changeset.opIterator(aline); + if (opIter.hasNext()) { + let listType = Changeset.opAttributeValue(opIter.next(), 'list', apool); + if (listType) { lineMarker = 1; listType = /([a-z]+)([0-9]+)/.exec(listType); - if (listType){ + if (listType) { line.listTypeName = listType[1]; line.listLevel = Number(listType[2]); } } } - var opIter2 = Changeset.opIterator(aline); - if (opIter2.hasNext()){ - var start = Changeset.opAttributeValue(opIter2.next(), 'start', apool); - if (start){ - line.start = start; + const opIter2 = Changeset.opIterator(aline); + if (opIter2.hasNext()) { + const start = Changeset.opAttributeValue(opIter2.next(), 'start', apool); + if (start) { + line.start = start; } } } - if (lineMarker){ + if (lineMarker) { line.text = text.substring(1); line.aline = Changeset.subattribution(aline, 1); - } - else{ + } else { line.text = text; line.aline = aline; } @@ -83,8 +81,6 @@ exports._analyzeLine = function(text, aline, apool){ }; -exports._encodeWhitespace = function(s){ - return s.replace(/[^\x21-\x7E\s\t\n\r]/gu, function(c){ - return "&#" +c.codePointAt(0) + ";"; - }); +exports._encodeWhitespace = function (s) { + return s.replace(/[^\x21-\x7E\s\t\n\r]/gu, (c) => `&#${c.codePointAt(0)};`); }; diff --git a/src/node/utils/ExportHtml.js b/src/node/utils/ExportHtml.js index 93637dc60..cfb294138 100644 --- a/src/node/utils/ExportHtml.js +++ b/src/node/utils/ExportHtml.js @@ -14,14 +14,14 @@ * limitations under the License. */ -var Changeset = require("ep_etherpad-lite/static/js/Changeset"); -var padManager = require("../db/PadManager"); -var _ = require('underscore'); -var Security = require('ep_etherpad-lite/static/js/security'); -var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); -var eejs = require('ep_etherpad-lite/node/eejs'); -var _analyzeLine = require('./ExportHelper')._analyzeLine; -var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; +const Changeset = require('ep_etherpad-lite/static/js/Changeset'); +const padManager = require('../db/PadManager'); +const _ = require('underscore'); +const Security = require('ep_etherpad-lite/static/js/security'); +const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); +const eejs = require('ep_etherpad-lite/node/eejs'); +const _analyzeLine = require('./ExportHelper')._analyzeLine; +const _encodeWhitespace = require('./ExportHelper')._encodeWhitespace; async function getPadHTML(pad, revNum) { let atext = pad.atext; @@ -39,12 +39,12 @@ exports.getPadHTML = getPadHTML; exports.getHTMLFromAtext = getHTMLFromAtext; async function getHTMLFromAtext(pad, atext, authorColors) { - var apool = pad.apool(); - var textLines = atext.text.slice(0, -1).split('\n'); - var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); + const apool = pad.apool(); + const textLines = atext.text.slice(0, -1).split('\n'); + const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); - var tags = ['h1', 'h2', 'strong', 'em', 'u', 's']; - var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; + const tags = ['h1', 'h2', 'strong', 'em', 'u', 's']; + const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; await Promise.all([ // 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 // *3:2 -> the attribute *3 means strong // *2:5 -> the attribute *2 means s(trikethrough) - var anumMap = {}; - var css = ""; + const anumMap = {}; + let css = ''; - var stripDotFromAuthorID = function(id){ - return id.replace(/\./g,'_'); + const stripDotFromAuthorID = function (id) { + return id.replace(/\./g, '_'); }; - if(authorColors){ - css+=""; + css += ''; } // 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 - props.forEach(function (propName, i) { - var attrib = [propName, true]; + props.forEach((propName, i) => { + let attrib = [propName, true]; if (_.isArray(propName)) { // propName can be in the form of ['color', 'red'], // see hook exportHtmlAdditionalTagsWithData attrib = propName; } - var propTrueNum = apool.putAttrib(attrib, true); - if (propTrueNum >= 0) - { + const propTrueNum = apool.putAttrib(attrib, true); + if (propTrueNum >= 0) { anumMap[propTrueNum] = i; } }); @@ -128,15 +127,15 @@ async function getHTMLFromAtext(pad, atext, authorColors) { // Just bold Bold and italics Just italics // becomes // Just bold Bold and italics Just italics - var taker = Changeset.stringIterator(text); - var assem = Changeset.stringAssembler(); - var openTags = []; + const taker = Changeset.stringIterator(text); + const assem = Changeset.stringAssembler(); + const openTags = []; - function getSpanClassFor(i){ - //return if author colors are disabled + function getSpanClassFor(i) { + // return if author colors are disabled if (!authorColors) return false; - var property = props[i]; + const property = props[i]; // we are not insterested on properties in the form of ['color', 'red'], // see hook exportHtmlAdditionalTagsWithData @@ -144,12 +143,12 @@ async function getHTMLFromAtext(pad, atext, authorColors) { return false; } - if(property.substr(0,6) === "author"){ + if (property.substr(0, 6) === 'author') { return stripDotFromAuthorID(property); } - if(property === "removed"){ - return "removed"; + if (property === 'removed') { + return 'removed'; } return false; @@ -157,16 +156,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) { // tags added by exportHtmlAdditionalTagsWithData will be exported as with // data attributes - function isSpanWithData(i){ - var property = props[i]; + function isSpanWithData(i) { + const property = props[i]; return _.isArray(property); } function emitOpenTag(i) { openTags.unshift(i); - var spanClass = getSpanClassFor(i); + const spanClass = getSpanClassFor(i); - if(spanClass){ + if (spanClass) { assem.append(''); @@ -180,10 +179,10 @@ async function getHTMLFromAtext(pad, atext, authorColors) { // this closes an open tag and removes its reference from openTags function emitCloseTag(i) { openTags.shift(); - var spanClass = getSpanClassFor(i); - var spanWithData = isSpanWithData(i); + const spanClass = getSpanClassFor(i); + const spanWithData = isSpanWithData(i); - if(spanClass || spanWithData){ + if (spanClass || spanWithData) { assem.append(''); } else { assem.append(' { + if (a in anumMap) { 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 - for (var i = openTags.length - 1; i >= 0; i--) - { - if (usedAttribs.indexOf(openTags[i]) === -1) - { + for (var i = openTags.length - 1; i >= 0; i--) { + if (usedAttribs.indexOf(openTags[i]) === -1) { outermostTag = i; break; } } // close all tags upto the outer most - if (outermostTag !== -1) - { - while ( outermostTag >= 0 ) - { + if (outermostTag !== -1) { + while (outermostTag >= 0) { emitCloseTag(openTags[0]); outermostTag--; } } // open all tags that are used but not open - for (i=0; i < usedAttribs.length; i++) - { - if (openTags.indexOf(usedAttribs[i]) === -1) - { + for (i = 0; i < usedAttribs.length; i++) { + if (openTags.indexOf(usedAttribs[i]) === -1) { emitOpenTag(usedAttribs[i]); } } - var chars = o.chars; - if (o.lines) - { + let chars = o.chars; + if (o.lines) { 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 - //from but they break the abiword parser and are completly useless - s = s.replace(String.fromCharCode(12), ""); + // removes the characters with the code 12. Don't know where they come + // from but they break the abiword parser and are completly useless + s = s.replace(String.fromCharCode(12), ''); assem.append(_encodeWhitespace(Security.escapeHTML(s))); } // end iteration over spans in line // close all the tags that are open after the last op - while (openTags.length > 0) - { + while (openTags.length > 0) { emitCloseTag(openTags[0]); } } // end processNextChars - if (urls) - { - urls.forEach(function (urlData) { - var startIndex = urlData[0]; - var url = urlData[1]; - var urlLength = url.length; + if (urls) { + urls.forEach((urlData) => { + const startIndex = urlData[0]; + const url = urlData[1]; + const urlLength = url.length; processNextChars(startIndex - idx); // 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. @@ -284,16 +271,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) { // https://html.spec.whatwg.org/multipage/links.html#link-type-noopener // https://mathiasbynens.github.io/rel-noopener/ // https://github.com/ether/etherpad-lite/pull/3636 - assem.append(''); + assem.append(``); processNextChars(urlLength); assem.append(''); }); } processNextChars(text.length - idx); - + return _processSpaces(assem.toString()); } // end getLineHTML - var pieces = [css]; + const pieces = [css]; // Need to deal with constraints imposed on HTML lists; can // 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 // want to deal gracefully with blank lines. // => keeps track of the parents level of indentation - var openLists = []; - for (var i = 0; i < textLines.length; i++) - { + let openLists = []; + for (let i = 0; i < textLines.length; i++) { var context; var line = _analyzeLine(textLines[i], attribLines[i], apool); - var lineContent = getLineHTML(line.text, line.aline); - if (line.listLevel)//If we are inside a list + const lineContent = getLineHTML(line.text, line.aline); + if (line.listLevel)// If we are inside a list { context = { - line: line, - lineContent: lineContent, - apool: apool, + line, + lineContent, + apool, attribLine: attribLines[i], text: textLines[i], - padId: pad.id + padId: pad.id, }; - var prevLine = null; - var nextLine = null; - if (i > 0) - { - prevLine = _analyzeLine(textLines[i -1], attribLines[i -1], apool); + let prevLine = null; + let nextLine = null; + if (i > 0) { + 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); } await hooks.aCallAll('getLineHTMLForExport', context); - //To create list parent elements - if ((!prevLine || prevLine.listLevel !== line.listLevel) || (prevLine && line.listTypeName !== prevLine.listTypeName)) - { - var exists = _.find(openLists, function (item) { - return (item.level === line.listLevel && item.type === line.listTypeName); - }); + // To create list parent elements + if ((!prevLine || prevLine.listLevel !== line.listLevel) || (prevLine && line.listTypeName !== prevLine.listTypeName)) { + const exists = _.find(openLists, (item) => (item.level === line.listLevel && item.type === line.listTypeName)); if (!exists) { - var prevLevel = 0; + let prevLevel = 0; if (prevLine && prevLine.listLevel) { prevLevel = prevLine.listLevel; } - if (prevLine && line.listTypeName !== prevLine.listTypeName) - { + if (prevLine && line.listTypeName !== prevLine.listTypeName) { prevLevel = 0; } for (var diff = prevLevel; diff < line.listLevel; diff++) { openLists.push({level: diff, type: line.listTypeName}); - var prevPiece = pieces[pieces.length - 1]; + const prevPiece = pieces[pieces.length - 1]; - if (prevPiece.indexOf("") === 0) - { - /* + if (prevPiece.indexOf('') === 0) { + /* uncommenting this breaks nested ols.. 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.. @@ -367,19 +346,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) { // pieces.push(""); */ - if( (nextLine.listTypeName === 'number') && (nextLine.text === '') ){ + if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) { // is the listTypeName check needed here? null text might be completely fine! // 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 - }else{ - pieces.push("
  • "); + } else { + pieces.push('
  • '); } - - } - if (line.listTypeName === "number") - { + if (line.listTypeName === 'number') { // 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 // 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 // but that's a job for another day.... - if(line.start){ - pieces.push("
      "); - }else{ - pieces.push("
        "); + if (line.start) { + pieces.push(`
          `); + } else { + pieces.push(`
            `); } - } - else - { - pieces.push("
              "); + } else { + pieces.push(`
                `); } } } } // if we're going up a level we shouldn't be adding.. - if(context.lineContent){ - pieces.push("
              • ", context.lineContent); + if (context.lineContent) { + pieces.push('
              • ', context.lineContent); } // To close list elements - if (nextLine && nextLine.listLevel === line.listLevel && line.listTypeName === nextLine.listTypeName) - { - if(context.lineContent){ - if( (nextLine.listTypeName === 'number') && (nextLine.text === '') ){ + if (nextLine && nextLine.listLevel === line.listLevel && line.listTypeName === nextLine.listTypeName) { + if (context.lineContent) { + if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) { // is the listTypeName check needed here? null text might be completely fine! // 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 - }else{ - pieces.push("
              • "); + } else { + pieces.push(''); } - } } - if ((!nextLine || !nextLine.listLevel || nextLine.listLevel < line.listLevel) || (nextLine && line.listTypeName !== nextLine.listTypeName)) - { - var nextLevel = 0; + if ((!nextLine || !nextLine.listLevel || nextLine.listLevel < line.listLevel) || (nextLine && line.listTypeName !== nextLine.listTypeName)) { + let nextLevel = 0; if (nextLine && nextLine.listLevel) { nextLevel = nextLine.listLevel; } - if (nextLine && line.listTypeName !== nextLine.listTypeName) - { + if (nextLine && line.listTypeName !== nextLine.listTypeName) { nextLevel = 0; } - for (var diff = nextLevel; diff < line.listLevel; diff++) - { - openLists = openLists.filter(function(el) { - return el.level !== diff && el.type !== line.listTypeName; - }); + for (var diff = nextLevel; diff < line.listLevel; diff++) { + openLists = openLists.filter((el) => el.level !== diff && el.type !== line.listTypeName); - if (pieces[pieces.length - 1].indexOf(""); + if (pieces[pieces.length - 1].indexOf(''); } - if (line.listTypeName === "number") - { - pieces.push("
          "); - } - else - { - pieces.push(""); + if (line.listTypeName === 'number') { + pieces.push('
        '); + } else { + pieces.push(''); } } } - } - else//outside any list, need to close line.listLevel of lists + } else// outside any list, need to close line.listLevel of lists { context = { - line: line, - lineContent: lineContent, - apool: apool, + line, + lineContent, + apool, attribLine: attribLines[i], text: textLines[i], - padId: pad.id + padId: pad.id, }; await hooks.aCallAll('getLineHTMLForExport', context); @@ -475,46 +437,45 @@ async function getHTMLFromAtext(pad, atext, authorColors) { } 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 - let stylesForExportCSS = ""; - let stylesForExport = await hooks.aCallAll("stylesForExport", padId); - stylesForExport.forEach(function(css){ + let stylesForExportCSS = ''; + const stylesForExport = await hooks.aCallAll('stylesForExport', padId); + stylesForExport.forEach((css) => { stylesForExportCSS += css; }); 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; } - return eejs.require("ep_etherpad-lite/templates/export_html.html", { + return eejs.require('ep_etherpad-lite/templates/export_html.html', { body: html, padId: Security.escapeHTML(padId), - extraCSS: stylesForExportCSS + extraCSS: stylesForExportCSS, }); -} +}; // 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]/; -var _REGEX_SPACE = /\s/; -var _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_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_SPACE = /\s/; +const _REGEX_URLCHAR = new RegExp(`(${/[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source}|${_REGEX_WORDCHAR.source})`); +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], ...] function _findURLs(text) { _REGEX_URL.lastIndex = 0; - var urls = null; - var execResult; - while ((execResult = _REGEX_URL.exec(text))) - { + let urls = null; + let execResult; + while ((execResult = _REGEX_URL.exec(text))) { urls = (urls || []); - var startIndex = execResult.index; - var url = execResult[0]; + const startIndex = execResult.index; + const url = execResult[0]; urls.push([startIndex, url]); } @@ -523,50 +484,46 @@ function _findURLs(text) { // copied from ACE -function _processSpaces(s){ - var doesWrap = true; - if (s.indexOf("<") < 0 && !doesWrap){ +function _processSpaces(s) { + const doesWrap = true; + if (s.indexOf('<') < 0 && !doesWrap) { // short-cut return s.replace(/ /g, ' '); } - var parts = []; - s.replace(/<[^>]*>?| |[^ <]+/g, function (m){ + const parts = []; + s.replace(/<[^>]*>?| |[^ <]+/g, (m) => { parts.push(m); }); - if (doesWrap){ - var endOfLine = true; - var beforeSpace = false; + if (doesWrap) { + let endOfLine = true; + let beforeSpace = false; // last space in a run is normal, others are 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]; - if (p == " "){ + if (p == ' ') { if (endOfLine || beforeSpace) parts[i] = ' '; endOfLine = false; beforeSpace = true; - } - else if (p.charAt(0) != "<"){ + } else if (p.charAt(0) != '<') { endOfLine = false; beforeSpace = false; } } // beginning of line is nbsp - for (i = 0; i < parts.length; i++){ + for (i = 0; i < parts.length; i++) { p = parts[i]; - if (p == " "){ + if (p == ' ') { parts[i] = ' '; break; - } - else if (p.charAt(0) != "<"){ + } else if (p.charAt(0) != '<') { break; } } - } - else - { - for (i = 0; i < parts.length; i++){ + } else { + for (i = 0; i < parts.length; i++) { p = parts[i]; - if (p == " "){ + if (p == ' ') { parts[i] = ' '; } } diff --git a/src/node/utils/ExportTxt.js b/src/node/utils/ExportTxt.js index 7390e2948..9d47896bc 100644 --- a/src/node/utils/ExportTxt.js +++ b/src/node/utils/ExportTxt.js @@ -18,12 +18,12 @@ * limitations under the License. */ -var Changeset = require("ep_etherpad-lite/static/js/Changeset"); -var padManager = require("../db/PadManager"); -var _analyzeLine = require('./ExportHelper')._analyzeLine; +const Changeset = require('ep_etherpad-lite/static/js/Changeset'); +const padManager = require('../db/PadManager'); +const _analyzeLine = require('./ExportHelper')._analyzeLine; // 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; if (revNum != undefined) { @@ -33,57 +33,57 @@ var getPadTXT = async function(pad, revNum) { // convert atext to html return getTXTFromAtext(pad, atext); -} +}; // This is different than the functionality provided in ExportHtml as it provides formatting // functionality that is designed specifically for TXT exports function getTXTFromAtext(pad, atext, authorColors) { - var apool = pad.apool(); - var textLines = atext.text.slice(0, -1).split('\n'); - var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); + const apool = pad.apool(); + const textLines = atext.text.slice(0, -1).split('\n'); + const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); - var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; - var anumMap = {}; - var css = ""; + const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough']; + const anumMap = {}; + const css = ''; - props.forEach(function(propName, i) { - var propTrueNum = apool.putAttrib([propName, true], true); + props.forEach((propName, i) => { + const propTrueNum = apool.putAttrib([propName, true], true); if (propTrueNum >= 0) { anumMap[propTrueNum] = i; } }); function getLineTXT(text, attribs) { - var propVals = [false, false, false]; - var ENTER = 1; - var STAY = 2; - var LEAVE = 0; + const propVals = [false, false, false]; + const ENTER = 1; + const STAY = 2; + const LEAVE = 0; // Use order of tags (b/i/u) as order of nesting, for simplicity // and decent nesting. For example, // Just bold Bold and italics Just italics // becomes // Just bold Bold and italics Just italics - var taker = Changeset.stringIterator(text); - var assem = Changeset.stringAssembler(); + const taker = Changeset.stringIterator(text); + const assem = Changeset.stringAssembler(); - var idx = 0; + let idx = 0; function processNextChars(numChars) { if (numChars <= 0) { return; } - var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars)); + const iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars)); idx += numChars; while (iter.hasNext()) { - var o = iter.next(); + const o = iter.next(); var propChanged = false; - Changeset.eachAttribNumber(o.attribs, function(a) { + Changeset.eachAttribNumber(o.attribs, (a) => { if (a in anumMap) { - var i = anumMap[a]; // i = 0 => bold, etc. + const i = anumMap[a]; // i = 0 => bold, etc. if (!propVals[i]) { propVals[i] = ENTER; @@ -108,20 +108,18 @@ function getTXTFromAtext(pad, atext, authorColors) { // according to what happens at start of span if (propChanged) { // leaving bold (e.g.) also leaves italics, etc. - var left = false; + let left = false; for (var i = 0; i < propVals.length; i++) { - var v = propVals[i]; + const v = propVals[i]; if (!left) { if (v === LEAVE) { left = true; } - } else { - if (v === true) { - // tag will be closed and re-opened - propVals[i] = STAY; - } + } else if (v === true) { + // tag will be closed and re-opened + propVals[i] = STAY; } } @@ -129,11 +127,11 @@ function getTXTFromAtext(pad, atext, authorColors) { for (var i = propVals.length - 1; i >= 0; i--) { if (propVals[i] === LEAVE) { - //emitCloseTag(i); + // emitCloseTag(i); tags2close.push(i); propVals[i] = false; } else if (propVals[i] === STAY) { - //emitCloseTag(i); + // emitCloseTag(i); tags2close.push(i); } } @@ -146,13 +144,13 @@ function getTXTFromAtext(pad, atext, authorColors) { // propVals is now all {true,false} again } // end if (propChanged) - var chars = o.chars; + let chars = o.chars; if (o.lines) { // exclude newline at end of line, if present chars--; } - var s = taker.take(chars); + const s = taker.take(chars); // removes the characters with the code 12. Don't know where they come // from but they break the abiword parser and are completly useless @@ -172,14 +170,13 @@ function getTXTFromAtext(pad, atext, authorColors) { propVals[i] = false; } } - } // end processNextChars processNextChars(text.length - idx); - return(assem.toString()); + return (assem.toString()); } // end getLineHTML - var pieces = [css]; + const pieces = [css]; // Need to deal with constraints imposed on HTML lists; can // 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. // => keeps track of the parents level of indentation - var listNumbers = {}; - var prevListLevel; + const listNumbers = {}; + 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); - var lineContent = getLineTXT(line.text, line.aline); - - if (line.listTypeName == "bullet") { - 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 - for (var key in listNumbers) { + for (const key in listNumbers) { delete listNumbers[key]; } } 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.. - if(!listNumbers[line.listLevel]){ + if (!listNumbers[line.listLevel]) { listNumbers[line.listLevel] = 0; } } - if (line.listTypeName == "number") { + if (line.listTypeName == 'number') { /* * listLevel == amount of indentation * 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 * than current line.listLevel then reset the object value */ - if(line.listLevel < prevListLevel){ + if (line.listLevel < prevListLevel) { delete listNumbers[prevListLevel]; } listNumbers[line.listLevel]++; - if(line.listLevel > 1){ - var x = 1; - while(x <= line.listLevel-1){ - pieces.push(listNumbers[x]+".") + if (line.listLevel > 1) { + let x = 1; + while (x <= line.listLevel - 1) { + pieces.push(`${listNumbers[x]}.`); x++; } } - pieces.push(listNumbers[line.listLevel]+". ") + pieces.push(`${listNumbers[line.listLevel]}. `); prevListLevel = line.listLevel; } @@ -257,7 +253,7 @@ function getTXTFromAtext(pad, atext, authorColors) { exports.getTXTFromAtext = getTXTFromAtext; -exports.getPadTXTDocument = async function(padId, revNum) { - let pad = await padManager.getPad(padId); +exports.getPadTXTDocument = async function (padId, revNum) { + const pad = await padManager.getPad(padId); return getPadTXT(pad, revNum); -} +}; diff --git a/src/node/utils/ImportEtherpad.js b/src/node/utils/ImportEtherpad.js index d2a58c2c9..0c0dbcc7a 100644 --- a/src/node/utils/ImportEtherpad.js +++ b/src/node/utils/ImportEtherpad.js @@ -14,14 +14,14 @@ * limitations under the License. */ -var log4js = require('log4js'); -const db = require("../db/DB"); +const log4js = require('log4js'); +const db = require('../db/DB'); const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); -exports.setPadRaw = function(padId, records) { +exports.setPadRaw = function (padId, records) { records = JSON.parse(records); - Object.keys(records).forEach(async function(key) { + Object.keys(records).forEach(async (key) => { let value = records[key]; if (!value) { @@ -36,7 +36,7 @@ exports.setPadRaw = function(padId, records) { newKey = key; // Does this author already exist? - let author = await db.get(key); + const author = await db.get(key); if (author) { // Yes, add the padID to the author @@ -47,20 +47,20 @@ exports.setPadRaw = function(padId, records) { value = author; } else { // No, create a new array with the author info in - value.padIDs = [ padId ]; + value.padIDs = [padId]; } } else { // Not author data, probably 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 - if (oldPadId[0] === "pad") { + if (oldPadId[0] === 'pad') { // so set the new pad id for the author oldPadId[1] = padId; // 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? @@ -74,4 +74,4 @@ exports.setPadRaw = function(padId, records) { // Write the value to the server await db.set(newKey, value); }); -} +}; diff --git a/src/node/utils/ImportHtml.js b/src/node/utils/ImportHtml.js index 8cbfdbab9..9032eda77 100644 --- a/src/node/utils/ImportHtml.js +++ b/src/node/utils/ImportHtml.js @@ -14,75 +14,75 @@ * limitations under the License. */ -const log4js = require('log4js'); -const Changeset = require("ep_etherpad-lite/static/js/Changeset"); -const contentcollector = require("ep_etherpad-lite/static/js/contentcollector"); -const cheerio = require("cheerio"); -const rehype = require("rehype") -const format = require("rehype-format") +const log4js = require('log4js'); +const Changeset = require('ep_etherpad-lite/static/js/Changeset'); +const contentcollector = require('ep_etherpad-lite/static/js/contentcollector'); +const cheerio = require('cheerio'); +const rehype = require('rehype'); +const format = require('rehype-format'); exports.setPadHTML = async (pad, html) => { - var apiLogger = log4js.getLogger("ImportHtml"); + const apiLogger = log4js.getLogger('ImportHtml'); - var opts = { + const opts = { indentInitial: false, - indent: -1 - } + indent: -1, + }; rehype() - .use(format, opts) - .process(html, function(err, output){ - html = String(output).replace(/(\r\n|\n|\r)/gm,""); - }) + .use(format, opts) + .process(html, (err, output) => { + 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 // below the last line of an import - $('body').append("

        "); + $('body').append('

        '); - var doc = $('html')[0]; + const doc = $('html')[0]; apiLogger.debug('html:'); apiLogger.debug(html); // Convert a dom tree into a list of lines and attribute liens // using the content collector object - var cc = contentcollector.makeContentCollector(true, null, pad.pool); + const cc = contentcollector.makeContentCollector(true, null, pad.pool); try { // we use a try here because if the HTML is bad it will blow up cc.collectContent(doc); - } catch(e) { - apiLogger.warn("HTML was not properly formed", e); + } catch (e) { + apiLogger.warn('HTML was not properly formed', e); // don't process the HTML because it was bad throw e; } - var result = cc.finish(); + const result = cc.finish(); apiLogger.debug('Lines:'); - var i; + let i; for (i = 0; i < result.lines.length; i++) { - apiLogger.debug('Line ' + (i + 1) + ' text: ' + result.lines[i]); - apiLogger.debug('Line ' + (i + 1) + ' attributes: ' + result.lineAttribs[i]); + apiLogger.debug(`Line ${i + 1} text: ${result.lines[i]}`); + apiLogger.debug(`Line ${i + 1} attributes: ${result.lineAttribs[i]}`); } // 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); - 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)*/ ) { - var attribsIter = Changeset.opIterator(attribs); - var textIndex = 0; - var newTextStart = 0; - var newTextEnd = newText.length; + function eachAttribRun(attribs, func /* (startInNewText, endInNewText, attribs)*/) { + const attribsIter = Changeset.opIterator(attribs); + let textIndex = 0; + const newTextStart = 0; + const newTextEnd = newText.length; while (attribsIter.hasNext()) { - var op = attribsIter.next(); - var nextIndex = textIndex + op.chars; + const op = attribsIter.next(); + const nextIndex = textIndex + op.chars; if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) { 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 - var builder = Changeset.builder(1); + const builder = Changeset.builder(1); // assemble each line into the builder - eachAttribRun(newAttribs, function(start, end, attribs) { + eachAttribRun(newAttribs, (start, end, attribs) => { builder.insert(newText.substring(start, end), attribs); }); // 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([ pad.setText('\n'), pad.appendRevision(theChangeset), ]); -} +}; diff --git a/src/node/utils/LibreOffice.js b/src/node/utils/LibreOffice.js index 6ed28210e..74496cabd 100644 --- a/src/node/utils/LibreOffice.js +++ b/src/node/utils/LibreOffice.js @@ -16,18 +16,18 @@ * limitations under the License. */ -var async = require("async"); -var fs = require("fs"); -var log4js = require('log4js'); -var os = require("os"); -var path = require("path"); -var settings = require("./Settings"); -var spawn = require("child_process").spawn; +const async = require('async'); +const fs = require('fs'); +const log4js = require('log4js'); +const os = require('os'); +const path = require('path'); +const settings = require('./Settings'); +const spawn = require('child_process').spawn; // 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 @@ -37,18 +37,18 @@ var libreOfficeLogger = log4js.getLogger('LibreOffice'); * @param {String} type The type to convert into * @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 - 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 - if (path.extname(srcFile).toLowerCase() === ".doc") { - type = "html"; + if (path.extname(srcFile).toLowerCase() === '.doc') { + type = 'html'; } // PDF files need to be converted with LO Draw ref https://github.com/ether/etherpad-lite/issues/4151 - if (path.extname(srcFile).toLowerCase() === ".pdf") { - type = "html:XHTML Draw File" + if (path.extname(srcFile).toLowerCase() === '.pdf') { + type = 'html:XHTML Draw File'; } } @@ -57,58 +57,60 @@ exports.convertFile = function(srcFile, destFile, type, callback) { // to avoid `Error: no export filter for /tmp/xxxx.doc` error if (type === 'doc') { queue.push({ - "srcFile": srcFile, - "destFile": destFile.replace(/\.doc$/, '.odt'), - "type": 'odt', - "callback": function () { - queue.push({"srcFile": srcFile.replace(/\.html$/, '.odt'), "destFile": destFile, "type": type, "callback": callback, "fileExtension": fileExtension }); - } + srcFile, + destFile: destFile.replace(/\.doc$/, '.odt'), + type: 'odt', + callback() { + queue.push({srcFile: srcFile.replace(/\.html$/, '.odt'), destFile, type, callback, fileExtension}); + }, }); } else { - queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback, "fileExtension": fileExtension}); + queue.push({srcFile, destFile, type, callback, fileExtension}); } }; function doConvertTask(task, callback) { - var tmpDir = os.tmpdir(); + const tmpDir = os.tmpdir(); async.series([ /* * use LibreOffice to convert task.srcFile to another format, given in * task.type */ - function(callback) { + function (callback) { 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', '--invisible', '--nologo', '--nolockcheck', '--writer', - '--convert-to', task.type, + '--convert-to', + task.type, task.srcFile, - '--outdir', tmpDir + '--outdir', + tmpDir, ]); // Soffice/libreoffice is buggy and often hangs. // To remedy this we kill the spawned process after a while. const hangTimeout = setTimeout(() => { soffice.stdin.pause(); // required to kill hanging threads soffice.kill(); - }, 120000); - - var stdoutBuffer = ''; + }, 120000); + + let stdoutBuffer = ''; // Delegate the processing of stdout to another function - soffice.stdout.on('data', function(data) { + soffice.stdout.on('data', (data) => { stdoutBuffer += data.toString(); }); // Append error messages to the buffer - soffice.stderr.on('data', function(data) { + soffice.stderr.on('data', (data) => { stdoutBuffer += data.toString(); }); - soffice.on('exit', function(code) { + soffice.on('exit', (code) => { clearTimeout(hangTimeout); if (code != 0) { // Throw an exception if libreoffice failed @@ -117,18 +119,18 @@ function doConvertTask(task, callback) { // if LibreOffice exited succesfully, go on with processing callback(); - }) + }); }, // Move the converted file to the correct place - function(callback) { - var filename = path.basename(task.srcFile); - var sourceFilename = filename.substr(0, filename.lastIndexOf('.')) + '.' + task.fileExtension; - var sourcePath = path.join(tmpDir, sourceFilename); + function (callback) { + const filename = path.basename(task.srcFile); + const sourceFilename = `${filename.substr(0, filename.lastIndexOf('.'))}.${task.fileExtension}`; + const sourcePath = path.join(tmpDir, sourceFilename); libreOfficeLogger.debug(`Renaming ${sourcePath} to ${task.destFile}`); fs.rename(sourcePath, task.destFile, callback); - } - ], function(err) { + }, + ], (err) => { // Invoke the callback for the local queue callback(); diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.js index 8951d5159..0e5d66abc 100644 --- a/src/node/utils/Minify.js +++ b/src/node/utils/Minify.js @@ -19,31 +19,29 @@ * limitations under the License. */ -var ERR = require("async-stacktrace"); -var settings = require('./Settings'); -var async = require('async'); -var fs = require('fs'); -var StringDecoder = require('string_decoder').StringDecoder; -var CleanCSS = require('clean-css'); -var path = require('path'); -var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugin_defs"); -var RequireKernel = require('etherpad-require-kernel'); -var urlutil = require('url'); -var mime = require('mime-types') -var Threads = require('threads') -var log4js = require('log4js'); +const ERR = require('async-stacktrace'); +const settings = require('./Settings'); +const async = require('async'); +const fs = require('fs'); +const StringDecoder = require('string_decoder').StringDecoder; +const CleanCSS = require('clean-css'); +const path = require('path'); +const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs'); +const RequireKernel = require('etherpad-require-kernel'); +const urlutil = require('url'); +const mime = require('mime-types'); +const Threads = require('threads'); +const log4js = require('log4js'); -var logger = log4js.getLogger("Minify"); +const logger = log4js.getLogger('Minify'); -var ROOT_DIR = path.normalize(__dirname + "/../../static/"); -var TAR_PATH = path.join(__dirname, 'tar.json'); -var tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8')); +const ROOT_DIR = path.normalize(`${__dirname}/../../static/`); +const TAR_PATH = path.join(__dirname, 'tar.json'); +const tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8')); -var threadsPool = Threads.Pool(function () { - return Threads.spawn(new Threads.Worker("./MinifyWorker")) -}, 2) +const threadsPool = Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2); -var LIBRARY_WHITELIST = [ +const LIBRARY_WHITELIST = [ 'async', 'js-cookie', 'security', @@ -53,71 +51,68 @@ var LIBRARY_WHITELIST = [ ]; // 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 = {}; function prefixLocalLibraryPath(path) { if (path.charAt(0) == '$') { return path.slice(1); } else { - return LIBRARY_PREFIX + '/' + path; + return `${LIBRARY_PREFIX}/${path}`; } } -for (var key in tar) { +for (const key in tar) { exports.tar[prefixLocalLibraryPath(key)] = tar[key].map(prefixLocalLibraryPath).concat( - tar[key].map(prefixLocalLibraryPath).map(function (p) { - return p.replace(/\.js$/, ''); - }) + tar[key].map(prefixLocalLibraryPath).map((p) => p.replace(/\.js$/, '')), ).concat( - tar[key].map(prefixLocalLibraryPath).map(function (p) { - return p.replace(/\.js$/, '') + '/index.js'; - }) + tar[key].map(prefixLocalLibraryPath).map((p) => `${p.replace(/\.js$/, '')}/index.js`), ); } // 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. 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 = { - url: url - , method: method - , params: {filename: parsedURL.path.replace(/^\/static\//, '')} - , headers: headers + const mockRequest = { + url, + method, + params: {filename: parsedURL.path.replace(/^\/static\//, '')}, + headers, }; - var mockResponse = { - writeHead: function (_status, _headers) { + const mockResponse = { + writeHead(_status, _headers) { status = _status; - for (var header in _headers) { + for (const header in _headers) { if (Object.prototype.hasOwnProperty.call(_headers, header)) { headers[header] = _headers[header]; } } - } - , setHeader: function (header, value) { + }, + setHeader(header, value) { headers[header.toLowerCase()] = value.toString(); - } - , header: function (header, value) { + }, + header(header, value) { headers[header.toLowerCase()] = value.toString(); - } - , write: function (_content) { - _content && content.push(_content); - } - , end: function (_content) { + }, + write(_content) { + _content && content.push(_content); + }, + end(_content) { _content && content.push(_content); callback(status, headers, content.join('')); - } + }, }; minify(mockRequest, mockResponse); } function requestURIs(locations, method, headers, callback) { - var pendingRequests = locations.length; - var responses = []; + let pendingRequests = locations.length; + const responses = []; function respondFor(i) { 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)); } function completed() { - var statuss = responses.map(function (x) {return x[0];}); - var headerss = responses.map(function (x) {return x[1];}); - var contentss = responses.map(function (x) {return x[2];}); + const statuss = responses.map((x) => x[0]); + const headerss = responses.map((x) => x[1]); + const contentss = responses.map((x) => x[2]); callback(statuss, headerss, contentss); } } @@ -146,15 +141,15 @@ function requestURIs(locations, method, headers, callback) { * @param res the Express response */ 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. filename = path.normalize(path.join(ROOT_DIR, filename)); - filename = filename.replace(/\.\./g, '') + filename = filename.replace(/\.\./g, ''); if (filename.indexOf(ROOT_DIR) == 0) { filename = filename.slice(ROOT_DIR.length); - filename = filename.replace(/\\/g, '/') + filename = filename.replace(/\\/g, '/'); } else { res.writeHead(404, {}); res.end(); @@ -166,36 +161,36 @@ function minify(req, res) { are rewritten into ROOT_PATH_OF_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) { - var library = match[1]; - var libraryPath = match[2] || ''; + const library = match[1]; + const libraryPath = match[2] || ''; if (plugins.plugins[library] && match[3]) { - var plugin = plugins.plugins[library]; - var pluginPath = plugin.package.realPath; + const plugin = plugins.plugins[library]; + const pluginPath = plugin.package.realPath; filename = path.relative(ROOT_DIR, pluginPath + libraryPath); filename = filename.replace(/\\/g, '/'); // windows path fix } else if (LIBRARY_WHITELIST.indexOf(library) != -1) { // Go straight into node_modules // Avoid `require.resolve()`, since 'mustache' and 'mustache/index.js' // 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) { date = new Date(date); date.setMilliseconds(0); res.setHeader('last-modified', date.toUTCString()); res.setHeader('date', (new Date()).toUTCString()); 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('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) { res.writeHead(304, {}); res.end(); - } else { - if (req.method == 'HEAD') { - res.header("Content-Type", contentType); - res.writeHead(200, {}); - res.end(); - } else if (req.method == 'GET') { - getFileCompressed(filename, contentType, function (error, content) { - if(ERR(error, function(){ - res.writeHead(500, {}); - res.end(); - })) return; - res.header("Content-Type", contentType); - res.writeHead(200, {}); - res.write(content); + } else if (req.method == 'HEAD') { + res.header('Content-Type', contentType); + res.writeHead(200, {}); + res.end(); + } else if (req.method == 'GET') { + getFileCompressed(filename, contentType, (error, content) => { + if (ERR(error, () => { + res.writeHead(500, {}); res.end(); - }); - } else { - res.writeHead(405, {'allow': 'HEAD, GET'}); + })) return; + res.header('Content-Type', contentType); + res.writeHead(200, {}); + res.write(content); res.end(); - } + }); + } else { + res.writeHead(405, {allow: 'HEAD, GET'}); + res.end(); } }, 3); } // find all includes in ace.js and embed them. function getAceFile(callback) { - fs.readFile(ROOT_DIR + 'js/ace.js', "utf8", function(err, data) { - if(ERR(err, callback)) return; + fs.readFile(`${ROOT_DIR}js/ace.js`, 'utf8', (err, data) => { + if (ERR(err, callback)) return; // 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) { founds = []; } @@ -250,25 +243,25 @@ function getAceFile(callback) { // Request the contents of the included file on the server-side and write // them into the file. - async.forEach(founds, function (item, callback) { - var filename = item.match(/"([^"]*)"/)[1]; + async.forEach(founds, (item, callback) => { + const filename = item.match(/"([^"]*)"/)[1]; // Hostname "invalid.invalid" is a dummy value to allow parsing as a URI. - var baseURI = 'http://invalid.invalid'; - var resourceURI = baseURI + path.normalize(path.join('/static/', filename)); + const baseURI = 'http://invalid.invalid'; + let resourceURI = baseURI + path.normalize(path.join('/static/', filename)); resourceURI = resourceURI.replace(/\\/g, '/'); // Windows (safe generally?) - requestURI(resourceURI, 'GET', {}, function (status, headers, body) { - var error = !(status == 200 || status == 404); + requestURI(resourceURI, 'GET', {}, (status, headers, body) => { + const error = !(status == 200 || status == 404); if (!error) { - data += 'Ace2Editor.EMBEDED[' + JSON.stringify(filename) + '] = ' - + JSON.stringify(status == 200 ? body || '' : null) + ';\n'; + data += `Ace2Editor.EMBEDED[${JSON.stringify(filename)}] = ${ + JSON.stringify(status == 200 ? body || '' : null)};\n`; } else { console.error(`getAceFile(): error getting ${resourceURI}. Status code: ${status}`); } callback(); }); - }, function(error) { + }, (error) => { callback(error, data); }); }); @@ -289,19 +282,19 @@ function statFile(filename, callback, dirStatLimit) { } else if (filename == 'js/ace.js') { // Sometimes static assets are inlined into this file, so we have to stat // everything. - lastModifiedDateOfEverything(function (error, date) { + lastModifiedDateOfEverything((error, date) => { callback(error, date, !error); }); } else if (filename == 'js/require-kernel.js') { callback(null, requireLastModified(), true); } else { - fs.stat(ROOT_DIR + filename, function (error, stats) { + fs.stat(ROOT_DIR + filename, (error, stats) => { if (error) { - if (error.code == "ENOENT") { + if (error.code == 'ENOENT') { // Stat the directory instead. - statFile(path.dirname(filename), function (error, date, exists) { + statFile(path.dirname(filename), (error, date, exists) => { callback(error, date, false); - }, dirStatLimit-1); + }, dirStatLimit - 1); } else { callback(error); } @@ -314,29 +307,28 @@ function statFile(filename, callback, dirStatLimit) { } } function lastModifiedDateOfEverything(callback) { - var folders2check = [ROOT_DIR + 'js/', ROOT_DIR + 'css/']; - var latestModification = 0; - //go trough this two folders - async.forEach(folders2check, function(path, callback) { - //read the files in the folder - fs.readdir(path, function(err, files) { - if(ERR(err, callback)) return; + const folders2check = [`${ROOT_DIR}js/`, `${ROOT_DIR}css/`]; + let latestModification = 0; + // go trough this two folders + async.forEach(folders2check, (path, callback) => { + // read the files in the folder + fs.readdir(path, (err, files) => { + if (ERR(err, callback)) return; - //we wanna check the directory itself for changes too - files.push("."); + // we wanna check the directory itself for changes too + files.push('.'); - //go trough all files in this folder - async.forEach(files, function(filename, callback) { - //get the stat data of this file - fs.stat(path + "/" + filename, function(err, stats) { - if(ERR(err, callback)) return; + // go trough all files in this folder + async.forEach(files, (filename, callback) => { + // get the stat data of this file + fs.stat(`${path}/${filename}`, (err, stats) => { + if (ERR(err, callback)) return; - //get the modification time - var modificationTime = stats.mtime.getTime(); + // get the modification time + const modificationTime = stats.mtime.getTime(); - //compare the modification time to the highest found - if(modificationTime > latestModification) - { + // compare the modification time to the highest found + if (modificationTime > latestModification) { latestModification = modificationTime; } @@ -344,29 +336,29 @@ function lastModifiedDateOfEverything(callback) { }); }, callback); }); - }, function () { + }, () => { callback(null, latestModification); }); } // This should be provided by the module, but until then, just use startup // time. -var _requireLastModified = new Date(); +const _requireLastModified = new Date(); function requireLastModified() { return _requireLastModified.toUTCString(); } function requireDefinition() { - return 'var require = ' + RequireKernel.kernelSource + ';\n'; + return `var require = ${RequireKernel.kernelSource};\n`; } function getFileCompressed(filename, contentType, callback) { - getFile(filename, function (error, content) { + getFile(filename, (error, content) => { if (error || !content || !settings.minify) { callback(error, content); } else if (contentType == 'application/javascript') { - threadsPool.queue(async ({ compressJS }) => { + threadsPool.queue(async ({compressJS}) => { try { - logger.info('Compress JS file %s.', filename) + logger.info('Compress JS file %s.', filename); content = content.toString(); const compressResult = await compressJS(content); @@ -381,11 +373,11 @@ function getFileCompressed(filename, contentType, callback) { } callback(null, content); - }) + }); } else if (contentType == 'text/css') { - threadsPool.queue(async ({ compressCSS }) => { + threadsPool.queue(async ({compressCSS}) => { try { - logger.info('Compress CSS file %s.', filename) + logger.info('Compress CSS file %s.', filename); content = await compressCSS(filename, ROOT_DIR); } catch (error) { @@ -393,7 +385,7 @@ function getFileCompressed(filename, contentType, callback) { } callback(null, content); - }) + }); } else { callback(null, content); } diff --git a/src/node/utils/MinifyWorker.js b/src/node/utils/MinifyWorker.js index 6fbbd24fc..c8bc09eb1 100644 --- a/src/node/utils/MinifyWorker.js +++ b/src/node/utils/MinifyWorker.js @@ -2,10 +2,10 @@ * Worker thread to minify JS & CSS files out of the main NodeJS thread */ -var CleanCSS = require('clean-css'); -var Terser = require("terser"); -var path = require('path'); -var Threads = require('threads') +const CleanCSS = require('clean-css'); +const Terser = require('terser'); +const path = require('path'); +const Threads = require('threads'); function compressJS(content) { return Terser.minify(content); @@ -46,20 +46,20 @@ function compressCSS(filename, ROOT_DIR) { new CleanCSS({ rebase: true, rebaseTo: basePath, - }).minify([absPath], function (errors, minified) { - if (errors) return rej(errors) + }).minify([absPath], (errors, minified) => { + if (errors) return rej(errors); - return res(minified.styles) + return res(minified.styles); }); } catch (error) { // on error, just yield the un-minified original, but write a log message console.error(`Unexpected error minifying ${filename} (${absPath}): ${error}`); callback(null, content); } - }) + }); } Threads.expose({ compressJS, - compressCSS -}) + compressCSS, +}); diff --git a/src/node/utils/NodeVersion.js b/src/node/utils/NodeVersion.js index 1ebbcbca0..f237e6637 100644 --- a/src/node/utils/NodeVersion.js +++ b/src/node/utils/NodeVersion.js @@ -25,17 +25,17 @@ const semver = require('semver'); * * @param {String} minNodeVersion Minimum required Node version */ -exports.enforceMinNodeVersion = function(minNodeVersion) { +exports.enforceMinNodeVersion = function (minNodeVersion) { const currentNodeVersion = process.version; // we cannot use template literals, since we still do not know if we are // running under Node >= 4.0 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); } - 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 {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; if (semver.lt(currentNodeVersion, lowestNonDeprecatedNodeVersion)) { diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index 143d873a5..c4245b77c 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.js @@ -26,17 +26,17 @@ * limitations under the License. */ -var absolutePaths = require('./AbsolutePaths'); -var fs = require("fs"); -var os = require("os"); -var path = require('path'); -var argv = require('./Cli').argv; -var npm = require("npm/lib/npm.js"); -var jsonminify = require("jsonminify"); -var log4js = require("log4js"); -var randomString = require("./randomstring"); -var suppressDisableMsg = " -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n"; -var _ = require("underscore"); +const absolutePaths = require('./AbsolutePaths'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const argv = require('./Cli').argv; +const npm = require('npm/lib/npm.js'); +const jsonminify = require('jsonminify'); +const log4js = require('log4js'); +const randomString = require('./randomstring'); +const suppressDisableMsg = ' -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n'; +const _ = require('underscore'); /* Root path of the installation */ 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 */ -exports.title = "Etherpad"; +exports.title = 'Etherpad'; /** * The app favicon fully specified url, visible e.g. in the browser window */ -exports.favicon = "favicon.ico"; -exports.faviconPad = "../" + exports.favicon; -exports.faviconTimeslider = "../../" + exports.favicon; +exports.favicon = 'favicon.ico'; +exports.faviconPad = `../${exports.favicon}`; +exports.faviconTimeslider = `../../${exports.favicon}`; /* * Skin name. @@ -76,12 +76,12 @@ exports.faviconTimeslider = "../../" + exports.favicon; */ 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 */ -exports.ip = "0.0.0.0"; +exports.ip = '0.0.0.0'; /** * The Port ep-lite should listen to @@ -107,60 +107,60 @@ exports.socketTransportProtocols = ['xhr-polling', 'jsonp-polling', 'htmlfile']; /* * The Type of the database */ -exports.dbType = "dirty"; +exports.dbType = 'dirty'; /** * 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 */ -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 */ exports.padOptions = { - "noColors": false, - "showControls": true, - "showChat": true, - "showLineNumbers": true, - "useMonospaceFont": false, - "userName": false, - "userColor": false, - "rtl": false, - "alwaysShowChat": false, - "chatAndUsers": false, - "lang": "en-gb" + noColors: false, + showControls: true, + showChat: true, + showLineNumbers: true, + useMonospaceFont: false, + userName: false, + userColor: false, + rtl: false, + alwaysShowChat: false, + chatAndUsers: false, + lang: 'en-gb', }, /** * Whether certain shortcut keys are enabled for a user in the pad */ exports.padShortcutEnabled = { - "altF9" : true, - "altC" : true, - "delete" : true, - "cmdShift2" : true, - "return" : true, - "esc" : true, - "cmdS" : true, - "tab" : true, - "cmdZ" : true, - "cmdY" : true, - "cmdB" : true, - "cmdI" : true, - "cmdU" : true, - "cmd5" : true, - "cmdShiftL" : true, - "cmdShiftN" : true, - "cmdShift1" : true, - "cmdShiftC" : true, - "cmdH" : true, - "ctrlHome" : true, - "pageUp" : true, - "pageDown" : true, + altF9: true, + altC: true, + delete: true, + cmdShift2: true, + return: true, + esc: true, + cmdS: true, + tab: true, + cmdZ: true, + cmdY: true, + cmdB: true, + cmdI: true, + cmdU: true, + cmd5: true, + cmdShiftL: true, + cmdShiftN: true, + cmdShift1: true, + cmdShiftC: true, + cmdH: true, + ctrlHome: true, + pageUp: true, + pageDown: true, }, /** @@ -168,20 +168,20 @@ exports.padShortcutEnabled = { */ exports.toolbar = { left: [ - ["bold", "italic", "underline", "strikethrough"], - ["orderedlist", "unorderedlist", "indent", "outdent"], - ["undo", "redo"], - ["clearauthorship"] + ['bold', 'italic', 'underline', 'strikethrough'], + ['orderedlist', 'unorderedlist', 'indent', 'outdent'], + ['undo', 'redo'], + ['clearauthorship'], ], right: [ - ["importexport", "timeslider", "savedrevision"], - ["settings", "embed"], - ["showusers"] + ['importexport', 'timeslider', 'savedrevision'], + ['settings', 'embed'], + ['showusers'], ], 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 @@ -196,7 +196,7 @@ exports.editOnly = false; /** * 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 @@ -226,7 +226,7 @@ exports.allowUnknownFileEnds = true; /** * The log level of log4js */ -exports.loglevel = "INFO"; +exports.loglevel = 'INFO'; /** * Disable IP logging @@ -251,7 +251,7 @@ exports.indentationOnNewLine = true; /* * log4js appender configuration */ -exports.logconfig = { appenders: [{ type: "console" }]}; +exports.logconfig = {appenders: [{type: 'console'}]}; /* * Session Key, do not sure this. @@ -303,28 +303,28 @@ exports.scrollWhenFocusLineIsOutOfViewport = { /* * Percentage of viewport height to be additionally scrolled. */ - "percentage": { - "editionAboveViewport": 0, - "editionBelowViewport": 0 + percentage: { + editionAboveViewport: 0, + editionBelowViewport: 0, }, /* * Time (in milliseconds) used to animate the scroll transition. Set to 0 to * disable animation */ - "duration": 0, + duration: 0, /* * Percentage of viewport height to be additionally scrolled when user presses arrow up * 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 * line of the viewport */ - "scrollWhenCaretIsInTheLastLineOfViewport": false + scrollWhenCaretIsInTheLastLineOfViewport: false, }; /* @@ -350,10 +350,10 @@ exports.customLocaleStrings = {}; */ exports.importExportRateLimiting = { // duration of the rate limit window (milliseconds) - "windowMs": 90000, + windowMs: 90000, // 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 = { // duration of the rate limit window (seconds) - "duration": 1, + duration: 1, // 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; // checks if abiword is avaiable -exports.abiwordAvailable = function() { +exports.abiwordAvailable = function () { if (exports.abiword != null) { - return os.type().indexOf("Windows") != -1 ? "withoutPDF" : "yes"; + return os.type().indexOf('Windows') != -1 ? 'withoutPDF' : 'yes'; } else { - return "no"; + return 'no'; } }; -exports.sofficeAvailable = function() { +exports.sofficeAvailable = function () { if (exports.soffice != null) { - return os.type().indexOf("Windows") != -1 ? "withoutPDF": "yes"; + return os.type().indexOf('Windows') != -1 ? 'withoutPDF' : 'yes'; } else { - return "no"; + return 'no'; } }; -exports.exportAvailable = function() { - var abiword = exports.abiwordAvailable(); - var soffice = exports.sofficeAvailable(); +exports.exportAvailable = function () { + const abiword = exports.abiwordAvailable(); + const soffice = exports.sofficeAvailable(); - if (abiword == "no" && soffice == "no") { - return "no"; - } else if ((abiword == "withoutPDF" && soffice == "no") || (abiword == "no" && soffice == "withoutPDF")) { - return "withoutPDF"; + if (abiword == 'no' && soffice == 'no') { + return 'no'; + } else if ((abiword == 'withoutPDF' && soffice == 'no') || (abiword == 'no' && soffice == 'withoutPDF')) { + return 'withoutPDF'; } else { - return "yes"; + return 'yes'; } }; // Provide git version if available -exports.getGitCommit = function() { - var version = ""; +exports.getGitCommit = function () { + let version = ''; try { - var rootPath = exports.root; - if (fs.lstatSync(rootPath + '/.git').isFile()) { - rootPath = fs.readFileSync(rootPath + '/.git', "utf8"); + let rootPath = exports.root; + if (fs.lstatSync(`${rootPath}/.git`).isFile()) { + rootPath = fs.readFileSync(`${rootPath}/.git`, 'utf8'); rootPath = rootPath.split(' ').pop().trim(); } else { rootPath += '/.git'; } - var ref = fs.readFileSync(rootPath + "/HEAD", "utf-8"); - if (ref.startsWith("ref: ")) { - var refPath = rootPath + "/" + ref.substring(5, ref.indexOf("\n")); - version = fs.readFileSync(refPath, "utf-8"); + const ref = fs.readFileSync(`${rootPath}/HEAD`, 'utf-8'); + if (ref.startsWith('ref: ')) { + const refPath = `${rootPath}/${ref.substring(5, ref.indexOf('\n'))}`; + version = fs.readFileSync(refPath, 'utf-8'); } else { version = ref; } version = version.substring(0, 7); - } catch(e) { - console.warn("Can't get git version for server header\n" + e.message) + } catch (e) { + console.warn(`Can't get git version for server header\n${e.message}`); } return version; -} +}; // Return etherpad version from package.json -exports.getEpVersion = function() { +exports.getEpVersion = function () { return require('ep_etherpad-lite/package.json').version; -} +}; /** * 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". */ function storeSettings(settingsObj) { - for (var i in settingsObj) { + for (const i in settingsObj) { // 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}'`); } @@ -482,26 +482,26 @@ function storeSettings(settingsObj) { * in the literal string "null", instead. */ function coerceValue(stringValue) { - // 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)); + // 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)); - if (isNumeric) { - // detected numeric string. Coerce to a number + if (isNumeric) { + // detected numeric string. Coerce to a number - return +stringValue; - } + return +stringValue; + } - // the boolean literal case is easy. - if (stringValue === "true" ) { - return true; - } + // the boolean literal case is easy. + if (stringValue === 'true') { + return true; + } - if (stringValue === "false") { - return false; - } + if (stringValue === 'false') { + return false; + } - // otherwise, return this value as-is - return stringValue; + // otherwise, return this value as-is + return stringValue; } /** @@ -624,24 +624,24 @@ function lookupEnvironmentVariables(obj) { * The isSettings variable only controls the error logging. */ function parseSettings(settingsFilename, isSettings) { - let settingsStr = ""; + let settingsStr = ''; let settingsType, notFoundMessage, notFoundFunction; if (isSettings) { - settingsType = "settings"; - notFoundMessage = "Continuing using defaults!"; + settingsType = 'settings'; + notFoundMessage = 'Continuing using defaults!'; notFoundFunction = console.warn; } else { - settingsType = "credentials"; - notFoundMessage = "Ignoring."; + settingsType = 'credentials'; + notFoundMessage = 'Ignoring.'; notFoundFunction = console.info; } try { - //read the settings file + // read the settings file settingsStr = fs.readFileSync(settingsFilename).toString(); - } catch(e) { + } catch (e) { notFoundFunction(`No ${settingsType} file found in ${settingsFilename}. ${notFoundMessage}`); // or maybe undefined! @@ -649,7 +649,7 @@ function parseSettings(settingsFilename, isSettings) { } try { - settingsStr = jsonminify(settingsStr).replace(",]","]").replace(",}","}"); + settingsStr = jsonminify(settingsStr).replace(',]', ']').replace(',}', '}'); const settings = JSON.parse(settingsStr); @@ -658,7 +658,7 @@ function parseSettings(settingsFilename, isSettings) { const replacedSettings = lookupEnvironmentVariables(settings); return replacedSettings; - } catch(e) { + } catch (e) { console.error(`There was an error processing your ${settingsType} file from ${settingsFilename}: ${e.message}`); process.exit(1); @@ -667,55 +667,55 @@ function parseSettings(settingsFilename, isSettings) { exports.reloadSettings = function reloadSettings() { // 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 - var credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || "credentials.json"); + const credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || 'credentials.json'); // try to parse the settings - var settings = parseSettings(settingsFilename, true); + const settings = parseSettings(settingsFilename, true); // try to parse the credentials - var credentials = parseSettings(credentialsFilename, false); + const credentials = parseSettings(credentialsFilename, false); storeSettings(settings); storeSettings(credentials); - log4js.configure(exports.logconfig);//Configure the logging appenders - log4js.setGlobalLogLevel(exports.loglevel);//set loglevel - process.env['DEBUG'] = 'socket.io:' + exports.loglevel; // Used by SocketIO for Debug + log4js.configure(exports.logconfig);// Configure the logging appenders + log4js.setGlobalLogLevel(exports.loglevel);// set loglevel + process.env.DEBUG = `socket.io:${exports.loglevel}`; // Used by SocketIO for Debug log4js.replaceConsole(); 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".`); - exports.skinName = "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'; } // checks if skinName has an acceptable value, otherwise falls back to "colibris" 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; 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".`); - exports.skinName = "colibris"; + exports.skinName = 'colibris'; } // 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! if (absolutePaths.isSubdir(skinBasePath, skinPath) === false) { 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); } if (fs.existsSync(skinPath) === false) { 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); } @@ -725,13 +725,13 @@ exports.reloadSettings = function reloadSettings() { if (exports.abiword) { // Check abiword actually exists if (exports.abiword != null) { - fs.exists(exports.abiword, function(exists) { + fs.exists(exports.abiword, (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) { - 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; } }); @@ -739,46 +739,46 @@ exports.reloadSettings = function reloadSettings() { } if (exports.soffice) { - fs.exists(exports.soffice, function(exists) { + fs.exists(exports.soffice, (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) { - 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; } }); } if (!exports.sessionKey) { - var sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || "./SESSIONKEY.txt"); + const sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || './SESSIONKEY.txt'); try { - exports.sessionKey = fs.readFileSync(sessionkeyFilename,"utf8"); + exports.sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8'); console.info(`Session key loaded from: ${sessionkeyFilename}`); - } catch(e) { + } catch (e) { console.info(`Session key file "${sessionkeyFilename}" not found. Creating with random contents.`); exports.sessionKey = randomString(32); - fs.writeFileSync(sessionkeyFilename,exports.sessionKey,"utf8"); + fs.writeFileSync(sessionkeyFilename, exports.sessionKey, 'utf8'); } } 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") { - var dirtyWarning = "DirtyDB is used. This is fine for testing but not recommended for production."; + if (exports.dbType === 'dirty') { + const dirtyWarning = 'DirtyDB is used. This is fine for testing but not recommended for production.'; 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); - 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 - 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.'); } }; diff --git a/src/node/utils/TidyHtml.js b/src/node/utils/TidyHtml.js index 26d48a62f..42e3e3547 100644 --- a/src/node/utils/TidyHtml.js +++ b/src/node/utils/TidyHtml.js @@ -2,42 +2,41 @@ * Tidy up the HTML in a given file */ -var log4js = require('log4js'); -var settings = require('./Settings'); -var spawn = require('child_process').spawn; +const log4js = require('log4js'); +const settings = require('./Settings'); +const spawn = require('child_process').spawn; -exports.tidy = function(srcFile) { - var logger = log4js.getLogger('TidyHtml'); +exports.tidy = function (srcFile) { + const logger = log4js.getLogger('TidyHtml'); return new Promise((resolve, reject) => { - // Don't do anything if Tidy hasn't been enabled if (!settings.tidyHtml) { logger.debug('tidyHtml has not been configured yet, ignoring tidy request'); return resolve(null); } - var errMessage = ''; + let errMessage = ''; // Spawn a new tidy instance that cleans up the file inline - logger.debug('Tidying ' + srcFile); - var tidy = spawn(settings.tidyHtml, ['-modify', srcFile]); + logger.debug(`Tidying ${srcFile}`); + const tidy = spawn(settings.tidyHtml, ['-modify', srcFile]); // Keep track of any error messages - tidy.stderr.on('data', function (data) { + tidy.stderr.on('data', (data) => { 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 // the file could be tidied but a few warnings were generated if (code === 0 || code === 1) { - logger.debug('Tidied ' + srcFile + ' successfully'); + logger.debug(`Tidied ${srcFile} successfully`); resolve(null); } else { - logger.error('Failed to tidy ' + srcFile + '\n' + errMessage); - reject('Tidy died with exit code ' + code); + logger.error(`Failed to tidy ${srcFile}\n${errMessage}`); + reject(`Tidy died with exit code ${code}`); } }); }); -} +}; diff --git a/src/node/utils/UpdateCheck.js b/src/node/utils/UpdateCheck.js index 640aab719..8332ff204 100644 --- a/src/node/utils/UpdateCheck.js +++ b/src/node/utils/UpdateCheck.js @@ -5,8 +5,8 @@ const request = require('request'); let infos; function loadEtherpadInformations() { - return new Promise(function(resolve, reject) { - request('https://static.etherpad.org/info.json', function (er, response, body) { + return new Promise((resolve, reject) => { + request('https://static.etherpad.org/info.json', (er, response, body) => { if (er) return reject(er); try { @@ -16,29 +16,29 @@ function loadEtherpadInformations() { return reject(err); } }); - }) + }); } -exports.getLatestVersion = function() { +exports.getLatestVersion = function () { exports.needsUpdate(); return infos.latestVersion; -} +}; -exports.needsUpdate = function(cb) { - loadEtherpadInformations().then(function(info) { +exports.needsUpdate = function (cb) { + loadEtherpadInformations().then((info) => { if (semver.gt(info.latestVersion, settings.getEpVersion())) { if (cb) return cb(true); } - }).catch(function (err) { - console.error('Can not perform Etherpad update check: ' + err) + }).catch((err) => { + console.error(`Can not perform Etherpad update check: ${err}`); if (cb) return cb(false); - }) -} + }); +}; -exports.check = function() { - exports.needsUpdate(function (needsUpdate) { +exports.check = function () { + exports.needsUpdate((needsUpdate) => { if (needsUpdate) { - console.warn('Update available: Download the actual version ' + infos.latestVersion) + console.warn(`Update available: Download the actual version ${infos.latestVersion}`); } - }) -} + }); +}; diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.js index 6df4faa97..8bb3ab00d 100644 --- a/src/node/utils/caching_middleware.js +++ b/src/node/utils/caching_middleware.js @@ -14,13 +14,13 @@ * limitations under the License. */ -var async = require('async'); -var Buffer = require('buffer').Buffer; -var fs = require('fs'); -var path = require('path'); -var zlib = require('zlib'); -var settings = require('./Settings'); -var existsSync = require('./path_exists'); +const async = require('async'); +const Buffer = require('buffer').Buffer; +const fs = require('fs'); +const path = require('path'); +const zlib = require('zlib'); +const settings = require('./Settings'); +const existsSync = require('./path_exists'); /* * The crypto module can be absent on reduced node installations. @@ -42,13 +42,13 @@ try { _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; -var responseCache = {}; +const responseCache = {}; 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)}`; } @@ -81,23 +81,23 @@ function CachingMiddleware() { } CachingMiddleware.prototype = new function () { 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); } - var old_req = {}; - var old_res = {}; + const old_req = {}; + const old_res = {}; - var supportsGzip = + const supportsGzip = (req.get('Accept-Encoding') || '').indexOf('gzip') != -1; - var path = require('url').parse(req.url).path; - var cacheKey = generateCacheKey(path); + const path = require('url').parse(req.url).path; + const cacheKey = generateCacheKey(path); - fs.stat(CACHE_DIR + 'minified_' + cacheKey, function (error, stats) { - var modifiedSince = (req.headers['if-modified-since'] - && new Date(req.headers['if-modified-since'])); - var lastModifiedCache = !error && stats.mtime; + fs.stat(`${CACHE_DIR}minified_${cacheKey}`, (error, stats) => { + const modifiedSince = (req.headers['if-modified-since'] && + new Date(req.headers['if-modified-since'])); + const lastModifiedCache = !error && stats.mtime; if (lastModifiedCache && responseCache[cacheKey]) { req.headers['if-modified-since'] = lastModifiedCache.toUTCString(); } else { @@ -108,13 +108,13 @@ CachingMiddleware.prototype = new function () { old_req.method = req.method; req.method = 'GET'; - var expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {})['expires']); + const expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {}).expires); if (expirationDate > new Date()) { // Our cached version is still valid. return respond(); } - var _headers = {}; + const _headers = {}; old_res.setHeader = res.setHeader; res.setHeader = function (key, value) { // Don't set cookies, see issue #707 @@ -126,46 +126,46 @@ CachingMiddleware.prototype = new function () { old_res.writeHead = res.writeHead; res.writeHead = function (status, headers) { - var lastModified = (res.getHeader('last-modified') - && new Date(res.getHeader('last-modified'))); + const lastModified = (res.getHeader('last-modified') && + new Date(res.getHeader('last-modified'))); res.writeHead = old_res.writeHead; if (status == 200) { // Update cache - var buffer = ''; + let buffer = ''; - Object.keys(headers || {}).forEach(function (key) { + Object.keys(headers || {}).forEach((key) => { res.setHeader(key, headers[key]); }); headers = _headers; old_res.write = res.write; old_res.end = res.end; - res.write = function(data, encoding) { + res.write = function (data, encoding) { buffer += data.toString(encoding); }; - res.end = function(data, encoding) { + res.end = function (data, encoding) { async.parallel([ function (callback) { - var path = CACHE_DIR + 'minified_' + cacheKey; - fs.writeFile(path, buffer, function (error, stats) { + const path = `${CACHE_DIR}minified_${cacheKey}`; + fs.writeFile(path, buffer, (error, stats) => { callback(); }); - } - , function (callback) { - var path = CACHE_DIR + 'minified_' + cacheKey + '.gz'; - zlib.gzip(buffer, function(error, content) { + }, + function (callback) { + const path = `${CACHE_DIR}minified_${cacheKey}.gz`; + zlib.gzip(buffer, (error, content) => { if (error) { callback(); } else { - fs.writeFile(path, content, function (error, stats) { + fs.writeFile(path, content, (error, stats) => { callback(); }); } }); - } - ], function () { - responseCache[cacheKey] = {statusCode: status, headers: headers}; + }, + ], () => { + responseCache[cacheKey] = {statusCode: status, headers}; respond(); }); }; @@ -173,8 +173,8 @@ CachingMiddleware.prototype = new function () { // Nothing new changed from the cached version. old_res.write = res.write; old_res.end = res.end; - res.write = function(data, encoding) {}; - res.end = function(data, encoding) { respond(); }; + res.write = function (data, encoding) {}; + res.end = function (data, encoding) { respond(); }; } else { res.writeHead(status, headers); } @@ -191,24 +191,24 @@ CachingMiddleware.prototype = new function () { res.write = old_res.write || res.write; res.end = old_res.end || res.end; - let headers = {}; + const 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'])) { - pathStr = pathStr + '.gz'; + pathStr += '.gz'; headers['content-encoding'] = 'gzip'; } - var lastModified = (headers['last-modified'] - && new Date(headers['last-modified'])); + const lastModified = (headers['last-modified'] && + new Date(headers['last-modified'])); if (statusCode == 200 && lastModified <= modifiedSince) { res.writeHead(304, headers); res.end(); } else if (req.method == 'GET') { - var readStream = fs.createReadStream(pathStr); + const readStream = fs.createReadStream(pathStr); res.writeHead(statusCode, headers); readStream.pipe(res); } else { diff --git a/src/node/utils/padDiff.js b/src/node/utils/padDiff.js index 3018813f7..2fc36660e 100644 --- a/src/node/utils/padDiff.js +++ b/src/node/utils/padDiff.js @@ -1,17 +1,17 @@ -var Changeset = require("../../static/js/Changeset"); -var exportHtml = require('./ExportHtml'); +const Changeset = require('../../static/js/Changeset'); +const exportHtml = require('./ExportHtml'); -function PadDiff (pad, fromRev, toRev) { +function PadDiff(pad, fromRev, toRev) { // check parameters if (!pad || !pad.id || !pad.atext || !pad.pool) { throw new Error('Invalid pad'); } - var range = pad.getValidRevisionRange(fromRev, toRev); + const range = pad.getValidRevisionRange(fromRev, toRev); if (!range) { - throw new Error('Invalid revision range.' + - ' startRev: ' + fromRev + - ' endRev: ' + toRev); + throw new Error(`${'Invalid revision range.' + + ' startRev: '}${fromRev + } endRev: ${toRev}`); } this._pad = pad; @@ -21,12 +21,12 @@ function PadDiff (pad, fromRev, toRev) { this._authors = []; } -PadDiff.prototype._isClearAuthorship = function(changeset) { +PadDiff.prototype._isClearAuthorship = function (changeset) { // unpack - var unpacked = Changeset.unpack(changeset); + const unpacked = Changeset.unpack(changeset); // check if there is nothing in the charBank - if (unpacked.charBank !== "") { + if (unpacked.charBank !== '') { return false; } @@ -36,10 +36,10 @@ PadDiff.prototype._isClearAuthorship = function(changeset) { } // 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 - var clearOperator = iterator.next(); + const clearOperator = iterator.next(); // check if there is only one operator if (iterator.hasNext() === true) { @@ -47,18 +47,18 @@ PadDiff.prototype._isClearAuthorship = function(changeset) { } // check if this operator doesn't change text - if (clearOperator.opcode !== "=") { + if (clearOperator.opcode !== '=') { return false; } // 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 (clearOperator.chars !== unpacked.oldLen-1 && clearOperator.chars !== unpacked.oldLen) { + if (clearOperator.chars !== unpacked.oldLen - 1 && clearOperator.chars !== unpacked.oldLen) { return false; } - var attributes = []; - Changeset.eachAttribNumber(changeset, function(attrNum) { + const attributes = []; + Changeset.eachAttribNumber(changeset, (attrNum) => { attributes.push(attrNum); }); @@ -67,90 +67,84 @@ PadDiff.prototype._isClearAuthorship = function(changeset) { 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 - if (appliedAttribute[0] !== "author" || appliedAttribute[1] !== "") { + if (appliedAttribute[0] !== 'author' || appliedAttribute[1] !== '') { return false; } return true; }; -PadDiff.prototype._createClearAuthorship = async function(rev) { - - let atext = await this._pad.getInternalRevisionAText(rev); +PadDiff.prototype._createClearAuthorship = async function (rev) { + const atext = await this._pad.getInternalRevisionAText(rev); // build clearAuthorship changeset - var builder = Changeset.builder(atext.text.length); - builder.keepText(atext.text, [['author','']], this._pad.pool); - var changeset = builder.toString(); + const builder = Changeset.builder(atext.text.length); + builder.keepText(atext.text, [['author', '']], this._pad.pool); + const changeset = builder.toString(); return changeset; -} - -PadDiff.prototype._createClearStartAtext = async function(rev) { +}; +PadDiff.prototype._createClearStartAtext = async function (rev) { // get the atext of this revision - let atext = this._pad.getInternalRevisionAText(rev); + const atext = this._pad.getInternalRevisionAText(rev); // create the clearAuthorship changeset - let changeset = await this._createClearAuthorship(rev); + const changeset = await this._createClearAuthorship(rev); // apply the clearAuthorship changeset - let newAText = Changeset.applyToAText(changeset, atext, this._pad.pool); + const newAText = Changeset.applyToAText(changeset, atext, this._pad.pool); return newAText; -} - -PadDiff.prototype._getChangesetsInBulk = async function(startRev, count) { +}; +PadDiff.prototype._getChangesetsInBulk = async function (startRev, count) { // find out which revisions we need - let revisions = []; + const revisions = []; for (let i = startRev; i < (startRev + count) && i <= this._pad.head; i++) { revisions.push(i); } // get all needed revisions (in parallel) - let changesets = [], authors = []; - await Promise.all(revisions.map(rev => { - return this._pad.getRevision(rev).then(revision => { - let arrayNum = rev - startRev; - changesets[arrayNum] = revision.changeset; - authors[arrayNum] = revision.meta.author; - }); - })); + const changesets = []; const + authors = []; + await Promise.all(revisions.map((rev) => this._pad.getRevision(rev).then((revision) => { + const arrayNum = rev - startRev; + changesets[arrayNum] = revision.changeset; + authors[arrayNum] = revision.meta.author; + }))); - return { changesets, authors }; -} + return {changesets, authors}; +}; -PadDiff.prototype._addAuthors = function(authors) { - var self = this; +PadDiff.prototype._addAuthors = function (authors) { + const self = this; // add to array if not in the array - authors.forEach(function(author) { + authors.forEach((author) => { if (self._authors.indexOf(author) == -1) { self._authors.push(author); } }); }; -PadDiff.prototype._createDiffAtext = async function() { - - let bulkSize = 100; +PadDiff.prototype._createDiffAtext = async function () { + const bulkSize = 100; // get the cleaned startAText let atext = await this._createClearStartAtext(this._fromRev); let superChangeset = null; - let rev = this._fromRev + 1; + const rev = this._fromRev + 1; for (let rev = this._fromRev + 1; rev <= this._toRev; rev += bulkSize) { - // 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 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 (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 atext = Changeset.applyToAText(superChangeset, atext, this._pad.pool); @@ -190,59 +184,57 @@ PadDiff.prototype._createDiffAtext = async function() { } return atext; -} - -PadDiff.prototype.getHtml = async function() { +}; +PadDiff.prototype.getHtml = async function () { // cache the html if (this._html != null) { return this._html; } // get the diff atext - let atext = await this._createDiffAtext(); + const atext = await this._createDiffAtext(); // get the authorColor table - let authorColors = await this._pad.getAllAuthorColors(); + const authorColors = await this._pad.getAllAuthorColors(); // convert the atext to html this._html = await exportHtml.getHTMLFromAtext(this._pad, atext, authorColors); 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 if (this._html == null) { await this.getHtml(); } return self._authors; -} +}; -PadDiff.prototype._extendChangesetWithAuthor = function(changeset, author, apool) { +PadDiff.prototype._extendChangesetWithAuthor = function (changeset, author, apool) { // unpack - var unpacked = Changeset.unpack(changeset); + const unpacked = Changeset.unpack(changeset); - var iterator = Changeset.opIterator(unpacked.ops); - var assem = Changeset.opAssembler(); + const iterator = Changeset.opIterator(unpacked.ops); + const assem = Changeset.opAssembler(); // create deleted attribs - var authorAttrib = apool.putAttrib(["author", author || ""]); - var deletedAttrib = apool.putAttrib(["removed", true]); - var attribs = "*" + Changeset.numToString(authorAttrib) + "*" + Changeset.numToString(deletedAttrib); + const authorAttrib = apool.putAttrib(['author', author || '']); + const deletedAttrib = apool.putAttrib(['removed', true]); + const attribs = `*${Changeset.numToString(authorAttrib)}*${Changeset.numToString(deletedAttrib)}`; // iteratore over the operators of the changeset - while(iterator.hasNext()) { - var operator = iterator.next(); + while (iterator.hasNext()) { + const operator = iterator.next(); - if (operator.opcode === "-") { + if (operator.opcode === '-') { // this is a delete operator, extend it with the author 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 - operator.attribs+="*"+Changeset.numToString(authorAttrib); + operator.attribs += `*${Changeset.numToString(authorAttrib)}`; } // 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. -PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { - var lines = Changeset.splitTextLines(startAText.text); - var alines = Changeset.splitAttributionLines(startAText.attribs, startAText.text); +PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) { + const lines = Changeset.splitTextLines(startAText.text); + const alines = Changeset.splitAttributionLines(startAText.attribs, startAText.text); // lines and alines are what the exports is meant to apply to. // 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; - var curChar = 0; - var curLineOpIter = null; - var curLineOpIterLine; - var curLineNextOp = Changeset.newOp('+'); + let curLine = 0; + let curChar = 0; + let curLineOpIter = null; + let curLineOpIterLine; + const curLineNextOp = Changeset.newOp('+'); - var unpacked = Changeset.unpack(cs); - var csIter = Changeset.opIterator(unpacked.ops); - var builder = Changeset.builder(unpacked.newLen); - - function consumeAttribRuns(numChars, func /*(len, attribs, endsLine)*/ ) { + const unpacked = Changeset.unpack(cs); + const csIter = Changeset.opIterator(unpacked.ops); + const builder = Changeset.builder(unpacked.newLen); + function consumeAttribRuns(numChars, func /* (len, attribs, endsLine)*/) { if ((!curLineOpIter) || (curLineOpIterLine != curLine)) { // create curLineOpIter and advance it to curChar curLineOpIter = Changeset.opIterator(alines_get(curLine)); curLineOpIterLine = curLine; - var indexIntoLine = 0; - var done = false; + let indexIntoLine = 0; + let done = false; while (!done) { curLineOpIter.next(curLineNextOp); if (indexIntoLine + curLineNextOp.chars >= curChar) { @@ -320,7 +311,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { 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); numChars -= charsToUse; @@ -338,26 +329,24 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { if (L) { curLine += L; curChar = 0; + } else if (curLineOpIter && curLineOpIterLine == curLine) { + consumeAttribRuns(N, () => {}); } else { - if (curLineOpIter && curLineOpIterLine == curLine) { - consumeAttribRuns(N, function () {}); - } else { - curChar += N; - } + curChar += N; } } function nextText(numChars) { - var len = 0; - var assem = Changeset.stringAssembler(); - var firstString = lines_get(curLine).substring(curChar); + let len = 0; + const assem = Changeset.stringAssembler(); + const firstString = lines_get(curLine).substring(curChar); len += firstString.length; assem.append(firstString); - var lineNum = curLine + 1; + let lineNum = curLine + 1; while (len < numChars) { - var nextString = lines_get(lineNum); + const nextString = lines_get(lineNum); len += nextString.length; assem.append(nextString); lineNum++; @@ -367,7 +356,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { } function cachedStrFunc(func) { - var cache = {}; + const cache = {}; return function (s) { if (!cache[s]) { @@ -377,8 +366,8 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { }; } - var attribKeys = []; - var attribValues = []; + const attribKeys = []; + const attribValues = []; // iterate over all operators of this changeset 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. // 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 != "*") { - var deletedAttrib = apool.putAttrib(["removed", true]); - var authorAttrib = apool.putAttrib(["author", ""]); + if (csOp.attribs && textBank != '*') { + const deletedAttrib = apool.putAttrib(['removed', true]); + var authorAttrib = apool.putAttrib(['author', '']); attribKeys.length = 0; attribValues.length = 0; - Changeset.eachAttribNumber(csOp.attribs, function (n) { + Changeset.eachAttribNumber(csOp.attribs, (n) => { attribKeys.push(apool.getAttribKey(n)); attribValues.push(apool.getAttribValue(n)); - if (apool.getAttribKey(n) === "author") { + if (apool.getAttribKey(n) === 'author') { authorAttrib = n; } }); - var undoBackToAttribs = cachedStrFunc(function (attribs) { - var backAttribs = []; - for (var i = 0; i < attribKeys.length; i++) { - var appliedKey = attribKeys[i]; - var appliedValue = attribValues[i]; - var oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, apool); + var undoBackToAttribs = cachedStrFunc((attribs) => { + const backAttribs = []; + for (let i = 0; i < attribKeys.length; i++) { + const appliedKey = attribKeys[i]; + const appliedValue = attribValues[i]; + const oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, apool); if (appliedValue != oldValue) { backAttribs.push([appliedKey, oldValue]); @@ -419,21 +408,21 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, 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 - var lengthToProcess = textLeftToProcess.indexOf("\n"); - var lineBreak = false; - switch(lengthToProcess) { + let lengthToProcess = textLeftToProcess.indexOf('\n'); + let lineBreak = false; + switch (lengthToProcess) { case -1: - lengthToProcess=textLeftToProcess.length; + lengthToProcess = textLeftToProcess.length; break; case 0: lineBreak = true; - lengthToProcess=1; + lengthToProcess = 1; 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 // consume the attributes of this linebreak - consumeAttribRuns(1, function() {}); + consumeAttribRuns(1, () => {}); } else { // add the old text via an insert, but add a deletion attribute + the author attribute of the author who deleted it var textBankIndex = 0; - consumeAttribRuns(lengthToProcess, function (len, attribs, endsLine) { + consumeAttribRuns(lengthToProcess, (len, attribs, endsLine) => { // get the old attributes back - var attribs = (undoBackToAttribs(attribs) || "") + oldAttribsAddition; + var attribs = (undoBackToAttribs(attribs) || '') + oldAttribsAddition; builder.insert(processText.substr(textBankIndex, len), attribs); textBankIndex += len; @@ -471,7 +460,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) { var textBank = nextText(csOp.chars); 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); textBankIndex += len; }); diff --git a/src/node/utils/path_exists.js b/src/node/utils/path_exists.js index c2d43f6c2..18dc35270 100644 --- a/src/node/utils/path_exists.js +++ b/src/node/utils/path_exists.js @@ -1,15 +1,15 @@ -var fs = require('fs'); +const fs = require('fs'); -var check = function(path) { - var existsSync = fs.statSync || fs.existsSync || path.existsSync; +const check = function (path) { + const existsSync = fs.statSync || fs.existsSync || path.existsSync; - var result; + let result; try { result = existsSync(path); } catch (e) { result = false; } return result; -} +}; module.exports = check; diff --git a/src/node/utils/promises.js b/src/node/utils/promises.js index bb973befa..60c1cff2a 100644 --- a/src/node/utils/promises.js +++ b/src/node/utils/promises.js @@ -13,7 +13,7 @@ exports.firstSatisfies = (promises, predicate) => { // value does not satisfy `predicate`. These transformed Promises will be passed to Promise.race, // yielding the first resolved value that satisfies `predicate`. 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 // `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(); }); const promises = []; - for (var i = 0; i < concurrency && i < total; i++) { + for (let i = 0; i < concurrency && i < total; i++) { promises.push(addAnother()); } await Promise.all(promises); -} +}; diff --git a/src/node/utils/randomstring.js b/src/node/utils/randomstring.js index f12bd7f78..622b0082d 100644 --- a/src/node/utils/randomstring.js +++ b/src/node/utils/randomstring.js @@ -1,10 +1,10 @@ /** * 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) { - return crypto.randomBytes(len).toString('hex') +const randomString = function (len) { + return crypto.randomBytes(len).toString('hex'); }; module.exports = randomString; diff --git a/src/node/utils/toolbar.js b/src/node/utils/toolbar.js index a2b8f5579..9e036ab47 100644 --- a/src/node/utils/toolbar.js +++ b/src/node/utils/toolbar.js @@ -1,53 +1,50 @@ /** * The Toolbar Module creates and renders the toolbars and buttons */ -var _ = require("underscore") - , tagAttributes - , tag - , Button - , ButtonsGroup - , Separator - , defaultButtonAttributes - , removeItem; +const _ = require('underscore'); +let tagAttributes; +let tag; +let Button; +let ButtonsGroup; +let Separator; +let defaultButtonAttributes; +let removeItem; -removeItem = function(array,what) { - var ax; +removeItem = function (array, what) { + let ax; while ((ax = array.indexOf(what)) !== -1) { array.splice(ax, 1); } - return array; + return array; }; defaultButtonAttributes = function (name, overrides) { return { command: name, - localizationId: "pad.toolbar." + name + ".title", - class: "buttonicon buttonicon-" + name + localizationId: `pad.toolbar.${name}.title`, + class: `buttonicon buttonicon-${name}`, }; }; tag = function (name, attributes, contents) { - var aStr = tagAttributes(attributes); + const aStr = tagAttributes(attributes); if (_.isString(contents) && contents.length > 0) { - return '<' + name + aStr + '>' + contents + ''; - } - else { - return '<' + name + aStr + '>'; + return `<${name}${aStr}>${contents}`; + } else { + return `<${name}${aStr}>`; } }; tagAttributes = function (attributes) { - attributes = _.reduce(attributes || {}, function (o, val, name) { + attributes = _.reduce(attributes || {}, (o, val, name) => { if (!_.isUndefined(val)) { o[name] = val; } return o; }, {}); - return " " + _.map(attributes, function (val, name) { - return "" + name + '="' + _.escape(val) + '"'; - }).join(" "); + return ` ${_.map(attributes, (val, name) => `${name}="${_.escape(val)}"`).join(' ')}`; }; ButtonsGroup = function () { @@ -55,8 +52,8 @@ ButtonsGroup = function () { }; ButtonsGroup.fromArray = function (array) { - var btnGroup = new this; - _.each(array, function (btnName) { + const btnGroup = new this(); + _.each(array, (btnName) => { btnGroup.addButton(Button.load(btnName)); }); return btnGroup; @@ -69,19 +66,18 @@ ButtonsGroup.prototype.addButton = function (button) { ButtonsGroup.prototype.render = function () { if (this.buttons && this.buttons.length == 1) { - this.buttons[0].grouping = ""; - } - else { - _.first(this.buttons).grouping = "grouped-left"; - _.last(this.buttons).grouping = "grouped-right"; - _.each(this.buttons.slice(1, -1), function (btn) { - btn.grouping = "grouped-middle" + this.buttons[0].grouping = ''; + } else { + _.first(this.buttons).grouping = 'grouped-left'; + _.last(this.buttons).grouping = 'grouped-right'; + _.each(this.buttons.slice(1, -1), (btn) => { + btn.grouping = 'grouped-middle'; }); } - return _.map(this.buttons, function (btn) { - if(btn) return btn.render(); - }).join("\n"); + return _.map(this.buttons, (btn) => { + if (btn) return btn.render(); + }).join('\n'); }; Button = function (attributes) { @@ -89,165 +85,163 @@ Button = function (attributes) { }; Button.load = function (btnName) { - var button = module.exports.availableButtons[btnName]; - try{ + const button = module.exports.availableButtons[btnName]; + try { if (button.constructor === Button || button.constructor === SelectButton) { return button; - } - else { + } else { return new Button(button); } - }catch(e){ - console.warn("Error loading button", btnName); + } catch (e) { + console.warn('Error loading button', btnName); return false; } }; _.extend(Button.prototype, { - grouping: "", + grouping: '', - render: function () { - var liAttributes = { - "data-type": "button", - "data-key": this.attributes.command, + render() { + const liAttributes = { + 'data-type': 'button', + 'data-key': this.attributes.command, }; - return tag("li", liAttributes, - tag("a", { "class": this.grouping, "data-l10n-id": this.attributes.localizationId }, - tag("button", { "class": " "+ this.attributes.class, "data-l10n-id": this.attributes.localizationId }) - ) + return tag('li', liAttributes, + tag('a', {'class': this.grouping, 'data-l10n-id': this.attributes.localizationId}, + tag('button', {'class': ` ${this.attributes.class}`, 'data-l10n-id': this.attributes.localizationId}), + ), ); - } + }, }); - var SelectButton = function (attributes) { this.attributes = attributes; this.options = []; }; _.extend(SelectButton.prototype, Button.prototype, { - addOption: function (value, text, attributes) { + addOption(value, text, attributes) { this.options.push({ - value: value, - text: text, - attributes: attributes + value, + text, + attributes, }); return this; }, - select: function (attributes) { - var options = []; + select(attributes) { + const options = []; - _.each(this.options, function (opt) { - var a = _.extend({ - value: opt.value + _.each(this.options, (opt) => { + const a = _.extend({ + value: opt.value, }, 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 () { - var attributes = { - id: this.attributes.id, - "data-key": this.attributes.command, - "data-type": "select" + render() { + const attributes = { + 'id': this.attributes.id, + 'data-key': this.attributes.command, + 'data-type': 'select', }; - return tag("li", attributes, - this.select({ id: this.attributes.selectId }) + return tag('li', attributes, + this.select({id: this.attributes.selectId}), ); - } + }, }); Separator = function () {}; Separator.prototype.render = function () { - return tag("li", { "class": "separator" }); + return tag('li', {class: 'separator'}); }; module.exports = { availableButtons: { - bold: defaultButtonAttributes("bold"), - italic: defaultButtonAttributes("italic"), - underline: defaultButtonAttributes("underline"), - strikethrough: defaultButtonAttributes("strikethrough"), + bold: defaultButtonAttributes('bold'), + italic: defaultButtonAttributes('italic'), + underline: defaultButtonAttributes('underline'), + strikethrough: defaultButtonAttributes('strikethrough'), orderedlist: { - command: "insertorderedlist", - localizationId: "pad.toolbar.ol.title", - class: "buttonicon buttonicon-insertorderedlist" + command: 'insertorderedlist', + localizationId: 'pad.toolbar.ol.title', + class: 'buttonicon buttonicon-insertorderedlist', }, unorderedlist: { - command: "insertunorderedlist", - localizationId: "pad.toolbar.ul.title", - class: "buttonicon buttonicon-insertunorderedlist" + command: 'insertunorderedlist', + localizationId: 'pad.toolbar.ul.title', + class: 'buttonicon buttonicon-insertunorderedlist', }, - indent: defaultButtonAttributes("indent"), + indent: defaultButtonAttributes('indent'), outdent: { - command: "outdent", - localizationId: "pad.toolbar.unindent.title", - class: "buttonicon buttonicon-outdent" + command: 'outdent', + localizationId: 'pad.toolbar.unindent.title', + class: 'buttonicon buttonicon-outdent', }, - undo: defaultButtonAttributes("undo"), - redo: defaultButtonAttributes("redo"), + undo: defaultButtonAttributes('undo'), + redo: defaultButtonAttributes('redo'), clearauthorship: { - command: "clearauthorship", - localizationId: "pad.toolbar.clearAuthorship.title", - class: "buttonicon buttonicon-clearauthorship" + command: 'clearauthorship', + localizationId: 'pad.toolbar.clearAuthorship.title', + class: 'buttonicon buttonicon-clearauthorship', }, importexport: { - command: "import_export", - localizationId: "pad.toolbar.import_export.title", - class: "buttonicon buttonicon-import_export" + command: 'import_export', + localizationId: 'pad.toolbar.import_export.title', + class: 'buttonicon buttonicon-import_export', }, timeslider: { - command: "showTimeSlider", - localizationId: "pad.toolbar.timeslider.title", - class: "buttonicon buttonicon-history" + command: 'showTimeSlider', + localizationId: 'pad.toolbar.timeslider.title', + class: 'buttonicon buttonicon-history', }, - savedrevision: defaultButtonAttributes("savedRevision"), - settings: defaultButtonAttributes("settings"), - embed: defaultButtonAttributes("embed"), - showusers: defaultButtonAttributes("showusers"), + savedrevision: defaultButtonAttributes('savedRevision'), + settings: defaultButtonAttributes('settings'), + embed: defaultButtonAttributes('embed'), + showusers: defaultButtonAttributes('showusers'), timeslider_export: { - command: "import_export", - localizationId: "timeslider.toolbar.exportlink.title", - class: "buttonicon buttonicon-import_export" + command: 'import_export', + localizationId: 'timeslider.toolbar.exportlink.title', + class: 'buttonicon buttonicon-import_export', }, timeslider_settings: { - command: "settings", - localizationId: "pad.toolbar.settings.title", - class: "buttonicon buttonicon-settings" + command: 'settings', + localizationId: 'pad.toolbar.settings.title', + class: 'buttonicon buttonicon-settings', }, timeslider_returnToPad: { - command: "timeslider_returnToPad", - localizationId: "timeslider.toolbar.returnbutton", - class: "buttontext" - } + command: 'timeslider_returnToPad', + localizationId: 'timeslider.toolbar.returnbutton', + class: 'buttontext', + }, }, - registerButton: function (buttonName, buttonInfo) { + registerButton(buttonName, buttonInfo) { this.availableButtons[buttonName] = buttonInfo; }, - button: function (attributes) { + button(attributes) { return new Button(attributes); }, - separator: function () { - return (new Separator).render(); + separator() { + return (new Separator()).render(); }, - selectButton: function (attributes) { + selectButton(attributes) { return new SelectButton(attributes); }, @@ -255,15 +249,15 @@ module.exports = { * Valid values for whichMenu: 'left' | 'right' | 'timeslider-right' * Valid values for page: 'pad' | 'timeslider' */ - menu: function (buttons, isReadOnly, whichMenu, page) { + menu(buttons, isReadOnly, whichMenu, page) { if (isReadOnly) { // 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 buttons = []; } else { // Remove Save Revision from the right menu - removeItem(buttons[0],"savedrevision"); + removeItem(buttons[0], 'savedrevision'); } } else { /* @@ -277,14 +271,12 @@ module.exports = { * sufficient to visit a single read only pad to cause the disappearence * of the star button from all the pads. */ - if ((buttons[0].indexOf("savedrevision") === -1) && (whichMenu === "right") && (page === "pad")) { - buttons[0].push("savedrevision"); + if ((buttons[0].indexOf('savedrevision') === -1) && (whichMenu === 'right') && (page === 'pad')) { + buttons[0].push('savedrevision'); } } - var groups = _.map(buttons, function (group) { - return ButtonsGroup.fromArray(group).render(); - }); + const groups = _.map(buttons, (group) => ButtonsGroup.fromArray(group).render()); return groups.join(this.separator()); - } + }, }; diff --git a/src/static/js/AttributeManager.js b/src/static/js/AttributeManager.js index 9dcf89fc3..c1257a9bc 100644 --- a/src/static/js/AttributeManager.js +++ b/src/static/js/AttributeManager.js @@ -1,18 +1,18 @@ -var Changeset = require('./Changeset'); -var ChangesetUtils = require('./ChangesetUtils'); -var _ = require('./underscore'); +const Changeset = require('./Changeset'); +const ChangesetUtils = require('./ChangesetUtils'); +const _ = require('./underscore'); -var lineMarkerAttribute = 'lmkr'; +const lineMarkerAttribute = 'lmkr'; // Some of these attributes are kept for compatibility purposes. // 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 // line it is considered as a line attribute marker i.e. attributes // set on this marker are applied to the whole line. // 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 @@ -29,7 +29,7 @@ var lineAttributes = [lineMarkerAttribute,'list']; - a SkipList `lines` containing the text lines of the document. */ -var AttributeManager = function(rep, applyChangesetCallback) { +const AttributeManager = function (rep, applyChangesetCallback) { this.rep = rep; this.applyChangesetCallback = applyChangesetCallback; this.author = ''; @@ -43,12 +43,11 @@ AttributeManager.lineAttributes = lineAttributes; AttributeManager.prototype = _(AttributeManager.prototype).extend({ - applyChangeset: function(changeset){ - if(!this.applyChangesetCallback) return changeset; + applyChangeset(changeset) { + if (!this.applyChangesetCallback) return changeset; - var cs = changeset.toString(); - if (!Changeset.isIdentity(cs)) - { + const cs = changeset.toString(); + if (!Changeset.isIdentity(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 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 // 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 - var allChangesets; - for(var row = start[0]; row <= end[0]; row++) { - var rowRange = this._findRowRange(row, start, end); - var startCol = rowRange[0]; - var endCol = rowRange[1]; + let allChangesets; + for (let row = start[0]; row <= end[0]; row++) { + const rowRange = this._findRowRange(row, start, end); + const startCol = rowRange[0]; + 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 // due to the presence of line markers on the rows @@ -85,12 +84,12 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ return this.applyChangeset(allChangesets); }, - _findRowRange: function(row, start, end) { - var startCol, endCol; + _findRowRange(row, start, end) { + let startCol, endCol; - var startLineOffset = this.rep.lines.offsetOfIndex(row); - var endLineOffset = this.rep.lines.offsetOfIndex(row+1); - var lineLength = endLineOffset - startLineOffset; + const startLineOffset = this.rep.lines.offsetOfIndex(row); + const endLineOffset = this.rep.lines.offsetOfIndex(row + 1); + const lineLength = endLineOffset - startLineOffset; // find column where range on this row starts 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 attribs: an array of attributes */ - _setAttributesOnRangeByLine: function(row, startCol, endCol, attribs) { - var builder = Changeset.builder(this.rep.lines.totalWidth()); + _setAttributesOnRangeByLine(row, startCol, endCol, attribs) { + const builder = Changeset.builder(this.rep.lines.totalWidth()); ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [row, startCol]); ChangesetUtils.buildKeepRange(this.rep, builder, [row, startCol], [row, endCol], attribs, this.rep.apool); return builder; @@ -127,12 +126,10 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ Returns if the line already has a line marker @param lineNum: the number of the line */ - lineHasMarker: function(lineNum){ - var that = this; + lineHasMarker(lineNum) { + const that = this; - return _.find(lineAttributes, function(attribute){ - return that.getAttributeOnLine(lineNum, attribute) != ''; - }) !== undefined; + return _.find(lineAttributes, (attribute) => 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 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 - var aline = this.rep.alines[lineNum]; - if (aline) - { - var opIter = Changeset.opIterator(aline); - if (opIter.hasNext()) - { + const aline = this.rep.alines[lineNum]; + if (aline) { + const opIter = Changeset.opIterator(aline); + if (opIter.hasNext()) { return Changeset.opAttributeValue(opIter.next(), attributeName, this.rep.apool) || ''; } } @@ -158,96 +153,94 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ Gets all attributes on a line @param lineNum: the number of the line to get the attribute for */ - getAttributesOnLine: function(lineNum){ + getAttributesOnLine(lineNum) { // get attributes of first char of line - var aline = this.rep.alines[lineNum]; - var attributes = [] - if (aline) - { - var opIter = Changeset.opIterator(aline) - , op - if (opIter.hasNext()) - { - op = opIter.next() - if(!op.attribs) return [] + const aline = this.rep.alines[lineNum]; + const attributes = []; + if (aline) { + const opIter = Changeset.opIterator(aline); + let op; + if (opIter.hasNext()) { + op = opIter.next(); + if (!op.attribs) return []; - Changeset.eachAttribNumber(op.attribs, function(n) { - attributes.push([this.rep.apool.getAttribKey(n), this.rep.apool.getAttribValue(n)]) - }.bind(this)) + Changeset.eachAttribNumber(op.attribs, (n) => { + attributes.push([this.rep.apool.getAttribKey(n), this.rep.apool.getAttribValue(n)]); + }); return attributes; } } return []; }, - /* + /* Gets a given attribute on a selection @param attributeName @param prevChar returns true or false if an attribute is visible in range */ - getAttributeOnSelection: function(attributeName, prevChar){ - var rep = this.rep; - if (!(rep.selStart && rep.selEnd)) return + getAttributeOnSelection(attributeName, prevChar) { + const rep = this.rep; + if (!(rep.selStart && rep.selEnd)) return; // 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? - var isNotSelection = (rep.selStart[0] == rep.selEnd[0] && rep.selEnd[1] === rep.selStart[1]); - if(isNotSelection){ - if(prevChar){ + const isNotSelection = (rep.selStart[0] == rep.selEnd[0] && rep.selEnd[1] === rep.selStart[1]); + if (isNotSelection) { + if (prevChar) { // If it's not the start of the line - if(rep.selStart[1] !== 0){ + if (rep.selStart[1] !== 0) { rep.selStart[1]--; } } } - var withIt = Changeset.makeAttribsString('+', [ - [attributeName, 'true'] + const withIt = Changeset.makeAttribsString('+', [ + [attributeName, 'true'], ], rep.apool); - var withItRegex = new RegExp(withIt.replace(/\*/g, '\\*') + "(\\*|$)"); + const withItRegex = new RegExp(`${withIt.replace(/\*/g, '\\*')}(\\*|$)`); function hasIt(attribs) { return withItRegex.test(attribs); } - return rangeHasAttrib(rep.selStart, rep.selEnd) + return rangeHasAttrib(rep.selStart, rep.selEnd); function rangeHasAttrib(selStart, selEnd) { // 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 - var hasAttrib = true + if (selStart[0] != selEnd[0]) { // -> More than one line selected + var hasAttrib = true; // 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(var n=selStart[0]+1; n < selEnd[0]; n++) { - hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length]) + for (let n = selStart[0] + 1; n < selEnd[0]; n++) { + hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length]); } // 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 - var lineNum = selStart[0] - , start = selStart[1] - , end = selEnd[1] - , hasAttrib = true + const lineNum = selStart[0]; + const start = selStart[1]; + const end = selEnd[1]; + var hasAttrib = true; // Iterate over attribs on this line - var opIter = Changeset.opIterator(rep.alines[lineNum]) - , indexIntoLine = 0 + const opIter = Changeset.opIterator(rep.alines[lineNum]); + let indexIntoLine = 0; while (opIter.hasNext()) { - var op = opIter.next(); - var opStartInLine = indexIntoLine; - var opEndInLine = opStartInLine + op.chars; + const op = opIter.next(); + const opStartInLine = indexIntoLine; + const opEndInLine = opStartInLine + op.chars; if (!hasIt(op.attribs)) { // does op overlap selection? if (!(opEndInLine <= start || opStartInLine >= end)) { @@ -258,7 +251,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ indexIntoLine = opEndInLine; } - return hasAttrib + return hasAttrib; } }, @@ -269,40 +262,39 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ returns a list of attributes in the format [ ["key","value"], ["key","value"], ... ] */ - getAttributesOnPosition: function(lineNumber, column){ + getAttributesOnPosition(lineNumber, column) { // get all attributes of the line - var aline = this.rep.alines[lineNumber]; + const aline = this.rep.alines[lineNumber]; if (!aline) { - return []; + return []; } // 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 - var currentPointer = 0; - var attributes = []; - var currentOperation; + let currentPointer = 0; + const attributes = []; + let currentOperation; while (opIter.hasNext()) { currentOperation = opIter.next(); - currentPointer = currentPointer + currentOperation.chars; + currentPointer += currentOperation.chars; if (currentPointer > column) { // 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([ this.rep.apool.getAttribKey(n), - this.rep.apool.getAttribValue(n) + this.rep.apool.getAttribValue(n), ]); - }.bind(this)); + }); // skip the loop return attributes; } } return attributes; - }, /* @@ -311,7 +303,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ returns a list of attributes in the format [ ["key","value"], ["key","value"], ... ] */ - getAttributesOnCaret: function(){ + getAttributesOnCaret() { 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) */ - setAttributeOnLine: function(lineNum, attributeName, attributeValue){ - var loc = [0,0]; - var builder = Changeset.builder(this.rep.lines.totalWidth()); - var hasMarker = this.lineHasMarker(lineNum); + setAttributeOnLine(lineNum, attributeName, attributeValue) { + let loc = [0, 0]; + const builder = Changeset.builder(this.rep.lines.totalWidth()); + const hasMarker = this.lineHasMarker(lineNum); ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0])); - if(hasMarker){ + if (hasMarker) { 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); - }else{ - // add a line marker - builder.insert('*', [ - ['author', this.author], - ['insertorder', 'first'], - [lineMarkerAttribute, '1'], - [attributeName, attributeValue] - ], this.rep.apool); } return this.applyChangeset(builder); }, - /** + /** * Removes a specified attribute on a line * @param lineNum the number of the affected line * @param attributeName the name of the attribute to remove, e.g. list * @param attributeValue if given only attributes with equal value will be removed */ - removeAttributeOnLine: function(lineNum, attributeName, attributeValue){ - var builder = Changeset.builder(this.rep.lines.totalWidth()); - var hasMarker = this.lineHasMarker(lineNum); - var found = false; + removeAttributeOnLine(lineNum, attributeName, attributeValue) { + const builder = Changeset.builder(this.rep.lines.totalWidth()); + const hasMarker = this.lineHasMarker(lineNum); + let found = false; - var attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) { - if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)){ - found = true; - return [attributeName, '']; - }else if (attrib[0] === 'author'){ - // update last author to make changes to line attributes on this line - return [attributeName, this.author]; - } - return attrib; - }); + const attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) { + if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)) { + found = true; + return [attributeName, '']; + } else if (attrib[0] === 'author') { + // update last author to make changes to line attributes on this line + return [attributeName, this.author]; + } + return attrib; + }); - if (!found) { - return; - } + if (!found) { + 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];}) - .map(function(a){return a[0];}).difference(DEFAULT_LINE_ATTRIBUTES).size().value(); + const countAttribsWithMarker = _.chain(attribs).filter((a) => !!a[1]) + .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(hasMarker && !countAttribsWithMarker){ - ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]); - }else{ - ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool); - } + // if we have marker and any of attributes don't need to have marker. we need delete it + if (hasMarker && !countAttribsWithMarker) { + ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]); + } else { + 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 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 @@ -395,20 +387,19 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ @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) */ - toggleAttributeOnLine: function(lineNum, attributeName, attributeValue) { - return this.getAttributeOnLine(lineNum, attributeName) ? - this.removeAttributeOnLine(lineNum, attributeName) : - this.setAttributeOnLine(lineNum, attributeName, attributeValue); - + toggleAttributeOnLine(lineNum, attributeName, attributeValue) { + return this.getAttributeOnLine(lineNum, attributeName) + ? this.removeAttributeOnLine(lineNum, attributeName) + : this.setAttributeOnLine(lineNum, attributeName, attributeValue); }, - hasAttributeOnSelectionOrCaretPosition: function(attributeName) { - var hasSelection = ((this.rep.selStart[0] !== this.rep.selEnd[0]) || (this.rep.selEnd[1] !== this.rep.selStart[1])); - var hasAttrib; + hasAttributeOnSelectionOrCaretPosition(attributeName) { + const hasSelection = ((this.rep.selStart[0] !== this.rep.selEnd[0]) || (this.rep.selEnd[1] !== this.rep.selStart[1])); + let hasAttrib; if (hasSelection) { hasAttrib = this.getAttributeOnSelection(attributeName); - }else { - var attributesOnCaretPosition = this.getAttributesOnCaret(); + } else { + const attributesOnCaretPosition = this.getAttributesOnCaret(); hasAttrib = _.contains(_.flatten(attributesOnCaretPosition), attributeName); } return hasAttrib; diff --git a/src/static/js/AttributePool.js b/src/static/js/AttributePool.js index 7e7634e42..78d3e7c5b 100644 --- a/src/static/js/AttributePool.js +++ b/src/static/js/AttributePool.js @@ -28,28 +28,28 @@ used to reference Attributes in Changesets. */ -var AttributePool = function () { +const AttributePool = function () { this.numToAttrib = {}; // e.g. {0: ['foo','bar']} this.attribToNum = {}; // e.g. {'foo,bar': 0} this.nextNum = 0; }; AttributePool.prototype.putAttrib = function (attrib, dontAddIfAbsent) { - var str = String(attrib); + const str = String(attrib); if (str in this.attribToNum) { return this.attribToNum[str]; } if (dontAddIfAbsent) { return -1; } - var num = this.nextNum++; + const num = this.nextNum++; this.attribToNum[str] = num; this.numToAttrib[num] = [String(attrib[0] || ''), String(attrib[1] || '')]; return num; }; AttributePool.prototype.getAttrib = function (num) { - var pair = this.numToAttrib[num]; + const pair = this.numToAttrib[num]; if (!pair) { return pair; } @@ -57,20 +57,20 @@ AttributePool.prototype.getAttrib = function (num) { }; AttributePool.prototype.getAttribKey = function (num) { - var pair = this.numToAttrib[num]; + const pair = this.numToAttrib[num]; if (!pair) return ''; return pair[0]; }; AttributePool.prototype.getAttribValue = function (num) { - var pair = this.numToAttrib[num]; + const pair = this.numToAttrib[num]; if (!pair) return ''; return pair[1]; }; AttributePool.prototype.eachAttrib = function (func) { - for (var n in this.numToAttrib) { - var pair = this.numToAttrib[n]; + for (const n in this.numToAttrib) { + const pair = this.numToAttrib[n]; func(pair[0], pair[1]); } }; @@ -78,7 +78,7 @@ AttributePool.prototype.eachAttrib = function (func) { AttributePool.prototype.toJsonable = function () { return { numToAttrib: this.numToAttrib, - nextNum: this.nextNum + nextNum: this.nextNum, }; }; @@ -86,7 +86,7 @@ AttributePool.prototype.fromJsonable = function (obj) { this.numToAttrib = obj.numToAttrib; this.nextNum = obj.nextNum; this.attribToNum = {}; - for (var n in this.numToAttrib) { + for (const n in this.numToAttrib) { this.attribToNum[String(this.numToAttrib[n])] = Number(n); } return this; diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js index e4e6d2d63..422c7ede6 100644 --- a/src/static/js/Changeset.js +++ b/src/static/js/Changeset.js @@ -25,7 +25,7 @@ * limitations under the License. */ -var AttributePool = require("./AttributePool"); +const AttributePool = require('./AttributePool'); /** * ==================== General Util Functions ======================= @@ -36,7 +36,7 @@ var AttributePool = require("./AttributePool"); * @param msg {string} Just some message */ exports.error = function error(msg) { - var e = new Error(msg); + const e = new Error(msg); e.easysync = true; throw e; }; @@ -49,8 +49,8 @@ exports.error = function error(msg) { */ exports.assert = function assert(b, msgParts) { if (!b) { - var msg = Array.prototype.slice.call(arguments, 1).join(''); - exports.error("Failed assertion: " + msg); + const msg = Array.prototype.slice.call(arguments, 1).join(''); + exports.error(`Failed assertion: ${msg}`); } }; @@ -79,12 +79,10 @@ exports.numToString = function (num) { * @return integer */ exports.toBaseTen = function (cs) { - var dollarIndex = cs.indexOf('$'); - var beforeDollar = cs.substring(0, dollarIndex); - var fromDollar = cs.substring(dollarIndex); - return beforeDollar.replace(/[0-9a-z]+/g, function (s) { - return String(exports.parseNum(s)); - }) + fromDollar; + const dollarIndex = cs.indexOf('$'); + const beforeDollar = cs.substring(0, dollarIndex); + const fromDollar = cs.substring(dollarIndex); + return beforeDollar.replace(/[0-9a-z]+/g, (s) => String(exports.parseNum(s))) + fromDollar; }; @@ -116,29 +114,29 @@ exports.newLen = function (cs) { * @return {Op} type object iterator */ exports.opIterator = function (opsStr, optStartIndex) { - //print(opsStr); - var regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|\?|/g; - var startIndex = (optStartIndex || 0); - var curIndex = startIndex; - var prevIndex = curIndex; + // print(opsStr); + const regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|\?|/g; + const startIndex = (optStartIndex || 0); + let curIndex = startIndex; + let prevIndex = curIndex; function nextRegexMatch() { prevIndex = curIndex; - var result; + let result; regex.lastIndex = curIndex; result = regex.exec(opsStr); curIndex = regex.lastIndex; if (result[0] == '?') { - exports.error("Hit error opcode in op stream"); + exports.error('Hit error opcode in op stream'); } return result; } - var regexResult = nextRegexMatch(); - var obj = exports.newOp(); + let regexResult = nextRegexMatch(); + const obj = exports.newOp(); function next(optObj) { - var op = (optObj || obj); + const op = (optObj || obj); if (regexResult[0]) { op.attribs = regexResult[1]; op.lines = exports.parseNum(regexResult[2] || 0); @@ -159,9 +157,9 @@ exports.opIterator = function (opsStr, optStartIndex) { return prevIndex; } return { - next: next, - hasNext: hasNext, - lastIndex: lastIndex + next, + hasNext, + lastIndex, }; }; @@ -185,7 +183,7 @@ exports.newOp = function (optOpcode) { opcode: (optOpcode || ''), chars: 0, lines: 0, - attribs: '' + attribs: '', }; }; @@ -198,7 +196,7 @@ exports.cloneOp = function (op) { opcode: op.opcode, chars: op.chars, lines: op.lines, - attribs: op.attribs + attribs: op.attribs, }; }; @@ -220,7 +218,7 @@ exports.copyOp = function (op1, op2) { exports.opString = function (op) { // just for debugging if (!op.opcode) return 'null'; - var assem = exports.opAssembler(); + const assem = exports.opAssembler(); assem.append(op); return assem.toString(); }; @@ -240,33 +238,33 @@ exports.stringOp = function (str) { exports.checkRep = function (cs) { // doesn't check things that require access to attrib pool (e.g. attribute order) // or original string (e.g. newline positions) - var unpacked = exports.unpack(cs); - var oldLen = unpacked.oldLen; - var newLen = unpacked.newLen; - var ops = unpacked.ops; - var charBank = unpacked.charBank; + const unpacked = exports.unpack(cs); + const oldLen = unpacked.oldLen; + const newLen = unpacked.newLen; + const ops = unpacked.ops; + let charBank = unpacked.charBank; - var assem = exports.smartOpAssembler(); - var oldPos = 0; - var calcNewLen = 0; - var numInserted = 0; - var iter = exports.opIterator(ops); + const assem = exports.smartOpAssembler(); + let oldPos = 0; + let calcNewLen = 0; + let numInserted = 0; + const iter = exports.opIterator(ops); while (iter.hasNext()) { - var o = iter.next(); + const o = iter.next(); switch (o.opcode) { - case '=': - oldPos += o.chars; - calcNewLen += o.chars; - break; - case '-': - oldPos += o.chars; - exports.assert(oldPos <= oldLen, oldPos, " > ", oldLen, " in ", cs); - break; - case '+': + case '=': + oldPos += o.chars; + calcNewLen += o.chars; + break; + case '-': + oldPos += o.chars; + exports.assert(oldPos <= oldLen, oldPos, ' > ', oldLen, ' in ', cs); + break; + case '+': { calcNewLen += o.chars; numInserted += o.chars; - exports.assert(calcNewLen <= newLen, calcNewLen, " > ", newLen, " in ", cs); + exports.assert(calcNewLen <= newLen, calcNewLen, ' > ', newLen, ' in ', cs); break; } } @@ -276,15 +274,15 @@ exports.checkRep = function (cs) { calcNewLen += oldLen - oldPos; charBank = charBank.substring(0, numInserted); while (charBank.length < numInserted) { - charBank += "?"; + charBank += '?'; } assem.endDocument(); - var normalized = exports.pack(oldLen, calcNewLen, assem.toString(), charBank); + const normalized = exports.pack(oldLen, calcNewLen, assem.toString(), charBank); exports.assert(normalized == cs, 'Invalid changeset (checkRep failed)'); return cs; -} +}; /** @@ -303,12 +301,12 @@ exports.smartOpAssembler = function () { // - strips final "=" // - ignores 0-length changes // - reorders consecutive + and - (which margingOpAssembler doesn't do) - var minusAssem = exports.mergingOpAssembler(); - var plusAssem = exports.mergingOpAssembler(); - var keepAssem = exports.mergingOpAssembler(); - var assem = exports.stringAssembler(); - var lastOpcode = ''; - var lengthChange = 0; + const minusAssem = exports.mergingOpAssembler(); + const plusAssem = exports.mergingOpAssembler(); + const keepAssem = exports.mergingOpAssembler(); + const assem = exports.stringAssembler(); + let lastOpcode = ''; + let lengthChange = 0; function flushKeeps() { assem.append(keepAssem.toString()); @@ -348,9 +346,9 @@ exports.smartOpAssembler = function () { } function appendOpWithText(opcode, text, attribs, pool) { - var op = exports.newOp(opcode); + const op = exports.newOp(opcode); op.attribs = exports.makeAttribsString(opcode, attribs, pool); - var lastNewlinePos = text.lastIndexOf('\n'); + const lastNewlinePos = text.lastIndexOf('\n'); if (lastNewlinePos < 0) { op.chars = text.length; op.lines = 0; @@ -388,12 +386,12 @@ exports.smartOpAssembler = function () { } return { - append: append, - toString: toString, - clear: clear, - endDocument: endDocument, - appendOpWithText: appendOpWithText, - getLengthChange: getLengthChange + append, + toString, + clear, + endDocument, + appendOpWithText, + getLengthChange, }; }; @@ -403,14 +401,14 @@ exports.mergingOpAssembler = function () { // merges consecutive operations that are mergeable, ignores // no-ops, and drops final pure "keeps". It does not re-order // operations. - var assem = exports.opAssembler(); - var bufOp = exports.newOp(); + const assem = exports.opAssembler(); + const bufOp = exports.newOp(); // If we get, for example, insertions [xxx\n,yyy], those don't merge, // but if we get [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n]. // This variable stores the length of yyy and any other newline-less // ops immediately after it. - var bufOpAdditionalCharsAfterNewline = 0; + let bufOpAdditionalCharsAfterNewline = 0; function flush(isEndDocument) { if (bufOp.opcode) { @@ -465,17 +463,16 @@ exports.mergingOpAssembler = function () { exports.clearOp(bufOp); } return { - append: append, - toString: toString, - clear: clear, - endDocument: endDocument + append, + toString, + clear, + endDocument, }; }; - exports.opAssembler = function () { - var pieces = []; + const pieces = []; // this function allows op to be mutated later (doesn't keep a ref) function append(op) { @@ -495,9 +492,9 @@ exports.opAssembler = function () { pieces.length = 0; } return { - append: append, - toString: toString, - clear: clear + append, + toString, + clear, }; }; @@ -506,28 +503,28 @@ exports.opAssembler = function () { * @param str {string} String to be iterated over */ exports.stringIterator = function (str) { - var curIndex = 0; + let curIndex = 0; // newLines is the number of \n between curIndex and str.length - var newLines = str.split("\n").length - 1 - function getnewLines(){ - return newLines + let newLines = str.split('\n').length - 1; + function getnewLines() { + return newLines; } function assertRemaining(n) { - exports.assert(n <= remaining(), "!(", n, " <= ", remaining(), ")"); + exports.assert(n <= remaining(), '!(', n, ' <= ', remaining(), ')'); } function take(n) { assertRemaining(n); - var s = str.substr(curIndex, n); - newLines -= s.split("\n").length - 1 + const s = str.substr(curIndex, n); + newLines -= s.split('\n').length - 1; curIndex += n; return s; } function peek(n) { assertRemaining(n); - var s = str.substr(curIndex, n); + const s = str.substr(curIndex, n); return s; } @@ -540,11 +537,11 @@ exports.stringIterator = function (str) { return str.length - curIndex; } return { - take: take, - skip: skip, - remaining: remaining, - peek: peek, - newlines: getnewLines + take, + skip, + remaining, + peek, + newlines: getnewLines, }; }; @@ -552,7 +549,7 @@ exports.stringIterator = function (str) { * A custom made StringBuffer */ exports.stringAssembler = function () { - var pieces = []; + const pieces = []; function append(x) { pieces.push(String(x)); @@ -562,8 +559,8 @@ exports.stringAssembler = function () { return pieces.join(''); } return { - append: append, - toString: toString + append, + toString, }; }; @@ -581,11 +578,11 @@ exports.textLinesMutator = function (lines) { // is not actually a newline, but for the purposes of N and L values, // the caller should pretend it is, and for things to work right in that case, the input // to insert() should be a single line with no newlines. - var curSplice = [0, 0]; - var inSplice = false; + const curSplice = [0, 0]; + let inSplice = false; // position in document after curSplice is applied: - var curLine = 0, - curCol = 0; + let curLine = 0; + let curCol = 0; // invariant: if (inSplice) then (curLine is in curSplice[0] + curSplice.length - {2,3}) && // curLine >= curSplice[0] // invariant: if (inSplice && (curLine >= curSplice[0] + curSplice.length - 2)) then @@ -617,7 +614,7 @@ exports.textLinesMutator = function (lines) { } function lines_length() { - if ((typeof lines.length) == "number") { + if ((typeof lines.length) === 'number') { return lines.length; } else { return lines.length(); @@ -645,7 +642,7 @@ exports.textLinesMutator = function (lines) { } function debugPrint(typ) { - print(typ + ": " + curSplice.toSource() + " / " + curLine + "," + curCol + " / " + lines_toSource()); + print(`${typ}: ${curSplice.toSource()} / ${curLine},${curCol} / ${lines_toSource()}`); } function putCurLineInSplice() { @@ -662,7 +659,7 @@ exports.textLinesMutator = function (lines) { if (!inSplice) { enterSplice(); } - for (var i = 0; i < L; i++) { + for (let i = 0; i < L; i++) { curCol = 0; putCurLineInSplice(); curLine++; @@ -678,14 +675,14 @@ exports.textLinesMutator = function (lines) { curLine += L; curCol = 0; } - //print(inSplice+" / "+isCurLineInSplice()+" / "+curSplice[0]+" / "+curSplice[1]+" / "+lines.length); -/*if (inSplice && (! isCurLineInSplice()) && (curSplice[0] + curSplice[1] < lines.length)) { + // print(inSplice+" / "+isCurLineInSplice()+" / "+curSplice[0]+" / "+curSplice[1]+" / "+lines.length); + /* if (inSplice && (! isCurLineInSplice()) && (curSplice[0] + curSplice[1] < lines.length)) { print("BLAH"); putCurLineInSplice(); }*/ // tests case foo in remove(), which isn't otherwise covered in current impl } - //debugPrint("skip"); + // debugPrint("skip"); } function skip(N, L, includeInSplice) { @@ -700,24 +697,24 @@ exports.textLinesMutator = function (lines) { putCurLineInSplice(); } curCol += N; - //debugPrint("skip"); + // debugPrint("skip"); } } } function removeLines(L) { - var removed = ''; + let removed = ''; if (L) { if (!inSplice) { enterSplice(); } function nextKLinesText(k) { - var m = curSplice[0] + curSplice[1]; + const m = curSplice[0] + curSplice[1]; return lines_slice(m, m + k).join(''); } if (isCurLineInSplice()) { - //print(curCol); + // print(curCol); if (curCol == 0) { removed = curSplice[curSplice.length - 1]; // print("FOO"); // case foo @@ -727,7 +724,7 @@ exports.textLinesMutator = function (lines) { } else { removed = nextKLinesText(L - 1); curSplice[1] += L - 1; - var sline = curSplice.length - 1; + const sline = curSplice.length - 1; removed = curSplice[sline].substring(curCol) + removed; curSplice[sline] = curSplice[sline].substring(0, curCol) + lines_get(curSplice[0] + curSplice[1]); curSplice[1] += 1; @@ -736,13 +733,13 @@ exports.textLinesMutator = function (lines) { removed = nextKLinesText(L); curSplice[1] += L; } - //debugPrint("remove"); + // debugPrint("remove"); } return removed; } function remove(N, L) { - var removed = ''; + let removed = ''; if (N) { if (L) { return removeLines(L); @@ -750,10 +747,10 @@ exports.textLinesMutator = function (lines) { if (!inSplice) { enterSplice(); } - var sline = putCurLineInSplice(); + const sline = putCurLineInSplice(); removed = curSplice[sline].substring(curCol, curCol + N); curSplice[sline] = curSplice[sline].substring(0, curCol) + curSplice[sline].substring(curCol + N); - //debugPrint("remove"); + // debugPrint("remove"); } } return removed; @@ -765,18 +762,18 @@ exports.textLinesMutator = function (lines) { enterSplice(); } if (L) { - var newLines = exports.splitTextLines(text); + const newLines = exports.splitTextLines(text); if (isCurLineInSplice()) { - //if (curCol == 0) { - //curSplice.length--; - //curSplice[1]--; - //Array.prototype.push.apply(curSplice, newLines); - //curLine += newLines.length; - //} - //else { + // if (curCol == 0) { + // curSplice.length--; + // curSplice[1]--; + // Array.prototype.push.apply(curSplice, newLines); + // curLine += newLines.length; + // } + // else { var sline = curSplice.length - 1; - var theLine = curSplice[sline]; - var lineCol = curCol; + const theLine = curSplice[sline]; + const lineCol = curCol; curSplice[sline] = theLine.substring(0, lineCol) + newLines[0]; curLine++; newLines.splice(0, 1); @@ -784,7 +781,7 @@ exports.textLinesMutator = function (lines) { curLine += newLines.length; curSplice.push(theLine.substring(lineCol)); curCol = 0; - //} + // } } else { Array.prototype.push.apply(curSplice, newLines); curLine += newLines.length; @@ -792,18 +789,18 @@ exports.textLinesMutator = function (lines) { } else { var sline = putCurLineInSplice(); if (!curSplice[sline]) { - console.error("curSplice[sline] not populated, actual curSplice contents is ", curSplice, ". Possibly related to https://github.com/ether/etherpad-lite/issues/2802"); + console.error('curSplice[sline] not populated, actual curSplice contents is ', curSplice, '. Possibly related to https://github.com/ether/etherpad-lite/issues/2802'); } curSplice[sline] = curSplice[sline].substring(0, curCol) + text + curSplice[sline].substring(curCol); curCol += text.length; } - //debugPrint("insert"); + // debugPrint("insert"); } } function hasMore() { - //print(lines.length+" / "+inSplice+" / "+(curSplice.length - 2)+" / "+curSplice[1]); - var docLines = lines_length(); + // print(lines.length+" / "+inSplice+" / "+(curSplice.length - 2)+" / "+curSplice[1]); + let docLines = lines_length(); if (inSplice) { docLines += curSplice.length - 2 - curSplice[1]; } @@ -814,17 +811,17 @@ exports.textLinesMutator = function (lines) { if (inSplice) { leaveSplice(); } - //debugPrint("close"); + // debugPrint("close"); } - var self = { - skip: skip, - remove: remove, - insert: insert, - close: close, - hasMore: hasMore, - removeLines: removeLines, - skipLines: skipLines + const self = { + skip, + remove, + insert, + close, + hasMore, + removeLines, + skipLines, }; return self; }; @@ -845,18 +842,18 @@ exports.textLinesMutator = function (lines) { * @return {string} the integrated changeset */ exports.applyZip = function (in1, idx1, in2, idx2, func) { - var iter1 = exports.opIterator(in1, idx1); - var iter2 = exports.opIterator(in2, idx2); - var assem = exports.smartOpAssembler(); - var op1 = exports.newOp(); - var op2 = exports.newOp(); - var opOut = exports.newOp(); + const iter1 = exports.opIterator(in1, idx1); + const iter2 = exports.opIterator(in2, idx2); + const assem = exports.smartOpAssembler(); + const op1 = exports.newOp(); + const op2 = exports.newOp(); + const opOut = exports.newOp(); while (op1.opcode || iter1.hasNext() || op2.opcode || iter2.hasNext()) { if ((!op1.opcode) && iter1.hasNext()) iter1.next(op1); if ((!op2.opcode) && iter2.hasNext()) iter2.next(op2); func(op1, op2, opOut); if (opOut.opcode) { - //print(opOut.toSource()); + // print(opOut.toSource()); assem.append(opOut); opOut.opcode = ''; } @@ -871,23 +868,23 @@ exports.applyZip = function (in1, idx1, in2, idx2, func) { * @returns {Changeset} a Changeset class */ exports.unpack = function (cs) { - var headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/; - var headerMatch = headerRegex.exec(cs); + const headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/; + const headerMatch = headerRegex.exec(cs); if ((!headerMatch) || (!headerMatch[0])) { - exports.error("Not a exports: " + cs); + exports.error(`Not a exports: ${cs}`); } - var oldLen = exports.parseNum(headerMatch[1]); - var changeSign = (headerMatch[2] == '>') ? 1 : -1; - var changeMag = exports.parseNum(headerMatch[3]); - var newLen = oldLen + changeSign * changeMag; - var opsStart = headerMatch[0].length; - var opsEnd = cs.indexOf("$"); + const oldLen = exports.parseNum(headerMatch[1]); + const changeSign = (headerMatch[2] == '>') ? 1 : -1; + const changeMag = exports.parseNum(headerMatch[3]); + const newLen = oldLen + changeSign * changeMag; + const opsStart = headerMatch[0].length; + let opsEnd = cs.indexOf('$'); if (opsEnd < 0) opsEnd = cs.length; return { - oldLen: oldLen, - newLen: newLen, + oldLen, + newLen, ops: cs.substring(opsStart, opsEnd), - charBank: cs.substring(opsEnd + 1) + charBank: cs.substring(opsEnd + 1), }; }; @@ -900,9 +897,9 @@ exports.unpack = function (cs) { * @returns {Changeset} a Changeset class */ exports.pack = function (oldLen, newLen, opsStr, bank) { - var lenDiff = newLen - oldLen; - var lenDiffStr = (lenDiff >= 0 ? '>' + exports.numToString(lenDiff) : '<' + exports.numToString(-lenDiff)); - var a = []; + const lenDiff = newLen - oldLen; + const lenDiffStr = (lenDiff >= 0 ? `>${exports.numToString(lenDiff)}` : `<${exports.numToString(-lenDiff)}`); + const a = []; a.push('Z:', exports.numToString(oldLen), lenDiffStr, opsStr, '$', bank); return a.join(''); }; @@ -913,39 +910,39 @@ exports.pack = function (oldLen, newLen, opsStr, bank) { * @params str {string} String to which a Changeset should be applied */ exports.applyToText = function (cs, str) { - var unpacked = exports.unpack(cs); - exports.assert(str.length == unpacked.oldLen, "mismatched apply: ", str.length, " / ", unpacked.oldLen); - var csIter = exports.opIterator(unpacked.ops); - var bankIter = exports.stringIterator(unpacked.charBank); - var strIter = exports.stringIterator(str); - var assem = exports.stringAssembler(); + const unpacked = exports.unpack(cs); + exports.assert(str.length == unpacked.oldLen, 'mismatched apply: ', str.length, ' / ', unpacked.oldLen); + const csIter = exports.opIterator(unpacked.ops); + const bankIter = exports.stringIterator(unpacked.charBank); + const strIter = exports.stringIterator(str); + const assem = exports.stringAssembler(); while (csIter.hasNext()) { - var op = csIter.next(); + const op = csIter.next(); switch (op.opcode) { - case '+': - //op is + and op.lines 0: no newlines must be in op.chars - //op is + and op.lines >0: op.chars must include op.lines newlines - if(op.lines != bankIter.peek(op.chars).split("\n").length - 1){ - throw new Error("newline count is wrong in op +; cs:"+cs+" and text:"+str); - } - assem.append(bankIter.take(op.chars)); - break; - case '-': - //op is - and op.lines 0: no newlines must be in the deleted string - //op is - and op.lines >0: op.lines newlines must be in the deleted string - if(op.lines != strIter.peek(op.chars).split("\n").length - 1){ - throw new Error("newline count is wrong in op -; cs:"+cs+" and text:"+str); - } - strIter.skip(op.chars); - break; - case '=': - //op is = and op.lines 0: no newlines must be in the copied string - //op is = and op.lines >0: op.lines newlines must be in the copied string - if(op.lines != strIter.peek(op.chars).split("\n").length - 1){ - throw new Error("newline count is wrong in op =; cs:"+cs+" and text:"+str); - } - assem.append(strIter.take(op.chars)); - break; + case '+': + // op is + and op.lines 0: no newlines must be in op.chars + // op is + and op.lines >0: op.chars must include op.lines newlines + if (op.lines != bankIter.peek(op.chars).split('\n').length - 1) { + throw new Error(`newline count is wrong in op +; cs:${cs} and text:${str}`); + } + assem.append(bankIter.take(op.chars)); + break; + case '-': + // op is - and op.lines 0: no newlines must be in the deleted string + // op is - and op.lines >0: op.lines newlines must be in the deleted string + if (op.lines != strIter.peek(op.chars).split('\n').length - 1) { + throw new Error(`newline count is wrong in op -; cs:${cs} and text:${str}`); + } + strIter.skip(op.chars); + break; + case '=': + // op is = and op.lines 0: no newlines must be in the copied string + // op is = and op.lines >0: op.lines newlines must be in the copied string + if (op.lines != strIter.peek(op.chars).split('\n').length - 1) { + throw new Error(`newline count is wrong in op =; cs:${cs} and text:${str}`); + } + assem.append(strIter.take(op.chars)); + break; } } assem.append(strIter.take(strIter.remaining())); @@ -958,22 +955,22 @@ exports.applyToText = function (cs, str) { * @param lines The lines to which the changeset needs to be applied */ exports.mutateTextLines = function (cs, lines) { - var unpacked = exports.unpack(cs); - var csIter = exports.opIterator(unpacked.ops); - var bankIter = exports.stringIterator(unpacked.charBank); - var mut = exports.textLinesMutator(lines); + const unpacked = exports.unpack(cs); + const csIter = exports.opIterator(unpacked.ops); + const bankIter = exports.stringIterator(unpacked.charBank); + const mut = exports.textLinesMutator(lines); while (csIter.hasNext()) { - var op = csIter.next(); + const op = csIter.next(); switch (op.opcode) { - case '+': - mut.insert(bankIter.take(op.chars), op.lines); - break; - case '-': - mut.remove(op.chars, op.lines); - break; - case '=': - mut.skip(op.chars, op.lines, ( !! op.attribs)); - break; + case '+': + mut.insert(bankIter.take(op.chars), op.lines); + break; + case '-': + mut.remove(op.chars, op.lines); + break; + case '=': + mut.skip(op.chars, op.lines, (!!op.attribs)); + break; } } mut.close(); @@ -1008,16 +1005,16 @@ exports.composeAttributes = function (att1, att2, resultIsMutation, pool) { return att2; } if (!att2) return att1; - var atts = []; - att1.replace(/\*([0-9a-z]+)/g, function (_, a) { + const atts = []; + att1.replace(/\*([0-9a-z]+)/g, (_, a) => { atts.push(pool.getAttrib(exports.parseNum(a))); return ''; }); - att2.replace(/\*([0-9a-z]+)/g, function (_, a) { - var pair = pool.getAttrib(exports.parseNum(a)); - var found = false; - for (var i = 0; i < atts.length; i++) { - var oldPair = atts[i]; + att2.replace(/\*([0-9a-z]+)/g, (_, a) => { + const pair = pool.getAttrib(exports.parseNum(a)); + let found = false; + for (let i = 0; i < atts.length; i++) { + const oldPair = atts[i]; if (oldPair[0] == pair[0]) { if (pair[1] || resultIsMutation) { oldPair[1] = pair[1]; @@ -1034,12 +1031,12 @@ exports.composeAttributes = function (att1, att2, resultIsMutation, pool) { return ''; }); atts.sort(); - var buf = exports.stringAssembler(); - for (var i = 0; i < atts.length; i++) { + const buf = exports.stringAssembler(); + for (let i = 0; i < atts.length; i++) { buf.append('*'); buf.append(exports.numToString(pool.putAttrib(atts[i]))); } - //print(att1+" / "+att2+" / "+buf.toString()); + // print(att1+" / "+att2+" / "+buf.toString()); return buf.toString(); }; @@ -1051,7 +1048,7 @@ exports._slicerZipperFunc = function (attOp, csOp, opOut, pool) { // attOp is the op from the sequence that is being operated on, either an // attribution string or the earlier of two exportss being composed. // pool can be null if definitely not needed. - //print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource()); + // print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource()); if (attOp.opcode == '-') { exports.copyOp(attOp, opOut); attOp.opcode = ''; @@ -1060,7 +1057,7 @@ exports._slicerZipperFunc = function (attOp, csOp, opOut, pool) { csOp.opcode = ''; } else { switch (csOp.opcode) { - case '-': + case '-': { if (csOp.chars <= attOp.chars) { // delete or delete part @@ -1090,14 +1087,14 @@ exports._slicerZipperFunc = function (attOp, csOp, opOut, pool) { } break; } - case '+': + case '+': { // insert exports.copyOp(csOp, opOut); csOp.opcode = ''; break; } - case '=': + case '=': { if (csOp.chars <= attOp.chars) { // keep or keep part @@ -1123,7 +1120,7 @@ exports._slicerZipperFunc = function (attOp, csOp, opOut, pool) { } break; } - case '': + case '': { exports.copyOp(attOp, opOut); attOp.opcode = ''; @@ -1140,30 +1137,28 @@ exports._slicerZipperFunc = function (attOp, csOp, opOut, pool) { * @param pool {AttribsPool} the attibutes pool */ exports.applyToAttribution = function (cs, astr, pool) { - var unpacked = exports.unpack(cs); + const unpacked = exports.unpack(cs); - return exports.applyZip(astr, 0, unpacked.ops, 0, function (op1, op2, opOut) { - return exports._slicerZipperFunc(op1, op2, opOut, pool); - }); + return exports.applyZip(astr, 0, unpacked.ops, 0, (op1, op2, opOut) => exports._slicerZipperFunc(op1, op2, opOut, pool)); }; -/*exports.oneInsertedLineAtATimeOpIterator = function(opsStr, optStartIndex, charBank) { +/* exports.oneInsertedLineAtATimeOpIterator = function(opsStr, optStartIndex, charBank) { var iter = exports.opIterator(opsStr, optStartIndex); var bankIndex = 0; };*/ exports.mutateAttributionLines = function (cs, lines, pool) { - //dmesg(cs); - //dmesg(lines.toSource()+" ->"); - var unpacked = exports.unpack(cs); - var csIter = exports.opIterator(unpacked.ops); - var csBank = unpacked.charBank; - var csBankIndex = 0; + // dmesg(cs); + // dmesg(lines.toSource()+" ->"); + const unpacked = exports.unpack(cs); + const csIter = exports.opIterator(unpacked.ops); + const csBank = unpacked.charBank; + let csBankIndex = 0; // treat the attribution lines as text lines, mutating a line at a time - var mut = exports.textLinesMutator(lines); + const mut = exports.textLinesMutator(lines); - var lineIter = null; + let lineIter = null; function isNextMutOp() { return (lineIter && lineIter.hasNext()) || mut.hasMore(); @@ -1171,7 +1166,7 @@ exports.mutateAttributionLines = function (cs, lines, pool) { function nextMutOp(destOp) { if ((!(lineIter && lineIter.hasNext())) && mut.hasMore()) { - var line = mut.removeLines(1); + const line = mut.removeLines(1); lineIter = exports.opIterator(line); } if (lineIter && lineIter.hasNext()) { @@ -1180,42 +1175,42 @@ exports.mutateAttributionLines = function (cs, lines, pool) { destOp.opcode = ''; } } - var lineAssem = null; + let lineAssem = null; function outputMutOp(op) { - //print("outputMutOp: "+op.toSource()); + // print("outputMutOp: "+op.toSource()); if (!lineAssem) { lineAssem = exports.mergingOpAssembler(); } lineAssem.append(op); if (op.lines > 0) { - exports.assert(op.lines == 1, "Can't have op.lines of ", op.lines, " in attribution lines"); + exports.assert(op.lines == 1, "Can't have op.lines of ", op.lines, ' in attribution lines'); // ship it to the mut mut.insert(lineAssem.toString(), 1); lineAssem = null; } } - var csOp = exports.newOp(); - var attOp = exports.newOp(); - var opOut = exports.newOp(); + const csOp = exports.newOp(); + const attOp = exports.newOp(); + const opOut = exports.newOp(); while (csOp.opcode || csIter.hasNext() || attOp.opcode || isNextMutOp()) { if ((!csOp.opcode) && csIter.hasNext()) { csIter.next(csOp); } - //print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource()); - //print(csOp.opcode+"/"+csOp.lines+"/"+csOp.attribs+"/"+lineAssem+"/"+lineIter+"/"+(lineIter?lineIter.hasNext():null)); - //print("csOp: "+csOp.toSource()); + // print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource()); + // print(csOp.opcode+"/"+csOp.lines+"/"+csOp.attribs+"/"+lineAssem+"/"+lineIter+"/"+(lineIter?lineIter.hasNext():null)); + // print("csOp: "+csOp.toSource()); if ((!csOp.opcode) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) { break; // done } else if (csOp.opcode == '=' && csOp.lines > 0 && (!csOp.attribs) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) { // skip multiple lines; this is what makes small changes not order of the document size mut.skipLines(csOp.lines); - //print("skipped: "+csOp.lines); + // print("skipped: "+csOp.lines); csOp.opcode = ''; } else if (csOp.opcode == '+') { if (csOp.lines > 1) { - var firstLineLen = csBank.indexOf('\n', csBankIndex) + 1 - csBankIndex; + const firstLineLen = csBank.indexOf('\n', csBankIndex) + 1 - csBankIndex; exports.copyOp(csOp, opOut); csOp.chars -= firstLineLen; csOp.lines--; @@ -1232,7 +1227,7 @@ exports.mutateAttributionLines = function (cs, lines, pool) { if ((!attOp.opcode) && isNextMutOp()) { nextMutOp(attOp); } - //print("attOp: "+attOp.toSource()); + // print("attOp: "+attOp.toSource()); exports._slicerZipperFunc(attOp, csOp, opOut, pool); if (opOut.opcode) { outputMutOp(opOut); @@ -1241,10 +1236,10 @@ exports.mutateAttributionLines = function (cs, lines, pool) { } } - exports.assert(!lineAssem, "line assembler not finished:"+cs); + exports.assert(!lineAssem, `line assembler not finished:${cs}`); mut.close(); - //dmesg("-> "+lines.toSource()); + // dmesg("-> "+lines.toSource()); }; /** @@ -1253,10 +1248,10 @@ exports.mutateAttributionLines = function (cs, lines, pool) { * @returns {string} joined Attribution lines */ exports.joinAttributionLines = function (theAlines) { - var assem = exports.mergingOpAssembler(); - for (var i = 0; i < theAlines.length; i++) { - var aline = theAlines[i]; - var iter = exports.opIterator(aline); + const assem = exports.mergingOpAssembler(); + for (let i = 0; i < theAlines.length; i++) { + const aline = theAlines[i]; + const iter = exports.opIterator(aline); while (iter.hasNext()) { assem.append(iter.next()); } @@ -1265,10 +1260,10 @@ exports.joinAttributionLines = function (theAlines) { }; exports.splitAttributionLines = function (attrOps, text) { - var iter = exports.opIterator(attrOps); - var assem = exports.mergingOpAssembler(); - var lines = []; - var pos = 0; + const iter = exports.opIterator(attrOps); + const assem = exports.mergingOpAssembler(); + const lines = []; + let pos = 0; function appendOp(op) { assem.append(op); @@ -1280,12 +1275,12 @@ exports.splitAttributionLines = function (attrOps, text) { } while (iter.hasNext()) { - var op = iter.next(); - var numChars = op.chars; - var numLines = op.lines; + const op = iter.next(); + let numChars = op.chars; + let numLines = op.lines; while (numLines > 1) { - var newlineEnd = text.indexOf('\n', pos) + 1; - exports.assert(newlineEnd > 0, "newlineEnd <= 0 in splitAttributionLines"); + const newlineEnd = text.indexOf('\n', pos) + 1; + exports.assert(newlineEnd > 0, 'newlineEnd <= 0 in splitAttributionLines'); op.chars = newlineEnd - pos; op.lines = 1; appendOp(op); @@ -1317,24 +1312,24 @@ exports.splitTextLines = function (text) { * @param pool {AtribsPool} Attribs pool */ exports.compose = function (cs1, cs2, pool) { - var unpacked1 = exports.unpack(cs1); - var unpacked2 = exports.unpack(cs2); - var len1 = unpacked1.oldLen; - var len2 = unpacked1.newLen; - exports.assert(len2 == unpacked2.oldLen, "mismatched composition of two changesets"); - var len3 = unpacked2.newLen; - var bankIter1 = exports.stringIterator(unpacked1.charBank); - var bankIter2 = exports.stringIterator(unpacked2.charBank); - var bankAssem = exports.stringAssembler(); + const unpacked1 = exports.unpack(cs1); + const unpacked2 = exports.unpack(cs2); + const len1 = unpacked1.oldLen; + const len2 = unpacked1.newLen; + exports.assert(len2 == unpacked2.oldLen, 'mismatched composition of two changesets'); + const len3 = unpacked2.newLen; + const bankIter1 = exports.stringIterator(unpacked1.charBank); + const bankIter2 = exports.stringIterator(unpacked2.charBank); + const bankAssem = exports.stringAssembler(); - var newOps = exports.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function (op1, op2, opOut) { - //var debugBuilder = exports.stringAssembler(); - //debugBuilder.append(exports.opString(op1)); - //debugBuilder.append(','); - //debugBuilder.append(exports.opString(op2)); - //debugBuilder.append(' / '); - var op1code = op1.opcode; - var op2code = op2.opcode; + const newOps = exports.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, (op1, op2, opOut) => { + // var debugBuilder = exports.stringAssembler(); + // debugBuilder.append(exports.opString(op1)); + // debugBuilder.append(','); + // debugBuilder.append(exports.opString(op2)); + // debugBuilder.append(' / '); + const op1code = op1.opcode; + const op2code = op2.opcode; if (op1code == '+' && op2code == '-') { bankIter1.skip(Math.min(op1.chars, op2.chars)); } @@ -1347,12 +1342,12 @@ exports.compose = function (cs1, cs2, pool) { } } - //debugBuilder.append(exports.opString(op1)); - //debugBuilder.append(','); - //debugBuilder.append(exports.opString(op2)); - //debugBuilder.append(' -> '); - //debugBuilder.append(exports.opString(opOut)); - //print(debugBuilder.toString()); + // debugBuilder.append(exports.opString(op1)); + // debugBuilder.append(','); + // debugBuilder.append(exports.opString(op2)); + // debugBuilder.append(' -> '); + // debugBuilder.append(exports.opString(opOut)); + // print(debugBuilder.toString()); }); return exports.pack(len1, len3, newOps, bankAssem.toString()); @@ -1369,11 +1364,11 @@ exports.attributeTester = function (attribPair, pool) { if (!pool) { return never; } - var attribNum = pool.putAttrib(attribPair, true); + const attribNum = pool.putAttrib(attribPair, true); if (attribNum < 0) { return never; } else { - var re = new RegExp('\\*' + exports.numToString(attribNum) + '(?!\\w)'); + const re = new RegExp(`\\*${exports.numToString(attribNum)}(?!\\w)`); return function (attribs) { return re.test(attribs); }; @@ -1389,7 +1384,7 @@ exports.attributeTester = function (attribPair, pool) { * @param N {int} length of the identity changeset */ exports.identity = function (N) { - return exports.pack(N, N, "", ""); + return exports.pack(N, N, '', ''); }; @@ -1406,7 +1401,7 @@ exports.identity = function (N) { * @param pool {AttribPool} Attribution Pool */ exports.makeSplice = function (oldFullText, spliceStart, numRemoved, newText, optNewTextAPairs, pool) { - var oldLen = oldFullText.length; + const oldLen = oldFullText.length; if (spliceStart >= oldLen) { spliceStart = oldLen - 1; @@ -1414,10 +1409,10 @@ exports.makeSplice = function (oldFullText, spliceStart, numRemoved, newText, op if (numRemoved > oldFullText.length - spliceStart) { numRemoved = oldFullText.length - spliceStart; } - var oldText = oldFullText.substring(spliceStart, spliceStart + numRemoved); - var newLen = oldLen + newText.length - oldText.length; + const oldText = oldFullText.substring(spliceStart, spliceStart + numRemoved); + const newLen = oldLen + newText.length - oldText.length; - var assem = exports.smartOpAssembler(); + const assem = exports.smartOpAssembler(); assem.appendOpWithText('=', oldFullText.substring(0, spliceStart)); assem.appendOpWithText('-', oldText); assem.appendOpWithText('+', newText, optNewTextAPairs, pool); @@ -1433,21 +1428,21 @@ exports.makeSplice = function (oldFullText, spliceStart, numRemoved, newText, op */ exports.toSplices = function (cs) { // - var unpacked = exports.unpack(cs); - var splices = []; + const unpacked = exports.unpack(cs); + const splices = []; - var oldPos = 0; - var iter = exports.opIterator(unpacked.ops); - var charIter = exports.stringIterator(unpacked.charBank); - var inSplice = false; + let oldPos = 0; + const iter = exports.opIterator(unpacked.ops); + const charIter = exports.stringIterator(unpacked.charBank); + let inSplice = false; while (iter.hasNext()) { - var op = iter.next(); + const op = iter.next(); if (op.opcode == '=') { oldPos += op.chars; inSplice = false; } else { if (!inSplice) { - splices.push([oldPos, oldPos, ""]); + splices.push([oldPos, oldPos, '']); inSplice = true; } if (op.opcode == '-') { @@ -1466,16 +1461,16 @@ exports.toSplices = function (cs) { * */ exports.characterRangeFollow = function (cs, startChar, endChar, insertionsAfter) { - var newStartChar = startChar; - var newEndChar = endChar; - var splices = exports.toSplices(cs); - var lengthChangeSoFar = 0; - for (var i = 0; i < splices.length; i++) { - var splice = splices[i]; - var spliceStart = splice[0] + lengthChangeSoFar; - var spliceEnd = splice[1] + lengthChangeSoFar; - var newTextLength = splice[2].length; - var thisLengthChange = newTextLength - (spliceEnd - spliceStart); + let newStartChar = startChar; + let newEndChar = endChar; + const splices = exports.toSplices(cs); + let lengthChangeSoFar = 0; + for (let i = 0; i < splices.length; i++) { + const splice = splices[i]; + const spliceStart = splice[0] + lengthChangeSoFar; + const spliceEnd = splice[1] + lengthChangeSoFar; + const newTextLength = splice[2].length; + const thisLengthChange = newTextLength - (spliceEnd - spliceStart); if (spliceStart <= newStartChar && spliceEnd >= newEndChar) { // splice fully replaces/deletes range @@ -1519,16 +1514,16 @@ exports.characterRangeFollow = function (cs, startChar, endChar, insertionsAfter */ exports.moveOpsToNewPool = function (cs, oldPool, newPool) { // works on exports or attribution string - var dollarPos = cs.indexOf('$'); + let dollarPos = cs.indexOf('$'); if (dollarPos < 0) { dollarPos = cs.length; } - var upToDollar = cs.substring(0, dollarPos); - var fromDollar = cs.substring(dollarPos); + const upToDollar = cs.substring(0, dollarPos); + const fromDollar = cs.substring(dollarPos); // order of attribs stays the same - return upToDollar.replace(/\*([0-9a-z]+)/g, function (_, a) { - var oldNum = exports.parseNum(a); - var pair = oldPool.getAttrib(oldNum); + return upToDollar.replace(/\*([0-9a-z]+)/g, (_, a) => { + const oldNum = exports.parseNum(a); + let pair = oldPool.getAttrib(oldNum); /* * Setting an empty pair. Required for when delete pad contents / attributes @@ -1540,8 +1535,8 @@ exports.moveOpsToNewPool = function (cs, oldPool, newPool) { pair = []; } - var newNum = newPool.putAttrib(pair); - return '*' + exports.numToString(newNum); + const newNum = newPool.putAttrib(pair); + return `*${exports.numToString(newNum)}`; }) + fromDollar; }; @@ -1550,7 +1545,7 @@ exports.moveOpsToNewPool = function (cs, oldPool, newPool) { * @param text {string} text to be inserted */ exports.makeAttribution = function (text) { - var assem = exports.smartOpAssembler(); + const assem = exports.smartOpAssembler(); assem.appendOpWithText('+', text); return assem.toString(); }; @@ -1562,13 +1557,13 @@ exports.makeAttribution = function (text) { * @param func {function} function to be called */ exports.eachAttribNumber = function (cs, func) { - var dollarPos = cs.indexOf('$'); + let dollarPos = cs.indexOf('$'); if (dollarPos < 0) { dollarPos = cs.length; } - var upToDollar = cs.substring(0, dollarPos); + const upToDollar = cs.substring(0, dollarPos); - upToDollar.replace(/\*([0-9a-z]+)/g, function (_, a) { + upToDollar.replace(/\*([0-9a-z]+)/g, (_, a) => { func(exports.parseNum(a)); return ''; }); @@ -1590,18 +1585,18 @@ exports.filterAttribNumbers = function (cs, filter) { * does exactly the same as exports.filterAttribNumbers */ exports.mapAttribNumbers = function (cs, func) { - var dollarPos = cs.indexOf('$'); + let dollarPos = cs.indexOf('$'); if (dollarPos < 0) { dollarPos = cs.length; } - var upToDollar = cs.substring(0, dollarPos); + const upToDollar = cs.substring(0, dollarPos); - var newUpToDollar = upToDollar.replace(/\*([0-9a-z]+)/g, function (s, a) { - var n = func(exports.parseNum(a)); + const newUpToDollar = upToDollar.replace(/\*([0-9a-z]+)/g, (s, a) => { + const n = func(exports.parseNum(a)); if (n === true) { return s; - } else if ((typeof n) === "number") { - return '*' + exports.numToString(n); + } else if ((typeof n) === 'number') { + return `*${exports.numToString(n)}`; } else { return ''; } @@ -1618,8 +1613,8 @@ exports.mapAttribNumbers = function (cs, func) { */ exports.makeAText = function (text, attribs) { return { - text: text, - attribs: (attribs || exports.makeAttribution(text)) + text, + attribs: (attribs || exports.makeAttribution(text)), }; }; @@ -1632,7 +1627,7 @@ exports.makeAText = function (text, attribs) { exports.applyToAText = function (cs, atext, pool) { return { text: exports.applyToText(cs, atext.text), - attribs: exports.applyToAttribution(cs, atext.attribs, pool) + attribs: exports.applyToAttribution(cs, atext.attribs, pool), }; }; @@ -1644,9 +1639,9 @@ exports.cloneAText = function (atext) { if (atext) { return { text: atext.text, - attribs: atext.attribs - } - } else exports.error("atext is null"); + attribs: atext.attribs, + }; + } else { exports.error('atext is null'); } }; /** @@ -1665,8 +1660,8 @@ exports.copyAText = function (atext1, atext2) { */ exports.appendATextToAssembler = function (atext, assem) { // intentionally skips last newline char of atext - var iter = exports.opIterator(atext.attribs); - var op = exports.newOp(); + const iter = exports.opIterator(atext.attribs); + const op = exports.newOp(); while (iter.hasNext()) { iter.next(op); if (!iter.hasNext()) { @@ -1678,9 +1673,9 @@ exports.appendATextToAssembler = function (atext, assem) { assem.append(op); } } else { - var nextToLastNewlineEnd = + const nextToLastNewlineEnd = atext.text.lastIndexOf('\n', atext.text.length - 2) + 1; - var lastLineLength = atext.text.length - nextToLastNewlineEnd - 1; + const lastLineLength = atext.text.length - nextToLastNewlineEnd - 1; op.lines--; op.chars -= (lastLineLength + 1); assem.append(op); @@ -1702,11 +1697,11 @@ exports.appendATextToAssembler = function (atext, assem) { * @param pool {AtributePool} */ exports.prepareForWire = function (cs, pool) { - var newPool = new AttributePool(); - var newCs = exports.moveOpsToNewPool(cs, pool, newPool); + const newPool = new AttributePool(); + const newCs = exports.moveOpsToNewPool(cs, pool, newPool); return { translated: newCs, - pool: newPool + pool: newPool, }; }; @@ -1714,8 +1709,8 @@ exports.prepareForWire = function (cs, pool) { * Checks if a changeset s the identity changeset */ exports.isIdentity = function (cs) { - var unpacked = exports.unpack(cs); - return unpacked.ops == "" && unpacked.oldLen == unpacked.newLen; + const unpacked = exports.unpack(cs); + return unpacked.ops == '' && unpacked.oldLen == unpacked.newLen; }; /** @@ -1737,9 +1732,9 @@ exports.opAttributeValue = function (op, key, pool) { * @param pool {AttribPool} attribute pool */ exports.attribsAttributeValue = function (attribs, key, pool) { - var value = ''; + let value = ''; if (attribs) { - exports.eachAttribNumber(attribs, function (n) { + exports.eachAttribNumber(attribs, (n) => { if (pool.getAttribKey(n) == key) { value = pool.getAttribValue(n); } @@ -1754,13 +1749,13 @@ exports.attribsAttributeValue = function (attribs, key, pool) { * @param oldLen {int} Old length */ exports.builder = function (oldLen) { - var assem = exports.smartOpAssembler(); - var o = exports.newOp(); - var charBank = exports.stringAssembler(); + const assem = exports.smartOpAssembler(); + const o = exports.newOp(); + const charBank = exports.stringAssembler(); var self = { // attribs are [[key1,value1],[key2,value2],...] or '*0*1...' (no pool needed in latter case) - keep: function (N, L, attribs, pool) { + keep(N, L, attribs, pool) { o.opcode = '='; o.attribs = (attribs && exports.makeAttribsString('=', attribs, pool)) || ''; o.chars = N; @@ -1768,16 +1763,16 @@ exports.builder = function (oldLen) { assem.append(o); return self; }, - keepText: function (text, attribs, pool) { + keepText(text, attribs, pool) { assem.appendOpWithText('=', text, attribs, pool); return self; }, - insert: function (text, attribs, pool) { + insert(text, attribs, pool) { assem.appendOpWithText('+', text, attribs, pool); charBank.append(text); return self; }, - remove: function (N, L) { + remove(N, L) { o.opcode = '-'; o.attribs = ''; o.chars = N; @@ -1785,11 +1780,11 @@ exports.builder = function (oldLen) { assem.append(o); return self; }, - toString: function () { + toString() { assem.endDocument(); - var newLen = oldLen + assem.getLengthChange(); + const newLen = oldLen + assem.getLengthChange(); return exports.pack(oldLen, newLen, assem.toString(), charBank.toString()); - } + }, }; return self; @@ -1799,18 +1794,18 @@ exports.makeAttribsString = function (opcode, attribs, pool) { // makeAttribsString(opcode, '*3') or makeAttribsString(opcode, [['foo','bar']], myPool) work if (!attribs) { return ''; - } else if ((typeof attribs) == "string") { + } else if ((typeof attribs) === 'string') { return attribs; } else if (pool && attribs && attribs.length) { if (attribs.length > 1) { attribs = attribs.slice(); attribs.sort(); } - var result = []; - for (var i = 0; i < attribs.length; i++) { - var pair = attribs[i]; + const result = []; + for (let i = 0; i < attribs.length; i++) { + const pair = attribs[i]; if (opcode == '=' || (opcode == '+' && pair[1])) { - result.push('*' + exports.numToString(pool.putAttrib(pair))); + result.push(`*${exports.numToString(pool.putAttrib(pair))}`); } } return result.join(''); @@ -1819,11 +1814,11 @@ exports.makeAttribsString = function (opcode, attribs, pool) { // like "substring" but on a single-line attribution string exports.subattribution = function (astr, start, optEnd) { - var iter = exports.opIterator(astr, 0); - var assem = exports.smartOpAssembler(); - var attOp = exports.newOp(); - var csOp = exports.newOp(); - var opOut = exports.newOp(); + const iter = exports.opIterator(astr, 0); + const assem = exports.smartOpAssembler(); + const attOp = exports.newOp(); + const csOp = exports.newOp(); + const opOut = exports.newOp(); function doCsOp() { if (csOp.chars) { @@ -1886,24 +1881,23 @@ exports.inverse = function (cs, lines, alines, pool) { } } - var curLine = 0; - var curChar = 0; - var curLineOpIter = null; - var curLineOpIterLine; - var curLineNextOp = exports.newOp('+'); + let curLine = 0; + let curChar = 0; + let curLineOpIter = null; + let curLineOpIterLine; + const curLineNextOp = exports.newOp('+'); - var unpacked = exports.unpack(cs); - var csIter = exports.opIterator(unpacked.ops); - var builder = exports.builder(unpacked.newLen); - - function consumeAttribRuns(numChars, func /*(len, attribs, endsLine)*/ ) { + const unpacked = exports.unpack(cs); + const csIter = exports.opIterator(unpacked.ops); + const builder = exports.builder(unpacked.newLen); + function consumeAttribRuns(numChars, func /* (len, attribs, endsLine)*/) { if ((!curLineOpIter) || (curLineOpIterLine != curLine)) { // create curLineOpIter and advance it to curChar curLineOpIter = exports.opIterator(alines_get(curLine)); curLineOpIterLine = curLine; - var indexIntoLine = 0; - var done = false; + let indexIntoLine = 0; + let done = false; while (!done && curLineOpIter.hasNext()) { curLineOpIter.next(curLineNextOp); if (indexIntoLine + curLineNextOp.chars >= curChar) { @@ -1926,7 +1920,7 @@ exports.inverse = function (cs, lines, alines, pool) { if (!curLineNextOp.chars) { 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); numChars -= charsToUse; curLineNextOp.chars -= charsToUse; @@ -1943,25 +1937,23 @@ exports.inverse = function (cs, lines, alines, pool) { if (L) { curLine += L; curChar = 0; + } else if (curLineOpIter && curLineOpIterLine == curLine) { + consumeAttribRuns(N, () => {}); } else { - if (curLineOpIter && curLineOpIterLine == curLine) { - consumeAttribRuns(N, function () {}); - } else { - curChar += N; - } + curChar += N; } } function nextText(numChars) { - var len = 0; - var assem = exports.stringAssembler(); - var firstString = lines_get(curLine).substring(curChar); + let len = 0; + const assem = exports.stringAssembler(); + const firstString = lines_get(curLine).substring(curChar); len += firstString.length; assem.append(firstString); - var lineNum = curLine + 1; + let lineNum = curLine + 1; while (len < numChars) { - var nextString = lines_get(lineNum); + const nextString = lines_get(lineNum); len += nextString.length; assem.append(nextString); lineNum++; @@ -1971,7 +1963,7 @@ exports.inverse = function (cs, lines, alines, pool) { } function cachedStrFunc(func) { - var cache = {}; + const cache = {}; return function (s) { if (!cache[s]) { cache[s] = func(s); @@ -1980,31 +1972,31 @@ exports.inverse = function (cs, lines, alines, pool) { }; } - var attribKeys = []; - var attribValues = []; + const attribKeys = []; + const attribValues = []; while (csIter.hasNext()) { - var csOp = csIter.next(); + const csOp = csIter.next(); if (csOp.opcode == '=') { if (csOp.attribs) { attribKeys.length = 0; attribValues.length = 0; - exports.eachAttribNumber(csOp.attribs, function (n) { + exports.eachAttribNumber(csOp.attribs, (n) => { attribKeys.push(pool.getAttribKey(n)); attribValues.push(pool.getAttribValue(n)); }); - var undoBackToAttribs = cachedStrFunc(function (attribs) { - var backAttribs = []; - for (var i = 0; i < attribKeys.length; i++) { - var appliedKey = attribKeys[i]; - var appliedValue = attribValues[i]; - var oldValue = exports.attribsAttributeValue(attribs, appliedKey, pool); + var undoBackToAttribs = cachedStrFunc((attribs) => { + const backAttribs = []; + for (let i = 0; i < attribKeys.length; i++) { + const appliedKey = attribKeys[i]; + const appliedValue = attribValues[i]; + const oldValue = exports.attribsAttributeValue(attribs, appliedKey, pool); if (appliedValue != oldValue) { backAttribs.push([appliedKey, oldValue]); } } return exports.makeAttribsString('=', backAttribs, pool); }); - consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) { + consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => { builder.keep(len, endsLine ? 1 : 0, undoBackToAttribs(attribs)); }); } else { @@ -2016,7 +2008,7 @@ exports.inverse = function (cs, lines, alines, pool) { } else if (csOp.opcode == '-') { var textBank = nextText(csOp.chars); var textBankIndex = 0; - consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) { + consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => { builder.insert(textBank.substr(textBankIndex, len), attribs); textBankIndex += len; }); @@ -2028,33 +2020,33 @@ exports.inverse = function (cs, lines, alines, pool) { // %CLIENT FILE ENDS HERE% exports.follow = function (cs1, cs2, reverseInsertOrder, pool) { - var unpacked1 = exports.unpack(cs1); - var unpacked2 = exports.unpack(cs2); - var len1 = unpacked1.oldLen; - var len2 = unpacked2.oldLen; - exports.assert(len1 == len2, "mismatched follow - cannot transform cs1 on top of cs2"); - var chars1 = exports.stringIterator(unpacked1.charBank); - var chars2 = exports.stringIterator(unpacked2.charBank); + const unpacked1 = exports.unpack(cs1); + const unpacked2 = exports.unpack(cs2); + const len1 = unpacked1.oldLen; + const len2 = unpacked2.oldLen; + exports.assert(len1 == len2, 'mismatched follow - cannot transform cs1 on top of cs2'); + const chars1 = exports.stringIterator(unpacked1.charBank); + const chars2 = exports.stringIterator(unpacked2.charBank); - var oldLen = unpacked1.newLen; - var oldPos = 0; - var newLen = 0; + const oldLen = unpacked1.newLen; + let oldPos = 0; + let newLen = 0; - var hasInsertFirst = exports.attributeTester(['insertorder', 'first'], pool); + const hasInsertFirst = exports.attributeTester(['insertorder', 'first'], pool); - var newOps = exports.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function (op1, op2, opOut) { + const newOps = exports.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, (op1, op2, opOut) => { if (op1.opcode == '+' || op2.opcode == '+') { - var whichToDo; + let whichToDo; if (op2.opcode != '+') { whichToDo = 1; } else if (op1.opcode != '+') { whichToDo = 2; } else { // both + - var firstChar1 = chars1.peek(1); - var firstChar2 = chars2.peek(1); - var insertFirst1 = hasInsertFirst(op1.attribs); - var insertFirst2 = hasInsertFirst(op2.attribs); + const firstChar1 = chars1.peek(1); + const firstChar2 = chars2.peek(1); + const insertFirst1 = hasInsertFirst(op1.attribs); + const insertFirst2 = hasInsertFirst(op2.attribs); if (insertFirst1 && !insertFirst2) { whichToDo = 1; } else if (insertFirst2 && !insertFirst1) { @@ -2089,19 +2081,17 @@ exports.follow = function (cs1, cs2, reverseInsertOrder, pool) { } else if (op1.opcode == '-') { if (!op2.opcode) { op1.opcode = ''; - } else { - if (op1.chars <= op2.chars) { - op2.chars -= op1.chars; - op2.lines -= op1.lines; - op1.opcode = ''; - if (!op2.chars) { - op2.opcode = ''; - } - } else { - op1.chars -= op2.chars; - op1.lines -= op2.lines; + } else if (op1.chars <= op2.chars) { + op2.chars -= op1.chars; + op2.lines -= op1.lines; + op1.opcode = ''; + if (!op2.chars) { op2.opcode = ''; } + } else { + op1.chars -= op2.chars; + op1.lines -= op2.lines; + op2.opcode = ''; } } else if (op2.opcode == '-') { exports.copyOp(op2, opOut); @@ -2153,16 +2143,16 @@ exports.follow = function (cs1, cs2, reverseInsertOrder, pool) { } } switch (opOut.opcode) { - case '=': - oldPos += opOut.chars; - newLen += opOut.chars; - break; - case '-': - oldPos += opOut.chars; - break; - case '+': - newLen += opOut.chars; - break; + case '=': + oldPos += opOut.chars; + newLen += opOut.chars; + break; + case '-': + oldPos += opOut.chars; + break; + case '+': + newLen += opOut.chars; + break; } }); newLen += oldLen - oldPos; @@ -2179,15 +2169,15 @@ exports.followAttributes = function (att1, att2, pool) { // to produce the merged set. if ((!att2) || (!pool)) return ''; if (!att1) return att2; - var atts = []; - att2.replace(/\*([0-9a-z]+)/g, function (_, a) { + const atts = []; + att2.replace(/\*([0-9a-z]+)/g, (_, a) => { atts.push(pool.getAttrib(exports.parseNum(a))); return ''; }); - att1.replace(/\*([0-9a-z]+)/g, function (_, a) { - var pair1 = pool.getAttrib(exports.parseNum(a)); - for (var i = 0; i < atts.length; i++) { - var pair2 = atts[i]; + att1.replace(/\*([0-9a-z]+)/g, (_, a) => { + const pair1 = pool.getAttrib(exports.parseNum(a)); + for (let i = 0; i < atts.length; i++) { + const pair2 = atts[i]; if (pair1[0] == pair2[0]) { if (pair1[1] <= pair2[1]) { // winner of merge is pair1, delete this attribute @@ -2199,8 +2189,8 @@ exports.followAttributes = function (att1, att2, pool) { return ''; }); // we've only removed attributes, so they're already sorted - var buf = exports.stringAssembler(); - for (var i = 0; i < atts.length; i++) { + const buf = exports.stringAssembler(); + for (let i = 0; i < atts.length; i++) { buf.append('*'); buf.append(exports.numToString(pool.putAttrib(atts[i]))); } @@ -2208,19 +2198,19 @@ exports.followAttributes = function (att1, att2, pool) { }; exports.composeWithDeletions = function (cs1, cs2, pool) { - var unpacked1 = exports.unpack(cs1); - var unpacked2 = exports.unpack(cs2); - var len1 = unpacked1.oldLen; - var len2 = unpacked1.newLen; - exports.assert(len2 == unpacked2.oldLen, "mismatched composition of two changesets"); - var len3 = unpacked2.newLen; - var bankIter1 = exports.stringIterator(unpacked1.charBank); - var bankIter2 = exports.stringIterator(unpacked2.charBank); - var bankAssem = exports.stringAssembler(); + const unpacked1 = exports.unpack(cs1); + const unpacked2 = exports.unpack(cs2); + const len1 = unpacked1.oldLen; + const len2 = unpacked1.newLen; + exports.assert(len2 == unpacked2.oldLen, 'mismatched composition of two changesets'); + const len3 = unpacked2.newLen; + const bankIter1 = exports.stringIterator(unpacked1.charBank); + const bankIter2 = exports.stringIterator(unpacked2.charBank); + const bankAssem = exports.stringAssembler(); - var newOps = exports.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function (op1, op2, opOut) { - var op1code = op1.opcode; - var op2code = op2.opcode; + const newOps = exports.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, (op1, op2, opOut) => { + const op1code = op1.opcode; + const op2code = op2.opcode; if (op1code == '+' && op2code == '-') { bankIter1.skip(Math.min(op1.chars, op2.chars)); } @@ -2239,11 +2229,11 @@ exports.composeWithDeletions = function (cs1, cs2, pool) { // This function is 95% like _slicerZipperFunc, we just changed two lines to ensure it merges the attribs of deletions properly. // This is necassary for correct paddiff. But to ensure these changes doesn't affect anything else, we've created a seperate function only used for paddiffs -exports._slicerZipperFuncWithDeletions= function (attOp, csOp, opOut, pool) { +exports._slicerZipperFuncWithDeletions = function (attOp, csOp, opOut, pool) { // attOp is the op from the sequence that is being operated on, either an // attribution string or the earlier of two exportss being composed. // pool can be null if definitely not needed. - //print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource()); + // print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource()); if (attOp.opcode == '-') { exports.copyOp(attOp, opOut); attOp.opcode = ''; @@ -2252,7 +2242,7 @@ exports._slicerZipperFuncWithDeletions= function (attOp, csOp, opOut, pool) { csOp.opcode = ''; } else { switch (csOp.opcode) { - case '-': + case '-': { if (csOp.chars <= attOp.chars) { // delete or delete part @@ -2260,7 +2250,7 @@ exports._slicerZipperFuncWithDeletions= function (attOp, csOp, opOut, pool) { opOut.opcode = '-'; opOut.chars = csOp.chars; opOut.lines = csOp.lines; - opOut.attribs = csOp.attribs; //changed by yammer + opOut.attribs = csOp.attribs; // changed by yammer } attOp.chars -= csOp.chars; attOp.lines -= csOp.lines; @@ -2274,7 +2264,7 @@ exports._slicerZipperFuncWithDeletions= function (attOp, csOp, opOut, pool) { opOut.opcode = '-'; opOut.chars = attOp.chars; opOut.lines = attOp.lines; - opOut.attribs = csOp.attribs; //changed by yammer + opOut.attribs = csOp.attribs; // changed by yammer } csOp.chars -= attOp.chars; csOp.lines -= attOp.lines; @@ -2282,14 +2272,14 @@ exports._slicerZipperFuncWithDeletions= function (attOp, csOp, opOut, pool) { } break; } - case '+': + case '+': { // insert exports.copyOp(csOp, opOut); csOp.opcode = ''; break; } - case '=': + case '=': { if (csOp.chars <= attOp.chars) { // keep or keep part @@ -2315,7 +2305,7 @@ exports._slicerZipperFuncWithDeletions= function (attOp, csOp, opOut, pool) { } break; } - case '': + case '': { exports.copyOp(attOp, opOut); attOp.opcode = ''; diff --git a/src/static/js/ChangesetUtils.js b/src/static/js/ChangesetUtils.js index cae2d81ac..c7333afcf 100644 --- a/src/static/js/ChangesetUtils.js +++ b/src/static/js/ChangesetUtils.js @@ -18,40 +18,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -exports.buildRemoveRange = function(rep, builder, start, end) { - var startLineOffset = rep.lines.offsetOfIndex(start[0]); - var endLineOffset = rep.lines.offsetOfIndex(end[0]); +exports.buildRemoveRange = function (rep, builder, start, end) { + const startLineOffset = rep.lines.offsetOfIndex(start[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(end[1]); - } - else - { + } else { builder.remove(end[1] - start[1]); } -} +}; -exports.buildKeepRange = function(rep, builder, start, end, attribs, pool) { - var startLineOffset = rep.lines.offsetOfIndex(start[0]); - var endLineOffset = rep.lines.offsetOfIndex(end[0]); +exports.buildKeepRange = function (rep, builder, start, end, attribs, pool) { + const startLineOffset = rep.lines.offsetOfIndex(start[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(end[1], 0, attribs, pool); - } - else - { + } else { builder.keep(end[1] - start[1], 0, attribs, pool); } -} +}; -exports.buildKeepToStartOfRange = function(rep, builder, start) { - var startLineOffset = rep.lines.offsetOfIndex(start[0]); +exports.buildKeepToStartOfRange = function (rep, builder, start) { + const startLineOffset = rep.lines.offsetOfIndex(start[0]); builder.keep(startLineOffset, start[0]); builder.keep(start[1]); -} - +}; diff --git a/src/static/js/ace.js b/src/static/js/ace.js index 36b8558fe..3bc788d0e 100644 --- a/src/static/js/ace.js +++ b/src/static/js/ace.js @@ -23,61 +23,57 @@ // requires: top // requires: undefined -var KERNEL_SOURCE = '../static/js/require-kernel.js'; +const KERNEL_SOURCE = '../static/js/require-kernel.js'; Ace2Editor.registry = { - nextId: 1 + nextId: 1, }; -var hooks = require('./pluginfw/hooks'); -var pluginUtils = require('./pluginfw/shared'); -var _ = require('./underscore'); +const hooks = require('./pluginfw/hooks'); +const pluginUtils = require('./pluginfw/shared'); +const _ = require('./underscore'); function scriptTag(source) { return ( - '' - ) + `` + ); } function Ace2Editor() { - var ace2 = Ace2Editor; + const ace2 = Ace2Editor; - var editor = {}; - var info = { - editor: editor, - id: (ace2.registry.nextId++) + const editor = {}; + let info = { + editor, + id: (ace2.registry.nextId++), }; - var loaded = false; + let loaded = false; - var actionsPendingInit = []; + let actionsPendingInit = []; function pendingInit(func, optDoNow) { - return function() { - var that = this; - var args = arguments; - var action = function() { + return function () { + const that = this; + const args = arguments; + const action = function () { func.apply(that, args); - } - if (optDoNow) - { + }; + if (optDoNow) { optDoNow.apply(that, args); } - if (loaded) - { + if (loaded) { action(); - } - else - { + } else { actionsPendingInit.push(action); } }; } function doActionsPendingInit() { - _.each(actionsPendingInit, function(fn,i){ - fn() + _.each(actionsPendingInit, (fn, i) => { + fn(); }); actionsPendingInit = []; } @@ -86,43 +82,56 @@ function Ace2Editor() { // The following functions (prefixed by 'ace_') are exposed by editor, but // execution is delayed until init is complete - var aceFunctionsPendingInit = ['importText', 'importAText', 'focus', - 'setEditable', 'getFormattedCode', 'setOnKeyPress', 'setOnKeyDown', - 'setNotifyDirty', 'setProperty', 'setBaseText', 'setBaseAttributedText', - 'applyChangesToBase', 'applyPreparedChangesetToBase', - 'setUserChangeNotificationCallback', 'setAuthorInfo', - 'setAuthorSelectionRange', 'callWithAce', 'execCommand', 'replaceRange']; + const aceFunctionsPendingInit = ['importText', + 'importAText', + 'focus', + 'setEditable', + 'getFormattedCode', + 'setOnKeyPress', + 'setOnKeyDown', + 'setNotifyDirty', + 'setProperty', + 'setBaseText', + 'setBaseAttributedText', + 'applyChangesToBase', + 'applyPreparedChangesetToBase', + 'setUserChangeNotificationCallback', + 'setAuthorInfo', + 'setAuthorSelectionRange', + 'callWithAce', + 'execCommand', + 'replaceRange']; - _.each(aceFunctionsPendingInit, function(fnName,i){ - var prefix = 'ace_'; - var name = prefix + fnName; - editor[fnName] = pendingInit(function(){ - if(fnName === "setAuthorInfo"){ - if(!arguments[0]){ + _.each(aceFunctionsPendingInit, (fnName, i) => { + const prefix = 'ace_'; + const name = prefix + fnName; + editor[fnName] = pendingInit(function () { + if (fnName === 'setAuthorInfo') { + if (!arguments[0]) { // setAuthorInfo AuthorId not set for some reason - }else{ + } else { info[prefix + fnName].apply(this, arguments); } - }else{ + } else { info[prefix + fnName].apply(this, arguments); } }); }); - editor.exportText = function() { - if (!loaded) return "(awaiting init)\n"; + editor.exportText = function () { + if (!loaded) return '(awaiting init)\n'; return info.ace_exportText(); }; - editor.getFrame = function() { + editor.getFrame = function () { return info.frame || null; }; - editor.getDebugProperty = function(prop) { + editor.getDebugProperty = function (prop) { return info.ace_getDebugProperty(prop); }; - editor.getInInternationalComposition = function() { + editor.getInInternationalComposition = function () { if (!loaded) return false; return info.ace_getInInternationalComposition(); }; @@ -136,26 +145,25 @@ function Ace2Editor() { // to prepareUserChangeset will return an updated changeset that takes into account the // latest user changes, and modify the changeset to be applied by applyPreparedChangesetToBase // accordingly. - editor.prepareUserChangeset = function() { + editor.prepareUserChangeset = function () { if (!loaded) return null; return info.ace_prepareUserChangeset(); }; - editor.getUnhandledErrors = function() { + editor.getUnhandledErrors = function () { if (!loaded) return []; // returns array of {error: , time: +new Date()} return info.ace_getUnhandledErrors(); }; - function sortFilesByEmbeded(files) { - var embededFiles = []; - var remoteFiles = []; + const embededFiles = []; + let remoteFiles = []; if (Ace2Editor.EMBEDED) { - for (var i = 0, ii = files.length; i < ii; i++) { - var file = files[i]; + for (let i = 0, ii = files.length; i < ii; i++) { + const file = files[i]; if (Object.prototype.hasOwnProperty.call(Ace2Editor.EMBEDED, file)) { embededFiles.push(file); } else { @@ -169,9 +177,9 @@ function Ace2Editor() { return {embeded: embededFiles, remote: remoteFiles}; } function pushStyleTagsFor(buffer, files) { - var sorted = sortFilesByEmbeded(files); - var embededFiles = sorted.embeded; - var remoteFiles = sorted.remote; + const sorted = sortFilesByEmbeded(files); + const embededFiles = sorted.embeded; + const remoteFiles = sorted.remote; if (embededFiles.length > 0) { buffer.push(''); - hooks.callAll("aceInitInnerdocbodyHead", { - iframeHTML: iframeHTML + hooks.callAll('aceInitInnerdocbodyHead', { + iframeHTML, }); iframeHTML.push(' '); // Expose myself to global for my child frame. - var thisFunctionsName = "ChildAccessibleAce2Editor"; - (function () {return this}())[thisFunctionsName] = Ace2Editor; + const thisFunctionsName = 'ChildAccessibleAce2Editor'; + (function () { return this; }())[thisFunctionsName] = Ace2Editor; - var outerScript = '\ -editorId = ' + JSON.stringify(info.id) + ';\n\ -editorInfo = parent[' + JSON.stringify(thisFunctionsName) + '].registry[editorId];\n\ + const outerScript = `\ +editorId = ${JSON.stringify(info.id)};\n\ +editorInfo = parent[${JSON.stringify(thisFunctionsName)}].registry[editorId];\n\ window.onload = function () {\n\ window.onload = null;\n\ setTimeout(function () {\n\ @@ -293,34 +300,35 @@ window.onload = function () {\n\ };\n\ var doc = iframe.contentWindow.document;\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.close();\n\ }, 0);\n\ -}'; +}`; - var outerHTML = [doctype, ''] + const outerHTML = [doctype, ``]; var includedCSS = []; - var $$INCLUDE_CSS = function(filename) {includedCSS.push(filename)}; - $$INCLUDE_CSS("../static/css/iframe_editor.css"); - $$INCLUDE_CSS("../static/css/pad.css?v=" + clientVars.randomVersionString); + var $$INCLUDE_CSS = function (filename) { includedCSS.push(filename); }; + $$INCLUDE_CSS('../static/css/iframe_editor.css'); + $$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 return path; } - return '../static/plugins/' + path } + return `../static/plugins/${path}`; + }, ); 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); // bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly // (throbs busy while typing) - var pluginNames = pluginUtils.clientPluginNames(); + const pluginNames = pluginUtils.clientPluginNames(); outerHTML.push( '', '', @@ -331,14 +339,14 @@ window.onload = function () {\n\ '
        x
        ', ''); - var outerFrame = document.createElement("IFRAME"); - outerFrame.name = "ace_outer"; + const outerFrame = document.createElement('IFRAME'); + outerFrame.name = 'ace_outer'; outerFrame.frameBorder = 0; // for IE - outerFrame.title = "Ether"; + outerFrame.title = 'Ether'; info.frame = outerFrame; document.getElementById(containerId).appendChild(outerFrame); - var editorDocument = outerFrame.contentWindow.document; + const editorDocument = outerFrame.contentWindow.document; editorDocument.open(); editorDocument.write(outerHTML.join('')); diff --git a/src/static/js/ace2_common.js b/src/static/js/ace2_common.js index 2e602097a..9055b34e3 100644 --- a/src/static/js/ace2_common.js +++ b/src/static/js/ace2_common.js @@ -20,27 +20,27 @@ * limitations under the License. */ -var Security = require('./security'); +const Security = require('./security'); function isNodeText(node) { return (node.nodeType == 3); } function object(o) { - var f = function(){}; + const f = function () {}; f.prototype = o; return new f(); } function getAssoc(obj, name) { - return obj["_magicdom_" + name]; + return obj[`_magicdom_${name}`]; } function setAssoc(obj, name, value) { // note that in IE designMode, properties of a node can get // copied to new nodes that are spawned during editing; also, // 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 @@ -52,11 +52,10 @@ function binarySearch(numItems, func) { if (numItems < 1) return 0; if (func(0)) return 0; if (!func(numItems - 1)) return numItems; - var low = 0; // func(low) is always false - var high = numItems - 1; // func(high) is always true - while ((high - low) > 1) - { - var x = Math.floor((low + high) / 2); // x != low, x != high + let low = 0; // func(low) is always false + let high = numItems - 1; // func(high) is always true + while ((high - low) > 1) { + const x = Math.floor((low + high) / 2); // x != low, x != high if (func(x)) high = x; else low = x; } @@ -64,7 +63,7 @@ function binarySearch(numItems, func) { } function binarySearchInfinite(expectedLength, func) { - var i = 0; + let i = 0; while (!func(i)) i += expectedLength; return binarySearch(i, func); } @@ -73,7 +72,7 @@ function htmlPrettyEscape(str) { return Security.escapeHTML(str).replace(/\r?\n/g, '\\n'); } -var noop = function(){}; +const noop = function () {}; exports.isNodeText = isNodeText; exports.object = object; diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index efae0e052..277e7d35c 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -19,12 +19,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -var _, $, jQuery, plugins, Ace2Common; -var browser = require('./browser'); -if(browser.msie){ +let _, $, jQuery, plugins, Ace2Common; +const browser = require('./browser'); +if (browser.msie) { // Honestly fuck IE royally. // Basically every hack we have since V11 causes a problem - if(parseInt(browser.version) >= 11){ + if (parseInt(browser.version) >= 11) { delete browser.msie; browser.chrome = true; browser.modernIE = true; @@ -35,115 +35,110 @@ Ace2Common = require('./ace2_common'); plugins = require('ep_etherpad-lite/static/js/pluginfw/client_plugins'); $ = jQuery = require('./rjquery').$; -_ = require("./underscore"); +_ = require('./underscore'); -var isNodeText = Ace2Common.isNodeText, - getAssoc = Ace2Common.getAssoc, - setAssoc = Ace2Common.setAssoc, - isTextNode = Ace2Common.isTextNode, - binarySearchInfinite = Ace2Common.binarySearchInfinite, - htmlPrettyEscape = Ace2Common.htmlPrettyEscape, - noop = Ace2Common.noop; -var hooks = require('./pluginfw/hooks'); +const isNodeText = Ace2Common.isNodeText; +const getAssoc = Ace2Common.getAssoc; +const setAssoc = Ace2Common.setAssoc; +const isTextNode = Ace2Common.isTextNode; +const binarySearchInfinite = Ace2Common.binarySearchInfinite; +const htmlPrettyEscape = Ace2Common.htmlPrettyEscape; +const noop = Ace2Common.noop; +const hooks = require('./pluginfw/hooks'); -function Ace2Inner(){ +function Ace2Inner() { + const makeChangesetTracker = require('./changesettracker').makeChangesetTracker; + const colorutils = require('./colorutils').colorutils; + const makeContentCollector = require('./contentcollector').makeContentCollector; + const makeCSSManager = require('./cssmanager').makeCSSManager; + const domline = require('./domline').domline; + const AttribPool = require('./AttributePool'); + const Changeset = require('./Changeset'); + const ChangesetUtils = require('./ChangesetUtils'); + const linestylefilter = require('./linestylefilter').linestylefilter; + const SkipList = require('./skiplist'); + const undoModule = require('./undomodule').undoModule; + const AttributeManager = require('./AttributeManager'); + const Scroll = require('./scroll'); - var makeChangesetTracker = require('./changesettracker').makeChangesetTracker; - var colorutils = require('./colorutils').colorutils; - var makeContentCollector = require('./contentcollector').makeContentCollector; - var makeCSSManager = require('./cssmanager').makeCSSManager; - var domline = require('./domline').domline; - var AttribPool = require('./AttributePool'); - var Changeset = require('./Changeset'); - var ChangesetUtils = require('./ChangesetUtils'); - var linestylefilter = require('./linestylefilter').linestylefilter; - var SkipList = require('./skiplist'); - var undoModule = require('./undomodule').undoModule; - var AttributeManager = require('./AttributeManager'); - var Scroll = require('./scroll'); - - var DEBUG = false; //$$ build script replaces the string "var DEBUG=true;//$$" with "var DEBUG=false;" + const DEBUG = false; // $$ build script replaces the string "var DEBUG=true;//$$" with "var DEBUG=false;" // changed to false - var isSetUp = false; + let isSetUp = false; - var THE_TAB = ' '; //4 - var MAX_LIST_LEVEL = 16; + const THE_TAB = ' '; // 4 + const MAX_LIST_LEVEL = 16; - var FORMATTING_STYLES = ['bold', 'italic', 'underline', 'strikethrough']; - var SELECT_BUTTON_CLASS = 'selected'; + const FORMATTING_STYLES = ['bold', 'italic', 'underline', 'strikethrough']; + const SELECT_BUTTON_CLASS = 'selected'; - var caughtErrors = []; + const caughtErrors = []; - var thisAuthor = ''; + let thisAuthor = ''; - var disposed = false; - var editorInfo = parent.editorInfo; + let disposed = false; + const editorInfo = parent.editorInfo; - var iframe = window.frameElement; - var outerWin = iframe.ace_outerWin; + const iframe = window.frameElement; + const outerWin = iframe.ace_outerWin; iframe.ace_outerWin = null; // prevent IE 6 memory leak - var sideDiv = iframe.nextSibling; - var lineMetricsDiv = sideDiv.nextSibling; - var lineNumbersShown; - var sideDivInner; + const sideDiv = iframe.nextSibling; + const lineMetricsDiv = sideDiv.nextSibling; + let lineNumbersShown; + let sideDivInner; initLineNumbers(); - var scroll = Scroll.init(outerWin); + const scroll = Scroll.init(outerWin); - var outsideKeyDown = noop; + let outsideKeyDown = noop; - var outsideKeyPress = function(){return true;}; + let outsideKeyPress = function () { return true; }; - var outsideNotifyDirty = noop; + let outsideNotifyDirty = noop; // selFocusAtStart -- determines whether the selection extends "backwards", so that the focus // point (controlled with the arrow keys) is at the beginning; not supported in IE, though // native IE selections have that behavior (which we try not to interfere with). // Must be false if selection is collapsed! - var rep = { + const rep = { lines: new SkipList(), selStart: null, selEnd: null, selFocusAtStart: false, - alltext: "", + alltext: '', alines: [], - apool: new AttribPool() + apool: new AttribPool(), }; // lines, alltext, alines, and DOM are set up in init() - if (undoModule.enabled) - { + if (undoModule.enabled) { undoModule.apool = rep.apool; } - var root, doc; // set in init() - var isEditable = true; - var doesWrap = true; - var hasLineNumbers = true; - var isStyled = true; + let root, doc; // set in init() + let isEditable = true; + let doesWrap = true; + let hasLineNumbers = true; + let isStyled = true; - var console = (DEBUG && window.console); - var documentAttributeManager; + let console = (DEBUG && window.console); + let documentAttributeManager; - if (!window.console) - { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; + if (!window.console) { + const names = ['log', 'debug', 'info', 'warn', 'error', 'assert', 'dir', 'dirxml', 'group', 'groupEnd', 'time', 'timeEnd', 'count', 'trace', 'profile', 'profileEnd']; console = {}; - for (var i = 0; i < names.length; ++i) - console[names[i]] = noop; + for (let i = 0; i < names.length; ++i) console[names[i]] = noop; } - var PROFILER = window.PROFILER; - if (!PROFILER) - { - PROFILER = function() { + let PROFILER = window.PROFILER; + if (!PROFILER) { + PROFILER = function () { return { start: noop, mark: noop, literal: noop, end: noop, - cancel: noop + cancel: noop, }; }; } @@ -152,135 +147,118 @@ function Ace2Inner(){ // visible when "?djs=1" is appended to the pad URL. It generally // remains a no-op unless djs is enabled, but we make a habit of // only calling it in error cases or while debugging. - var dmesg = noop; + let dmesg = noop; window.dmesg = noop; - var scheduler = parent; // hack for opera required + const scheduler = parent; // hack for opera required - var dynamicCSS = null; - var outerDynamicCSS = null; - var parentDynamicCSS = null; + let dynamicCSS = null; + let outerDynamicCSS = null; + let parentDynamicCSS = null; function initDynamicCSS() { - dynamicCSS = makeCSSManager("dynamicsyntax"); - outerDynamicCSS = makeCSSManager("dynamicsyntax", "outer"); - parentDynamicCSS = makeCSSManager("dynamicsyntax", "parent"); + dynamicCSS = makeCSSManager('dynamicsyntax'); + outerDynamicCSS = makeCSSManager('dynamicsyntax', 'outer'); + parentDynamicCSS = makeCSSManager('dynamicsyntax', 'parent'); } - var changesetTracker = makeChangesetTracker(scheduler, rep.apool, { - withCallbacks: function(operationName, f) { - inCallStackIfNecessary(operationName, function() { + const changesetTracker = makeChangesetTracker(scheduler, rep.apool, { + withCallbacks(operationName, f) { + inCallStackIfNecessary(operationName, () => { fastIncorp(1); f( - { - setDocumentAttributedText: function(atext) { - setDocAText(atext); - }, - applyChangesetToDocument: function(changeset, preferInsertionAfterCaret) { - var oldEventType = currentCallStack.editEvent.eventType; - currentCallStack.startNewEvent("nonundoable"); + { + setDocumentAttributedText(atext) { + setDocAText(atext); + }, + applyChangesetToDocument(changeset, preferInsertionAfterCaret) { + const oldEventType = currentCallStack.editEvent.eventType; + currentCallStack.startNewEvent('nonundoable'); - performDocumentApplyChangeset(changeset, preferInsertionAfterCaret); + performDocumentApplyChangeset(changeset, preferInsertionAfterCaret); - currentCallStack.startNewEvent(oldEventType); - } - }); + currentCallStack.startNewEvent(oldEventType); + }, + }); }); - } + }, }); - var authorInfos = {}; // presence of key determines if author is present in doc + const authorInfos = {}; // presence of key determines if author is present in doc - function getAuthorInfos(){ + function getAuthorInfos() { return authorInfos; - }; - editorInfo.ace_getAuthorInfos= getAuthorInfos; + } + editorInfo.ace_getAuthorInfos = getAuthorInfos; function setAuthorStyle(author, info) { if (!dynamicCSS) { return; } - var authorSelector = getAuthorColorClassSelector(getAuthorClassName(author)); + const authorSelector = getAuthorColorClassSelector(getAuthorClassName(author)); - var authorStyleSet = hooks.callAll('aceSetAuthorStyle', { - dynamicCSS: dynamicCSS, - parentDynamicCSS: parentDynamicCSS, - outerDynamicCSS: outerDynamicCSS, - info: info, - author: author, - authorSelector: authorSelector, + const authorStyleSet = hooks.callAll('aceSetAuthorStyle', { + dynamicCSS, + parentDynamicCSS, + outerDynamicCSS, + info, + author, + authorSelector, }); // Prevent default behaviour if any hook says so - if (_.any(authorStyleSet, function(it) { return it })) - { - return + if (_.any(authorStyleSet, (it) => it)) { + return; } - if (!info) - { + if (!info) { dynamicCSS.removeSelectorStyle(authorSelector); parentDynamicCSS.removeSelectorStyle(authorSelector); - } - else - { - if (info.bgcolor) - { - var bgcolor = info.bgcolor; - if ((typeof info.fade) == "number") - { - bgcolor = fadeColor(bgcolor, info.fade); - } - - var authorStyle = dynamicCSS.selectorStyle(authorSelector); - var parentAuthorStyle = parentDynamicCSS.selectorStyle(authorSelector); - - // author color - authorStyle.backgroundColor = bgcolor; - parentAuthorStyle.backgroundColor = bgcolor; - - var textColor = colorutils.textColorFromBackgroundColor(bgcolor, parent.parent.clientVars.skinName); - authorStyle.color = textColor; - parentAuthorStyle.color = textColor; + } else if (info.bgcolor) { + let bgcolor = info.bgcolor; + if ((typeof info.fade) === 'number') { + bgcolor = fadeColor(bgcolor, info.fade); } + + const authorStyle = dynamicCSS.selectorStyle(authorSelector); + const parentAuthorStyle = parentDynamicCSS.selectorStyle(authorSelector); + + // author color + authorStyle.backgroundColor = bgcolor; + parentAuthorStyle.backgroundColor = bgcolor; + + const textColor = colorutils.textColorFromBackgroundColor(bgcolor, parent.parent.clientVars.skinName); + authorStyle.color = textColor; + parentAuthorStyle.color = textColor; } } function setAuthorInfo(author, info) { - if ((typeof author) != "string") - { + if ((typeof author) !== 'string') { // Potentially caused by: https://github.com/ether/etherpad-lite/issues/2802"); - throw new Error("setAuthorInfo: author (" + author + ") is not a string"); + throw new Error(`setAuthorInfo: author (${author}) is not a string`); } - if (!info) - { + if (!info) { delete authorInfos[author]; - } - else - { + } else { authorInfos[author] = info; } setAuthorStyle(author, info); } function getAuthorClassName(author) { - return "author-" + author.replace(/[^a-y0-9]/g, function(c) { - if (c == ".") return "-"; - return 'z' + c.charCodeAt(0) + 'z'; - }); + return `author-${author.replace(/[^a-y0-9]/g, (c) => { + if (c == '.') return '-'; + return `z${c.charCodeAt(0)}z`; + })}`; } function className2Author(className) { - if (className.substring(0, 7) == "author-") - { - return className.substring(7).replace(/[a-y0-9]+|-|z.+?z/g, function(cc) { - if (cc == '-') return '.'; - else if (cc.charAt(0) == 'z') - { + if (className.substring(0, 7) == 'author-') { + return className.substring(7).replace(/[a-y0-9]+|-|z.+?z/g, (cc) => { + if (cc == '-') { return '.'; } else if (cc.charAt(0) == 'z') { return String.fromCharCode(Number(cc.slice(1, -1))); - } - else - { + } else { return cc; } }); @@ -289,29 +267,29 @@ function Ace2Inner(){ } function getAuthorColorClassSelector(oneClassName) { - return ".authorColors ." + oneClassName; + return `.authorColors .${oneClassName}`; } function fadeColor(colorCSS, fadeFrac) { - var color = colorutils.css2triple(colorCSS); + let color = colorutils.css2triple(colorCSS); color = colorutils.blend(color, [1, 1, 1], fadeFrac); return colorutils.triple2css(color); } - editorInfo.ace_getRep = function() { + editorInfo.ace_getRep = function () { return rep; }; - editorInfo.ace_getAuthor = function() { + editorInfo.ace_getAuthor = function () { return thisAuthor; - } - - var _nonScrollableEditEvents = { - "applyChangesToBase": 1 }; - _.each(hooks.callAll('aceRegisterNonScrollableEditEvents'), function(eventType) { - _nonScrollableEditEvents[eventType] = 1; + const _nonScrollableEditEvents = { + applyChangesToBase: 1, + }; + + _.each(hooks.callAll('aceRegisterNonScrollableEditEvents'), (eventType) => { + _nonScrollableEditEvents[eventType] = 1; }); function isScrollableEditEvent(eventType) { @@ -323,13 +301,12 @@ function Ace2Inner(){ function inCallStack(type, action) { if (disposed) return; - if (currentCallStack) - { + if (currentCallStack) { // Do not uncomment this in production. It will break Etherpad being provided in iFrames. I'm leaving this in for testing usefulness. // top.console.error("Can't enter callstack " + type + ", already in " + currentCallStack.type); } - var profiling = false; + let profiling = false; function profileRest() { profiling = true; @@ -337,46 +314,34 @@ function Ace2Inner(){ function newEditEvent(eventType) { return { - eventType: eventType, - backset: null + eventType, + backset: null, }; } function submitOldEvent(evt) { - if (rep.selStart && rep.selEnd) - { - var selStartChar = rep.lines.offsetOfIndex(rep.selStart[0]) + rep.selStart[1]; - var selEndChar = rep.lines.offsetOfIndex(rep.selEnd[0]) + rep.selEnd[1]; + if (rep.selStart && rep.selEnd) { + const selStartChar = rep.lines.offsetOfIndex(rep.selStart[0]) + rep.selStart[1]; + const selEndChar = rep.lines.offsetOfIndex(rep.selEnd[0]) + rep.selEnd[1]; evt.selStart = selStartChar; evt.selEnd = selEndChar; evt.selFocusAtStart = rep.selFocusAtStart; } - if (undoModule.enabled) - { - var undoWorked = false; - try - { - if (isPadLoading(evt.eventType)) - { + if (undoModule.enabled) { + let undoWorked = false; + try { + if (isPadLoading(evt.eventType)) { undoModule.clearHistory(); - } - else if (evt.eventType == "nonundoable") - { - if (evt.changeset) - { + } else if (evt.eventType == 'nonundoable') { + if (evt.changeset) { undoModule.reportExternalChange(evt.changeset); } - } - else - { + } else { undoModule.reportEvent(evt); } undoWorked = true; - } - finally - { - if (!undoWorked) - { + } finally { + if (!undoWorked) { undoModule.enabled = false; // for safety } } @@ -384,9 +349,8 @@ function Ace2Inner(){ } function startNewEvent(eventType, dontSubmitOld) { - var oldEvent = currentCallStack.editEvent; - if (!dontSubmitOld) - { + const oldEvent = currentCallStack.editEvent; + if (!dontSubmitOld) { submitOldEvent(oldEvent); } currentCallStack.editEvent = newEditEvent(eventType); @@ -394,75 +358,62 @@ function Ace2Inner(){ } currentCallStack = { - type: type, + type, docTextChanged: false, selectionAffected: false, userChangedSelection: false, domClean: false, - profileRest: profileRest, + profileRest, isUserChange: false, // is this a "user change" type of call-stack repChanged: false, editEvent: newEditEvent(type), - startNewEvent: startNewEvent + startNewEvent, }; - var cleanExit = false; - var result; - try - { + let cleanExit = false; + let result; + try { result = action(); hooks.callAll('aceEditEvent', { callstack: currentCallStack, - editorInfo: editorInfo, - rep: rep, - documentAttributeManager: documentAttributeManager + editorInfo, + rep, + documentAttributeManager, }); cleanExit = true; - } - catch (e) - { + } catch (e) { caughtErrors.push( - { - error: e, - time: +new Date() - }); + { + error: e, + time: +new Date(), + }); dmesg(e.toString()); throw e; - } - finally - { - var cs = currentCallStack; - if (cleanExit) - { + } finally { + const cs = currentCallStack; + if (cleanExit) { submitOldEvent(cs.editEvent); - if (cs.domClean && cs.type != "setup") - { + if (cs.domClean && cs.type != 'setup') { // if (cs.isUserChange) // { // if (cs.repChanged) parenModule.notifyChange(); // else parenModule.notifyTick(); // } - if (cs.selectionAffected) - { + if (cs.selectionAffected) { updateBrowserSelectionFromRep(); } - if ((cs.docTextChanged || cs.userChangedSelection) && isScrollableEditEvent(cs.type)) - { + if ((cs.docTextChanged || cs.userChangedSelection) && isScrollableEditEvent(cs.type)) { scrollSelectionIntoView(); } - if (cs.docTextChanged && cs.type.indexOf("importText") < 0) - { + if (cs.docTextChanged && cs.type.indexOf('importText') < 0) { outsideNotifyDirty(); } } - } - else - { + } else { // non-clean exit - if (currentCallStack.type == "idleWorkTimer") - { + if (currentCallStack.type == 'idleWorkTimer') { idleWorkTimer.atLeast(1000); } } @@ -473,12 +424,9 @@ function Ace2Inner(){ editorInfo.ace_inCallStack = inCallStack; function inCallStackIfNecessary(type, action) { - if (!currentCallStack) - { + if (!currentCallStack) { inCallStack(type, action); - } - else - { + } else { action(); } } @@ -495,34 +443,25 @@ function Ace2Inner(){ function error() { - throw new Error("checkALines"); + throw new Error('checkALines'); } - if (rep.alines.length != rep.lines.length()) - { + if (rep.alines.length != rep.lines.length()) { error(); } - for (var i = 0; i < rep.alines.length; i++) - { - var aline = rep.alines[i]; - var lineText = rep.lines.atIndex(i).text + "\n"; - var lineTextLength = lineText.length; - var opIter = Changeset.opIterator(aline); - var alineLength = 0; - while (opIter.hasNext()) - { - var o = opIter.next(); + for (let i = 0; i < rep.alines.length; i++) { + const aline = rep.alines[i]; + const lineText = `${rep.lines.atIndex(i).text}\n`; + const lineTextLength = lineText.length; + const opIter = Changeset.opIterator(aline); + let alineLength = 0; + while (opIter.hasNext()) { + const o = opIter.next(); alineLength += o.chars; - if (opIter.hasNext()) - { + if (opIter.hasNext()) { if (o.lines !== 0) error(); - } - else - { - if (o.lines != 1) error(); - } + } else if (o.lines != 1) { error(); } } - if (alineLength != lineTextLength) - { + if (alineLength != lineTextLength) { error(); } } @@ -530,32 +469,28 @@ function Ace2Inner(){ function setWraps(newVal) { doesWrap = newVal; - var dwClass = "doesWrap"; + const dwClass = 'doesWrap'; root.classList.toggle('doesWrap', doesWrap); - scheduler.setTimeout(function() { - inCallStackIfNecessary("setWraps", function() { + scheduler.setTimeout(() => { + inCallStackIfNecessary('setWraps', () => { fastIncorp(7); recreateDOM(); fixView(); }); }, 0); - } function setStyled(newVal) { - var oldVal = isStyled; - isStyled = !! newVal; + const oldVal = isStyled; + isStyled = !!newVal; - if (newVal != oldVal) - { - if (!newVal) - { + if (newVal != oldVal) { + if (!newVal) { // clear styles - inCallStackIfNecessary("setStyled", function() { + inCallStackIfNecessary('setStyled', () => { fastIncorp(12); - var clearStyles = []; - for (var k in STYLE_ATTRIBS) - { + const clearStyles = []; + for (const k in STYLE_ATTRIBS) { clearStyles.push([k, '']); } performDocumentApplyAttributesToCharRange(0, rep.alltext.length, clearStyles); @@ -585,53 +520,45 @@ function Ace2Inner(){ } function importText(text, undoable, dontProcess) { - var lines; - if (dontProcess) - { - if (text.charAt(text.length - 1) != "\n") - { - throw new Error("new raw text must end with newline"); + let lines; + if (dontProcess) { + if (text.charAt(text.length - 1) != '\n') { + throw new Error('new raw text must end with newline'); } - if (/[\r\t\xa0]/.exec(text)) - { - throw new Error("new raw text must not contain CR, tab, or nbsp"); + if (/[\r\t\xa0]/.exec(text)) { + throw new Error('new raw text must not contain CR, tab, or nbsp'); } lines = text.substring(0, text.length - 1).split('\n'); - } - else - { + } else { lines = _.map(text.split('\n'), textify); } - var newText = "\n"; - if (lines.length > 0) - { - newText = lines.join('\n') + '\n'; + let newText = '\n'; + if (lines.length > 0) { + newText = `${lines.join('\n')}\n`; } - inCallStackIfNecessary("importText" + (undoable ? "Undoable" : ""), function() { + inCallStackIfNecessary(`importText${undoable ? 'Undoable' : ''}`, () => { setDocText(newText); }); - if (dontProcess && rep.alltext != text) - { - throw new Error("mismatch error setting raw text in importText"); + if (dontProcess && rep.alltext != text) { + throw new Error('mismatch error setting raw text in importText'); } } function importAText(atext, apoolJsonObj, undoable) { atext = Changeset.cloneAText(atext); - if (apoolJsonObj) - { - var wireApool = (new AttribPool()).fromJsonable(apoolJsonObj); + if (apoolJsonObj) { + const wireApool = (new AttribPool()).fromJsonable(apoolJsonObj); atext.attribs = Changeset.moveOpsToNewPool(atext.attribs, wireApool, rep.apool); } - inCallStackIfNecessary("importText" + (undoable ? "Undoable" : ""), function() { + inCallStackIfNecessary(`importText${undoable ? 'Undoable' : ''}`, () => { setDocAText(atext); }); } function setDocAText(atext) { - if (atext.text === "") { + if (atext.text === '') { /* * The server is fine with atext.text being an empty string, but the front * end is not, and crashes. @@ -643,17 +570,17 @@ function Ace2Inner(){ * See for reference: * - https://github.com/ether/etherpad-lite/issues/3861 */ - atext.text = "\n"; + atext.text = '\n'; } fastIncorp(8); - var oldLen = rep.lines.totalWidth(); - var numLines = rep.lines.length(); - var upToLastLine = rep.lines.offsetOfIndex(numLines - 1); - var lastLineLength = rep.lines.atIndex(numLines - 1).text.length; - var assem = Changeset.smartOpAssembler(); - var o = Changeset.newOp('-'); + const oldLen = rep.lines.totalWidth(); + const numLines = rep.lines.length(); + const upToLastLine = rep.lines.offsetOfIndex(numLines - 1); + const lastLineLength = rep.lines.atIndex(numLines - 1).text.length; + const assem = Changeset.smartOpAssembler(); + const o = Changeset.newOp('-'); o.chars = upToLastLine; o.lines = numLines - 1; assem.append(o); @@ -661,20 +588,19 @@ function Ace2Inner(){ o.lines = 0; assem.append(o); Changeset.appendATextToAssembler(atext, assem); - var newLen = oldLen + assem.getLengthChange(); - var changeset = Changeset.checkRep( - Changeset.pack(oldLen, newLen, assem.toString(), atext.text.slice(0, -1))); + const newLen = oldLen + assem.getLengthChange(); + const changeset = Changeset.checkRep( + Changeset.pack(oldLen, newLen, assem.toString(), atext.text.slice(0, -1))); performDocumentApplyChangeset(changeset); performSelectionChange([0, rep.lines.atIndex(0).lineMarker], [0, rep.lines.atIndex(0).lineMarker]); idleWorkTimer.atMost(100); - if (rep.alltext != atext.text) - { + if (rep.alltext != atext.text) { dmesg(htmlPrettyEscape(rep.alltext)); dmesg(htmlPrettyEscape(atext.text)); - throw new Error("mismatch error setting raw text in setDocAText"); + throw new Error('mismatch error setting raw text in setDocAText'); } } @@ -683,16 +609,15 @@ function Ace2Inner(){ } function getDocText() { - var alltext = rep.alltext; - var len = alltext.length; + const alltext = rep.alltext; + let len = alltext.length; if (len > 0) len--; // final extra newline return alltext.substring(0, len); } function exportText() { - if (currentCallStack && !currentCallStack.domClean) - { - inCallStackIfNecessary("exportText", function() { + if (currentCallStack && !currentCallStack.domClean) { + inCallStackIfNecessary('exportText', () => { fastIncorp(2); }); } @@ -716,53 +641,43 @@ function Ace2Inner(){ } function getFormattedCode() { - if (currentCallStack && !currentCallStack.domClean) - { - inCallStackIfNecessary("getFormattedCode", incorporateUserChanges); + if (currentCallStack && !currentCallStack.domClean) { + inCallStackIfNecessary('getFormattedCode', incorporateUserChanges); } - var buf = []; - if (rep.lines.length() > 0) - { + const buf = []; + if (rep.lines.length() > 0) { // should be the case, even for empty file - var entry = rep.lines.atIndex(0); - while (entry) - { - var domInfo = entry.domInfo; - buf.push((domInfo && domInfo.getInnerHTML()) || domline.processSpaces(domline.escapeHTML(entry.text), doesWrap) || ' ' /*empty line*/ ); + let entry = rep.lines.atIndex(0); + while (entry) { + const domInfo = entry.domInfo; + buf.push((domInfo && domInfo.getInnerHTML()) || domline.processSpaces(domline.escapeHTML(entry.text), doesWrap) || ' ' /* empty line*/); entry = rep.lines.next(entry); } } - return '
        ' + buf.join('
        \n
        ') + '
        '; + return `
        ${buf.join('
        \n
        ')}
        `; } - var CMDS = { - clearauthorship: function(prompt) { - if ((!(rep.selStart && rep.selEnd)) || isCaret()) - { - if (prompt) - { + const CMDS = { + clearauthorship(prompt) { + if ((!(rep.selStart && rep.selEnd)) || isCaret()) { + if (prompt) { prompt(); - } - else - { + } else { performDocumentApplyAttributesToCharRange(0, rep.alltext.length, [ - ['author', ''] + ['author', ''], ]); } - } - else - { + } else { setAttributeOnSelection('author', ''); } - } + }, }; function execCommand(cmd) { cmd = cmd.toLowerCase(); - var cmdArgs = Array.prototype.slice.call(arguments, 1); - if (CMDS[cmd]) - { - inCallStackIfNecessary(cmd, function() { + const cmdArgs = Array.prototype.slice.call(arguments, 1); + if (CMDS[cmd]) { + inCallStackIfNecessary(cmd, () => { fastIncorp(9); CMDS[cmd].apply(CMDS, cmdArgs); }); @@ -770,7 +685,7 @@ function Ace2Inner(){ } function replaceRange(start, end, text) { - inCallStackIfNecessary('replaceRange', function() { + inCallStackIfNecessary('replaceRange', () => { fastIncorp(9); performDocumentReplaceRange(start, end, text); }); @@ -789,7 +704,7 @@ function Ace2Inner(){ editorInfo.ace_setEditable = setEditable; editorInfo.ace_execCommand = execCommand; editorInfo.ace_replaceRange = replaceRange; - editorInfo.ace_getAuthorInfos= getAuthorInfos; + editorInfo.ace_getAuthorInfos = getAuthorInfos; editorInfo.ace_performDocumentReplaceRange = performDocumentReplaceRange; editorInfo.ace_performDocumentReplaceCharRange = performDocumentReplaceCharRange; editorInfo.ace_renumberList = renumberList; @@ -797,26 +712,22 @@ function Ace2Inner(){ editorInfo.ace_isBlockElement = isBlockElement; editorInfo.ace_getLineListType = getLineListType; - editorInfo.ace_callWithAce = function(fn, callStack, normalize) { - var wrapper = function() { + editorInfo.ace_callWithAce = function (fn, callStack, normalize) { + let wrapper = function () { return fn(editorInfo); }; - if (normalize !== undefined) - { - var wrapper1 = wrapper; - wrapper = function() { + if (normalize !== undefined) { + const wrapper1 = wrapper; + wrapper = function () { editorInfo.ace_fastIncorp(9); wrapper1(); }; } - if (callStack !== undefined) - { + if (callStack !== undefined) { return editorInfo.ace_inCallStack(callStack, wrapper); - } - else - { + } else { return wrapper(); } }; @@ -824,89 +735,82 @@ function Ace2Inner(){ // This methed exposes a setter for some ace properties // @param key the name of the parameter // @param value the value to set to - editorInfo.ace_setProperty = function(key, value) { + editorInfo.ace_setProperty = function (key, value) { // These properties are exposed - var setters = { + const setters = { wraps: setWraps, showsauthorcolors: (val) => root.classList.toggle('authorColors', !!val), showsuserselections: (val) => root.classList.toggle('userSelections', !!val), - showslinenumbers : function(value){ - hasLineNumbers = !! value; + showslinenumbers(value) { + hasLineNumbers = !!value; sideDiv.parentNode.classList.toggle('line-numbers-hidden', !hasLineNumbers); fixView(); }, grayedout: (val) => outerWin.document.body.classList.toggle('grayedout', !!val), - dmesg: function(){ dmesg = window.dmesg = value; }, - userauthor: function(value){ + dmesg() { dmesg = window.dmesg = value; }, + userauthor(value) { thisAuthor = String(value); documentAttributeManager.author = thisAuthor; }, styled: setStyled, textface: setTextFace, - rtlistrue: function(value) { + rtlistrue(value) { root.classList.toggle('rtl', value); root.classList.toggle('ltr', !value); - document.documentElement.dir = value? 'rtl' : 'ltr' - } + document.documentElement.dir = value ? 'rtl' : 'ltr'; + }, }; - var setter = setters[key.toLowerCase()]; + const setter = setters[key.toLowerCase()]; // check if setter is present - if(setter !== undefined){ - setter(value) + if (setter !== undefined) { + setter(value); } }; - editorInfo.ace_setBaseText = function(txt) { + editorInfo.ace_setBaseText = function (txt) { changesetTracker.setBaseText(txt); }; - editorInfo.ace_setBaseAttributedText = function(atxt, apoolJsonObj) { + editorInfo.ace_setBaseAttributedText = function (atxt, apoolJsonObj) { changesetTracker.setBaseAttributedText(atxt, apoolJsonObj); }; - editorInfo.ace_applyChangesToBase = function(c, optAuthor, apoolJsonObj) { + editorInfo.ace_applyChangesToBase = function (c, optAuthor, apoolJsonObj) { changesetTracker.applyChangesToBase(c, optAuthor, apoolJsonObj); }; - editorInfo.ace_prepareUserChangeset = function() { + editorInfo.ace_prepareUserChangeset = function () { return changesetTracker.prepareUserChangeset(); }; - editorInfo.ace_applyPreparedChangesetToBase = function() { + editorInfo.ace_applyPreparedChangesetToBase = function () { changesetTracker.applyPreparedChangesetToBase(); }; - editorInfo.ace_setUserChangeNotificationCallback = function(f) { + editorInfo.ace_setUserChangeNotificationCallback = function (f) { changesetTracker.setUserChangeNotificationCallback(f); }; - editorInfo.ace_setAuthorInfo = function(author, info) { + editorInfo.ace_setAuthorInfo = function (author, info) { setAuthorInfo(author, info); }; - editorInfo.ace_setAuthorSelectionRange = function(author, start, end) { + editorInfo.ace_setAuthorSelectionRange = function (author, start, end) { changesetTracker.setAuthorSelectionRange(author, start, end); }; - editorInfo.ace_getUnhandledErrors = function() { + editorInfo.ace_getUnhandledErrors = function () { return caughtErrors.slice(); }; - editorInfo.ace_getDocument = function() { + editorInfo.ace_getDocument = function () { return doc; }; - editorInfo.ace_getDebugProperty = function(prop) { - if (prop == "debugger") - { + editorInfo.ace_getDebugProperty = function (prop) { + if (prop == 'debugger') { // obfuscate "eval" so as not to scare yuicompressor - window['ev' + 'al']("debugger"); - } - else if (prop == "rep") - { + window['ev' + 'al']('debugger'); + } else if (prop == 'rep') { return rep; - } - else if (prop == "window") - { + } else if (prop == 'window') { return window; - } - else if (prop == "document") - { + } else if (prop == 'document') { return document; } return undefined; @@ -917,33 +821,28 @@ function Ace2Inner(){ } function newTimeLimit(ms) { - var startTime = now(); - var lastElapsed = 0; - var exceededAlready = false; - var printedTrace = false; - var isTimeUp = function() { - if (exceededAlready) - { - if ((!printedTrace)) - { // && now() - startTime - ms > 300) { - printedTrace = true; - } - return true; + const startTime = now(); + let lastElapsed = 0; + let exceededAlready = false; + let printedTrace = false; + const isTimeUp = function () { + if (exceededAlready) { + if ((!printedTrace)) { // && now() - startTime - ms > 300) { + printedTrace = true; } - var elapsed = now() - startTime; - if (elapsed > ms) - { - exceededAlready = true; - return true; - } - else - { - lastElapsed = elapsed; - return false; - } - }; + return true; + } + const elapsed = now() - startTime; + if (elapsed > ms) { + exceededAlready = true; + return true; + } else { + lastElapsed = elapsed; + return false; + } + }; - isTimeUp.elapsed = function() { + isTimeUp.elapsed = function () { return now() - startTime; }; return isTimeUp; @@ -951,12 +850,11 @@ function Ace2Inner(){ function makeIdleAction(func) { - var scheduledTimeout = null; - var scheduledTime = 0; + let scheduledTimeout = null; + let scheduledTime = 0; function unschedule() { - if (scheduledTimeout) - { + if (scheduledTimeout) { scheduler.clearTimeout(scheduledTimeout); scheduledTimeout = null; } @@ -965,7 +863,7 @@ function Ace2Inner(){ function reschedule(time) { unschedule(); scheduledTime = time; - var delay = time - now(); + let delay = time - now(); if (delay < 0) delay = 0; scheduledTimeout = scheduler.setTimeout(callback, delay); } @@ -976,26 +874,24 @@ function Ace2Inner(){ func(); } return { - atMost: function(ms) { - var latestTime = now() + ms; - if ((!scheduledTimeout) || scheduledTime > latestTime) - { + atMost(ms) { + const latestTime = now() + ms; + if ((!scheduledTimeout) || scheduledTime > latestTime) { reschedule(latestTime); } }, // atLeast(ms) will schedule the action if not scheduled yet. // In other words, "infinity" is replaced by ms, even though // it is technically larger. - atLeast: function(ms) { - var earliestTime = now() + ms; - if ((!scheduledTimeout) || scheduledTime < earliestTime) - { + atLeast(ms) { + const earliestTime = now() + ms; + if ((!scheduledTimeout) || scheduledTime < earliestTime) { reschedule(earliestTime); } }, - never: function() { + never() { unschedule(); - } + }, }; } @@ -1005,24 +901,20 @@ function Ace2Inner(){ } editorInfo.ace_fastIncorp = fastIncorp; - var idleWorkTimer = makeIdleAction(function() { - if (inInternationalComposition) - { + var idleWorkTimer = makeIdleAction(() => { + if (inInternationalComposition) { // don't do idle input incorporation during international input composition idleWorkTimer.atLeast(500); return; } - inCallStackIfNecessary("idleWorkTimer", function() { + inCallStackIfNecessary('idleWorkTimer', () => { + const isTimeUp = newTimeLimit(250); - var isTimeUp = newTimeLimit(250); - - var finishedImportantWork = false; - var finishedWork = false; - - try - { + let finishedImportantWork = false; + let finishedWork = false; + try { // isTimeUp() is a soft constraint for incorporateUserChanges, // which always renormalizes the DOM, no matter how long it takes, // but doesn't necessarily lex and highlight it @@ -1033,27 +925,20 @@ function Ace2Inner(){ updateLineNumbers(); // update line numbers if any time left if (isTimeUp()) return; - var visibleRange = scroll.getVisibleCharRange(rep); - var docRange = [0, rep.lines.totalWidth()]; + const visibleRange = scroll.getVisibleCharRange(rep); + const docRange = [0, rep.lines.totalWidth()]; finishedImportantWork = true; finishedWork = true; - } - finally - { - if (finishedWork) - { + } finally { + if (finishedWork) { idleWorkTimer.atMost(1000); - } - else if (finishedImportantWork) - { + } else if (finishedImportantWork) { // if we've finished highlighting the view area, // more highlighting could be counter-productive, // e.g. if the user just opened a triple-quote and will soon close it. idleWorkTimer.atMost(500); - } - else - { - var timeToWait = Math.round(isTimeUp.elapsed() / 2); + } else { + let timeToWait = Math.round(isTimeUp.elapsed() / 2); if (timeToWait < 100) timeToWait = 100; idleWorkTimer.atMost(timeToWait); } @@ -1061,47 +946,45 @@ function Ace2Inner(){ }); }); - var _nextId = 1; + let _nextId = 1; function uniqueId(n) { // not actually guaranteed to be unique, e.g. if user copy-pastes // nodes with ids - var nid = n.id; + const nid = n.id; if (nid) return nid; - return (n.id = "magicdomid" + (_nextId++)); + return (n.id = `magicdomid${_nextId++}`); } function recolorLinesInRange(startChar, endChar, isTimeUp, optModFunc) { if (endChar <= startChar) return; if (startChar < 0 || startChar >= rep.lines.totalWidth()) return; - var lineEntry = rep.lines.atOffset(startChar); // rounds down to line boundary - var lineStart = rep.lines.offsetOfEntry(lineEntry); - var lineIndex = rep.lines.indexOfEntry(lineEntry); - var selectionNeedsResetting = false; - var firstLine = null; - var lastLine = null; + let lineEntry = rep.lines.atOffset(startChar); // rounds down to line boundary + let lineStart = rep.lines.offsetOfEntry(lineEntry); + let lineIndex = rep.lines.indexOfEntry(lineEntry); + let selectionNeedsResetting = false; + let firstLine = null; + let lastLine = null; isTimeUp = (isTimeUp || noop); // tokenFunc function; accesses current value of lineEntry and curDocChar, // also mutates curDocChar - var curDocChar; - var tokenFunc = function(tokenText, tokenClass) { - lineEntry.domInfo.appendSpan(tokenText, tokenClass); - }; - if (optModFunc) - { - var f = tokenFunc; - tokenFunc = function(tokenText, tokenClass) { + let curDocChar; + let tokenFunc = function (tokenText, tokenClass) { + lineEntry.domInfo.appendSpan(tokenText, tokenClass); + }; + if (optModFunc) { + const f = tokenFunc; + tokenFunc = function (tokenText, tokenClass) { optModFunc(tokenText, tokenClass, f, curDocChar); curDocChar += tokenText.length; }; } - while (lineEntry && lineStart < endChar && !isTimeUp()) - { - //var timer = newTimeLimit(200); - var lineEnd = lineStart + lineEntry.width; + while (lineEntry && lineStart < endChar && !isTimeUp()) { + // var timer = newTimeLimit(200); + const lineEnd = lineStart + lineEntry.width; curDocChar = lineStart; lineEntry.domInfo.clearSpans(); @@ -1110,8 +993,7 @@ function Ace2Inner(){ markNodeClean(lineEntry.lineNode); - if (rep.selStart && rep.selStart[0] == lineIndex || rep.selEnd && rep.selEnd[0] == lineIndex) - { + if (rep.selStart && rep.selStart[0] == lineIndex || rep.selEnd && rep.selEnd[0] == lineIndex) { selectionNeedsResetting = true; } @@ -1121,8 +1003,7 @@ function Ace2Inner(){ lineEntry = rep.lines.next(lineEntry); lineIndex++; } - if (selectionNeedsResetting) - { + if (selectionNeedsResetting) { currentCallStack.selectionAffected = true; } } @@ -1133,53 +1014,48 @@ function Ace2Inner(){ function getSpansForLine(lineEntry, textAndClassFunc, lineEntryOffsetHint) { - var lineEntryOffset = lineEntryOffsetHint; - if ((typeof lineEntryOffset) != "number") - { + let lineEntryOffset = lineEntryOffsetHint; + if ((typeof lineEntryOffset) !== 'number') { lineEntryOffset = rep.lines.offsetOfEntry(lineEntry); } - var text = lineEntry.text; - var width = lineEntry.width; // text.length+1 - if (text.length === 0) - { + const text = lineEntry.text; + const width = lineEntry.width; // text.length+1 + if (text.length === 0) { // allow getLineStyleFilter to set line-div styles - var func = linestylefilter.getLineStyleFilter( - 0, '', textAndClassFunc, rep.apool); + const func = linestylefilter.getLineStyleFilter( + 0, '', textAndClassFunc, rep.apool); func('', ''); - } - else - { - var offsetIntoLine = 0; - var filteredFunc = linestylefilter.getFilterStack(text, textAndClassFunc, browser); - var lineNum = rep.lines.indexOfEntry(lineEntry); - var aline = rep.alines[lineNum]; + } else { + const offsetIntoLine = 0; + let filteredFunc = linestylefilter.getFilterStack(text, textAndClassFunc, browser); + const lineNum = rep.lines.indexOfEntry(lineEntry); + const aline = rep.alines[lineNum]; filteredFunc = linestylefilter.getLineStyleFilter( - text.length, aline, filteredFunc, rep.apool); + text.length, aline, filteredFunc, rep.apool); filteredFunc(text, ''); } } - var observedChanges; + let observedChanges; function clearObservedChanges() { observedChanges = { - cleanNodesNearChanges: {} + cleanNodesNearChanges: {}, }; } clearObservedChanges(); function getCleanNodeByKey(key) { - var p = PROFILER("getCleanNodeByKey", false); + const p = PROFILER('getCleanNodeByKey', false); p.extra = 0; - var n = doc.getElementById(key); + let n = doc.getElementById(key); // copying and pasting can lead to duplicate ids - while (n && isNodeDirty(n)) - { + while (n && isNodeDirty(n)) { p.extra++; - n.id = ""; + n.id = ''; n = doc.getElementById(key); } - p.literal(p.extra, "extra"); + p.literal(p.extra, 'extra'); p.end(); return n; } @@ -1189,41 +1065,31 @@ function Ace2Inner(){ // (from how it looks in our representation) and record them in a way // that can be used to "normalize" the document (apply the changes to our // representation, and put the DOM in a canonical form). - var cleanNode; - var hasAdjacentDirtyness; - if (!isNodeDirty(node)) - { + let cleanNode; + let hasAdjacentDirtyness; + if (!isNodeDirty(node)) { cleanNode = node; var prevSib = cleanNode.previousSibling; var nextSib = cleanNode.nextSibling; hasAdjacentDirtyness = ((prevSib && isNodeDirty(prevSib)) || (nextSib && isNodeDirty(nextSib))); - } - else - { + } else { // node is dirty, look for clean node above - var upNode = node.previousSibling; - while (upNode && isNodeDirty(upNode)) - { + let upNode = node.previousSibling; + while (upNode && isNodeDirty(upNode)) { upNode = upNode.previousSibling; } - if (upNode) - { + if (upNode) { cleanNode = upNode; - } - else - { - var downNode = node.nextSibling; - while (downNode && isNodeDirty(downNode)) - { + } else { + let downNode = node.nextSibling; + while (downNode && isNodeDirty(downNode)) { downNode = downNode.nextSibling; } - if (downNode) - { + if (downNode) { cleanNode = downNode; } } - if (!cleanNode) - { + if (!cleanNode) { // Couldn't find any adjacent clean nodes! // Since top and bottom of doc is dirty, the dirty area will be detected. return; @@ -1231,26 +1097,22 @@ function Ace2Inner(){ hasAdjacentDirtyness = true; } - if (hasAdjacentDirtyness) - { + if (hasAdjacentDirtyness) { // previous or next line is dirty - observedChanges.cleanNodesNearChanges['$' + uniqueId(cleanNode)] = true; - } - else - { + observedChanges.cleanNodesNearChanges[`$${uniqueId(cleanNode)}`] = true; + } else { // next and prev lines are clean (if they exist) - var lineKey = uniqueId(cleanNode); + const lineKey = uniqueId(cleanNode); var prevSib = cleanNode.previousSibling; var nextSib = cleanNode.nextSibling; - var actualPrevKey = ((prevSib && uniqueId(prevSib)) || null); - var actualNextKey = ((nextSib && uniqueId(nextSib)) || null); - var repPrevEntry = rep.lines.prev(rep.lines.atKey(lineKey)); - var repNextEntry = rep.lines.next(rep.lines.atKey(lineKey)); - var repPrevKey = ((repPrevEntry && repPrevEntry.key) || null); - var repNextKey = ((repNextEntry && repNextEntry.key) || null); - if (actualPrevKey != repPrevKey || actualNextKey != repNextKey) - { - observedChanges.cleanNodesNearChanges['$' + uniqueId(cleanNode)] = true; + const actualPrevKey = ((prevSib && uniqueId(prevSib)) || null); + const actualNextKey = ((nextSib && uniqueId(nextSib)) || null); + const repPrevEntry = rep.lines.prev(rep.lines.atKey(lineKey)); + const repNextEntry = rep.lines.next(rep.lines.atKey(lineKey)); + const repPrevKey = ((repPrevEntry && repPrevEntry.key) || null); + const repNextKey = ((repNextEntry && repNextEntry.key) || null); + if (actualPrevKey != repPrevKey || actualNextKey != repNextKey) { + observedChanges.cleanNodesNearChanges[`$${uniqueId(cleanNode)}`] = true; } } } @@ -1259,17 +1121,15 @@ function Ace2Inner(){ if (currentCallStack.observedSelection) return; currentCallStack.observedSelection = true; - var p = PROFILER("getSelection", false); - var selection = getSelection(); + const p = PROFILER('getSelection', false); + const selection = getSelection(); p.end(); - if (selection) - { - var node1 = topLevel(selection.startPoint.node); - var node2 = topLevel(selection.endPoint.node); + if (selection) { + const node1 = topLevel(selection.startPoint.node); + const node2 = topLevel(selection.endPoint.node); if (node1) observeChangesAroundNode(node1); - if (node2 && node1 != node2) - { + if (node2 && node1 != node2) { observeChangesAroundNode(node2); } } @@ -1278,14 +1138,11 @@ function Ace2Inner(){ function observeSuspiciousNodes() { // inspired by Firefox bug #473255, where pasting formatted text // causes the cursor to jump away, making the new HTML never found. - if (root.getElementsByTagName) - { - var nds = root.getElementsByTagName("style"); - for (var i = 0; i < nds.length; i++) - { - var n = topLevel(nds[i]); - if (n && n.parentNode == root) - { + if (root.getElementsByTagName) { + const nds = root.getElementsByTagName('style'); + for (let i = 0; i < nds.length; i++) { + const n = topLevel(nds[i]); + if (n && n.parentNode == root) { observeChangesAroundNode(n); } } @@ -1293,56 +1150,49 @@ function Ace2Inner(){ } function incorporateUserChanges(isTimeUp) { - if (currentCallStack.domClean) return false; currentCallStack.isUserChange = true; isTimeUp = (isTimeUp || - function() { + function () { return false; }); if (DEBUG && window.DONT_INCORP || window.DEBUG_DONT_INCORP) return false; - var p = PROFILER("incorp", false); + const p = PROFILER('incorp', false); - //if (doc.body.innerHTML.indexOf("AppJet") >= 0) - //dmesg(htmlPrettyEscape(doc.body.innerHTML)); - //if (top.RECORD) top.RECORD.push(doc.body.innerHTML); + // if (doc.body.innerHTML.indexOf("AppJet") >= 0) + // dmesg(htmlPrettyEscape(doc.body.innerHTML)); + // if (top.RECORD) top.RECORD.push(doc.body.innerHTML); // returns true if dom changes were made - if (!root.firstChild) - { - root.innerHTML = "
        "; + if (!root.firstChild) { + root.innerHTML = '
        '; } - p.mark("obs"); + p.mark('obs'); observeChangesAroundSelection(); observeSuspiciousNodes(); - p.mark("dirty"); - var dirtyRanges = getDirtyRanges(); - var dirtyRangesCheckOut = true; - var j = 0; - var a, b; - while (j < dirtyRanges.length) - { + p.mark('dirty'); + let dirtyRanges = getDirtyRanges(); + let dirtyRangesCheckOut = true; + let j = 0; + let a, b; + while (j < dirtyRanges.length) { a = dirtyRanges[j][0]; b = dirtyRanges[j][1]; - if (!((a === 0 || getCleanNodeByKey(rep.lines.atIndex(a - 1).key)) && (b == rep.lines.length() || getCleanNodeByKey(rep.lines.atIndex(b).key)))) - { + if (!((a === 0 || getCleanNodeByKey(rep.lines.atIndex(a - 1).key)) && (b == rep.lines.length() || getCleanNodeByKey(rep.lines.atIndex(b).key)))) { dirtyRangesCheckOut = false; break; } j++; } - if (!dirtyRangesCheckOut) - { - var numBodyNodes = root.childNodes.length; - for (var k = 0; k < numBodyNodes; k++) - { - var bodyNode = root.childNodes.item(k); - if ((bodyNode.tagName) && ((!bodyNode.id) || (!rep.lines.containsKey(bodyNode.id)))) - { + if (!dirtyRangesCheckOut) { + const numBodyNodes = root.childNodes.length; + for (var k = 0; k < numBodyNodes; k++) { + const bodyNode = root.childNodes.item(k); + if ((bodyNode.tagName) && ((!bodyNode.id) || (!rep.lines.containsKey(bodyNode.id)))) { observeChangesAroundNode(bodyNode); } } @@ -1351,74 +1201,65 @@ function Ace2Inner(){ clearObservedChanges(); - p.mark("getsel"); - var selection = getSelection(); + p.mark('getsel'); + const selection = getSelection(); - var selStart, selEnd; // each one, if truthy, has [line,char] needed to set selection - var i = 0; - var splicesToDo = []; - var netNumLinesChangeSoFar = 0; - var toDeleteAtEnd = []; - p.mark("ranges"); - p.literal(dirtyRanges.length, "numdirt"); - var domInsertsNeeded = []; // each entry is [nodeToInsertAfter, [info1, info2, ...]] - while (i < dirtyRanges.length) - { - var range = dirtyRanges[i]; + let selStart, selEnd; // each one, if truthy, has [line,char] needed to set selection + let i = 0; + const splicesToDo = []; + let netNumLinesChangeSoFar = 0; + const toDeleteAtEnd = []; + p.mark('ranges'); + p.literal(dirtyRanges.length, 'numdirt'); + const domInsertsNeeded = []; // each entry is [nodeToInsertAfter, [info1, info2, ...]] + while (i < dirtyRanges.length) { + const range = dirtyRanges[i]; a = range[0]; b = range[1]; - var firstDirtyNode = (((a === 0) && root.firstChild) || getCleanNodeByKey(rep.lines.atIndex(a - 1).key).nextSibling); + let firstDirtyNode = (((a === 0) && root.firstChild) || getCleanNodeByKey(rep.lines.atIndex(a - 1).key).nextSibling); firstDirtyNode = (firstDirtyNode && isNodeDirty(firstDirtyNode) && firstDirtyNode); - var lastDirtyNode = (((b == rep.lines.length()) && root.lastChild) || getCleanNodeByKey(rep.lines.atIndex(b).key).previousSibling); + let lastDirtyNode = (((b == rep.lines.length()) && root.lastChild) || getCleanNodeByKey(rep.lines.atIndex(b).key).previousSibling); lastDirtyNode = (lastDirtyNode && isNodeDirty(lastDirtyNode) && lastDirtyNode); - if (firstDirtyNode && lastDirtyNode) - { - var cc = makeContentCollector(isStyled, browser, rep.apool, null, className2Author); + if (firstDirtyNode && lastDirtyNode) { + const cc = makeContentCollector(isStyled, browser, rep.apool, null, className2Author); cc.notifySelection(selection); - var dirtyNodes = []; - for (var n = firstDirtyNode; n && !(n.previousSibling && n.previousSibling == lastDirtyNode); - n = n.nextSibling) - { - if (browser.msie) - { + const dirtyNodes = []; + for (let n = firstDirtyNode; n && !(n.previousSibling && n.previousSibling == lastDirtyNode); + n = n.nextSibling) { + if (browser.msie) { // try to undo IE's pesky and overzealous linkification - try - { - n.createTextRange().execCommand("unlink", false, null); - } - catch (e) - {} + try { + n.createTextRange().execCommand('unlink', false, null); + } catch (e) {} } cc.collectContent(n); dirtyNodes.push(n); } cc.notifyNextNode(lastDirtyNode.nextSibling); - var lines = cc.getLines(); - if ((lines.length <= 1 || lines[lines.length - 1] !== "") && lastDirtyNode.nextSibling) - { + let lines = cc.getLines(); + if ((lines.length <= 1 || lines[lines.length - 1] !== '') && lastDirtyNode.nextSibling) { // dirty region doesn't currently end a line, even taking the following node // (or lack of node) into account, so include the following clean node. // It could be SPAN or a DIV; basically this is any case where the contentCollector // decides it isn't done. // Note that this clean node might need to be there for the next dirty range. b++; - var cleanLine = lastDirtyNode.nextSibling; + const cleanLine = lastDirtyNode.nextSibling; cc.collectContent(cleanLine); toDeleteAtEnd.push(cleanLine); cc.notifyNextNode(cleanLine.nextSibling); } - var ccData = cc.finish(); - var ss = ccData.selStart; - var se = ccData.selEnd; + const ccData = cc.finish(); + const ss = ccData.selStart; + const se = ccData.selEnd; lines = ccData.lines; - var lineAttribs = ccData.lineAttribs; - var linesWrapped = ccData.linesWrapped; + const lineAttribs = ccData.lineAttribs; + const linesWrapped = ccData.linesWrapped; var scrollToTheLeftNeeded = false; - if (linesWrapped > 0) - { - if(!browser.msie){ + if (linesWrapped > 0) { + if (!browser.msie) { // chrome decides in it's infinite wisdom that its okay to put the browsers visisble window in the middle of the span // an outcome of this is that the first chars of the string are no longer visible to the user.. Yay chrome.. // Move the browsers visible area to the left hand side of the span @@ -1430,120 +1271,113 @@ function Ace2Inner(){ if (ss[0] >= 0) selStart = [ss[0] + a + netNumLinesChangeSoFar, ss[1]]; if (se[0] >= 0) selEnd = [se[0] + a + netNumLinesChangeSoFar, se[1]]; - var entries = []; - var nodeToAddAfter = lastDirtyNode; - var lineNodeInfos = new Array(lines.length); - for (var k = 0; k < lines.length; k++) - { - var lineString = lines[k]; - var newEntry = createDomLineEntry(lineString); + const entries = []; + const nodeToAddAfter = lastDirtyNode; + const lineNodeInfos = new Array(lines.length); + for (var k = 0; k < lines.length; k++) { + const lineString = lines[k]; + const newEntry = createDomLineEntry(lineString); entries.push(newEntry); lineNodeInfos[k] = newEntry.domInfo; } - //var fragment = magicdom.wrapDom(document.createDocumentFragment()); + // var fragment = magicdom.wrapDom(document.createDocumentFragment()); domInsertsNeeded.push([nodeToAddAfter, lineNodeInfos]); - _.each(dirtyNodes,function(n){ + _.each(dirtyNodes, (n) => { toDeleteAtEnd.push(n); }); - var spliceHints = {}; + const spliceHints = {}; if (selStart) spliceHints.selStart = selStart; if (selEnd) spliceHints.selEnd = selEnd; splicesToDo.push([a + netNumLinesChangeSoFar, b - a, entries, lineAttribs, spliceHints]); netNumLinesChangeSoFar += (lines.length - (b - a)); - } - else if (b > a) - { - splicesToDo.push([a + netNumLinesChangeSoFar, b - a, [], - [] - ]); + } else if (b > a) { + splicesToDo.push([a + netNumLinesChangeSoFar, + b - a, + [], + []]); } i++; } - var domChanges = (splicesToDo.length > 0); + const domChanges = (splicesToDo.length > 0); // update the representation - p.mark("splice"); - _.each(splicesToDo, function(splice) { + p.mark('splice'); + _.each(splicesToDo, (splice) => { doIncorpLineSplice(splice[0], splice[1], splice[2], splice[3], splice[4]); }); - //p.mark("relex"); - //rep.lexer.lexCharRange(scroll.getVisibleCharRange(rep), function() { return false; }); - //var isTimeUp = newTimeLimit(100); + // p.mark("relex"); + // rep.lexer.lexCharRange(scroll.getVisibleCharRange(rep), function() { return false; }); + // var isTimeUp = newTimeLimit(100); // do DOM inserts - p.mark("insert"); - _.each(domInsertsNeeded,function(ins) { + p.mark('insert'); + _.each(domInsertsNeeded, (ins) => { insertDomLines(ins[0], ins[1], isTimeUp); }); - p.mark("del"); + p.mark('del'); // delete old dom nodes - _.each(toDeleteAtEnd,function(n) { - //var id = n.uniqueId(); + _.each(toDeleteAtEnd, (n) => { + // var id = n.uniqueId(); // parent of n may not be "root" in IE due to non-tree-shaped DOM (wtf) - if(n.parentNode) n.parentNode.removeChild(n); + if (n.parentNode) n.parentNode.removeChild(n); - //dmesg(htmlPrettyEscape(htmlForRemovedChild(n))); + // dmesg(htmlPrettyEscape(htmlForRemovedChild(n))); }); - if(scrollToTheLeftNeeded){ // needed to stop chrome from breaking the ui when long strings without spaces are pasted - $("#innerdocbody").scrollLeft(0); + if (scrollToTheLeftNeeded) { // needed to stop chrome from breaking the ui when long strings without spaces are pasted + $('#innerdocbody').scrollLeft(0); } - p.mark("findsel"); + p.mark('findsel'); // if the nodes that define the selection weren't encountered during // content collection, figure out where those nodes are now. - if (selection && !selStart) - { - //if (domChanges) dmesg("selection not collected"); - var selStartFromHook = hooks.callAll('aceStartLineAndCharForPoint', { + if (selection && !selStart) { + // if (domChanges) dmesg("selection not collected"); + const selStartFromHook = hooks.callAll('aceStartLineAndCharForPoint', { callstack: currentCallStack, - editorInfo: editorInfo, - rep: rep, - root:root, - point:selection.startPoint, - documentAttributeManager: documentAttributeManager + editorInfo, + rep, + root, + point: selection.startPoint, + documentAttributeManager, }); - selStart = (selStartFromHook==null||selStartFromHook.length==0)?getLineAndCharForPoint(selection.startPoint):selStartFromHook; + selStart = (selStartFromHook == null || selStartFromHook.length == 0) ? getLineAndCharForPoint(selection.startPoint) : selStartFromHook; } - if (selection && !selEnd) - { - var selEndFromHook = hooks.callAll('aceEndLineAndCharForPoint', { + if (selection && !selEnd) { + const selEndFromHook = hooks.callAll('aceEndLineAndCharForPoint', { callstack: currentCallStack, - editorInfo: editorInfo, - rep: rep, - root:root, - point:selection.endPoint, - documentAttributeManager: documentAttributeManager + editorInfo, + rep, + root, + point: selection.endPoint, + documentAttributeManager, }); - selEnd = (selEndFromHook==null||selEndFromHook.length==0)?getLineAndCharForPoint(selection.endPoint):selEndFromHook; + selEnd = (selEndFromHook == null || selEndFromHook.length == 0) ? getLineAndCharForPoint(selection.endPoint) : selEndFromHook; } // selection from content collection can, in various ways, extend past final // BR in firefox DOM, so cap the line - var numLines = rep.lines.length(); - if (selStart && selStart[0] >= numLines) - { + const numLines = rep.lines.length(); + if (selStart && selStart[0] >= numLines) { selStart[0] = numLines - 1; selStart[1] = rep.lines.atIndex(selStart[0]).text.length; } - if (selEnd && selEnd[0] >= numLines) - { + if (selEnd && selEnd[0] >= numLines) { selEnd[0] = numLines - 1; selEnd[1] = rep.lines.atIndex(selEnd[0]).text.length; } - p.mark("repsel"); + p.mark('repsel'); // update rep if we have a new selection // NOTE: IE loses the selection when you click stuff in e.g. the // editbar, so removing the selection when it's lost is not a good // idea. if (selection) repSelectionChange(selStart, selEnd, selection && selection.focusAtStart); // update browser selection - p.mark("browsel"); - if (selection && (domChanges || isCaret())) - { + p.mark('browsel'); + if (selection && (domChanges || isCaret())) { // if no DOM changes (not this case), want to treat range selection delicately, // e.g. in IE not lose which end of the selection is the focus/anchor; // on the other hand, we may have just noticed a press of PageUp/PageDown @@ -1552,11 +1386,11 @@ function Ace2Inner(){ currentCallStack.domClean = true; - p.mark("fixview"); + p.mark('fixview'); fixView(); - p.end("END"); + p.end('END'); return domChanges; } @@ -1566,7 +1400,7 @@ function Ace2Inner(){ italic: true, underline: true, strikethrough: true, - list: true + list: true, }; function isStyleAttribute(aname) { @@ -1579,64 +1413,57 @@ function Ace2Inner(){ function insertDomLines(nodeToAddAfter, infoStructs, isTimeUp) { isTimeUp = (isTimeUp || - function() { + function () { return false; }); - var lastEntry; - var lineStartOffset; + let lastEntry; + let lineStartOffset; if (infoStructs.length < 1) return; - var startEntry = rep.lines.atKey(uniqueId(infoStructs[0].node)); - var endEntry = rep.lines.atKey(uniqueId(infoStructs[infoStructs.length - 1].node)); - var charStart = rep.lines.offsetOfEntry(startEntry); - var charEnd = rep.lines.offsetOfEntry(endEntry) + endEntry.width; + const startEntry = rep.lines.atKey(uniqueId(infoStructs[0].node)); + const endEntry = rep.lines.atKey(uniqueId(infoStructs[infoStructs.length - 1].node)); + const charStart = rep.lines.offsetOfEntry(startEntry); + const charEnd = rep.lines.offsetOfEntry(endEntry) + endEntry.width; - //rep.lexer.lexCharRange([charStart, charEnd], isTimeUp); - _.each(infoStructs, function(info) { - var p2 = PROFILER("insertLine", false); - var node = info.node; - var key = uniqueId(node); - var entry; - p2.mark("findEntry"); - if (lastEntry) - { + // rep.lexer.lexCharRange([charStart, charEnd], isTimeUp); + _.each(infoStructs, (info) => { + const p2 = PROFILER('insertLine', false); + const node = info.node; + const key = uniqueId(node); + let entry; + p2.mark('findEntry'); + if (lastEntry) { // optimization to avoid recalculation - var next = rep.lines.next(lastEntry); - if (next && next.key == key) - { + const next = rep.lines.next(lastEntry); + if (next && next.key == key) { entry = next; lineStartOffset += lastEntry.width; } } - if (!entry) - { - p2.literal(1, "nonopt"); + if (!entry) { + p2.literal(1, 'nonopt'); entry = rep.lines.atKey(key); lineStartOffset = rep.lines.offsetOfKey(key); - } - else p2.literal(0, "nonopt"); + } else { p2.literal(0, 'nonopt'); } lastEntry = entry; - p2.mark("spans"); - getSpansForLine(entry, function(tokenText, tokenClass) { + p2.mark('spans'); + getSpansForLine(entry, (tokenText, tokenClass) => { info.appendSpan(tokenText, tokenClass); }, lineStartOffset, isTimeUp()); - //else if (entry.text.length > 0) { - //info.appendSpan(entry.text, 'dirty'); - //} - p2.mark("addLine"); + // else if (entry.text.length > 0) { + // info.appendSpan(entry.text, 'dirty'); + // } + p2.mark('addLine'); info.prepareForAdd(); entry.lineMarker = info.lineMarker; - if (!nodeToAddAfter) - { + if (!nodeToAddAfter) { root.insertBefore(node, root.firstChild); - } - else - { + } else { root.insertBefore(node, nodeToAddAfter.nextSibling); } nodeToAddAfter = node; info.notifyAdded(); - p2.mark("markClean"); + p2.mark('markClean'); markNodeClean(node); p2.end(); }); @@ -1667,175 +1494,141 @@ function Ace2Inner(){ function handleReturnIndentation() { // on return, indent to level of previous line - if (isCaret() && caretColumn() === 0 && caretLine() > 0) - { - var lineNum = caretLine(); - var thisLine = rep.lines.atIndex(lineNum); - var prevLine = rep.lines.prev(thisLine); - var prevLineText = prevLine.text; - var theIndent = /^ *(?:)/.exec(prevLineText)[0]; - var shouldIndent = parent.parent.clientVars.indentationOnNewLine; - if (shouldIndent && /[\[\(\:\{]\s*$/.exec(prevLineText)) - { + if (isCaret() && caretColumn() === 0 && caretLine() > 0) { + const lineNum = caretLine(); + const thisLine = rep.lines.atIndex(lineNum); + const prevLine = rep.lines.prev(thisLine); + const prevLineText = prevLine.text; + let theIndent = /^ *(?:)/.exec(prevLineText)[0]; + const shouldIndent = parent.parent.clientVars.indentationOnNewLine; + if (shouldIndent && /[\[\(\:\{]\s*$/.exec(prevLineText)) { theIndent += THE_TAB; } - var cs = Changeset.builder(rep.lines.totalWidth()).keep( - rep.lines.offsetOfIndex(lineNum), lineNum).insert( - theIndent, [ - ['author', thisAuthor] - ], rep.apool).toString(); + const cs = Changeset.builder(rep.lines.totalWidth()).keep( + rep.lines.offsetOfIndex(lineNum), lineNum).insert( + theIndent, [ + ['author', thisAuthor], + ], rep.apool).toString(); performDocumentApplyChangeset(cs); performSelectionChange([lineNum, theIndent.length], [lineNum, theIndent.length]); } } function getPointForLineAndChar(lineAndChar) { - var line = lineAndChar[0]; - var charsLeft = lineAndChar[1]; + const line = lineAndChar[0]; + let charsLeft = lineAndChar[1]; // Do not uncomment this in production it will break iFrames. - //top.console.log("line: %d, key: %s, node: %o", line, rep.lines.atIndex(line).key, - //getCleanNodeByKey(rep.lines.atIndex(line).key)); - var lineEntry = rep.lines.atIndex(line); + // top.console.log("line: %d, key: %s, node: %o", line, rep.lines.atIndex(line).key, + // getCleanNodeByKey(rep.lines.atIndex(line).key)); + const lineEntry = rep.lines.atIndex(line); charsLeft -= lineEntry.lineMarker; - if (charsLeft < 0) - { + if (charsLeft < 0) { charsLeft = 0; } - var lineNode = lineEntry.lineNode; - var n = lineNode; - var after = false; - if (charsLeft === 0) - { - var index = 0; + const lineNode = lineEntry.lineNode; + let n = lineNode; + let after = false; + if (charsLeft === 0) { + let index = 0; if (browser.msie && parseInt(browser.version) >= 11) { browser.msie = false; // Temp fix to resolve enter and backspace issues.. // Note that this makes MSIE behave like modern browsers.. } - if (browser.msie && line == (rep.lines.length() - 1) && lineNode.childNodes.length === 0) - { + if (browser.msie && line == (rep.lines.length() - 1) && lineNode.childNodes.length === 0) { // best to stay at end of last empty div in IE index = 1; } return { node: lineNode, - index: index, - maxIndex: 1 + index, + maxIndex: 1, }; } - while (!(n == lineNode && after)) - { - if (after) - { - if (n.nextSibling) - { + while (!(n == lineNode && after)) { + if (after) { + if (n.nextSibling) { n = n.nextSibling; after = false; + } else { n = n.parentNode; } + } else if (isNodeText(n)) { + const len = n.nodeValue.length; + if (charsLeft <= len) { + return { + node: n, + index: charsLeft, + maxIndex: len, + }; } - else n = n.parentNode; - } - else - { - if (isNodeText(n)) - { - var len = n.nodeValue.length; - if (charsLeft <= len) - { - return { - node: n, - index: charsLeft, - maxIndex: len - }; - } - charsLeft -= len; - after = true; - } - else - { - if (n.firstChild) n = n.firstChild; - else after = true; - } - } + charsLeft -= len; + after = true; + } else if (n.firstChild) { n = n.firstChild; } else { after = true; } } return { node: lineNode, index: 1, - maxIndex: 1 + maxIndex: 1, }; } function nodeText(n) { - if (browser.msie) { + if (browser.msie) { return n.innerText; - } else { + } else { return n.textContent || n.nodeValue || ''; - } + } } function getLineAndCharForPoint(point) { // Turn DOM node selection into [line,char] selection. // This method has to work when the DOM is not pristine, // assuming the point is not in a dirty node. - if (point.node == root) - { - if (point.index === 0) - { + if (point.node == root) { + if (point.index === 0) { return [0, 0]; - } - else - { - var N = rep.lines.length(); - var ln = rep.lines.atIndex(N - 1); + } else { + const N = rep.lines.length(); + const ln = rep.lines.atIndex(N - 1); return [N - 1, ln.text.length]; } - } - else - { - var n = point.node; - var col = 0; + } else { + let n = point.node; + let col = 0; // if this part fails, it probably means the selection node // was dirty, and we didn't see it when collecting dirty nodes. - if (isNodeText(n)) - { + if (isNodeText(n)) { col = point.index; - } - else if (point.index > 0) - { + } else if (point.index > 0) { col = nodeText(n).length; } - var parNode, prevSib; - while ((parNode = n.parentNode) != root) - { - if ((prevSib = n.previousSibling)) - { + let parNode, prevSib; + while ((parNode = n.parentNode) != root) { + if ((prevSib = n.previousSibling)) { n = prevSib; col += nodeText(n).length; - } - else - { + } else { n = parNode; } } - if (n.firstChild && isBlockElement(n.firstChild)) - { + if (n.firstChild && isBlockElement(n.firstChild)) { col += 1; // lineMarker } - var lineEntry = rep.lines.atKey(n.id); - var lineNum = rep.lines.indexOfEntry(lineEntry); + const lineEntry = rep.lines.atKey(n.id); + const lineNum = rep.lines.indexOfEntry(lineEntry); return [lineNum, col]; } } editorInfo.ace_getLineAndCharForPoint = getLineAndCharForPoint; function createDomLineEntry(lineString) { - var info = doCreateDomLine(lineString.length > 0); - var newNode = info.node; + const info = doCreateDomLine(lineString.length > 0); + const newNode = info.node; return { key: uniqueId(newNode), text: lineString, lineNode: newNode, domInfo: info, - lineMarker: 0 + lineMarker: 0, }; } @@ -1846,39 +1639,35 @@ function Ace2Inner(){ function performDocumentApplyChangeset(changes, insertsAfterSelection) { doRepApplyChangeset(changes, insertsAfterSelection); - var requiredSelectionSetting = null; - if (rep.selStart && rep.selEnd) - { - var selStartChar = rep.lines.offsetOfIndex(rep.selStart[0]) + rep.selStart[1]; - var selEndChar = rep.lines.offsetOfIndex(rep.selEnd[0]) + rep.selEnd[1]; - var result = Changeset.characterRangeFollow(changes, selStartChar, selEndChar, insertsAfterSelection); + let requiredSelectionSetting = null; + if (rep.selStart && rep.selEnd) { + const selStartChar = rep.lines.offsetOfIndex(rep.selStart[0]) + rep.selStart[1]; + const selEndChar = rep.lines.offsetOfIndex(rep.selEnd[0]) + rep.selEnd[1]; + const result = Changeset.characterRangeFollow(changes, selStartChar, selEndChar, insertsAfterSelection); requiredSelectionSetting = [result[0], result[1], rep.selFocusAtStart]; } - var linesMutatee = { - splice: function(start, numRemoved, newLinesVA) { - var args = Array.prototype.slice.call(arguments, 2); - domAndRepSplice(start, numRemoved, _.map(args, function(s){ return s.slice(0, -1); }), null); + const linesMutatee = { + splice(start, numRemoved, newLinesVA) { + const args = Array.prototype.slice.call(arguments, 2); + domAndRepSplice(start, numRemoved, _.map(args, (s) => s.slice(0, -1)), null); }, - get: function(i) { - return rep.lines.atIndex(i).text + '\n'; + get(i) { + return `${rep.lines.atIndex(i).text}\n`; }, - length: function() { + length() { return rep.lines.length(); }, - slice_notused: function(start, end) { - return _.map(rep.lines.slice(start, end), function(e) { - return e.text + '\n'; - }); - } + slice_notused(start, end) { + return _.map(rep.lines.slice(start, end), (e) => `${e.text}\n`); + }, }; Changeset.mutateTextLines(changes, linesMutatee); checkALines(); - if (requiredSelectionSetting) - { + if (requiredSelectionSetting) { performSelectionChange(lineAndColumnFromChar(requiredSelectionSetting[0]), lineAndColumnFromChar(requiredSelectionSetting[1]), requiredSelectionSetting[2]); } @@ -1887,39 +1676,32 @@ function Ace2Inner(){ // so if no explicit time limit, don't spend a lot of time highlighting isTimeUp = (isTimeUp || newTimeLimit(50)); - var keysToDelete = []; - if (deleteCount > 0) - { - var entryToDelete = rep.lines.atIndex(startLine); - for (var i = 0; i < deleteCount; i++) - { + const keysToDelete = []; + if (deleteCount > 0) { + let entryToDelete = rep.lines.atIndex(startLine); + for (let i = 0; i < deleteCount; i++) { keysToDelete.push(entryToDelete.key); entryToDelete = rep.lines.next(entryToDelete); } } - var lineEntries = _.map(newLineStrings, createDomLineEntry); + const lineEntries = _.map(newLineStrings, createDomLineEntry); doRepLineSplice(startLine, deleteCount, lineEntries); - var nodeToAddAfter; - if (startLine > 0) - { + let nodeToAddAfter; + if (startLine > 0) { nodeToAddAfter = getCleanNodeByKey(rep.lines.atIndex(startLine - 1).key); - } - else nodeToAddAfter = null; + } else { nodeToAddAfter = null; } - insertDomLines(nodeToAddAfter, _.map(lineEntries, function(entry) { - return entry.domInfo; - }), isTimeUp); + insertDomLines(nodeToAddAfter, _.map(lineEntries, (entry) => entry.domInfo), isTimeUp); - _.each(keysToDelete, function(k) { - var n = doc.getElementById(k); + _.each(keysToDelete, (k) => { + const n = doc.getElementById(k); n.parentNode.removeChild(n); }); - if ((rep.selStart && rep.selStart[0] >= startLine && rep.selStart[0] <= startLine + deleteCount) || (rep.selEnd && rep.selEnd[0] >= startLine && rep.selEnd[0] <= startLine + deleteCount)) - { + if ((rep.selStart && rep.selStart[0] >= startLine && rep.selStart[0] <= startLine + deleteCount) || (rep.selEnd && rep.selEnd[0] >= startLine && rep.selEnd[0] <= startLine + deleteCount)) { currentCallStack.selectionAffected = true; } } @@ -1927,31 +1709,25 @@ function Ace2Inner(){ function checkChangesetLineInformationAgainstRep(changes) { return true; // disable for speed - var opIter = Changeset.opIterator(Changeset.unpack(changes).ops); - var curOffset = 0; - var curLine = 0; - var curCol = 0; - while (opIter.hasNext()) - { - var o = opIter.next(); - if (o.opcode == '-' || o.opcode == '=') - { + const opIter = Changeset.opIterator(Changeset.unpack(changes).ops); + let curOffset = 0; + let curLine = 0; + let curCol = 0; + while (opIter.hasNext()) { + const o = opIter.next(); + if (o.opcode == '-' || o.opcode == '=') { curOffset += o.chars; - if (o.lines) - { + if (o.lines) { curLine += o.lines; curCol = 0; - } - else - { + } else { curCol += o.chars; } } - var calcLine = rep.lines.indexOfOffset(curOffset); - var calcLineStart = rep.lines.offsetOfIndex(calcLine); - var calcCol = curOffset - calcLineStart; - if (calcCol != curCol || calcLine != curLine) - { + const calcLine = rep.lines.indexOfOffset(curOffset); + const calcLineStart = rep.lines.offsetOfIndex(calcLine); + const calcCol = curOffset - calcLineStart; + if (calcCol != curCol || calcLine != curLine) { return false; } } @@ -1961,94 +1737,75 @@ function Ace2Inner(){ function doRepApplyChangeset(changes, insertsAfterSelection) { Changeset.checkRep(changes); - if (Changeset.oldLen(changes) != rep.alltext.length) throw new Error("doRepApplyChangeset length mismatch: " + Changeset.oldLen(changes) + "/" + rep.alltext.length); + if (Changeset.oldLen(changes) != rep.alltext.length) throw new Error(`doRepApplyChangeset length mismatch: ${Changeset.oldLen(changes)}/${rep.alltext.length}`); - if (!checkChangesetLineInformationAgainstRep(changes)) - { - throw new Error("doRepApplyChangeset line break mismatch"); + if (!checkChangesetLineInformationAgainstRep(changes)) { + throw new Error('doRepApplyChangeset line break mismatch'); } (function doRecordUndoInformation(changes) { - var editEvent = currentCallStack.editEvent; - if (editEvent.eventType == "nonundoable") - { - if (!editEvent.changeset) - { + const editEvent = currentCallStack.editEvent; + if (editEvent.eventType == 'nonundoable') { + if (!editEvent.changeset) { editEvent.changeset = changes; - } - else - { + } else { editEvent.changeset = Changeset.compose(editEvent.changeset, changes, rep.apool); } - } - else - { - var inverseChangeset = Changeset.inverse(changes, { - get: function(i) { - return rep.lines.atIndex(i).text + '\n'; + } else { + const inverseChangeset = Changeset.inverse(changes, { + get(i) { + return `${rep.lines.atIndex(i).text}\n`; }, - length: function() { + length() { return rep.lines.length(); - } + }, }, rep.alines, rep.apool); - if (!editEvent.backset) - { + if (!editEvent.backset) { editEvent.backset = inverseChangeset; - } - else - { + } else { editEvent.backset = Changeset.compose(inverseChangeset, editEvent.backset, rep.apool); } } })(changes); - //rep.alltext = Changeset.applyToText(changes, rep.alltext); + // rep.alltext = Changeset.applyToText(changes, rep.alltext); Changeset.mutateAttributionLines(changes, rep.alines, rep.apool); - if (changesetTracker.isTracking()) - { + if (changesetTracker.isTracking()) { changesetTracker.composeUserChangeset(changes); } - } /* Converts the position of a char (index in String) into a [row, col] tuple */ function lineAndColumnFromChar(x) { - var lineEntry = rep.lines.atOffset(x); - var lineStart = rep.lines.offsetOfEntry(lineEntry); - var lineNum = rep.lines.indexOfEntry(lineEntry); + const lineEntry = rep.lines.atOffset(x); + const lineStart = rep.lines.offsetOfEntry(lineEntry); + const lineNum = rep.lines.indexOfEntry(lineEntry); return [lineNum, x - lineStart]; } function performDocumentReplaceCharRange(startChar, endChar, newText) { - if (startChar == endChar && newText.length === 0) - { + if (startChar == endChar && newText.length === 0) { return; } // Requires that the replacement preserve the property that the // internal document text ends in a newline. Given this, we // rewrite the splice so that it doesn't touch the very last // char of the document. - if (endChar == rep.alltext.length) - { - if (startChar == endChar) - { + if (endChar == rep.alltext.length) { + if (startChar == endChar) { // an insert at end startChar--; endChar--; - newText = '\n' + newText.substring(0, newText.length - 1); - } - else if (newText.length === 0) - { + newText = `\n${newText.substring(0, newText.length - 1)}`; + } else if (newText.length === 0) { // a delete at end startChar--; endChar--; - } - else - { + } else { // a replace at end endChar--; newText = newText.substring(0, newText.length - 1); @@ -2061,18 +1818,18 @@ function Ace2Inner(){ if (start === undefined) start = rep.selStart; if (end === undefined) end = rep.selEnd; - //dmesg(String([start.toSource(),end.toSource(),newText.toSource()])); + // dmesg(String([start.toSource(),end.toSource(),newText.toSource()])); // start[0]: <--- start[1] --->CCCCCCCCCCC\n // CCCCCCCCCCCCCCCCCCCC\n // CCCC\n // end[0]: -------\n - var builder = Changeset.builder(rep.lines.totalWidth()); + const builder = Changeset.builder(rep.lines.totalWidth()); ChangesetUtils.buildKeepToStartOfRange(rep, builder, start); ChangesetUtils.buildRemoveRange(rep, builder, start, end); builder.insert(newText, [ - ['author', thisAuthor] + ['author', thisAuthor], ], rep.apool); - var cs = builder.toString(); + const cs = builder.toString(); performDocumentApplyChangeset(cs); } @@ -2088,71 +1845,71 @@ function Ace2Inner(){ if (!(rep.selStart && rep.selEnd)) return; documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [ - [attributeName, attributeValue] + [attributeName, attributeValue], ]); } editorInfo.ace_setAttributeOnSelection = setAttributeOnSelection; - function getAttributeOnSelection(attributeName, prevChar){ - if (!(rep.selStart && rep.selEnd)) return - var isNotSelection = (rep.selStart[0] == rep.selEnd[0] && rep.selEnd[1] === rep.selStart[1]); - if(isNotSelection){ - if(prevChar){ + function getAttributeOnSelection(attributeName, prevChar) { + if (!(rep.selStart && rep.selEnd)) return; + const isNotSelection = (rep.selStart[0] == rep.selEnd[0] && rep.selEnd[1] === rep.selStart[1]); + if (isNotSelection) { + if (prevChar) { // If it's not the start of the line - if(rep.selStart[1] !== 0){ + if (rep.selStart[1] !== 0) { rep.selStart[1]--; } } } - var withIt = Changeset.makeAttribsString('+', [ - [attributeName, 'true'] + const withIt = Changeset.makeAttribsString('+', [ + [attributeName, 'true'], ], rep.apool); - var withItRegex = new RegExp(withIt.replace(/\*/g, '\\*') + "(\\*|$)"); + const withItRegex = new RegExp(`${withIt.replace(/\*/g, '\\*')}(\\*|$)`); function hasIt(attribs) { return withItRegex.test(attribs); } - return rangeHasAttrib(rep.selStart, rep.selEnd) + return rangeHasAttrib(rep.selStart, rep.selEnd); function rangeHasAttrib(selStart, selEnd) { // 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 - var hasAttrib = true + if (selStart[0] != selEnd[0]) { // -> More than one line selected + var hasAttrib = true; // 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(var n=selStart[0]+1; n < selEnd[0]; n++) { - hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length]) + for (let n = selStart[0] + 1; n < selEnd[0]; n++) { + hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length]); } // 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 - var lineNum = selStart[0] - , start = selStart[1] - , end = selEnd[1] - , hasAttrib = true + const lineNum = selStart[0]; + const start = selStart[1]; + const end = selEnd[1]; + var hasAttrib = true; // Iterate over attribs on this line - var opIter = Changeset.opIterator(rep.alines[lineNum]) - , indexIntoLine = 0 + const opIter = Changeset.opIterator(rep.alines[lineNum]); + let indexIntoLine = 0; while (opIter.hasNext()) { - var op = opIter.next(); - var opStartInLine = indexIntoLine; - var opEndInLine = opStartInLine + op.chars; + const op = opIter.next(); + const opStartInLine = indexIntoLine; + const opEndInLine = opStartInLine + op.chars; if (!hasIt(op.attribs)) { // does op overlap selection? if (!(opEndInLine <= start || opStartInLine >= end)) { @@ -2163,7 +1920,7 @@ function Ace2Inner(){ indexIntoLine = opEndInLine; } - return hasAttrib + return hasAttrib; } } @@ -2172,59 +1929,52 @@ function Ace2Inner(){ function toggleAttributeOnSelection(attributeName) { if (!(rep.selStart && rep.selEnd)) return; - var selectionAllHasIt = true; - var withIt = Changeset.makeAttribsString('+', [ - [attributeName, 'true'] + let selectionAllHasIt = true; + const withIt = Changeset.makeAttribsString('+', [ + [attributeName, 'true'], ], rep.apool); - var withItRegex = new RegExp(withIt.replace(/\*/g, '\\*') + "(\\*|$)"); + const withItRegex = new RegExp(`${withIt.replace(/\*/g, '\\*')}(\\*|$)`); function hasIt(attribs) { return withItRegex.test(attribs); } - var selStartLine = rep.selStart[0]; - var selEndLine = rep.selEnd[0]; - for (var n = selStartLine; n <= selEndLine; n++) - { - var opIter = Changeset.opIterator(rep.alines[n]); - var indexIntoLine = 0; - var selectionStartInLine = 0; + const selStartLine = rep.selStart[0]; + const selEndLine = rep.selEnd[0]; + for (let n = selStartLine; n <= selEndLine; n++) { + const opIter = Changeset.opIterator(rep.alines[n]); + let indexIntoLine = 0; + let selectionStartInLine = 0; if (documentAttributeManager.lineHasMarker(n)) { selectionStartInLine = 1; // ignore "*" used as line marker } - var selectionEndInLine = rep.lines.atIndex(n).text.length; // exclude newline - if (n == selStartLine) - { + let selectionEndInLine = rep.lines.atIndex(n).text.length; // exclude newline + if (n == selStartLine) { selectionStartInLine = rep.selStart[1]; } - if (n == selEndLine) - { + if (n == selEndLine) { selectionEndInLine = rep.selEnd[1]; } - while (opIter.hasNext()) - { - var op = opIter.next(); - var opStartInLine = indexIntoLine; - var opEndInLine = opStartInLine + op.chars; - if (!hasIt(op.attribs)) - { + while (opIter.hasNext()) { + const op = opIter.next(); + const opStartInLine = indexIntoLine; + const opEndInLine = opStartInLine + op.chars; + if (!hasIt(op.attribs)) { // does op overlap selection? - if (!(opEndInLine <= selectionStartInLine || opStartInLine >= selectionEndInLine)) - { + if (!(opEndInLine <= selectionStartInLine || opStartInLine >= selectionEndInLine)) { selectionAllHasIt = false; break; } } indexIntoLine = opEndInLine; } - if (!selectionAllHasIt) - { + if (!selectionAllHasIt) { break; } } - var attributeValue = selectionAllHasIt ? '' : 'true'; + const attributeValue = selectionAllHasIt ? '' : 'true'; documentAttributeManager.setAttributesOnRange(rep.selStart, rep.selEnd, [[attributeName, attributeValue]]); if (attribIsFormattingStyle(attributeName)) { updateStyleButtonState(attributeName, !selectionAllHasIt); // italic, bold, ... @@ -2242,67 +1992,59 @@ function Ace2Inner(){ function doRepLineSplice(startLine, deleteCount, newLineEntries) { - - _.each(newLineEntries, function(entry) { + _.each(newLineEntries, (entry) => { entry.width = entry.text.length + 1; }); - var startOldChar = rep.lines.offsetOfIndex(startLine); - var endOldChar = rep.lines.offsetOfIndex(startLine + deleteCount); + const startOldChar = rep.lines.offsetOfIndex(startLine); + const endOldChar = rep.lines.offsetOfIndex(startLine + deleteCount); - var oldRegionStart = rep.lines.offsetOfIndex(startLine); - var oldRegionEnd = rep.lines.offsetOfIndex(startLine + deleteCount); + const oldRegionStart = rep.lines.offsetOfIndex(startLine); + const oldRegionEnd = rep.lines.offsetOfIndex(startLine + deleteCount); rep.lines.splice(startLine, deleteCount, newLineEntries); currentCallStack.docTextChanged = true; currentCallStack.repChanged = true; - var newRegionEnd = rep.lines.offsetOfIndex(startLine + newLineEntries.length); + const newRegionEnd = rep.lines.offsetOfIndex(startLine + newLineEntries.length); - var newText = _.map(newLineEntries, function(e) { - return e.text + '\n'; - }).join(''); + const newText = _.map(newLineEntries, (e) => `${e.text}\n`).join(''); rep.alltext = rep.alltext.substring(0, startOldChar) + newText + rep.alltext.substring(endOldChar, rep.alltext.length); - //var newTotalLength = rep.alltext.length; - //rep.lexer.updateBuffer(rep.alltext, oldRegionStart, oldRegionEnd - oldRegionStart, - //newRegionEnd - oldRegionStart); + // var newTotalLength = rep.alltext.length; + // rep.lexer.updateBuffer(rep.alltext, oldRegionStart, oldRegionEnd - oldRegionStart, + // newRegionEnd - oldRegionStart); } function doIncorpLineSplice(startLine, deleteCount, newLineEntries, lineAttribs, hints) { - var startOldChar = rep.lines.offsetOfIndex(startLine); - var endOldChar = rep.lines.offsetOfIndex(startLine + deleteCount); + const startOldChar = rep.lines.offsetOfIndex(startLine); + const endOldChar = rep.lines.offsetOfIndex(startLine + deleteCount); - var oldRegionStart = rep.lines.offsetOfIndex(startLine); + const oldRegionStart = rep.lines.offsetOfIndex(startLine); - var selStartHintChar, selEndHintChar; - if (hints && hints.selStart) - { + let selStartHintChar, selEndHintChar; + if (hints && hints.selStart) { selStartHintChar = rep.lines.offsetOfIndex(hints.selStart[0]) + hints.selStart[1] - oldRegionStart; } - if (hints && hints.selEnd) - { + if (hints && hints.selEnd) { selEndHintChar = rep.lines.offsetOfIndex(hints.selEnd[0]) + hints.selEnd[1] - oldRegionStart; } - var newText = _.map(newLineEntries, function(e) { - return e.text + '\n'; - }).join(''); - var oldText = rep.alltext.substring(startOldChar, endOldChar); - var oldAttribs = rep.alines.slice(startLine, startLine + deleteCount).join(''); - var newAttribs = lineAttribs.join('|1+1') + '|1+1'; // not valid in a changeset - var analysis = analyzeChange(oldText, newText, oldAttribs, newAttribs, selStartHintChar, selEndHintChar); - var commonStart = analysis[0]; - var commonEnd = analysis[1]; - var shortOldText = oldText.substring(commonStart, oldText.length - commonEnd); - var shortNewText = newText.substring(commonStart, newText.length - commonEnd); - var spliceStart = startOldChar + commonStart; - var spliceEnd = endOldChar - commonEnd; - var shiftFinalNewlineToBeforeNewText = false; + const newText = _.map(newLineEntries, (e) => `${e.text}\n`).join(''); + const oldText = rep.alltext.substring(startOldChar, endOldChar); + const oldAttribs = rep.alines.slice(startLine, startLine + deleteCount).join(''); + const newAttribs = `${lineAttribs.join('|1+1')}|1+1`; // not valid in a changeset + const analysis = analyzeChange(oldText, newText, oldAttribs, newAttribs, selStartHintChar, selEndHintChar); + const commonStart = analysis[0]; + let commonEnd = analysis[1]; + let shortOldText = oldText.substring(commonStart, oldText.length - commonEnd); + let shortNewText = newText.substring(commonStart, newText.length - commonEnd); + let spliceStart = startOldChar + commonStart; + let spliceEnd = endOldChar - commonEnd; + let shiftFinalNewlineToBeforeNewText = false; // adjust the splice to not involve the final newline of the document; // be very defensive - if (shortOldText.charAt(shortOldText.length - 1) == '\n' && shortNewText.charAt(shortNewText.length - 1) == '\n') - { + if (shortOldText.charAt(shortOldText.length - 1) == '\n' && shortNewText.charAt(shortNewText.length - 1) == '\n') { // replacing text that ends in newline with text that also ends in newline // (still, after analysis, somehow) shortOldText = shortOldText.slice(0, -1); @@ -2310,19 +2052,16 @@ function Ace2Inner(){ spliceEnd--; commonEnd++; } - if (shortOldText.length === 0 && spliceStart == rep.alltext.length && shortNewText.length > 0) - { + if (shortOldText.length === 0 && spliceStart == rep.alltext.length && shortNewText.length > 0) { // inserting after final newline, bad spliceStart--; spliceEnd--; - shortNewText = '\n' + shortNewText.slice(0, -1); + shortNewText = `\n${shortNewText.slice(0, -1)}`; shiftFinalNewlineToBeforeNewText = true; } - if (spliceEnd == rep.alltext.length && shortOldText.length > 0 && shortNewText.length === 0) - { + if (spliceEnd == rep.alltext.length && shortOldText.length > 0 && shortNewText.length === 0) { // deletion at end of rep.alltext - if (rep.alltext.charAt(spliceStart - 1) == '\n') - { + if (rep.alltext.charAt(spliceStart - 1) == '\n') { // (if not then what the heck? it will definitely lead // to a rep.alltext without a final newline) spliceStart--; @@ -2330,141 +2069,119 @@ function Ace2Inner(){ } } - if (!(shortOldText.length === 0 && shortNewText.length === 0)) - { - var oldDocText = rep.alltext; - var oldLen = oldDocText.length; + if (!(shortOldText.length === 0 && shortNewText.length === 0)) { + const oldDocText = rep.alltext; + const oldLen = oldDocText.length; - var spliceStartLine = rep.lines.indexOfOffset(spliceStart); - var spliceStartLineStart = rep.lines.offsetOfIndex(spliceStartLine); + const spliceStartLine = rep.lines.indexOfOffset(spliceStart); + const spliceStartLineStart = rep.lines.offsetOfIndex(spliceStartLine); - var startBuilder = function() { - var builder = Changeset.builder(oldLen); + const startBuilder = function () { + const builder = Changeset.builder(oldLen); builder.keep(spliceStartLineStart, spliceStartLine); builder.keep(spliceStart - spliceStartLineStart); return builder; }; - var eachAttribRun = function(attribs, func /*(startInNewText, endInNewText, attribs)*/ ) { - var attribsIter = Changeset.opIterator(attribs); - var textIndex = 0; - var newTextStart = commonStart; - var newTextEnd = newText.length - commonEnd - (shiftFinalNewlineToBeforeNewText ? 1 : 0); - while (attribsIter.hasNext()) - { - var op = attribsIter.next(); - var nextIndex = textIndex + op.chars; - if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) - { + const eachAttribRun = function (attribs, func /* (startInNewText, endInNewText, attribs)*/) { + const attribsIter = Changeset.opIterator(attribs); + let textIndex = 0; + const newTextStart = commonStart; + const newTextEnd = newText.length - commonEnd - (shiftFinalNewlineToBeforeNewText ? 1 : 0); + while (attribsIter.hasNext()) { + const op = attribsIter.next(); + const nextIndex = textIndex + op.chars; + if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) { func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs); } textIndex = nextIndex; } }; - var justApplyStyles = (shortNewText == shortOldText); - var theChangeset; + const justApplyStyles = (shortNewText == shortOldText); + let theChangeset; - if (justApplyStyles) - { + if (justApplyStyles) { // create changeset that clears the incorporated styles on // the existing text. we compose this with the // changeset the applies the styles found in the DOM. // This allows us to incorporate, e.g., Safari's native "unbold". - var incorpedAttribClearer = cachedStrFunc(function(oldAtts) { - return Changeset.mapAttribNumbers(oldAtts, function(n) { - var k = rep.apool.getAttribKey(n); - if (isStyleAttribute(k)) - { - return rep.apool.putAttrib([k, '']); - } - return false; - }); - }); + const incorpedAttribClearer = cachedStrFunc((oldAtts) => Changeset.mapAttribNumbers(oldAtts, (n) => { + const k = rep.apool.getAttribKey(n); + if (isStyleAttribute(k)) { + return rep.apool.putAttrib([k, '']); + } + return false; + })); - var builder1 = startBuilder(); - if (shiftFinalNewlineToBeforeNewText) - { + const builder1 = startBuilder(); + if (shiftFinalNewlineToBeforeNewText) { builder1.keep(1, 1); } - eachAttribRun(oldAttribs, function(start, end, attribs) { + eachAttribRun(oldAttribs, (start, end, attribs) => { builder1.keepText(newText.substring(start, end), incorpedAttribClearer(attribs)); }); - var clearer = builder1.toString(); + const clearer = builder1.toString(); - var builder2 = startBuilder(); - if (shiftFinalNewlineToBeforeNewText) - { + const builder2 = startBuilder(); + if (shiftFinalNewlineToBeforeNewText) { builder2.keep(1, 1); } - eachAttribRun(newAttribs, function(start, end, attribs) { + eachAttribRun(newAttribs, (start, end, attribs) => { builder2.keepText(newText.substring(start, end), attribs); }); - var styler = builder2.toString(); + const styler = builder2.toString(); theChangeset = Changeset.compose(clearer, styler, rep.apool); - } - else - { - var builder = startBuilder(); + } else { + const builder = startBuilder(); - var spliceEndLine = rep.lines.indexOfOffset(spliceEnd); - var spliceEndLineStart = rep.lines.offsetOfIndex(spliceEndLine); - if (spliceEndLineStart > spliceStart) - { + const spliceEndLine = rep.lines.indexOfOffset(spliceEnd); + const spliceEndLineStart = rep.lines.offsetOfIndex(spliceEndLine); + if (spliceEndLineStart > spliceStart) { builder.remove(spliceEndLineStart - spliceStart, spliceEndLine - spliceStartLine); builder.remove(spliceEnd - spliceEndLineStart); - } - else - { + } else { builder.remove(spliceEnd - spliceStart); } - var isNewTextMultiauthor = false; - var authorAtt = Changeset.makeAttribsString('+', (thisAuthor ? [ - ['author', thisAuthor] + let isNewTextMultiauthor = false; + const authorAtt = Changeset.makeAttribsString('+', (thisAuthor ? [ + ['author', thisAuthor], ] : []), rep.apool); - var authorizer = cachedStrFunc(function(oldAtts) { - if (isNewTextMultiauthor) - { + const authorizer = cachedStrFunc((oldAtts) => { + if (isNewTextMultiauthor) { // prefer colors from DOM return Changeset.composeAttributes(authorAtt, oldAtts, true, rep.apool); - } - else - { + } else { // use this author's color return Changeset.composeAttributes(oldAtts, authorAtt, true, rep.apool); } }); - var foundDomAuthor = ''; - eachAttribRun(newAttribs, function(start, end, attribs) { - var a = Changeset.attribsAttributeValue(attribs, 'author', rep.apool); - if (a && a != foundDomAuthor) - { - if (!foundDomAuthor) - { + let foundDomAuthor = ''; + eachAttribRun(newAttribs, (start, end, attribs) => { + const a = Changeset.attribsAttributeValue(attribs, 'author', rep.apool); + if (a && a != foundDomAuthor) { + if (!foundDomAuthor) { foundDomAuthor = a; - } - else - { + } else { isNewTextMultiauthor = true; // multiple authors in DOM! } } }); - if (shiftFinalNewlineToBeforeNewText) - { + if (shiftFinalNewlineToBeforeNewText) { builder.insert('\n', authorizer('')); } - eachAttribRun(newAttribs, function(start, end, attribs) { + eachAttribRun(newAttribs, (start, end, attribs) => { builder.insert(newText.substring(start, end), authorizer(attribs)); }); theChangeset = builder.toString(); } - //dmesg(htmlPrettyEscape(theChangeset)); + // dmesg(htmlPrettyEscape(theChangeset)); doRepApplyChangeset(theChangeset); } @@ -2476,10 +2193,9 @@ function Ace2Inner(){ } function cachedStrFunc(func) { - var cache = {}; - return function(s) { - if (!cache[s]) - { + const cache = {}; + return function (s) { + if (!cache[s]) { cache[s] = func(s); } return cache[s]; @@ -2495,12 +2211,11 @@ function Ace2Inner(){ } function attribRuns(attribs) { - var lengs = []; - var atts = []; - var iter = Changeset.opIterator(attribs); - while (iter.hasNext()) - { - var op = iter.next(); + const lengs = []; + const atts = []; + const iter = Changeset.opIterator(attribs); + while (iter.hasNext()) { + const op = iter.next(); lengs.push(op.chars); atts.push(op.attribs); } @@ -2508,94 +2223,76 @@ function Ace2Inner(){ } function attribIterator(runs, backward) { - var lengs = runs[0]; - var atts = runs[1]; - var i = (backward ? lengs.length - 1 : 0); - var j = 0; + const lengs = runs[0]; + const atts = runs[1]; + let i = (backward ? lengs.length - 1 : 0); + let j = 0; return function next() { - while (j >= lengs[i]) - { + while (j >= lengs[i]) { if (backward) i--; else i++; j = 0; } - var a = atts[i]; + const a = atts[i]; j++; return a; }; } - var oldLen = oldText.length; - var newLen = newText.length; - var minLen = Math.min(oldLen, newLen); + const oldLen = oldText.length; + const newLen = newText.length; + const minLen = Math.min(oldLen, newLen); - var oldARuns = attribRuns(Changeset.filterAttribNumbers(oldAttribs, incorpedAttribFilter)); - var newARuns = attribRuns(Changeset.filterAttribNumbers(newAttribs, incorpedAttribFilter)); + const oldARuns = attribRuns(Changeset.filterAttribNumbers(oldAttribs, incorpedAttribFilter)); + const newARuns = attribRuns(Changeset.filterAttribNumbers(newAttribs, incorpedAttribFilter)); - var commonStart = 0; - var oldStartIter = attribIterator(oldARuns, false); - var newStartIter = attribIterator(newARuns, false); - while (commonStart < minLen) - { - if (oldText.charAt(commonStart) == newText.charAt(commonStart) && oldStartIter() == newStartIter()) - { + let commonStart = 0; + const oldStartIter = attribIterator(oldARuns, false); + const newStartIter = attribIterator(newARuns, false); + while (commonStart < minLen) { + if (oldText.charAt(commonStart) == newText.charAt(commonStart) && oldStartIter() == newStartIter()) { commonStart++; - } - else break; + } else { break; } } - var commonEnd = 0; - var oldEndIter = attribIterator(oldARuns, true); - var newEndIter = attribIterator(newARuns, true); - while (commonEnd < minLen) - { - if (commonEnd === 0) - { + let commonEnd = 0; + const oldEndIter = attribIterator(oldARuns, true); + const newEndIter = attribIterator(newARuns, true); + while (commonEnd < minLen) { + if (commonEnd === 0) { // assume newline in common oldEndIter(); newEndIter(); commonEnd++; - } - else if (oldText.charAt(oldLen - 1 - commonEnd) == newText.charAt(newLen - 1 - commonEnd) && oldEndIter() == newEndIter()) - { + } else if (oldText.charAt(oldLen - 1 - commonEnd) == newText.charAt(newLen - 1 - commonEnd) && oldEndIter() == newEndIter()) { commonEnd++; - } - else break; + } else { break; } } - var hintedCommonEnd = -1; - if ((typeof optSelEndHint) == "number") - { + let hintedCommonEnd = -1; + if ((typeof optSelEndHint) === 'number') { hintedCommonEnd = newLen - optSelEndHint; } - if (commonStart + commonEnd > oldLen) - { + if (commonStart + commonEnd > oldLen) { // ambiguous insertion var minCommonEnd = oldLen - commonStart; var maxCommonEnd = commonEnd; - if (hintedCommonEnd >= minCommonEnd && hintedCommonEnd <= maxCommonEnd) - { + if (hintedCommonEnd >= minCommonEnd && hintedCommonEnd <= maxCommonEnd) { commonEnd = hintedCommonEnd; - } - else - { + } else { commonEnd = minCommonEnd; } commonStart = oldLen - commonEnd; } - if (commonStart + commonEnd > newLen) - { + if (commonStart + commonEnd > newLen) { // ambiguous deletion var minCommonEnd = newLen - commonStart; var maxCommonEnd = commonEnd; - if (hintedCommonEnd >= minCommonEnd && hintedCommonEnd <= maxCommonEnd) - { + if (hintedCommonEnd >= minCommonEnd && hintedCommonEnd <= maxCommonEnd) { commonEnd = hintedCommonEnd; - } - else - { + } else { commonEnd = minCommonEnd; } commonStart = newLen - commonEnd; @@ -2611,8 +2308,7 @@ function Ace2Inner(){ } function performSelectionChange(selectStart, selectEnd, focusAtStart) { - if (repSelectionChange(selectStart, selectEnd, focusAtStart)) - { + if (repSelectionChange(selectStart, selectEnd, focusAtStart)) { currentCallStack.selectionAffected = true; } } @@ -2623,12 +2319,11 @@ function Ace2Inner(){ function repSelectionChange(selectStart, selectEnd, focusAtStart) { - focusAtStart = !! focusAtStart; + focusAtStart = !!focusAtStart; - var newSelFocusAtStart = (focusAtStart && ((!selectStart) || (!selectEnd) || (selectStart[0] != selectEnd[0]) || (selectStart[1] != selectEnd[1]))); + const newSelFocusAtStart = (focusAtStart && ((!selectStart) || (!selectEnd) || (selectStart[0] != selectEnd[0]) || (selectStart[1] != selectEnd[1]))); - if ((!equalLineAndChars(rep.selStart, selectStart)) || (!equalLineAndChars(rep.selEnd, selectEnd)) || (rep.selFocusAtStart != newSelFocusAtStart)) - { + if ((!equalLineAndChars(rep.selStart, selectStart)) || (!equalLineAndChars(rep.selEnd, selectEnd)) || (rep.selFocusAtStart != newSelFocusAtStart)) { rep.selStart = selectStart; rep.selEnd = selectEnd; rep.selFocusAtStart = newSelFocusAtStart; @@ -2638,28 +2333,28 @@ function Ace2Inner(){ selectFormattingButtonIfLineHasStyleApplied(rep); hooks.callAll('aceSelectionChanged', { - rep: rep, + rep, callstack: currentCallStack, - documentAttributeManager: documentAttributeManager, + documentAttributeManager, }); // we scroll when user places the caret at the last line of the pad // when this settings is enabled - var docTextChanged = currentCallStack.docTextChanged; - if(!docTextChanged){ - var isScrollableEvent = !isPadLoading(currentCallStack.type) && isScrollableEditEvent(currentCallStack.type); - var innerHeight = getInnerHeight(); - scroll.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary(rep, isScrollableEvent, innerHeight); + const docTextChanged = currentCallStack.docTextChanged; + if (!docTextChanged) { + const isScrollableEvent = !isPadLoading(currentCallStack.type) && isScrollableEditEvent(currentCallStack.type); + const innerHeight = getInnerHeight(); + scroll.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary(rep, isScrollableEvent, innerHeight); } return true; // Do not uncomment this in production it will break iFrames. - //top.console.log("selStart: %o, selEnd: %o, focusAtStart: %s", rep.selStart, rep.selEnd, - //String(!!rep.selFocusAtStart)); + // top.console.log("selStart: %o, selEnd: %o, focusAtStart: %s", rep.selStart, rep.selEnd, + // String(!!rep.selFocusAtStart)); } return false; // Do not uncomment this in production it will break iFrames. - //top.console.log("%o %o %s", rep.selStart, rep.selEnd, rep.selFocusAtStart); + // top.console.log("%o %o %s", rep.selStart, rep.selEnd, rep.selFocusAtStart); } function isPadLoading(eventType) { @@ -2667,7 +2362,7 @@ function Ace2Inner(){ } function updateStyleButtonState(attribName, hasStyleOnRepSelection) { - var $formattingButton = parent.parent.$('[data-key="' + attribName + '"]').find('a'); + const $formattingButton = parent.parent.$(`[data-key="${attribName}"]`).find('a'); $formattingButton.toggleClass(SELECT_BUTTON_CLASS, hasStyleOnRepSelection); } @@ -2675,69 +2370,65 @@ function Ace2Inner(){ return _.contains(FORMATTING_STYLES, attributeName); } - function selectFormattingButtonIfLineHasStyleApplied (rep) { - _.each(FORMATTING_STYLES, function (style) { - var hasStyleOnRepSelection = documentAttributeManager.hasAttributeOnSelectionOrCaretPosition(style); + function selectFormattingButtonIfLineHasStyleApplied(rep) { + _.each(FORMATTING_STYLES, (style) => { + const hasStyleOnRepSelection = documentAttributeManager.hasAttributeOnSelectionOrCaretPosition(style); updateStyleButtonState(style, hasStyleOnRepSelection); - }) + }); } function doCreateDomLine(nonEmpty) { - if (browser.msie && (!nonEmpty)) - { - var result = { + if (browser.msie && (!nonEmpty)) { + const result = { node: null, appendSpan: noop, prepareForAdd: noop, notifyAdded: noop, clearSpans: noop, finishUpdate: noop, - lineMarker: 0 + lineMarker: 0, }; - var lineElem = doc.createElement("div"); + const lineElem = doc.createElement('div'); result.node = lineElem; - result.notifyAdded = function() { + result.notifyAdded = function () { // magic -- settng an empty div's innerHTML to the empty string // keeps it from collapsing. Apparently innerHTML must be set *after* // adding the node to the DOM. // Such a div is what IE 6 creates naturally when you make a blank line // in a document of divs. However, when copy-and-pasted the div will // contain a space, so we note its emptiness with a property. - lineElem.innerHTML = " "; // Frist we set a value that isnt blank + lineElem.innerHTML = ' '; // Frist we set a value that isnt blank // a primitive-valued property survives copy-and-paste - setAssoc(lineElem, "shouldBeEmpty", true); + setAssoc(lineElem, 'shouldBeEmpty', true); // an object property doesn't - setAssoc(lineElem, "unpasted", {}); - lineElem.innerHTML = ""; // Then we make it blank.. New line and no space = Awesome :) + setAssoc(lineElem, 'unpasted', {}); + lineElem.innerHTML = ''; // Then we make it blank.. New line and no space = Awesome :) }; - var lineClass = 'ace-line'; - result.appendSpan = function(txt, cls) { - if ((!txt) && cls) - { + let lineClass = 'ace-line'; + result.appendSpan = function (txt, cls) { + if ((!txt) && cls) { // gain a whole-line style (currently to show insertion point in CSS) lineClass = domline.addToLineClass(lineClass, cls); } // otherwise, ignore appendSpan, this is an empty line }; - result.clearSpans = function() { + result.clearSpans = function () { lineClass = ''; // non-null to cause update }; - var writeClass = function() { + const writeClass = function () { if (lineClass !== null) lineElem.className = lineClass; }; result.prepareForAdd = writeClass; result.finishUpdate = writeClass; - result.getInnerHTML = function() { - return ""; + result.getInnerHTML = function () { + return ''; }; return result; - } - else - { + } else { return domline.createDomLine(nonEmpty, doesWrap, browser, doc); } } @@ -2746,21 +2437,21 @@ function Ace2Inner(){ return str.replace(/[\n\r ]/g, ' ').replace(/\xa0/g, ' ').replace(/\t/g, ' '); } - var _blockElems = { - "div": 1, - "p": 1, - "pre": 1, - "li": 1, - "ol": 1, - "ul": 1 + const _blockElems = { + div: 1, + p: 1, + pre: 1, + li: 1, + ol: 1, + ul: 1, }; - _.each(hooks.callAll('aceRegisterBlockElements'), function(element){ - _blockElems[element] = 1; + _.each(hooks.callAll('aceRegisterBlockElements'), (element) => { + _blockElems[element] = 1; }); function isBlockElement(n) { - return !!_blockElems[(n.tagName || "").toLowerCase()]; + return !!_blockElems[(n.tagName || '').toLowerCase()]; } function getDirtyRanges() { @@ -2770,47 +2461,42 @@ function Ace2Inner(){ // indicating inserted content. for example, [0,0] means content was inserted // at the top of the document, while [3,4] means line 3 was deleted, modified, // or replaced with one or more new lines of content. ranges do not touch. - var p = PROFILER("getDirtyRanges", false); + const p = PROFILER('getDirtyRanges', false); p.forIndices = 0; p.consecutives = 0; p.corrections = 0; - var cleanNodeForIndexCache = {}; - var N = rep.lines.length(); // old number of lines + const cleanNodeForIndexCache = {}; + const N = rep.lines.length(); // old number of lines function cleanNodeForIndex(i) { // if line (i) in the un-updated line representation maps to a clean node // in the document, return that node. // if (i) is out of bounds, return true. else return false. - if (cleanNodeForIndexCache[i] === undefined) - { + if (cleanNodeForIndexCache[i] === undefined) { p.forIndices++; - var result; - if (i < 0 || i >= N) - { + let result; + if (i < 0 || i >= N) { result = true; // truthy, but no actual node - } - else - { - var key = rep.lines.atIndex(i).key; + } else { + const key = rep.lines.atIndex(i).key; result = (getCleanNodeByKey(key) || false); } cleanNodeForIndexCache[i] = result; } return cleanNodeForIndexCache[i]; } - var isConsecutiveCache = {}; + const isConsecutiveCache = {}; function isConsecutive(i) { - if (isConsecutiveCache[i] === undefined) - { + if (isConsecutiveCache[i] === undefined) { p.consecutives++; - isConsecutiveCache[i] = (function() { + isConsecutiveCache[i] = (function () { // returns whether line (i) and line (i-1), assumed to be map to clean DOM nodes, // or document boundaries, are consecutive in the changed DOM - var a = cleanNodeForIndex(i - 1); - var b = cleanNodeForIndex(i); + const a = cleanNodeForIndex(i - 1); + const b = cleanNodeForIndex(i); if ((!a) || (!b)) return false; // violates precondition if ((a === true) && (b === true)) return !root.firstChild; if ((a === true) && b.previousSibling) return false; @@ -2830,14 +2516,14 @@ function Ace2Inner(){ // list of pairs, each representing a range of lines that is clean and consecutive // in the changed DOM. lines (-1) and (N) are always clean, but may or may not // be consecutive with lines in the document. pairs are in sorted order. - var cleanRanges = [ - [-1, N + 1] + const cleanRanges = [ + [-1, N + 1], ]; function rangeForLine(i) { // returns index of cleanRange containing i, or -1 if none - var answer = -1; - _.each(cleanRanges ,function(r, idx) { + let answer = -1; + _.each(cleanRanges, (r, idx) => { if (i >= r[1]) return false; // keep looking if (i < r[0]) return true; // not found, stop looking answer = idx; @@ -2849,8 +2535,8 @@ function Ace2Inner(){ function removeLineFromRange(rng, line) { // rng is index into cleanRanges, line is line number // precond: line is in rng - var a = cleanRanges[rng][0]; - var b = cleanRanges[rng][1]; + const a = cleanRanges[rng][0]; + const b = cleanRanges[rng][1]; if ((a + 1) == b) cleanRanges.splice(rng, 1); else if (line == a) cleanRanges[rng][0]++; else if (line == (b - 1)) cleanRanges[rng][1]--; @@ -2859,11 +2545,11 @@ function Ace2Inner(){ function splitRange(rng, pt) { // precond: pt splits cleanRanges[rng] into two non-empty ranges - var a = cleanRanges[rng][0]; - var b = cleanRanges[rng][1]; + const a = cleanRanges[rng][0]; + const b = cleanRanges[rng][1]; cleanRanges.splice(rng, 1, [a, pt], [pt, b]); } - var correctedLines = {}; + const correctedLines = {}; function correctlyAssignLine(line) { if (correctedLines[line]) return true; @@ -2873,39 +2559,32 @@ function Ace2Inner(){ // returns whether line was already correctly assigned (i.e. correctly // clean or dirty, according to cleanRanges, and if clean, correctly // attached or not attached (i.e. in the same range as) the prev and next lines). - var rng = rangeForLine(line); - var lineClean = isClean(line); - if (rng < 0) - { - if (lineClean) - { + const rng = rangeForLine(line); + const lineClean = isClean(line); + if (rng < 0) { + if (lineClean) { // somehow lost clean line } return true; } - if (!lineClean) - { + if (!lineClean) { // a clean-range includes this dirty line, fix it removeLineFromRange(rng, line); return false; - } - else - { + } else { // line is clean, but could be wrongly connected to a clean line // above or below - var a = cleanRanges[rng][0]; - var b = cleanRanges[rng][1]; - var didSomething = false; + const a = cleanRanges[rng][0]; + const b = cleanRanges[rng][1]; + let didSomething = false; // we'll leave non-clean adjacent nodes in the clean range for the caller to // detect and deal with. we deal with whether the range should be split // just above or just below this line. - if (a < line && isClean(line - 1) && !isConsecutive(line)) - { + if (a < line && isClean(line - 1) && !isConsecutive(line)) { splitRange(rng, line); didSomething = true; } - if (b > (line + 1) && isClean(line + 1) && !isConsecutive(line + 1)) - { + if (b > (line + 1) && isClean(line + 1) && !isConsecutive(line + 1)) { splitRange(rng, line + 1); didSomething = true; } @@ -2919,63 +2598,50 @@ function Ace2Inner(){ // making for several consecutive lines. note that iteration is over old lines, // so this operation takes time proportional to the number of old lines // that are changed or missing, not the number of new lines inserted. - var correctInARow = 0; - var currentIndex = line; - while (correctInARow < reqInARow && currentIndex >= 0) - { - if (correctlyAssignLine(currentIndex)) - { + let correctInARow = 0; + let currentIndex = line; + while (correctInARow < reqInARow && currentIndex >= 0) { + if (correctlyAssignLine(currentIndex)) { correctInARow++; - } - else correctInARow = 0; + } else { correctInARow = 0; } currentIndex--; } correctInARow = 0; currentIndex = line; - while (correctInARow < reqInARow && currentIndex < N) - { - if (correctlyAssignLine(currentIndex)) - { + while (correctInARow < reqInARow && currentIndex < N) { + if (correctlyAssignLine(currentIndex)) { correctInARow++; - } - else correctInARow = 0; + } else { correctInARow = 0; } currentIndex++; } } - if (N === 0) - { + if (N === 0) { p.cancel(); - if (!isConsecutive(0)) - { + if (!isConsecutive(0)) { splitRange(0, 0); } - } - else - { - p.mark("topbot"); + } else { + p.mark('topbot'); detectChangesAroundLine(0, 1); detectChangesAroundLine(N - 1, 1); - p.mark("obs"); - for (var k in observedChanges.cleanNodesNearChanges) - { - var key = k.substring(1); - if (rep.lines.containsKey(key)) - { - var line = rep.lines.indexOfKey(key); + p.mark('obs'); + for (const k in observedChanges.cleanNodesNearChanges) { + const key = k.substring(1); + if (rep.lines.containsKey(key)) { + const line = rep.lines.indexOfKey(key); detectChangesAroundLine(line, 2); } } - p.mark("stats&calc"); - p.literal(p.forIndices, "byidx"); - p.literal(p.consecutives, "cons"); - p.literal(p.corrections, "corr"); + p.mark('stats&calc'); + p.literal(p.forIndices, 'byidx'); + p.literal(p.consecutives, 'cons'); + p.literal(p.corrections, 'corr'); } - var dirtyRanges = []; - for (var r = 0; r < cleanRanges.length - 1; r++) - { + const dirtyRanges = []; + for (let r = 0; r < cleanRanges.length - 1; r++) { dirtyRanges.push([cleanRanges[r][1], cleanRanges[r + 1][0]]); } @@ -2986,27 +2652,25 @@ function Ace2Inner(){ function markNodeClean(n) { // clean nodes have knownHTML that matches their innerHTML - var dirtiness = {}; + const dirtiness = {}; dirtiness.nodeId = uniqueId(n); dirtiness.knownHTML = n.innerHTML; - if (browser.msie) - { + if (browser.msie) { // adding a space to an "empty" div in IE designMode doesn't // change the innerHTML of the div's parent; also, other // browsers don't support innerText dirtiness.knownText = n.innerText; } - setAssoc(n, "dirtiness", dirtiness); + setAssoc(n, 'dirtiness', dirtiness); } function isNodeDirty(n) { - var p = PROFILER("cleanCheck", false); + const p = PROFILER('cleanCheck', false); if (n.parentNode != root) return true; - var data = getAssoc(n, "dirtiness"); + const data = getAssoc(n, 'dirtiness'); if (!data) return true; if (n.id !== data.nodeId) return true; - if (browser.msie) - { + if (browser.msie) { if (n.innerText !== data.knownText) return true; } if (n.innerHTML !== data.knownHTML) return true; @@ -3015,67 +2679,61 @@ function Ace2Inner(){ } function getViewPortTopBottom() { - var theTop = scroll.getScrollY(); - var doc = outerWin.document; - var height = doc.documentElement.clientHeight; // includes padding + const theTop = scroll.getScrollY(); + const doc = outerWin.document; + 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 // the viewport height (E.g. padding, position top) - var viewportExtraSpacesAndPosition = getEditorPositionTop() + getPaddingTopAddedWhenPageViewIsEnable(); + const viewportExtraSpacesAndPosition = getEditorPositionTop() + getPaddingTopAddedWhenPageViewIsEnable(); return { top: theTop, - bottom: (theTop + height - viewportExtraSpacesAndPosition) + bottom: (theTop + height - viewportExtraSpacesAndPosition), }; } function getEditorPositionTop() { - var editor = parent.document.getElementsByTagName('iframe'); - var editorPositionTop = editor[0].offsetTop; + const editor = parent.document.getElementsByTagName('iframe'); + const editorPositionTop = editor[0].offsetTop; return editorPositionTop; } // ep_page_view adds padding-top, which makes the viewport smaller function getPaddingTopAddedWhenPageViewIsEnable() { - var rootDocument = parent.parent.document; - var aceOuter = rootDocument.getElementsByName("ace_outer"); - var aceOuterPaddingTop = parseInt($(aceOuter).css("padding-top")); + const rootDocument = parent.parent.document; + const aceOuter = rootDocument.getElementsByName('ace_outer'); + const aceOuterPaddingTop = parseInt($(aceOuter).css('padding-top')); return aceOuterPaddingTop; } function handleCut(evt) { - inCallStackIfNecessary("handleCut", function() { + inCallStackIfNecessary('handleCut', () => { doDeleteKey(evt); }); return true; } function handleClick(evt) { - inCallStackIfNecessary("handleClick", function() { + inCallStackIfNecessary('handleClick', () => { idleWorkTimer.atMost(200); }); function isLink(n) { - return (n.tagName || '').toLowerCase() == "a" && n.href; + return (n.tagName || '').toLowerCase() == 'a' && n.href; } // only want to catch left-click - if ((!evt.ctrlKey) && (evt.button != 2) && (evt.button != 3)) - { + if ((!evt.ctrlKey) && (evt.button != 2) && (evt.button != 3)) { // find A tag with HREF - var n = evt.target; - while (n && n.parentNode && !isLink(n)) - { + let n = evt.target; + while (n && n.parentNode && !isLink(n)) { n = n.parentNode; } - if (n && isLink(n)) - { - try - { + if (n && isLink(n)) { + try { window.open(n.href, '_blank', 'noopener,noreferrer'); - } - catch (e) - { + } catch (e) { // absorb "user canceled" error in IE for certain prompts } evt.preventDefault(); @@ -3086,49 +2744,39 @@ function Ace2Inner(){ } function hideEditBarDropdowns() { - if(window.parent.parent.padeditbar){ // required in case its in an iframe should probably use parent.. See Issue 327 https://github.com/ether/etherpad-lite/issues/327 - window.parent.parent.padeditbar.toggleDropDown("none"); + if (window.parent.parent.padeditbar) { // required in case its in an iframe should probably use parent.. See Issue 327 https://github.com/ether/etherpad-lite/issues/327 + window.parent.parent.padeditbar.toggleDropDown('none'); } } function doReturnKey() { - if (!(rep.selStart && rep.selEnd)) - { + if (!(rep.selStart && rep.selEnd)) { return; } - var lineNum = rep.selStart[0]; - var listType = getLineListType(lineNum); + const lineNum = rep.selStart[0]; + let listType = getLineListType(lineNum); - if (listType) - { - var text = rep.lines.atIndex(lineNum).text; + if (listType) { + const text = rep.lines.atIndex(lineNum).text; listType = /([a-z]+)([0-9]+)/.exec(listType); - var type = listType[1]; - var level = Number(listType[2]); + const type = listType[1]; + const level = Number(listType[2]); - //detect empty list item; exclude indentation - if(text === '*' && type !== "indent") - { - //if not already on the highest level - if(level > 1) - { - setLineListType(lineNum, type+(level-1));//automatically decrease the level + // detect empty list item; exclude indentation + if (text === '*' && type !== 'indent') { + // if not already on the highest level + if (level > 1) { + setLineListType(lineNum, type + (level - 1));// automatically decrease the level + } else { + setLineListType(lineNum, '');// remove the list + renumberList(lineNum + 1);// trigger renumbering of list that may be right after } - else - { - setLineListType(lineNum, '');//remove the list - renumberList(lineNum + 1);//trigger renumbering of list that may be right after - } - } - else if (lineNum + 1 <= rep.lines.length()) - { + } else if (lineNum + 1 <= rep.lines.length()) { performDocumentReplaceSelection('\n'); - setLineListType(lineNum + 1, type+level); + setLineListType(lineNum + 1, type + level); } - } - else - { + } else { performDocumentReplaceSelection('\n'); handleReturnIndentation(); } @@ -3136,39 +2784,34 @@ function Ace2Inner(){ function doIndentOutdent(isOut) { if (!((rep.selStart && rep.selEnd) || - ((rep.selStart[0] == rep.selEnd[0]) && (rep.selStart[1] == rep.selEnd[1]) && rep.selEnd[1] > 1)) && + ((rep.selStart[0] == rep.selEnd[0]) && (rep.selStart[1] == rep.selEnd[1]) && rep.selEnd[1] > 1)) && (isOut != true) - ) - { + ) { return false; } - var firstLine, lastLine; + let firstLine, lastLine; firstLine = rep.selStart[0]; lastLine = Math.max(firstLine, rep.selEnd[0] - ((rep.selEnd[1] === 0) ? 1 : 0)); - var mods = []; - for (var n = firstLine; n <= lastLine; n++) - { - var listType = getLineListType(n); - var t = 'indent'; - var level = 0; - if (listType) - { + const mods = []; + for (let n = firstLine; n <= lastLine; n++) { + let listType = getLineListType(n); + let t = 'indent'; + let level = 0; + if (listType) { listType = /([a-z]+)([0-9]+)/.exec(listType); - if (listType) - { + if (listType) { t = listType[1]; level = Number(listType[2]); } } - var newLevel = Math.max(0, Math.min(MAX_LIST_LEVEL, level + (isOut ? -1 : 1))); - if (level != newLevel) - { + const newLevel = Math.max(0, Math.min(MAX_LIST_LEVEL, level + (isOut ? -1 : 1))); + if (level != newLevel) { mods.push([n, (newLevel > 0) ? t + newLevel : '']); } } - _.each(mods, function(mod){ + _.each(mods, (mod) => { setLineListType(mod[0], mod[1]); }); return true; @@ -3176,119 +2819,96 @@ function Ace2Inner(){ editorInfo.ace_doIndentOutdent = doIndentOutdent; function doTabKey(shiftDown) { - if (!doIndentOutdent(shiftDown)) - { + if (!doIndentOutdent(shiftDown)) { performDocumentReplaceSelection(THE_TAB); } } function doDeleteKey(optEvt) { - var evt = optEvt || {}; - var handled = false; - if (rep.selStart) - { - if (isCaret()) - { - var lineNum = caretLine(); - var col = caretColumn(); + const evt = optEvt || {}; + let handled = false; + if (rep.selStart) { + if (isCaret()) { + const lineNum = caretLine(); + const col = caretColumn(); var lineEntry = rep.lines.atIndex(lineNum); - var lineText = lineEntry.text; - var lineMarker = lineEntry.lineMarker; - if (/^ +$/.exec(lineText.substring(lineMarker, col))) - { - var col2 = col - lineMarker; - var tabSize = THE_TAB.length; - var toDelete = ((col2 - 1) % tabSize) + 1; + const lineText = lineEntry.text; + const lineMarker = lineEntry.lineMarker; + if (/^ +$/.exec(lineText.substring(lineMarker, col))) { + const col2 = col - lineMarker; + const tabSize = THE_TAB.length; + const toDelete = ((col2 - 1) % tabSize) + 1; performDocumentReplaceRange([lineNum, col - toDelete], [lineNum, col], ''); - //scrollSelectionIntoView(); + // scrollSelectionIntoView(); handled = true; } } - if (!handled) - { - if (isCaret()) - { - var theLine = caretLine(); + if (!handled) { + if (isCaret()) { + const theLine = caretLine(); var lineEntry = rep.lines.atIndex(theLine); - if (caretColumn() <= lineEntry.lineMarker) - { + if (caretColumn() <= lineEntry.lineMarker) { // delete at beginning of line - var action = 'delete_newline'; - var prevLineListType = (theLine > 0 ? getLineListType(theLine - 1) : ''); - var thisLineListType = getLineListType(theLine); - var prevLineEntry = (theLine > 0 && rep.lines.atIndex(theLine - 1)); - var prevLineBlank = (prevLineEntry && prevLineEntry.text.length == prevLineEntry.lineMarker); + const action = 'delete_newline'; + const prevLineListType = (theLine > 0 ? getLineListType(theLine - 1) : ''); + const thisLineListType = getLineListType(theLine); + const prevLineEntry = (theLine > 0 && rep.lines.atIndex(theLine - 1)); + const prevLineBlank = (prevLineEntry && prevLineEntry.text.length == prevLineEntry.lineMarker); - var thisLineHasMarker = documentAttributeManager.lineHasMarker(theLine); + const thisLineHasMarker = documentAttributeManager.lineHasMarker(theLine); - if (thisLineListType) - { + if (thisLineListType) { // this line is a list - if (prevLineBlank && !prevLineListType) - { + if (prevLineBlank && !prevLineListType) { // previous line is blank, remove it performDocumentReplaceRange([theLine - 1, prevLineEntry.text.length], [theLine, 0], ''); - } - else - { + } else { // delistify performDocumentReplaceRange([theLine, 0], [theLine, lineEntry.lineMarker], ''); } - }else if (thisLineHasMarker && prevLineEntry){ + } else if (thisLineHasMarker && prevLineEntry) { // If the line has any attributes assigned, remove them by removing the marker '*' - performDocumentReplaceRange([theLine -1 , prevLineEntry.text.length], [theLine, lineEntry.lineMarker], ''); - } - else if (theLine > 0) - { + performDocumentReplaceRange([theLine - 1, prevLineEntry.text.length], [theLine, lineEntry.lineMarker], ''); + } else if (theLine > 0) { // remove newline performDocumentReplaceRange([theLine - 1, prevLineEntry.text.length], [theLine, 0], ''); } - } - else - { - var docChar = caretDocChar(); - if (docChar > 0) - { - if (evt.metaKey || evt.ctrlKey || evt.altKey) - { + } else { + const docChar = caretDocChar(); + if (docChar > 0) { + if (evt.metaKey || evt.ctrlKey || evt.altKey) { // delete as many unicode "letters or digits" in a row as possible; // always delete one char, delete further even if that first char // isn't actually a word char. - var deleteBackTo = docChar - 1; - while (deleteBackTo > lineEntry.lineMarker && isWordChar(rep.alltext.charAt(deleteBackTo - 1))) - { + let deleteBackTo = docChar - 1; + while (deleteBackTo > lineEntry.lineMarker && isWordChar(rep.alltext.charAt(deleteBackTo - 1))) { deleteBackTo--; } performDocumentReplaceCharRange(deleteBackTo, docChar, ''); - } - else - { + } else { // normal delete performDocumentReplaceCharRange(docChar - 1, docChar, ''); } } } - } - else - { + } else { performDocumentReplaceSelection(''); } } } - //if the list has been removed, it is necessary to renumber - //starting from the *next* line because the list may have been - //separated. If it returns null, it means that the list was not cut, try - //from the current one. - var line = caretLine(); - if(line != -1 && renumberList(line+1) === null) - { + // if the list has been removed, it is necessary to renumber + // starting from the *next* line because the list may have been + // separated. If it returns null, it means that the list was not cut, try + // from the current one. + const line = caretLine(); + if (line != -1 && renumberList(line + 1) === null) { renumberList(line); } } // set of "letter or digit" chars is based on section 20.5.16 of the original Java Language Spec - 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]/; - var REGEX_SPACE = /\s/; + 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]/; + const REGEX_SPACE = /\s/; function isWordChar(c) { return !!REGEX_WORDCHAR.exec(c); @@ -3300,7 +2920,7 @@ function Ace2Inner(){ } function moveByWordInLine(lineText, initialIndex, forwardNotBack) { - var i = initialIndex; + let i = initialIndex; function nextChar() { if (forwardNotBack) return lineText.charAt(i); @@ -3320,25 +2940,18 @@ function Ace2Inner(){ // On Mac and Linux, move right moves to end of word and move left moves to start; // on Windows, always move to start of word. // On Windows, Firefox and IE disagree on whether to stop for punctuation (FF says no). - if (browser.msie && forwardNotBack) - { - while ((!isDone()) && isWordChar(nextChar())) - { + if (browser.msie && forwardNotBack) { + while ((!isDone()) && isWordChar(nextChar())) { advance(); } - while ((!isDone()) && !isWordChar(nextChar())) - { + while ((!isDone()) && !isWordChar(nextChar())) { advance(); } - } - else - { - while ((!isDone()) && !isWordChar(nextChar())) - { + } else { + while ((!isDone()) && !isWordChar(nextChar())) { advance(); } - while ((!isDone()) && isWordChar(nextChar())) - { + while ((!isDone()) && isWordChar(nextChar())) { advance(); } } @@ -3348,68 +2961,62 @@ function Ace2Inner(){ function handleKeyEvent(evt) { if (!isEditable) return; - var type = evt.type; - var charCode = evt.charCode; - var keyCode = evt.keyCode; - var which = evt.which; - var altKey = evt.altKey; - var shiftKey = evt.shiftKey; + const type = evt.type; + const charCode = evt.charCode; + const keyCode = evt.keyCode; + const which = evt.which; + const altKey = evt.altKey; + const shiftKey = evt.shiftKey; // Is caret potentially hidden by the chat button? - var myselection = document.getSelection(); // get the current caret selection - var caretOffsetTop = myselection.focusNode.parentNode.offsetTop | myselection.focusNode.offsetTop; // get the carets selection offset in px IE 214 + const myselection = document.getSelection(); // get the current caret selection + const caretOffsetTop = myselection.focusNode.parentNode.offsetTop | myselection.focusNode.offsetTop; // get the carets selection offset in px IE 214 - if(myselection.focusNode.wholeText){ // Is there any content? If not lineHeight will report wrong.. + if (myselection.focusNode.wholeText) { // Is there any content? If not lineHeight will report wrong.. var lineHeight = myselection.focusNode.parentNode.offsetHeight; // line height of populated links - }else{ + } else { var lineHeight = myselection.focusNode.offsetHeight; // line height of blank lines } - //dmesg("keyevent type: "+type+", which: "+which); + // dmesg("keyevent type: "+type+", which: "+which); // Don't take action based on modifier keys going up and down. // Modifier keys do not generate "keypress" events. // 224 is the command-key under Mac Firefox. // 91 is the Windows key in IE; it is ASCII for open-bracket but isn't the keycode for that key // 20 is capslock in IE. - var isModKey = ((!charCode) && ((type == "keyup") || (type == "keydown")) && (keyCode == 16 || keyCode == 17 || keyCode == 18 || keyCode == 20 || keyCode == 224 || keyCode == 91)); + const isModKey = ((!charCode) && ((type == 'keyup') || (type == 'keydown')) && (keyCode == 16 || keyCode == 17 || keyCode == 18 || keyCode == 20 || keyCode == 224 || keyCode == 91)); if (isModKey) return; // If the key is a keypress and the browser is opera and the key is enter, do nothign at all as this fires twice. - if (keyCode == 13 && browser.opera && (type == "keypress")){ + if (keyCode == 13 && browser.opera && (type == 'keypress')) { return; // This stops double enters in Opera but double Tabs still show on single tab keypress, adding keyCode == 9 to this doesn't help as the event is fired twice } - var specialHandled = false; - var isTypeForSpecialKey = ((browser.msie || browser.safari || browser.chrome || browser.firefox) ? (type == "keydown") : (type == "keypress")); - var isTypeForCmdKey = ((browser.msie || browser.safari || browser.chrome || browser.firefox) ? (type == "keydown") : (type == "keypress")); - var stopped = false; + let specialHandled = false; + const isTypeForSpecialKey = ((browser.msie || browser.safari || browser.chrome || browser.firefox) ? (type == 'keydown') : (type == 'keypress')); + const isTypeForCmdKey = ((browser.msie || browser.safari || browser.chrome || browser.firefox) ? (type == 'keydown') : (type == 'keypress')); + let stopped = false; - inCallStackIfNecessary("handleKeyEvent", function() { - if (type == "keypress" || (isTypeForSpecialKey && keyCode == 13 /*return*/ )) - { + inCallStackIfNecessary('handleKeyEvent', function () { + if (type == 'keypress' || (isTypeForSpecialKey && keyCode == 13 /* return*/)) { // in IE, special keys don't send keypress, the keydown does the action - if (!outsideKeyPress(evt)) - { + if (!outsideKeyPress(evt)) { evt.preventDefault(); stopped = true; } - } - else if (evt.key === "Dead"){ + } else if (evt.key === 'Dead') { // If it's a dead key we don't want to do any Etherpad behavior. stopped = true; return true; - } - else if (type == "keydown") - { + } else if (type == 'keydown') { outsideKeyDown(evt); } - if (!stopped) - { - var specialHandledInHook = hooks.callAll('aceKeyEvent', { + if (!stopped) { + const specialHandledInHook = hooks.callAll('aceKeyEvent', { callstack: currentCallStack, - editorInfo: editorInfo, - rep: rep, - documentAttributeManager: documentAttributeManager, - evt:evt + editorInfo, + rep, + documentAttributeManager, + evt, }); // if any hook returned true, set specialHandled with true @@ -3417,89 +3024,86 @@ function Ace2Inner(){ specialHandled = _.contains(specialHandledInHook, true); } - var padShortcutEnabled = parent.parent.clientVars.padShortcutEnabled; - if ((!specialHandled) && altKey && isTypeForSpecialKey && keyCode == 120 && padShortcutEnabled.altF9){ + const padShortcutEnabled = parent.parent.clientVars.padShortcutEnabled; + if ((!specialHandled) && altKey && isTypeForSpecialKey && keyCode == 120 && padShortcutEnabled.altF9) { // Alt F9 focuses on the File Menu and/or editbar. // Note that while most editors use Alt F10 this is not desirable // As ubuntu cannot use Alt F10.... // Focus on the editbar. -- TODO: Move Focus back to previous state (we know it so we can use it) - 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(); firstEditbarElement.focus(); evt.preventDefault(); } - if ((!specialHandled) && altKey && keyCode == 67 && type === "keydown" && padShortcutEnabled.altC){ + if ((!specialHandled) && altKey && keyCode == 67 && type === 'keydown' && padShortcutEnabled.altC) { // Alt c focuses on the Chat window $(this).blur(); parent.parent.chat.show(); - parent.parent.$("#chatinput").focus(); + parent.parent.$('#chatinput').focus(); evt.preventDefault(); } - if ((!specialHandled) && evt.ctrlKey && shiftKey && keyCode == 50 && type === "keydown" && padShortcutEnabled.cmdShift2){ + if ((!specialHandled) && evt.ctrlKey && shiftKey && keyCode == 50 && type === 'keydown' && padShortcutEnabled.cmdShift2) { // Control-Shift-2 shows a gritter popup showing a line author - var lineNumber = rep.selEnd[0]; - var alineAttrs = rep.alines[lineNumber]; - var apool = rep.apool; + const lineNumber = rep.selEnd[0]; + const alineAttrs = rep.alines[lineNumber]; + const apool = rep.apool; // TODO: support selection ranges // TODO: Still work when authorship colors have been cleared // TODO: i18n // TODO: There appears to be a race condition or so. - var author = null; + let author = null; if (alineAttrs) { var authors = []; var authorNames = []; - var opIter = Changeset.opIterator(alineAttrs); + const opIter = Changeset.opIterator(alineAttrs); - while (opIter.hasNext()){ - var op = opIter.next(); + while (opIter.hasNext()) { + const op = opIter.next(); authorId = Changeset.opAttributeValue(op, 'author', apool); // Only push unique authors and ones with values - if(authors.indexOf(authorId) === -1 && authorId !== ""){ + if (authors.indexOf(authorId) === -1 && authorId !== '') { authors.push(authorId); } - } - } // No author information is available IE on a new pad. - if(authors.length === 0){ - var authorString = "No author information is available"; - } - else{ + if (authors.length === 0) { + var authorString = 'No author information is available'; + } else { // Known authors info, both current and historical - var padAuthors = parent.parent.pad.userList(); - var authorObj = {}; - authors.forEach(function(authorId){ - padAuthors.forEach(function(padAuthor){ + const padAuthors = parent.parent.pad.userList(); + let authorObj = {}; + authors.forEach((authorId) => { + padAuthors.forEach((padAuthor) => { // If the person doing the lookup is the author.. - if(padAuthor.userId === authorId){ - if(parent.parent.clientVars.userId === authorId){ + if (padAuthor.userId === authorId) { + if (parent.parent.clientVars.userId === authorId) { authorObj = { - name: "Me" - } - }else{ + name: 'Me', + }; + } else { authorObj = padAuthor; } } }); - if(!authorObj){ - author = "Unknown"; + if (!authorObj) { + author = 'Unknown'; return; } author = authorObj.name; - if(!author) author = "Unknown"; + if (!author) author = 'Unknown'; authorNames.push(author); - }) + }); } - if(authors.length === 1){ - var authorString = "The author of this line is " + authorNames; + if (authors.length === 1) { + var authorString = `The author of this line is ${authorNames}`; } - if(authors.length > 1){ - var authorString = "The authors of this line are " + authorNames.join(" & "); + if (authors.length > 1) { + var authorString = `The authors of this line are ${authorNames.join(' & ')}`; } parent.parent.$.gritter.add({ @@ -3510,11 +3114,10 @@ function Ace2Inner(){ // (bool | optional) if you want it to fade out on its own or just sit there sticky: false, // (int | optional) the time you want it to be alive for before fading out - time: '4000' + time: '4000', }); } - if ((!specialHandled) && isTypeForSpecialKey && keyCode == 8 && padShortcutEnabled.delete) - { + if ((!specialHandled) && isTypeForSpecialKey && keyCode == 8 && padShortcutEnabled.delete) { // "delete" key; in mozilla, if we're at the beginning of a line, normalize now, // or else deleting a blank line can take two delete presses. // -- @@ -3527,21 +3130,19 @@ function Ace2Inner(){ doDeleteKey(evt); specialHandled = true; } - if ((!specialHandled) && isTypeForSpecialKey && keyCode == 13 && padShortcutEnabled.return) - { + if ((!specialHandled) && isTypeForSpecialKey && keyCode == 13 && padShortcutEnabled.return) { // return key, handle specially; // note that in mozilla we need to do an incorporation for proper return behavior anyway. fastIncorp(4); evt.preventDefault(); doReturnKey(); - //scrollSelectionIntoView(); - scheduler.setTimeout(function() { + // scrollSelectionIntoView(); + scheduler.setTimeout(() => { outerWin.scrollBy(-100, 0); }, 0); specialHandled = true; } - if ((!specialHandled) && isTypeForSpecialKey && keyCode == 27 && padShortcutEnabled.esc) - { + if ((!specialHandled) && isTypeForSpecialKey && keyCode == 27 && padShortcutEnabled.esc) { // prevent esc key; // in mozilla versions 14-19 avoid reconnecting pad. @@ -3552,229 +3153,202 @@ function Ace2Inner(){ // close all gritters when the user hits escape key parent.parent.$.gritter.removeAll(); } - if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "s" && (evt.metaKey || evt.ctrlKey) && !evt.altKey && padShortcutEnabled.cmdS) /* Do a saved revision on ctrl S */ + if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == 's' && (evt.metaKey || evt.ctrlKey) && !evt.altKey && padShortcutEnabled.cmdS) /* Do a saved revision on ctrl S */ { evt.preventDefault(); - var originalBackground = parent.parent.$('#revisionlink').css("background") - parent.parent.$('#revisionlink').css({"background":"lightyellow"}); - scheduler.setTimeout(function(){ - parent.parent.$('#revisionlink').css({"background":originalBackground}); + const originalBackground = parent.parent.$('#revisionlink').css('background'); + parent.parent.$('#revisionlink').css({background: 'lightyellow'}); + scheduler.setTimeout(() => { + parent.parent.$('#revisionlink').css({background: originalBackground}); }, 1000); - parent.parent.pad.collabClient.sendMessage({"type":"SAVE_REVISION"}); /* The parent.parent part of this is BAD and I feel bad.. It may break something */ + parent.parent.pad.collabClient.sendMessage({type: 'SAVE_REVISION'}); /* The parent.parent part of this is BAD and I feel bad.. It may break something */ specialHandled = true; } - if ((!specialHandled) && isTypeForSpecialKey && keyCode == 9 && !(evt.metaKey || evt.ctrlKey) && padShortcutEnabled.tab) - { + if ((!specialHandled) && isTypeForSpecialKey && keyCode == 9 && !(evt.metaKey || evt.ctrlKey) && padShortcutEnabled.tab) { // tab fastIncorp(5); evt.preventDefault(); doTabKey(evt.shiftKey); - //scrollSelectionIntoView(); + // scrollSelectionIntoView(); specialHandled = true; } - if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "z" && (evt.metaKey || evt.ctrlKey) && !evt.altKey && padShortcutEnabled.cmdZ) - { + if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == 'z' && (evt.metaKey || evt.ctrlKey) && !evt.altKey && padShortcutEnabled.cmdZ) { // cmd-Z (undo) fastIncorp(6); evt.preventDefault(); - if (evt.shiftKey) - { - doUndoRedo("redo"); - } - else - { - doUndoRedo("undo"); + if (evt.shiftKey) { + doUndoRedo('redo'); + } else { + doUndoRedo('undo'); } specialHandled = true; } - if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "y" && (evt.metaKey || evt.ctrlKey) && padShortcutEnabled.cmdY) - { + if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == 'y' && (evt.metaKey || evt.ctrlKey) && padShortcutEnabled.cmdY) { // cmd-Y (redo) fastIncorp(10); evt.preventDefault(); - doUndoRedo("redo"); + doUndoRedo('redo'); specialHandled = true; } - if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "b" && (evt.metaKey || evt.ctrlKey) && padShortcutEnabled.cmdB) - { + if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == 'b' && (evt.metaKey || evt.ctrlKey) && padShortcutEnabled.cmdB) { // cmd-B (bold) fastIncorp(13); evt.preventDefault(); toggleAttributeOnSelection('bold'); specialHandled = true; } - if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "i" && (evt.metaKey || evt.ctrlKey) && padShortcutEnabled.cmdI) - { + if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == 'i' && (evt.metaKey || evt.ctrlKey) && padShortcutEnabled.cmdI) { // cmd-I (italic) fastIncorp(14); evt.preventDefault(); toggleAttributeOnSelection('italic'); specialHandled = true; } - if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "u" && (evt.metaKey || evt.ctrlKey) && padShortcutEnabled.cmdU) - { + if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == 'u' && (evt.metaKey || evt.ctrlKey) && padShortcutEnabled.cmdU) { // cmd-U (underline) fastIncorp(15); evt.preventDefault(); toggleAttributeOnSelection('underline'); specialHandled = true; } - if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "5" && (evt.metaKey || evt.ctrlKey) && evt.altKey !== true && padShortcutEnabled.cmd5) - { + if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == '5' && (evt.metaKey || evt.ctrlKey) && evt.altKey !== true && padShortcutEnabled.cmd5) { // cmd-5 (strikethrough) fastIncorp(13); evt.preventDefault(); toggleAttributeOnSelection('strikethrough'); specialHandled = true; } - if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "l" && (evt.metaKey || evt.ctrlKey) && evt.shiftKey && padShortcutEnabled.cmdShiftL) - { + if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == 'l' && (evt.metaKey || evt.ctrlKey) && evt.shiftKey && padShortcutEnabled.cmdShiftL) { // cmd-shift-L (unorderedlist) fastIncorp(9); evt.preventDefault(); - doInsertUnorderedList() + doInsertUnorderedList(); specialHandled = true; - } - if ((!specialHandled) && isTypeForCmdKey && ((String.fromCharCode(which).toLowerCase() == "n" && padShortcutEnabled.cmdShiftN) || (String.fromCharCode(which) == 1 && padShortcutEnabled.cmdShift1)) && (evt.metaKey || evt.ctrlKey) && evt.shiftKey) - { + } + if ((!specialHandled) && isTypeForCmdKey && ((String.fromCharCode(which).toLowerCase() == 'n' && padShortcutEnabled.cmdShiftN) || (String.fromCharCode(which) == 1 && padShortcutEnabled.cmdShift1)) && (evt.metaKey || evt.ctrlKey) && evt.shiftKey) { // cmd-shift-N and cmd-shift-1 (orderedlist) fastIncorp(9); evt.preventDefault(); - doInsertOrderedList() + doInsertOrderedList(); specialHandled = true; - } - if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "c" && (evt.metaKey || evt.ctrlKey) && evt.shiftKey && padShortcutEnabled.cmdShiftC) { + } + if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == 'c' && (evt.metaKey || evt.ctrlKey) && evt.shiftKey && padShortcutEnabled.cmdShiftC) { // cmd-shift-C (clearauthorship) fastIncorp(9); evt.preventDefault(); CMDS.clearauthorship(); } - if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == "h" && (evt.ctrlKey) && padShortcutEnabled.cmdH) - { + if ((!specialHandled) && isTypeForCmdKey && String.fromCharCode(which).toLowerCase() == 'h' && (evt.ctrlKey) && padShortcutEnabled.cmdH) { // cmd-H (backspace) fastIncorp(20); evt.preventDefault(); doDeleteKey(); specialHandled = true; } - if((evt.which == 36 && evt.ctrlKey == true) && padShortcutEnabled.ctrlHome){ scroll.setScrollY(0); } // Control Home send to Y = 0 - if((evt.which == 33 || evt.which == 34) && type == 'keydown' && !evt.ctrlKey){ - + if ((evt.which == 36 && evt.ctrlKey == true) && padShortcutEnabled.ctrlHome) { scroll.setScrollY(0); } // Control Home send to Y = 0 + if ((evt.which == 33 || evt.which == 34) && type == 'keydown' && !evt.ctrlKey) { evt.preventDefault(); // This is required, browsers will try to do normal default behavior on page up / down and the default behavior SUCKS - var oldVisibleLineRange = scroll.getVisibleLineRange(rep); - var topOffset = rep.selStart[0] - oldVisibleLineRange[0]; - if(topOffset < 0 ){ + const oldVisibleLineRange = scroll.getVisibleLineRange(rep); + let topOffset = rep.selStart[0] - oldVisibleLineRange[0]; + if (topOffset < 0) { topOffset = 0; } - var isPageDown = evt.which === 34; - var isPageUp = evt.which === 33; + const isPageDown = evt.which === 34; + const isPageUp = evt.which === 33; - scheduler.setTimeout(function(){ - var newVisibleLineRange = scroll.getVisibleLineRange(rep); // the visible lines IE 1,10 - var linesCount = rep.lines.length(); // total count of lines in pad IE 10 - var numberOfLinesInViewport = newVisibleLineRange[1] - newVisibleLineRange[0]; // How many lines are in the viewport right now? + scheduler.setTimeout(() => { + const newVisibleLineRange = scroll.getVisibleLineRange(rep); // the visible lines IE 1,10 + const linesCount = rep.lines.length(); // total count of lines in pad IE 10 + const numberOfLinesInViewport = newVisibleLineRange[1] - newVisibleLineRange[0]; // How many lines are in the viewport right now? - if(isPageUp && padShortcutEnabled.pageUp){ + if (isPageUp && padShortcutEnabled.pageUp) { rep.selEnd[0] = rep.selEnd[0] - numberOfLinesInViewport; // move to the bottom line +1 in the viewport (essentially skipping over a page) rep.selStart[0] = rep.selStart[0] - numberOfLinesInViewport; // move to the bottom line +1 in the viewport (essentially skipping over a page) } - if(isPageDown && padShortcutEnabled.pageDown){ // if we hit page down - if(rep.selEnd[0] >= oldVisibleLineRange[0]){ // If the new viewpoint position is actually further than where we are right now - rep.selStart[0] = oldVisibleLineRange[1] -1; // dont go further in the page down than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content - rep.selEnd[0] = oldVisibleLineRange[1] -1; // dont go further in the page down than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content + if (isPageDown && padShortcutEnabled.pageDown) { // if we hit page down + if (rep.selEnd[0] >= oldVisibleLineRange[0]) { // If the new viewpoint position is actually further than where we are right now + rep.selStart[0] = oldVisibleLineRange[1] - 1; // dont go further in the page down than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content + rep.selEnd[0] = oldVisibleLineRange[1] - 1; // dont go further in the page down than what's visible IE go from 0 to 50 if 50 is visible on screen but dont go below that else we miss content } } - //ensure min and max - if(rep.selEnd[0] < 0){ + // ensure min and max + if (rep.selEnd[0] < 0) { rep.selEnd[0] = 0; } - if(rep.selStart[0] < 0){ + if (rep.selStart[0] < 0) { rep.selStart[0] = 0; } - if(rep.selEnd[0] >= linesCount){ - rep.selEnd[0] = linesCount-1; + if (rep.selEnd[0] >= linesCount) { + rep.selEnd[0] = linesCount - 1; } updateBrowserSelectionFromRep(); - var myselection = document.getSelection(); // get the current caret selection, can't use rep. here because that only gives us the start position not the current - var caretOffsetTop = myselection.focusNode.parentNode.offsetTop || myselection.focusNode.offsetTop; // get the carets selection offset in px IE 214 + const myselection = document.getSelection(); // get the current caret selection, can't use rep. here because that only gives us the start position not the current + let caretOffsetTop = myselection.focusNode.parentNode.offsetTop || myselection.focusNode.offsetTop; // get the carets selection offset in px IE 214 // sometimes the first selection is -1 which causes problems (Especially with ep_page_view) // so use focusNode.offsetTop value. - if(caretOffsetTop === -1) caretOffsetTop = myselection.focusNode.offsetTop; + if (caretOffsetTop === -1) caretOffsetTop = myselection.focusNode.offsetTop; scroll.setScrollY(caretOffsetTop); // set the scrollY offset of the viewport on the document - }, 200); } // scroll to viewport when user presses arrow keys and caret is out of the viewport - if((evt.which == 37 || evt.which == 38 || evt.which == 39 || evt.which == 40)){ + if ((evt.which == 37 || evt.which == 38 || evt.which == 39 || evt.which == 40)) { // we use arrowKeyWasReleased to avoid triggering the animation when a key is continuously pressed // this makes the scroll smooth - if(!continuouslyPressingArrowKey(type)){ + if (!continuouslyPressingArrowKey(type)) { // We use getSelection() instead of rep to get the caret position. This avoids errors like when // the caret position is not synchronized with the rep. For example, when an user presses arrow // down to scroll the pad without releasing the key. When the key is released the rep is not // synchronized, so we don't get the right node where caret is. - var selection = getSelection(); + const selection = getSelection(); - if(selection){ - var arrowUp = evt.which === 38; - var innerHeight = getInnerHeight(); + if (selection) { + const arrowUp = evt.which === 38; + const innerHeight = getInnerHeight(); scroll.scrollWhenPressArrowKeys(arrowUp, rep, innerHeight); } } } } - if (type == "keydown") - { + if (type == 'keydown') { idleWorkTimer.atLeast(500); - } - else if (type == "keypress") - { - if ((!specialHandled) && false /*parenModule.shouldNormalizeOnChar(charCode)*/) - { + } else if (type == 'keypress') { + if ((!specialHandled) && false /* parenModule.shouldNormalizeOnChar(charCode)*/) { idleWorkTimer.atMost(0); - } - else - { + } else { idleWorkTimer.atLeast(500); } - } - else if (type == "keyup") - { - var wait = 0; + } else if (type == 'keyup') { + const wait = 0; idleWorkTimer.atLeast(wait); idleWorkTimer.atMost(wait); } // Is part of multi-keystroke international character on Firefox Mac - var isFirefoxHalfCharacter = (browser.firefox && evt.altKey && charCode === 0 && keyCode === 0); + const isFirefoxHalfCharacter = (browser.firefox && evt.altKey && charCode === 0 && keyCode === 0); // Is part of multi-keystroke international character on Safari Mac - var isSafariHalfCharacter = (browser.safari && evt.altKey && keyCode == 229); + const isSafariHalfCharacter = (browser.safari && evt.altKey && keyCode == 229); - if (thisKeyDoesntTriggerNormalize || isFirefoxHalfCharacter || isSafariHalfCharacter) - { + if (thisKeyDoesntTriggerNormalize || isFirefoxHalfCharacter || isSafariHalfCharacter) { idleWorkTimer.atLeast(3000); // give user time to type // if this is a keydown, e.g., the keyup shouldn't trigger a normalize thisKeyDoesntTriggerNormalize = true; } - if ((!specialHandled) && (!thisKeyDoesntTriggerNormalize) && (!inInternationalComposition)) - { - if (type != "keyup") - { + if ((!specialHandled) && (!thisKeyDoesntTriggerNormalize) && (!inInternationalComposition)) { + if (type != 'keyup') { observeChangesAroundSelection(); } } - if (type == "keyup") - { + if (type == 'keyup') { thisKeyDoesntTriggerNormalize = false; } }); @@ -3782,12 +3356,11 @@ function Ace2Inner(){ var thisKeyDoesntTriggerNormalize = false; - var arrowKeyWasReleased = true; + let arrowKeyWasReleased = true; function continuouslyPressingArrowKey(type) { - var firstTimeKeyIsContinuouslyPressed = false; + let firstTimeKeyIsContinuouslyPressed = false; - if (type == 'keyup') arrowKeyWasReleased = true; - else if (type == 'keydown' && arrowKeyWasReleased) { + if (type == 'keyup') { arrowKeyWasReleased = true; } else if (type == 'keydown' && arrowKeyWasReleased) { firstTimeKeyIsContinuouslyPressed = true; arrowKeyWasReleased = false; } @@ -3797,25 +3370,21 @@ function Ace2Inner(){ function doUndoRedo(which) { // precond: normalized DOM - if (undoModule.enabled) - { - var whichMethod; - if (which == "undo") whichMethod = 'performUndo'; - if (which == "redo") whichMethod = 'performRedo'; - if (whichMethod) - { - var oldEventType = currentCallStack.editEvent.eventType; + if (undoModule.enabled) { + let whichMethod; + if (which == 'undo') whichMethod = 'performUndo'; + if (which == 'redo') whichMethod = 'performRedo'; + if (whichMethod) { + const oldEventType = currentCallStack.editEvent.eventType; currentCallStack.startNewEvent(which); - undoModule[whichMethod](function(backset, selectionInfo) { - if (backset) - { + undoModule[whichMethod]((backset, selectionInfo) => { + if (backset) { performDocumentApplyChangeset(backset); } - if (selectionInfo) - { + if (selectionInfo) { performSelectionChange(lineAndColumnFromChar(selectionInfo.selStart), lineAndColumnFromChar(selectionInfo.selEnd), selectionInfo.selFocusAtStart); } - var oldEvent = currentCallStack.startNewEvent(oldEventType, true); + const oldEvent = currentCallStack.startNewEvent(oldEventType, true); return oldEvent; }); } @@ -3825,24 +3394,23 @@ function Ace2Inner(){ function updateBrowserSelectionFromRep() { // requires normalized DOM! - var selStart = rep.selStart, - selEnd = rep.selEnd; + const selStart = rep.selStart; + const selEnd = rep.selEnd; - if (!(selStart && selEnd)) - { + if (!(selStart && selEnd)) { setSelection(null); return; } - var selection = {}; + const selection = {}; - var ss = [selStart[0], selStart[1]]; + const ss = [selStart[0], selStart[1]]; selection.startPoint = getPointForLineAndChar(ss); - var se = [selEnd[0], selEnd[1]]; + const se = [selEnd[0], selEnd[1]]; selection.endPoint = getPointForLineAndChar(se); - selection.focusAtStart = !! rep.selFocusAtStart; + selection.focusAtStart = !!rep.selFocusAtStart; setSelection(selection); } editorInfo.ace_updateBrowserSelectionFromRep = updateBrowserSelectionFromRep; @@ -3853,21 +3421,15 @@ function Ace2Inner(){ } function hasIESelection() { - var browserSelection; - try - { + let browserSelection; + try { browserSelection = doc.selection; - } - catch (e) - {} + } catch (e) {} if (!browserSelection) return false; - var origSelectionRange; - try - { + let origSelectionRange; + try { origSelectionRange = browserSelection.createRange(); - } - catch (e) - {} + } catch (e) {} if (!origSelectionRange) return false; return true; } @@ -3877,167 +3439,138 @@ function Ace2Inner(){ // each of which has node (a magicdom node), index, and maxIndex. If the node // is a text node, maxIndex is the length of the text; else maxIndex is 1. // index is between 0 and maxIndex, inclusive. - if (browser.msie) - { + if (browser.msie) { var browserSelection; - try - { + try { browserSelection = doc.selection; - } - catch (e) - {} + } catch (e) {} if (!browserSelection) return null; - var origSelectionRange; - try - { + let origSelectionRange; + try { origSelectionRange = browserSelection.createRange(); - } - catch (e) - {} + } catch (e) {} if (!origSelectionRange) return null; - var selectionParent = origSelectionRange.parentElement(); + const selectionParent = origSelectionRange.parentElement(); if (selectionParent.ownerDocument != doc) return null; - var newRange = function() { + const newRange = function () { return doc.body.createTextRange(); }; - var rangeForElementNode = function(nd) { - var rng = newRange(); + const rangeForElementNode = function (nd) { + const rng = newRange(); // doesn't work on text nodes rng.moveToElementText(nd); return rng; }; - var pointFromCollapsedRange = function(rng) { - var parNode = rng.parentElement(); - var elemBelow = -1; - var elemAbove = parNode.childNodes.length; - var rangeWithin = rangeForElementNode(parNode); + const pointFromCollapsedRange = function (rng) { + const parNode = rng.parentElement(); + let elemBelow = -1; + let elemAbove = parNode.childNodes.length; + const rangeWithin = rangeForElementNode(parNode); - if (rng.compareEndPoints("StartToStart", rangeWithin) === 0) - { + if (rng.compareEndPoints('StartToStart', rangeWithin) === 0) { return { node: parNode, index: 0, - maxIndex: 1 + maxIndex: 1, }; - } - else if (rng.compareEndPoints("EndToEnd", rangeWithin) === 0) - { - if (isBlockElement(parNode) && parNode.nextSibling) - { + } else if (rng.compareEndPoints('EndToEnd', rangeWithin) === 0) { + if (isBlockElement(parNode) && parNode.nextSibling) { // caret after block is not consistent across browsers // (same line vs next) so put caret before next node return { node: parNode.nextSibling, index: 0, - maxIndex: 1 + maxIndex: 1, }; } return { node: parNode, index: 1, - maxIndex: 1 + maxIndex: 1, }; - } - else if (parNode.childNodes.length === 0) - { + } else if (parNode.childNodes.length === 0) { return { node: parNode, index: 0, - maxIndex: 1 + maxIndex: 1, }; } - for (var i = 0; i < parNode.childNodes.length; i++) - { - var n = parNode.childNodes.item(i); - if (!isNodeText(n)) - { - var nodeRange = rangeForElementNode(n); - var startComp = rng.compareEndPoints("StartToStart", nodeRange); - var endComp = rng.compareEndPoints("EndToEnd", nodeRange); - if (startComp >= 0 && endComp <= 0) - { - var index = 0; - if (startComp > 0) - { + for (let i = 0; i < parNode.childNodes.length; i++) { + const n = parNode.childNodes.item(i); + if (!isNodeText(n)) { + const nodeRange = rangeForElementNode(n); + const startComp = rng.compareEndPoints('StartToStart', nodeRange); + const endComp = rng.compareEndPoints('EndToEnd', nodeRange); + if (startComp >= 0 && endComp <= 0) { + let index = 0; + if (startComp > 0) { index = 1; } return { node: n, - index: index, - maxIndex: 1 + index, + maxIndex: 1, }; - } - else if (endComp > 0) - { - if (i > elemBelow) - { + } else if (endComp > 0) { + if (i > elemBelow) { elemBelow = i; - rangeWithin.setEndPoint("StartToEnd", nodeRange); + rangeWithin.setEndPoint('StartToEnd', nodeRange); } - } - else if (startComp < 0) - { - if (i < elemAbove) - { + } else if (startComp < 0) { + if (i < elemAbove) { elemAbove = i; - rangeWithin.setEndPoint("EndToStart", nodeRange); + rangeWithin.setEndPoint('EndToStart', nodeRange); } } } } - if ((elemAbove - elemBelow) == 1) - { - if (elemBelow >= 0) - { + if ((elemAbove - elemBelow) == 1) { + if (elemBelow >= 0) { return { node: parNode.childNodes.item(elemBelow), index: 1, - maxIndex: 1 + maxIndex: 1, }; - } - else - { + } else { return { node: parNode.childNodes.item(elemAbove), index: 0, - maxIndex: 1 + maxIndex: 1, }; } } - var idx = 0; - var r = rng.duplicate(); + let idx = 0; + const r = rng.duplicate(); // infinite stateful binary search! call function for values 0 to inf, // expecting the answer to be about 40. return index of smallest // true value. - var indexIntoRange = binarySearchInfinite(40, function(i) { + const indexIntoRange = binarySearchInfinite(40, (i) => { // the search algorithm whips the caret back and forth, // though it has to be moved relatively and may hit // the end of the buffer - var delta = i - idx; - var moved = Math.abs(r.move("character", -delta)); + const delta = i - idx; + const moved = Math.abs(r.move('character', -delta)); // next line is work-around for fact that when moving left, the beginning // of a text node is considered to be after the start of the parent element: - if (r.move("character", -1)) r.move("character", 1); + if (r.move('character', -1)) r.move('character', 1); if (delta < 0) idx -= moved; else idx += moved; - return (r.compareEndPoints("StartToStart", rangeWithin) <= 0); + return (r.compareEndPoints('StartToStart', rangeWithin) <= 0); }); // iterate over consecutive text nodes, point is in one of them - var textNode = elemBelow + 1; - var indexLeft = indexIntoRange; - while (textNode < elemAbove) - { + let textNode = elemBelow + 1; + let indexLeft = indexIntoRange; + while (textNode < elemAbove) { var tn = parNode.childNodes.item(textNode); - if (indexLeft <= tn.nodeValue.length) - { + if (indexLeft <= tn.nodeValue.length) { return { node: tn, index: indexLeft, - maxIndex: tn.nodeValue.length + maxIndex: tn.nodeValue.length, }; } indexLeft -= tn.nodeValue.length; @@ -4047,98 +3580,84 @@ function Ace2Inner(){ return { node: tn, index: tn.nodeValue.length, - maxIndex: tn.nodeValue.length + maxIndex: tn.nodeValue.length, }; }; var selection = {}; - if (origSelectionRange.compareEndPoints("StartToEnd", origSelectionRange) === 0) - { + if (origSelectionRange.compareEndPoints('StartToEnd', origSelectionRange) === 0) { // collapsed - var pnt = pointFromCollapsedRange(origSelectionRange); + const pnt = pointFromCollapsedRange(origSelectionRange); selection.startPoint = pnt; selection.endPoint = { node: pnt.node, index: pnt.index, - maxIndex: pnt.maxIndex + maxIndex: pnt.maxIndex, }; - } - else - { - var start = origSelectionRange.duplicate(); + } else { + const start = origSelectionRange.duplicate(); start.collapse(true); - var end = origSelectionRange.duplicate(); + const end = origSelectionRange.duplicate(); end.collapse(false); selection.startPoint = pointFromCollapsedRange(start); selection.endPoint = pointFromCollapsedRange(end); } return selection; - } - else - { + } else { // non-IE browser var browserSelection = window.getSelection(); - if (browserSelection && browserSelection.type != "None" && browserSelection.rangeCount !== 0) - { - var range = browserSelection.getRangeAt(0); + if (browserSelection && browserSelection.type != 'None' && browserSelection.rangeCount !== 0) { + const range = browserSelection.getRangeAt(0); function isInBody(n) { - while (n && !(n.tagName && n.tagName.toLowerCase() == "body")) - { + while (n && !(n.tagName && n.tagName.toLowerCase() == 'body')) { n = n.parentNode; } return !!n; } function pointFromRangeBound(container, offset) { - if (!isInBody(container)) - { + if (!isInBody(container)) { // command-click in Firefox selects whole document, HEAD and BODY! return { node: root, index: 0, - maxIndex: 1 + maxIndex: 1, }; } - var n = container; - var childCount = n.childNodes.length; - if (isNodeText(n)) - { + const n = container; + const childCount = n.childNodes.length; + if (isNodeText(n)) { return { node: n, index: offset, - maxIndex: n.nodeValue.length + maxIndex: n.nodeValue.length, }; - } - else if (childCount === 0) - { + } else if (childCount === 0) { return { node: n, index: 0, - maxIndex: 1 + maxIndex: 1, }; } // treat point between two nodes as BEFORE the second (rather than after the first) // if possible; this way point at end of a line block-element is treated as // at beginning of next line - else if (offset == childCount) - { + else if (offset == childCount) { var nd = n.childNodes.item(childCount - 1); var max = nodeMaxIndex(nd); return { node: nd, index: max, - maxIndex: max + maxIndex: max, }; - } - else - { + } else { var nd = n.childNodes.item(offset); var max = nodeMaxIndex(nd); return { node: nd, index: 0, - maxIndex: max + maxIndex: max, }; } } @@ -4147,13 +3666,12 @@ function Ace2Inner(){ selection.endPoint = pointFromRangeBound(range.endContainer, range.endOffset); selection.focusAtStart = (((range.startContainer != range.endContainer) || (range.startOffset != range.endOffset)) && browserSelection.anchorNode && (browserSelection.anchorNode == range.endContainer) && (browserSelection.anchorOffset == range.endOffset)); - if(selection.startPoint.node.ownerDocument !== window.document){ + if (selection.startPoint.node.ownerDocument !== window.document) { return null; } return selection; - } - else return null; + } else { return null; } } } @@ -4162,18 +3680,16 @@ function Ace2Inner(){ return { node: pt.node, index: pt.index, - maxIndex: pt.maxIndex + maxIndex: pt.maxIndex, }; } - if (browser.msie) - { + if (browser.msie) { // Oddly enough, accessing scrollHeight fixes return key handling on IE 8, // presumably by forcing some kind of internal DOM update. doc.body.scrollHeight; function moveToElementText(s, n) { - while (n.firstChild && !isNodeText(n.firstChild)) - { + while (n.firstChild && !isNodeText(n.firstChild)) { n = n.firstChild; } s.moveToElementText(n); @@ -4185,25 +3701,18 @@ function Ace2Inner(){ function setCollapsedBefore(s, n) { // s is an IE TextRange, n is a dom node - if (isNodeText(n)) - { + if (isNodeText(n)) { // previous node should not also be text, but prevent inf recurs - if (n.previousSibling && !isNodeText(n.previousSibling)) - { + if (n.previousSibling && !isNodeText(n.previousSibling)) { setCollapsedAfter(s, n.previousSibling); - } - else - { + } else { setCollapsedBefore(s, n.parentNode); } - } - else - { + } else { moveToElementText(s, n); // work around for issue that caret at beginning of line // somehow ends up at end of previous line - if (s.move('character', 1)) - { + if (s.move('character', 1)) { s.move('character', -1); } s.collapse(true); // to start @@ -4212,59 +3721,48 @@ function Ace2Inner(){ function setCollapsedAfter(s, n) { // s is an IE TextRange, n is a magicdom node - if (isNodeText(n)) - { + if (isNodeText(n)) { // can't use end of container when no nextSibling (could be on next line), // so use previousSibling or start of container and move forward. setCollapsedBefore(s, n); - s.move("character", n.nodeValue.length); - } - else - { + s.move('character', n.nodeValue.length); + } else { moveToElementText(s, n); s.collapse(false); // to end } } function getPointRange(point) { - var s = newRange(); - var n = point.node; - if (isNodeText(n)) - { + const s = newRange(); + const n = point.node; + if (isNodeText(n)) { setCollapsedBefore(s, n); - s.move("character", point.index); - } - else if (point.index === 0) - { + s.move('character', point.index); + } else if (point.index === 0) { setCollapsedBefore(s, n); - } - else - { + } else { setCollapsedAfter(s, n); } return s; } - if (selection) - { - if (!hasIESelection()) - { + if (selection) { + if (!hasIESelection()) { return; // don't steal focus } - var startPoint = copyPoint(selection.startPoint); - var endPoint = copyPoint(selection.endPoint); + const startPoint = copyPoint(selection.startPoint); + const endPoint = copyPoint(selection.endPoint); // fix issue where selection can't be extended past end of line // with shift-rightarrow or shift-downarrow - if (endPoint.index == endPoint.maxIndex && endPoint.node.nextSibling) - { + if (endPoint.index == endPoint.maxIndex && endPoint.node.nextSibling) { endPoint.node = endPoint.node.nextSibling; endPoint.index = 0; endPoint.maxIndex = nodeMaxIndex(endPoint.node); } var range = getPointRange(startPoint); - range.setEndPoint("EndToEnd", getPointRange(endPoint)); + range.setEndPoint('EndToEnd', getPointRange(endPoint)); // setting the selection in IE causes everything to scroll // so that the selection is visible. if setting the selection @@ -4272,78 +3770,58 @@ function Ace2Inner(){ function isEqualToDocumentSelection(rng) { - var browserSelection; - try - { + let browserSelection; + try { browserSelection = doc.selection; - } - catch (e) - {} + } catch (e) {} if (!browserSelection) return false; - var rng2 = browserSelection.createRange(); + const rng2 = browserSelection.createRange(); if (rng2.parentElement().ownerDocument != doc) return false; - if (rng.compareEndPoints("StartToStart", rng2) !== 0) return false; - if (rng.compareEndPoints("EndToEnd", rng2) !== 0) return false; + if (rng.compareEndPoints('StartToStart', rng2) !== 0) return false; + if (rng.compareEndPoints('EndToEnd', rng2) !== 0) return false; return true; } - if (!isEqualToDocumentSelection(range)) - { - //dmesg(toSource(selection)); - //dmesg(escapeHTML(doc.body.innerHTML)); + if (!isEqualToDocumentSelection(range)) { + // dmesg(toSource(selection)); + // dmesg(escapeHTML(doc.body.innerHTML)); range.select(); } - } - else - { - try - { + } else { + try { doc.selection.empty(); - } - catch (e) - {} + } catch (e) {} } - } - else - { + } else { // non-IE browser - var isCollapsed; + let isCollapsed; function pointToRangeBound(pt) { - var p = copyPoint(pt); + const p = copyPoint(pt); // Make sure Firefox cursor is deep enough; fixes cursor jumping when at top level, // and also problem where cut/copy of a whole line selected with fake arrow-keys // copies the next line too. - if (isCollapsed) - { + if (isCollapsed) { function diveDeep() { - while (p.node.childNodes.length > 0) - { - //&& (p.node == root || p.node.parentNode == root)) { - if (p.index === 0) - { + while (p.node.childNodes.length > 0) { + // && (p.node == root || p.node.parentNode == root)) { + if (p.index === 0) { p.node = p.node.firstChild; p.maxIndex = nodeMaxIndex(p.node); - } - else if (p.index == p.maxIndex) - { + } else if (p.index == p.maxIndex) { p.node = p.node.lastChild; p.maxIndex = nodeMaxIndex(p.node); p.index = p.maxIndex; - } - else break; + } else { break; } } } // now fix problem where cursor at end of text node at end of span-like element // with background doesn't seem to show up... - if (isNodeText(p.node) && p.index == p.maxIndex) - { - var n = p.node; - while ((!n.nextSibling) && (n != root) && (n.parentNode != root)) - { + if (isNodeText(p.node) && p.index == p.maxIndex) { + let n = p.node; + while ((!n.nextSibling) && (n != root) && (n.parentNode != root)) { n = n.parentNode; } - if (n.nextSibling && (!((typeof n.nextSibling.tagName) == "string" && n.nextSibling.tagName.toLowerCase() == "br")) && (n != p.node) && (n != root) && (n.parentNode != root)) - { + if (n.nextSibling && (!((typeof n.nextSibling.tagName) === 'string' && n.nextSibling.tagName.toLowerCase() == 'br')) && (n != p.node) && (n != root) && (n.parentNode != root)) { // found a parent, go to next node and dive in p.node = n.nextSibling; p.maxIndex = nodeMaxIndex(p.node); @@ -4353,46 +3831,37 @@ function Ace2Inner(){ } // try to make sure insertion point is styled; // also fixes other FF problems - if (!isNodeText(p.node)) - { + if (!isNodeText(p.node)) { diveDeep(); } } - if (isNodeText(p.node)) - { + if (isNodeText(p.node)) { return { container: p.node, - offset: p.index + offset: p.index, }; - } - else - { + } else { // p.index in {0,1} return { container: p.node.parentNode, - offset: childIndex(p.node) + p.index + offset: childIndex(p.node) + p.index, }; } } - var browserSelection = window.getSelection(); - if (browserSelection) - { + const browserSelection = window.getSelection(); + if (browserSelection) { browserSelection.removeAllRanges(); - if (selection) - { + if (selection) { isCollapsed = (selection.startPoint.node === selection.endPoint.node && selection.startPoint.index === selection.endPoint.index); - var start = pointToRangeBound(selection.startPoint); - var end = pointToRangeBound(selection.endPoint); + const start = pointToRangeBound(selection.startPoint); + const end = pointToRangeBound(selection.endPoint); - if ((!isCollapsed) && selection.focusAtStart && browserSelection.collapse && browserSelection.extend) - { + if ((!isCollapsed) && selection.focusAtStart && browserSelection.collapse && browserSelection.extend) { // can handle "backwards"-oriented selection, shift-arrow-keys move start // of selection browserSelection.collapse(end.container, end.offset); browserSelection.extend(start.container, start.offset); - } - else - { + } else { var range = doc.createRange(); range.setStart(start.container, start.offset); range.setEnd(end.container, end.offset); @@ -4405,9 +3874,8 @@ function Ace2Inner(){ } function childIndex(n) { - var idx = 0; - while (n.previousSibling) - { + let idx = 0; + while (n.previousSibling) { idx++; n = n.previousSibling; } @@ -4416,27 +3884,26 @@ function Ace2Inner(){ function fixView() { // calling this method repeatedly should be fast - if (getInnerWidth() === 0 || getInnerHeight() === 0) - { + if (getInnerWidth() === 0 || getInnerHeight() === 0) { return; } - var win = outerWin; + const win = outerWin; enforceEditability(); $(sideDiv).addClass('sidedivdelayed'); } - var _teardownActions = []; + const _teardownActions = []; function teardown() { - _.each(_teardownActions, function(a) { + _.each(_teardownActions, (a) => { a(); }); } - var iePastedLines = null; + const iePastedLines = null; function handleIEPaste(evt) { // Pasting in IE loses blank lines in a way that loses information; @@ -4444,19 +3911,18 @@ function Ace2Inner(){ // which becomes "one\ntwo\nthree". We can get the correct text // from the clipboard directly, but we still have to let the paste // happen to get the style information. - var clipText = window.clipboardData && window.clipboardData.getData("Text"); - if (clipText && doc.selection) - { + const clipText = window.clipboardData && window.clipboardData.getData('Text'); + if (clipText && doc.selection) { // this "paste" event seems to mess with the selection whether we try to // stop it or not, so can't really do document-level manipulation now // or in an idle call-stack. instead, use IE native manipulation - //function escapeLine(txt) { - //return processSpaces(escapeHTML(textify(txt))); - //} - //var newHTML = map(clipText.replace(/\r/g,'').split('\n'), escapeLine).join('
        '); - //doc.selection.createRange().pasteHTML(newHTML); - //evt.preventDefault(); - //iePastedLines = map(clipText.replace(/\r/g,'').split('\n'), textify); + // function escapeLine(txt) { + // return processSpaces(escapeHTML(textify(txt))); + // } + // var newHTML = map(clipText.replace(/\r/g,'').split('\n'), escapeLine).join('
        '); + // doc.selection.createRange().pasteHTML(newHTML); + // evt.preventDefault(); + // iePastedLines = map(clipText.replace(/\r/g,'').split('\n'), textify); } } @@ -4464,42 +3930,38 @@ function Ace2Inner(){ var inInternationalComposition = false; function handleCompositionEvent(evt) { // international input events, fired in FF3, at least; allow e.g. Japanese input - if (evt.type == "compositionstart") - { + if (evt.type == 'compositionstart') { inInternationalComposition = true; - } - else if (evt.type == "compositionend") - { + } else if (evt.type == 'compositionend') { inInternationalComposition = false; } } editorInfo.ace_getInInternationalComposition = function () { return inInternationalComposition; - } + }; function bindTheEventHandlers() { - $(document).on("keydown", handleKeyEvent); - $(document).on("keypress", handleKeyEvent); - $(document).on("keyup", handleKeyEvent); - $(document).on("click", handleClick); + $(document).on('keydown', handleKeyEvent); + $(document).on('keypress', handleKeyEvent); + $(document).on('keyup', handleKeyEvent); + $(document).on('click', handleClick); // dropdowns on edit bar need to be closed on clicks on both pad inner and pad outer - $(outerWin.document).on("click", hideEditBarDropdowns); + $(outerWin.document).on('click', hideEditBarDropdowns); // Disabled: https://github.com/ether/etherpad-lite/issues/2546 // Will break OL re-numbering: https://github.com/ether/etherpad-lite/pull/2533 // $(document).on("cut", handleCut); - $(root).on("blur", handleBlur); - if (browser.msie) - { - $(document).on("click", handleIEOuterClick); + $(root).on('blur', handleBlur); + if (browser.msie) { + $(document).on('click', handleIEOuterClick); } - if (browser.msie) $(root).on("paste", handleIEPaste); + if (browser.msie) $(root).on('paste', handleIEPaste); // If non-nullish, pasting on a link should be suppressed. let suppressPasteOnLink = null; - $(root).on('auxclick', function(e) { + $(root).on('auxclick', (e) => { if (e.originalEvent.button === 1 && (e.target.a || e.target.localName === 'a')) { // The user middle-clicked on a link. Usually users do this to open a link in a new tab, but // in X11 (Linux) this will instead paste the contents of the primary selection at the mouse @@ -4521,8 +3983,8 @@ function Ace2Inner(){ } }); - $(root).on("paste", function(e){ - if (suppressPasteOnLink != null && (e.target.a || e.target.localName === "a")) { + $(root).on('paste', (e) => { + if (suppressPasteOnLink != null && (e.target.a || e.target.localName === 'a')) { scheduler.clearTimeout(suppressPasteOnLink); suppressPasteOnLink = null; e.preventDefault(); @@ -4531,18 +3993,18 @@ function Ace2Inner(){ // Call paste hook hooks.callAll('acePaste', { - editorInfo: editorInfo, - rep: rep, - documentAttributeManager: documentAttributeManager, - e: e + editorInfo, + rep, + documentAttributeManager, + e, }); - }) + }); // We reference document here, this is because if we don't this will expose a bug // in Google Chrome. This bug will cause the last character on the last line to // not fire an event when dropped into.. - $(document).on("drop", function(e){ - if(e.target.a || e.target.localName === "a"){ + $(document).on('drop', (e) => { + if (e.target.a || e.target.localName === 'a') { e.preventDefault(); } @@ -4550,72 +4012,66 @@ function Ace2Inner(){ // need to merge the changes into a single changeset. So mark origin with