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