mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-22 16:36:15 -04:00
lint: Run eslint --fix
on src/
This commit is contained in:
parent
b8d07a42eb
commit
8e5fd19db2
109 changed files with 9061 additions and 10572 deletions
|
@ -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});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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)};`);
|
||||
};
|
||||
|
|
|
@ -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, ' ');
|
||||
}
|
||||
var parts = [];
|
||||
s.replace(/<[^>]*>?| |[^ <]+/g, function (m){
|
||||
const parts = [];
|
||||
s.replace(/<[^>]*>?| |[^ <]+/g, (m) => {
|
||||
parts.push(m);
|
||||
});
|
||||
if (doesWrap){
|
||||
var endOfLine = true;
|
||||
var beforeSpace = false;
|
||||
if (doesWrap) {
|
||||
let endOfLine = true;
|
||||
let beforeSpace = false;
|
||||
// last space in a run is normal, others are nbsp,
|
||||
// end of line is nbsp
|
||||
for (var i = parts.length - 1; i >= 0; i--){
|
||||
for (var i = parts.length - 1; i >= 0; i--) {
|
||||
var p = parts[i];
|
||||
if (p == " "){
|
||||
if (p == ' ') {
|
||||
if (endOfLine || beforeSpace) parts[i] = ' ';
|
||||
endOfLine = false;
|
||||
beforeSpace = true;
|
||||
}
|
||||
else if (p.charAt(0) != "<"){
|
||||
} else if (p.charAt(0) != '<') {
|
||||
endOfLine = false;
|
||||
beforeSpace = false;
|
||||
}
|
||||
}
|
||||
// beginning of line is nbsp
|
||||
for (i = 0; i < parts.length; i++){
|
||||
for (i = 0; i < parts.length; i++) {
|
||||
p = parts[i];
|
||||
if (p == " "){
|
||||
if (p == ' ') {
|
||||
parts[i] = ' ';
|
||||
break;
|
||||
}
|
||||
else if (p.charAt(0) != "<"){
|
||||
} else if (p.charAt(0) != '<') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < parts.length; i++){
|
||||
} else {
|
||||
for (i = 0; i < parts.length; i++) {
|
||||
p = parts[i];
|
||||
if (p == " "){
|
||||
if (p == ' ') {
|
||||
parts[i] = ' ';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue