lint: Run eslint --fix on src/

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

View file

@ -18,19 +18,19 @@
* limitations under the License.
*/
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 <strong>Text</strong>"}}
{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 = "<!DOCTYPE HTML><html><body>" + html + "</body></html>";
return { html };
}
html = `<!DOCTYPE HTML><html><body>${html}</body></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":"<style>\n.authora_HKIv23mEbachFYfH {background-color: #a979d9}\n.authora_n4gEeMLsv1GivNeh {background-color: #a9b5d9}\n.removed {text-decoration: line-through; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; filter: alpha(opacity=80); opacity: 0.8; }\n</style>Welcome to Etherpad!<br><br>This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!<br><br>Get involved with Etherpad at <a href=\"http&#x3a;&#x2F;&#x2F;etherpad&#x2e;org\">http:&#x2F;&#x2F;etherpad.org</a><br><span class=\"authora_HKIv23mEbachFYfH\">aw</span><br><br>","authors":["a.HKIv23mEbachFYfH",""]}}
{"code":4,"message":"no or wrong API Key","data":null}
*/
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');
}
}

View file

@ -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);
}
}
};

View file

@ -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();

View file

@ -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};
};

View file

@ -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;
};

View file

@ -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);
}
};

View file

@ -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};
};

View file

@ -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;

View file

@ -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 {

View file

@ -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);
}
};

File diff suppressed because it is too large Load diff

View file

@ -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));
}
};

View file

@ -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,

View file

@ -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;
}
});
}
};

View file

@ -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 <title> tag needs to be stripped out, otherwise it is appended to the
@ -211,13 +211,13 @@ async function doImport(req, res, padId) {
* added to the <title> tag. This is a quick & dirty way of matching the
* title and comment it out independently on the classes that are set on it.
*/
text = text.replace("<title", "<!-- <title");
text = text.replace("</title>","</title>-->");
text = text.replace('<title', '<!-- <title');
text = text.replace('</title>', '</title>-->');
// node on windows has a delay on releasing of the file lock.
// 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("<script>document.addEventListener('DOMContentLoaded', function(){ var impexp = window.parent.padimpexp.handleFrameCall('" + req.directDatabaseAccess +"', '" + status + "'); })</script>");
res.send(`<script>document.addEventListener('DOMContentLoaded', function(){ var impexp = window.parent.padimpexp.handleFrameCall('${req.directDatabaseAccess}', '${status}'); })</script>`);
});
}
};

File diff suppressed because it is too large Load diff

View file

@ -19,53 +19,53 @@
* limitations under the License.
*/
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);
}
});
});
}
};

View file

@ -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 <meta name="referrer" content="no-referrer">
// 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);

View file

@ -1,9 +1,9 @@
var eejs = require('ep_etherpad-lite/node/eejs');
const eejs = require('ep_etherpad-lite/node/eejs');
exports.expressCreateServer = function (hook_name, args, cb) {
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();
}
};

View file

@ -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

View file

@ -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();
}
};

View file

@ -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();
}
};

View file

@ -1,17 +1,17 @@
var stats = require('ep_etherpad-lite/node/stats')
const stats = require('ep_etherpad-lite/node/stats');
exports.expressCreateServer = function (hook_name, args, cb) {
exports.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();
}
};

View file

@ -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();
}
};

View file

@ -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;
}
};

View file

@ -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;
}
}

View file

@ -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();
}
};

View file

@ -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 <a href="' + real_url + '">' + real_url + '</a>');
res.status(302).send(`You should be redirected to <a href="${real_url}">${real_url}</a>`);
}
});
return cb();
}
};

View file

@ -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();
}
};

View file

@ -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();
}
};

View file

@ -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();
}
};

View file

@ -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');
}
};

View file

@ -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();
}
};

View file

@ -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;
}
}
};

View file

@ -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);
}

View file

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

View file

@ -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});
};
}

View file

@ -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() {
*
* <BASE_DIR>\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);

View file

@ -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;
}

View file

@ -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;
}
};

View file

@ -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)};`);
};

View file

@ -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+="<style>\n";
if (authorColors) {
css += '<style>\n';
for (var a in apool.numToAttrib) {
var attr = apool.numToAttrib[a];
for (const a in apool.numToAttrib) {
const attr = apool.numToAttrib[a];
//skip non author attributes
if(attr[0] === "author" && attr[1] !== ""){
//add to props array
var propName = "author" + stripDotFromAuthorID(attr[1]);
// skip non author attributes
if (attr[0] === 'author' && attr[1] !== '') {
// add to props array
var propName = `author${stripDotFromAuthorID(attr[1])}`;
var newLength = props.push(propName);
anumMap[a] = newLength -1;
anumMap[a] = newLength - 1;
css+="." + propName + " {background-color: " + authorColors[attr[1]]+ "}\n";
} else if(attr[0] === "removed") {
var propName = "removed";
css += `.${propName} {background-color: ${authorColors[attr[1]]}}\n`;
} else if (attr[0] === 'removed') {
var propName = 'removed';
var newLength = props.push(propName);
anumMap[a] = newLength -1;
anumMap[a] = newLength - 1;
css+=".removed {text-decoration: line-through; " +
"-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; "+
"filter: alpha(opacity=80); "+
"opacity: 0.8; "+
"}\n";
css += '.removed {text-decoration: line-through; ' +
"-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; " +
'filter: alpha(opacity=80); ' +
'opacity: 0.8; ' +
'}\n';
}
}
css+="</style>";
css += '</style>';
}
// 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) {
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
// becomes
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
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 <span> 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('<span class="');
assem.append(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('</span>');
} else {
assem.append('</');
@ -192,90 +191,78 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
}
var urls = _findURLs(text);
const urls = _findURLs(text);
var idx = 0;
let idx = 0;
function processNextChars(numChars) {
if (numChars <= 0)
{
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;
// this iterates over every op string and decides which tags to open or to close
// based on the attribs used
while (iter.hasNext())
{
var o = iter.next();
while (iter.hasNext()) {
const o = iter.next();
var usedAttribs = [];
// mark all attribs as used
Changeset.eachAttribNumber(o.attribs, function (a) {
if (a in anumMap)
{
Changeset.eachAttribNumber(o.attribs, (a) => {
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('<a href="' + Security.escapeHTMLAttribute(url) + '" rel="noreferrer noopener">');
assem.append(`<a href="${Security.escapeHTMLAttribute(url)}" rel="noreferrer noopener">`);
processNextChars(urlLength);
assem.append('</a>');
});
}
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("<ul") === 0 || prevPiece.indexOf("<ol") === 0 || prevPiece.indexOf("</li>") === 0)
{
/*
if (prevPiece.indexOf('<ul') === 0 || prevPiece.indexOf('<ol') === 0 || prevPiece.indexOf('</li>') === 0) {
/*
uncommenting this breaks nested ols..
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("</li>");
*/
if( (nextLine.listTypeName === 'number') && (nextLine.text === '') ){
if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) {
// is the listTypeName check needed here? null text might be completely fine!
// 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("<li>");
} else {
pieces.push('<li>');
}
}
if (line.listTypeName === "number")
{
if (line.listTypeName === 'number') {
// We introduce line.start here, this is useful for continuing Ordered list line numbers
// 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("<ol start=\""+Number(line.start)+"\" class=\"" + line.listTypeName + "\">");
}else{
pieces.push("<ol class=\"" + line.listTypeName + "\">");
if (line.start) {
pieces.push(`<ol start="${Number(line.start)}" class="${line.listTypeName}">`);
} else {
pieces.push(`<ol class="${line.listTypeName}">`);
}
}
else
{
pieces.push("<ul class=\"" + line.listTypeName + "\">");
} else {
pieces.push(`<ul class="${line.listTypeName}">`);
}
}
}
}
// if we're going up a level we shouldn't be adding..
if(context.lineContent){
pieces.push("<li>", context.lineContent);
if (context.lineContent) {
pieces.push('<li>', 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("</li>");
} else {
pieces.push('</li>');
}
}
}
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("</ul") === 0 || pieces[pieces.length - 1].indexOf("</ol") === 0)
{
pieces.push("</li>");
if (pieces[pieces.length - 1].indexOf('</ul') === 0 || pieces[pieces.length - 1].indexOf('</ol') === 0) {
pieces.push('</li>');
}
if (line.listTypeName === "number")
{
pieces.push("</ol>");
}
else
{
pieces.push("</ul>");
if (line.listTypeName === 'number') {
pieces.push('</ol>');
} else {
pieces.push('</ul>');
}
}
}
}
else//outside any list, need to close line.listLevel of lists
} else// outside any list, need to close line.listLevel of lists
{
context = {
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, '&nbsp;');
}
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] = '&nbsp;';
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] = '&nbsp;';
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] = '&nbsp;';
}
}

View file

@ -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,
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
// becomes
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
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);
}
};

View file

@ -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);
});
}
};

View file

@ -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("<p></p>");
$('body').append('<p></p>');
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),
]);
}
};

View file

@ -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();

View file

@ -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);
}

View file

@ -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,
});

View file

@ -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)) {

View file

@ -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.');
}
};

View file

@ -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}`);
}
});
});
}
};

View file

@ -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}`);
}
})
}
});
};

View file

@ -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 {

View file

@ -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;
});

View file

@ -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;

View file

@ -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);
}
};

View file

@ -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;

View file

@ -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 + '</' + name + '>';
}
else {
return '<' + name + aStr + '></' + name + '>';
return `<${name}${aStr}>${contents}</${name}>`;
} else {
return `<${name}${aStr}></${name}>`;
}
};
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());
}
},
};