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,19 +18,19 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
var customError = require("../utils/customError");
|
||||
var padManager = require("./PadManager");
|
||||
var padMessageHandler = require("../handler/PadMessageHandler");
|
||||
var readOnlyManager = require("./ReadOnlyManager");
|
||||
var groupManager = require("./GroupManager");
|
||||
var authorManager = require("./AuthorManager");
|
||||
var sessionManager = require("./SessionManager");
|
||||
var exportHtml = require("../utils/ExportHtml");
|
||||
var exportTxt = require("../utils/ExportTxt");
|
||||
var importHtml = require("../utils/ImportHtml");
|
||||
var cleanText = require("./Pad").cleanText;
|
||||
var PadDiff = require("../utils/padDiff");
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
const customError = require('../utils/customError');
|
||||
const padManager = require('./PadManager');
|
||||
const padMessageHandler = require('../handler/PadMessageHandler');
|
||||
const readOnlyManager = require('./ReadOnlyManager');
|
||||
const groupManager = require('./GroupManager');
|
||||
const authorManager = require('./AuthorManager');
|
||||
const sessionManager = require('./SessionManager');
|
||||
const exportHtml = require('../utils/ExportHtml');
|
||||
const exportTxt = require('../utils/ExportTxt');
|
||||
const importHtml = require('../utils/ImportHtml');
|
||||
const cleanText = require('./Pad').cleanText;
|
||||
const PadDiff = require('../utils/padDiff');
|
||||
|
||||
/* ********************
|
||||
* GROUP FUNCTIONS ****
|
||||
|
@ -101,10 +101,10 @@ Example returns:
|
|||
}
|
||||
|
||||
*/
|
||||
exports.getAttributePool = async function(padID) {
|
||||
let pad = await getPadSafe(padID, true);
|
||||
return { pool: pad.pool };
|
||||
}
|
||||
exports.getAttributePool = async function (padID) {
|
||||
const pad = await getPadSafe(padID, true);
|
||||
return {pool: pad.pool};
|
||||
};
|
||||
|
||||
/**
|
||||
getRevisionChangeset (padID, [rev])
|
||||
|
@ -119,22 +119,21 @@ Example returns:
|
|||
}
|
||||
|
||||
*/
|
||||
exports.getRevisionChangeset = async function(padID, rev) {
|
||||
exports.getRevisionChangeset = async function (padID, rev) {
|
||||
// try to parse the revision number
|
||||
if (rev !== undefined) {
|
||||
rev = checkValidRev(rev);
|
||||
}
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
let head = pad.getHeadRevisionNumber();
|
||||
const pad = await getPadSafe(padID, true);
|
||||
const head = pad.getHeadRevisionNumber();
|
||||
|
||||
// the client asked for a special revision
|
||||
if (rev !== undefined) {
|
||||
|
||||
// check if this is a valid revision
|
||||
if (rev > head) {
|
||||
throw new customError("rev is higher than the head revision of the pad", "apierror");
|
||||
throw new customError('rev is higher than the head revision of the pad', 'apierror');
|
||||
}
|
||||
|
||||
// get the changeset for this revision
|
||||
|
@ -143,7 +142,7 @@ exports.getRevisionChangeset = async function(padID, rev) {
|
|||
|
||||
// the client wants the latest changeset, lets return it to him
|
||||
return pad.getRevisionChangeset(head);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
getText(padID, [rev]) returns the text of a pad
|
||||
|
@ -153,33 +152,32 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {text:"Welcome Text"}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getText = async function(padID, rev) {
|
||||
exports.getText = async function (padID, rev) {
|
||||
// try to parse the revision number
|
||||
if (rev !== undefined) {
|
||||
rev = checkValidRev(rev);
|
||||
}
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
let head = pad.getHeadRevisionNumber();
|
||||
const pad = await getPadSafe(padID, true);
|
||||
const head = pad.getHeadRevisionNumber();
|
||||
|
||||
// the client asked for a special revision
|
||||
if (rev !== undefined) {
|
||||
|
||||
// check if this is a valid revision
|
||||
if (rev > head) {
|
||||
throw new customError("rev is higher than the head revision of the pad", "apierror");
|
||||
throw new customError('rev is higher than the head revision of the pad', 'apierror');
|
||||
}
|
||||
|
||||
// get the text of this revision
|
||||
let text = await pad.getInternalRevisionAText(rev);
|
||||
return { text };
|
||||
const text = await pad.getInternalRevisionAText(rev);
|
||||
return {text};
|
||||
}
|
||||
|
||||
// the client wants the latest text, lets return it to him
|
||||
let text = exportTxt.getTXTFromAtext(pad, pad.atext);
|
||||
return { text };
|
||||
}
|
||||
const text = exportTxt.getTXTFromAtext(pad, pad.atext);
|
||||
return {text};
|
||||
};
|
||||
|
||||
/**
|
||||
setText(padID, text) sets the text of a pad
|
||||
|
@ -190,20 +188,20 @@ Example returns:
|
|||
{code: 1, message:"padID does not exist", data: null}
|
||||
{code: 1, message:"text too long", data: null}
|
||||
*/
|
||||
exports.setText = async function(padID, text) {
|
||||
exports.setText = async function (padID, text) {
|
||||
// text is required
|
||||
if (typeof text !== "string") {
|
||||
throw new customError("text is not a string", "apierror");
|
||||
if (typeof text !== 'string') {
|
||||
throw new customError('text is not a string', 'apierror');
|
||||
}
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
|
||||
await Promise.all([
|
||||
pad.setText(text),
|
||||
padMessageHandler.updatePadClients(pad),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
appendText(padID, text) appends text to a pad
|
||||
|
@ -214,18 +212,18 @@ Example returns:
|
|||
{code: 1, message:"padID does not exist", data: null}
|
||||
{code: 1, message:"text too long", data: null}
|
||||
*/
|
||||
exports.appendText = async function(padID, text) {
|
||||
exports.appendText = async function (padID, text) {
|
||||
// text is required
|
||||
if (typeof text !== "string") {
|
||||
throw new customError("text is not a string", "apierror");
|
||||
if (typeof text !== 'string') {
|
||||
throw new customError('text is not a string', 'apierror');
|
||||
}
|
||||
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
await Promise.all([
|
||||
pad.appendText(text),
|
||||
padMessageHandler.updatePadClients(pad),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
getHTML(padID, [rev]) returns the html of a pad
|
||||
|
@ -235,19 +233,19 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {text:"Welcome <strong>Text</strong>"}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getHTML = async function(padID, rev) {
|
||||
exports.getHTML = async function (padID, rev) {
|
||||
if (rev !== undefined) {
|
||||
rev = checkValidRev(rev);
|
||||
}
|
||||
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
|
||||
// the client asked for a special revision
|
||||
if (rev !== undefined) {
|
||||
// check if this is a valid revision
|
||||
let head = pad.getHeadRevisionNumber();
|
||||
const head = pad.getHeadRevisionNumber();
|
||||
if (rev > head) {
|
||||
throw new customError("rev is higher than the head revision of the pad", "apierror");
|
||||
throw new customError('rev is higher than the head revision of the pad', 'apierror');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,9 +253,9 @@ exports.getHTML = async function(padID, rev) {
|
|||
let html = await exportHtml.getPadHTML(pad, rev);
|
||||
|
||||
// wrap the HTML
|
||||
html = "<!DOCTYPE HTML><html><body>" + html + "</body></html>";
|
||||
return { html };
|
||||
}
|
||||
html = `<!DOCTYPE HTML><html><body>${html}</body></html>`;
|
||||
return {html};
|
||||
};
|
||||
|
||||
/**
|
||||
setHTML(padID, html) sets the text of a pad based on HTML
|
||||
|
@ -267,20 +265,20 @@ Example returns:
|
|||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.setHTML = async function(padID, html) {
|
||||
exports.setHTML = async function (padID, html) {
|
||||
// html string is required
|
||||
if (typeof html !== "string") {
|
||||
throw new customError("html is not a string", "apierror");
|
||||
if (typeof html !== 'string') {
|
||||
throw new customError('html is not a string', 'apierror');
|
||||
}
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
|
||||
// add a new changeset with the new html to the pad
|
||||
try {
|
||||
await importHtml.setPadHTML(pad, cleanText(html));
|
||||
} catch (e) {
|
||||
throw new customError("HTML is malformed", "apierror");
|
||||
throw new customError('HTML is malformed', 'apierror');
|
||||
}
|
||||
|
||||
// update the clients on the pad
|
||||
|
@ -303,23 +301,23 @@ Example returns:
|
|||
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getChatHistory = async function(padID, start, end) {
|
||||
exports.getChatHistory = async function (padID, start, end) {
|
||||
if (start && end) {
|
||||
if (start < 0) {
|
||||
throw new customError("start is below zero", "apierror");
|
||||
throw new customError('start is below zero', 'apierror');
|
||||
}
|
||||
if (end < 0) {
|
||||
throw new customError("end is below zero", "apierror");
|
||||
throw new customError('end is below zero', 'apierror');
|
||||
}
|
||||
if (start > end) {
|
||||
throw new customError("start is higher than end", "apierror");
|
||||
throw new customError('start is higher than end', 'apierror');
|
||||
}
|
||||
}
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
|
||||
var chatHead = pad.chatHead;
|
||||
const chatHead = pad.chatHead;
|
||||
|
||||
// fall back to getting the whole chat-history if a parameter is missing
|
||||
if (!start || !end) {
|
||||
|
@ -328,17 +326,17 @@ exports.getChatHistory = async function(padID, start, end) {
|
|||
}
|
||||
|
||||
if (start > chatHead) {
|
||||
throw new customError("start is higher than the current chatHead", "apierror");
|
||||
throw new customError('start is higher than the current chatHead', 'apierror');
|
||||
}
|
||||
if (end > chatHead) {
|
||||
throw new customError("end is higher than the current chatHead", "apierror");
|
||||
throw new customError('end is higher than the current chatHead', 'apierror');
|
||||
}
|
||||
|
||||
// the the whole message-log and return it to the client
|
||||
let messages = await pad.getChatMessages(start, end);
|
||||
const messages = await pad.getChatMessages(start, end);
|
||||
|
||||
return { messages };
|
||||
}
|
||||
return {messages};
|
||||
};
|
||||
|
||||
/**
|
||||
appendChatMessage(padID, text, authorID, time), creates a chat message for the pad id, time is a timestamp
|
||||
|
@ -348,10 +346,10 @@ Example returns:
|
|||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.appendChatMessage = async function(padID, text, authorID, time) {
|
||||
exports.appendChatMessage = async function (padID, text, authorID, time) {
|
||||
// text is required
|
||||
if (typeof text !== "string") {
|
||||
throw new customError("text is not a string", "apierror");
|
||||
if (typeof text !== 'string') {
|
||||
throw new customError('text is not a string', 'apierror');
|
||||
}
|
||||
|
||||
// if time is not an integer value set time to current timestamp
|
||||
|
@ -363,7 +361,7 @@ exports.appendChatMessage = async function(padID, text, authorID, time) {
|
|||
|
||||
// save chat message to database and send message to all connected clients
|
||||
await padMessageHandler.sendChatMessageToPadClients(time, authorID, text, padID);
|
||||
}
|
||||
};
|
||||
|
||||
/* ***************
|
||||
* PAD FUNCTIONS *
|
||||
|
@ -377,11 +375,11 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {revisions: 56}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getRevisionsCount = async function(padID) {
|
||||
exports.getRevisionsCount = async function (padID) {
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
return { revisions: pad.getHeadRevisionNumber() };
|
||||
}
|
||||
const pad = await getPadSafe(padID, true);
|
||||
return {revisions: pad.getHeadRevisionNumber()};
|
||||
};
|
||||
|
||||
/**
|
||||
getSavedRevisionsCount(padID) returns the number of saved revisions of this pad
|
||||
|
@ -391,11 +389,11 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {savedRevisions: 42}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getSavedRevisionsCount = async function(padID) {
|
||||
exports.getSavedRevisionsCount = async function (padID) {
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
return { savedRevisions: pad.getSavedRevisionsNumber() };
|
||||
}
|
||||
const pad = await getPadSafe(padID, true);
|
||||
return {savedRevisions: pad.getSavedRevisionsNumber()};
|
||||
};
|
||||
|
||||
/**
|
||||
listSavedRevisions(padID) returns the list of saved revisions of this pad
|
||||
|
@ -405,11 +403,11 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.listSavedRevisions = async function(padID) {
|
||||
exports.listSavedRevisions = async function (padID) {
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
return { savedRevisions: pad.getSavedRevisionsList() };
|
||||
}
|
||||
const pad = await getPadSafe(padID, true);
|
||||
return {savedRevisions: pad.getSavedRevisionsList()};
|
||||
};
|
||||
|
||||
/**
|
||||
saveRevision(padID) returns the list of saved revisions of this pad
|
||||
|
@ -419,28 +417,28 @@ Example returns:
|
|||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.saveRevision = async function(padID, rev) {
|
||||
exports.saveRevision = async function (padID, rev) {
|
||||
// check if rev is a number
|
||||
if (rev !== undefined) {
|
||||
rev = checkValidRev(rev);
|
||||
}
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
let head = pad.getHeadRevisionNumber();
|
||||
const pad = await getPadSafe(padID, true);
|
||||
const head = pad.getHeadRevisionNumber();
|
||||
|
||||
// the client asked for a special revision
|
||||
if (rev !== undefined) {
|
||||
if (rev > head) {
|
||||
throw new customError("rev is higher than the head revision of the pad", "apierror");
|
||||
throw new customError('rev is higher than the head revision of the pad', 'apierror');
|
||||
}
|
||||
} else {
|
||||
rev = pad.getHeadRevisionNumber();
|
||||
}
|
||||
|
||||
let author = await authorManager.createAuthor('API');
|
||||
const author = await authorManager.createAuthor('API');
|
||||
await pad.addSavedRevision(rev, author.authorID, 'Saved through API call');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
getLastEdited(padID) returns the timestamp of the last revision of the pad
|
||||
|
@ -450,12 +448,12 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {lastEdited: 1340815946602}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getLastEdited = async function(padID) {
|
||||
exports.getLastEdited = async function (padID) {
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
let lastEdited = await pad.getLastEdit();
|
||||
return { lastEdited };
|
||||
}
|
||||
const pad = await getPadSafe(padID, true);
|
||||
const lastEdited = await pad.getLastEdit();
|
||||
return {lastEdited};
|
||||
};
|
||||
|
||||
/**
|
||||
createPad(padName [, text]) creates a new pad in this group
|
||||
|
@ -465,22 +463,22 @@ Example returns:
|
|||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"pad does already exist", data: null}
|
||||
*/
|
||||
exports.createPad = async function(padID, text) {
|
||||
exports.createPad = async function (padID, text) {
|
||||
if (padID) {
|
||||
// ensure there is no $ in the padID
|
||||
if (padID.indexOf("$") !== -1) {
|
||||
throw new customError("createPad can't create group pads", "apierror");
|
||||
if (padID.indexOf('$') !== -1) {
|
||||
throw new customError("createPad can't create group pads", 'apierror');
|
||||
}
|
||||
|
||||
// check for url special characters
|
||||
if (padID.match(/(\/|\?|&|#)/)) {
|
||||
throw new customError("malformed padID: Remove special characters", "apierror");
|
||||
throw new customError('malformed padID: Remove special characters', 'apierror');
|
||||
}
|
||||
}
|
||||
|
||||
// create pad
|
||||
await getPadSafe(padID, false, text);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
deletePad(padID) deletes a pad
|
||||
|
@ -490,10 +488,10 @@ Example returns:
|
|||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.deletePad = async function(padID) {
|
||||
let pad = await getPadSafe(padID, true);
|
||||
exports.deletePad = async function (padID) {
|
||||
const pad = await getPadSafe(padID, true);
|
||||
await pad.remove();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
restoreRevision(padID, [rev]) Restores revision from past as new changeset
|
||||
|
@ -503,34 +501,34 @@ exports.deletePad = async function(padID) {
|
|||
{code:0, message:"ok", data:null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.restoreRevision = async function(padID, rev) {
|
||||
exports.restoreRevision = async function (padID, rev) {
|
||||
// check if rev is a number
|
||||
if (rev === undefined) {
|
||||
throw new customError("rev is not defined", "apierror");
|
||||
throw new customError('rev is not defined', 'apierror');
|
||||
}
|
||||
rev = checkValidRev(rev);
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
|
||||
// check if this is a valid revision
|
||||
if (rev > pad.getHeadRevisionNumber()) {
|
||||
throw new customError("rev is higher than the head revision of the pad", "apierror");
|
||||
throw new customError('rev is higher than the head revision of the pad', 'apierror');
|
||||
}
|
||||
|
||||
let atext = await pad.getInternalRevisionAText(rev);
|
||||
const atext = await pad.getInternalRevisionAText(rev);
|
||||
|
||||
var oldText = pad.text();
|
||||
atext.text += "\n";
|
||||
const oldText = pad.text();
|
||||
atext.text += '\n';
|
||||
|
||||
function eachAttribRun(attribs, func) {
|
||||
var attribsIter = Changeset.opIterator(attribs);
|
||||
var textIndex = 0;
|
||||
var newTextStart = 0;
|
||||
var newTextEnd = atext.text.length;
|
||||
const attribsIter = Changeset.opIterator(attribs);
|
||||
let textIndex = 0;
|
||||
const newTextStart = 0;
|
||||
const newTextEnd = atext.text.length;
|
||||
while (attribsIter.hasNext()) {
|
||||
var op = attribsIter.next();
|
||||
var nextIndex = textIndex + op.chars;
|
||||
const op = attribsIter.next();
|
||||
const nextIndex = textIndex + op.chars;
|
||||
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
|
||||
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
|
||||
}
|
||||
|
@ -539,14 +537,14 @@ exports.restoreRevision = async function(padID, rev) {
|
|||
}
|
||||
|
||||
// create a new changeset with a helper builder object
|
||||
var builder = Changeset.builder(oldText.length);
|
||||
const builder = Changeset.builder(oldText.length);
|
||||
|
||||
// assemble each line into the builder
|
||||
eachAttribRun(atext.attribs, function(start, end, attribs) {
|
||||
eachAttribRun(atext.attribs, (start, end, attribs) => {
|
||||
builder.insert(atext.text.substring(start, end), attribs);
|
||||
});
|
||||
|
||||
var lastNewlinePos = oldText.lastIndexOf('\n');
|
||||
const lastNewlinePos = oldText.lastIndexOf('\n');
|
||||
if (lastNewlinePos < 0) {
|
||||
builder.remove(oldText.length - 1, 0);
|
||||
} else {
|
||||
|
@ -554,13 +552,13 @@ exports.restoreRevision = async function(padID, rev) {
|
|||
builder.remove(oldText.length - lastNewlinePos - 1, 0);
|
||||
}
|
||||
|
||||
var changeset = builder.toString();
|
||||
const changeset = builder.toString();
|
||||
|
||||
await Promise.all([
|
||||
pad.appendRevision(changeset),
|
||||
padMessageHandler.updatePadClients(pad),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true,
|
||||
|
@ -571,10 +569,10 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {padID: destinationID}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.copyPad = async function(sourceID, destinationID, force) {
|
||||
let pad = await getPadSafe(sourceID, true);
|
||||
exports.copyPad = async function (sourceID, destinationID, force) {
|
||||
const pad = await getPadSafe(sourceID, true);
|
||||
await pad.copy(destinationID, force);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
copyPadWithoutHistory(sourceID, destinationID[, force=false]) copies a pad. If force is true,
|
||||
|
@ -585,10 +583,10 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {padID: destinationID}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.copyPadWithoutHistory = async function(sourceID, destinationID, force) {
|
||||
let pad = await getPadSafe(sourceID, true);
|
||||
exports.copyPadWithoutHistory = async function (sourceID, destinationID, force) {
|
||||
const pad = await getPadSafe(sourceID, true);
|
||||
await pad.copyPadWithoutHistory(destinationID, force);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
movePad(sourceID, destinationID[, force=false]) moves a pad. If force is true,
|
||||
|
@ -599,11 +597,11 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {padID: destinationID}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.movePad = async function(sourceID, destinationID, force) {
|
||||
let pad = await getPadSafe(sourceID, true);
|
||||
exports.movePad = async function (sourceID, destinationID, force) {
|
||||
const pad = await getPadSafe(sourceID, true);
|
||||
await pad.copy(destinationID, force);
|
||||
await pad.remove();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
getReadOnlyLink(padID) returns the read only link of a pad
|
||||
|
@ -613,15 +611,15 @@ Example returns:
|
|||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getReadOnlyID = async function(padID) {
|
||||
exports.getReadOnlyID = async function (padID) {
|
||||
// we don't need the pad object, but this function does all the security stuff for us
|
||||
await getPadSafe(padID, true);
|
||||
|
||||
// get the readonlyId
|
||||
let readOnlyID = await readOnlyManager.getReadOnlyId(padID);
|
||||
const readOnlyID = await readOnlyManager.getReadOnlyId(padID);
|
||||
|
||||
return { readOnlyID };
|
||||
}
|
||||
return {readOnlyID};
|
||||
};
|
||||
|
||||
/**
|
||||
getPadID(roID) returns the padID of a pad based on the readonlyID(roID)
|
||||
|
@ -631,15 +629,15 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {padID: padID}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getPadID = async function(roID) {
|
||||
exports.getPadID = async function (roID) {
|
||||
// get the PadId
|
||||
let padID = await readOnlyManager.getPadId(roID);
|
||||
const padID = await readOnlyManager.getPadId(roID);
|
||||
if (padID === null) {
|
||||
throw new customError("padID does not exist", "apierror");
|
||||
throw new customError('padID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
return { padID };
|
||||
}
|
||||
return {padID};
|
||||
};
|
||||
|
||||
/**
|
||||
setPublicStatus(padID, publicStatus) sets a boolean for the public status of a pad
|
||||
|
@ -649,20 +647,20 @@ Example returns:
|
|||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.setPublicStatus = async function(padID, publicStatus) {
|
||||
exports.setPublicStatus = async function (padID, publicStatus) {
|
||||
// ensure this is a group pad
|
||||
checkGroupPad(padID, "publicStatus");
|
||||
checkGroupPad(padID, 'publicStatus');
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
|
||||
// convert string to boolean
|
||||
if (typeof publicStatus === "string") {
|
||||
publicStatus = (publicStatus.toLowerCase() === "true");
|
||||
if (typeof publicStatus === 'string') {
|
||||
publicStatus = (publicStatus.toLowerCase() === 'true');
|
||||
}
|
||||
|
||||
await pad.setPublicStatus(publicStatus);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
getPublicStatus(padID) return true of false
|
||||
|
@ -672,14 +670,14 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {publicStatus: true}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getPublicStatus = async function(padID) {
|
||||
exports.getPublicStatus = async function (padID) {
|
||||
// ensure this is a group pad
|
||||
checkGroupPad(padID, "publicStatus");
|
||||
checkGroupPad(padID, 'publicStatus');
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
return { publicStatus: pad.getPublicStatus() };
|
||||
}
|
||||
const pad = await getPadSafe(padID, true);
|
||||
return {publicStatus: pad.getPublicStatus()};
|
||||
};
|
||||
|
||||
/**
|
||||
listAuthorsOfPad(padID) returns an array of authors who contributed to this pad
|
||||
|
@ -689,12 +687,12 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.listAuthorsOfPad = async function(padID) {
|
||||
exports.listAuthorsOfPad = async function (padID) {
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
let authorIDs = pad.getAllAuthors();
|
||||
return { authorIDs };
|
||||
}
|
||||
const pad = await getPadSafe(padID, true);
|
||||
const authorIDs = pad.getAllAuthors();
|
||||
return {authorIDs};
|
||||
};
|
||||
|
||||
/**
|
||||
sendClientsMessage(padID, msg) sends a message to all clients connected to the
|
||||
|
@ -719,10 +717,10 @@ Example returns:
|
|||
{code: 1, message:"padID does not exist"}
|
||||
*/
|
||||
|
||||
exports.sendClientsMessage = async function(padID, msg) {
|
||||
let pad = await getPadSafe(padID, true);
|
||||
exports.sendClientsMessage = async function (padID, msg) {
|
||||
const pad = await getPadSafe(padID, true);
|
||||
padMessageHandler.handleCustomMessage(padID, msg);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
checkToken() returns ok when the current api token is valid
|
||||
|
@ -732,8 +730,8 @@ Example returns:
|
|||
{"code":0,"message":"ok","data":null}
|
||||
{"code":4,"message":"no or wrong API Key","data":null}
|
||||
*/
|
||||
exports.checkToken = async function() {
|
||||
}
|
||||
exports.checkToken = async function () {
|
||||
};
|
||||
|
||||
/**
|
||||
getChatHead(padID) returns the chatHead (last number of the last chat-message) of the pad
|
||||
|
@ -743,11 +741,11 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {chatHead: 42}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getChatHead = async function(padID) {
|
||||
exports.getChatHead = async function (padID) {
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
return { chatHead: pad.chatHead };
|
||||
}
|
||||
const pad = await getPadSafe(padID, true);
|
||||
return {chatHead: pad.chatHead};
|
||||
};
|
||||
|
||||
/**
|
||||
createDiffHTML(padID, startRev, endRev) returns an object of diffs from 2 points in a pad
|
||||
|
@ -757,8 +755,7 @@ Example returns:
|
|||
{"code":0,"message":"ok","data":{"html":"<style>\n.authora_HKIv23mEbachFYfH {background-color: #a979d9}\n.authora_n4gEeMLsv1GivNeh {background-color: #a9b5d9}\n.removed {text-decoration: line-through; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; filter: alpha(opacity=80); opacity: 0.8; }\n</style>Welcome to Etherpad!<br><br>This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!<br><br>Get involved with Etherpad at <a href=\"http://etherpad.org\">http://etherpad.org</a><br><span class=\"authora_HKIv23mEbachFYfH\">aw</span><br><br>","authors":["a.HKIv23mEbachFYfH",""]}}
|
||||
{"code":4,"message":"no or wrong API Key","data":null}
|
||||
*/
|
||||
exports.createDiffHTML = async function(padID, startRev, endRev) {
|
||||
|
||||
exports.createDiffHTML = async function (padID, startRev, endRev) {
|
||||
// check if startRev is a number
|
||||
if (startRev !== undefined) {
|
||||
startRev = checkValidRev(startRev);
|
||||
|
@ -770,18 +767,18 @@ exports.createDiffHTML = async function(padID, startRev, endRev) {
|
|||
}
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
try {
|
||||
var padDiff = new PadDiff(pad, startRev, endRev);
|
||||
} catch (e) {
|
||||
throw { stop: e.message };
|
||||
throw {stop: e.message};
|
||||
}
|
||||
|
||||
let html = await padDiff.getHtml();
|
||||
let authors = await padDiff.getAuthors();
|
||||
const html = await padDiff.getHtml();
|
||||
const authors = await padDiff.getAuthors();
|
||||
|
||||
return { html, authors };
|
||||
}
|
||||
return {html, authors};
|
||||
};
|
||||
|
||||
/* ********************
|
||||
** GLOBAL FUNCTIONS **
|
||||
|
@ -796,20 +793,20 @@ exports.createDiffHTML = async function(padID, startRev, endRev) {
|
|||
{"code":4,"message":"no or wrong API Key","data":null}
|
||||
*/
|
||||
|
||||
exports.getStats = async function() {
|
||||
exports.getStats = async function () {
|
||||
const sessionInfos = padMessageHandler.sessioninfos;
|
||||
|
||||
const sessionKeys = Object.keys(sessionInfos);
|
||||
const activePads = new Set(Object.entries(sessionInfos).map(k => k[1].padId));
|
||||
const activePads = new Set(Object.entries(sessionInfos).map((k) => k[1].padId));
|
||||
|
||||
const { padIDs } = await padManager.listAllPads();
|
||||
const {padIDs} = await padManager.listAllPads();
|
||||
|
||||
return {
|
||||
totalPads: padIDs.length,
|
||||
totalSessions: sessionKeys.length,
|
||||
totalActivePads: activePads.size,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/* ****************************
|
||||
** INTERNAL HELPER FUNCTIONS *
|
||||
|
@ -817,32 +814,32 @@ exports.getStats = async function() {
|
|||
|
||||
// checks if a number is an int
|
||||
function is_int(value) {
|
||||
return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value)
|
||||
return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value);
|
||||
}
|
||||
|
||||
// gets a pad safe
|
||||
async function getPadSafe(padID, shouldExist, text) {
|
||||
// check if padID is a string
|
||||
if (typeof padID !== "string") {
|
||||
throw new customError("padID is not a string", "apierror");
|
||||
if (typeof padID !== 'string') {
|
||||
throw new customError('padID is not a string', 'apierror');
|
||||
}
|
||||
|
||||
// check if the padID maches the requirements
|
||||
if (!padManager.isValidPadId(padID)) {
|
||||
throw new customError("padID did not match requirements", "apierror");
|
||||
throw new customError('padID did not match requirements', 'apierror');
|
||||
}
|
||||
|
||||
// check if the pad exists
|
||||
let exists = await padManager.doesPadExists(padID);
|
||||
const exists = await padManager.doesPadExists(padID);
|
||||
|
||||
if (!exists && shouldExist) {
|
||||
// does not exist, but should
|
||||
throw new customError("padID does not exist", "apierror");
|
||||
throw new customError('padID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
if (exists && !shouldExist) {
|
||||
// does exist, but shouldn't
|
||||
throw new customError("padID does already exist", "apierror");
|
||||
throw new customError('padID does already exist', 'apierror');
|
||||
}
|
||||
|
||||
// pad exists, let's get it
|
||||
|
@ -852,23 +849,23 @@ async function getPadSafe(padID, shouldExist, text) {
|
|||
// checks if a rev is a legal number
|
||||
// pre-condition is that `rev` is not undefined
|
||||
function checkValidRev(rev) {
|
||||
if (typeof rev !== "number") {
|
||||
if (typeof rev !== 'number') {
|
||||
rev = parseInt(rev, 10);
|
||||
}
|
||||
|
||||
// check if rev is a number
|
||||
if (isNaN(rev)) {
|
||||
throw new customError("rev is not a number", "apierror");
|
||||
throw new customError('rev is not a number', 'apierror');
|
||||
}
|
||||
|
||||
// ensure this is not a negative number
|
||||
if (rev < 0) {
|
||||
throw new customError("rev is not a negative number", "apierror");
|
||||
throw new customError('rev is not a negative number', 'apierror');
|
||||
}
|
||||
|
||||
// ensure this is not a float value
|
||||
if (!is_int(rev)) {
|
||||
throw new customError("rev is a float value", "apierror");
|
||||
throw new customError('rev is a float value', 'apierror');
|
||||
}
|
||||
|
||||
return rev;
|
||||
|
@ -877,7 +874,7 @@ function checkValidRev(rev) {
|
|||
// checks if a padID is part of a group
|
||||
function checkGroupPad(padID, field) {
|
||||
// ensure this is a group pad
|
||||
if (padID && padID.indexOf("$") === -1) {
|
||||
throw new customError(`You can only get/set the ${field} of pads that belong to a group`, "apierror");
|
||||
if (padID && padID.indexOf('$') === -1) {
|
||||
throw new customError(`You can only get/set the ${field} of pads that belong to a group`, 'apierror');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,31 +18,87 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var db = require("./DB");
|
||||
var customError = require("../utils/customError");
|
||||
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
const db = require('./DB');
|
||||
const customError = require('../utils/customError');
|
||||
const randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
|
||||
exports.getColorPalette = function() {
|
||||
exports.getColorPalette = function () {
|
||||
return [
|
||||
"#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1",
|
||||
"#ffa8a8", "#ffe699", "#cfff9e", "#99ffb3", "#a3ffff", "#99b3ff", "#cc99ff", "#ff99e5",
|
||||
"#e7b1b1", "#e9dcAf", "#cde9af", "#bfedcc", "#b1e7e7", "#c3cdee", "#d2b8ea", "#eec3e6",
|
||||
"#e9cece", "#e7e0ca", "#d3e5c7", "#bce1c5", "#c1e2e2", "#c1c9e2", "#cfc1e2", "#e0bdd9",
|
||||
"#baded3", "#a0f8eb", "#b1e7e0", "#c3c8e4", "#cec5e2", "#b1d5e7", "#cda8f0", "#f0f0a8",
|
||||
"#f2f2a6", "#f5a8eb", "#c5f9a9", "#ececbb", "#e7c4bc", "#daf0b2", "#b0a0fd", "#bce2e7",
|
||||
"#cce2bb", "#ec9afe", "#edabbd", "#aeaeea", "#c4e7b1", "#d722bb", "#f3a5e7", "#ffa8a8",
|
||||
"#d8c0c5", "#eaaedd", "#adc6eb", "#bedad1", "#dee9af", "#e9afc2", "#f8d2a0", "#b3b3e6"
|
||||
'#ffc7c7',
|
||||
'#fff1c7',
|
||||
'#e3ffc7',
|
||||
'#c7ffd5',
|
||||
'#c7ffff',
|
||||
'#c7d5ff',
|
||||
'#e3c7ff',
|
||||
'#ffc7f1',
|
||||
'#ffa8a8',
|
||||
'#ffe699',
|
||||
'#cfff9e',
|
||||
'#99ffb3',
|
||||
'#a3ffff',
|
||||
'#99b3ff',
|
||||
'#cc99ff',
|
||||
'#ff99e5',
|
||||
'#e7b1b1',
|
||||
'#e9dcAf',
|
||||
'#cde9af',
|
||||
'#bfedcc',
|
||||
'#b1e7e7',
|
||||
'#c3cdee',
|
||||
'#d2b8ea',
|
||||
'#eec3e6',
|
||||
'#e9cece',
|
||||
'#e7e0ca',
|
||||
'#d3e5c7',
|
||||
'#bce1c5',
|
||||
'#c1e2e2',
|
||||
'#c1c9e2',
|
||||
'#cfc1e2',
|
||||
'#e0bdd9',
|
||||
'#baded3',
|
||||
'#a0f8eb',
|
||||
'#b1e7e0',
|
||||
'#c3c8e4',
|
||||
'#cec5e2',
|
||||
'#b1d5e7',
|
||||
'#cda8f0',
|
||||
'#f0f0a8',
|
||||
'#f2f2a6',
|
||||
'#f5a8eb',
|
||||
'#c5f9a9',
|
||||
'#ececbb',
|
||||
'#e7c4bc',
|
||||
'#daf0b2',
|
||||
'#b0a0fd',
|
||||
'#bce2e7',
|
||||
'#cce2bb',
|
||||
'#ec9afe',
|
||||
'#edabbd',
|
||||
'#aeaeea',
|
||||
'#c4e7b1',
|
||||
'#d722bb',
|
||||
'#f3a5e7',
|
||||
'#ffa8a8',
|
||||
'#d8c0c5',
|
||||
'#eaaedd',
|
||||
'#adc6eb',
|
||||
'#bedad1',
|
||||
'#dee9af',
|
||||
'#e9afc2',
|
||||
'#f8d2a0',
|
||||
'#b3b3e6',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the author exists
|
||||
*/
|
||||
exports.doesAuthorExist = async function(authorID) {
|
||||
let author = await db.get("globalAuthor:" + authorID);
|
||||
exports.doesAuthorExist = async function (authorID) {
|
||||
const author = await db.get(`globalAuthor:${authorID}`);
|
||||
|
||||
return author !== null;
|
||||
}
|
||||
};
|
||||
|
||||
/* exported for backwards compatibility */
|
||||
exports.doesAuthorExists = exports.doesAuthorExist;
|
||||
|
@ -51,20 +107,20 @@ exports.doesAuthorExists = exports.doesAuthorExist;
|
|||
* Returns the AuthorID for a token.
|
||||
* @param {String} token The token
|
||||
*/
|
||||
exports.getAuthor4Token = async function(token) {
|
||||
let author = await mapAuthorWithDBKey("token2author", token);
|
||||
exports.getAuthor4Token = async function (token) {
|
||||
const author = await mapAuthorWithDBKey('token2author', token);
|
||||
|
||||
// return only the sub value authorID
|
||||
return author ? author.authorID : author;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the AuthorID for a mapper.
|
||||
* @param {String} token The mapper
|
||||
* @param {String} name The name of the author (optional)
|
||||
*/
|
||||
exports.createAuthorIfNotExistsFor = async function(authorMapper, name) {
|
||||
let author = await mapAuthorWithDBKey("mapper2author", authorMapper);
|
||||
exports.createAuthorIfNotExistsFor = async function (authorMapper, name) {
|
||||
const author = await mapAuthorWithDBKey('mapper2author', authorMapper);
|
||||
|
||||
if (name) {
|
||||
// set the name of this author
|
||||
|
@ -80,16 +136,16 @@ exports.createAuthorIfNotExistsFor = async function(authorMapper, name) {
|
|||
* @param {String} mapperkey The database key name for this mapper
|
||||
* @param {String} mapper The mapper
|
||||
*/
|
||||
async function mapAuthorWithDBKey (mapperkey, mapper) {
|
||||
async function mapAuthorWithDBKey(mapperkey, mapper) {
|
||||
// try to map to an author
|
||||
let author = await db.get(mapperkey + ":" + mapper);
|
||||
const author = await db.get(`${mapperkey}:${mapper}`);
|
||||
|
||||
if (author === null) {
|
||||
// there is no author with this mapper, so create one
|
||||
let author = await exports.createAuthor(null);
|
||||
const author = await exports.createAuthor(null);
|
||||
|
||||
// create the token2author relation
|
||||
await db.set(mapperkey + ":" + mapper, author.authorID);
|
||||
await db.set(`${mapperkey}:${mapper}`, author.authorID);
|
||||
|
||||
// return the author
|
||||
return author;
|
||||
|
@ -97,109 +153,109 @@ async function mapAuthorWithDBKey (mapperkey, mapper) {
|
|||
|
||||
// there is an author with this mapper
|
||||
// update the timestamp of this author
|
||||
await db.setSub("globalAuthor:" + author, ["timestamp"], Date.now());
|
||||
await db.setSub(`globalAuthor:${author}`, ['timestamp'], Date.now());
|
||||
|
||||
// return the author
|
||||
return { authorID: author};
|
||||
return {authorID: author};
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function that creates the database entry for an author
|
||||
* @param {String} name The name of the author
|
||||
*/
|
||||
exports.createAuthor = function(name) {
|
||||
exports.createAuthor = function (name) {
|
||||
// create the new author name
|
||||
let author = "a." + randomString(16);
|
||||
const author = `a.${randomString(16)}`;
|
||||
|
||||
// create the globalAuthors db entry
|
||||
let authorObj = {
|
||||
"colorId": Math.floor(Math.random() * (exports.getColorPalette().length)),
|
||||
"name": name,
|
||||
"timestamp": Date.now()
|
||||
const authorObj = {
|
||||
colorId: Math.floor(Math.random() * (exports.getColorPalette().length)),
|
||||
name,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
// set the global author db entry
|
||||
// NB: no await, since we're not waiting for the DB set to finish
|
||||
db.set("globalAuthor:" + author, authorObj);
|
||||
db.set(`globalAuthor:${author}`, authorObj);
|
||||
|
||||
return { authorID: author };
|
||||
}
|
||||
return {authorID: author};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the Author Obj of the author
|
||||
* @param {String} author The id of the author
|
||||
*/
|
||||
exports.getAuthor = function(author) {
|
||||
exports.getAuthor = function (author) {
|
||||
// NB: result is already a Promise
|
||||
return db.get("globalAuthor:" + author);
|
||||
}
|
||||
return db.get(`globalAuthor:${author}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the color Id of the author
|
||||
* @param {String} author The id of the author
|
||||
*/
|
||||
exports.getAuthorColorId = function(author) {
|
||||
return db.getSub("globalAuthor:" + author, ["colorId"]);
|
||||
}
|
||||
exports.getAuthorColorId = function (author) {
|
||||
return db.getSub(`globalAuthor:${author}`, ['colorId']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the color Id of the author
|
||||
* @param {String} author The id of the author
|
||||
* @param {String} colorId The color id of the author
|
||||
*/
|
||||
exports.setAuthorColorId = function(author, colorId) {
|
||||
return db.setSub("globalAuthor:" + author, ["colorId"], colorId);
|
||||
}
|
||||
exports.setAuthorColorId = function (author, colorId) {
|
||||
return db.setSub(`globalAuthor:${author}`, ['colorId'], colorId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the name of the author
|
||||
* @param {String} author The id of the author
|
||||
*/
|
||||
exports.getAuthorName = function(author) {
|
||||
return db.getSub("globalAuthor:" + author, ["name"]);
|
||||
}
|
||||
exports.getAuthorName = function (author) {
|
||||
return db.getSub(`globalAuthor:${author}`, ['name']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the name of the author
|
||||
* @param {String} author The id of the author
|
||||
* @param {String} name The name of the author
|
||||
*/
|
||||
exports.setAuthorName = function(author, name) {
|
||||
return db.setSub("globalAuthor:" + author, ["name"], name);
|
||||
}
|
||||
exports.setAuthorName = function (author, name) {
|
||||
return db.setSub(`globalAuthor:${author}`, ['name'], name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of all pads this author contributed to
|
||||
* @param {String} author The id of the author
|
||||
*/
|
||||
exports.listPadsOfAuthor = async function(authorID) {
|
||||
exports.listPadsOfAuthor = async function (authorID) {
|
||||
/* There are two other places where this array is manipulated:
|
||||
* (1) When the author is added to a pad, the author object is also updated
|
||||
* (2) When a pad is deleted, each author of that pad is also updated
|
||||
*/
|
||||
|
||||
// get the globalAuthor
|
||||
let author = await db.get("globalAuthor:" + authorID);
|
||||
const author = await db.get(`globalAuthor:${authorID}`);
|
||||
|
||||
if (author === null) {
|
||||
// author does not exist
|
||||
throw new customError("authorID does not exist", "apierror");
|
||||
throw new customError('authorID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// everything is fine, return the pad IDs
|
||||
let padIDs = Object.keys(author.padIDs || {});
|
||||
const padIDs = Object.keys(author.padIDs || {});
|
||||
|
||||
return { padIDs };
|
||||
}
|
||||
return {padIDs};
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new pad to the list of contributions
|
||||
* @param {String} author The id of the author
|
||||
* @param {String} padID The id of the pad the author contributes to
|
||||
*/
|
||||
exports.addPad = async function(authorID, padID) {
|
||||
exports.addPad = async function (authorID, padID) {
|
||||
// get the entry
|
||||
let author = await db.get("globalAuthor:" + authorID);
|
||||
const author = await db.get(`globalAuthor:${authorID}`);
|
||||
|
||||
if (author === null) return;
|
||||
|
||||
|
@ -216,22 +272,22 @@ exports.addPad = async function(authorID, padID) {
|
|||
author.padIDs[padID] = 1; // anything, because value is not used
|
||||
|
||||
// save the new element back
|
||||
db.set("globalAuthor:" + authorID, author);
|
||||
}
|
||||
db.set(`globalAuthor:${authorID}`, author);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a pad from the list of contributions
|
||||
* @param {String} author The id of the author
|
||||
* @param {String} padID The id of the pad the author contributes to
|
||||
*/
|
||||
exports.removePad = async function(authorID, padID) {
|
||||
let author = await db.get("globalAuthor:" + authorID);
|
||||
exports.removePad = async function (authorID, padID) {
|
||||
const author = await db.get(`globalAuthor:${authorID}`);
|
||||
|
||||
if (author === null) return;
|
||||
|
||||
if (author.padIDs !== null) {
|
||||
// remove pad from author
|
||||
delete author.padIDs[padID];
|
||||
await db.set('globalAuthor:' + authorID, author);
|
||||
await db.set(`globalAuthor:${authorID}`, author);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var ueberDB = require("ueberdb2");
|
||||
var settings = require("../utils/Settings");
|
||||
var log4js = require('log4js');
|
||||
const util = require("util");
|
||||
const ueberDB = require('ueberdb2');
|
||||
const settings = require('../utils/Settings');
|
||||
const log4js = require('log4js');
|
||||
const util = require('util');
|
||||
|
||||
// set database settings
|
||||
let db = new ueberDB.database(settings.dbType, settings.dbSettings, null, log4js.getLogger("ueberDB"));
|
||||
const db = new ueberDB.database(settings.dbType, settings.dbSettings, null, log4js.getLogger('ueberDB'));
|
||||
|
||||
/**
|
||||
* The UeberDB Object that provides the database functions
|
||||
|
@ -36,32 +36,32 @@ exports.db = null;
|
|||
* Initalizes the database with the settings provided by the settings module
|
||||
* @param {Function} callback
|
||||
*/
|
||||
exports.init = function() {
|
||||
exports.init = function () {
|
||||
// initalize the database async
|
||||
return new Promise((resolve, reject) => {
|
||||
db.init(function(err) {
|
||||
db.init((err) => {
|
||||
if (err) {
|
||||
// there was an error while initializing the database, output it and stop
|
||||
console.error("ERROR: Problem while initalizing the database");
|
||||
console.error('ERROR: Problem while initalizing the database');
|
||||
console.error(err.stack ? err.stack : err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// everything ok, set up Promise-based methods
|
||||
['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove', 'doShutdown'].forEach(fn => {
|
||||
['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove', 'doShutdown'].forEach((fn) => {
|
||||
exports[fn] = util.promisify(db[fn].bind(db));
|
||||
});
|
||||
|
||||
// set up wrappers for get and getSub that can't return "undefined"
|
||||
let get = exports.get;
|
||||
exports.get = async function(key) {
|
||||
let result = await get(key);
|
||||
const get = exports.get;
|
||||
exports.get = async function (key) {
|
||||
const result = await get(key);
|
||||
return (result === undefined) ? null : result;
|
||||
};
|
||||
|
||||
let getSub = exports.getSub;
|
||||
exports.getSub = async function(key, sub) {
|
||||
let result = await getSub(key, sub);
|
||||
const getSub = exports.getSub;
|
||||
exports.getSub = async function (key, sub) {
|
||||
const result = await getSub(key, sub);
|
||||
return (result === undefined) ? null : result;
|
||||
};
|
||||
|
||||
|
@ -70,7 +70,7 @@ exports.init = function() {
|
|||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.shutdown = async (hookName, context) => {
|
||||
await exports.doShutdown();
|
||||
|
|
|
@ -18,52 +18,48 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var customError = require("../utils/customError");
|
||||
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
var db = require("./DB");
|
||||
var padManager = require("./PadManager");
|
||||
var sessionManager = require("./SessionManager");
|
||||
const customError = require('../utils/customError');
|
||||
const randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
const db = require('./DB');
|
||||
const padManager = require('./PadManager');
|
||||
const sessionManager = require('./SessionManager');
|
||||
|
||||
exports.listAllGroups = async function() {
|
||||
let groups = await db.get("groups");
|
||||
exports.listAllGroups = async function () {
|
||||
let groups = await db.get('groups');
|
||||
groups = groups || {};
|
||||
|
||||
let groupIDs = Object.keys(groups);
|
||||
return { groupIDs };
|
||||
}
|
||||
const groupIDs = Object.keys(groups);
|
||||
return {groupIDs};
|
||||
};
|
||||
|
||||
exports.deleteGroup = async function(groupID) {
|
||||
let group = await db.get("group:" + groupID);
|
||||
exports.deleteGroup = async function (groupID) {
|
||||
const group = await db.get(`group:${groupID}`);
|
||||
|
||||
// ensure group exists
|
||||
if (group == null) {
|
||||
// group does not exist
|
||||
throw new customError("groupID does not exist", "apierror");
|
||||
throw new customError('groupID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// iterate through all pads of this group and delete them (in parallel)
|
||||
await Promise.all(Object.keys(group.pads).map(padID => {
|
||||
return padManager.getPad(padID).then(pad => pad.remove());
|
||||
}));
|
||||
await Promise.all(Object.keys(group.pads).map((padID) => padManager.getPad(padID).then((pad) => pad.remove())));
|
||||
|
||||
// iterate through group2sessions and delete all sessions
|
||||
let group2sessions = await db.get("group2sessions:" + groupID);
|
||||
let sessions = group2sessions ? group2sessions.sessionIDs : {};
|
||||
const group2sessions = await db.get(`group2sessions:${groupID}`);
|
||||
const sessions = group2sessions ? group2sessions.sessionIDs : {};
|
||||
|
||||
// loop through all sessions and delete them (in parallel)
|
||||
await Promise.all(Object.keys(sessions).map(session => {
|
||||
return sessionManager.deleteSession(session);
|
||||
}));
|
||||
await Promise.all(Object.keys(sessions).map((session) => sessionManager.deleteSession(session)));
|
||||
|
||||
// remove group and group2sessions entry
|
||||
await db.remove("group2sessions:" + groupID);
|
||||
await db.remove("group:" + groupID);
|
||||
await db.remove(`group2sessions:${groupID}`);
|
||||
await db.remove(`group:${groupID}`);
|
||||
|
||||
// unlist the group
|
||||
let groups = await exports.listAllGroups();
|
||||
groups = groups ? groups.groupIDs : [];
|
||||
|
||||
let index = groups.indexOf(groupID);
|
||||
const index = groups.indexOf(groupID);
|
||||
|
||||
if (index === -1) {
|
||||
// it's not listed
|
||||
|
@ -75,102 +71,102 @@ exports.deleteGroup = async function(groupID) {
|
|||
groups.splice(index, 1);
|
||||
|
||||
// regenerate group list
|
||||
var newGroups = {};
|
||||
groups.forEach(group => newGroups[group] = 1);
|
||||
await db.set("groups", newGroups);
|
||||
}
|
||||
const newGroups = {};
|
||||
groups.forEach((group) => newGroups[group] = 1);
|
||||
await db.set('groups', newGroups);
|
||||
};
|
||||
|
||||
exports.doesGroupExist = async function(groupID) {
|
||||
exports.doesGroupExist = async function (groupID) {
|
||||
// try to get the group entry
|
||||
let group = await db.get("group:" + groupID);
|
||||
const group = await db.get(`group:${groupID}`);
|
||||
|
||||
return (group != null);
|
||||
}
|
||||
};
|
||||
|
||||
exports.createGroup = async function() {
|
||||
exports.createGroup = async function () {
|
||||
// search for non existing groupID
|
||||
var groupID = "g." + randomString(16);
|
||||
const groupID = `g.${randomString(16)}`;
|
||||
|
||||
// create the group
|
||||
await db.set("group:" + groupID, {pads: {}});
|
||||
await db.set(`group:${groupID}`, {pads: {}});
|
||||
|
||||
// list the group
|
||||
let groups = await exports.listAllGroups();
|
||||
groups = groups? groups.groupIDs : [];
|
||||
groups = groups ? groups.groupIDs : [];
|
||||
groups.push(groupID);
|
||||
|
||||
// regenerate group list
|
||||
var newGroups = {};
|
||||
groups.forEach(group => newGroups[group] = 1);
|
||||
await db.set("groups", newGroups);
|
||||
const newGroups = {};
|
||||
groups.forEach((group) => newGroups[group] = 1);
|
||||
await db.set('groups', newGroups);
|
||||
|
||||
return { groupID };
|
||||
}
|
||||
return {groupID};
|
||||
};
|
||||
|
||||
exports.createGroupIfNotExistsFor = async function(groupMapper) {
|
||||
exports.createGroupIfNotExistsFor = async function (groupMapper) {
|
||||
// ensure mapper is optional
|
||||
if (typeof groupMapper !== "string") {
|
||||
throw new customError("groupMapper is not a string", "apierror");
|
||||
if (typeof groupMapper !== 'string') {
|
||||
throw new customError('groupMapper is not a string', 'apierror');
|
||||
}
|
||||
|
||||
// try to get a group for this mapper
|
||||
let groupID = await db.get("mapper2group:" + groupMapper);
|
||||
const groupID = await db.get(`mapper2group:${groupMapper}`);
|
||||
|
||||
if (groupID) {
|
||||
// there is a group for this mapper
|
||||
let exists = await exports.doesGroupExist(groupID);
|
||||
const exists = await exports.doesGroupExist(groupID);
|
||||
|
||||
if (exists) return { groupID };
|
||||
if (exists) return {groupID};
|
||||
}
|
||||
|
||||
// hah, the returned group doesn't exist, let's create one
|
||||
let result = await exports.createGroup();
|
||||
const result = await exports.createGroup();
|
||||
|
||||
// create the mapper entry for this group
|
||||
await db.set("mapper2group:" + groupMapper, result.groupID);
|
||||
await db.set(`mapper2group:${groupMapper}`, result.groupID);
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
exports.createGroupPad = async function(groupID, padName, text) {
|
||||
exports.createGroupPad = async function (groupID, padName, text) {
|
||||
// create the padID
|
||||
let padID = groupID + "$" + padName;
|
||||
const padID = `${groupID}$${padName}`;
|
||||
|
||||
// ensure group exists
|
||||
let groupExists = await exports.doesGroupExist(groupID);
|
||||
const groupExists = await exports.doesGroupExist(groupID);
|
||||
|
||||
if (!groupExists) {
|
||||
throw new customError("groupID does not exist", "apierror");
|
||||
throw new customError('groupID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// ensure pad doesn't exist already
|
||||
let padExists = await padManager.doesPadExists(padID);
|
||||
const padExists = await padManager.doesPadExists(padID);
|
||||
|
||||
if (padExists) {
|
||||
// pad exists already
|
||||
throw new customError("padName does already exist", "apierror");
|
||||
throw new customError('padName does already exist', 'apierror');
|
||||
}
|
||||
|
||||
// create the pad
|
||||
await padManager.getPad(padID, text);
|
||||
|
||||
//create an entry in the group for this pad
|
||||
await db.setSub("group:" + groupID, ["pads", padID], 1);
|
||||
// create an entry in the group for this pad
|
||||
await db.setSub(`group:${groupID}`, ['pads', padID], 1);
|
||||
|
||||
return { padID };
|
||||
}
|
||||
return {padID};
|
||||
};
|
||||
|
||||
exports.listPads = async function(groupID) {
|
||||
let exists = await exports.doesGroupExist(groupID);
|
||||
exports.listPads = async function (groupID) {
|
||||
const exists = await exports.doesGroupExist(groupID);
|
||||
|
||||
// ensure the group exists
|
||||
if (!exists) {
|
||||
throw new customError("groupID does not exist", "apierror");
|
||||
throw new customError('groupID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// group exists, let's get the pads
|
||||
let result = await db.getSub("group:" + groupID, ["pads"]);
|
||||
let padIDs = Object.keys(result);
|
||||
const result = await db.getSub(`group:${groupID}`, ['pads']);
|
||||
const padIDs = Object.keys(result);
|
||||
|
||||
return { padIDs };
|
||||
}
|
||||
return {padIDs};
|
||||
};
|
||||
|
|
|
@ -3,36 +3,36 @@
|
|||
*/
|
||||
|
||||
|
||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");
|
||||
var db = require("./DB");
|
||||
var settings = require('../utils/Settings');
|
||||
var authorManager = require("./AuthorManager");
|
||||
var padManager = require("./PadManager");
|
||||
var padMessageHandler = require("../handler/PadMessageHandler");
|
||||
var groupManager = require("./GroupManager");
|
||||
var customError = require("../utils/customError");
|
||||
var readOnlyManager = require("./ReadOnlyManager");
|
||||
var crypto = require("crypto");
|
||||
var randomString = require("../utils/randomstring");
|
||||
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
var promises = require('../utils/promises')
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
const AttributePool = require('ep_etherpad-lite/static/js/AttributePool');
|
||||
const db = require('./DB');
|
||||
const settings = require('../utils/Settings');
|
||||
const authorManager = require('./AuthorManager');
|
||||
const padManager = require('./PadManager');
|
||||
const padMessageHandler = require('../handler/PadMessageHandler');
|
||||
const groupManager = require('./GroupManager');
|
||||
const customError = require('../utils/customError');
|
||||
const readOnlyManager = require('./ReadOnlyManager');
|
||||
const crypto = require('crypto');
|
||||
const randomString = require('../utils/randomstring');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
const promises = require('../utils/promises');
|
||||
|
||||
// serialization/deserialization attributes
|
||||
var attributeBlackList = ["id"];
|
||||
var jsonableList = ["pool"];
|
||||
const attributeBlackList = ['id'];
|
||||
const jsonableList = ['pool'];
|
||||
|
||||
/**
|
||||
* Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces
|
||||
* @param txt
|
||||
*/
|
||||
exports.cleanText = function (txt) {
|
||||
return txt.replace(/\r\n/g,'\n').replace(/\r/g,'\n').replace(/\t/g, ' ').replace(/\xa0/g, ' ');
|
||||
return txt.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\t/g, ' ').replace(/\xa0/g, ' ');
|
||||
};
|
||||
|
||||
|
||||
let Pad = function Pad(id) {
|
||||
this.atext = Changeset.makeAText("\n");
|
||||
const Pad = function Pad(id) {
|
||||
this.atext = Changeset.makeAText('\n');
|
||||
this.pool = new AttributePool();
|
||||
this.head = -1;
|
||||
this.chatHead = -1;
|
||||
|
@ -56,13 +56,11 @@ Pad.prototype.getSavedRevisionsNumber = function getSavedRevisionsNumber() {
|
|||
};
|
||||
|
||||
Pad.prototype.getSavedRevisionsList = function getSavedRevisionsList() {
|
||||
var savedRev = new Array();
|
||||
for (var rev in this.savedRevisions) {
|
||||
const savedRev = new Array();
|
||||
for (const rev in this.savedRevisions) {
|
||||
savedRev.push(this.savedRevisions[rev].revNum);
|
||||
}
|
||||
savedRev.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
savedRev.sort((a, b) => a - b);
|
||||
return savedRev;
|
||||
};
|
||||
|
||||
|
@ -75,12 +73,12 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
|
|||
author = '';
|
||||
}
|
||||
|
||||
var newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool);
|
||||
const newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool);
|
||||
Changeset.copyAText(newAText, this.atext);
|
||||
|
||||
var newRev = ++this.head;
|
||||
const newRev = ++this.head;
|
||||
|
||||
var newRevData = {};
|
||||
const newRevData = {};
|
||||
newRevData.changeset = aChangeset;
|
||||
newRevData.meta = {};
|
||||
newRevData.meta.author = author;
|
||||
|
@ -97,7 +95,7 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
|
|||
}
|
||||
|
||||
const p = [
|
||||
db.set('pad:' + this.id + ':revs:' + newRev, newRevData),
|
||||
db.set(`pad:${this.id}:revs:${newRev}`, newRevData),
|
||||
this.saveToDatabase(),
|
||||
];
|
||||
|
||||
|
@ -107,9 +105,9 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
|
|||
}
|
||||
|
||||
if (this.head == 0) {
|
||||
hooks.callAll("padCreate", {'pad':this, 'author': author});
|
||||
hooks.callAll('padCreate', {pad: this, author});
|
||||
} else {
|
||||
hooks.callAll("padUpdate", {'pad':this, 'author': author, 'revs': newRev, 'changeset': aChangeset});
|
||||
hooks.callAll('padUpdate', {pad: this, author, revs: newRev, changeset: aChangeset});
|
||||
}
|
||||
|
||||
await Promise.all(p);
|
||||
|
@ -117,10 +115,10 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
|
|||
|
||||
// save all attributes to the database
|
||||
Pad.prototype.saveToDatabase = async function saveToDatabase() {
|
||||
var dbObject = {};
|
||||
const dbObject = {};
|
||||
|
||||
for (var attr in this) {
|
||||
if (typeof this[attr] === "function") continue;
|
||||
for (const attr in this) {
|
||||
if (typeof this[attr] === 'function') continue;
|
||||
if (attributeBlackList.indexOf(attr) !== -1) continue;
|
||||
|
||||
dbObject[attr] = this[attr];
|
||||
|
@ -130,32 +128,32 @@ Pad.prototype.saveToDatabase = async function saveToDatabase() {
|
|||
}
|
||||
}
|
||||
|
||||
await db.set('pad:' + this.id, dbObject);
|
||||
}
|
||||
await db.set(`pad:${this.id}`, dbObject);
|
||||
};
|
||||
|
||||
// get time of last edit (changeset application)
|
||||
Pad.prototype.getLastEdit = function getLastEdit() {
|
||||
var revNum = this.getHeadRevisionNumber();
|
||||
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "timestamp"]);
|
||||
}
|
||||
const revNum = this.getHeadRevisionNumber();
|
||||
return db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'timestamp']);
|
||||
};
|
||||
|
||||
Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum) {
|
||||
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["changeset"]);
|
||||
}
|
||||
return db.getSub(`pad:${this.id}:revs:${revNum}`, ['changeset']);
|
||||
};
|
||||
|
||||
Pad.prototype.getRevisionAuthor = function getRevisionAuthor(revNum) {
|
||||
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "author"]);
|
||||
}
|
||||
return db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'author']);
|
||||
};
|
||||
|
||||
Pad.prototype.getRevisionDate = function getRevisionDate(revNum) {
|
||||
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "timestamp"]);
|
||||
}
|
||||
return db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'timestamp']);
|
||||
};
|
||||
|
||||
Pad.prototype.getAllAuthors = function getAllAuthors() {
|
||||
var authors = [];
|
||||
const authors = [];
|
||||
|
||||
for(var key in this.pool.numToAttrib) {
|
||||
if (this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "") {
|
||||
for (const key in this.pool.numToAttrib) {
|
||||
if (this.pool.numToAttrib[key][0] == 'author' && this.pool.numToAttrib[key][1] != '') {
|
||||
authors.push(this.pool.numToAttrib[key][1]);
|
||||
}
|
||||
}
|
||||
|
@ -164,63 +162,59 @@ Pad.prototype.getAllAuthors = function getAllAuthors() {
|
|||
};
|
||||
|
||||
Pad.prototype.getInternalRevisionAText = async function getInternalRevisionAText(targetRev) {
|
||||
let keyRev = this.getKeyRevisionNumber(targetRev);
|
||||
const keyRev = this.getKeyRevisionNumber(targetRev);
|
||||
|
||||
// find out which changesets are needed
|
||||
let neededChangesets = [];
|
||||
for (let curRev = keyRev; curRev < targetRev; ) {
|
||||
const neededChangesets = [];
|
||||
for (let curRev = keyRev; curRev < targetRev;) {
|
||||
neededChangesets.push(++curRev);
|
||||
}
|
||||
|
||||
// get all needed data out of the database
|
||||
|
||||
// start to get the atext of the key revision
|
||||
let p_atext = db.getSub("pad:" + this.id + ":revs:" + keyRev, ["meta", "atext"]);
|
||||
const p_atext = db.getSub(`pad:${this.id}:revs:${keyRev}`, ['meta', 'atext']);
|
||||
|
||||
// get all needed changesets
|
||||
let changesets = [];
|
||||
await Promise.all(neededChangesets.map(item => {
|
||||
return this.getRevisionChangeset(item).then(changeset => {
|
||||
changesets[item] = changeset;
|
||||
});
|
||||
}));
|
||||
const changesets = [];
|
||||
await Promise.all(neededChangesets.map((item) => this.getRevisionChangeset(item).then((changeset) => {
|
||||
changesets[item] = changeset;
|
||||
})));
|
||||
|
||||
// we should have the atext by now
|
||||
let atext = await p_atext;
|
||||
atext = Changeset.cloneAText(atext);
|
||||
|
||||
// apply all changesets to the key changeset
|
||||
let apool = this.apool();
|
||||
for (let curRev = keyRev; curRev < targetRev; ) {
|
||||
let cs = changesets[++curRev];
|
||||
const apool = this.apool();
|
||||
for (let curRev = keyRev; curRev < targetRev;) {
|
||||
const cs = changesets[++curRev];
|
||||
atext = Changeset.applyToAText(cs, atext, apool);
|
||||
}
|
||||
|
||||
return atext;
|
||||
}
|
||||
};
|
||||
|
||||
Pad.prototype.getRevision = function getRevisionChangeset(revNum) {
|
||||
return db.get("pad:" + this.id + ":revs:" + revNum);
|
||||
}
|
||||
return db.get(`pad:${this.id}:revs:${revNum}`);
|
||||
};
|
||||
|
||||
Pad.prototype.getAllAuthorColors = async function getAllAuthorColors() {
|
||||
let authors = this.getAllAuthors();
|
||||
let returnTable = {};
|
||||
let colorPalette = authorManager.getColorPalette();
|
||||
const authors = this.getAllAuthors();
|
||||
const returnTable = {};
|
||||
const colorPalette = authorManager.getColorPalette();
|
||||
|
||||
await Promise.all(authors.map(author => {
|
||||
return authorManager.getAuthorColorId(author).then(colorId => {
|
||||
// colorId might be a hex color or an number out of the palette
|
||||
returnTable[author] = colorPalette[colorId] || colorId;
|
||||
});
|
||||
}));
|
||||
await Promise.all(authors.map((author) => authorManager.getAuthorColorId(author).then((colorId) => {
|
||||
// colorId might be a hex color or an number out of the palette
|
||||
returnTable[author] = colorPalette[colorId] || colorId;
|
||||
})));
|
||||
|
||||
return returnTable;
|
||||
}
|
||||
};
|
||||
|
||||
Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, endRev) {
|
||||
startRev = parseInt(startRev, 10);
|
||||
var head = this.getHeadRevisionNumber();
|
||||
const head = this.getHeadRevisionNumber();
|
||||
endRev = endRev ? parseInt(endRev, 10) : head;
|
||||
|
||||
if (isNaN(startRev) || startRev < 0 || startRev > head) {
|
||||
|
@ -234,7 +228,7 @@ Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, e
|
|||
}
|
||||
|
||||
if (startRev !== null && endRev !== null) {
|
||||
return { startRev: startRev , endRev: endRev }
|
||||
return {startRev, endRev};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
@ -251,16 +245,16 @@ Pad.prototype.setText = async function setText(newText) {
|
|||
// clean the new text
|
||||
newText = exports.cleanText(newText);
|
||||
|
||||
var oldText = this.text();
|
||||
const oldText = this.text();
|
||||
|
||||
// create the changeset
|
||||
// We want to ensure the pad still ends with a \n, but otherwise keep
|
||||
// getText() and setText() consistent.
|
||||
var changeset;
|
||||
let changeset;
|
||||
if (newText[newText.length - 1] == '\n') {
|
||||
changeset = Changeset.makeSplice(oldText, 0, oldText.length, newText);
|
||||
} else {
|
||||
changeset = Changeset.makeSplice(oldText, 0, oldText.length-1, newText);
|
||||
changeset = Changeset.makeSplice(oldText, 0, oldText.length - 1, newText);
|
||||
}
|
||||
|
||||
// append the changeset
|
||||
|
@ -271,10 +265,10 @@ Pad.prototype.appendText = async function appendText(newText) {
|
|||
// clean the new text
|
||||
newText = exports.cleanText(newText);
|
||||
|
||||
var oldText = this.text();
|
||||
const oldText = this.text();
|
||||
|
||||
// create the changeset
|
||||
var changeset = Changeset.makeSplice(oldText, oldText.length, 0, newText);
|
||||
const changeset = Changeset.makeSplice(oldText, oldText.length, 0, newText);
|
||||
|
||||
// append the changeset
|
||||
await this.appendRevision(changeset);
|
||||
|
@ -284,14 +278,14 @@ Pad.prototype.appendChatMessage = async function appendChatMessage(text, userId,
|
|||
this.chatHead++;
|
||||
// save the chat entry in the database
|
||||
await Promise.all([
|
||||
db.set('pad:' + this.id + ':chat:' + this.chatHead, {text, userId, time}),
|
||||
db.set(`pad:${this.id}:chat:${this.chatHead}`, {text, userId, time}),
|
||||
this.saveToDatabase(),
|
||||
]);
|
||||
};
|
||||
|
||||
Pad.prototype.getChatMessage = async function getChatMessage(entryNum) {
|
||||
// get the chat entry
|
||||
let entry = await db.get("pad:" + this.id + ":chat:" + entryNum);
|
||||
const entry = await db.get(`pad:${this.id}:chat:${entryNum}`);
|
||||
|
||||
// get the authorName if the entry exists
|
||||
if (entry != null) {
|
||||
|
@ -302,49 +296,45 @@ Pad.prototype.getChatMessage = async function getChatMessage(entryNum) {
|
|||
};
|
||||
|
||||
Pad.prototype.getChatMessages = async function getChatMessages(start, end) {
|
||||
|
||||
// collect the numbers of chat entries and in which order we need them
|
||||
let neededEntries = [];
|
||||
const neededEntries = [];
|
||||
for (let order = 0, entryNum = start; entryNum <= end; ++order, ++entryNum) {
|
||||
neededEntries.push({ entryNum, order });
|
||||
neededEntries.push({entryNum, order});
|
||||
}
|
||||
|
||||
// get all entries out of the database
|
||||
let entries = [];
|
||||
await Promise.all(neededEntries.map(entryObject => {
|
||||
return this.getChatMessage(entryObject.entryNum).then(entry => {
|
||||
entries[entryObject.order] = entry;
|
||||
});
|
||||
}));
|
||||
const entries = [];
|
||||
await Promise.all(neededEntries.map((entryObject) => this.getChatMessage(entryObject.entryNum).then((entry) => {
|
||||
entries[entryObject.order] = entry;
|
||||
})));
|
||||
|
||||
// sort out broken chat entries
|
||||
// it looks like in happened in the past that the chat head was
|
||||
// incremented, but the chat message wasn't added
|
||||
let cleanedEntries = entries.filter(entry => {
|
||||
let pass = (entry != null);
|
||||
const cleanedEntries = entries.filter((entry) => {
|
||||
const pass = (entry != null);
|
||||
if (!pass) {
|
||||
console.warn("WARNING: Found broken chat entry in pad " + this.id);
|
||||
console.warn(`WARNING: Found broken chat entry in pad ${this.id}`);
|
||||
}
|
||||
return pass;
|
||||
});
|
||||
|
||||
return cleanedEntries;
|
||||
}
|
||||
};
|
||||
|
||||
Pad.prototype.init = async function init(text) {
|
||||
|
||||
// replace text with default text if text isn't set
|
||||
if (text == null) {
|
||||
text = settings.defaultPadText;
|
||||
}
|
||||
|
||||
// try to load the pad
|
||||
let value = await db.get("pad:" + this.id);
|
||||
const value = await db.get(`pad:${this.id}`);
|
||||
|
||||
// if this pad exists, load it
|
||||
if (value != null) {
|
||||
// copy all attr. To a transfrom via fromJsonable if necassary
|
||||
for (var attr in value) {
|
||||
for (const attr in value) {
|
||||
if (jsonableList.indexOf(attr) !== -1) {
|
||||
this[attr] = this[attr].fromJsonable(value[attr]);
|
||||
} else {
|
||||
|
@ -353,17 +343,17 @@ Pad.prototype.init = async function init(text) {
|
|||
}
|
||||
} else {
|
||||
// this pad doesn't exist, so create it
|
||||
let firstChangeset = Changeset.makeSplice("\n", 0, 0, exports.cleanText(text));
|
||||
const firstChangeset = Changeset.makeSplice('\n', 0, 0, exports.cleanText(text));
|
||||
|
||||
await this.appendRevision(firstChangeset, '');
|
||||
}
|
||||
|
||||
hooks.callAll("padLoad", { 'pad': this });
|
||||
}
|
||||
hooks.callAll('padLoad', {pad: this});
|
||||
};
|
||||
|
||||
Pad.prototype.copy = async function copy(destinationID, force) {
|
||||
let destGroupID;
|
||||
let sourceID = this.id;
|
||||
const sourceID = this.id;
|
||||
|
||||
// Kick everyone from this pad.
|
||||
// This was commented due to https://github.com/ether/etherpad-lite/issues/3183.
|
||||
|
@ -380,32 +370,28 @@ Pad.prototype.copy = async function copy(destinationID, force) {
|
|||
|
||||
// if force is true and already exists a Pad with the same id, remove that Pad
|
||||
await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force);
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// copy the 'pad' entry
|
||||
let pad = await db.get("pad:" + sourceID);
|
||||
db.set("pad:" + destinationID, pad);
|
||||
const pad = await db.get(`pad:${sourceID}`);
|
||||
db.set(`pad:${destinationID}`, pad);
|
||||
|
||||
// copy all relations in parallel
|
||||
let promises = [];
|
||||
const promises = [];
|
||||
|
||||
// copy all chat messages
|
||||
let chatHead = this.chatHead;
|
||||
const chatHead = this.chatHead;
|
||||
for (let i = 0; i <= chatHead; ++i) {
|
||||
let p = db.get("pad:" + sourceID + ":chat:" + i).then(chat => {
|
||||
return db.set("pad:" + destinationID + ":chat:" + i, chat);
|
||||
});
|
||||
const p = db.get(`pad:${sourceID}:chat:${i}`).then((chat) => db.set(`pad:${destinationID}:chat:${i}`, chat));
|
||||
promises.push(p);
|
||||
}
|
||||
|
||||
// copy all revisions
|
||||
let revHead = this.head;
|
||||
const revHead = this.head;
|
||||
for (let i = 0; i <= revHead; ++i) {
|
||||
let p = db.get("pad:" + sourceID + ":revs:" + i).then(rev => {
|
||||
return db.set("pad:" + destinationID + ":revs:" + i, rev);
|
||||
});
|
||||
const p = db.get(`pad:${sourceID}:revs:${i}`).then((rev) => db.set(`pad:${destinationID}:revs:${i}`, rev));
|
||||
promises.push(p);
|
||||
}
|
||||
|
||||
|
@ -416,70 +402,69 @@ Pad.prototype.copy = async function copy(destinationID, force) {
|
|||
|
||||
// Group pad? Add it to the group's list
|
||||
if (destGroupID) {
|
||||
await db.setSub("group:" + destGroupID, ["pads", destinationID], 1);
|
||||
await db.setSub(`group:${destGroupID}`, ['pads', destinationID], 1);
|
||||
}
|
||||
|
||||
// delay still necessary?
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
// Initialize the new pad (will update the listAllPads cache)
|
||||
await padManager.getPad(destinationID, null); // this runs too early.
|
||||
|
||||
// let the plugins know the pad was copied
|
||||
hooks.callAll('padCopy', { 'originalPad': this, 'destinationID': destinationID });
|
||||
hooks.callAll('padCopy', {originalPad: this, destinationID});
|
||||
|
||||
return { padID: destinationID };
|
||||
}
|
||||
return {padID: destinationID};
|
||||
};
|
||||
|
||||
Pad.prototype.checkIfGroupExistAndReturnIt = async function checkIfGroupExistAndReturnIt(destinationID) {
|
||||
let destGroupID = false;
|
||||
|
||||
if (destinationID.indexOf("$") >= 0) {
|
||||
|
||||
destGroupID = destinationID.split("$")[0]
|
||||
let groupExists = await groupManager.doesGroupExist(destGroupID);
|
||||
if (destinationID.indexOf('$') >= 0) {
|
||||
destGroupID = destinationID.split('$')[0];
|
||||
const groupExists = await groupManager.doesGroupExist(destGroupID);
|
||||
|
||||
// group does not exist
|
||||
if (!groupExists) {
|
||||
throw new customError("groupID does not exist for destinationID", "apierror");
|
||||
throw new customError('groupID does not exist for destinationID', 'apierror');
|
||||
}
|
||||
}
|
||||
return destGroupID;
|
||||
}
|
||||
};
|
||||
|
||||
Pad.prototype.removePadIfForceIsTrueAndAlreadyExist = async function removePadIfForceIsTrueAndAlreadyExist(destinationID, force) {
|
||||
// if the pad exists, we should abort, unless forced.
|
||||
let exists = await padManager.doesPadExist(destinationID);
|
||||
const exists = await padManager.doesPadExist(destinationID);
|
||||
|
||||
// allow force to be a string
|
||||
if (typeof force === "string") {
|
||||
force = (force.toLowerCase() === "true");
|
||||
if (typeof force === 'string') {
|
||||
force = (force.toLowerCase() === 'true');
|
||||
} else {
|
||||
force = !!force;
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
if (!force) {
|
||||
console.error("erroring out without force");
|
||||
throw new customError("destinationID already exists", "apierror");
|
||||
console.error('erroring out without force');
|
||||
throw new customError('destinationID already exists', 'apierror');
|
||||
}
|
||||
|
||||
// exists and forcing
|
||||
let pad = await padManager.getPad(destinationID);
|
||||
const pad = await padManager.getPad(destinationID);
|
||||
await pad.remove();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Pad.prototype.copyAuthorInfoToDestinationPad = function copyAuthorInfoToDestinationPad(destinationID) {
|
||||
// add the new sourcePad to all authors who contributed to the old one
|
||||
this.getAllAuthors().forEach(authorID => {
|
||||
this.getAllAuthors().forEach((authorID) => {
|
||||
authorManager.addPad(authorID, destinationID);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Pad.prototype.copyPadWithoutHistory = async function copyPadWithoutHistory(destinationID, force) {
|
||||
let destGroupID;
|
||||
let sourceID = this.id;
|
||||
const sourceID = this.id;
|
||||
|
||||
// flush the source pad
|
||||
this.saveToDatabase();
|
||||
|
@ -490,53 +475,53 @@ Pad.prototype.copyPadWithoutHistory = async function copyPadWithoutHistory(desti
|
|||
|
||||
// if force is true and already exists a Pad with the same id, remove that Pad
|
||||
await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force);
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
let sourcePad = await padManager.getPad(sourceID);
|
||||
const sourcePad = await padManager.getPad(sourceID);
|
||||
|
||||
// add the new sourcePad to all authors who contributed to the old one
|
||||
this.copyAuthorInfoToDestinationPad(destinationID);
|
||||
|
||||
// Group pad? Add it to the group's list
|
||||
if (destGroupID) {
|
||||
await db.setSub("group:" + destGroupID, ["pads", destinationID], 1);
|
||||
await db.setSub(`group:${destGroupID}`, ['pads', destinationID], 1);
|
||||
}
|
||||
|
||||
// initialize the pad with a new line to avoid getting the defaultText
|
||||
let newPad = await padManager.getPad(destinationID, '\n');
|
||||
const newPad = await padManager.getPad(destinationID, '\n');
|
||||
|
||||
let oldAText = this.atext;
|
||||
let newPool = newPad.pool;
|
||||
const oldAText = this.atext;
|
||||
const newPool = newPad.pool;
|
||||
newPool.fromJsonable(sourcePad.pool.toJsonable()); // copy that sourceId pool to the new pad
|
||||
|
||||
// based on Changeset.makeSplice
|
||||
let assem = Changeset.smartOpAssembler();
|
||||
const assem = Changeset.smartOpAssembler();
|
||||
assem.appendOpWithText('=', '');
|
||||
Changeset.appendATextToAssembler(oldAText, assem);
|
||||
assem.endDocument();
|
||||
|
||||
// although we have instantiated the newPad with '\n', an additional '\n' is
|
||||
// added internally, so the pad text on the revision 0 is "\n\n"
|
||||
let oldLength = 2;
|
||||
const oldLength = 2;
|
||||
|
||||
let newLength = assem.getLengthChange();
|
||||
let newText = oldAText.text;
|
||||
const newLength = assem.getLengthChange();
|
||||
const newText = oldAText.text;
|
||||
|
||||
// create a changeset that removes the previous text and add the newText with
|
||||
// all atributes present on the source pad
|
||||
let changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
|
||||
const changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
|
||||
newPad.appendRevision(changeset);
|
||||
|
||||
hooks.callAll('padCopy', { 'originalPad': this, 'destinationID': destinationID });
|
||||
hooks.callAll('padCopy', {originalPad: this, destinationID});
|
||||
|
||||
return { padID: destinationID };
|
||||
}
|
||||
return {padID: destinationID};
|
||||
};
|
||||
|
||||
|
||||
Pad.prototype.remove = async function remove() {
|
||||
var padID = this.id;
|
||||
const padID = this.id;
|
||||
const p = [];
|
||||
|
||||
// kick everyone from this pad
|
||||
|
@ -548,45 +533,44 @@ Pad.prototype.remove = async function remove() {
|
|||
// run to completion
|
||||
|
||||
// is it a group pad? -> delete the entry of this pad in the group
|
||||
if (padID.indexOf("$") >= 0) {
|
||||
|
||||
if (padID.indexOf('$') >= 0) {
|
||||
// it is a group pad
|
||||
let groupID = padID.substring(0, padID.indexOf("$"));
|
||||
let group = await db.get("group:" + groupID);
|
||||
const groupID = padID.substring(0, padID.indexOf('$'));
|
||||
const group = await db.get(`group:${groupID}`);
|
||||
|
||||
// remove the pad entry
|
||||
delete group.pads[padID];
|
||||
|
||||
// set the new value
|
||||
p.push(db.set('group:' + groupID, group));
|
||||
p.push(db.set(`group:${groupID}`, group));
|
||||
}
|
||||
|
||||
// remove the readonly entries
|
||||
p.push(readOnlyManager.getReadOnlyId(padID).then(async (readonlyID) => {
|
||||
await db.remove('readonly2pad:' + readonlyID);
|
||||
await db.remove(`readonly2pad:${readonlyID}`);
|
||||
}));
|
||||
p.push(db.remove('pad2readonly:' + padID));
|
||||
p.push(db.remove(`pad2readonly:${padID}`));
|
||||
|
||||
// delete all chat messages
|
||||
p.push(promises.timesLimit(this.chatHead + 1, 500, async (i) => {
|
||||
await db.remove('pad:' + padID + ':chat:' + i, null);
|
||||
await db.remove(`pad:${padID}:chat:${i}`, null);
|
||||
}));
|
||||
|
||||
// delete all revisions
|
||||
p.push(promises.timesLimit(this.head + 1, 500, async (i) => {
|
||||
await db.remove('pad:' + padID + ':revs:' + i, null);
|
||||
await db.remove(`pad:${padID}:revs:${i}`, null);
|
||||
}));
|
||||
|
||||
// remove pad from all authors who contributed
|
||||
this.getAllAuthors().forEach(authorID => {
|
||||
this.getAllAuthors().forEach((authorID) => {
|
||||
p.push(authorManager.removePad(authorID, padID));
|
||||
});
|
||||
|
||||
// delete the pad entry and delete pad from padManager
|
||||
p.push(padManager.removePad(padID));
|
||||
hooks.callAll("padRemove", { padID });
|
||||
hooks.callAll('padRemove', {padID});
|
||||
await Promise.all(p);
|
||||
}
|
||||
};
|
||||
|
||||
// set in db
|
||||
Pad.prototype.setPublicStatus = async function setPublicStatus(publicStatus) {
|
||||
|
@ -596,17 +580,17 @@ Pad.prototype.setPublicStatus = async function setPublicStatus(publicStatus) {
|
|||
|
||||
Pad.prototype.addSavedRevision = async function addSavedRevision(revNum, savedById, label) {
|
||||
// if this revision is already saved, return silently
|
||||
for (var i in this.savedRevisions) {
|
||||
for (const i in this.savedRevisions) {
|
||||
if (this.savedRevisions[i] && this.savedRevisions[i].revNum === revNum) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// build the saved revision object
|
||||
var savedRevision = {};
|
||||
const savedRevision = {};
|
||||
savedRevision.revNum = revNum;
|
||||
savedRevision.savedById = savedById;
|
||||
savedRevision.label = label || "Revision " + revNum;
|
||||
savedRevision.label = label || `Revision ${revNum}`;
|
||||
savedRevision.timestamp = Date.now();
|
||||
savedRevision.id = randomString(10);
|
||||
|
||||
|
@ -618,4 +602,3 @@ Pad.prototype.addSavedRevision = async function addSavedRevision(revNum, savedBy
|
|||
Pad.prototype.getSavedRevisions = function getSavedRevisions() {
|
||||
return this.savedRevisions;
|
||||
};
|
||||
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var customError = require("../utils/customError");
|
||||
var Pad = require("../db/Pad").Pad;
|
||||
var db = require("./DB");
|
||||
const customError = require('../utils/customError');
|
||||
const Pad = require('../db/Pad').Pad;
|
||||
const db = require('./DB');
|
||||
|
||||
/**
|
||||
* A cache of all loaded Pads.
|
||||
|
@ -33,14 +33,14 @@ var db = require("./DB");
|
|||
* If this is needed in other places, it would be wise to make this a prototype
|
||||
* that's defined somewhere more sensible.
|
||||
*/
|
||||
var globalPads = {
|
||||
get: function(name) { return this[':'+name]; },
|
||||
set: function(name, value) {
|
||||
this[':'+name] = value;
|
||||
},
|
||||
remove: function(name) {
|
||||
delete this[':'+name];
|
||||
}
|
||||
const globalPads = {
|
||||
get(name) { return this[`:${name}`]; },
|
||||
set(name, value) {
|
||||
this[`:${name}`] = value;
|
||||
},
|
||||
remove(name) {
|
||||
delete this[`:${name}`];
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -48,24 +48,24 @@ var globalPads = {
|
|||
*
|
||||
* Updated without db access as new pads are created/old ones removed.
|
||||
*/
|
||||
let padList = {
|
||||
const padList = {
|
||||
list: new Set(),
|
||||
cachedList: undefined,
|
||||
initiated: false,
|
||||
init: async function() {
|
||||
let dbData = await db.findKeys("pad:*", "*:*:*");
|
||||
async init() {
|
||||
const dbData = await db.findKeys('pad:*', '*:*:*');
|
||||
|
||||
if (dbData != null) {
|
||||
this.initiated = true;
|
||||
|
||||
for (let val of dbData) {
|
||||
this.addPad(val.replace(/^pad:/,""), false);
|
||||
for (const val of dbData) {
|
||||
this.addPad(val.replace(/^pad:/, ''), false);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
load: async function() {
|
||||
async load() {
|
||||
if (!this.initiated) {
|
||||
return this.init();
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ let padList = {
|
|||
/**
|
||||
* Returns all pads in alphabetical order as array.
|
||||
*/
|
||||
getPads: async function() {
|
||||
async getPads() {
|
||||
await this.load();
|
||||
|
||||
if (!this.cachedList) {
|
||||
|
@ -84,7 +84,7 @@ let padList = {
|
|||
|
||||
return this.cachedList;
|
||||
},
|
||||
addPad: function(name) {
|
||||
addPad(name) {
|
||||
if (!this.initiated) return;
|
||||
|
||||
if (!this.list.has(name)) {
|
||||
|
@ -92,14 +92,14 @@ let padList = {
|
|||
this.cachedList = undefined;
|
||||
}
|
||||
},
|
||||
removePad: function(name) {
|
||||
removePad(name) {
|
||||
if (!this.initiated) return;
|
||||
|
||||
if (this.list.has(name)) {
|
||||
this.list.delete(name);
|
||||
this.cachedList = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// initialises the all-knowing data structure
|
||||
|
@ -109,22 +109,22 @@ let padList = {
|
|||
* @param id A String with the id of the pad
|
||||
* @param {Function} callback
|
||||
*/
|
||||
exports.getPad = async function(id, text) {
|
||||
exports.getPad = async function (id, text) {
|
||||
// check if this is a valid padId
|
||||
if (!exports.isValidPadId(id)) {
|
||||
throw new customError(id + " is not a valid padId", "apierror");
|
||||
throw new customError(`${id} is not a valid padId`, 'apierror');
|
||||
}
|
||||
|
||||
// check if this is a valid text
|
||||
if (text != null) {
|
||||
// check if text is a string
|
||||
if (typeof text != "string") {
|
||||
throw new customError("text is not a string", "apierror");
|
||||
if (typeof text !== 'string') {
|
||||
throw new customError('text is not a string', 'apierror');
|
||||
}
|
||||
|
||||
// check if text is less than 100k chars
|
||||
if (text.length > 100000) {
|
||||
throw new customError("text must be less than 100k chars", "apierror");
|
||||
throw new customError('text must be less than 100k chars', 'apierror');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,20 +144,20 @@ exports.getPad = async function(id, text) {
|
|||
padList.addPad(id);
|
||||
|
||||
return pad;
|
||||
}
|
||||
};
|
||||
|
||||
exports.listAllPads = async function() {
|
||||
let padIDs = await padList.getPads();
|
||||
exports.listAllPads = async function () {
|
||||
const padIDs = await padList.getPads();
|
||||
|
||||
return { padIDs };
|
||||
}
|
||||
return {padIDs};
|
||||
};
|
||||
|
||||
// checks if a pad exists
|
||||
exports.doesPadExist = async function(padId) {
|
||||
let value = await db.get("pad:" + padId);
|
||||
exports.doesPadExist = async function (padId) {
|
||||
const value = await db.get(`pad:${padId}`);
|
||||
|
||||
return (value != null && value.atext);
|
||||
}
|
||||
};
|
||||
|
||||
// alias for backwards compatibility
|
||||
exports.doesPadExists = exports.doesPadExist;
|
||||
|
@ -168,42 +168,42 @@ exports.doesPadExists = exports.doesPadExist;
|
|||
*/
|
||||
const padIdTransforms = [
|
||||
[/\s+/g, '_'],
|
||||
[/:+/g, '_']
|
||||
[/:+/g, '_'],
|
||||
];
|
||||
|
||||
// returns a sanitized padId, respecting legacy pad id formats
|
||||
exports.sanitizePadId = async function sanitizePadId(padId) {
|
||||
for (let i = 0, n = padIdTransforms.length; i < n; ++i) {
|
||||
let exists = await exports.doesPadExist(padId);
|
||||
const exists = await exports.doesPadExist(padId);
|
||||
|
||||
if (exists) {
|
||||
return padId;
|
||||
}
|
||||
|
||||
let [from, to] = padIdTransforms[i];
|
||||
const [from, to] = padIdTransforms[i];
|
||||
|
||||
padId = padId.replace(from, to);
|
||||
}
|
||||
|
||||
// we're out of possible transformations, so just return it
|
||||
return padId;
|
||||
}
|
||||
};
|
||||
|
||||
exports.isValidPadId = function(padId) {
|
||||
exports.isValidPadId = function (padId) {
|
||||
return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the pad from database and unloads it.
|
||||
*/
|
||||
exports.removePad = async (padId) => {
|
||||
const p = db.remove('pad:' + padId);
|
||||
const p = db.remove(`pad:${padId}`);
|
||||
exports.unloadPad(padId);
|
||||
padList.removePad(padId);
|
||||
await p;
|
||||
}
|
||||
};
|
||||
|
||||
// removes a pad from the cache
|
||||
exports.unloadPad = function(padId) {
|
||||
exports.unloadPad = function (padId) {
|
||||
globalPads.remove(padId);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,17 +19,17 @@
|
|||
*/
|
||||
|
||||
|
||||
var db = require("./DB");
|
||||
var randomString = require("../utils/randomstring");
|
||||
const db = require('./DB');
|
||||
const randomString = require('../utils/randomstring');
|
||||
|
||||
|
||||
/**
|
||||
* checks if the id pattern matches a read-only pad id
|
||||
* @param {String} the pad's id
|
||||
*/
|
||||
exports.isReadOnlyId = function(id) {
|
||||
return id.indexOf("r.") === 0;
|
||||
}
|
||||
exports.isReadOnlyId = function (id) {
|
||||
return id.indexOf('r.') === 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* returns a read only id for a pad
|
||||
|
@ -37,36 +37,36 @@ exports.isReadOnlyId = function(id) {
|
|||
*/
|
||||
exports.getReadOnlyId = async function (padId) {
|
||||
// check if there is a pad2readonly entry
|
||||
let readOnlyId = await db.get("pad2readonly:" + padId);
|
||||
let readOnlyId = await db.get(`pad2readonly:${padId}`);
|
||||
|
||||
// there is no readOnly Entry in the database, let's create one
|
||||
if (readOnlyId == null) {
|
||||
readOnlyId = "r." + randomString(16);
|
||||
db.set("pad2readonly:" + padId, readOnlyId);
|
||||
db.set("readonly2pad:" + readOnlyId, padId);
|
||||
readOnlyId = `r.${randomString(16)}`;
|
||||
db.set(`pad2readonly:${padId}`, readOnlyId);
|
||||
db.set(`readonly2pad:${readOnlyId}`, padId);
|
||||
}
|
||||
|
||||
return readOnlyId;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* returns the padId for a read only id
|
||||
* @param {String} readOnlyId read only id
|
||||
*/
|
||||
exports.getPadId = function(readOnlyId) {
|
||||
return db.get("readonly2pad:" + readOnlyId);
|
||||
}
|
||||
exports.getPadId = function (readOnlyId) {
|
||||
return db.get(`readonly2pad:${readOnlyId}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* returns the padId and readonlyPadId in an object for any id
|
||||
* @param {String} padIdOrReadonlyPadId read only id or real pad id
|
||||
*/
|
||||
exports.getIds = async function(id) {
|
||||
let readonly = (id.indexOf("r.") === 0);
|
||||
exports.getIds = async function (id) {
|
||||
const readonly = (id.indexOf('r.') === 0);
|
||||
|
||||
// Might be null, if this is an unknown read-only id
|
||||
let readOnlyPadId = readonly ? id : await exports.getReadOnlyId(id);
|
||||
let padId = readonly ? await exports.getPadId(id) : id;
|
||||
const readOnlyPadId = readonly ? id : await exports.getReadOnlyId(id);
|
||||
const padId = readonly ? await exports.getPadId(id) : id;
|
||||
|
||||
return { readOnlyPadId, padId, readonly };
|
||||
}
|
||||
return {readOnlyPadId, padId, readonly};
|
||||
};
|
||||
|
|
|
@ -18,14 +18,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var authorManager = require("./AuthorManager");
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
|
||||
var padManager = require("./PadManager");
|
||||
var sessionManager = require("./SessionManager");
|
||||
var settings = require("../utils/Settings");
|
||||
const authorManager = require('./AuthorManager');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
|
||||
const padManager = require('./PadManager');
|
||||
const sessionManager = require('./SessionManager');
|
||||
const settings = require('../utils/Settings');
|
||||
const webaccess = require('../hooks/express/webaccess');
|
||||
var log4js = require('log4js');
|
||||
var authLogger = log4js.getLogger("auth");
|
||||
const log4js = require('log4js');
|
||||
const authLogger = log4js.getLogger('auth');
|
||||
|
||||
const DENY = Object.freeze({accessStatus: 'deny'});
|
||||
|
||||
|
@ -47,7 +47,7 @@ const DENY = Object.freeze({accessStatus: 'deny'});
|
|||
* WARNING: Tokens and session IDs MUST be kept secret, otherwise users will be able to impersonate
|
||||
* each other (which might allow them to gain privileges).
|
||||
*/
|
||||
exports.checkAccess = async function(padID, sessionCookie, token, userSettings) {
|
||||
exports.checkAccess = async function (padID, sessionCookie, token, userSettings) {
|
||||
if (!padID) {
|
||||
authLogger.debug('access denied: missing padID');
|
||||
return DENY;
|
||||
|
|
|
@ -18,12 +18,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var customError = require("../utils/customError");
|
||||
const customError = require('../utils/customError');
|
||||
const promises = require('../utils/promises');
|
||||
var randomString = require("../utils/randomstring");
|
||||
var db = require("./DB");
|
||||
var groupManager = require("./GroupManager");
|
||||
var authorManager = require("./AuthorManager");
|
||||
const randomString = require('../utils/randomstring');
|
||||
const db = require('./DB');
|
||||
const groupManager = require('./GroupManager');
|
||||
const authorManager = require('./AuthorManager');
|
||||
|
||||
/**
|
||||
* Finds the author ID for a session with matching ID and group.
|
||||
|
@ -78,61 +78,61 @@ exports.findAuthorID = async (groupID, sessionCookie) => {
|
|||
return sessionInfo.authorID;
|
||||
};
|
||||
|
||||
exports.doesSessionExist = async function(sessionID) {
|
||||
//check if the database entry of this session exists
|
||||
let session = await db.get("session:" + sessionID);
|
||||
exports.doesSessionExist = async function (sessionID) {
|
||||
// check if the database entry of this session exists
|
||||
const session = await db.get(`session:${sessionID}`);
|
||||
return (session !== null);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new session between an author and a group
|
||||
*/
|
||||
exports.createSession = async function(groupID, authorID, validUntil) {
|
||||
exports.createSession = async function (groupID, authorID, validUntil) {
|
||||
// check if the group exists
|
||||
let groupExists = await groupManager.doesGroupExist(groupID);
|
||||
const groupExists = await groupManager.doesGroupExist(groupID);
|
||||
if (!groupExists) {
|
||||
throw new customError("groupID does not exist", "apierror");
|
||||
throw new customError('groupID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// check if the author exists
|
||||
let authorExists = await authorManager.doesAuthorExist(authorID);
|
||||
const authorExists = await authorManager.doesAuthorExist(authorID);
|
||||
if (!authorExists) {
|
||||
throw new customError("authorID does not exist", "apierror");
|
||||
throw new customError('authorID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// try to parse validUntil if it's not a number
|
||||
if (typeof validUntil !== "number") {
|
||||
if (typeof validUntil !== 'number') {
|
||||
validUntil = parseInt(validUntil);
|
||||
}
|
||||
|
||||
// check it's a valid number
|
||||
if (isNaN(validUntil)) {
|
||||
throw new customError("validUntil is not a number", "apierror");
|
||||
throw new customError('validUntil is not a number', 'apierror');
|
||||
}
|
||||
|
||||
// ensure this is not a negative number
|
||||
if (validUntil < 0) {
|
||||
throw new customError("validUntil is a negative number", "apierror");
|
||||
throw new customError('validUntil is a negative number', 'apierror');
|
||||
}
|
||||
|
||||
// ensure this is not a float value
|
||||
if (!is_int(validUntil)) {
|
||||
throw new customError("validUntil is a float value", "apierror");
|
||||
throw new customError('validUntil is a float value', 'apierror');
|
||||
}
|
||||
|
||||
// check if validUntil is in the future
|
||||
if (validUntil < Math.floor(Date.now() / 1000)) {
|
||||
throw new customError("validUntil is in the past", "apierror");
|
||||
throw new customError('validUntil is in the past', 'apierror');
|
||||
}
|
||||
|
||||
// generate sessionID
|
||||
let sessionID = "s." + randomString(16);
|
||||
const sessionID = `s.${randomString(16)}`;
|
||||
|
||||
// set the session into the database
|
||||
await db.set("session:" + sessionID, {"groupID": groupID, "authorID": authorID, "validUntil": validUntil});
|
||||
await db.set(`session:${sessionID}`, {groupID, authorID, validUntil});
|
||||
|
||||
// get the entry
|
||||
let group2sessions = await db.get("group2sessions:" + groupID);
|
||||
let group2sessions = await db.get(`group2sessions:${groupID}`);
|
||||
|
||||
/*
|
||||
* In some cases, the db layer could return "undefined" as well as "null".
|
||||
|
@ -144,115 +144,115 @@ exports.createSession = async function(groupID, authorID, validUntil) {
|
|||
*/
|
||||
if (!group2sessions || !group2sessions.sessionIDs) {
|
||||
// the entry doesn't exist so far, let's create it
|
||||
group2sessions = {sessionIDs : {}};
|
||||
group2sessions = {sessionIDs: {}};
|
||||
}
|
||||
|
||||
// add the entry for this session
|
||||
group2sessions.sessionIDs[sessionID] = 1;
|
||||
|
||||
// save the new element back
|
||||
await db.set("group2sessions:" + groupID, group2sessions);
|
||||
await db.set(`group2sessions:${groupID}`, group2sessions);
|
||||
|
||||
// get the author2sessions entry
|
||||
let author2sessions = await db.get("author2sessions:" + authorID);
|
||||
let author2sessions = await db.get(`author2sessions:${authorID}`);
|
||||
|
||||
if (author2sessions == null || author2sessions.sessionIDs == null) {
|
||||
// the entry doesn't exist so far, let's create it
|
||||
author2sessions = {sessionIDs : {}};
|
||||
author2sessions = {sessionIDs: {}};
|
||||
}
|
||||
|
||||
// add the entry for this session
|
||||
author2sessions.sessionIDs[sessionID] = 1;
|
||||
|
||||
//save the new element back
|
||||
await db.set("author2sessions:" + authorID, author2sessions);
|
||||
// save the new element back
|
||||
await db.set(`author2sessions:${authorID}`, author2sessions);
|
||||
|
||||
return { sessionID };
|
||||
}
|
||||
return {sessionID};
|
||||
};
|
||||
|
||||
exports.getSessionInfo = async function(sessionID) {
|
||||
exports.getSessionInfo = async function (sessionID) {
|
||||
// check if the database entry of this session exists
|
||||
let session = await db.get("session:" + sessionID);
|
||||
const session = await db.get(`session:${sessionID}`);
|
||||
|
||||
if (session == null) {
|
||||
// session does not exist
|
||||
throw new customError("sessionID does not exist", "apierror");
|
||||
throw new customError('sessionID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// everything is fine, return the sessioninfos
|
||||
return session;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes a session
|
||||
*/
|
||||
exports.deleteSession = async function(sessionID) {
|
||||
exports.deleteSession = async function (sessionID) {
|
||||
// ensure that the session exists
|
||||
let session = await db.get("session:" + sessionID);
|
||||
const session = await db.get(`session:${sessionID}`);
|
||||
if (session == null) {
|
||||
throw new customError("sessionID does not exist", "apierror");
|
||||
throw new customError('sessionID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// everything is fine, use the sessioninfos
|
||||
let groupID = session.groupID;
|
||||
let authorID = session.authorID;
|
||||
const groupID = session.groupID;
|
||||
const authorID = session.authorID;
|
||||
|
||||
// get the group2sessions and author2sessions entries
|
||||
let group2sessions = await db.get("group2sessions:" + groupID);
|
||||
let author2sessions = await db.get("author2sessions:" + authorID);
|
||||
const group2sessions = await db.get(`group2sessions:${groupID}`);
|
||||
const author2sessions = await db.get(`author2sessions:${authorID}`);
|
||||
|
||||
// remove the session
|
||||
await db.remove("session:" + sessionID);
|
||||
await db.remove(`session:${sessionID}`);
|
||||
|
||||
// remove session from group2sessions
|
||||
if (group2sessions != null) { // Maybe the group was already deleted
|
||||
delete group2sessions.sessionIDs[sessionID];
|
||||
await db.set("group2sessions:" + groupID, group2sessions);
|
||||
await db.set(`group2sessions:${groupID}`, group2sessions);
|
||||
}
|
||||
|
||||
// remove session from author2sessions
|
||||
if (author2sessions != null) { // Maybe the author was already deleted
|
||||
delete author2sessions.sessionIDs[sessionID];
|
||||
await db.set("author2sessions:" + authorID, author2sessions);
|
||||
await db.set(`author2sessions:${authorID}`, author2sessions);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.listSessionsOfGroup = async function(groupID) {
|
||||
exports.listSessionsOfGroup = async function (groupID) {
|
||||
// check that the group exists
|
||||
let exists = await groupManager.doesGroupExist(groupID);
|
||||
const exists = await groupManager.doesGroupExist(groupID);
|
||||
if (!exists) {
|
||||
throw new customError("groupID does not exist", "apierror");
|
||||
throw new customError('groupID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
let sessions = await listSessionsWithDBKey("group2sessions:" + groupID);
|
||||
const sessions = await listSessionsWithDBKey(`group2sessions:${groupID}`);
|
||||
return sessions;
|
||||
}
|
||||
};
|
||||
|
||||
exports.listSessionsOfAuthor = async function(authorID) {
|
||||
exports.listSessionsOfAuthor = async function (authorID) {
|
||||
// check that the author exists
|
||||
let exists = await authorManager.doesAuthorExist(authorID)
|
||||
const exists = await authorManager.doesAuthorExist(authorID);
|
||||
if (!exists) {
|
||||
throw new customError("authorID does not exist", "apierror");
|
||||
throw new customError('authorID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
let sessions = await listSessionsWithDBKey("author2sessions:" + authorID);
|
||||
const sessions = await listSessionsWithDBKey(`author2sessions:${authorID}`);
|
||||
return sessions;
|
||||
}
|
||||
};
|
||||
|
||||
// this function is basically the code listSessionsOfAuthor and listSessionsOfGroup has in common
|
||||
// required to return null rather than an empty object if there are none
|
||||
async function listSessionsWithDBKey(dbkey) {
|
||||
// get the group2sessions entry
|
||||
let sessionObject = await db.get(dbkey);
|
||||
let sessions = sessionObject ? sessionObject.sessionIDs : null;
|
||||
const sessionObject = await db.get(dbkey);
|
||||
const sessions = sessionObject ? sessionObject.sessionIDs : null;
|
||||
|
||||
// iterate through the sessions and get the sessioninfos
|
||||
for (let sessionID in sessions) {
|
||||
for (const sessionID in sessions) {
|
||||
try {
|
||||
let sessionInfo = await exports.getSessionInfo(sessionID);
|
||||
const sessionInfo = await exports.getSessionInfo(sessionID);
|
||||
sessions[sessionID] = sessionInfo;
|
||||
} catch (err) {
|
||||
if (err == "apierror: sessionID does not exist") {
|
||||
if (err == 'apierror: sessionID does not exist') {
|
||||
console.warn(`Found bad session ${sessionID} in ${dbkey}`);
|
||||
sessions[sessionID] = null;
|
||||
} else {
|
||||
|
|
|
@ -15,11 +15,11 @@ const logger = log4js.getLogger('SessionStore');
|
|||
|
||||
module.exports = class SessionStore extends Store {
|
||||
get(sid, fn) {
|
||||
logger.debug('GET ' + sid);
|
||||
DB.db.get('sessionstorage:' + sid, (err, sess) => {
|
||||
logger.debug(`GET ${sid}`);
|
||||
DB.db.get(`sessionstorage:${sid}`, (err, sess) => {
|
||||
if (sess) {
|
||||
sess.cookie.expires = ('string' == typeof sess.cookie.expires
|
||||
? new Date(sess.cookie.expires) : sess.cookie.expires);
|
||||
sess.cookie.expires = ('string' === typeof sess.cookie.expires
|
||||
? new Date(sess.cookie.expires) : sess.cookie.expires);
|
||||
if (!sess.cookie.expires || new Date() < sess.cookie.expires) {
|
||||
fn(null, sess);
|
||||
} else {
|
||||
|
@ -32,12 +32,12 @@ module.exports = class SessionStore extends Store {
|
|||
}
|
||||
|
||||
set(sid, sess, fn) {
|
||||
logger.debug('SET ' + sid);
|
||||
DB.db.set('sessionstorage:' + sid, sess, fn);
|
||||
logger.debug(`SET ${sid}`);
|
||||
DB.db.set(`sessionstorage:${sid}`, sess, fn);
|
||||
}
|
||||
|
||||
destroy(sid, fn) {
|
||||
logger.debug('DESTROY ' + sid);
|
||||
DB.db.remove('sessionstorage:' + sid, fn);
|
||||
logger.debug(`DESTROY ${sid}`);
|
||||
DB.db.remove(`sessionstorage:${sid}`, fn);
|
||||
}
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -19,84 +19,84 @@
|
|||
* require("./index").require("./examples/foo.ejs")
|
||||
*/
|
||||
|
||||
var ejs = require("ejs");
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
|
||||
var resolve = require("resolve");
|
||||
var settings = require('../utils/Settings');
|
||||
const ejs = require('ejs');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
|
||||
const resolve = require('resolve');
|
||||
const settings = require('../utils/Settings');
|
||||
|
||||
const templateCache = new Map()
|
||||
const templateCache = new Map();
|
||||
|
||||
exports.info = {
|
||||
__output_stack: [],
|
||||
block_stack: [],
|
||||
file_stack: [],
|
||||
args: []
|
||||
args: [],
|
||||
};
|
||||
|
||||
function getCurrentFile() {
|
||||
return exports.info.file_stack[exports.info.file_stack.length-1];
|
||||
return exports.info.file_stack[exports.info.file_stack.length - 1];
|
||||
}
|
||||
|
||||
function createBlockId(name) {
|
||||
return getCurrentFile().path + '|' + name;
|
||||
return `${getCurrentFile().path}|${name}`;
|
||||
}
|
||||
|
||||
exports._init = function (b, recursive) {
|
||||
exports.info.__output_stack.push(exports.info.__output);
|
||||
exports.info.__output = b;
|
||||
}
|
||||
};
|
||||
|
||||
exports._exit = function (b, recursive) {
|
||||
getCurrentFile().inherit.forEach(function (item) {
|
||||
getCurrentFile().inherit.forEach((item) => {
|
||||
exports._require(item.name, item.args);
|
||||
});
|
||||
exports.info.__output = exports.info.__output_stack.pop();
|
||||
}
|
||||
};
|
||||
|
||||
exports.begin_capture = function() {
|
||||
exports.begin_capture = function () {
|
||||
exports.info.__output_stack.push(exports.info.__output.concat());
|
||||
exports.info.__output.splice(0, exports.info.__output.length);
|
||||
}
|
||||
};
|
||||
|
||||
exports.end_capture = function () {
|
||||
var res = exports.info.__output.join("");
|
||||
const res = exports.info.__output.join('');
|
||||
exports.info.__output.splice.apply(
|
||||
exports.info.__output,
|
||||
[0, exports.info.__output.length].concat(exports.info.__output_stack.pop()));
|
||||
exports.info.__output,
|
||||
[0, exports.info.__output.length].concat(exports.info.__output_stack.pop()));
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
exports.begin_define_block = function (name) {
|
||||
exports.info.block_stack.push(name);
|
||||
exports.begin_capture();
|
||||
}
|
||||
};
|
||||
|
||||
exports.end_define_block = function () {
|
||||
var content = exports.end_capture();
|
||||
const content = exports.end_capture();
|
||||
return content;
|
||||
}
|
||||
};
|
||||
|
||||
exports.end_block = function () {
|
||||
var name = exports.info.block_stack.pop();
|
||||
var renderContext = exports.info.args[exports.info.args.length-1];
|
||||
var args = {content: exports.end_define_block(), renderContext: renderContext};
|
||||
hooks.callAll("eejsBlock_" + name, args);
|
||||
const name = exports.info.block_stack.pop();
|
||||
const renderContext = exports.info.args[exports.info.args.length - 1];
|
||||
const args = {content: exports.end_define_block(), renderContext};
|
||||
hooks.callAll(`eejsBlock_${name}`, args);
|
||||
exports.info.__output.push(args.content);
|
||||
}
|
||||
};
|
||||
|
||||
exports.begin_block = exports.begin_define_block;
|
||||
|
||||
exports.inherit = function (name, args) {
|
||||
getCurrentFile().inherit.push({name:name, args:args});
|
||||
}
|
||||
getCurrentFile().inherit.push({name, args});
|
||||
};
|
||||
|
||||
exports.require = function (name, args, mod) {
|
||||
if (args == undefined) args = {};
|
||||
|
||||
var basedir = __dirname;
|
||||
var paths = [];
|
||||
let basedir = __dirname;
|
||||
let paths = [];
|
||||
|
||||
if (exports.info.file_stack.length) {
|
||||
basedir = path.dirname(getCurrentFile().path);
|
||||
|
@ -106,43 +106,43 @@ exports.require = function (name, args, mod) {
|
|||
paths = mod.paths;
|
||||
}
|
||||
|
||||
var ejspath = resolve.sync(
|
||||
name,
|
||||
{
|
||||
paths : paths,
|
||||
basedir : basedir,
|
||||
extensions : [ '.html', '.ejs' ],
|
||||
}
|
||||
)
|
||||
const ejspath = resolve.sync(
|
||||
name,
|
||||
{
|
||||
paths,
|
||||
basedir,
|
||||
extensions: ['.html', '.ejs'],
|
||||
},
|
||||
);
|
||||
|
||||
args.e = exports;
|
||||
args.require = require;
|
||||
|
||||
let template
|
||||
if (settings.maxAge !== 0){ // don't cache if maxAge is 0
|
||||
let template;
|
||||
if (settings.maxAge !== 0) { // don't cache if maxAge is 0
|
||||
if (!templateCache.has(ejspath)) {
|
||||
template = '<% e._init(__output); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>';
|
||||
templateCache.set(ejspath, template)
|
||||
template = `<% e._init(__output); %>${fs.readFileSync(ejspath).toString()}<% e._exit(); %>`;
|
||||
templateCache.set(ejspath, template);
|
||||
} else {
|
||||
template = templateCache.get(ejspath)
|
||||
template = templateCache.get(ejspath);
|
||||
}
|
||||
}else{
|
||||
template = '<% e._init(__output); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>';
|
||||
} else {
|
||||
template = `<% e._init(__output); %>${fs.readFileSync(ejspath).toString()}<% e._exit(); %>`;
|
||||
}
|
||||
|
||||
exports.info.args.push(args);
|
||||
exports.info.file_stack.push({path: ejspath, inherit: []});
|
||||
if(settings.maxAge !== 0){
|
||||
var res = ejs.render(template, args, { cache: true, filename: ejspath });
|
||||
}else{
|
||||
var res = ejs.render(template, args, { cache: false, filename: ejspath });
|
||||
if (settings.maxAge !== 0) {
|
||||
var res = ejs.render(template, args, {cache: true, filename: ejspath});
|
||||
} else {
|
||||
var res = ejs.render(template, args, {cache: false, filename: ejspath});
|
||||
}
|
||||
exports.info.file_stack.pop();
|
||||
exports.info.args.pop();
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
exports._require = function (name, args) {
|
||||
exports.info.__output.push(exports.require(name, args));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -18,131 +18,118 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var absolutePaths = require('../utils/AbsolutePaths');
|
||||
var fs = require("fs");
|
||||
var api = require("../db/API");
|
||||
var log4js = require('log4js');
|
||||
var padManager = require("../db/PadManager");
|
||||
var randomString = require("../utils/randomstring");
|
||||
var argv = require('../utils/Cli').argv;
|
||||
var createHTTPError = require('http-errors');
|
||||
const absolutePaths = require('../utils/AbsolutePaths');
|
||||
const fs = require('fs');
|
||||
const api = require('../db/API');
|
||||
const log4js = require('log4js');
|
||||
const padManager = require('../db/PadManager');
|
||||
const randomString = require('../utils/randomstring');
|
||||
const argv = require('../utils/Cli').argv;
|
||||
const createHTTPError = require('http-errors');
|
||||
|
||||
var apiHandlerLogger = log4js.getLogger('APIHandler');
|
||||
const apiHandlerLogger = log4js.getLogger('APIHandler');
|
||||
|
||||
//ensure we have an apikey
|
||||
var apikey = null;
|
||||
var apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || "./APIKEY.txt");
|
||||
// ensure we have an apikey
|
||||
let apikey = null;
|
||||
const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt');
|
||||
|
||||
try {
|
||||
apikey = fs.readFileSync(apikeyFilename,"utf8");
|
||||
apikey = fs.readFileSync(apikeyFilename, 'utf8');
|
||||
apiHandlerLogger.info(`Api key file read from: "${apikeyFilename}"`);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
apiHandlerLogger.info(`Api key file "${apikeyFilename}" not found. Creating with random contents.`);
|
||||
apikey = randomString(32);
|
||||
fs.writeFileSync(apikeyFilename,apikey,"utf8");
|
||||
fs.writeFileSync(apikeyFilename, apikey, 'utf8');
|
||||
}
|
||||
|
||||
// a list of all functions
|
||||
var version = {};
|
||||
const version = {};
|
||||
|
||||
version["1"] = Object.assign({},
|
||||
{ "createGroup" : []
|
||||
, "createGroupIfNotExistsFor" : ["groupMapper"]
|
||||
, "deleteGroup" : ["groupID"]
|
||||
, "listPads" : ["groupID"]
|
||||
, "createPad" : ["padID", "text"]
|
||||
, "createGroupPad" : ["groupID", "padName", "text"]
|
||||
, "createAuthor" : ["name"]
|
||||
, "createAuthorIfNotExistsFor": ["authorMapper" , "name"]
|
||||
, "listPadsOfAuthor" : ["authorID"]
|
||||
, "createSession" : ["groupID", "authorID", "validUntil"]
|
||||
, "deleteSession" : ["sessionID"]
|
||||
, "getSessionInfo" : ["sessionID"]
|
||||
, "listSessionsOfGroup" : ["groupID"]
|
||||
, "listSessionsOfAuthor" : ["authorID"]
|
||||
, "getText" : ["padID", "rev"]
|
||||
, "setText" : ["padID", "text"]
|
||||
, "getHTML" : ["padID", "rev"]
|
||||
, "setHTML" : ["padID", "html"]
|
||||
, "getRevisionsCount" : ["padID"]
|
||||
, "getLastEdited" : ["padID"]
|
||||
, "deletePad" : ["padID"]
|
||||
, "getReadOnlyID" : ["padID"]
|
||||
, "setPublicStatus" : ["padID", "publicStatus"]
|
||||
, "getPublicStatus" : ["padID"]
|
||||
, "listAuthorsOfPad" : ["padID"]
|
||||
, "padUsersCount" : ["padID"]
|
||||
}
|
||||
version['1'] = Object.assign({},
|
||||
{createGroup: [],
|
||||
createGroupIfNotExistsFor: ['groupMapper'],
|
||||
deleteGroup: ['groupID'],
|
||||
listPads: ['groupID'],
|
||||
createPad: ['padID', 'text'],
|
||||
createGroupPad: ['groupID', 'padName', 'text'],
|
||||
createAuthor: ['name'],
|
||||
createAuthorIfNotExistsFor: ['authorMapper', 'name'],
|
||||
listPadsOfAuthor: ['authorID'],
|
||||
createSession: ['groupID', 'authorID', 'validUntil'],
|
||||
deleteSession: ['sessionID'],
|
||||
getSessionInfo: ['sessionID'],
|
||||
listSessionsOfGroup: ['groupID'],
|
||||
listSessionsOfAuthor: ['authorID'],
|
||||
getText: ['padID', 'rev'],
|
||||
setText: ['padID', 'text'],
|
||||
getHTML: ['padID', 'rev'],
|
||||
setHTML: ['padID', 'html'],
|
||||
getRevisionsCount: ['padID'],
|
||||
getLastEdited: ['padID'],
|
||||
deletePad: ['padID'],
|
||||
getReadOnlyID: ['padID'],
|
||||
setPublicStatus: ['padID', 'publicStatus'],
|
||||
getPublicStatus: ['padID'],
|
||||
listAuthorsOfPad: ['padID'],
|
||||
padUsersCount: ['padID']},
|
||||
);
|
||||
|
||||
version["1.1"] = Object.assign({}, version["1"],
|
||||
{ "getAuthorName" : ["authorID"]
|
||||
, "padUsers" : ["padID"]
|
||||
, "sendClientsMessage" : ["padID", "msg"]
|
||||
, "listAllGroups" : []
|
||||
}
|
||||
version['1.1'] = Object.assign({}, version['1'],
|
||||
{getAuthorName: ['authorID'],
|
||||
padUsers: ['padID'],
|
||||
sendClientsMessage: ['padID', 'msg'],
|
||||
listAllGroups: []},
|
||||
);
|
||||
|
||||
version["1.2"] = Object.assign({}, version["1.1"],
|
||||
{ "checkToken" : []
|
||||
}
|
||||
version['1.2'] = Object.assign({}, version['1.1'],
|
||||
{checkToken: []},
|
||||
);
|
||||
|
||||
version["1.2.1"] = Object.assign({}, version["1.2"],
|
||||
{ "listAllPads" : []
|
||||
}
|
||||
version['1.2.1'] = Object.assign({}, version['1.2'],
|
||||
{listAllPads: []},
|
||||
);
|
||||
|
||||
version["1.2.7"] = Object.assign({}, version["1.2.1"],
|
||||
{ "createDiffHTML" : ["padID", "startRev", "endRev"]
|
||||
, "getChatHistory" : ["padID", "start", "end"]
|
||||
, "getChatHead" : ["padID"]
|
||||
}
|
||||
version['1.2.7'] = Object.assign({}, version['1.2.1'],
|
||||
{createDiffHTML: ['padID', 'startRev', 'endRev'],
|
||||
getChatHistory: ['padID', 'start', 'end'],
|
||||
getChatHead: ['padID']},
|
||||
);
|
||||
|
||||
version["1.2.8"] = Object.assign({}, version["1.2.7"],
|
||||
{ "getAttributePool" : ["padID"]
|
||||
, "getRevisionChangeset" : ["padID", "rev"]
|
||||
}
|
||||
version['1.2.8'] = Object.assign({}, version['1.2.7'],
|
||||
{getAttributePool: ['padID'],
|
||||
getRevisionChangeset: ['padID', 'rev']},
|
||||
);
|
||||
|
||||
version["1.2.9"] = Object.assign({}, version["1.2.8"],
|
||||
{ "copyPad" : ["sourceID", "destinationID", "force"]
|
||||
, "movePad" : ["sourceID", "destinationID", "force"]
|
||||
}
|
||||
version['1.2.9'] = Object.assign({}, version['1.2.8'],
|
||||
{copyPad: ['sourceID', 'destinationID', 'force'],
|
||||
movePad: ['sourceID', 'destinationID', 'force']},
|
||||
);
|
||||
|
||||
version["1.2.10"] = Object.assign({}, version["1.2.9"],
|
||||
{ "getPadID" : ["roID"]
|
||||
}
|
||||
version['1.2.10'] = Object.assign({}, version['1.2.9'],
|
||||
{getPadID: ['roID']},
|
||||
);
|
||||
|
||||
version["1.2.11"] = Object.assign({}, version["1.2.10"],
|
||||
{ "getSavedRevisionsCount" : ["padID"]
|
||||
, "listSavedRevisions" : ["padID"]
|
||||
, "saveRevision" : ["padID", "rev"]
|
||||
, "restoreRevision" : ["padID", "rev"]
|
||||
}
|
||||
version['1.2.11'] = Object.assign({}, version['1.2.10'],
|
||||
{getSavedRevisionsCount: ['padID'],
|
||||
listSavedRevisions: ['padID'],
|
||||
saveRevision: ['padID', 'rev'],
|
||||
restoreRevision: ['padID', 'rev']},
|
||||
);
|
||||
|
||||
version["1.2.12"] = Object.assign({}, version["1.2.11"],
|
||||
{ "appendChatMessage" : ["padID", "text", "authorID", "time"]
|
||||
}
|
||||
version['1.2.12'] = Object.assign({}, version['1.2.11'],
|
||||
{appendChatMessage: ['padID', 'text', 'authorID', 'time']},
|
||||
);
|
||||
|
||||
version["1.2.13"] = Object.assign({}, version["1.2.12"],
|
||||
{ "appendText" : ["padID", "text"]
|
||||
}
|
||||
version['1.2.13'] = Object.assign({}, version['1.2.12'],
|
||||
{appendText: ['padID', 'text']},
|
||||
);
|
||||
|
||||
version["1.2.14"] = Object.assign({}, version["1.2.13"],
|
||||
{ "getStats" : []
|
||||
}
|
||||
version['1.2.14'] = Object.assign({}, version['1.2.13'],
|
||||
{getStats: []},
|
||||
);
|
||||
|
||||
version["1.2.15"] = Object.assign({}, version["1.2.14"],
|
||||
{ "copyPadWithoutHistory" : ["sourceID", "destinationID", "force"]
|
||||
}
|
||||
version['1.2.15'] = Object.assign({}, version['1.2.14'],
|
||||
{copyPadWithoutHistory: ['sourceID', 'destinationID', 'force']},
|
||||
);
|
||||
|
||||
// set the latest available API version here
|
||||
|
@ -158,7 +145,7 @@ exports.version = version;
|
|||
* @req express request object
|
||||
* @res express response object
|
||||
*/
|
||||
exports.handle = async function(apiVersion, functionName, fields, req, res) {
|
||||
exports.handle = async function (apiVersion, functionName, fields, req, res) {
|
||||
// say goodbye if this is an unknown API version
|
||||
if (!(apiVersion in version)) {
|
||||
throw new createHTTPError.NotFound('no such api version');
|
||||
|
@ -170,31 +157,29 @@ exports.handle = async function(apiVersion, functionName, fields, req, res) {
|
|||
}
|
||||
|
||||
// check the api key!
|
||||
fields["apikey"] = fields["apikey"] || fields["api_key"];
|
||||
fields.apikey = fields.apikey || fields.api_key;
|
||||
|
||||
if (fields["apikey"] !== apikey.trim()) {
|
||||
if (fields.apikey !== apikey.trim()) {
|
||||
throw new createHTTPError.Unauthorized('no or wrong API Key');
|
||||
}
|
||||
|
||||
// sanitize any padIDs before continuing
|
||||
if (fields["padID"]) {
|
||||
fields["padID"] = await padManager.sanitizePadId(fields["padID"]);
|
||||
if (fields.padID) {
|
||||
fields.padID = await padManager.sanitizePadId(fields.padID);
|
||||
}
|
||||
// there was an 'else' here before - removed it to ensure
|
||||
// that this sanitize step can't be circumvented by forcing
|
||||
// the first branch to be taken
|
||||
if (fields["padName"]) {
|
||||
fields["padName"] = await padManager.sanitizePadId(fields["padName"]);
|
||||
if (fields.padName) {
|
||||
fields.padName = await padManager.sanitizePadId(fields.padName);
|
||||
}
|
||||
|
||||
// put the function parameters in an array
|
||||
var functionParams = version[apiVersion][functionName].map(function (field) {
|
||||
return fields[field]
|
||||
});
|
||||
const functionParams = version[apiVersion][functionName].map((field) => fields[field]);
|
||||
|
||||
// call the api function
|
||||
return api[functionName].apply(this, functionParams);
|
||||
}
|
||||
};
|
||||
|
||||
exports.exportedForTestingOnly = {
|
||||
apiKey: apikey,
|
||||
|
|
|
@ -19,15 +19,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var exporthtml = require("../utils/ExportHtml");
|
||||
var exporttxt = require("../utils/ExportTxt");
|
||||
var exportEtherpad = require("../utils/ExportEtherpad");
|
||||
var fs = require("fs");
|
||||
var settings = require('../utils/Settings');
|
||||
var os = require('os');
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
|
||||
var TidyHtml = require('../utils/TidyHtml');
|
||||
const util = require("util");
|
||||
const exporthtml = require('../utils/ExportHtml');
|
||||
const exporttxt = require('../utils/ExportTxt');
|
||||
const exportEtherpad = require('../utils/ExportEtherpad');
|
||||
const fs = require('fs');
|
||||
const settings = require('../utils/Settings');
|
||||
const os = require('os');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
const TidyHtml = require('../utils/TidyHtml');
|
||||
const util = require('util');
|
||||
|
||||
const fsp_writeFile = util.promisify(fs.writeFile);
|
||||
const fsp_unlink = util.promisify(fs.unlink);
|
||||
|
@ -36,12 +36,12 @@ let convertor = null;
|
|||
|
||||
// load abiword only if it is enabled
|
||||
if (settings.abiword != null) {
|
||||
convertor = require("../utils/Abiword");
|
||||
convertor = require('../utils/Abiword');
|
||||
}
|
||||
|
||||
// Use LibreOffice if an executable has been defined in the settings
|
||||
if (settings.soffice != null) {
|
||||
convertor = require("../utils/LibreOffice");
|
||||
convertor = require('../utils/LibreOffice');
|
||||
}
|
||||
|
||||
const tempDirectory = os.tmpdir();
|
||||
|
@ -51,10 +51,10 @@ const tempDirectory = os.tmpdir();
|
|||
*/
|
||||
async function doExport(req, res, padId, readOnlyId, type) {
|
||||
// avoid naming the read-only file as the original pad's id
|
||||
var fileName = readOnlyId ? readOnlyId : padId;
|
||||
let fileName = readOnlyId ? readOnlyId : padId;
|
||||
|
||||
// allow fileName to be overwritten by a hook, the type type is kept static for security reasons
|
||||
let hookFileName = await hooks.aCallFirst("exportFileName", padId);
|
||||
const hookFileName = await hooks.aCallFirst('exportFileName', padId);
|
||||
|
||||
// if fileName is set then set it to the padId, note that fileName is returned as an array.
|
||||
if (hookFileName.length) {
|
||||
|
@ -62,15 +62,15 @@ async function doExport(req, res, padId, readOnlyId, type) {
|
|||
}
|
||||
|
||||
// tell the browser that this is a downloadable file
|
||||
res.attachment(fileName + "." + type);
|
||||
res.attachment(`${fileName}.${type}`);
|
||||
|
||||
// if this is a plain text export, we can do this directly
|
||||
// We have to over engineer this because tabs are stored as attributes and not plain text
|
||||
if (type === "etherpad") {
|
||||
let pad = await exportEtherpad.getPadRaw(padId);
|
||||
if (type === 'etherpad') {
|
||||
const pad = await exportEtherpad.getPadRaw(padId);
|
||||
res.send(pad);
|
||||
} else if (type === "txt") {
|
||||
let txt = await exporttxt.getPadTXTDocument(padId, req.params.rev);
|
||||
} else if (type === 'txt') {
|
||||
const txt = await exporttxt.getPadTXTDocument(padId, req.params.rev);
|
||||
res.send(txt);
|
||||
} else {
|
||||
// render the html document
|
||||
|
@ -79,17 +79,17 @@ async function doExport(req, res, padId, readOnlyId, type) {
|
|||
// decide what to do with the html export
|
||||
|
||||
// if this is a html export, we can send this from here directly
|
||||
if (type === "html") {
|
||||
if (type === 'html') {
|
||||
// do any final changes the plugin might want to make
|
||||
let newHTML = await hooks.aCallFirst("exportHTMLSend", html);
|
||||
const newHTML = await hooks.aCallFirst('exportHTMLSend', html);
|
||||
if (newHTML.length) html = newHTML;
|
||||
res.send(html);
|
||||
throw "stop";
|
||||
throw 'stop';
|
||||
}
|
||||
|
||||
// else write the html export to a file
|
||||
let randNum = Math.floor(Math.random()*0xFFFFFFFF);
|
||||
let srcFile = tempDirectory + "/etherpad_export_" + randNum + ".html";
|
||||
const randNum = Math.floor(Math.random() * 0xFFFFFFFF);
|
||||
const srcFile = `${tempDirectory}/etherpad_export_${randNum}.html`;
|
||||
await fsp_writeFile(srcFile, html);
|
||||
|
||||
// Tidy up the exported HTML
|
||||
|
@ -98,42 +98,42 @@ async function doExport(req, res, padId, readOnlyId, type) {
|
|||
await TidyHtml.tidy(srcFile);
|
||||
|
||||
// send the convert job to the convertor (abiword, libreoffice, ..)
|
||||
let destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type;
|
||||
const destFile = `${tempDirectory}/etherpad_export_${randNum}.${type}`;
|
||||
|
||||
// Allow plugins to overwrite the convert in export process
|
||||
let result = await hooks.aCallAll("exportConvert", { srcFile, destFile, req, res });
|
||||
const result = await hooks.aCallAll('exportConvert', {srcFile, destFile, req, res});
|
||||
if (result.length > 0) {
|
||||
// console.log("export handled by plugin", destFile);
|
||||
handledByPlugin = true;
|
||||
} else {
|
||||
// @TODO no Promise interface for convertors (yet)
|
||||
await new Promise((resolve, reject) => {
|
||||
convertor.convertFile(srcFile, destFile, type, function(err) {
|
||||
err ? reject("convertFailed") : resolve();
|
||||
convertor.convertFile(srcFile, destFile, type, (err) => {
|
||||
err ? reject('convertFailed') : resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// send the file
|
||||
let sendFile = util.promisify(res.sendFile);
|
||||
const sendFile = util.promisify(res.sendFile);
|
||||
await res.sendFile(destFile, null);
|
||||
|
||||
// clean up temporary files
|
||||
await fsp_unlink(srcFile);
|
||||
|
||||
// 100ms delay to accommodate for slow windows fs
|
||||
if (os.type().indexOf("Windows") > -1) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
if (os.type().indexOf('Windows') > -1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
await fsp_unlink(destFile);
|
||||
}
|
||||
}
|
||||
|
||||
exports.doExport = function(req, res, padId, readOnlyId, type) {
|
||||
doExport(req, res, padId, readOnlyId, type).catch(err => {
|
||||
if (err !== "stop") {
|
||||
exports.doExport = function (req, res, padId, readOnlyId, type) {
|
||||
doExport(req, res, padId, readOnlyId, type).catch((err) => {
|
||||
if (err !== 'stop') {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -20,36 +20,36 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var padManager = require("../db/PadManager")
|
||||
, padMessageHandler = require("./PadMessageHandler")
|
||||
, fs = require("fs")
|
||||
, path = require("path")
|
||||
, settings = require('../utils/Settings')
|
||||
, formidable = require('formidable')
|
||||
, os = require("os")
|
||||
, importHtml = require("../utils/ImportHtml")
|
||||
, importEtherpad = require("../utils/ImportEtherpad")
|
||||
, log4js = require("log4js")
|
||||
, hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js")
|
||||
, util = require("util");
|
||||
const padManager = require('../db/PadManager');
|
||||
const padMessageHandler = require('./PadMessageHandler');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const settings = require('../utils/Settings');
|
||||
const formidable = require('formidable');
|
||||
const os = require('os');
|
||||
const importHtml = require('../utils/ImportHtml');
|
||||
const importEtherpad = require('../utils/ImportEtherpad');
|
||||
const log4js = require('log4js');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
|
||||
const util = require('util');
|
||||
|
||||
let fsp_exists = util.promisify(fs.exists);
|
||||
let fsp_rename = util.promisify(fs.rename);
|
||||
let fsp_readFile = util.promisify(fs.readFile);
|
||||
let fsp_unlink = util.promisify(fs.unlink)
|
||||
const fsp_exists = util.promisify(fs.exists);
|
||||
const fsp_rename = util.promisify(fs.rename);
|
||||
const fsp_readFile = util.promisify(fs.readFile);
|
||||
const fsp_unlink = util.promisify(fs.unlink);
|
||||
|
||||
let convertor = null;
|
||||
let exportExtension = "htm";
|
||||
let exportExtension = 'htm';
|
||||
|
||||
// load abiword only if it is enabled and if soffice is disabled
|
||||
if (settings.abiword != null && settings.soffice === null) {
|
||||
convertor = require("../utils/Abiword");
|
||||
convertor = require('../utils/Abiword');
|
||||
}
|
||||
|
||||
// load soffice only if it is enabled
|
||||
if (settings.soffice != null) {
|
||||
convertor = require("../utils/LibreOffice");
|
||||
exportExtension = "html";
|
||||
convertor = require('../utils/LibreOffice');
|
||||
exportExtension = 'html';
|
||||
}
|
||||
|
||||
const tmpDirectory = os.tmpdir();
|
||||
|
@ -58,28 +58,28 @@ const tmpDirectory = os.tmpdir();
|
|||
* do a requested import
|
||||
*/
|
||||
async function doImport(req, res, padId) {
|
||||
var apiLogger = log4js.getLogger("ImportHandler");
|
||||
const apiLogger = log4js.getLogger('ImportHandler');
|
||||
|
||||
// pipe to a file
|
||||
// convert file to html via abiword or soffice
|
||||
// set html in the pad
|
||||
var randNum = Math.floor(Math.random()*0xFFFFFFFF);
|
||||
const randNum = Math.floor(Math.random() * 0xFFFFFFFF);
|
||||
|
||||
// setting flag for whether to use convertor or not
|
||||
let useConvertor = (convertor != null);
|
||||
|
||||
let form = new formidable.IncomingForm();
|
||||
const form = new formidable.IncomingForm();
|
||||
form.keepExtensions = true;
|
||||
form.uploadDir = tmpDirectory;
|
||||
form.maxFileSize = settings.importMaxFileSize;
|
||||
|
||||
|
||||
// Ref: https://github.com/node-formidable/formidable/issues/469
|
||||
// Crash in Etherpad was Uploading Error: Error: Request aborted
|
||||
// [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed
|
||||
form.onPart = part => {
|
||||
form.onPart = (part) => {
|
||||
form.handlePart(part);
|
||||
if (part.filename !== undefined) {
|
||||
form.openedFiles[form.openedFiles.length - 1]._writeStream.on('error', err => {
|
||||
form.openedFiles[form.openedFiles.length - 1]._writeStream.on('error', (err) => {
|
||||
form.emit('error', err);
|
||||
});
|
||||
}
|
||||
|
@ -87,23 +87,23 @@ async function doImport(req, res, padId) {
|
|||
|
||||
// locally wrapped Promise, since form.parse requires a callback
|
||||
let srcFile = await new Promise((resolve, reject) => {
|
||||
form.parse(req, function(err, fields, files) {
|
||||
form.parse(req, (err, fields, files) => {
|
||||
if (err || files.file === undefined) {
|
||||
// the upload failed, stop at this point
|
||||
if (err) {
|
||||
console.warn("Uploading Error: " + err.stack);
|
||||
console.warn(`Uploading Error: ${err.stack}`);
|
||||
}
|
||||
|
||||
// I hate doing indexOf here but I can't see anything to use...
|
||||
if (err && err.stack && err.stack.indexOf("maxFileSize") !== -1) {
|
||||
reject("maxFileSize");
|
||||
if (err && err.stack && err.stack.indexOf('maxFileSize') !== -1) {
|
||||
reject('maxFileSize');
|
||||
}
|
||||
|
||||
reject("uploadFailed");
|
||||
reject('uploadFailed');
|
||||
}
|
||||
if(!files.file){ // might not be a graceful fix but it works
|
||||
reject("uploadFailed");
|
||||
}else{
|
||||
if (!files.file) { // might not be a graceful fix but it works
|
||||
reject('uploadFailed');
|
||||
} else {
|
||||
resolve(files.file.path);
|
||||
}
|
||||
});
|
||||
|
@ -111,47 +111,47 @@ async function doImport(req, res, padId) {
|
|||
|
||||
// ensure this is a file ending we know, else we change the file ending to .txt
|
||||
// this allows us to accept source code files like .c or .java
|
||||
let fileEnding = path.extname(srcFile).toLowerCase()
|
||||
, knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm", ".etherpad", ".rtf"]
|
||||
, fileEndingUnknown = (knownFileEndings.indexOf(fileEnding) < 0);
|
||||
const fileEnding = path.extname(srcFile).toLowerCase();
|
||||
const knownFileEndings = ['.txt', '.doc', '.docx', '.pdf', '.odt', '.html', '.htm', '.etherpad', '.rtf'];
|
||||
const fileEndingUnknown = (knownFileEndings.indexOf(fileEnding) < 0);
|
||||
|
||||
if (fileEndingUnknown) {
|
||||
// the file ending is not known
|
||||
|
||||
if (settings.allowUnknownFileEnds === true) {
|
||||
// we need to rename this file with a .txt ending
|
||||
let oldSrcFile = srcFile;
|
||||
const oldSrcFile = srcFile;
|
||||
|
||||
srcFile = path.join(path.dirname(srcFile), path.basename(srcFile, fileEnding) + ".txt");
|
||||
srcFile = path.join(path.dirname(srcFile), `${path.basename(srcFile, fileEnding)}.txt`);
|
||||
await fsp_rename(oldSrcFile, srcFile);
|
||||
} else {
|
||||
console.warn("Not allowing unknown file type to be imported", fileEnding);
|
||||
throw "uploadFailed";
|
||||
console.warn('Not allowing unknown file type to be imported', fileEnding);
|
||||
throw 'uploadFailed';
|
||||
}
|
||||
}
|
||||
|
||||
let destFile = path.join(tmpDirectory, "etherpad_import_" + randNum + "." + exportExtension);
|
||||
const destFile = path.join(tmpDirectory, `etherpad_import_${randNum}.${exportExtension}`);
|
||||
|
||||
// Logic for allowing external Import Plugins
|
||||
let result = await hooks.aCallAll("import", { srcFile, destFile, fileEnding });
|
||||
let importHandledByPlugin = (result.length > 0); // This feels hacky and wrong..
|
||||
const result = await hooks.aCallAll('import', {srcFile, destFile, fileEnding});
|
||||
const importHandledByPlugin = (result.length > 0); // This feels hacky and wrong..
|
||||
|
||||
let fileIsEtherpad = (fileEnding === ".etherpad");
|
||||
let fileIsHTML = (fileEnding === ".html" || fileEnding === ".htm");
|
||||
let fileIsTXT = (fileEnding === ".txt");
|
||||
const fileIsEtherpad = (fileEnding === '.etherpad');
|
||||
const fileIsHTML = (fileEnding === '.html' || fileEnding === '.htm');
|
||||
const fileIsTXT = (fileEnding === '.txt');
|
||||
|
||||
if (fileIsEtherpad) {
|
||||
// we do this here so we can see if the pad has quite a few edits
|
||||
let _pad = await padManager.getPad(padId);
|
||||
let headCount = _pad.head;
|
||||
const _pad = await padManager.getPad(padId);
|
||||
const headCount = _pad.head;
|
||||
|
||||
if (headCount >= 10) {
|
||||
apiLogger.warn("Direct database Import attempt of a pad that already has content, we won't be doing this");
|
||||
throw "padHasData";
|
||||
throw 'padHasData';
|
||||
}
|
||||
|
||||
const fsp_readFile = util.promisify(fs.readFile);
|
||||
let _text = await fsp_readFile(srcFile, "utf8");
|
||||
const _text = await fsp_readFile(srcFile, 'utf8');
|
||||
req.directDatabaseAccess = true;
|
||||
await importEtherpad.setPadRaw(padId, _text);
|
||||
}
|
||||
|
@ -170,11 +170,11 @@ async function doImport(req, res, padId) {
|
|||
} else {
|
||||
// @TODO - no Promise interface for convertors (yet)
|
||||
await new Promise((resolve, reject) => {
|
||||
convertor.convertFile(srcFile, destFile, exportExtension, function(err) {
|
||||
convertor.convertFile(srcFile, destFile, exportExtension, (err) => {
|
||||
// catch convert errors
|
||||
if (err) {
|
||||
console.warn("Converting Error:", err);
|
||||
reject("convertFailed");
|
||||
console.warn('Converting Error:', err);
|
||||
reject('convertFailed');
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
|
@ -184,13 +184,13 @@ async function doImport(req, res, padId) {
|
|||
|
||||
if (!useConvertor && !req.directDatabaseAccess) {
|
||||
// Read the file with no encoding for raw buffer access.
|
||||
let buf = await fsp_readFile(destFile);
|
||||
const buf = await fsp_readFile(destFile);
|
||||
|
||||
// Check if there are only ascii chars in the uploaded file
|
||||
let isAscii = ! Array.prototype.some.call(buf, c => (c > 240));
|
||||
const isAscii = !Array.prototype.some.call(buf, (c) => (c > 240));
|
||||
|
||||
if (!isAscii) {
|
||||
throw "uploadFailed";
|
||||
throw 'uploadFailed';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,7 +201,7 @@ async function doImport(req, res, padId) {
|
|||
let text;
|
||||
|
||||
if (!req.directDatabaseAccess) {
|
||||
text = await fsp_readFile(destFile, "utf8");
|
||||
text = await fsp_readFile(destFile, 'utf8');
|
||||
|
||||
/*
|
||||
* The <title> tag needs to be stripped out, otherwise it is appended to the
|
||||
|
@ -211,13 +211,13 @@ async function doImport(req, res, padId) {
|
|||
* added to the <title> tag. This is a quick & dirty way of matching the
|
||||
* title and comment it out independently on the classes that are set on it.
|
||||
*/
|
||||
text = text.replace("<title", "<!-- <title");
|
||||
text = text.replace("</title>","</title>-->");
|
||||
text = text.replace('<title', '<!-- <title');
|
||||
text = text.replace('</title>', '</title>-->');
|
||||
|
||||
// node on windows has a delay on releasing of the file lock.
|
||||
// We add a 100ms delay to work around this
|
||||
if (os.type().indexOf("Windows") > -1){
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
if (os.type().indexOf('Windows') > -1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,7 +227,7 @@ async function doImport(req, res, padId) {
|
|||
try {
|
||||
await importHtml.setPadHTML(pad, text);
|
||||
} catch (e) {
|
||||
apiLogger.warn("Error importing, possibly caused by malformed HTML");
|
||||
apiLogger.warn('Error importing, possibly caused by malformed HTML');
|
||||
}
|
||||
} else {
|
||||
await pad.setText(text);
|
||||
|
@ -274,16 +274,16 @@ exports.doImport = function (req, res, padId) {
|
|||
* the function above there's no other way to return
|
||||
* a value to the caller.
|
||||
*/
|
||||
let status = "ok";
|
||||
doImport(req, res, padId).catch(err => {
|
||||
let status = 'ok';
|
||||
doImport(req, res, padId).catch((err) => {
|
||||
// check for known errors and replace the status
|
||||
if (err == "uploadFailed" || err == "convertFailed" || err == "padHasData" || err == "maxFileSize") {
|
||||
if (err == 'uploadFailed' || err == 'convertFailed' || err == 'padHasData' || err == 'maxFileSize') {
|
||||
status = err;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}).then(() => {
|
||||
// close the connection
|
||||
res.send("<script>document.addEventListener('DOMContentLoaded', function(){ var impexp = window.parent.padimpexp.handleFrameCall('" + req.directDatabaseAccess +"', '" + status + "'); })</script>");
|
||||
res.send(`<script>document.addEventListener('DOMContentLoaded', function(){ var impexp = window.parent.padimpexp.handleFrameCall('${req.directDatabaseAccess}', '${status}'); })</script>`);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -19,53 +19,53 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var log4js = require('log4js');
|
||||
var messageLogger = log4js.getLogger("message");
|
||||
var securityManager = require("../db/SecurityManager");
|
||||
var readOnlyManager = require("../db/ReadOnlyManager");
|
||||
var settings = require('../utils/Settings');
|
||||
const log4js = require('log4js');
|
||||
const messageLogger = log4js.getLogger('message');
|
||||
const securityManager = require('../db/SecurityManager');
|
||||
const readOnlyManager = require('../db/ReadOnlyManager');
|
||||
const settings = require('../utils/Settings');
|
||||
|
||||
/**
|
||||
* Saves all components
|
||||
* key is the component name
|
||||
* value is the component module
|
||||
*/
|
||||
var components = {};
|
||||
const components = {};
|
||||
|
||||
var socket;
|
||||
let socket;
|
||||
|
||||
/**
|
||||
* adds a component
|
||||
*/
|
||||
exports.addComponent = function(moduleName, module) {
|
||||
exports.addComponent = function (moduleName, module) {
|
||||
// save the component
|
||||
components[moduleName] = module;
|
||||
|
||||
// give the module the socket
|
||||
module.setSocketIO(socket);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* sets the socket.io and adds event functions for routing
|
||||
*/
|
||||
exports.setSocketIO = function(_socket) {
|
||||
exports.setSocketIO = function (_socket) {
|
||||
// save this socket internaly
|
||||
socket = _socket;
|
||||
|
||||
socket.sockets.on('connection', function(client) {
|
||||
socket.sockets.on('connection', (client) => {
|
||||
// wrap the original send function to log the messages
|
||||
client._send = client.send;
|
||||
client.send = function(message) {
|
||||
client.send = function (message) {
|
||||
messageLogger.debug(`to ${client.id}: ${JSON.stringify(message)}`);
|
||||
client._send(message);
|
||||
}
|
||||
};
|
||||
|
||||
// tell all components about this connect
|
||||
for (let i in components) {
|
||||
for (const i in components) {
|
||||
components[i].handleConnect(client);
|
||||
}
|
||||
|
||||
client.on('message', async function(message) {
|
||||
client.on('message', async (message) => {
|
||||
if (message.protocolVersion && message.protocolVersion != 2) {
|
||||
messageLogger.warn(`Protocolversion header is not correct: ${JSON.stringify(message)}`);
|
||||
return;
|
||||
|
@ -78,11 +78,11 @@ exports.setSocketIO = function(_socket) {
|
|||
await components[message.component].handleMessage(client, message);
|
||||
});
|
||||
|
||||
client.on('disconnect', function() {
|
||||
client.on('disconnect', () => {
|
||||
// tell all components about this disconnect
|
||||
for (let i in components) {
|
||||
for (const i in components) {
|
||||
components[i].handleDisconnect(client);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -18,7 +18,7 @@ let serverName;
|
|||
exports.server = null;
|
||||
|
||||
exports.createServer = async () => {
|
||||
console.log("Report bugs at https://github.com/ether/etherpad-lite/issues")
|
||||
console.log('Report bugs at https://github.com/ether/etherpad-lite/issues');
|
||||
|
||||
serverName = `Etherpad ${settings.getGitCommit()} (https://etherpad.org)`;
|
||||
|
||||
|
@ -26,7 +26,7 @@ exports.createServer = async () => {
|
|||
|
||||
await exports.restartServer();
|
||||
|
||||
if (settings.ip === "") {
|
||||
if (settings.ip === '') {
|
||||
// using Unix socket for connectivity
|
||||
console.log(`You can access your Etherpad instance using the Unix socket at ${settings.port}`);
|
||||
} else {
|
||||
|
@ -42,26 +42,26 @@ exports.createServer = async () => {
|
|||
const env = process.env.NODE_ENV || 'development';
|
||||
|
||||
if (env !== 'production') {
|
||||
console.warn("Etherpad is running in Development mode. This mode is slower for users and less secure than production mode. You should set the NODE_ENV environment variable to production by using: export NODE_ENV=production");
|
||||
console.warn('Etherpad is running in Development mode. This mode is slower for users and less secure than production mode. You should set the NODE_ENV environment variable to production by using: export NODE_ENV=production');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.restartServer = async () => {
|
||||
if (exports.server) {
|
||||
console.log("Restarting express server");
|
||||
console.log('Restarting express server');
|
||||
await util.promisify(exports.server.close).bind(exports.server)();
|
||||
}
|
||||
|
||||
const app = express(); // New syntax for express v3
|
||||
|
||||
if (settings.ssl) {
|
||||
console.log("SSL -- enabled");
|
||||
console.log('SSL -- enabled');
|
||||
console.log(`SSL -- server key file: ${settings.ssl.key}`);
|
||||
console.log(`SSL -- Certificate Authority's certificate file: ${settings.ssl.cert}`);
|
||||
|
||||
const options = {
|
||||
key: fs.readFileSync( settings.ssl.key ),
|
||||
cert: fs.readFileSync( settings.ssl.cert )
|
||||
key: fs.readFileSync(settings.ssl.key),
|
||||
cert: fs.readFileSync(settings.ssl.cert),
|
||||
};
|
||||
|
||||
if (settings.ssl.ca) {
|
||||
|
@ -79,16 +79,16 @@ exports.restartServer = async () => {
|
|||
exports.server = http.createServer(app);
|
||||
}
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
app.use((req, res, next) => {
|
||||
// res.header("X-Frame-Options", "deny"); // breaks embedded pads
|
||||
if (settings.ssl) {
|
||||
// we use SSL
|
||||
res.header("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
||||
res.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||
}
|
||||
|
||||
// Stop IE going into compatability mode
|
||||
// https://github.com/ether/etherpad-lite/issues/2547
|
||||
res.header("X-UA-Compatible", "IE=Edge,chrome=1");
|
||||
res.header('X-UA-Compatible', 'IE=Edge,chrome=1');
|
||||
|
||||
// Enable a strong referrer policy. Same-origin won't drop Referers when
|
||||
// loading local resources, but it will drop them when loading foreign resources.
|
||||
|
@ -97,11 +97,11 @@ exports.restartServer = async () => {
|
|||
// marked with <meta name="referrer" content="no-referrer">
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
|
||||
// https://github.com/ether/etherpad-lite/pull/3636
|
||||
res.header("Referrer-Policy", "same-origin");
|
||||
res.header('Referrer-Policy', 'same-origin');
|
||||
|
||||
// send git version in the Server response header if exposeVersion is true.
|
||||
if (settings.exposeVersion) {
|
||||
res.header("Server", serverName);
|
||||
res.header('Server', serverName);
|
||||
}
|
||||
|
||||
next();
|
||||
|
@ -165,13 +165,13 @@ exports.restartServer = async () => {
|
|||
//
|
||||
// reference: https://github.com/expressjs/session/blob/v1.17.0/README.md#cookiesecure
|
||||
secure: 'auto',
|
||||
}
|
||||
},
|
||||
});
|
||||
app.use(exports.sessionMiddleware);
|
||||
|
||||
app.use(cookieParser(settings.sessionKey, {}));
|
||||
|
||||
hooks.callAll("expressConfigure", {"app": app});
|
||||
hooks.callAll('expressConfigure', {app});
|
||||
hooks.callAll('expressCreateServer', {app, server: exports.server});
|
||||
|
||||
await util.promisify(exports.server.listen).bind(exports.server)(settings.port, settings.ip);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
var eejs = require('ep_etherpad-lite/node/eejs');
|
||||
const eejs = require('ep_etherpad-lite/node/eejs');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
args.app.get('/admin', function(req, res) {
|
||||
if('/' != req.path[req.path.length-1]) return res.redirect('./admin/');
|
||||
args.app.get('/admin', (req, res) => {
|
||||
if ('/' != req.path[req.path.length - 1]) return res.redirect('./admin/');
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/index.html', {req}));
|
||||
});
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
var eejs = require('ep_etherpad-lite/node/eejs');
|
||||
var settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
var installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
|
||||
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
|
||||
var _ = require('underscore');
|
||||
var semver = require('semver');
|
||||
const eejs = require('ep_etherpad-lite/node/eejs');
|
||||
const settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
const installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
|
||||
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
|
||||
const _ = require('underscore');
|
||||
const semver = require('semver');
|
||||
const UpdateCheck = require('ep_etherpad-lite/node/utils/UpdateCheck');
|
||||
|
||||
exports.expressCreateServer = function(hook_name, args, cb) {
|
||||
args.app.get('/admin/plugins', function(req, res) {
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
args.app.get('/admin/plugins', (req, res) => {
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins.html', {
|
||||
plugins: plugins.plugins,
|
||||
req,
|
||||
|
@ -16,114 +16,108 @@ exports.expressCreateServer = function(hook_name, args, cb) {
|
|||
}));
|
||||
});
|
||||
|
||||
args.app.get('/admin/plugins/info', function(req, res) {
|
||||
var gitCommit = settings.getGitCommit();
|
||||
var epVersion = settings.getEpVersion();
|
||||
args.app.get('/admin/plugins/info', (req, res) => {
|
||||
const gitCommit = settings.getGitCommit();
|
||||
const epVersion = settings.getEpVersion();
|
||||
|
||||
res.send(eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", {
|
||||
gitCommit: gitCommit,
|
||||
epVersion: epVersion,
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins-info.html', {
|
||||
gitCommit,
|
||||
epVersion,
|
||||
latestVersion: UpdateCheck.getLatestVersion(),
|
||||
req,
|
||||
}));
|
||||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
||||
exports.socketio = function(hook_name, args, cb) {
|
||||
var io = args.io.of("/pluginfw/installer");
|
||||
io.on('connection', function(socket) {
|
||||
exports.socketio = function (hook_name, args, cb) {
|
||||
const io = args.io.of('/pluginfw/installer');
|
||||
io.on('connection', (socket) => {
|
||||
if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return;
|
||||
|
||||
socket.on("getInstalled", function(query) {
|
||||
socket.on('getInstalled', (query) => {
|
||||
// send currently installed plugins
|
||||
var installed = Object.keys(plugins.plugins).map(function(plugin) {
|
||||
return plugins.plugins[plugin].package
|
||||
});
|
||||
const installed = Object.keys(plugins.plugins).map((plugin) => plugins.plugins[plugin].package);
|
||||
|
||||
socket.emit("results:installed", {installed: installed});
|
||||
socket.emit('results:installed', {installed});
|
||||
});
|
||||
|
||||
socket.on("checkUpdates", async function() {
|
||||
socket.on('checkUpdates', async () => {
|
||||
// Check plugins for updates
|
||||
try {
|
||||
let results = await installer.getAvailablePlugins(/*maxCacheAge:*/ 60 * 10);
|
||||
const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ 60 * 10);
|
||||
|
||||
var updatable = _(plugins.plugins).keys().filter(function(plugin) {
|
||||
const updatable = _(plugins.plugins).keys().filter((plugin) => {
|
||||
if (!results[plugin]) return false;
|
||||
|
||||
var latestVersion = results[plugin].version;
|
||||
var currentVersion = plugins.plugins[plugin].package.version;
|
||||
const latestVersion = results[plugin].version;
|
||||
const currentVersion = plugins.plugins[plugin].package.version;
|
||||
|
||||
return semver.gt(latestVersion, currentVersion);
|
||||
});
|
||||
|
||||
socket.emit("results:updatable", {updatable: updatable});
|
||||
socket.emit('results:updatable', {updatable});
|
||||
} catch (er) {
|
||||
console.warn(er);
|
||||
|
||||
socket.emit("results:updatable", {updatable: {}});
|
||||
socket.emit('results:updatable', {updatable: {}});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("getAvailable", async function(query) {
|
||||
socket.on('getAvailable', async (query) => {
|
||||
try {
|
||||
let results = await installer.getAvailablePlugins(/*maxCacheAge:*/ false);
|
||||
socket.emit("results:available", results);
|
||||
const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ false);
|
||||
socket.emit('results:available', results);
|
||||
} catch (er) {
|
||||
console.error(er);
|
||||
socket.emit("results:available", {});
|
||||
socket.emit('results:available', {});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("search", async function(query) {
|
||||
socket.on('search', async (query) => {
|
||||
try {
|
||||
let results = await installer.search(query.searchTerm, /*maxCacheAge:*/ 60 * 10);
|
||||
var res = Object.keys(results)
|
||||
.map(function(pluginName) {
|
||||
return results[pluginName];
|
||||
})
|
||||
.filter(function(plugin) {
|
||||
return !plugins.plugins[plugin.name];
|
||||
});
|
||||
const results = await installer.search(query.searchTerm, /* maxCacheAge:*/ 60 * 10);
|
||||
let res = Object.keys(results)
|
||||
.map((pluginName) => results[pluginName])
|
||||
.filter((plugin) => !plugins.plugins[plugin.name]);
|
||||
res = sortPluginList(res, query.sortBy, query.sortDir)
|
||||
.slice(query.offset, query.offset+query.limit);
|
||||
socket.emit("results:search", {results: res, query: query});
|
||||
.slice(query.offset, query.offset + query.limit);
|
||||
socket.emit('results:search', {results: res, query});
|
||||
} catch (er) {
|
||||
console.error(er);
|
||||
|
||||
socket.emit("results:search", {results: {}, query: query});
|
||||
socket.emit('results:search', {results: {}, query});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("install", function(plugin_name) {
|
||||
installer.install(plugin_name, function(er) {
|
||||
socket.on('install', (plugin_name) => {
|
||||
installer.install(plugin_name, (er) => {
|
||||
if (er) console.warn(er);
|
||||
|
||||
socket.emit("finished:install", {plugin: plugin_name, code: er? er.code : null, error: er? er.message : null});
|
||||
socket.emit('finished:install', {plugin: plugin_name, code: er ? er.code : null, error: er ? er.message : null});
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("uninstall", function(plugin_name) {
|
||||
installer.uninstall(plugin_name, function(er) {
|
||||
socket.on('uninstall', (plugin_name) => {
|
||||
installer.uninstall(plugin_name, (er) => {
|
||||
if (er) console.warn(er);
|
||||
|
||||
socket.emit("finished:uninstall", {plugin: plugin_name, error: er? er.message : null});
|
||||
socket.emit('finished:uninstall', {plugin: plugin_name, error: er ? er.message : null});
|
||||
});
|
||||
});
|
||||
});
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
||||
function sortPluginList(plugins, property, /*ASC?*/dir) {
|
||||
return plugins.sort(function(a, b) {
|
||||
function sortPluginList(plugins, property, /* ASC?*/dir) {
|
||||
return plugins.sort((a, b) => {
|
||||
if (a[property] < b[property]) {
|
||||
return dir? -1 : 1;
|
||||
return dir ? -1 : 1;
|
||||
}
|
||||
|
||||
if (a[property] > b[property]) {
|
||||
return dir? 1 : -1;
|
||||
return dir ? 1 : -1;
|
||||
}
|
||||
|
||||
// a must be equal to b
|
||||
|
|
|
@ -1,55 +1,52 @@
|
|||
var eejs = require('ep_etherpad-lite/node/eejs');
|
||||
var settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
|
||||
var fs = require('fs');
|
||||
const eejs = require('ep_etherpad-lite/node/eejs');
|
||||
const settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
const fs = require('fs');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
args.app.get('/admin/settings', function(req, res) {
|
||||
args.app.get('/admin/settings', (req, res) => {
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/settings.html', {
|
||||
req,
|
||||
settings: "",
|
||||
settings: '',
|
||||
search_results: {},
|
||||
errors: []
|
||||
errors: [],
|
||||
}));
|
||||
});
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
||||
exports.socketio = function (hook_name, args, cb) {
|
||||
var io = args.io.of("/settings");
|
||||
io.on('connection', function (socket) {
|
||||
|
||||
const io = args.io.of('/settings');
|
||||
io.on('connection', (socket) => {
|
||||
if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return;
|
||||
|
||||
socket.on("load", function (query) {
|
||||
fs.readFile('settings.json', 'utf8', function (err,data) {
|
||||
socket.on('load', (query) => {
|
||||
fs.readFile('settings.json', 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
return console.log(err);
|
||||
}
|
||||
|
||||
// if showSettingsInAdminPage is set to false, then return NOT_ALLOWED in the result
|
||||
if(settings.showSettingsInAdminPage === false) {
|
||||
socket.emit("settings", {results: 'NOT_ALLOWED'});
|
||||
}
|
||||
else {
|
||||
socket.emit("settings", {results: data});
|
||||
if (settings.showSettingsInAdminPage === false) {
|
||||
socket.emit('settings', {results: 'NOT_ALLOWED'});
|
||||
} else {
|
||||
socket.emit('settings', {results: data});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("saveSettings", function (settings) {
|
||||
fs.writeFile('settings.json', settings, function (err) {
|
||||
socket.on('saveSettings', (settings) => {
|
||||
fs.writeFile('settings.json', settings, (err) => {
|
||||
if (err) throw err;
|
||||
socket.emit("saveprogress", "saved");
|
||||
socket.emit('saveprogress', 'saved');
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('restartServer', async () => {
|
||||
console.log("Admin request to restart server through a socket on /admin/settings");
|
||||
console.log('Admin request to restart server through a socket on /admin/settings');
|
||||
settings.reloadSettings();
|
||||
await hooks.aCallAll('restartServer');
|
||||
});
|
||||
|
||||
});
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
var log4js = require('log4js');
|
||||
var clientLogger = log4js.getLogger("client");
|
||||
var formidable = require('formidable');
|
||||
var apiHandler = require('../../handler/APIHandler');
|
||||
const log4js = require('log4js');
|
||||
const clientLogger = log4js.getLogger('client');
|
||||
const formidable = require('formidable');
|
||||
const apiHandler = require('../../handler/APIHandler');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
//The Etherpad client side sends information about how a disconnect happened
|
||||
args.app.post('/ep/pad/connection-diagnostic-info', function(req, res) {
|
||||
new formidable.IncomingForm().parse(req, function(err, fields, files) {
|
||||
clientLogger.info("DIAGNOSTIC-INFO: " + fields.diagnosticInfo);
|
||||
res.end("OK");
|
||||
// The Etherpad client side sends information about how a disconnect happened
|
||||
args.app.post('/ep/pad/connection-diagnostic-info', (req, res) => {
|
||||
new formidable.IncomingForm().parse(req, (err, fields, files) => {
|
||||
clientLogger.info(`DIAGNOSTIC-INFO: ${fields.diagnosticInfo}`);
|
||||
res.end('OK');
|
||||
});
|
||||
});
|
||||
|
||||
//The Etherpad client side sends information about client side javscript errors
|
||||
args.app.post('/jserror', function(req, res) {
|
||||
new formidable.IncomingForm().parse(req, function(err, fields, files) {
|
||||
// The Etherpad client side sends information about client side javscript errors
|
||||
args.app.post('/jserror', (req, res) => {
|
||||
new formidable.IncomingForm().parse(req, (err, fields, files) => {
|
||||
try {
|
||||
var data = JSON.parse(fields.errorInfo)
|
||||
}catch(e){
|
||||
return res.end()
|
||||
var data = JSON.parse(fields.errorInfo);
|
||||
} catch (e) {
|
||||
return res.end();
|
||||
}
|
||||
clientLogger.warn(data.msg+' --', data);
|
||||
res.end("OK");
|
||||
clientLogger.warn(`${data.msg} --`, data);
|
||||
res.end('OK');
|
||||
});
|
||||
});
|
||||
|
||||
//Provide a possibility to query the latest available API version
|
||||
args.app.get('/api', function (req, res) {
|
||||
res.json({"currentVersion" : apiHandler.latestApiVersion});
|
||||
// Provide a possibility to query the latest available API version
|
||||
args.app.get('/api', (req, res) => {
|
||||
res.json({currentVersion: apiHandler.latestApiVersion});
|
||||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
var stats = require('ep_etherpad-lite/node/stats')
|
||||
const stats = require('ep_etherpad-lite/node/stats');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
exports.app = args.app;
|
||||
|
||||
// Handle errors
|
||||
args.app.use(function(err, req, res, next) {
|
||||
args.app.use((err, req, res, next) => {
|
||||
// if an error occurs Connect will pass it down
|
||||
// through these "error-handling" middleware
|
||||
// allowing you to respond however you like
|
||||
res.status(500).send({ error: 'Sorry, something bad happened!' });
|
||||
console.error(err.stack? err.stack : err.toString());
|
||||
stats.meter('http500').mark()
|
||||
res.status(500).send({error: 'Sorry, something bad happened!'});
|
||||
console.error(err.stack ? err.stack : err.toString());
|
||||
stats.meter('http500').mark();
|
||||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,44 +1,43 @@
|
|||
const assert = require('assert').strict;
|
||||
var hasPadAccess = require("../../padaccess");
|
||||
var settings = require('../../utils/Settings');
|
||||
var exportHandler = require('../../handler/ExportHandler');
|
||||
var importHandler = require('../../handler/ImportHandler');
|
||||
var padManager = require("../../db/PadManager");
|
||||
var readOnlyManager = require("../../db/ReadOnlyManager");
|
||||
var authorManager = require("../../db/AuthorManager");
|
||||
const rateLimit = require("express-rate-limit");
|
||||
const securityManager = require("../../db/SecurityManager");
|
||||
const webaccess = require("./webaccess");
|
||||
const hasPadAccess = require('../../padaccess');
|
||||
const settings = require('../../utils/Settings');
|
||||
const exportHandler = require('../../handler/ExportHandler');
|
||||
const importHandler = require('../../handler/ImportHandler');
|
||||
const padManager = require('../../db/PadManager');
|
||||
const readOnlyManager = require('../../db/ReadOnlyManager');
|
||||
const authorManager = require('../../db/AuthorManager');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const securityManager = require('../../db/SecurityManager');
|
||||
const webaccess = require('./webaccess');
|
||||
|
||||
settings.importExportRateLimiting.onLimitReached = function(req, res, options) {
|
||||
settings.importExportRateLimiting.onLimitReached = function (req, res, options) {
|
||||
// when the rate limiter triggers, write a warning in the logs
|
||||
console.warn(`Import/Export rate limiter triggered on "${req.originalUrl}" for IP address ${req.ip}`);
|
||||
}
|
||||
};
|
||||
|
||||
var limiter = rateLimit(settings.importExportRateLimiting);
|
||||
const limiter = rateLimit(settings.importExportRateLimiting);
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
|
||||
// handle export requests
|
||||
args.app.use('/p/:pad/:rev?/export/:type', limiter);
|
||||
args.app.get('/p/:pad/:rev?/export/:type', async function(req, res, next) {
|
||||
var types = ["pdf", "doc", "txt", "html", "odt", "etherpad"];
|
||||
//send a 404 if we don't support this filetype
|
||||
args.app.get('/p/:pad/:rev?/export/:type', async (req, res, next) => {
|
||||
const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad'];
|
||||
// send a 404 if we don't support this filetype
|
||||
if (types.indexOf(req.params.type) == -1) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// if abiword is disabled, and this is a format we only support with abiword, output a message
|
||||
if (settings.exportAvailable() == "no" &&
|
||||
["odt", "pdf", "doc"].indexOf(req.params.type) !== -1) {
|
||||
if (settings.exportAvailable() == 'no' &&
|
||||
['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) {
|
||||
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format. There is no converter configured`);
|
||||
|
||||
// ACHTUNG: do not include req.params.type in res.send() because there is no HTML escaping and it would lead to an XSS
|
||||
res.send("This export is not enabled at this Etherpad instance. Set the path to Abiword or soffice (LibreOffice) in settings.json to enable this feature");
|
||||
res.send('This export is not enabled at this Etherpad instance. Set the path to Abiword or soffice (LibreOffice) in settings.json to enable this feature');
|
||||
return;
|
||||
}
|
||||
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
|
||||
if (await hasPadAccess(req, res)) {
|
||||
let padId = req.params.pad;
|
||||
|
@ -49,7 +48,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
padId = await readOnlyManager.getPadId(readOnlyId);
|
||||
}
|
||||
|
||||
let exists = await padManager.doesPadExists(padId);
|
||||
const exists = await padManager.doesPadExists(padId);
|
||||
if (!exists) {
|
||||
console.warn(`Someone tried to export a pad that doesn't exist (${padId})`);
|
||||
return next();
|
||||
|
@ -62,7 +61,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
|
||||
// handle import requests
|
||||
args.app.use('/p/:pad/import', limiter);
|
||||
args.app.post('/p/:pad/import', async function(req, res, next) {
|
||||
args.app.post('/p/:pad/import', async (req, res, next) => {
|
||||
const {session: {user} = {}} = req;
|
||||
const {accessStatus} = await securityManager.checkAccess(
|
||||
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
|
||||
|
@ -73,4 +72,4 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -62,14 +62,14 @@ const RESERVED_WORDS = [
|
|||
'volatile',
|
||||
'while',
|
||||
'with',
|
||||
'yield'
|
||||
'yield',
|
||||
];
|
||||
|
||||
const regex = /^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:".+"|\'.+\'|\d+)\])*?$/;
|
||||
|
||||
module.exports.check = function(inputStr) {
|
||||
var isValid = true;
|
||||
inputStr.split(".").forEach(function(part) {
|
||||
module.exports.check = function (inputStr) {
|
||||
let isValid = true;
|
||||
inputStr.split('.').forEach((part) => {
|
||||
if (!regex.test(part)) {
|
||||
isValid = false;
|
||||
}
|
||||
|
@ -80,4 +80,4 @@ module.exports.check = function(inputStr) {
|
|||
});
|
||||
|
||||
return isValid;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
const OpenAPIBackend = require('openapi-backend').default;
|
||||
const formidable = require('formidable');
|
||||
const { promisify } = require('util');
|
||||
const {promisify} = require('util');
|
||||
const cloneDeep = require('lodash.clonedeep');
|
||||
const createHTTPError = require('http-errors');
|
||||
|
||||
|
@ -57,12 +57,12 @@ const resources = {
|
|||
create: {
|
||||
operationId: 'createGroup',
|
||||
summary: 'creates a new group',
|
||||
responseSchema: { groupID: { type: 'string' } },
|
||||
responseSchema: {groupID: {type: 'string'}},
|
||||
},
|
||||
createIfNotExistsFor: {
|
||||
operationId: 'createGroupIfNotExistsFor',
|
||||
summary: 'this functions helps you to map your application group ids to Etherpad group ids',
|
||||
responseSchema: { groupID: { type: 'string' } },
|
||||
responseSchema: {groupID: {type: 'string'}},
|
||||
},
|
||||
delete: {
|
||||
operationId: 'deleteGroup',
|
||||
|
@ -71,7 +71,7 @@ const resources = {
|
|||
listPads: {
|
||||
operationId: 'listPads',
|
||||
summary: 'returns all pads of this group',
|
||||
responseSchema: { padIDs: { type: 'array', items: { type: 'string' } } },
|
||||
responseSchema: {padIDs: {type: 'array', items: {type: 'string'}}},
|
||||
},
|
||||
createPad: {
|
||||
operationId: 'createGroupPad',
|
||||
|
@ -80,12 +80,12 @@ const resources = {
|
|||
listSessions: {
|
||||
operationId: 'listSessionsOfGroup',
|
||||
summary: '',
|
||||
responseSchema: { sessions: { type: 'array', items: { $ref: '#/components/schemas/SessionInfo' } } },
|
||||
responseSchema: {sessions: {type: 'array', items: {$ref: '#/components/schemas/SessionInfo'}}},
|
||||
},
|
||||
list: {
|
||||
operationId: 'listAllGroups',
|
||||
summary: '',
|
||||
responseSchema: { groupIDs: { type: 'array', items: { type: 'string' } } },
|
||||
responseSchema: {groupIDs: {type: 'array', items: {type: 'string'}}},
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -94,28 +94,28 @@ const resources = {
|
|||
create: {
|
||||
operationId: 'createAuthor',
|
||||
summary: 'creates a new author',
|
||||
responseSchema: { authorID: { type: 'string' } },
|
||||
responseSchema: {authorID: {type: 'string'}},
|
||||
},
|
||||
createIfNotExistsFor: {
|
||||
operationId: 'createAuthorIfNotExistsFor',
|
||||
summary: 'this functions helps you to map your application author ids to Etherpad author ids',
|
||||
responseSchema: { authorID: { type: 'string' } },
|
||||
responseSchema: {authorID: {type: 'string'}},
|
||||
},
|
||||
listPads: {
|
||||
operationId: 'listPadsOfAuthor',
|
||||
summary: 'returns an array of all pads this author contributed to',
|
||||
responseSchema: { padIDs: { type: 'array', items: { type: 'string' } } },
|
||||
responseSchema: {padIDs: {type: 'array', items: {type: 'string'}}},
|
||||
},
|
||||
listSessions: {
|
||||
operationId: 'listSessionsOfAuthor',
|
||||
summary: 'returns all sessions of an author',
|
||||
responseSchema: { sessions: { type: 'array', items: { $ref: '#/components/schemas/SessionInfo' } } },
|
||||
responseSchema: {sessions: {type: 'array', items: {$ref: '#/components/schemas/SessionInfo'}}},
|
||||
},
|
||||
// We need an operation that return a UserInfo so it can be picked up by the codegen :(
|
||||
getName: {
|
||||
operationId: 'getAuthorName',
|
||||
summary: 'Returns the Author Name of the author',
|
||||
responseSchema: { info: { $ref: '#/components/schemas/UserInfo' } },
|
||||
responseSchema: {info: {$ref: '#/components/schemas/UserInfo'}},
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -124,7 +124,7 @@ const resources = {
|
|||
create: {
|
||||
operationId: 'createSession',
|
||||
summary: 'creates a new session. validUntil is an unix timestamp in seconds',
|
||||
responseSchema: { sessionID: { type: 'string' } },
|
||||
responseSchema: {sessionID: {type: 'string'}},
|
||||
},
|
||||
delete: {
|
||||
operationId: 'deleteSession',
|
||||
|
@ -134,7 +134,7 @@ const resources = {
|
|||
info: {
|
||||
operationId: 'getSessionInfo',
|
||||
summary: 'returns informations about a session',
|
||||
responseSchema: { info: { $ref: '#/components/schemas/SessionInfo' } },
|
||||
responseSchema: {info: {$ref: '#/components/schemas/SessionInfo'}},
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -143,7 +143,7 @@ const resources = {
|
|||
listAll: {
|
||||
operationId: 'listAllPads',
|
||||
summary: 'list all the pads',
|
||||
responseSchema: { padIDs: { type: 'array', items: { type: 'string' } } },
|
||||
responseSchema: {padIDs: {type: 'array', items: {type: 'string'}}},
|
||||
},
|
||||
createDiffHTML: {
|
||||
operationId: 'createDiffHTML',
|
||||
|
@ -158,7 +158,7 @@ const resources = {
|
|||
getText: {
|
||||
operationId: 'getText',
|
||||
summary: 'returns the text of a pad',
|
||||
responseSchema: { text: { type: 'string' } },
|
||||
responseSchema: {text: {type: 'string'}},
|
||||
},
|
||||
setText: {
|
||||
operationId: 'setText',
|
||||
|
@ -167,7 +167,7 @@ const resources = {
|
|||
getHTML: {
|
||||
operationId: 'getHTML',
|
||||
summary: 'returns the text of a pad formatted as HTML',
|
||||
responseSchema: { html: { type: 'string' } },
|
||||
responseSchema: {html: {type: 'string'}},
|
||||
},
|
||||
setHTML: {
|
||||
operationId: 'setHTML',
|
||||
|
@ -176,12 +176,12 @@ const resources = {
|
|||
getRevisionsCount: {
|
||||
operationId: 'getRevisionsCount',
|
||||
summary: 'returns the number of revisions of this pad',
|
||||
responseSchema: { revisions: { type: 'integer' } },
|
||||
responseSchema: {revisions: {type: 'integer'}},
|
||||
},
|
||||
getLastEdited: {
|
||||
operationId: 'getLastEdited',
|
||||
summary: 'returns the timestamp of the last revision of the pad',
|
||||
responseSchema: { lastEdited: { type: 'integer' } },
|
||||
responseSchema: {lastEdited: {type: 'integer'}},
|
||||
},
|
||||
delete: {
|
||||
operationId: 'deletePad',
|
||||
|
@ -190,7 +190,7 @@ const resources = {
|
|||
getReadOnlyID: {
|
||||
operationId: 'getReadOnlyID',
|
||||
summary: 'returns the read only link of a pad',
|
||||
responseSchema: { readOnlyID: { type: 'string' } },
|
||||
responseSchema: {readOnlyID: {type: 'string'}},
|
||||
},
|
||||
setPublicStatus: {
|
||||
operationId: 'setPublicStatus',
|
||||
|
@ -199,22 +199,22 @@ const resources = {
|
|||
getPublicStatus: {
|
||||
operationId: 'getPublicStatus',
|
||||
summary: 'return true of false',
|
||||
responseSchema: { publicStatus: { type: 'boolean' } },
|
||||
responseSchema: {publicStatus: {type: 'boolean'}},
|
||||
},
|
||||
authors: {
|
||||
operationId: 'listAuthorsOfPad',
|
||||
summary: 'returns an array of authors who contributed to this pad',
|
||||
responseSchema: { authorIDs: { type: 'array', items: { type: 'string' } } },
|
||||
responseSchema: {authorIDs: {type: 'array', items: {type: 'string'}}},
|
||||
},
|
||||
usersCount: {
|
||||
operationId: 'padUsersCount',
|
||||
summary: 'returns the number of user that are currently editing this pad',
|
||||
responseSchema: { padUsersCount: { type: 'integer' } },
|
||||
responseSchema: {padUsersCount: {type: 'integer'}},
|
||||
},
|
||||
users: {
|
||||
operationId: 'padUsers',
|
||||
summary: 'returns the list of users that are currently editing this pad',
|
||||
responseSchema: { padUsers: { type: 'array', items: { $ref: '#/components/schemas/UserInfo' } } },
|
||||
responseSchema: {padUsers: {type: 'array', items: {$ref: '#/components/schemas/UserInfo'}}},
|
||||
},
|
||||
sendClientsMessage: {
|
||||
operationId: 'sendClientsMessage',
|
||||
|
@ -227,13 +227,13 @@ const resources = {
|
|||
getChatHistory: {
|
||||
operationId: 'getChatHistory',
|
||||
summary: 'returns the chat history',
|
||||
responseSchema: { messages: { type: 'array', items: { $ref: '#/components/schemas/Message' } } },
|
||||
responseSchema: {messages: {type: 'array', items: {$ref: '#/components/schemas/Message'}}},
|
||||
},
|
||||
// We need an operation that returns a Message so it can be picked up by the codegen :(
|
||||
getChatHead: {
|
||||
operationId: 'getChatHead',
|
||||
summary: 'returns the chatHead (chat-message) of the pad',
|
||||
responseSchema: { chatHead: { $ref: '#/components/schemas/Message' } },
|
||||
responseSchema: {chatHead: {$ref: '#/components/schemas/Message'}},
|
||||
},
|
||||
appendChatMessage: {
|
||||
operationId: 'appendChatMessage',
|
||||
|
@ -384,10 +384,10 @@ const defaultResponseRefs = {
|
|||
const operations = {};
|
||||
for (const resource in resources) {
|
||||
for (const action in resources[resource]) {
|
||||
const { operationId, responseSchema, ...operation } = resources[resource][action];
|
||||
const {operationId, responseSchema, ...operation} = resources[resource][action];
|
||||
|
||||
// add response objects
|
||||
const responses = { ...defaultResponseRefs };
|
||||
const responses = {...defaultResponseRefs};
|
||||
if (responseSchema) {
|
||||
responses[200] = cloneDeep(defaultResponses.Success);
|
||||
responses[200].content['application/json'].schema.properties.data = {
|
||||
|
@ -478,14 +478,14 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
|
|||
},
|
||||
},
|
||||
},
|
||||
security: [{ ApiKey: [] }],
|
||||
security: [{ApiKey: []}],
|
||||
};
|
||||
|
||||
// build operations
|
||||
for (const funcName in apiHandler.version[version]) {
|
||||
let operation = {};
|
||||
if (operations[funcName]) {
|
||||
operation = { ...operations[funcName] };
|
||||
operation = {...operations[funcName]};
|
||||
} else {
|
||||
// console.warn(`No operation found for function: ${funcName}`);
|
||||
operation = {
|
||||
|
@ -497,7 +497,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
|
|||
// set parameters
|
||||
operation.parameters = operation.parameters || [];
|
||||
for (const paramName of apiHandler.version[version][funcName]) {
|
||||
operation.parameters.push({ $ref: `#/components/parameters/${paramName}` });
|
||||
operation.parameters.push({$ref: `#/components/parameters/${paramName}`});
|
||||
if (!definition.components.parameters[paramName]) {
|
||||
definition.components.parameters[paramName] = {
|
||||
name: paramName,
|
||||
|
@ -533,7 +533,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
|
|||
};
|
||||
|
||||
exports.expressCreateServer = (hookName, args, cb) => {
|
||||
const { app } = args;
|
||||
const {app} = args;
|
||||
|
||||
// create openapi-backend handlers for each api version under /api/{version}/*
|
||||
for (const version in apiHandler.version) {
|
||||
|
@ -550,7 +550,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
app.get(`${apiRoot}/openapi.json`, (req, res) => {
|
||||
// For openapi definitions, wide CORS is probably fine
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.json({ ...definition, servers: [generateServerForApiVersion(apiRoot, req)] });
|
||||
res.json({...definition, servers: [generateServerForApiVersion(apiRoot, req)]});
|
||||
});
|
||||
|
||||
// serve latest openapi definition file under /api/openapi.json
|
||||
|
@ -558,7 +558,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
if (isLatestAPIVersion) {
|
||||
app.get(`/${style}/openapi.json`, (req, res) => {
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.json({ ...definition, servers: [generateServerForApiVersion(apiRoot, req)] });
|
||||
res.json({...definition, servers: [generateServerForApiVersion(apiRoot, req)]});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -586,7 +586,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
for (const funcName in apiHandler.version[version]) {
|
||||
const handler = async (c, req, res) => {
|
||||
// parse fields from request
|
||||
const { header, params, query } = c.request;
|
||||
const {header, params, query} = c.request;
|
||||
|
||||
// read form data if method was POST
|
||||
let formData = {};
|
||||
|
@ -602,7 +602,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
apiLogger.info(`REQUEST, v${version}:${funcName}, ${JSON.stringify(fields)}`);
|
||||
|
||||
// pass to api handler
|
||||
let data = await apiHandler.handle(version, funcName, fields, req, res).catch((err) => {
|
||||
const data = await apiHandler.handle(version, funcName, fields, req, res).catch((err) => {
|
||||
// convert all errors to http errors
|
||||
if (createHTTPError.isHttpError(err)) {
|
||||
// pass http errors thrown by handler forward
|
||||
|
@ -620,7 +620,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
});
|
||||
|
||||
// return in common format
|
||||
let response = { code: 0, message: 'ok', data: data || null };
|
||||
const response = {code: 0, message: 'ok', data: data || null};
|
||||
|
||||
// log response
|
||||
apiLogger.info(`RESPONSE, ${funcName}, ${JSON.stringify(response)}`);
|
||||
|
@ -654,24 +654,24 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
// https://github.com/ether/etherpad-lite/tree/master/doc/api/http_api.md#response-format
|
||||
switch (res.statusCode) {
|
||||
case 403: // forbidden
|
||||
response = { code: 4, message: err.message, data: null };
|
||||
response = {code: 4, message: err.message, data: null};
|
||||
break;
|
||||
case 401: // unauthorized (no or wrong api key)
|
||||
response = { code: 4, message: err.message, data: null };
|
||||
response = {code: 4, message: err.message, data: null};
|
||||
break;
|
||||
case 404: // not found (no such function)
|
||||
response = { code: 3, message: err.message, data: null };
|
||||
response = {code: 3, message: err.message, data: null};
|
||||
break;
|
||||
case 500: // server error (internal error)
|
||||
response = { code: 2, message: err.message, data: null };
|
||||
response = {code: 2, message: err.message, data: null};
|
||||
break;
|
||||
case 400: // bad request (wrong parameters)
|
||||
// respond with 200 OK to keep old behavior and pass tests
|
||||
res.statusCode = 200; // @TODO: this is bad api design
|
||||
response = { code: 1, message: err.message, data: null };
|
||||
response = {code: 1, message: err.message, data: null};
|
||||
break;
|
||||
default:
|
||||
response = { code: 1, message: err.message, data: null };
|
||||
response = {code: 1, message: err.message, data: null};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
var readOnlyManager = require("../../db/ReadOnlyManager");
|
||||
var hasPadAccess = require("../../padaccess");
|
||||
var exporthtml = require("../../utils/ExportHtml");
|
||||
const readOnlyManager = require('../../db/ReadOnlyManager');
|
||||
const hasPadAccess = require('../../padaccess');
|
||||
const exporthtml = require('../../utils/ExportHtml');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
// serve read only pad
|
||||
args.app.get('/ro/:id', async function(req, res) {
|
||||
|
||||
args.app.get('/ro/:id', async (req, res) => {
|
||||
// translate the read only pad to a padId
|
||||
let padId = await readOnlyManager.getPadId(req.params.id);
|
||||
const padId = await readOnlyManager.getPadId(req.params.id);
|
||||
if (padId == null) {
|
||||
res.status(404).send('404 - Not Found');
|
||||
return;
|
||||
|
@ -18,9 +17,9 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
|
||||
if (await hasPadAccess(req, res)) {
|
||||
// render the html document
|
||||
let html = await exporthtml.getPadHTMLDocument(padId, null);
|
||||
const html = await exporthtml.getPadHTMLDocument(padId, null);
|
||||
res.send(html);
|
||||
}
|
||||
});
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,30 +1,29 @@
|
|||
var padManager = require('../../db/PadManager');
|
||||
var url = require('url');
|
||||
const padManager = require('../../db/PadManager');
|
||||
const url = require('url');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
|
||||
// redirects browser to the pad's sanitized url if needed. otherwise, renders the html
|
||||
args.app.param('pad', async function (req, res, next, padId) {
|
||||
args.app.param('pad', async (req, res, next, padId) => {
|
||||
// ensure the padname is valid and the url doesn't end with a /
|
||||
if (!padManager.isValidPadId(padId) || /\/$/.test(req.url)) {
|
||||
res.status(404).send('Such a padname is forbidden');
|
||||
return;
|
||||
}
|
||||
|
||||
let sanitizedPadId = await padManager.sanitizePadId(padId);
|
||||
const sanitizedPadId = await padManager.sanitizePadId(padId);
|
||||
|
||||
if (sanitizedPadId === padId) {
|
||||
// the pad id was fine, so just render it
|
||||
next();
|
||||
} else {
|
||||
// the pad id was sanitized, so we redirect to the sanitized version
|
||||
var real_url = sanitizedPadId;
|
||||
let real_url = sanitizedPadId;
|
||||
real_url = encodeURIComponent(real_url);
|
||||
var query = url.parse(req.url).query;
|
||||
if ( query ) real_url += '?' + query;
|
||||
const query = url.parse(req.url).query;
|
||||
if (query) real_url += `?${query}`;
|
||||
res.header('Location', real_url);
|
||||
res.status(302).send('You should be redirected to <a href="' + real_url + '">' + real_url + '</a>');
|
||||
res.status(302).send(`You should be redirected to <a href="${real_url}">${real_url}</a>`);
|
||||
}
|
||||
});
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
const express = require("../express");
|
||||
const express = require('../express');
|
||||
const proxyaddr = require('proxy-addr');
|
||||
var settings = require('../../utils/Settings');
|
||||
var socketio = require('socket.io');
|
||||
var socketIORouter = require("../../handler/SocketIORouter");
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
|
||||
const settings = require('../../utils/Settings');
|
||||
const socketio = require('socket.io');
|
||||
const socketIORouter = require('../../handler/SocketIORouter');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
|
||||
var padMessageHandler = require("../../handler/PadMessageHandler");
|
||||
const padMessageHandler = require('../../handler/PadMessageHandler');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
//init socket.io and redirect all requests to the MessageHandler
|
||||
// init socket.io and redirect all requests to the MessageHandler
|
||||
// there shouldn't be a browser that isn't compatible to all
|
||||
// transports in this list at once
|
||||
// e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling
|
||||
var io = socketio({
|
||||
transports: settings.socketTransportProtocols
|
||||
const io = socketio({
|
||||
transports: settings.socketTransportProtocols,
|
||||
}).listen(args.server, {
|
||||
/*
|
||||
* Do not set the "io" cookie.
|
||||
|
@ -61,17 +61,17 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
// https://github.com/Automattic/socket.io/wiki/Migrating-to-1.0
|
||||
// This debug logging environment is set in Settings.js
|
||||
|
||||
//minify socket.io javascript
|
||||
// minify socket.io javascript
|
||||
// Due to a shitty decision by the SocketIO team minification is
|
||||
// no longer available, details available at:
|
||||
// http://stackoverflow.com/questions/23981741/minify-socket-io-socket-io-js-with-1-0
|
||||
// if(settings.minify) io.enable('browser client minification');
|
||||
|
||||
//Initalize the Socket.IO Router
|
||||
// Initalize the Socket.IO Router
|
||||
socketIORouter.setSocketIO(io);
|
||||
socketIORouter.addComponent("pad", padMessageHandler);
|
||||
socketIORouter.addComponent('pad', padMessageHandler);
|
||||
|
||||
hooks.callAll("socketio", {"app": args.app, "io": io, "server": args.server});
|
||||
hooks.callAll('socketio', {app: args.app, io, server: args.server});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,83 +1,81 @@
|
|||
var path = require('path');
|
||||
var eejs = require('ep_etherpad-lite/node/eejs');
|
||||
var toolbar = require("ep_etherpad-lite/node/utils/toolbar");
|
||||
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
var settings = require('../../utils/Settings');
|
||||
const path = require('path');
|
||||
const eejs = require('ep_etherpad-lite/node/eejs');
|
||||
const toolbar = require('ep_etherpad-lite/node/utils/toolbar');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
const settings = require('../../utils/Settings');
|
||||
const webaccess = require('./webaccess');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
// expose current stats
|
||||
args.app.get('/stats', function(req, res) {
|
||||
res.json(require('ep_etherpad-lite/node/stats').toJSON())
|
||||
})
|
||||
args.app.get('/stats', (req, res) => {
|
||||
res.json(require('ep_etherpad-lite/node/stats').toJSON());
|
||||
});
|
||||
|
||||
//serve index.html under /
|
||||
args.app.get('/', function(req, res) {
|
||||
// serve index.html under /
|
||||
args.app.get('/', (req, res) => {
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/index.html', {req}));
|
||||
});
|
||||
|
||||
//serve javascript.html
|
||||
args.app.get('/javascript', function(req, res) {
|
||||
// serve javascript.html
|
||||
args.app.get('/javascript', (req, res) => {
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/javascript.html', {req}));
|
||||
});
|
||||
|
||||
|
||||
//serve robots.txt
|
||||
args.app.get('/robots.txt', function(req, res) {
|
||||
var filePath = path.join(settings.root, "src", "static", "skins", settings.skinName, "robots.txt");
|
||||
res.sendFile(filePath, function(err) {
|
||||
//there is no custom robots.txt, send the default robots.txt which dissallows all
|
||||
if(err)
|
||||
{
|
||||
filePath = path.join(settings.root, "src", "static", "robots.txt");
|
||||
// serve robots.txt
|
||||
args.app.get('/robots.txt', (req, res) => {
|
||||
let filePath = path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'robots.txt');
|
||||
res.sendFile(filePath, (err) => {
|
||||
// there is no custom robots.txt, send the default robots.txt which dissallows all
|
||||
if (err) {
|
||||
filePath = path.join(settings.root, 'src', 'static', 'robots.txt');
|
||||
res.sendFile(filePath);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//serve pad.html under /p
|
||||
args.app.get('/p/:pad', function(req, res, next) {
|
||||
// serve pad.html under /p
|
||||
args.app.get('/p/:pad', (req, res, next) => {
|
||||
// The below might break for pads being rewritten
|
||||
const isReadOnly =
|
||||
req.url.indexOf("/p/r.") === 0 || !webaccess.userCanModify(req.params.pad, req);
|
||||
req.url.indexOf('/p/r.') === 0 || !webaccess.userCanModify(req.params.pad, req);
|
||||
|
||||
hooks.callAll("padInitToolbar", {
|
||||
toolbar: toolbar,
|
||||
isReadOnly: isReadOnly
|
||||
hooks.callAll('padInitToolbar', {
|
||||
toolbar,
|
||||
isReadOnly,
|
||||
});
|
||||
|
||||
res.send(eejs.require("ep_etherpad-lite/templates/pad.html", {
|
||||
req: req,
|
||||
toolbar: toolbar,
|
||||
isReadOnly: isReadOnly
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/pad.html', {
|
||||
req,
|
||||
toolbar,
|
||||
isReadOnly,
|
||||
}));
|
||||
});
|
||||
|
||||
//serve timeslider.html under /p/$padname/timeslider
|
||||
args.app.get('/p/:pad/timeslider', function(req, res, next) {
|
||||
hooks.callAll("padInitToolbar", {
|
||||
toolbar: toolbar
|
||||
// serve timeslider.html under /p/$padname/timeslider
|
||||
args.app.get('/p/:pad/timeslider', (req, res, next) => {
|
||||
hooks.callAll('padInitToolbar', {
|
||||
toolbar,
|
||||
});
|
||||
|
||||
res.send(eejs.require("ep_etherpad-lite/templates/timeslider.html", {
|
||||
req: req,
|
||||
toolbar: toolbar
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', {
|
||||
req,
|
||||
toolbar,
|
||||
}));
|
||||
});
|
||||
|
||||
//serve favicon.ico from all path levels except as a pad name
|
||||
args.app.get( /\/favicon.ico$/, function(req, res) {
|
||||
var filePath = path.join(settings.root, "src", "static", "skins", settings.skinName, "favicon.ico");
|
||||
// serve favicon.ico from all path levels except as a pad name
|
||||
args.app.get(/\/favicon.ico$/, (req, res) => {
|
||||
let filePath = path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico');
|
||||
|
||||
res.sendFile(filePath, function(err) {
|
||||
//there is no custom favicon, send the default favicon
|
||||
if(err)
|
||||
{
|
||||
filePath = path.join(settings.root, "src", "static", "favicon.ico");
|
||||
res.sendFile(filePath, (err) => {
|
||||
// there is no custom favicon, send the default favicon
|
||||
if (err) {
|
||||
filePath = path.join(settings.root, 'src', 'static', 'favicon.ico');
|
||||
res.sendFile(filePath);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
var minify = require('../../utils/Minify');
|
||||
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugin_defs");
|
||||
var CachingMiddleware = require('../../utils/caching_middleware');
|
||||
var settings = require("../../utils/Settings");
|
||||
var Yajsml = require('etherpad-yajsml');
|
||||
var _ = require("underscore");
|
||||
const minify = require('../../utils/Minify');
|
||||
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
|
||||
const CachingMiddleware = require('../../utils/caching_middleware');
|
||||
const settings = require('../../utils/Settings');
|
||||
const Yajsml = require('etherpad-yajsml');
|
||||
const _ = require('underscore');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
|
||||
// Cache both minified and static.
|
||||
var assetCache = new CachingMiddleware;
|
||||
const assetCache = new CachingMiddleware();
|
||||
args.app.all(/\/javascripts\/(.*)/, assetCache.handle);
|
||||
|
||||
// Minify will serve static files compressed (minify enabled). It also has
|
||||
|
@ -18,43 +17,42 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
// Setup middleware that will package JavaScript files served by minify for
|
||||
// CommonJS loader on the client-side.
|
||||
// Hostname "invalid.invalid" is a dummy value to allow parsing as a URI.
|
||||
var jsServer = new (Yajsml.Server)({
|
||||
rootPath: 'javascripts/src/'
|
||||
, rootURI: 'http://invalid.invalid/static/js/'
|
||||
, libraryPath: 'javascripts/lib/'
|
||||
, libraryURI: 'http://invalid.invalid/static/plugins/'
|
||||
, requestURIs: minify.requestURIs // Loop-back is causing problems, this is a workaround.
|
||||
const jsServer = new (Yajsml.Server)({
|
||||
rootPath: 'javascripts/src/',
|
||||
rootURI: 'http://invalid.invalid/static/js/',
|
||||
libraryPath: 'javascripts/lib/',
|
||||
libraryURI: 'http://invalid.invalid/static/plugins/',
|
||||
requestURIs: minify.requestURIs, // Loop-back is causing problems, this is a workaround.
|
||||
});
|
||||
|
||||
var StaticAssociator = Yajsml.associators.StaticAssociator;
|
||||
var associations =
|
||||
const StaticAssociator = Yajsml.associators.StaticAssociator;
|
||||
const associations =
|
||||
Yajsml.associators.associationsForSimpleMapping(minify.tar);
|
||||
var associator = new StaticAssociator(associations);
|
||||
const associator = new StaticAssociator(associations);
|
||||
jsServer.setAssociator(associator);
|
||||
|
||||
args.app.use(jsServer.handle.bind(jsServer));
|
||||
|
||||
// serve plugin definitions
|
||||
// not very static, but served here so that client can do require("pluginfw/static/js/plugin-definitions.js");
|
||||
args.app.get('/pluginfw/plugin-definitions.json', function (req, res, next) {
|
||||
args.app.get('/pluginfw/plugin-definitions.json', (req, res, next) => {
|
||||
const clientParts = _(plugins.parts)
|
||||
.filter((part) => _(part).has('client_hooks'));
|
||||
|
||||
var clientParts = _(plugins.parts)
|
||||
.filter(function(part){ return _(part).has('client_hooks') });
|
||||
|
||||
var clientPlugins = {};
|
||||
const clientPlugins = {};
|
||||
|
||||
_(clientParts).chain()
|
||||
.map(function(part){ return part.plugin })
|
||||
.uniq()
|
||||
.each(function(name){
|
||||
clientPlugins[name] = _(plugins.plugins[name]).clone();
|
||||
delete clientPlugins[name]['package'];
|
||||
});
|
||||
.map((part) => part.plugin)
|
||||
.uniq()
|
||||
.each((name) => {
|
||||
clientPlugins[name] = _(plugins.plugins[name]).clone();
|
||||
delete clientPlugins[name].package;
|
||||
});
|
||||
|
||||
res.header("Content-Type","application/json; charset=utf-8");
|
||||
res.write(JSON.stringify({"plugins": clientPlugins, "parts": clientParts}));
|
||||
res.header('Content-Type', 'application/json; charset=utf-8');
|
||||
res.write(JSON.stringify({plugins: clientPlugins, parts: clientParts}));
|
||||
res.end();
|
||||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
var path = require("path")
|
||||
, npm = require("npm")
|
||||
, fs = require("fs")
|
||||
, util = require("util");
|
||||
const path = require('path');
|
||||
const npm = require('npm');
|
||||
const fs = require('fs');
|
||||
const util = require('util');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
args.app.get('/tests/frontend/specs_list.js', async function(req, res) {
|
||||
let [coreTests, pluginTests] = await Promise.all([
|
||||
args.app.get('/tests/frontend/specs_list.js', async (req, res) => {
|
||||
const [coreTests, pluginTests] = await Promise.all([
|
||||
exports.getCoreTests(),
|
||||
exports.getPluginTests()
|
||||
exports.getPluginTests(),
|
||||
]);
|
||||
|
||||
// merge the two sets of results
|
||||
|
@ -16,79 +16,77 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
// Keep only *.js files
|
||||
files = files.filter((f) => f.endsWith('.js'));
|
||||
|
||||
console.debug("Sent browser the following test specs:", files);
|
||||
console.debug('Sent browser the following test specs:', files);
|
||||
res.setHeader('content-type', 'text/javascript');
|
||||
res.end("var specs_list = " + JSON.stringify(files) + ";\n");
|
||||
res.end(`var specs_list = ${JSON.stringify(files)};\n`);
|
||||
});
|
||||
|
||||
// path.join seems to normalize by default, but we'll just be explicit
|
||||
var rootTestFolder = path.normalize(path.join(npm.root, "../tests/frontend/"));
|
||||
const rootTestFolder = path.normalize(path.join(npm.root, '../tests/frontend/'));
|
||||
|
||||
var url2FilePath = function(url) {
|
||||
var subPath = url.substr("/tests/frontend".length);
|
||||
if (subPath == "") {
|
||||
subPath = "index.html"
|
||||
const url2FilePath = function (url) {
|
||||
let subPath = url.substr('/tests/frontend'.length);
|
||||
if (subPath == '') {
|
||||
subPath = 'index.html';
|
||||
}
|
||||
subPath = subPath.split("?")[0];
|
||||
subPath = subPath.split('?')[0];
|
||||
|
||||
var filePath = path.normalize(path.join(rootTestFolder, subPath));
|
||||
let filePath = path.normalize(path.join(rootTestFolder, subPath));
|
||||
|
||||
// make sure we jail the paths to the test folder, otherwise serve index
|
||||
if (filePath.indexOf(rootTestFolder) !== 0) {
|
||||
filePath = path.join(rootTestFolder, "index.html");
|
||||
filePath = path.join(rootTestFolder, 'index.html');
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
};
|
||||
|
||||
args.app.get('/tests/frontend/specs/*', function (req, res) {
|
||||
var specFilePath = url2FilePath(req.url);
|
||||
var specFileName = path.basename(specFilePath);
|
||||
args.app.get('/tests/frontend/specs/*', (req, res) => {
|
||||
const specFilePath = url2FilePath(req.url);
|
||||
const specFileName = path.basename(specFilePath);
|
||||
|
||||
fs.readFile(specFilePath, function(err, content) {
|
||||
fs.readFile(specFilePath, (err, content) => {
|
||||
if (err) { return res.send(500); }
|
||||
|
||||
content = "describe(" + JSON.stringify(specFileName) + ", function(){ " + content + " });";
|
||||
content = `describe(${JSON.stringify(specFileName)}, function(){ ${content} });`;
|
||||
|
||||
res.send(content);
|
||||
});
|
||||
});
|
||||
|
||||
args.app.get('/tests/frontend/*', function (req, res) {
|
||||
var filePath = url2FilePath(req.url);
|
||||
args.app.get('/tests/frontend/*', (req, res) => {
|
||||
const filePath = url2FilePath(req.url);
|
||||
res.sendFile(filePath);
|
||||
});
|
||||
|
||||
args.app.get('/tests/frontend', function (req, res) {
|
||||
args.app.get('/tests/frontend', (req, res) => {
|
||||
res.redirect('/tests/frontend/index.html');
|
||||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
||||
const readdir = util.promisify(fs.readdir);
|
||||
|
||||
exports.getPluginTests = async function(callback) {
|
||||
const moduleDir = "node_modules/";
|
||||
const specPath = "/static/tests/frontend/specs/";
|
||||
const staticDir = "/static/plugins/";
|
||||
exports.getPluginTests = async function (callback) {
|
||||
const moduleDir = 'node_modules/';
|
||||
const specPath = '/static/tests/frontend/specs/';
|
||||
const staticDir = '/static/plugins/';
|
||||
|
||||
let pluginSpecs = [];
|
||||
const pluginSpecs = [];
|
||||
|
||||
let plugins = await readdir(moduleDir);
|
||||
let promises = plugins
|
||||
.map(plugin => [ plugin, moduleDir + plugin + specPath] )
|
||||
.filter(([plugin, specDir]) => fs.existsSync(specDir)) // check plugin exists
|
||||
.map(([plugin, specDir]) => {
|
||||
return readdir(specDir)
|
||||
.then(specFiles => specFiles.map(spec => {
|
||||
pluginSpecs.push(staticDir + plugin + specPath + spec);
|
||||
}));
|
||||
});
|
||||
const plugins = await readdir(moduleDir);
|
||||
const promises = plugins
|
||||
.map((plugin) => [plugin, moduleDir + plugin + specPath])
|
||||
.filter(([plugin, specDir]) => fs.existsSync(specDir)) // check plugin exists
|
||||
.map(([plugin, specDir]) => readdir(specDir)
|
||||
.then((specFiles) => specFiles.map((spec) => {
|
||||
pluginSpecs.push(staticDir + plugin + specPath + spec);
|
||||
})));
|
||||
|
||||
return Promise.all(promises).then(() => pluginSpecs);
|
||||
}
|
||||
};
|
||||
|
||||
exports.getCoreTests = function() {
|
||||
exports.getCoreTests = function () {
|
||||
// get the core test specs
|
||||
return readdir('tests/frontend/specs');
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,58 +1,58 @@
|
|||
var languages = require('languages4translatewiki')
|
||||
, fs = require('fs')
|
||||
, path = require('path')
|
||||
, _ = require('underscore')
|
||||
, npm = require('npm')
|
||||
, plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs.js').plugins
|
||||
, semver = require('semver')
|
||||
, existsSync = require('../utils/path_exists')
|
||||
, settings = require('../utils/Settings')
|
||||
const languages = require('languages4translatewiki');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const _ = require('underscore');
|
||||
const npm = require('npm');
|
||||
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs.js').plugins;
|
||||
const semver = require('semver');
|
||||
const existsSync = require('../utils/path_exists');
|
||||
const settings = require('../utils/Settings')
|
||||
;
|
||||
|
||||
|
||||
// returns all existing messages merged together and grouped by langcode
|
||||
// {es: {"foo": "string"}, en:...}
|
||||
function getAllLocales() {
|
||||
var locales2paths = {};
|
||||
const locales2paths = {};
|
||||
|
||||
// Puts the paths of all locale files contained in a given directory
|
||||
// into `locales2paths` (files from various dirs are grouped by lang code)
|
||||
// (only json files with valid language code as name)
|
||||
function extractLangs(dir) {
|
||||
if(!existsSync(dir)) return;
|
||||
var stat = fs.lstatSync(dir);
|
||||
if (!existsSync(dir)) return;
|
||||
let stat = fs.lstatSync(dir);
|
||||
if (!stat.isDirectory() || stat.isSymbolicLink()) return;
|
||||
|
||||
fs.readdirSync(dir).forEach(function(file) {
|
||||
fs.readdirSync(dir).forEach((file) => {
|
||||
file = path.resolve(dir, file);
|
||||
stat = fs.lstatSync(file);
|
||||
if (stat.isDirectory() || stat.isSymbolicLink()) return;
|
||||
|
||||
var ext = path.extname(file)
|
||||
, locale = path.basename(file, ext).toLowerCase();
|
||||
const ext = path.extname(file);
|
||||
const locale = path.basename(file, ext).toLowerCase();
|
||||
|
||||
if ((ext == '.json') && languages.isValid(locale)) {
|
||||
if(!locales2paths[locale]) locales2paths[locale] = [];
|
||||
if (!locales2paths[locale]) locales2paths[locale] = [];
|
||||
locales2paths[locale].push(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//add core supported languages first
|
||||
extractLangs(npm.root+"/ep_etherpad-lite/locales");
|
||||
// add core supported languages first
|
||||
extractLangs(`${npm.root}/ep_etherpad-lite/locales`);
|
||||
|
||||
//add plugins languages (if any)
|
||||
for(var pluginName in plugins) extractLangs(path.join(npm.root, pluginName, 'locales'));
|
||||
// add plugins languages (if any)
|
||||
for (const pluginName in plugins) extractLangs(path.join(npm.root, pluginName, 'locales'));
|
||||
|
||||
// Build a locale index (merge all locale data other than user-supplied overrides)
|
||||
var locales = {}
|
||||
_.each (locales2paths, function(files, langcode) {
|
||||
locales[langcode]={};
|
||||
const locales = {};
|
||||
_.each(locales2paths, (files, langcode) => {
|
||||
locales[langcode] = {};
|
||||
|
||||
files.forEach(function(file) {
|
||||
files.forEach((file) => {
|
||||
let fileContents;
|
||||
try {
|
||||
fileContents = JSON.parse(fs.readFileSync(file,'utf8'));
|
||||
fileContents = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
} catch (err) {
|
||||
console.error(`failed to read JSON file ${file}: ${err}`);
|
||||
throw err;
|
||||
|
@ -64,17 +64,17 @@ function getAllLocales() {
|
|||
// Add custom strings from settings.json
|
||||
// Since this is user-supplied, we'll do some extra sanity checks
|
||||
const wrongFormatErr = Error(
|
||||
"customLocaleStrings in wrong format. See documentation " +
|
||||
"for Customization for Administrators, under Localization.")
|
||||
'customLocaleStrings in wrong format. See documentation ' +
|
||||
'for Customization for Administrators, under Localization.');
|
||||
if (settings.customLocaleStrings) {
|
||||
if (typeof settings.customLocaleStrings !== "object") throw wrongFormatErr
|
||||
_.each(settings.customLocaleStrings, function(overrides, langcode) {
|
||||
if (typeof overrides !== "object") throw wrongFormatErr
|
||||
_.each(overrides, function(localeString, key) {
|
||||
if (typeof localeString !== "string") throw wrongFormatErr
|
||||
locales[langcode][key] = localeString
|
||||
})
|
||||
})
|
||||
if (typeof settings.customLocaleStrings !== 'object') throw wrongFormatErr;
|
||||
_.each(settings.customLocaleStrings, (overrides, langcode) => {
|
||||
if (typeof overrides !== 'object') throw wrongFormatErr;
|
||||
_.each(overrides, (localeString, key) => {
|
||||
if (typeof localeString !== 'string') throw wrongFormatErr;
|
||||
locales[langcode][key] = localeString;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return locales;
|
||||
|
@ -83,45 +83,44 @@ function getAllLocales() {
|
|||
// returns a hash of all available languages availables with nativeName and direction
|
||||
// e.g. { es: {nativeName: "español", direction: "ltr"}, ... }
|
||||
function getAvailableLangs(locales) {
|
||||
var result = {};
|
||||
_.each(_.keys(locales), function(langcode) {
|
||||
const result = {};
|
||||
_.each(_.keys(locales), (langcode) => {
|
||||
result[langcode] = languages.getLanguageInfo(langcode);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// returns locale index that will be served in /locales.json
|
||||
var generateLocaleIndex = function (locales) {
|
||||
var result = _.clone(locales) // keep English strings
|
||||
_.each(_.keys(locales), function(langcode) {
|
||||
if (langcode != 'en') result[langcode]='locales/'+langcode+'.json';
|
||||
const generateLocaleIndex = function (locales) {
|
||||
const result = _.clone(locales); // keep English strings
|
||||
_.each(_.keys(locales), (langcode) => {
|
||||
if (langcode != 'en') result[langcode] = `locales/${langcode}.json`;
|
||||
});
|
||||
return JSON.stringify(result);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
exports.expressCreateServer = function(n, args, cb) {
|
||||
|
||||
//regenerate locales on server restart
|
||||
var locales = getAllLocales();
|
||||
var localeIndex = generateLocaleIndex(locales);
|
||||
exports.expressCreateServer = function (n, args, cb) {
|
||||
// regenerate locales on server restart
|
||||
const locales = getAllLocales();
|
||||
const localeIndex = generateLocaleIndex(locales);
|
||||
exports.availableLangs = getAvailableLangs(locales);
|
||||
|
||||
args.app.get ('/locales/:locale', function(req, res) {
|
||||
//works with /locale/en and /locale/en.json requests
|
||||
var locale = req.params.locale.split('.')[0];
|
||||
args.app.get('/locales/:locale', (req, res) => {
|
||||
// works with /locale/en and /locale/en.json requests
|
||||
const locale = req.params.locale.split('.')[0];
|
||||
if (exports.availableLangs.hasOwnProperty(locale)) {
|
||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
res.send('{"'+locale+'":'+JSON.stringify(locales[locale])+'}');
|
||||
res.send(`{"${locale}":${JSON.stringify(locales[locale])}}`);
|
||||
} else {
|
||||
res.status(404).send('Language not available');
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
args.app.get('/locales.json', function(req, res) {
|
||||
args.app.get('/locales.json', (req, res) => {
|
||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
res.send(localeIndex);
|
||||
})
|
||||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var securityManager = require('./db/SecurityManager');
|
||||
const securityManager = require('./db/SecurityManager');
|
||||
|
||||
// checks for padAccess
|
||||
module.exports = async function (req, res) {
|
||||
|
@ -7,7 +7,7 @@ module.exports = async function (req, res) {
|
|||
const accessObj = await securityManager.checkAccess(
|
||||
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
|
||||
|
||||
if (accessObj.accessStatus === "grant") {
|
||||
if (accessObj.accessStatus === 'grant') {
|
||||
// there is access, continue
|
||||
return true;
|
||||
} else {
|
||||
|
@ -19,4 +19,4 @@ module.exports = async function (req, res) {
|
|||
// @TODO - send internal server error here?
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -61,13 +61,13 @@ exports.start = async () => {
|
|||
try {
|
||||
await db.init();
|
||||
await plugins.update();
|
||||
console.info('Installed plugins: ' + plugins.formatPluginsWithVersion());
|
||||
console.debug('Installed parts:\n' + plugins.formatParts());
|
||||
console.debug('Installed hooks:\n' + plugins.formatHooks());
|
||||
console.info(`Installed plugins: ${plugins.formatPluginsWithVersion()}`);
|
||||
console.debug(`Installed parts:\n${plugins.formatParts()}`);
|
||||
console.debug(`Installed hooks:\n${plugins.formatHooks()}`);
|
||||
await hooks.aCallAll('loadSettings', {settings});
|
||||
await hooks.aCallAll('createServer');
|
||||
} catch (e) {
|
||||
console.error('exception thrown: ' + e.message);
|
||||
console.error(`exception thrown: ${e.message}`);
|
||||
if (e.stack) console.log(e.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var measured = require('measured-core')
|
||||
const measured = require('measured-core');
|
||||
|
||||
module.exports = measured.createCollection();
|
||||
|
||||
|
|
|
@ -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