lint: Run eslint --fix on src/

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

View file

@ -18,19 +18,19 @@
* limitations under the License.
*/
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var customError = require("../utils/customError");
var padManager = require("./PadManager");
var padMessageHandler = require("../handler/PadMessageHandler");
var readOnlyManager = require("./ReadOnlyManager");
var groupManager = require("./GroupManager");
var authorManager = require("./AuthorManager");
var sessionManager = require("./SessionManager");
var exportHtml = require("../utils/ExportHtml");
var exportTxt = require("../utils/ExportTxt");
var importHtml = require("../utils/ImportHtml");
var cleanText = require("./Pad").cleanText;
var PadDiff = require("../utils/padDiff");
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const customError = require('../utils/customError');
const padManager = require('./PadManager');
const padMessageHandler = require('../handler/PadMessageHandler');
const readOnlyManager = require('./ReadOnlyManager');
const groupManager = require('./GroupManager');
const authorManager = require('./AuthorManager');
const sessionManager = require('./SessionManager');
const exportHtml = require('../utils/ExportHtml');
const exportTxt = require('../utils/ExportTxt');
const importHtml = require('../utils/ImportHtml');
const cleanText = require('./Pad').cleanText;
const PadDiff = require('../utils/padDiff');
/* ********************
* GROUP FUNCTIONS ****
@ -101,10 +101,10 @@ Example returns:
}
*/
exports.getAttributePool = async function(padID) {
let pad = await getPadSafe(padID, true);
return { pool: pad.pool };
}
exports.getAttributePool = async function (padID) {
const pad = await getPadSafe(padID, true);
return {pool: pad.pool};
};
/**
getRevisionChangeset (padID, [rev])
@ -119,22 +119,21 @@ Example returns:
}
*/
exports.getRevisionChangeset = async function(padID, rev) {
exports.getRevisionChangeset = async function (padID, rev) {
// try to parse the revision number
if (rev !== undefined) {
rev = checkValidRev(rev);
}
// get the pad
let pad = await getPadSafe(padID, true);
let head = pad.getHeadRevisionNumber();
const pad = await getPadSafe(padID, true);
const head = pad.getHeadRevisionNumber();
// the client asked for a special revision
if (rev !== undefined) {
// check if this is a valid revision
if (rev > head) {
throw new customError("rev is higher than the head revision of the pad", "apierror");
throw new customError('rev is higher than the head revision of the pad', 'apierror');
}
// get the changeset for this revision
@ -143,7 +142,7 @@ exports.getRevisionChangeset = async function(padID, rev) {
// the client wants the latest changeset, lets return it to him
return pad.getRevisionChangeset(head);
}
};
/**
getText(padID, [rev]) returns the text of a pad
@ -153,33 +152,32 @@ Example returns:
{code: 0, message:"ok", data: {text:"Welcome Text"}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getText = async function(padID, rev) {
exports.getText = async function (padID, rev) {
// try to parse the revision number
if (rev !== undefined) {
rev = checkValidRev(rev);
}
// get the pad
let pad = await getPadSafe(padID, true);
let head = pad.getHeadRevisionNumber();
const pad = await getPadSafe(padID, true);
const head = pad.getHeadRevisionNumber();
// the client asked for a special revision
if (rev !== undefined) {
// check if this is a valid revision
if (rev > head) {
throw new customError("rev is higher than the head revision of the pad", "apierror");
throw new customError('rev is higher than the head revision of the pad', 'apierror');
}
// get the text of this revision
let text = await pad.getInternalRevisionAText(rev);
return { text };
const text = await pad.getInternalRevisionAText(rev);
return {text};
}
// the client wants the latest text, lets return it to him
let text = exportTxt.getTXTFromAtext(pad, pad.atext);
return { text };
}
const text = exportTxt.getTXTFromAtext(pad, pad.atext);
return {text};
};
/**
setText(padID, text) sets the text of a pad
@ -190,20 +188,20 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
{code: 1, message:"text too long", data: null}
*/
exports.setText = async function(padID, text) {
exports.setText = async function (padID, text) {
// text is required
if (typeof text !== "string") {
throw new customError("text is not a string", "apierror");
if (typeof text !== 'string') {
throw new customError('text is not a string', 'apierror');
}
// get the pad
let pad = await getPadSafe(padID, true);
const pad = await getPadSafe(padID, true);
await Promise.all([
pad.setText(text),
padMessageHandler.updatePadClients(pad),
]);
}
};
/**
appendText(padID, text) appends text to a pad
@ -214,18 +212,18 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
{code: 1, message:"text too long", data: null}
*/
exports.appendText = async function(padID, text) {
exports.appendText = async function (padID, text) {
// text is required
if (typeof text !== "string") {
throw new customError("text is not a string", "apierror");
if (typeof text !== 'string') {
throw new customError('text is not a string', 'apierror');
}
let pad = await getPadSafe(padID, true);
const pad = await getPadSafe(padID, true);
await Promise.all([
pad.appendText(text),
padMessageHandler.updatePadClients(pad),
]);
}
};
/**
getHTML(padID, [rev]) returns the html of a pad
@ -235,19 +233,19 @@ Example returns:
{code: 0, message:"ok", data: {text:"Welcome <strong>Text</strong>"}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getHTML = async function(padID, rev) {
exports.getHTML = async function (padID, rev) {
if (rev !== undefined) {
rev = checkValidRev(rev);
}
let pad = await getPadSafe(padID, true);
const pad = await getPadSafe(padID, true);
// the client asked for a special revision
if (rev !== undefined) {
// check if this is a valid revision
let head = pad.getHeadRevisionNumber();
const head = pad.getHeadRevisionNumber();
if (rev > head) {
throw new customError("rev is higher than the head revision of the pad", "apierror");
throw new customError('rev is higher than the head revision of the pad', 'apierror');
}
}
@ -255,9 +253,9 @@ exports.getHTML = async function(padID, rev) {
let html = await exportHtml.getPadHTML(pad, rev);
// wrap the HTML
html = "<!DOCTYPE HTML><html><body>" + html + "</body></html>";
return { html };
}
html = `<!DOCTYPE HTML><html><body>${html}</body></html>`;
return {html};
};
/**
setHTML(padID, html) sets the text of a pad based on HTML
@ -267,20 +265,20 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.setHTML = async function(padID, html) {
exports.setHTML = async function (padID, html) {
// html string is required
if (typeof html !== "string") {
throw new customError("html is not a string", "apierror");
if (typeof html !== 'string') {
throw new customError('html is not a string', 'apierror');
}
// get the pad
let pad = await getPadSafe(padID, true);
const pad = await getPadSafe(padID, true);
// add a new changeset with the new html to the pad
try {
await importHtml.setPadHTML(pad, cleanText(html));
} catch (e) {
throw new customError("HTML is malformed", "apierror");
throw new customError('HTML is malformed', 'apierror');
}
// update the clients on the pad
@ -303,23 +301,23 @@ Example returns:
{code: 1, message:"padID does not exist", data: null}
*/
exports.getChatHistory = async function(padID, start, end) {
exports.getChatHistory = async function (padID, start, end) {
if (start && end) {
if (start < 0) {
throw new customError("start is below zero", "apierror");
throw new customError('start is below zero', 'apierror');
}
if (end < 0) {
throw new customError("end is below zero", "apierror");
throw new customError('end is below zero', 'apierror');
}
if (start > end) {
throw new customError("start is higher than end", "apierror");
throw new customError('start is higher than end', 'apierror');
}
}
// get the pad
let pad = await getPadSafe(padID, true);
const pad = await getPadSafe(padID, true);
var chatHead = pad.chatHead;
const chatHead = pad.chatHead;
// fall back to getting the whole chat-history if a parameter is missing
if (!start || !end) {
@ -328,17 +326,17 @@ exports.getChatHistory = async function(padID, start, end) {
}
if (start > chatHead) {
throw new customError("start is higher than the current chatHead", "apierror");
throw new customError('start is higher than the current chatHead', 'apierror');
}
if (end > chatHead) {
throw new customError("end is higher than the current chatHead", "apierror");
throw new customError('end is higher than the current chatHead', 'apierror');
}
// the the whole message-log and return it to the client
let messages = await pad.getChatMessages(start, end);
const messages = await pad.getChatMessages(start, end);
return { messages };
}
return {messages};
};
/**
appendChatMessage(padID, text, authorID, time), creates a chat message for the pad id, time is a timestamp
@ -348,10 +346,10 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.appendChatMessage = async function(padID, text, authorID, time) {
exports.appendChatMessage = async function (padID, text, authorID, time) {
// text is required
if (typeof text !== "string") {
throw new customError("text is not a string", "apierror");
if (typeof text !== 'string') {
throw new customError('text is not a string', 'apierror');
}
// if time is not an integer value set time to current timestamp
@ -363,7 +361,7 @@ exports.appendChatMessage = async function(padID, text, authorID, time) {
// save chat message to database and send message to all connected clients
await padMessageHandler.sendChatMessageToPadClients(time, authorID, text, padID);
}
};
/* ***************
* PAD FUNCTIONS *
@ -377,11 +375,11 @@ Example returns:
{code: 0, message:"ok", data: {revisions: 56}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getRevisionsCount = async function(padID) {
exports.getRevisionsCount = async function (padID) {
// get the pad
let pad = await getPadSafe(padID, true);
return { revisions: pad.getHeadRevisionNumber() };
}
const pad = await getPadSafe(padID, true);
return {revisions: pad.getHeadRevisionNumber()};
};
/**
getSavedRevisionsCount(padID) returns the number of saved revisions of this pad
@ -391,11 +389,11 @@ Example returns:
{code: 0, message:"ok", data: {savedRevisions: 42}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getSavedRevisionsCount = async function(padID) {
exports.getSavedRevisionsCount = async function (padID) {
// get the pad
let pad = await getPadSafe(padID, true);
return { savedRevisions: pad.getSavedRevisionsNumber() };
}
const pad = await getPadSafe(padID, true);
return {savedRevisions: pad.getSavedRevisionsNumber()};
};
/**
listSavedRevisions(padID) returns the list of saved revisions of this pad
@ -405,11 +403,11 @@ Example returns:
{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.listSavedRevisions = async function(padID) {
exports.listSavedRevisions = async function (padID) {
// get the pad
let pad = await getPadSafe(padID, true);
return { savedRevisions: pad.getSavedRevisionsList() };
}
const pad = await getPadSafe(padID, true);
return {savedRevisions: pad.getSavedRevisionsList()};
};
/**
saveRevision(padID) returns the list of saved revisions of this pad
@ -419,28 +417,28 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.saveRevision = async function(padID, rev) {
exports.saveRevision = async function (padID, rev) {
// check if rev is a number
if (rev !== undefined) {
rev = checkValidRev(rev);
}
// get the pad
let pad = await getPadSafe(padID, true);
let head = pad.getHeadRevisionNumber();
const pad = await getPadSafe(padID, true);
const head = pad.getHeadRevisionNumber();
// the client asked for a special revision
if (rev !== undefined) {
if (rev > head) {
throw new customError("rev is higher than the head revision of the pad", "apierror");
throw new customError('rev is higher than the head revision of the pad', 'apierror');
}
} else {
rev = pad.getHeadRevisionNumber();
}
let author = await authorManager.createAuthor('API');
const author = await authorManager.createAuthor('API');
await pad.addSavedRevision(rev, author.authorID, 'Saved through API call');
}
};
/**
getLastEdited(padID) returns the timestamp of the last revision of the pad
@ -450,12 +448,12 @@ Example returns:
{code: 0, message:"ok", data: {lastEdited: 1340815946602}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getLastEdited = async function(padID) {
exports.getLastEdited = async function (padID) {
// get the pad
let pad = await getPadSafe(padID, true);
let lastEdited = await pad.getLastEdit();
return { lastEdited };
}
const pad = await getPadSafe(padID, true);
const lastEdited = await pad.getLastEdit();
return {lastEdited};
};
/**
createPad(padName [, text]) creates a new pad in this group
@ -465,22 +463,22 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"pad does already exist", data: null}
*/
exports.createPad = async function(padID, text) {
exports.createPad = async function (padID, text) {
if (padID) {
// ensure there is no $ in the padID
if (padID.indexOf("$") !== -1) {
throw new customError("createPad can't create group pads", "apierror");
if (padID.indexOf('$') !== -1) {
throw new customError("createPad can't create group pads", 'apierror');
}
// check for url special characters
if (padID.match(/(\/|\?|&|#)/)) {
throw new customError("malformed padID: Remove special characters", "apierror");
throw new customError('malformed padID: Remove special characters', 'apierror');
}
}
// create pad
await getPadSafe(padID, false, text);
}
};
/**
deletePad(padID) deletes a pad
@ -490,10 +488,10 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.deletePad = async function(padID) {
let pad = await getPadSafe(padID, true);
exports.deletePad = async function (padID) {
const pad = await getPadSafe(padID, true);
await pad.remove();
}
};
/**
restoreRevision(padID, [rev]) Restores revision from past as new changeset
@ -503,34 +501,34 @@ exports.deletePad = async function(padID) {
{code:0, message:"ok", data:null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.restoreRevision = async function(padID, rev) {
exports.restoreRevision = async function (padID, rev) {
// check if rev is a number
if (rev === undefined) {
throw new customError("rev is not defined", "apierror");
throw new customError('rev is not defined', 'apierror');
}
rev = checkValidRev(rev);
// get the pad
let pad = await getPadSafe(padID, true);
const pad = await getPadSafe(padID, true);
// check if this is a valid revision
if (rev > pad.getHeadRevisionNumber()) {
throw new customError("rev is higher than the head revision of the pad", "apierror");
throw new customError('rev is higher than the head revision of the pad', 'apierror');
}
let atext = await pad.getInternalRevisionAText(rev);
const atext = await pad.getInternalRevisionAText(rev);
var oldText = pad.text();
atext.text += "\n";
const oldText = pad.text();
atext.text += '\n';
function eachAttribRun(attribs, func) {
var attribsIter = Changeset.opIterator(attribs);
var textIndex = 0;
var newTextStart = 0;
var newTextEnd = atext.text.length;
const attribsIter = Changeset.opIterator(attribs);
let textIndex = 0;
const newTextStart = 0;
const newTextEnd = atext.text.length;
while (attribsIter.hasNext()) {
var op = attribsIter.next();
var nextIndex = textIndex + op.chars;
const op = attribsIter.next();
const nextIndex = textIndex + op.chars;
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
}
@ -539,14 +537,14 @@ exports.restoreRevision = async function(padID, rev) {
}
// create a new changeset with a helper builder object
var builder = Changeset.builder(oldText.length);
const builder = Changeset.builder(oldText.length);
// assemble each line into the builder
eachAttribRun(atext.attribs, function(start, end, attribs) {
eachAttribRun(atext.attribs, (start, end, attribs) => {
builder.insert(atext.text.substring(start, end), attribs);
});
var lastNewlinePos = oldText.lastIndexOf('\n');
const lastNewlinePos = oldText.lastIndexOf('\n');
if (lastNewlinePos < 0) {
builder.remove(oldText.length - 1, 0);
} else {
@ -554,13 +552,13 @@ exports.restoreRevision = async function(padID, rev) {
builder.remove(oldText.length - lastNewlinePos - 1, 0);
}
var changeset = builder.toString();
const changeset = builder.toString();
await Promise.all([
pad.appendRevision(changeset),
padMessageHandler.updatePadClients(pad),
]);
}
};
/**
copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true,
@ -571,10 +569,10 @@ Example returns:
{code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.copyPad = async function(sourceID, destinationID, force) {
let pad = await getPadSafe(sourceID, true);
exports.copyPad = async function (sourceID, destinationID, force) {
const pad = await getPadSafe(sourceID, true);
await pad.copy(destinationID, force);
}
};
/**
copyPadWithoutHistory(sourceID, destinationID[, force=false]) copies a pad. If force is true,
@ -585,10 +583,10 @@ Example returns:
{code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.copyPadWithoutHistory = async function(sourceID, destinationID, force) {
let pad = await getPadSafe(sourceID, true);
exports.copyPadWithoutHistory = async function (sourceID, destinationID, force) {
const pad = await getPadSafe(sourceID, true);
await pad.copyPadWithoutHistory(destinationID, force);
}
};
/**
movePad(sourceID, destinationID[, force=false]) moves a pad. If force is true,
@ -599,11 +597,11 @@ Example returns:
{code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.movePad = async function(sourceID, destinationID, force) {
let pad = await getPadSafe(sourceID, true);
exports.movePad = async function (sourceID, destinationID, force) {
const pad = await getPadSafe(sourceID, true);
await pad.copy(destinationID, force);
await pad.remove();
}
};
/**
getReadOnlyLink(padID) returns the read only link of a pad
@ -613,15 +611,15 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getReadOnlyID = async function(padID) {
exports.getReadOnlyID = async function (padID) {
// we don't need the pad object, but this function does all the security stuff for us
await getPadSafe(padID, true);
// get the readonlyId
let readOnlyID = await readOnlyManager.getReadOnlyId(padID);
const readOnlyID = await readOnlyManager.getReadOnlyId(padID);
return { readOnlyID };
}
return {readOnlyID};
};
/**
getPadID(roID) returns the padID of a pad based on the readonlyID(roID)
@ -631,15 +629,15 @@ Example returns:
{code: 0, message:"ok", data: {padID: padID}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getPadID = async function(roID) {
exports.getPadID = async function (roID) {
// get the PadId
let padID = await readOnlyManager.getPadId(roID);
const padID = await readOnlyManager.getPadId(roID);
if (padID === null) {
throw new customError("padID does not exist", "apierror");
throw new customError('padID does not exist', 'apierror');
}
return { padID };
}
return {padID};
};
/**
setPublicStatus(padID, publicStatus) sets a boolean for the public status of a pad
@ -649,20 +647,20 @@ Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.setPublicStatus = async function(padID, publicStatus) {
exports.setPublicStatus = async function (padID, publicStatus) {
// ensure this is a group pad
checkGroupPad(padID, "publicStatus");
checkGroupPad(padID, 'publicStatus');
// get the pad
let pad = await getPadSafe(padID, true);
const pad = await getPadSafe(padID, true);
// convert string to boolean
if (typeof publicStatus === "string") {
publicStatus = (publicStatus.toLowerCase() === "true");
if (typeof publicStatus === 'string') {
publicStatus = (publicStatus.toLowerCase() === 'true');
}
await pad.setPublicStatus(publicStatus);
}
};
/**
getPublicStatus(padID) return true of false
@ -672,14 +670,14 @@ Example returns:
{code: 0, message:"ok", data: {publicStatus: true}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getPublicStatus = async function(padID) {
exports.getPublicStatus = async function (padID) {
// ensure this is a group pad
checkGroupPad(padID, "publicStatus");
checkGroupPad(padID, 'publicStatus');
// get the pad
let pad = await getPadSafe(padID, true);
return { publicStatus: pad.getPublicStatus() };
}
const pad = await getPadSafe(padID, true);
return {publicStatus: pad.getPublicStatus()};
};
/**
listAuthorsOfPad(padID) returns an array of authors who contributed to this pad
@ -689,12 +687,12 @@ Example returns:
{code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]}
{code: 1, message:"padID does not exist", data: null}
*/
exports.listAuthorsOfPad = async function(padID) {
exports.listAuthorsOfPad = async function (padID) {
// get the pad
let pad = await getPadSafe(padID, true);
let authorIDs = pad.getAllAuthors();
return { authorIDs };
}
const pad = await getPadSafe(padID, true);
const authorIDs = pad.getAllAuthors();
return {authorIDs};
};
/**
sendClientsMessage(padID, msg) sends a message to all clients connected to the
@ -719,10 +717,10 @@ Example returns:
{code: 1, message:"padID does not exist"}
*/
exports.sendClientsMessage = async function(padID, msg) {
let pad = await getPadSafe(padID, true);
exports.sendClientsMessage = async function (padID, msg) {
const pad = await getPadSafe(padID, true);
padMessageHandler.handleCustomMessage(padID, msg);
}
};
/**
checkToken() returns ok when the current api token is valid
@ -732,8 +730,8 @@ Example returns:
{"code":0,"message":"ok","data":null}
{"code":4,"message":"no or wrong API Key","data":null}
*/
exports.checkToken = async function() {
}
exports.checkToken = async function () {
};
/**
getChatHead(padID) returns the chatHead (last number of the last chat-message) of the pad
@ -743,11 +741,11 @@ Example returns:
{code: 0, message:"ok", data: {chatHead: 42}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.getChatHead = async function(padID) {
exports.getChatHead = async function (padID) {
// get the pad
let pad = await getPadSafe(padID, true);
return { chatHead: pad.chatHead };
}
const pad = await getPadSafe(padID, true);
return {chatHead: pad.chatHead};
};
/**
createDiffHTML(padID, startRev, endRev) returns an object of diffs from 2 points in a pad
@ -757,8 +755,7 @@ Example returns:
{"code":0,"message":"ok","data":{"html":"<style>\n.authora_HKIv23mEbachFYfH {background-color: #a979d9}\n.authora_n4gEeMLsv1GivNeh {background-color: #a9b5d9}\n.removed {text-decoration: line-through; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; filter: alpha(opacity=80); opacity: 0.8; }\n</style>Welcome to Etherpad!<br><br>This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!<br><br>Get involved with Etherpad at <a href=\"http&#x3a;&#x2F;&#x2F;etherpad&#x2e;org\">http:&#x2F;&#x2F;etherpad.org</a><br><span class=\"authora_HKIv23mEbachFYfH\">aw</span><br><br>","authors":["a.HKIv23mEbachFYfH",""]}}
{"code":4,"message":"no or wrong API Key","data":null}
*/
exports.createDiffHTML = async function(padID, startRev, endRev) {
exports.createDiffHTML = async function (padID, startRev, endRev) {
// check if startRev is a number
if (startRev !== undefined) {
startRev = checkValidRev(startRev);
@ -770,18 +767,18 @@ exports.createDiffHTML = async function(padID, startRev, endRev) {
}
// get the pad
let pad = await getPadSafe(padID, true);
const pad = await getPadSafe(padID, true);
try {
var padDiff = new PadDiff(pad, startRev, endRev);
} catch (e) {
throw { stop: e.message };
throw {stop: e.message};
}
let html = await padDiff.getHtml();
let authors = await padDiff.getAuthors();
const html = await padDiff.getHtml();
const authors = await padDiff.getAuthors();
return { html, authors };
}
return {html, authors};
};
/* ********************
** GLOBAL FUNCTIONS **
@ -796,20 +793,20 @@ exports.createDiffHTML = async function(padID, startRev, endRev) {
{"code":4,"message":"no or wrong API Key","data":null}
*/
exports.getStats = async function() {
exports.getStats = async function () {
const sessionInfos = padMessageHandler.sessioninfos;
const sessionKeys = Object.keys(sessionInfos);
const activePads = new Set(Object.entries(sessionInfos).map(k => k[1].padId));
const activePads = new Set(Object.entries(sessionInfos).map((k) => k[1].padId));
const { padIDs } = await padManager.listAllPads();
const {padIDs} = await padManager.listAllPads();
return {
totalPads: padIDs.length,
totalSessions: sessionKeys.length,
totalActivePads: activePads.size,
}
}
};
};
/* ****************************
** INTERNAL HELPER FUNCTIONS *
@ -817,32 +814,32 @@ exports.getStats = async function() {
// checks if a number is an int
function is_int(value) {
return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value)
return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value);
}
// gets a pad safe
async function getPadSafe(padID, shouldExist, text) {
// check if padID is a string
if (typeof padID !== "string") {
throw new customError("padID is not a string", "apierror");
if (typeof padID !== 'string') {
throw new customError('padID is not a string', 'apierror');
}
// check if the padID maches the requirements
if (!padManager.isValidPadId(padID)) {
throw new customError("padID did not match requirements", "apierror");
throw new customError('padID did not match requirements', 'apierror');
}
// check if the pad exists
let exists = await padManager.doesPadExists(padID);
const exists = await padManager.doesPadExists(padID);
if (!exists && shouldExist) {
// does not exist, but should
throw new customError("padID does not exist", "apierror");
throw new customError('padID does not exist', 'apierror');
}
if (exists && !shouldExist) {
// does exist, but shouldn't
throw new customError("padID does already exist", "apierror");
throw new customError('padID does already exist', 'apierror');
}
// pad exists, let's get it
@ -852,23 +849,23 @@ async function getPadSafe(padID, shouldExist, text) {
// checks if a rev is a legal number
// pre-condition is that `rev` is not undefined
function checkValidRev(rev) {
if (typeof rev !== "number") {
if (typeof rev !== 'number') {
rev = parseInt(rev, 10);
}
// check if rev is a number
if (isNaN(rev)) {
throw new customError("rev is not a number", "apierror");
throw new customError('rev is not a number', 'apierror');
}
// ensure this is not a negative number
if (rev < 0) {
throw new customError("rev is not a negative number", "apierror");
throw new customError('rev is not a negative number', 'apierror');
}
// ensure this is not a float value
if (!is_int(rev)) {
throw new customError("rev is a float value", "apierror");
throw new customError('rev is a float value', 'apierror');
}
return rev;
@ -877,7 +874,7 @@ function checkValidRev(rev) {
// checks if a padID is part of a group
function checkGroupPad(padID, field) {
// ensure this is a group pad
if (padID && padID.indexOf("$") === -1) {
throw new customError(`You can only get/set the ${field} of pads that belong to a group`, "apierror");
if (padID && padID.indexOf('$') === -1) {
throw new customError(`You can only get/set the ${field} of pads that belong to a group`, 'apierror');
}
}

View file

@ -18,31 +18,87 @@
* limitations under the License.
*/
var db = require("./DB");
var customError = require("../utils/customError");
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
const db = require('./DB');
const customError = require('../utils/customError');
const randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
exports.getColorPalette = function() {
exports.getColorPalette = function () {
return [
"#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1",
"#ffa8a8", "#ffe699", "#cfff9e", "#99ffb3", "#a3ffff", "#99b3ff", "#cc99ff", "#ff99e5",
"#e7b1b1", "#e9dcAf", "#cde9af", "#bfedcc", "#b1e7e7", "#c3cdee", "#d2b8ea", "#eec3e6",
"#e9cece", "#e7e0ca", "#d3e5c7", "#bce1c5", "#c1e2e2", "#c1c9e2", "#cfc1e2", "#e0bdd9",
"#baded3", "#a0f8eb", "#b1e7e0", "#c3c8e4", "#cec5e2", "#b1d5e7", "#cda8f0", "#f0f0a8",
"#f2f2a6", "#f5a8eb", "#c5f9a9", "#ececbb", "#e7c4bc", "#daf0b2", "#b0a0fd", "#bce2e7",
"#cce2bb", "#ec9afe", "#edabbd", "#aeaeea", "#c4e7b1", "#d722bb", "#f3a5e7", "#ffa8a8",
"#d8c0c5", "#eaaedd", "#adc6eb", "#bedad1", "#dee9af", "#e9afc2", "#f8d2a0", "#b3b3e6"
'#ffc7c7',
'#fff1c7',
'#e3ffc7',
'#c7ffd5',
'#c7ffff',
'#c7d5ff',
'#e3c7ff',
'#ffc7f1',
'#ffa8a8',
'#ffe699',
'#cfff9e',
'#99ffb3',
'#a3ffff',
'#99b3ff',
'#cc99ff',
'#ff99e5',
'#e7b1b1',
'#e9dcAf',
'#cde9af',
'#bfedcc',
'#b1e7e7',
'#c3cdee',
'#d2b8ea',
'#eec3e6',
'#e9cece',
'#e7e0ca',
'#d3e5c7',
'#bce1c5',
'#c1e2e2',
'#c1c9e2',
'#cfc1e2',
'#e0bdd9',
'#baded3',
'#a0f8eb',
'#b1e7e0',
'#c3c8e4',
'#cec5e2',
'#b1d5e7',
'#cda8f0',
'#f0f0a8',
'#f2f2a6',
'#f5a8eb',
'#c5f9a9',
'#ececbb',
'#e7c4bc',
'#daf0b2',
'#b0a0fd',
'#bce2e7',
'#cce2bb',
'#ec9afe',
'#edabbd',
'#aeaeea',
'#c4e7b1',
'#d722bb',
'#f3a5e7',
'#ffa8a8',
'#d8c0c5',
'#eaaedd',
'#adc6eb',
'#bedad1',
'#dee9af',
'#e9afc2',
'#f8d2a0',
'#b3b3e6',
];
};
/**
* Checks if the author exists
*/
exports.doesAuthorExist = async function(authorID) {
let author = await db.get("globalAuthor:" + authorID);
exports.doesAuthorExist = async function (authorID) {
const author = await db.get(`globalAuthor:${authorID}`);
return author !== null;
}
};
/* exported for backwards compatibility */
exports.doesAuthorExists = exports.doesAuthorExist;
@ -51,20 +107,20 @@ exports.doesAuthorExists = exports.doesAuthorExist;
* Returns the AuthorID for a token.
* @param {String} token The token
*/
exports.getAuthor4Token = async function(token) {
let author = await mapAuthorWithDBKey("token2author", token);
exports.getAuthor4Token = async function (token) {
const author = await mapAuthorWithDBKey('token2author', token);
// return only the sub value authorID
return author ? author.authorID : author;
}
};
/**
* Returns the AuthorID for a mapper.
* @param {String} token The mapper
* @param {String} name The name of the author (optional)
*/
exports.createAuthorIfNotExistsFor = async function(authorMapper, name) {
let author = await mapAuthorWithDBKey("mapper2author", authorMapper);
exports.createAuthorIfNotExistsFor = async function (authorMapper, name) {
const author = await mapAuthorWithDBKey('mapper2author', authorMapper);
if (name) {
// set the name of this author
@ -80,16 +136,16 @@ exports.createAuthorIfNotExistsFor = async function(authorMapper, name) {
* @param {String} mapperkey The database key name for this mapper
* @param {String} mapper The mapper
*/
async function mapAuthorWithDBKey (mapperkey, mapper) {
async function mapAuthorWithDBKey(mapperkey, mapper) {
// try to map to an author
let author = await db.get(mapperkey + ":" + mapper);
const author = await db.get(`${mapperkey}:${mapper}`);
if (author === null) {
// there is no author with this mapper, so create one
let author = await exports.createAuthor(null);
const author = await exports.createAuthor(null);
// create the token2author relation
await db.set(mapperkey + ":" + mapper, author.authorID);
await db.set(`${mapperkey}:${mapper}`, author.authorID);
// return the author
return author;
@ -97,109 +153,109 @@ async function mapAuthorWithDBKey (mapperkey, mapper) {
// there is an author with this mapper
// update the timestamp of this author
await db.setSub("globalAuthor:" + author, ["timestamp"], Date.now());
await db.setSub(`globalAuthor:${author}`, ['timestamp'], Date.now());
// return the author
return { authorID: author};
return {authorID: author};
}
/**
* Internal function that creates the database entry for an author
* @param {String} name The name of the author
*/
exports.createAuthor = function(name) {
exports.createAuthor = function (name) {
// create the new author name
let author = "a." + randomString(16);
const author = `a.${randomString(16)}`;
// create the globalAuthors db entry
let authorObj = {
"colorId": Math.floor(Math.random() * (exports.getColorPalette().length)),
"name": name,
"timestamp": Date.now()
const authorObj = {
colorId: Math.floor(Math.random() * (exports.getColorPalette().length)),
name,
timestamp: Date.now(),
};
// set the global author db entry
// NB: no await, since we're not waiting for the DB set to finish
db.set("globalAuthor:" + author, authorObj);
db.set(`globalAuthor:${author}`, authorObj);
return { authorID: author };
}
return {authorID: author};
};
/**
* Returns the Author Obj of the author
* @param {String} author The id of the author
*/
exports.getAuthor = function(author) {
exports.getAuthor = function (author) {
// NB: result is already a Promise
return db.get("globalAuthor:" + author);
}
return db.get(`globalAuthor:${author}`);
};
/**
* Returns the color Id of the author
* @param {String} author The id of the author
*/
exports.getAuthorColorId = function(author) {
return db.getSub("globalAuthor:" + author, ["colorId"]);
}
exports.getAuthorColorId = function (author) {
return db.getSub(`globalAuthor:${author}`, ['colorId']);
};
/**
* Sets the color Id of the author
* @param {String} author The id of the author
* @param {String} colorId The color id of the author
*/
exports.setAuthorColorId = function(author, colorId) {
return db.setSub("globalAuthor:" + author, ["colorId"], colorId);
}
exports.setAuthorColorId = function (author, colorId) {
return db.setSub(`globalAuthor:${author}`, ['colorId'], colorId);
};
/**
* Returns the name of the author
* @param {String} author The id of the author
*/
exports.getAuthorName = function(author) {
return db.getSub("globalAuthor:" + author, ["name"]);
}
exports.getAuthorName = function (author) {
return db.getSub(`globalAuthor:${author}`, ['name']);
};
/**
* Sets the name of the author
* @param {String} author The id of the author
* @param {String} name The name of the author
*/
exports.setAuthorName = function(author, name) {
return db.setSub("globalAuthor:" + author, ["name"], name);
}
exports.setAuthorName = function (author, name) {
return db.setSub(`globalAuthor:${author}`, ['name'], name);
};
/**
* Returns an array of all pads this author contributed to
* @param {String} author The id of the author
*/
exports.listPadsOfAuthor = async function(authorID) {
exports.listPadsOfAuthor = async function (authorID) {
/* There are two other places where this array is manipulated:
* (1) When the author is added to a pad, the author object is also updated
* (2) When a pad is deleted, each author of that pad is also updated
*/
// get the globalAuthor
let author = await db.get("globalAuthor:" + authorID);
const author = await db.get(`globalAuthor:${authorID}`);
if (author === null) {
// author does not exist
throw new customError("authorID does not exist", "apierror");
throw new customError('authorID does not exist', 'apierror');
}
// everything is fine, return the pad IDs
let padIDs = Object.keys(author.padIDs || {});
const padIDs = Object.keys(author.padIDs || {});
return { padIDs };
}
return {padIDs};
};
/**
* Adds a new pad to the list of contributions
* @param {String} author The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
exports.addPad = async function(authorID, padID) {
exports.addPad = async function (authorID, padID) {
// get the entry
let author = await db.get("globalAuthor:" + authorID);
const author = await db.get(`globalAuthor:${authorID}`);
if (author === null) return;
@ -216,22 +272,22 @@ exports.addPad = async function(authorID, padID) {
author.padIDs[padID] = 1; // anything, because value is not used
// save the new element back
db.set("globalAuthor:" + authorID, author);
}
db.set(`globalAuthor:${authorID}`, author);
};
/**
* Removes a pad from the list of contributions
* @param {String} author The id of the author
* @param {String} padID The id of the pad the author contributes to
*/
exports.removePad = async function(authorID, padID) {
let author = await db.get("globalAuthor:" + authorID);
exports.removePad = async function (authorID, padID) {
const author = await db.get(`globalAuthor:${authorID}`);
if (author === null) return;
if (author.padIDs !== null) {
// remove pad from author
delete author.padIDs[padID];
await db.set('globalAuthor:' + authorID, author);
await db.set(`globalAuthor:${authorID}`, author);
}
}
};

View file

@ -19,13 +19,13 @@
* limitations under the License.
*/
var ueberDB = require("ueberdb2");
var settings = require("../utils/Settings");
var log4js = require('log4js');
const util = require("util");
const ueberDB = require('ueberdb2');
const settings = require('../utils/Settings');
const log4js = require('log4js');
const util = require('util');
// set database settings
let db = new ueberDB.database(settings.dbType, settings.dbSettings, null, log4js.getLogger("ueberDB"));
const db = new ueberDB.database(settings.dbType, settings.dbSettings, null, log4js.getLogger('ueberDB'));
/**
* The UeberDB Object that provides the database functions
@ -36,32 +36,32 @@ exports.db = null;
* Initalizes the database with the settings provided by the settings module
* @param {Function} callback
*/
exports.init = function() {
exports.init = function () {
// initalize the database async
return new Promise((resolve, reject) => {
db.init(function(err) {
db.init((err) => {
if (err) {
// there was an error while initializing the database, output it and stop
console.error("ERROR: Problem while initalizing the database");
console.error('ERROR: Problem while initalizing the database');
console.error(err.stack ? err.stack : err);
process.exit(1);
}
// everything ok, set up Promise-based methods
['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove', 'doShutdown'].forEach(fn => {
['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove', 'doShutdown'].forEach((fn) => {
exports[fn] = util.promisify(db[fn].bind(db));
});
// set up wrappers for get and getSub that can't return "undefined"
let get = exports.get;
exports.get = async function(key) {
let result = await get(key);
const get = exports.get;
exports.get = async function (key) {
const result = await get(key);
return (result === undefined) ? null : result;
};
let getSub = exports.getSub;
exports.getSub = async function(key, sub) {
let result = await getSub(key, sub);
const getSub = exports.getSub;
exports.getSub = async function (key, sub) {
const result = await getSub(key, sub);
return (result === undefined) ? null : result;
};
@ -70,7 +70,7 @@ exports.init = function() {
resolve();
});
});
}
};
exports.shutdown = async (hookName, context) => {
await exports.doShutdown();

View file

@ -18,52 +18,48 @@
* limitations under the License.
*/
var customError = require("../utils/customError");
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
var db = require("./DB");
var padManager = require("./PadManager");
var sessionManager = require("./SessionManager");
const customError = require('../utils/customError');
const randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
const db = require('./DB');
const padManager = require('./PadManager');
const sessionManager = require('./SessionManager');
exports.listAllGroups = async function() {
let groups = await db.get("groups");
exports.listAllGroups = async function () {
let groups = await db.get('groups');
groups = groups || {};
let groupIDs = Object.keys(groups);
return { groupIDs };
}
const groupIDs = Object.keys(groups);
return {groupIDs};
};
exports.deleteGroup = async function(groupID) {
let group = await db.get("group:" + groupID);
exports.deleteGroup = async function (groupID) {
const group = await db.get(`group:${groupID}`);
// ensure group exists
if (group == null) {
// group does not exist
throw new customError("groupID does not exist", "apierror");
throw new customError('groupID does not exist', 'apierror');
}
// iterate through all pads of this group and delete them (in parallel)
await Promise.all(Object.keys(group.pads).map(padID => {
return padManager.getPad(padID).then(pad => pad.remove());
}));
await Promise.all(Object.keys(group.pads).map((padID) => padManager.getPad(padID).then((pad) => pad.remove())));
// iterate through group2sessions and delete all sessions
let group2sessions = await db.get("group2sessions:" + groupID);
let sessions = group2sessions ? group2sessions.sessionIDs : {};
const group2sessions = await db.get(`group2sessions:${groupID}`);
const sessions = group2sessions ? group2sessions.sessionIDs : {};
// loop through all sessions and delete them (in parallel)
await Promise.all(Object.keys(sessions).map(session => {
return sessionManager.deleteSession(session);
}));
await Promise.all(Object.keys(sessions).map((session) => sessionManager.deleteSession(session)));
// remove group and group2sessions entry
await db.remove("group2sessions:" + groupID);
await db.remove("group:" + groupID);
await db.remove(`group2sessions:${groupID}`);
await db.remove(`group:${groupID}`);
// unlist the group
let groups = await exports.listAllGroups();
groups = groups ? groups.groupIDs : [];
let index = groups.indexOf(groupID);
const index = groups.indexOf(groupID);
if (index === -1) {
// it's not listed
@ -75,102 +71,102 @@ exports.deleteGroup = async function(groupID) {
groups.splice(index, 1);
// regenerate group list
var newGroups = {};
groups.forEach(group => newGroups[group] = 1);
await db.set("groups", newGroups);
}
const newGroups = {};
groups.forEach((group) => newGroups[group] = 1);
await db.set('groups', newGroups);
};
exports.doesGroupExist = async function(groupID) {
exports.doesGroupExist = async function (groupID) {
// try to get the group entry
let group = await db.get("group:" + groupID);
const group = await db.get(`group:${groupID}`);
return (group != null);
}
};
exports.createGroup = async function() {
exports.createGroup = async function () {
// search for non existing groupID
var groupID = "g." + randomString(16);
const groupID = `g.${randomString(16)}`;
// create the group
await db.set("group:" + groupID, {pads: {}});
await db.set(`group:${groupID}`, {pads: {}});
// list the group
let groups = await exports.listAllGroups();
groups = groups? groups.groupIDs : [];
groups = groups ? groups.groupIDs : [];
groups.push(groupID);
// regenerate group list
var newGroups = {};
groups.forEach(group => newGroups[group] = 1);
await db.set("groups", newGroups);
const newGroups = {};
groups.forEach((group) => newGroups[group] = 1);
await db.set('groups', newGroups);
return { groupID };
}
return {groupID};
};
exports.createGroupIfNotExistsFor = async function(groupMapper) {
exports.createGroupIfNotExistsFor = async function (groupMapper) {
// ensure mapper is optional
if (typeof groupMapper !== "string") {
throw new customError("groupMapper is not a string", "apierror");
if (typeof groupMapper !== 'string') {
throw new customError('groupMapper is not a string', 'apierror');
}
// try to get a group for this mapper
let groupID = await db.get("mapper2group:" + groupMapper);
const groupID = await db.get(`mapper2group:${groupMapper}`);
if (groupID) {
// there is a group for this mapper
let exists = await exports.doesGroupExist(groupID);
const exists = await exports.doesGroupExist(groupID);
if (exists) return { groupID };
if (exists) return {groupID};
}
// hah, the returned group doesn't exist, let's create one
let result = await exports.createGroup();
const result = await exports.createGroup();
// create the mapper entry for this group
await db.set("mapper2group:" + groupMapper, result.groupID);
await db.set(`mapper2group:${groupMapper}`, result.groupID);
return result;
}
};
exports.createGroupPad = async function(groupID, padName, text) {
exports.createGroupPad = async function (groupID, padName, text) {
// create the padID
let padID = groupID + "$" + padName;
const padID = `${groupID}$${padName}`;
// ensure group exists
let groupExists = await exports.doesGroupExist(groupID);
const groupExists = await exports.doesGroupExist(groupID);
if (!groupExists) {
throw new customError("groupID does not exist", "apierror");
throw new customError('groupID does not exist', 'apierror');
}
// ensure pad doesn't exist already
let padExists = await padManager.doesPadExists(padID);
const padExists = await padManager.doesPadExists(padID);
if (padExists) {
// pad exists already
throw new customError("padName does already exist", "apierror");
throw new customError('padName does already exist', 'apierror');
}
// create the pad
await padManager.getPad(padID, text);
//create an entry in the group for this pad
await db.setSub("group:" + groupID, ["pads", padID], 1);
// create an entry in the group for this pad
await db.setSub(`group:${groupID}`, ['pads', padID], 1);
return { padID };
}
return {padID};
};
exports.listPads = async function(groupID) {
let exists = await exports.doesGroupExist(groupID);
exports.listPads = async function (groupID) {
const exists = await exports.doesGroupExist(groupID);
// ensure the group exists
if (!exists) {
throw new customError("groupID does not exist", "apierror");
throw new customError('groupID does not exist', 'apierror');
}
// group exists, let's get the pads
let result = await db.getSub("group:" + groupID, ["pads"]);
let padIDs = Object.keys(result);
const result = await db.getSub(`group:${groupID}`, ['pads']);
const padIDs = Object.keys(result);
return { padIDs };
}
return {padIDs};
};

View file

@ -3,36 +3,36 @@
*/
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");
var db = require("./DB");
var settings = require('../utils/Settings');
var authorManager = require("./AuthorManager");
var padManager = require("./PadManager");
var padMessageHandler = require("../handler/PadMessageHandler");
var groupManager = require("./GroupManager");
var customError = require("../utils/customError");
var readOnlyManager = require("./ReadOnlyManager");
var crypto = require("crypto");
var randomString = require("../utils/randomstring");
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var promises = require('../utils/promises')
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const AttributePool = require('ep_etherpad-lite/static/js/AttributePool');
const db = require('./DB');
const settings = require('../utils/Settings');
const authorManager = require('./AuthorManager');
const padManager = require('./PadManager');
const padMessageHandler = require('../handler/PadMessageHandler');
const groupManager = require('./GroupManager');
const customError = require('../utils/customError');
const readOnlyManager = require('./ReadOnlyManager');
const crypto = require('crypto');
const randomString = require('../utils/randomstring');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
const promises = require('../utils/promises');
// serialization/deserialization attributes
var attributeBlackList = ["id"];
var jsonableList = ["pool"];
const attributeBlackList = ['id'];
const jsonableList = ['pool'];
/**
* Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces
* @param txt
*/
exports.cleanText = function (txt) {
return txt.replace(/\r\n/g,'\n').replace(/\r/g,'\n').replace(/\t/g, ' ').replace(/\xa0/g, ' ');
return txt.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\t/g, ' ').replace(/\xa0/g, ' ');
};
let Pad = function Pad(id) {
this.atext = Changeset.makeAText("\n");
const Pad = function Pad(id) {
this.atext = Changeset.makeAText('\n');
this.pool = new AttributePool();
this.head = -1;
this.chatHead = -1;
@ -56,13 +56,11 @@ Pad.prototype.getSavedRevisionsNumber = function getSavedRevisionsNumber() {
};
Pad.prototype.getSavedRevisionsList = function getSavedRevisionsList() {
var savedRev = new Array();
for (var rev in this.savedRevisions) {
const savedRev = new Array();
for (const rev in this.savedRevisions) {
savedRev.push(this.savedRevisions[rev].revNum);
}
savedRev.sort(function(a, b) {
return a - b;
});
savedRev.sort((a, b) => a - b);
return savedRev;
};
@ -75,12 +73,12 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
author = '';
}
var newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool);
const newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool);
Changeset.copyAText(newAText, this.atext);
var newRev = ++this.head;
const newRev = ++this.head;
var newRevData = {};
const newRevData = {};
newRevData.changeset = aChangeset;
newRevData.meta = {};
newRevData.meta.author = author;
@ -97,7 +95,7 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
}
const p = [
db.set('pad:' + this.id + ':revs:' + newRev, newRevData),
db.set(`pad:${this.id}:revs:${newRev}`, newRevData),
this.saveToDatabase(),
];
@ -107,9 +105,9 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
}
if (this.head == 0) {
hooks.callAll("padCreate", {'pad':this, 'author': author});
hooks.callAll('padCreate', {pad: this, author});
} else {
hooks.callAll("padUpdate", {'pad':this, 'author': author, 'revs': newRev, 'changeset': aChangeset});
hooks.callAll('padUpdate', {pad: this, author, revs: newRev, changeset: aChangeset});
}
await Promise.all(p);
@ -117,10 +115,10 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
// save all attributes to the database
Pad.prototype.saveToDatabase = async function saveToDatabase() {
var dbObject = {};
const dbObject = {};
for (var attr in this) {
if (typeof this[attr] === "function") continue;
for (const attr in this) {
if (typeof this[attr] === 'function') continue;
if (attributeBlackList.indexOf(attr) !== -1) continue;
dbObject[attr] = this[attr];
@ -130,32 +128,32 @@ Pad.prototype.saveToDatabase = async function saveToDatabase() {
}
}
await db.set('pad:' + this.id, dbObject);
}
await db.set(`pad:${this.id}`, dbObject);
};
// get time of last edit (changeset application)
Pad.prototype.getLastEdit = function getLastEdit() {
var revNum = this.getHeadRevisionNumber();
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "timestamp"]);
}
const revNum = this.getHeadRevisionNumber();
return db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'timestamp']);
};
Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum) {
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["changeset"]);
}
return db.getSub(`pad:${this.id}:revs:${revNum}`, ['changeset']);
};
Pad.prototype.getRevisionAuthor = function getRevisionAuthor(revNum) {
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "author"]);
}
return db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'author']);
};
Pad.prototype.getRevisionDate = function getRevisionDate(revNum) {
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "timestamp"]);
}
return db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'timestamp']);
};
Pad.prototype.getAllAuthors = function getAllAuthors() {
var authors = [];
const authors = [];
for(var key in this.pool.numToAttrib) {
if (this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "") {
for (const key in this.pool.numToAttrib) {
if (this.pool.numToAttrib[key][0] == 'author' && this.pool.numToAttrib[key][1] != '') {
authors.push(this.pool.numToAttrib[key][1]);
}
}
@ -164,63 +162,59 @@ Pad.prototype.getAllAuthors = function getAllAuthors() {
};
Pad.prototype.getInternalRevisionAText = async function getInternalRevisionAText(targetRev) {
let keyRev = this.getKeyRevisionNumber(targetRev);
const keyRev = this.getKeyRevisionNumber(targetRev);
// find out which changesets are needed
let neededChangesets = [];
for (let curRev = keyRev; curRev < targetRev; ) {
const neededChangesets = [];
for (let curRev = keyRev; curRev < targetRev;) {
neededChangesets.push(++curRev);
}
// get all needed data out of the database
// start to get the atext of the key revision
let p_atext = db.getSub("pad:" + this.id + ":revs:" + keyRev, ["meta", "atext"]);
const p_atext = db.getSub(`pad:${this.id}:revs:${keyRev}`, ['meta', 'atext']);
// get all needed changesets
let changesets = [];
await Promise.all(neededChangesets.map(item => {
return this.getRevisionChangeset(item).then(changeset => {
changesets[item] = changeset;
});
}));
const changesets = [];
await Promise.all(neededChangesets.map((item) => this.getRevisionChangeset(item).then((changeset) => {
changesets[item] = changeset;
})));
// we should have the atext by now
let atext = await p_atext;
atext = Changeset.cloneAText(atext);
// apply all changesets to the key changeset
let apool = this.apool();
for (let curRev = keyRev; curRev < targetRev; ) {
let cs = changesets[++curRev];
const apool = this.apool();
for (let curRev = keyRev; curRev < targetRev;) {
const cs = changesets[++curRev];
atext = Changeset.applyToAText(cs, atext, apool);
}
return atext;
}
};
Pad.prototype.getRevision = function getRevisionChangeset(revNum) {
return db.get("pad:" + this.id + ":revs:" + revNum);
}
return db.get(`pad:${this.id}:revs:${revNum}`);
};
Pad.prototype.getAllAuthorColors = async function getAllAuthorColors() {
let authors = this.getAllAuthors();
let returnTable = {};
let colorPalette = authorManager.getColorPalette();
const authors = this.getAllAuthors();
const returnTable = {};
const colorPalette = authorManager.getColorPalette();
await Promise.all(authors.map(author => {
return authorManager.getAuthorColorId(author).then(colorId => {
// colorId might be a hex color or an number out of the palette
returnTable[author] = colorPalette[colorId] || colorId;
});
}));
await Promise.all(authors.map((author) => authorManager.getAuthorColorId(author).then((colorId) => {
// colorId might be a hex color or an number out of the palette
returnTable[author] = colorPalette[colorId] || colorId;
})));
return returnTable;
}
};
Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, endRev) {
startRev = parseInt(startRev, 10);
var head = this.getHeadRevisionNumber();
const head = this.getHeadRevisionNumber();
endRev = endRev ? parseInt(endRev, 10) : head;
if (isNaN(startRev) || startRev < 0 || startRev > head) {
@ -234,7 +228,7 @@ Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, e
}
if (startRev !== null && endRev !== null) {
return { startRev: startRev , endRev: endRev }
return {startRev, endRev};
}
return null;
};
@ -251,16 +245,16 @@ Pad.prototype.setText = async function setText(newText) {
// clean the new text
newText = exports.cleanText(newText);
var oldText = this.text();
const oldText = this.text();
// create the changeset
// We want to ensure the pad still ends with a \n, but otherwise keep
// getText() and setText() consistent.
var changeset;
let changeset;
if (newText[newText.length - 1] == '\n') {
changeset = Changeset.makeSplice(oldText, 0, oldText.length, newText);
} else {
changeset = Changeset.makeSplice(oldText, 0, oldText.length-1, newText);
changeset = Changeset.makeSplice(oldText, 0, oldText.length - 1, newText);
}
// append the changeset
@ -271,10 +265,10 @@ Pad.prototype.appendText = async function appendText(newText) {
// clean the new text
newText = exports.cleanText(newText);
var oldText = this.text();
const oldText = this.text();
// create the changeset
var changeset = Changeset.makeSplice(oldText, oldText.length, 0, newText);
const changeset = Changeset.makeSplice(oldText, oldText.length, 0, newText);
// append the changeset
await this.appendRevision(changeset);
@ -284,14 +278,14 @@ Pad.prototype.appendChatMessage = async function appendChatMessage(text, userId,
this.chatHead++;
// save the chat entry in the database
await Promise.all([
db.set('pad:' + this.id + ':chat:' + this.chatHead, {text, userId, time}),
db.set(`pad:${this.id}:chat:${this.chatHead}`, {text, userId, time}),
this.saveToDatabase(),
]);
};
Pad.prototype.getChatMessage = async function getChatMessage(entryNum) {
// get the chat entry
let entry = await db.get("pad:" + this.id + ":chat:" + entryNum);
const entry = await db.get(`pad:${this.id}:chat:${entryNum}`);
// get the authorName if the entry exists
if (entry != null) {
@ -302,49 +296,45 @@ Pad.prototype.getChatMessage = async function getChatMessage(entryNum) {
};
Pad.prototype.getChatMessages = async function getChatMessages(start, end) {
// collect the numbers of chat entries and in which order we need them
let neededEntries = [];
const neededEntries = [];
for (let order = 0, entryNum = start; entryNum <= end; ++order, ++entryNum) {
neededEntries.push({ entryNum, order });
neededEntries.push({entryNum, order});
}
// get all entries out of the database
let entries = [];
await Promise.all(neededEntries.map(entryObject => {
return this.getChatMessage(entryObject.entryNum).then(entry => {
entries[entryObject.order] = entry;
});
}));
const entries = [];
await Promise.all(neededEntries.map((entryObject) => this.getChatMessage(entryObject.entryNum).then((entry) => {
entries[entryObject.order] = entry;
})));
// sort out broken chat entries
// it looks like in happened in the past that the chat head was
// incremented, but the chat message wasn't added
let cleanedEntries = entries.filter(entry => {
let pass = (entry != null);
const cleanedEntries = entries.filter((entry) => {
const pass = (entry != null);
if (!pass) {
console.warn("WARNING: Found broken chat entry in pad " + this.id);
console.warn(`WARNING: Found broken chat entry in pad ${this.id}`);
}
return pass;
});
return cleanedEntries;
}
};
Pad.prototype.init = async function init(text) {
// replace text with default text if text isn't set
if (text == null) {
text = settings.defaultPadText;
}
// try to load the pad
let value = await db.get("pad:" + this.id);
const value = await db.get(`pad:${this.id}`);
// if this pad exists, load it
if (value != null) {
// copy all attr. To a transfrom via fromJsonable if necassary
for (var attr in value) {
for (const attr in value) {
if (jsonableList.indexOf(attr) !== -1) {
this[attr] = this[attr].fromJsonable(value[attr]);
} else {
@ -353,17 +343,17 @@ Pad.prototype.init = async function init(text) {
}
} else {
// this pad doesn't exist, so create it
let firstChangeset = Changeset.makeSplice("\n", 0, 0, exports.cleanText(text));
const firstChangeset = Changeset.makeSplice('\n', 0, 0, exports.cleanText(text));
await this.appendRevision(firstChangeset, '');
}
hooks.callAll("padLoad", { 'pad': this });
}
hooks.callAll('padLoad', {pad: this});
};
Pad.prototype.copy = async function copy(destinationID, force) {
let destGroupID;
let sourceID = this.id;
const sourceID = this.id;
// Kick everyone from this pad.
// This was commented due to https://github.com/ether/etherpad-lite/issues/3183.
@ -380,32 +370,28 @@ Pad.prototype.copy = async function copy(destinationID, force) {
// if force is true and already exists a Pad with the same id, remove that Pad
await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force);
} catch(err) {
} catch (err) {
throw err;
}
// copy the 'pad' entry
let pad = await db.get("pad:" + sourceID);
db.set("pad:" + destinationID, pad);
const pad = await db.get(`pad:${sourceID}`);
db.set(`pad:${destinationID}`, pad);
// copy all relations in parallel
let promises = [];
const promises = [];
// copy all chat messages
let chatHead = this.chatHead;
const chatHead = this.chatHead;
for (let i = 0; i <= chatHead; ++i) {
let p = db.get("pad:" + sourceID + ":chat:" + i).then(chat => {
return db.set("pad:" + destinationID + ":chat:" + i, chat);
});
const p = db.get(`pad:${sourceID}:chat:${i}`).then((chat) => db.set(`pad:${destinationID}:chat:${i}`, chat));
promises.push(p);
}
// copy all revisions
let revHead = this.head;
const revHead = this.head;
for (let i = 0; i <= revHead; ++i) {
let p = db.get("pad:" + sourceID + ":revs:" + i).then(rev => {
return db.set("pad:" + destinationID + ":revs:" + i, rev);
});
const p = db.get(`pad:${sourceID}:revs:${i}`).then((rev) => db.set(`pad:${destinationID}:revs:${i}`, rev));
promises.push(p);
}
@ -416,70 +402,69 @@ Pad.prototype.copy = async function copy(destinationID, force) {
// Group pad? Add it to the group's list
if (destGroupID) {
await db.setSub("group:" + destGroupID, ["pads", destinationID], 1);
await db.setSub(`group:${destGroupID}`, ['pads', destinationID], 1);
}
// delay still necessary?
await new Promise(resolve => setTimeout(resolve, 10));
await new Promise((resolve) => setTimeout(resolve, 10));
// Initialize the new pad (will update the listAllPads cache)
await padManager.getPad(destinationID, null); // this runs too early.
// let the plugins know the pad was copied
hooks.callAll('padCopy', { 'originalPad': this, 'destinationID': destinationID });
hooks.callAll('padCopy', {originalPad: this, destinationID});
return { padID: destinationID };
}
return {padID: destinationID};
};
Pad.prototype.checkIfGroupExistAndReturnIt = async function checkIfGroupExistAndReturnIt(destinationID) {
let destGroupID = false;
if (destinationID.indexOf("$") >= 0) {
destGroupID = destinationID.split("$")[0]
let groupExists = await groupManager.doesGroupExist(destGroupID);
if (destinationID.indexOf('$') >= 0) {
destGroupID = destinationID.split('$')[0];
const groupExists = await groupManager.doesGroupExist(destGroupID);
// group does not exist
if (!groupExists) {
throw new customError("groupID does not exist for destinationID", "apierror");
throw new customError('groupID does not exist for destinationID', 'apierror');
}
}
return destGroupID;
}
};
Pad.prototype.removePadIfForceIsTrueAndAlreadyExist = async function removePadIfForceIsTrueAndAlreadyExist(destinationID, force) {
// if the pad exists, we should abort, unless forced.
let exists = await padManager.doesPadExist(destinationID);
const exists = await padManager.doesPadExist(destinationID);
// allow force to be a string
if (typeof force === "string") {
force = (force.toLowerCase() === "true");
if (typeof force === 'string') {
force = (force.toLowerCase() === 'true');
} else {
force = !!force;
}
if (exists) {
if (!force) {
console.error("erroring out without force");
throw new customError("destinationID already exists", "apierror");
console.error('erroring out without force');
throw new customError('destinationID already exists', 'apierror');
}
// exists and forcing
let pad = await padManager.getPad(destinationID);
const pad = await padManager.getPad(destinationID);
await pad.remove();
}
}
};
Pad.prototype.copyAuthorInfoToDestinationPad = function copyAuthorInfoToDestinationPad(destinationID) {
// add the new sourcePad to all authors who contributed to the old one
this.getAllAuthors().forEach(authorID => {
this.getAllAuthors().forEach((authorID) => {
authorManager.addPad(authorID, destinationID);
});
}
};
Pad.prototype.copyPadWithoutHistory = async function copyPadWithoutHistory(destinationID, force) {
let destGroupID;
let sourceID = this.id;
const sourceID = this.id;
// flush the source pad
this.saveToDatabase();
@ -490,53 +475,53 @@ Pad.prototype.copyPadWithoutHistory = async function copyPadWithoutHistory(desti
// if force is true and already exists a Pad with the same id, remove that Pad
await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force);
} catch(err) {
} catch (err) {
throw err;
}
let sourcePad = await padManager.getPad(sourceID);
const sourcePad = await padManager.getPad(sourceID);
// add the new sourcePad to all authors who contributed to the old one
this.copyAuthorInfoToDestinationPad(destinationID);
// Group pad? Add it to the group's list
if (destGroupID) {
await db.setSub("group:" + destGroupID, ["pads", destinationID], 1);
await db.setSub(`group:${destGroupID}`, ['pads', destinationID], 1);
}
// initialize the pad with a new line to avoid getting the defaultText
let newPad = await padManager.getPad(destinationID, '\n');
const newPad = await padManager.getPad(destinationID, '\n');
let oldAText = this.atext;
let newPool = newPad.pool;
const oldAText = this.atext;
const newPool = newPad.pool;
newPool.fromJsonable(sourcePad.pool.toJsonable()); // copy that sourceId pool to the new pad
// based on Changeset.makeSplice
let assem = Changeset.smartOpAssembler();
const assem = Changeset.smartOpAssembler();
assem.appendOpWithText('=', '');
Changeset.appendATextToAssembler(oldAText, assem);
assem.endDocument();
// although we have instantiated the newPad with '\n', an additional '\n' is
// added internally, so the pad text on the revision 0 is "\n\n"
let oldLength = 2;
const oldLength = 2;
let newLength = assem.getLengthChange();
let newText = oldAText.text;
const newLength = assem.getLengthChange();
const newText = oldAText.text;
// create a changeset that removes the previous text and add the newText with
// all atributes present on the source pad
let changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
const changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
newPad.appendRevision(changeset);
hooks.callAll('padCopy', { 'originalPad': this, 'destinationID': destinationID });
hooks.callAll('padCopy', {originalPad: this, destinationID});
return { padID: destinationID };
}
return {padID: destinationID};
};
Pad.prototype.remove = async function remove() {
var padID = this.id;
const padID = this.id;
const p = [];
// kick everyone from this pad
@ -548,45 +533,44 @@ Pad.prototype.remove = async function remove() {
// run to completion
// is it a group pad? -> delete the entry of this pad in the group
if (padID.indexOf("$") >= 0) {
if (padID.indexOf('$') >= 0) {
// it is a group pad
let groupID = padID.substring(0, padID.indexOf("$"));
let group = await db.get("group:" + groupID);
const groupID = padID.substring(0, padID.indexOf('$'));
const group = await db.get(`group:${groupID}`);
// remove the pad entry
delete group.pads[padID];
// set the new value
p.push(db.set('group:' + groupID, group));
p.push(db.set(`group:${groupID}`, group));
}
// remove the readonly entries
p.push(readOnlyManager.getReadOnlyId(padID).then(async (readonlyID) => {
await db.remove('readonly2pad:' + readonlyID);
await db.remove(`readonly2pad:${readonlyID}`);
}));
p.push(db.remove('pad2readonly:' + padID));
p.push(db.remove(`pad2readonly:${padID}`));
// delete all chat messages
p.push(promises.timesLimit(this.chatHead + 1, 500, async (i) => {
await db.remove('pad:' + padID + ':chat:' + i, null);
await db.remove(`pad:${padID}:chat:${i}`, null);
}));
// delete all revisions
p.push(promises.timesLimit(this.head + 1, 500, async (i) => {
await db.remove('pad:' + padID + ':revs:' + i, null);
await db.remove(`pad:${padID}:revs:${i}`, null);
}));
// remove pad from all authors who contributed
this.getAllAuthors().forEach(authorID => {
this.getAllAuthors().forEach((authorID) => {
p.push(authorManager.removePad(authorID, padID));
});
// delete the pad entry and delete pad from padManager
p.push(padManager.removePad(padID));
hooks.callAll("padRemove", { padID });
hooks.callAll('padRemove', {padID});
await Promise.all(p);
}
};
// set in db
Pad.prototype.setPublicStatus = async function setPublicStatus(publicStatus) {
@ -596,17 +580,17 @@ Pad.prototype.setPublicStatus = async function setPublicStatus(publicStatus) {
Pad.prototype.addSavedRevision = async function addSavedRevision(revNum, savedById, label) {
// if this revision is already saved, return silently
for (var i in this.savedRevisions) {
for (const i in this.savedRevisions) {
if (this.savedRevisions[i] && this.savedRevisions[i].revNum === revNum) {
return;
}
}
// build the saved revision object
var savedRevision = {};
const savedRevision = {};
savedRevision.revNum = revNum;
savedRevision.savedById = savedById;
savedRevision.label = label || "Revision " + revNum;
savedRevision.label = label || `Revision ${revNum}`;
savedRevision.timestamp = Date.now();
savedRevision.id = randomString(10);
@ -618,4 +602,3 @@ Pad.prototype.addSavedRevision = async function addSavedRevision(revNum, savedBy
Pad.prototype.getSavedRevisions = function getSavedRevisions() {
return this.savedRevisions;
};

View file

@ -18,9 +18,9 @@
* limitations under the License.
*/
var customError = require("../utils/customError");
var Pad = require("../db/Pad").Pad;
var db = require("./DB");
const customError = require('../utils/customError');
const Pad = require('../db/Pad').Pad;
const db = require('./DB');
/**
* A cache of all loaded Pads.
@ -33,14 +33,14 @@ var db = require("./DB");
* If this is needed in other places, it would be wise to make this a prototype
* that's defined somewhere more sensible.
*/
var globalPads = {
get: function(name) { return this[':'+name]; },
set: function(name, value) {
this[':'+name] = value;
},
remove: function(name) {
delete this[':'+name];
}
const globalPads = {
get(name) { return this[`:${name}`]; },
set(name, value) {
this[`:${name}`] = value;
},
remove(name) {
delete this[`:${name}`];
},
};
/**
@ -48,24 +48,24 @@ var globalPads = {
*
* Updated without db access as new pads are created/old ones removed.
*/
let padList = {
const padList = {
list: new Set(),
cachedList: undefined,
initiated: false,
init: async function() {
let dbData = await db.findKeys("pad:*", "*:*:*");
async init() {
const dbData = await db.findKeys('pad:*', '*:*:*');
if (dbData != null) {
this.initiated = true;
for (let val of dbData) {
this.addPad(val.replace(/^pad:/,""), false);
for (const val of dbData) {
this.addPad(val.replace(/^pad:/, ''), false);
}
}
return this;
},
load: async function() {
async load() {
if (!this.initiated) {
return this.init();
}
@ -75,7 +75,7 @@ let padList = {
/**
* Returns all pads in alphabetical order as array.
*/
getPads: async function() {
async getPads() {
await this.load();
if (!this.cachedList) {
@ -84,7 +84,7 @@ let padList = {
return this.cachedList;
},
addPad: function(name) {
addPad(name) {
if (!this.initiated) return;
if (!this.list.has(name)) {
@ -92,14 +92,14 @@ let padList = {
this.cachedList = undefined;
}
},
removePad: function(name) {
removePad(name) {
if (!this.initiated) return;
if (this.list.has(name)) {
this.list.delete(name);
this.cachedList = undefined;
}
}
},
};
// initialises the all-knowing data structure
@ -109,22 +109,22 @@ let padList = {
* @param id A String with the id of the pad
* @param {Function} callback
*/
exports.getPad = async function(id, text) {
exports.getPad = async function (id, text) {
// check if this is a valid padId
if (!exports.isValidPadId(id)) {
throw new customError(id + " is not a valid padId", "apierror");
throw new customError(`${id} is not a valid padId`, 'apierror');
}
// check if this is a valid text
if (text != null) {
// check if text is a string
if (typeof text != "string") {
throw new customError("text is not a string", "apierror");
if (typeof text !== 'string') {
throw new customError('text is not a string', 'apierror');
}
// check if text is less than 100k chars
if (text.length > 100000) {
throw new customError("text must be less than 100k chars", "apierror");
throw new customError('text must be less than 100k chars', 'apierror');
}
}
@ -144,20 +144,20 @@ exports.getPad = async function(id, text) {
padList.addPad(id);
return pad;
}
};
exports.listAllPads = async function() {
let padIDs = await padList.getPads();
exports.listAllPads = async function () {
const padIDs = await padList.getPads();
return { padIDs };
}
return {padIDs};
};
// checks if a pad exists
exports.doesPadExist = async function(padId) {
let value = await db.get("pad:" + padId);
exports.doesPadExist = async function (padId) {
const value = await db.get(`pad:${padId}`);
return (value != null && value.atext);
}
};
// alias for backwards compatibility
exports.doesPadExists = exports.doesPadExist;
@ -168,42 +168,42 @@ exports.doesPadExists = exports.doesPadExist;
*/
const padIdTransforms = [
[/\s+/g, '_'],
[/:+/g, '_']
[/:+/g, '_'],
];
// returns a sanitized padId, respecting legacy pad id formats
exports.sanitizePadId = async function sanitizePadId(padId) {
for (let i = 0, n = padIdTransforms.length; i < n; ++i) {
let exists = await exports.doesPadExist(padId);
const exists = await exports.doesPadExist(padId);
if (exists) {
return padId;
}
let [from, to] = padIdTransforms[i];
const [from, to] = padIdTransforms[i];
padId = padId.replace(from, to);
}
// we're out of possible transformations, so just return it
return padId;
}
};
exports.isValidPadId = function(padId) {
exports.isValidPadId = function (padId) {
return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
}
};
/**
* Removes the pad from database and unloads it.
*/
exports.removePad = async (padId) => {
const p = db.remove('pad:' + padId);
const p = db.remove(`pad:${padId}`);
exports.unloadPad(padId);
padList.removePad(padId);
await p;
}
};
// removes a pad from the cache
exports.unloadPad = function(padId) {
exports.unloadPad = function (padId) {
globalPads.remove(padId);
}
};

View file

@ -19,17 +19,17 @@
*/
var db = require("./DB");
var randomString = require("../utils/randomstring");
const db = require('./DB');
const randomString = require('../utils/randomstring');
/**
* checks if the id pattern matches a read-only pad id
* @param {String} the pad's id
*/
exports.isReadOnlyId = function(id) {
return id.indexOf("r.") === 0;
}
exports.isReadOnlyId = function (id) {
return id.indexOf('r.') === 0;
};
/**
* returns a read only id for a pad
@ -37,36 +37,36 @@ exports.isReadOnlyId = function(id) {
*/
exports.getReadOnlyId = async function (padId) {
// check if there is a pad2readonly entry
let readOnlyId = await db.get("pad2readonly:" + padId);
let readOnlyId = await db.get(`pad2readonly:${padId}`);
// there is no readOnly Entry in the database, let's create one
if (readOnlyId == null) {
readOnlyId = "r." + randomString(16);
db.set("pad2readonly:" + padId, readOnlyId);
db.set("readonly2pad:" + readOnlyId, padId);
readOnlyId = `r.${randomString(16)}`;
db.set(`pad2readonly:${padId}`, readOnlyId);
db.set(`readonly2pad:${readOnlyId}`, padId);
}
return readOnlyId;
}
};
/**
* returns the padId for a read only id
* @param {String} readOnlyId read only id
*/
exports.getPadId = function(readOnlyId) {
return db.get("readonly2pad:" + readOnlyId);
}
exports.getPadId = function (readOnlyId) {
return db.get(`readonly2pad:${readOnlyId}`);
};
/**
* returns the padId and readonlyPadId in an object for any id
* @param {String} padIdOrReadonlyPadId read only id or real pad id
*/
exports.getIds = async function(id) {
let readonly = (id.indexOf("r.") === 0);
exports.getIds = async function (id) {
const readonly = (id.indexOf('r.') === 0);
// Might be null, if this is an unknown read-only id
let readOnlyPadId = readonly ? id : await exports.getReadOnlyId(id);
let padId = readonly ? await exports.getPadId(id) : id;
const readOnlyPadId = readonly ? id : await exports.getReadOnlyId(id);
const padId = readonly ? await exports.getPadId(id) : id;
return { readOnlyPadId, padId, readonly };
}
return {readOnlyPadId, padId, readonly};
};

View file

@ -18,14 +18,14 @@
* limitations under the License.
*/
var authorManager = require("./AuthorManager");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
var padManager = require("./PadManager");
var sessionManager = require("./SessionManager");
var settings = require("../utils/Settings");
const authorManager = require('./AuthorManager');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
const padManager = require('./PadManager');
const sessionManager = require('./SessionManager');
const settings = require('../utils/Settings');
const webaccess = require('../hooks/express/webaccess');
var log4js = require('log4js');
var authLogger = log4js.getLogger("auth");
const log4js = require('log4js');
const authLogger = log4js.getLogger('auth');
const DENY = Object.freeze({accessStatus: 'deny'});
@ -47,7 +47,7 @@ const DENY = Object.freeze({accessStatus: 'deny'});
* WARNING: Tokens and session IDs MUST be kept secret, otherwise users will be able to impersonate
* each other (which might allow them to gain privileges).
*/
exports.checkAccess = async function(padID, sessionCookie, token, userSettings) {
exports.checkAccess = async function (padID, sessionCookie, token, userSettings) {
if (!padID) {
authLogger.debug('access denied: missing padID');
return DENY;

View file

@ -18,12 +18,12 @@
* limitations under the License.
*/
var customError = require("../utils/customError");
const customError = require('../utils/customError');
const promises = require('../utils/promises');
var randomString = require("../utils/randomstring");
var db = require("./DB");
var groupManager = require("./GroupManager");
var authorManager = require("./AuthorManager");
const randomString = require('../utils/randomstring');
const db = require('./DB');
const groupManager = require('./GroupManager');
const authorManager = require('./AuthorManager');
/**
* Finds the author ID for a session with matching ID and group.
@ -78,61 +78,61 @@ exports.findAuthorID = async (groupID, sessionCookie) => {
return sessionInfo.authorID;
};
exports.doesSessionExist = async function(sessionID) {
//check if the database entry of this session exists
let session = await db.get("session:" + sessionID);
exports.doesSessionExist = async function (sessionID) {
// check if the database entry of this session exists
const session = await db.get(`session:${sessionID}`);
return (session !== null);
}
};
/**
* Creates a new session between an author and a group
*/
exports.createSession = async function(groupID, authorID, validUntil) {
exports.createSession = async function (groupID, authorID, validUntil) {
// check if the group exists
let groupExists = await groupManager.doesGroupExist(groupID);
const groupExists = await groupManager.doesGroupExist(groupID);
if (!groupExists) {
throw new customError("groupID does not exist", "apierror");
throw new customError('groupID does not exist', 'apierror');
}
// check if the author exists
let authorExists = await authorManager.doesAuthorExist(authorID);
const authorExists = await authorManager.doesAuthorExist(authorID);
if (!authorExists) {
throw new customError("authorID does not exist", "apierror");
throw new customError('authorID does not exist', 'apierror');
}
// try to parse validUntil if it's not a number
if (typeof validUntil !== "number") {
if (typeof validUntil !== 'number') {
validUntil = parseInt(validUntil);
}
// check it's a valid number
if (isNaN(validUntil)) {
throw new customError("validUntil is not a number", "apierror");
throw new customError('validUntil is not a number', 'apierror');
}
// ensure this is not a negative number
if (validUntil < 0) {
throw new customError("validUntil is a negative number", "apierror");
throw new customError('validUntil is a negative number', 'apierror');
}
// ensure this is not a float value
if (!is_int(validUntil)) {
throw new customError("validUntil is a float value", "apierror");
throw new customError('validUntil is a float value', 'apierror');
}
// check if validUntil is in the future
if (validUntil < Math.floor(Date.now() / 1000)) {
throw new customError("validUntil is in the past", "apierror");
throw new customError('validUntil is in the past', 'apierror');
}
// generate sessionID
let sessionID = "s." + randomString(16);
const sessionID = `s.${randomString(16)}`;
// set the session into the database
await db.set("session:" + sessionID, {"groupID": groupID, "authorID": authorID, "validUntil": validUntil});
await db.set(`session:${sessionID}`, {groupID, authorID, validUntil});
// get the entry
let group2sessions = await db.get("group2sessions:" + groupID);
let group2sessions = await db.get(`group2sessions:${groupID}`);
/*
* In some cases, the db layer could return "undefined" as well as "null".
@ -144,115 +144,115 @@ exports.createSession = async function(groupID, authorID, validUntil) {
*/
if (!group2sessions || !group2sessions.sessionIDs) {
// the entry doesn't exist so far, let's create it
group2sessions = {sessionIDs : {}};
group2sessions = {sessionIDs: {}};
}
// add the entry for this session
group2sessions.sessionIDs[sessionID] = 1;
// save the new element back
await db.set("group2sessions:" + groupID, group2sessions);
await db.set(`group2sessions:${groupID}`, group2sessions);
// get the author2sessions entry
let author2sessions = await db.get("author2sessions:" + authorID);
let author2sessions = await db.get(`author2sessions:${authorID}`);
if (author2sessions == null || author2sessions.sessionIDs == null) {
// the entry doesn't exist so far, let's create it
author2sessions = {sessionIDs : {}};
author2sessions = {sessionIDs: {}};
}
// add the entry for this session
author2sessions.sessionIDs[sessionID] = 1;
//save the new element back
await db.set("author2sessions:" + authorID, author2sessions);
// save the new element back
await db.set(`author2sessions:${authorID}`, author2sessions);
return { sessionID };
}
return {sessionID};
};
exports.getSessionInfo = async function(sessionID) {
exports.getSessionInfo = async function (sessionID) {
// check if the database entry of this session exists
let session = await db.get("session:" + sessionID);
const session = await db.get(`session:${sessionID}`);
if (session == null) {
// session does not exist
throw new customError("sessionID does not exist", "apierror");
throw new customError('sessionID does not exist', 'apierror');
}
// everything is fine, return the sessioninfos
return session;
}
};
/**
* Deletes a session
*/
exports.deleteSession = async function(sessionID) {
exports.deleteSession = async function (sessionID) {
// ensure that the session exists
let session = await db.get("session:" + sessionID);
const session = await db.get(`session:${sessionID}`);
if (session == null) {
throw new customError("sessionID does not exist", "apierror");
throw new customError('sessionID does not exist', 'apierror');
}
// everything is fine, use the sessioninfos
let groupID = session.groupID;
let authorID = session.authorID;
const groupID = session.groupID;
const authorID = session.authorID;
// get the group2sessions and author2sessions entries
let group2sessions = await db.get("group2sessions:" + groupID);
let author2sessions = await db.get("author2sessions:" + authorID);
const group2sessions = await db.get(`group2sessions:${groupID}`);
const author2sessions = await db.get(`author2sessions:${authorID}`);
// remove the session
await db.remove("session:" + sessionID);
await db.remove(`session:${sessionID}`);
// remove session from group2sessions
if (group2sessions != null) { // Maybe the group was already deleted
delete group2sessions.sessionIDs[sessionID];
await db.set("group2sessions:" + groupID, group2sessions);
await db.set(`group2sessions:${groupID}`, group2sessions);
}
// remove session from author2sessions
if (author2sessions != null) { // Maybe the author was already deleted
delete author2sessions.sessionIDs[sessionID];
await db.set("author2sessions:" + authorID, author2sessions);
await db.set(`author2sessions:${authorID}`, author2sessions);
}
}
};
exports.listSessionsOfGroup = async function(groupID) {
exports.listSessionsOfGroup = async function (groupID) {
// check that the group exists
let exists = await groupManager.doesGroupExist(groupID);
const exists = await groupManager.doesGroupExist(groupID);
if (!exists) {
throw new customError("groupID does not exist", "apierror");
throw new customError('groupID does not exist', 'apierror');
}
let sessions = await listSessionsWithDBKey("group2sessions:" + groupID);
const sessions = await listSessionsWithDBKey(`group2sessions:${groupID}`);
return sessions;
}
};
exports.listSessionsOfAuthor = async function(authorID) {
exports.listSessionsOfAuthor = async function (authorID) {
// check that the author exists
let exists = await authorManager.doesAuthorExist(authorID)
const exists = await authorManager.doesAuthorExist(authorID);
if (!exists) {
throw new customError("authorID does not exist", "apierror");
throw new customError('authorID does not exist', 'apierror');
}
let sessions = await listSessionsWithDBKey("author2sessions:" + authorID);
const sessions = await listSessionsWithDBKey(`author2sessions:${authorID}`);
return sessions;
}
};
// this function is basically the code listSessionsOfAuthor and listSessionsOfGroup has in common
// required to return null rather than an empty object if there are none
async function listSessionsWithDBKey(dbkey) {
// get the group2sessions entry
let sessionObject = await db.get(dbkey);
let sessions = sessionObject ? sessionObject.sessionIDs : null;
const sessionObject = await db.get(dbkey);
const sessions = sessionObject ? sessionObject.sessionIDs : null;
// iterate through the sessions and get the sessioninfos
for (let sessionID in sessions) {
for (const sessionID in sessions) {
try {
let sessionInfo = await exports.getSessionInfo(sessionID);
const sessionInfo = await exports.getSessionInfo(sessionID);
sessions[sessionID] = sessionInfo;
} catch (err) {
if (err == "apierror: sessionID does not exist") {
if (err == 'apierror: sessionID does not exist') {
console.warn(`Found bad session ${sessionID} in ${dbkey}`);
sessions[sessionID] = null;
} else {

View file

@ -15,11 +15,11 @@ const logger = log4js.getLogger('SessionStore');
module.exports = class SessionStore extends Store {
get(sid, fn) {
logger.debug('GET ' + sid);
DB.db.get('sessionstorage:' + sid, (err, sess) => {
logger.debug(`GET ${sid}`);
DB.db.get(`sessionstorage:${sid}`, (err, sess) => {
if (sess) {
sess.cookie.expires = ('string' == typeof sess.cookie.expires
? new Date(sess.cookie.expires) : sess.cookie.expires);
sess.cookie.expires = ('string' === typeof sess.cookie.expires
? new Date(sess.cookie.expires) : sess.cookie.expires);
if (!sess.cookie.expires || new Date() < sess.cookie.expires) {
fn(null, sess);
} else {
@ -32,12 +32,12 @@ module.exports = class SessionStore extends Store {
}
set(sid, sess, fn) {
logger.debug('SET ' + sid);
DB.db.set('sessionstorage:' + sid, sess, fn);
logger.debug(`SET ${sid}`);
DB.db.set(`sessionstorage:${sid}`, sess, fn);
}
destroy(sid, fn) {
logger.debug('DESTROY ' + sid);
DB.db.remove('sessionstorage:' + sid, fn);
logger.debug(`DESTROY ${sid}`);
DB.db.remove(`sessionstorage:${sid}`, fn);
}
};

File diff suppressed because it is too large Load diff

View file

@ -19,84 +19,84 @@
* require("./index").require("./examples/foo.ejs")
*/
var ejs = require("ejs");
var fs = require("fs");
var path = require("path");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
var resolve = require("resolve");
var settings = require('../utils/Settings');
const ejs = require('ejs');
const fs = require('fs');
const path = require('path');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
const resolve = require('resolve');
const settings = require('../utils/Settings');
const templateCache = new Map()
const templateCache = new Map();
exports.info = {
__output_stack: [],
block_stack: [],
file_stack: [],
args: []
args: [],
};
function getCurrentFile() {
return exports.info.file_stack[exports.info.file_stack.length-1];
return exports.info.file_stack[exports.info.file_stack.length - 1];
}
function createBlockId(name) {
return getCurrentFile().path + '|' + name;
return `${getCurrentFile().path}|${name}`;
}
exports._init = function (b, recursive) {
exports.info.__output_stack.push(exports.info.__output);
exports.info.__output = b;
}
};
exports._exit = function (b, recursive) {
getCurrentFile().inherit.forEach(function (item) {
getCurrentFile().inherit.forEach((item) => {
exports._require(item.name, item.args);
});
exports.info.__output = exports.info.__output_stack.pop();
}
};
exports.begin_capture = function() {
exports.begin_capture = function () {
exports.info.__output_stack.push(exports.info.__output.concat());
exports.info.__output.splice(0, exports.info.__output.length);
}
};
exports.end_capture = function () {
var res = exports.info.__output.join("");
const res = exports.info.__output.join('');
exports.info.__output.splice.apply(
exports.info.__output,
[0, exports.info.__output.length].concat(exports.info.__output_stack.pop()));
exports.info.__output,
[0, exports.info.__output.length].concat(exports.info.__output_stack.pop()));
return res;
}
};
exports.begin_define_block = function (name) {
exports.info.block_stack.push(name);
exports.begin_capture();
}
};
exports.end_define_block = function () {
var content = exports.end_capture();
const content = exports.end_capture();
return content;
}
};
exports.end_block = function () {
var name = exports.info.block_stack.pop();
var renderContext = exports.info.args[exports.info.args.length-1];
var args = {content: exports.end_define_block(), renderContext: renderContext};
hooks.callAll("eejsBlock_" + name, args);
const name = exports.info.block_stack.pop();
const renderContext = exports.info.args[exports.info.args.length - 1];
const args = {content: exports.end_define_block(), renderContext};
hooks.callAll(`eejsBlock_${name}`, args);
exports.info.__output.push(args.content);
}
};
exports.begin_block = exports.begin_define_block;
exports.inherit = function (name, args) {
getCurrentFile().inherit.push({name:name, args:args});
}
getCurrentFile().inherit.push({name, args});
};
exports.require = function (name, args, mod) {
if (args == undefined) args = {};
var basedir = __dirname;
var paths = [];
let basedir = __dirname;
let paths = [];
if (exports.info.file_stack.length) {
basedir = path.dirname(getCurrentFile().path);
@ -106,43 +106,43 @@ exports.require = function (name, args, mod) {
paths = mod.paths;
}
var ejspath = resolve.sync(
name,
{
paths : paths,
basedir : basedir,
extensions : [ '.html', '.ejs' ],
}
)
const ejspath = resolve.sync(
name,
{
paths,
basedir,
extensions: ['.html', '.ejs'],
},
);
args.e = exports;
args.require = require;
let template
if (settings.maxAge !== 0){ // don't cache if maxAge is 0
let template;
if (settings.maxAge !== 0) { // don't cache if maxAge is 0
if (!templateCache.has(ejspath)) {
template = '<% e._init(__output); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>';
templateCache.set(ejspath, template)
template = `<% e._init(__output); %>${fs.readFileSync(ejspath).toString()}<% e._exit(); %>`;
templateCache.set(ejspath, template);
} else {
template = templateCache.get(ejspath)
template = templateCache.get(ejspath);
}
}else{
template = '<% e._init(__output); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>';
} else {
template = `<% e._init(__output); %>${fs.readFileSync(ejspath).toString()}<% e._exit(); %>`;
}
exports.info.args.push(args);
exports.info.file_stack.push({path: ejspath, inherit: []});
if(settings.maxAge !== 0){
var res = ejs.render(template, args, { cache: true, filename: ejspath });
}else{
var res = ejs.render(template, args, { cache: false, filename: ejspath });
if (settings.maxAge !== 0) {
var res = ejs.render(template, args, {cache: true, filename: ejspath});
} else {
var res = ejs.render(template, args, {cache: false, filename: ejspath});
}
exports.info.file_stack.pop();
exports.info.args.pop();
return res;
}
};
exports._require = function (name, args) {
exports.info.__output.push(exports.require(name, args));
}
};

View file

@ -18,131 +18,118 @@
* limitations under the License.
*/
var absolutePaths = require('../utils/AbsolutePaths');
var fs = require("fs");
var api = require("../db/API");
var log4js = require('log4js');
var padManager = require("../db/PadManager");
var randomString = require("../utils/randomstring");
var argv = require('../utils/Cli').argv;
var createHTTPError = require('http-errors');
const absolutePaths = require('../utils/AbsolutePaths');
const fs = require('fs');
const api = require('../db/API');
const log4js = require('log4js');
const padManager = require('../db/PadManager');
const randomString = require('../utils/randomstring');
const argv = require('../utils/Cli').argv;
const createHTTPError = require('http-errors');
var apiHandlerLogger = log4js.getLogger('APIHandler');
const apiHandlerLogger = log4js.getLogger('APIHandler');
//ensure we have an apikey
var apikey = null;
var apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || "./APIKEY.txt");
// ensure we have an apikey
let apikey = null;
const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt');
try {
apikey = fs.readFileSync(apikeyFilename,"utf8");
apikey = fs.readFileSync(apikeyFilename, 'utf8');
apiHandlerLogger.info(`Api key file read from: "${apikeyFilename}"`);
} catch(e) {
} catch (e) {
apiHandlerLogger.info(`Api key file "${apikeyFilename}" not found. Creating with random contents.`);
apikey = randomString(32);
fs.writeFileSync(apikeyFilename,apikey,"utf8");
fs.writeFileSync(apikeyFilename, apikey, 'utf8');
}
// a list of all functions
var version = {};
const version = {};
version["1"] = Object.assign({},
{ "createGroup" : []
, "createGroupIfNotExistsFor" : ["groupMapper"]
, "deleteGroup" : ["groupID"]
, "listPads" : ["groupID"]
, "createPad" : ["padID", "text"]
, "createGroupPad" : ["groupID", "padName", "text"]
, "createAuthor" : ["name"]
, "createAuthorIfNotExistsFor": ["authorMapper" , "name"]
, "listPadsOfAuthor" : ["authorID"]
, "createSession" : ["groupID", "authorID", "validUntil"]
, "deleteSession" : ["sessionID"]
, "getSessionInfo" : ["sessionID"]
, "listSessionsOfGroup" : ["groupID"]
, "listSessionsOfAuthor" : ["authorID"]
, "getText" : ["padID", "rev"]
, "setText" : ["padID", "text"]
, "getHTML" : ["padID", "rev"]
, "setHTML" : ["padID", "html"]
, "getRevisionsCount" : ["padID"]
, "getLastEdited" : ["padID"]
, "deletePad" : ["padID"]
, "getReadOnlyID" : ["padID"]
, "setPublicStatus" : ["padID", "publicStatus"]
, "getPublicStatus" : ["padID"]
, "listAuthorsOfPad" : ["padID"]
, "padUsersCount" : ["padID"]
}
version['1'] = Object.assign({},
{createGroup: [],
createGroupIfNotExistsFor: ['groupMapper'],
deleteGroup: ['groupID'],
listPads: ['groupID'],
createPad: ['padID', 'text'],
createGroupPad: ['groupID', 'padName', 'text'],
createAuthor: ['name'],
createAuthorIfNotExistsFor: ['authorMapper', 'name'],
listPadsOfAuthor: ['authorID'],
createSession: ['groupID', 'authorID', 'validUntil'],
deleteSession: ['sessionID'],
getSessionInfo: ['sessionID'],
listSessionsOfGroup: ['groupID'],
listSessionsOfAuthor: ['authorID'],
getText: ['padID', 'rev'],
setText: ['padID', 'text'],
getHTML: ['padID', 'rev'],
setHTML: ['padID', 'html'],
getRevisionsCount: ['padID'],
getLastEdited: ['padID'],
deletePad: ['padID'],
getReadOnlyID: ['padID'],
setPublicStatus: ['padID', 'publicStatus'],
getPublicStatus: ['padID'],
listAuthorsOfPad: ['padID'],
padUsersCount: ['padID']},
);
version["1.1"] = Object.assign({}, version["1"],
{ "getAuthorName" : ["authorID"]
, "padUsers" : ["padID"]
, "sendClientsMessage" : ["padID", "msg"]
, "listAllGroups" : []
}
version['1.1'] = Object.assign({}, version['1'],
{getAuthorName: ['authorID'],
padUsers: ['padID'],
sendClientsMessage: ['padID', 'msg'],
listAllGroups: []},
);
version["1.2"] = Object.assign({}, version["1.1"],
{ "checkToken" : []
}
version['1.2'] = Object.assign({}, version['1.1'],
{checkToken: []},
);
version["1.2.1"] = Object.assign({}, version["1.2"],
{ "listAllPads" : []
}
version['1.2.1'] = Object.assign({}, version['1.2'],
{listAllPads: []},
);
version["1.2.7"] = Object.assign({}, version["1.2.1"],
{ "createDiffHTML" : ["padID", "startRev", "endRev"]
, "getChatHistory" : ["padID", "start", "end"]
, "getChatHead" : ["padID"]
}
version['1.2.7'] = Object.assign({}, version['1.2.1'],
{createDiffHTML: ['padID', 'startRev', 'endRev'],
getChatHistory: ['padID', 'start', 'end'],
getChatHead: ['padID']},
);
version["1.2.8"] = Object.assign({}, version["1.2.7"],
{ "getAttributePool" : ["padID"]
, "getRevisionChangeset" : ["padID", "rev"]
}
version['1.2.8'] = Object.assign({}, version['1.2.7'],
{getAttributePool: ['padID'],
getRevisionChangeset: ['padID', 'rev']},
);
version["1.2.9"] = Object.assign({}, version["1.2.8"],
{ "copyPad" : ["sourceID", "destinationID", "force"]
, "movePad" : ["sourceID", "destinationID", "force"]
}
version['1.2.9'] = Object.assign({}, version['1.2.8'],
{copyPad: ['sourceID', 'destinationID', 'force'],
movePad: ['sourceID', 'destinationID', 'force']},
);
version["1.2.10"] = Object.assign({}, version["1.2.9"],
{ "getPadID" : ["roID"]
}
version['1.2.10'] = Object.assign({}, version['1.2.9'],
{getPadID: ['roID']},
);
version["1.2.11"] = Object.assign({}, version["1.2.10"],
{ "getSavedRevisionsCount" : ["padID"]
, "listSavedRevisions" : ["padID"]
, "saveRevision" : ["padID", "rev"]
, "restoreRevision" : ["padID", "rev"]
}
version['1.2.11'] = Object.assign({}, version['1.2.10'],
{getSavedRevisionsCount: ['padID'],
listSavedRevisions: ['padID'],
saveRevision: ['padID', 'rev'],
restoreRevision: ['padID', 'rev']},
);
version["1.2.12"] = Object.assign({}, version["1.2.11"],
{ "appendChatMessage" : ["padID", "text", "authorID", "time"]
}
version['1.2.12'] = Object.assign({}, version['1.2.11'],
{appendChatMessage: ['padID', 'text', 'authorID', 'time']},
);
version["1.2.13"] = Object.assign({}, version["1.2.12"],
{ "appendText" : ["padID", "text"]
}
version['1.2.13'] = Object.assign({}, version['1.2.12'],
{appendText: ['padID', 'text']},
);
version["1.2.14"] = Object.assign({}, version["1.2.13"],
{ "getStats" : []
}
version['1.2.14'] = Object.assign({}, version['1.2.13'],
{getStats: []},
);
version["1.2.15"] = Object.assign({}, version["1.2.14"],
{ "copyPadWithoutHistory" : ["sourceID", "destinationID", "force"]
}
version['1.2.15'] = Object.assign({}, version['1.2.14'],
{copyPadWithoutHistory: ['sourceID', 'destinationID', 'force']},
);
// set the latest available API version here
@ -158,7 +145,7 @@ exports.version = version;
* @req express request object
* @res express response object
*/
exports.handle = async function(apiVersion, functionName, fields, req, res) {
exports.handle = async function (apiVersion, functionName, fields, req, res) {
// say goodbye if this is an unknown API version
if (!(apiVersion in version)) {
throw new createHTTPError.NotFound('no such api version');
@ -170,31 +157,29 @@ exports.handle = async function(apiVersion, functionName, fields, req, res) {
}
// check the api key!
fields["apikey"] = fields["apikey"] || fields["api_key"];
fields.apikey = fields.apikey || fields.api_key;
if (fields["apikey"] !== apikey.trim()) {
if (fields.apikey !== apikey.trim()) {
throw new createHTTPError.Unauthorized('no or wrong API Key');
}
// sanitize any padIDs before continuing
if (fields["padID"]) {
fields["padID"] = await padManager.sanitizePadId(fields["padID"]);
if (fields.padID) {
fields.padID = await padManager.sanitizePadId(fields.padID);
}
// there was an 'else' here before - removed it to ensure
// that this sanitize step can't be circumvented by forcing
// the first branch to be taken
if (fields["padName"]) {
fields["padName"] = await padManager.sanitizePadId(fields["padName"]);
if (fields.padName) {
fields.padName = await padManager.sanitizePadId(fields.padName);
}
// put the function parameters in an array
var functionParams = version[apiVersion][functionName].map(function (field) {
return fields[field]
});
const functionParams = version[apiVersion][functionName].map((field) => fields[field]);
// call the api function
return api[functionName].apply(this, functionParams);
}
};
exports.exportedForTestingOnly = {
apiKey: apikey,

View file

@ -19,15 +19,15 @@
* limitations under the License.
*/
var exporthtml = require("../utils/ExportHtml");
var exporttxt = require("../utils/ExportTxt");
var exportEtherpad = require("../utils/ExportEtherpad");
var fs = require("fs");
var settings = require('../utils/Settings');
var os = require('os');
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var TidyHtml = require('../utils/TidyHtml');
const util = require("util");
const exporthtml = require('../utils/ExportHtml');
const exporttxt = require('../utils/ExportTxt');
const exportEtherpad = require('../utils/ExportEtherpad');
const fs = require('fs');
const settings = require('../utils/Settings');
const os = require('os');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
const TidyHtml = require('../utils/TidyHtml');
const util = require('util');
const fsp_writeFile = util.promisify(fs.writeFile);
const fsp_unlink = util.promisify(fs.unlink);
@ -36,12 +36,12 @@ let convertor = null;
// load abiword only if it is enabled
if (settings.abiword != null) {
convertor = require("../utils/Abiword");
convertor = require('../utils/Abiword');
}
// Use LibreOffice if an executable has been defined in the settings
if (settings.soffice != null) {
convertor = require("../utils/LibreOffice");
convertor = require('../utils/LibreOffice');
}
const tempDirectory = os.tmpdir();
@ -51,10 +51,10 @@ const tempDirectory = os.tmpdir();
*/
async function doExport(req, res, padId, readOnlyId, type) {
// avoid naming the read-only file as the original pad's id
var fileName = readOnlyId ? readOnlyId : padId;
let fileName = readOnlyId ? readOnlyId : padId;
// allow fileName to be overwritten by a hook, the type type is kept static for security reasons
let hookFileName = await hooks.aCallFirst("exportFileName", padId);
const hookFileName = await hooks.aCallFirst('exportFileName', padId);
// if fileName is set then set it to the padId, note that fileName is returned as an array.
if (hookFileName.length) {
@ -62,15 +62,15 @@ async function doExport(req, res, padId, readOnlyId, type) {
}
// tell the browser that this is a downloadable file
res.attachment(fileName + "." + type);
res.attachment(`${fileName}.${type}`);
// if this is a plain text export, we can do this directly
// We have to over engineer this because tabs are stored as attributes and not plain text
if (type === "etherpad") {
let pad = await exportEtherpad.getPadRaw(padId);
if (type === 'etherpad') {
const pad = await exportEtherpad.getPadRaw(padId);
res.send(pad);
} else if (type === "txt") {
let txt = await exporttxt.getPadTXTDocument(padId, req.params.rev);
} else if (type === 'txt') {
const txt = await exporttxt.getPadTXTDocument(padId, req.params.rev);
res.send(txt);
} else {
// render the html document
@ -79,17 +79,17 @@ async function doExport(req, res, padId, readOnlyId, type) {
// decide what to do with the html export
// if this is a html export, we can send this from here directly
if (type === "html") {
if (type === 'html') {
// do any final changes the plugin might want to make
let newHTML = await hooks.aCallFirst("exportHTMLSend", html);
const newHTML = await hooks.aCallFirst('exportHTMLSend', html);
if (newHTML.length) html = newHTML;
res.send(html);
throw "stop";
throw 'stop';
}
// else write the html export to a file
let randNum = Math.floor(Math.random()*0xFFFFFFFF);
let srcFile = tempDirectory + "/etherpad_export_" + randNum + ".html";
const randNum = Math.floor(Math.random() * 0xFFFFFFFF);
const srcFile = `${tempDirectory}/etherpad_export_${randNum}.html`;
await fsp_writeFile(srcFile, html);
// Tidy up the exported HTML
@ -98,42 +98,42 @@ async function doExport(req, res, padId, readOnlyId, type) {
await TidyHtml.tidy(srcFile);
// send the convert job to the convertor (abiword, libreoffice, ..)
let destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type;
const destFile = `${tempDirectory}/etherpad_export_${randNum}.${type}`;
// Allow plugins to overwrite the convert in export process
let result = await hooks.aCallAll("exportConvert", { srcFile, destFile, req, res });
const result = await hooks.aCallAll('exportConvert', {srcFile, destFile, req, res});
if (result.length > 0) {
// console.log("export handled by plugin", destFile);
handledByPlugin = true;
} else {
// @TODO no Promise interface for convertors (yet)
await new Promise((resolve, reject) => {
convertor.convertFile(srcFile, destFile, type, function(err) {
err ? reject("convertFailed") : resolve();
convertor.convertFile(srcFile, destFile, type, (err) => {
err ? reject('convertFailed') : resolve();
});
});
}
// send the file
let sendFile = util.promisify(res.sendFile);
const sendFile = util.promisify(res.sendFile);
await res.sendFile(destFile, null);
// clean up temporary files
await fsp_unlink(srcFile);
// 100ms delay to accommodate for slow windows fs
if (os.type().indexOf("Windows") > -1) {
await new Promise(resolve => setTimeout(resolve, 100));
if (os.type().indexOf('Windows') > -1) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
await fsp_unlink(destFile);
}
}
exports.doExport = function(req, res, padId, readOnlyId, type) {
doExport(req, res, padId, readOnlyId, type).catch(err => {
if (err !== "stop") {
exports.doExport = function (req, res, padId, readOnlyId, type) {
doExport(req, res, padId, readOnlyId, type).catch((err) => {
if (err !== 'stop') {
throw err;
}
});
}
};

View file

@ -20,36 +20,36 @@
* limitations under the License.
*/
var padManager = require("../db/PadManager")
, padMessageHandler = require("./PadMessageHandler")
, fs = require("fs")
, path = require("path")
, settings = require('../utils/Settings')
, formidable = require('formidable')
, os = require("os")
, importHtml = require("../utils/ImportHtml")
, importEtherpad = require("../utils/ImportEtherpad")
, log4js = require("log4js")
, hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js")
, util = require("util");
const padManager = require('../db/PadManager');
const padMessageHandler = require('./PadMessageHandler');
const fs = require('fs');
const path = require('path');
const settings = require('../utils/Settings');
const formidable = require('formidable');
const os = require('os');
const importHtml = require('../utils/ImportHtml');
const importEtherpad = require('../utils/ImportEtherpad');
const log4js = require('log4js');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
const util = require('util');
let fsp_exists = util.promisify(fs.exists);
let fsp_rename = util.promisify(fs.rename);
let fsp_readFile = util.promisify(fs.readFile);
let fsp_unlink = util.promisify(fs.unlink)
const fsp_exists = util.promisify(fs.exists);
const fsp_rename = util.promisify(fs.rename);
const fsp_readFile = util.promisify(fs.readFile);
const fsp_unlink = util.promisify(fs.unlink);
let convertor = null;
let exportExtension = "htm";
let exportExtension = 'htm';
// load abiword only if it is enabled and if soffice is disabled
if (settings.abiword != null && settings.soffice === null) {
convertor = require("../utils/Abiword");
convertor = require('../utils/Abiword');
}
// load soffice only if it is enabled
if (settings.soffice != null) {
convertor = require("../utils/LibreOffice");
exportExtension = "html";
convertor = require('../utils/LibreOffice');
exportExtension = 'html';
}
const tmpDirectory = os.tmpdir();
@ -58,28 +58,28 @@ const tmpDirectory = os.tmpdir();
* do a requested import
*/
async function doImport(req, res, padId) {
var apiLogger = log4js.getLogger("ImportHandler");
const apiLogger = log4js.getLogger('ImportHandler');
// pipe to a file
// convert file to html via abiword or soffice
// set html in the pad
var randNum = Math.floor(Math.random()*0xFFFFFFFF);
const randNum = Math.floor(Math.random() * 0xFFFFFFFF);
// setting flag for whether to use convertor or not
let useConvertor = (convertor != null);
let form = new formidable.IncomingForm();
const form = new formidable.IncomingForm();
form.keepExtensions = true;
form.uploadDir = tmpDirectory;
form.maxFileSize = settings.importMaxFileSize;
// Ref: https://github.com/node-formidable/formidable/issues/469
// Crash in Etherpad was Uploading Error: Error: Request aborted
// [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed
form.onPart = part => {
form.onPart = (part) => {
form.handlePart(part);
if (part.filename !== undefined) {
form.openedFiles[form.openedFiles.length - 1]._writeStream.on('error', err => {
form.openedFiles[form.openedFiles.length - 1]._writeStream.on('error', (err) => {
form.emit('error', err);
});
}
@ -87,23 +87,23 @@ async function doImport(req, res, padId) {
// locally wrapped Promise, since form.parse requires a callback
let srcFile = await new Promise((resolve, reject) => {
form.parse(req, function(err, fields, files) {
form.parse(req, (err, fields, files) => {
if (err || files.file === undefined) {
// the upload failed, stop at this point
if (err) {
console.warn("Uploading Error: " + err.stack);
console.warn(`Uploading Error: ${err.stack}`);
}
// I hate doing indexOf here but I can't see anything to use...
if (err && err.stack && err.stack.indexOf("maxFileSize") !== -1) {
reject("maxFileSize");
if (err && err.stack && err.stack.indexOf('maxFileSize') !== -1) {
reject('maxFileSize');
}
reject("uploadFailed");
reject('uploadFailed');
}
if(!files.file){ // might not be a graceful fix but it works
reject("uploadFailed");
}else{
if (!files.file) { // might not be a graceful fix but it works
reject('uploadFailed');
} else {
resolve(files.file.path);
}
});
@ -111,47 +111,47 @@ async function doImport(req, res, padId) {
// ensure this is a file ending we know, else we change the file ending to .txt
// this allows us to accept source code files like .c or .java
let fileEnding = path.extname(srcFile).toLowerCase()
, knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm", ".etherpad", ".rtf"]
, fileEndingUnknown = (knownFileEndings.indexOf(fileEnding) < 0);
const fileEnding = path.extname(srcFile).toLowerCase();
const knownFileEndings = ['.txt', '.doc', '.docx', '.pdf', '.odt', '.html', '.htm', '.etherpad', '.rtf'];
const fileEndingUnknown = (knownFileEndings.indexOf(fileEnding) < 0);
if (fileEndingUnknown) {
// the file ending is not known
if (settings.allowUnknownFileEnds === true) {
// we need to rename this file with a .txt ending
let oldSrcFile = srcFile;
const oldSrcFile = srcFile;
srcFile = path.join(path.dirname(srcFile), path.basename(srcFile, fileEnding) + ".txt");
srcFile = path.join(path.dirname(srcFile), `${path.basename(srcFile, fileEnding)}.txt`);
await fsp_rename(oldSrcFile, srcFile);
} else {
console.warn("Not allowing unknown file type to be imported", fileEnding);
throw "uploadFailed";
console.warn('Not allowing unknown file type to be imported', fileEnding);
throw 'uploadFailed';
}
}
let destFile = path.join(tmpDirectory, "etherpad_import_" + randNum + "." + exportExtension);
const destFile = path.join(tmpDirectory, `etherpad_import_${randNum}.${exportExtension}`);
// Logic for allowing external Import Plugins
let result = await hooks.aCallAll("import", { srcFile, destFile, fileEnding });
let importHandledByPlugin = (result.length > 0); // This feels hacky and wrong..
const result = await hooks.aCallAll('import', {srcFile, destFile, fileEnding});
const importHandledByPlugin = (result.length > 0); // This feels hacky and wrong..
let fileIsEtherpad = (fileEnding === ".etherpad");
let fileIsHTML = (fileEnding === ".html" || fileEnding === ".htm");
let fileIsTXT = (fileEnding === ".txt");
const fileIsEtherpad = (fileEnding === '.etherpad');
const fileIsHTML = (fileEnding === '.html' || fileEnding === '.htm');
const fileIsTXT = (fileEnding === '.txt');
if (fileIsEtherpad) {
// we do this here so we can see if the pad has quite a few edits
let _pad = await padManager.getPad(padId);
let headCount = _pad.head;
const _pad = await padManager.getPad(padId);
const headCount = _pad.head;
if (headCount >= 10) {
apiLogger.warn("Direct database Import attempt of a pad that already has content, we won't be doing this");
throw "padHasData";
throw 'padHasData';
}
const fsp_readFile = util.promisify(fs.readFile);
let _text = await fsp_readFile(srcFile, "utf8");
const _text = await fsp_readFile(srcFile, 'utf8');
req.directDatabaseAccess = true;
await importEtherpad.setPadRaw(padId, _text);
}
@ -170,11 +170,11 @@ async function doImport(req, res, padId) {
} else {
// @TODO - no Promise interface for convertors (yet)
await new Promise((resolve, reject) => {
convertor.convertFile(srcFile, destFile, exportExtension, function(err) {
convertor.convertFile(srcFile, destFile, exportExtension, (err) => {
// catch convert errors
if (err) {
console.warn("Converting Error:", err);
reject("convertFailed");
console.warn('Converting Error:', err);
reject('convertFailed');
}
resolve();
});
@ -184,13 +184,13 @@ async function doImport(req, res, padId) {
if (!useConvertor && !req.directDatabaseAccess) {
// Read the file with no encoding for raw buffer access.
let buf = await fsp_readFile(destFile);
const buf = await fsp_readFile(destFile);
// Check if there are only ascii chars in the uploaded file
let isAscii = ! Array.prototype.some.call(buf, c => (c > 240));
const isAscii = !Array.prototype.some.call(buf, (c) => (c > 240));
if (!isAscii) {
throw "uploadFailed";
throw 'uploadFailed';
}
}
@ -201,7 +201,7 @@ async function doImport(req, res, padId) {
let text;
if (!req.directDatabaseAccess) {
text = await fsp_readFile(destFile, "utf8");
text = await fsp_readFile(destFile, 'utf8');
/*
* The <title> tag needs to be stripped out, otherwise it is appended to the
@ -211,13 +211,13 @@ async function doImport(req, res, padId) {
* added to the <title> tag. This is a quick & dirty way of matching the
* title and comment it out independently on the classes that are set on it.
*/
text = text.replace("<title", "<!-- <title");
text = text.replace("</title>","</title>-->");
text = text.replace('<title', '<!-- <title');
text = text.replace('</title>', '</title>-->');
// node on windows has a delay on releasing of the file lock.
// We add a 100ms delay to work around this
if (os.type().indexOf("Windows") > -1){
await new Promise(resolve => setTimeout(resolve, 100));
if (os.type().indexOf('Windows') > -1) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
@ -227,7 +227,7 @@ async function doImport(req, res, padId) {
try {
await importHtml.setPadHTML(pad, text);
} catch (e) {
apiLogger.warn("Error importing, possibly caused by malformed HTML");
apiLogger.warn('Error importing, possibly caused by malformed HTML');
}
} else {
await pad.setText(text);
@ -274,16 +274,16 @@ exports.doImport = function (req, res, padId) {
* the function above there's no other way to return
* a value to the caller.
*/
let status = "ok";
doImport(req, res, padId).catch(err => {
let status = 'ok';
doImport(req, res, padId).catch((err) => {
// check for known errors and replace the status
if (err == "uploadFailed" || err == "convertFailed" || err == "padHasData" || err == "maxFileSize") {
if (err == 'uploadFailed' || err == 'convertFailed' || err == 'padHasData' || err == 'maxFileSize') {
status = err;
} else {
throw err;
}
}).then(() => {
// close the connection
res.send("<script>document.addEventListener('DOMContentLoaded', function(){ var impexp = window.parent.padimpexp.handleFrameCall('" + req.directDatabaseAccess +"', '" + status + "'); })</script>");
res.send(`<script>document.addEventListener('DOMContentLoaded', function(){ var impexp = window.parent.padimpexp.handleFrameCall('${req.directDatabaseAccess}', '${status}'); })</script>`);
});
}
};

File diff suppressed because it is too large Load diff

View file

@ -19,53 +19,53 @@
* limitations under the License.
*/
var log4js = require('log4js');
var messageLogger = log4js.getLogger("message");
var securityManager = require("../db/SecurityManager");
var readOnlyManager = require("../db/ReadOnlyManager");
var settings = require('../utils/Settings');
const log4js = require('log4js');
const messageLogger = log4js.getLogger('message');
const securityManager = require('../db/SecurityManager');
const readOnlyManager = require('../db/ReadOnlyManager');
const settings = require('../utils/Settings');
/**
* Saves all components
* key is the component name
* value is the component module
*/
var components = {};
const components = {};
var socket;
let socket;
/**
* adds a component
*/
exports.addComponent = function(moduleName, module) {
exports.addComponent = function (moduleName, module) {
// save the component
components[moduleName] = module;
// give the module the socket
module.setSocketIO(socket);
}
};
/**
* sets the socket.io and adds event functions for routing
*/
exports.setSocketIO = function(_socket) {
exports.setSocketIO = function (_socket) {
// save this socket internaly
socket = _socket;
socket.sockets.on('connection', function(client) {
socket.sockets.on('connection', (client) => {
// wrap the original send function to log the messages
client._send = client.send;
client.send = function(message) {
client.send = function (message) {
messageLogger.debug(`to ${client.id}: ${JSON.stringify(message)}`);
client._send(message);
}
};
// tell all components about this connect
for (let i in components) {
for (const i in components) {
components[i].handleConnect(client);
}
client.on('message', async function(message) {
client.on('message', async (message) => {
if (message.protocolVersion && message.protocolVersion != 2) {
messageLogger.warn(`Protocolversion header is not correct: ${JSON.stringify(message)}`);
return;
@ -78,11 +78,11 @@ exports.setSocketIO = function(_socket) {
await components[message.component].handleMessage(client, message);
});
client.on('disconnect', function() {
client.on('disconnect', () => {
// tell all components about this disconnect
for (let i in components) {
for (const i in components) {
components[i].handleDisconnect(client);
}
});
});
}
};

View file

@ -18,7 +18,7 @@ let serverName;
exports.server = null;
exports.createServer = async () => {
console.log("Report bugs at https://github.com/ether/etherpad-lite/issues")
console.log('Report bugs at https://github.com/ether/etherpad-lite/issues');
serverName = `Etherpad ${settings.getGitCommit()} (https://etherpad.org)`;
@ -26,7 +26,7 @@ exports.createServer = async () => {
await exports.restartServer();
if (settings.ip === "") {
if (settings.ip === '') {
// using Unix socket for connectivity
console.log(`You can access your Etherpad instance using the Unix socket at ${settings.port}`);
} else {
@ -42,26 +42,26 @@ exports.createServer = async () => {
const env = process.env.NODE_ENV || 'development';
if (env !== 'production') {
console.warn("Etherpad is running in Development mode. This mode is slower for users and less secure than production mode. You should set the NODE_ENV environment variable to production by using: export NODE_ENV=production");
console.warn('Etherpad is running in Development mode. This mode is slower for users and less secure than production mode. You should set the NODE_ENV environment variable to production by using: export NODE_ENV=production');
}
}
};
exports.restartServer = async () => {
if (exports.server) {
console.log("Restarting express server");
console.log('Restarting express server');
await util.promisify(exports.server.close).bind(exports.server)();
}
const app = express(); // New syntax for express v3
if (settings.ssl) {
console.log("SSL -- enabled");
console.log('SSL -- enabled');
console.log(`SSL -- server key file: ${settings.ssl.key}`);
console.log(`SSL -- Certificate Authority's certificate file: ${settings.ssl.cert}`);
const options = {
key: fs.readFileSync( settings.ssl.key ),
cert: fs.readFileSync( settings.ssl.cert )
key: fs.readFileSync(settings.ssl.key),
cert: fs.readFileSync(settings.ssl.cert),
};
if (settings.ssl.ca) {
@ -79,16 +79,16 @@ exports.restartServer = async () => {
exports.server = http.createServer(app);
}
app.use(function(req, res, next) {
app.use((req, res, next) => {
// res.header("X-Frame-Options", "deny"); // breaks embedded pads
if (settings.ssl) {
// we use SSL
res.header("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
res.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
// Stop IE going into compatability mode
// https://github.com/ether/etherpad-lite/issues/2547
res.header("X-UA-Compatible", "IE=Edge,chrome=1");
res.header('X-UA-Compatible', 'IE=Edge,chrome=1');
// Enable a strong referrer policy. Same-origin won't drop Referers when
// loading local resources, but it will drop them when loading foreign resources.
@ -97,11 +97,11 @@ exports.restartServer = async () => {
// marked with <meta name="referrer" content="no-referrer">
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
// https://github.com/ether/etherpad-lite/pull/3636
res.header("Referrer-Policy", "same-origin");
res.header('Referrer-Policy', 'same-origin');
// send git version in the Server response header if exposeVersion is true.
if (settings.exposeVersion) {
res.header("Server", serverName);
res.header('Server', serverName);
}
next();
@ -165,13 +165,13 @@ exports.restartServer = async () => {
//
// reference: https://github.com/expressjs/session/blob/v1.17.0/README.md#cookiesecure
secure: 'auto',
}
},
});
app.use(exports.sessionMiddleware);
app.use(cookieParser(settings.sessionKey, {}));
hooks.callAll("expressConfigure", {"app": app});
hooks.callAll('expressConfigure', {app});
hooks.callAll('expressCreateServer', {app, server: exports.server});
await util.promisify(exports.server.listen).bind(exports.server)(settings.port, settings.ip);

View file

@ -1,9 +1,9 @@
var eejs = require('ep_etherpad-lite/node/eejs');
const eejs = require('ep_etherpad-lite/node/eejs');
exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/admin', function(req, res) {
if('/' != req.path[req.path.length-1]) return res.redirect('./admin/');
args.app.get('/admin', (req, res) => {
if ('/' != req.path[req.path.length - 1]) return res.redirect('./admin/');
res.send(eejs.require('ep_etherpad-lite/templates/admin/index.html', {req}));
});
return cb();
}
};

View file

@ -1,13 +1,13 @@
var eejs = require('ep_etherpad-lite/node/eejs');
var settings = require('ep_etherpad-lite/node/utils/Settings');
var installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
var _ = require('underscore');
var semver = require('semver');
const eejs = require('ep_etherpad-lite/node/eejs');
const settings = require('ep_etherpad-lite/node/utils/Settings');
const installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
const _ = require('underscore');
const semver = require('semver');
const UpdateCheck = require('ep_etherpad-lite/node/utils/UpdateCheck');
exports.expressCreateServer = function(hook_name, args, cb) {
args.app.get('/admin/plugins', function(req, res) {
exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/admin/plugins', (req, res) => {
res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins.html', {
plugins: plugins.plugins,
req,
@ -16,114 +16,108 @@ exports.expressCreateServer = function(hook_name, args, cb) {
}));
});
args.app.get('/admin/plugins/info', function(req, res) {
var gitCommit = settings.getGitCommit();
var epVersion = settings.getEpVersion();
args.app.get('/admin/plugins/info', (req, res) => {
const gitCommit = settings.getGitCommit();
const epVersion = settings.getEpVersion();
res.send(eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", {
gitCommit: gitCommit,
epVersion: epVersion,
res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins-info.html', {
gitCommit,
epVersion,
latestVersion: UpdateCheck.getLatestVersion(),
req,
}));
});
return cb();
}
};
exports.socketio = function(hook_name, args, cb) {
var io = args.io.of("/pluginfw/installer");
io.on('connection', function(socket) {
exports.socketio = function (hook_name, args, cb) {
const io = args.io.of('/pluginfw/installer');
io.on('connection', (socket) => {
if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return;
socket.on("getInstalled", function(query) {
socket.on('getInstalled', (query) => {
// send currently installed plugins
var installed = Object.keys(plugins.plugins).map(function(plugin) {
return plugins.plugins[plugin].package
});
const installed = Object.keys(plugins.plugins).map((plugin) => plugins.plugins[plugin].package);
socket.emit("results:installed", {installed: installed});
socket.emit('results:installed', {installed});
});
socket.on("checkUpdates", async function() {
socket.on('checkUpdates', async () => {
// Check plugins for updates
try {
let results = await installer.getAvailablePlugins(/*maxCacheAge:*/ 60 * 10);
const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ 60 * 10);
var updatable = _(plugins.plugins).keys().filter(function(plugin) {
const updatable = _(plugins.plugins).keys().filter((plugin) => {
if (!results[plugin]) return false;
var latestVersion = results[plugin].version;
var currentVersion = plugins.plugins[plugin].package.version;
const latestVersion = results[plugin].version;
const currentVersion = plugins.plugins[plugin].package.version;
return semver.gt(latestVersion, currentVersion);
});
socket.emit("results:updatable", {updatable: updatable});
socket.emit('results:updatable', {updatable});
} catch (er) {
console.warn(er);
socket.emit("results:updatable", {updatable: {}});
socket.emit('results:updatable', {updatable: {}});
}
});
socket.on("getAvailable", async function(query) {
socket.on('getAvailable', async (query) => {
try {
let results = await installer.getAvailablePlugins(/*maxCacheAge:*/ false);
socket.emit("results:available", results);
const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ false);
socket.emit('results:available', results);
} catch (er) {
console.error(er);
socket.emit("results:available", {});
socket.emit('results:available', {});
}
});
socket.on("search", async function(query) {
socket.on('search', async (query) => {
try {
let results = await installer.search(query.searchTerm, /*maxCacheAge:*/ 60 * 10);
var res = Object.keys(results)
.map(function(pluginName) {
return results[pluginName];
})
.filter(function(plugin) {
return !plugins.plugins[plugin.name];
});
const results = await installer.search(query.searchTerm, /* maxCacheAge:*/ 60 * 10);
let res = Object.keys(results)
.map((pluginName) => results[pluginName])
.filter((plugin) => !plugins.plugins[plugin.name]);
res = sortPluginList(res, query.sortBy, query.sortDir)
.slice(query.offset, query.offset+query.limit);
socket.emit("results:search", {results: res, query: query});
.slice(query.offset, query.offset + query.limit);
socket.emit('results:search', {results: res, query});
} catch (er) {
console.error(er);
socket.emit("results:search", {results: {}, query: query});
socket.emit('results:search', {results: {}, query});
}
});
socket.on("install", function(plugin_name) {
installer.install(plugin_name, function(er) {
socket.on('install', (plugin_name) => {
installer.install(plugin_name, (er) => {
if (er) console.warn(er);
socket.emit("finished:install", {plugin: plugin_name, code: er? er.code : null, error: er? er.message : null});
socket.emit('finished:install', {plugin: plugin_name, code: er ? er.code : null, error: er ? er.message : null});
});
});
socket.on("uninstall", function(plugin_name) {
installer.uninstall(plugin_name, function(er) {
socket.on('uninstall', (plugin_name) => {
installer.uninstall(plugin_name, (er) => {
if (er) console.warn(er);
socket.emit("finished:uninstall", {plugin: plugin_name, error: er? er.message : null});
socket.emit('finished:uninstall', {plugin: plugin_name, error: er ? er.message : null});
});
});
});
return cb();
}
};
function sortPluginList(plugins, property, /*ASC?*/dir) {
return plugins.sort(function(a, b) {
function sortPluginList(plugins, property, /* ASC?*/dir) {
return plugins.sort((a, b) => {
if (a[property] < b[property]) {
return dir? -1 : 1;
return dir ? -1 : 1;
}
if (a[property] > b[property]) {
return dir? 1 : -1;
return dir ? 1 : -1;
}
// a must be equal to b

View file

@ -1,55 +1,52 @@
var eejs = require('ep_etherpad-lite/node/eejs');
var settings = require('ep_etherpad-lite/node/utils/Settings');
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var fs = require('fs');
const eejs = require('ep_etherpad-lite/node/eejs');
const settings = require('ep_etherpad-lite/node/utils/Settings');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
const fs = require('fs');
exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/admin/settings', function(req, res) {
args.app.get('/admin/settings', (req, res) => {
res.send(eejs.require('ep_etherpad-lite/templates/admin/settings.html', {
req,
settings: "",
settings: '',
search_results: {},
errors: []
errors: [],
}));
});
return cb();
}
};
exports.socketio = function (hook_name, args, cb) {
var io = args.io.of("/settings");
io.on('connection', function (socket) {
const io = args.io.of('/settings');
io.on('connection', (socket) => {
if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return;
socket.on("load", function (query) {
fs.readFile('settings.json', 'utf8', function (err,data) {
socket.on('load', (query) => {
fs.readFile('settings.json', 'utf8', (err, data) => {
if (err) {
return console.log(err);
}
// if showSettingsInAdminPage is set to false, then return NOT_ALLOWED in the result
if(settings.showSettingsInAdminPage === false) {
socket.emit("settings", {results: 'NOT_ALLOWED'});
}
else {
socket.emit("settings", {results: data});
if (settings.showSettingsInAdminPage === false) {
socket.emit('settings', {results: 'NOT_ALLOWED'});
} else {
socket.emit('settings', {results: data});
}
});
});
socket.on("saveSettings", function (settings) {
fs.writeFile('settings.json', settings, function (err) {
socket.on('saveSettings', (settings) => {
fs.writeFile('settings.json', settings, (err) => {
if (err) throw err;
socket.emit("saveprogress", "saved");
socket.emit('saveprogress', 'saved');
});
});
socket.on('restartServer', async () => {
console.log("Admin request to restart server through a socket on /admin/settings");
console.log('Admin request to restart server through a socket on /admin/settings');
settings.reloadSettings();
await hooks.aCallAll('restartServer');
});
});
return cb();
}
};

View file

@ -1,34 +1,34 @@
var log4js = require('log4js');
var clientLogger = log4js.getLogger("client");
var formidable = require('formidable');
var apiHandler = require('../../handler/APIHandler');
const log4js = require('log4js');
const clientLogger = log4js.getLogger('client');
const formidable = require('formidable');
const apiHandler = require('../../handler/APIHandler');
exports.expressCreateServer = function (hook_name, args, cb) {
//The Etherpad client side sends information about how a disconnect happened
args.app.post('/ep/pad/connection-diagnostic-info', function(req, res) {
new formidable.IncomingForm().parse(req, function(err, fields, files) {
clientLogger.info("DIAGNOSTIC-INFO: " + fields.diagnosticInfo);
res.end("OK");
// The Etherpad client side sends information about how a disconnect happened
args.app.post('/ep/pad/connection-diagnostic-info', (req, res) => {
new formidable.IncomingForm().parse(req, (err, fields, files) => {
clientLogger.info(`DIAGNOSTIC-INFO: ${fields.diagnosticInfo}`);
res.end('OK');
});
});
//The Etherpad client side sends information about client side javscript errors
args.app.post('/jserror', function(req, res) {
new formidable.IncomingForm().parse(req, function(err, fields, files) {
// The Etherpad client side sends information about client side javscript errors
args.app.post('/jserror', (req, res) => {
new formidable.IncomingForm().parse(req, (err, fields, files) => {
try {
var data = JSON.parse(fields.errorInfo)
}catch(e){
return res.end()
var data = JSON.parse(fields.errorInfo);
} catch (e) {
return res.end();
}
clientLogger.warn(data.msg+' --', data);
res.end("OK");
clientLogger.warn(`${data.msg} --`, data);
res.end('OK');
});
});
//Provide a possibility to query the latest available API version
args.app.get('/api', function (req, res) {
res.json({"currentVersion" : apiHandler.latestApiVersion});
// Provide a possibility to query the latest available API version
args.app.get('/api', (req, res) => {
res.json({currentVersion: apiHandler.latestApiVersion});
});
return cb();
}
};

View file

@ -1,17 +1,17 @@
var stats = require('ep_etherpad-lite/node/stats')
const stats = require('ep_etherpad-lite/node/stats');
exports.expressCreateServer = function (hook_name, args, cb) {
exports.app = args.app;
// Handle errors
args.app.use(function(err, req, res, next) {
args.app.use((err, req, res, next) => {
// if an error occurs Connect will pass it down
// through these "error-handling" middleware
// allowing you to respond however you like
res.status(500).send({ error: 'Sorry, something bad happened!' });
console.error(err.stack? err.stack : err.toString());
stats.meter('http500').mark()
res.status(500).send({error: 'Sorry, something bad happened!'});
console.error(err.stack ? err.stack : err.toString());
stats.meter('http500').mark();
});
return cb();
}
};

View file

@ -1,44 +1,43 @@
const assert = require('assert').strict;
var hasPadAccess = require("../../padaccess");
var settings = require('../../utils/Settings');
var exportHandler = require('../../handler/ExportHandler');
var importHandler = require('../../handler/ImportHandler');
var padManager = require("../../db/PadManager");
var readOnlyManager = require("../../db/ReadOnlyManager");
var authorManager = require("../../db/AuthorManager");
const rateLimit = require("express-rate-limit");
const securityManager = require("../../db/SecurityManager");
const webaccess = require("./webaccess");
const hasPadAccess = require('../../padaccess');
const settings = require('../../utils/Settings');
const exportHandler = require('../../handler/ExportHandler');
const importHandler = require('../../handler/ImportHandler');
const padManager = require('../../db/PadManager');
const readOnlyManager = require('../../db/ReadOnlyManager');
const authorManager = require('../../db/AuthorManager');
const rateLimit = require('express-rate-limit');
const securityManager = require('../../db/SecurityManager');
const webaccess = require('./webaccess');
settings.importExportRateLimiting.onLimitReached = function(req, res, options) {
settings.importExportRateLimiting.onLimitReached = function (req, res, options) {
// when the rate limiter triggers, write a warning in the logs
console.warn(`Import/Export rate limiter triggered on "${req.originalUrl}" for IP address ${req.ip}`);
}
};
var limiter = rateLimit(settings.importExportRateLimiting);
const limiter = rateLimit(settings.importExportRateLimiting);
exports.expressCreateServer = function (hook_name, args, cb) {
// handle export requests
args.app.use('/p/:pad/:rev?/export/:type', limiter);
args.app.get('/p/:pad/:rev?/export/:type', async function(req, res, next) {
var types = ["pdf", "doc", "txt", "html", "odt", "etherpad"];
//send a 404 if we don't support this filetype
args.app.get('/p/:pad/:rev?/export/:type', async (req, res, next) => {
const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad'];
// send a 404 if we don't support this filetype
if (types.indexOf(req.params.type) == -1) {
return next();
}
// if abiword is disabled, and this is a format we only support with abiword, output a message
if (settings.exportAvailable() == "no" &&
["odt", "pdf", "doc"].indexOf(req.params.type) !== -1) {
if (settings.exportAvailable() == 'no' &&
['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) {
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format. There is no converter configured`);
// ACHTUNG: do not include req.params.type in res.send() because there is no HTML escaping and it would lead to an XSS
res.send("This export is not enabled at this Etherpad instance. Set the path to Abiword or soffice (LibreOffice) in settings.json to enable this feature");
res.send('This export is not enabled at this Etherpad instance. Set the path to Abiword or soffice (LibreOffice) in settings.json to enable this feature');
return;
}
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Origin', '*');
if (await hasPadAccess(req, res)) {
let padId = req.params.pad;
@ -49,7 +48,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
padId = await readOnlyManager.getPadId(readOnlyId);
}
let exists = await padManager.doesPadExists(padId);
const exists = await padManager.doesPadExists(padId);
if (!exists) {
console.warn(`Someone tried to export a pad that doesn't exist (${padId})`);
return next();
@ -62,7 +61,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// handle import requests
args.app.use('/p/:pad/import', limiter);
args.app.post('/p/:pad/import', async function(req, res, next) {
args.app.post('/p/:pad/import', async (req, res, next) => {
const {session: {user} = {}} = req;
const {accessStatus} = await securityManager.checkAccess(
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
@ -73,4 +72,4 @@ exports.expressCreateServer = function (hook_name, args, cb) {
});
return cb();
}
};

View file

@ -62,14 +62,14 @@ const RESERVED_WORDS = [
'volatile',
'while',
'with',
'yield'
'yield',
];
const regex = /^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:".+"|\'.+\'|\d+)\])*?$/;
module.exports.check = function(inputStr) {
var isValid = true;
inputStr.split(".").forEach(function(part) {
module.exports.check = function (inputStr) {
let isValid = true;
inputStr.split('.').forEach((part) => {
if (!regex.test(part)) {
isValid = false;
}
@ -80,4 +80,4 @@ module.exports.check = function(inputStr) {
});
return isValid;
}
};

View file

@ -14,7 +14,7 @@
const OpenAPIBackend = require('openapi-backend').default;
const formidable = require('formidable');
const { promisify } = require('util');
const {promisify} = require('util');
const cloneDeep = require('lodash.clonedeep');
const createHTTPError = require('http-errors');
@ -57,12 +57,12 @@ const resources = {
create: {
operationId: 'createGroup',
summary: 'creates a new group',
responseSchema: { groupID: { type: 'string' } },
responseSchema: {groupID: {type: 'string'}},
},
createIfNotExistsFor: {
operationId: 'createGroupIfNotExistsFor',
summary: 'this functions helps you to map your application group ids to Etherpad group ids',
responseSchema: { groupID: { type: 'string' } },
responseSchema: {groupID: {type: 'string'}},
},
delete: {
operationId: 'deleteGroup',
@ -71,7 +71,7 @@ const resources = {
listPads: {
operationId: 'listPads',
summary: 'returns all pads of this group',
responseSchema: { padIDs: { type: 'array', items: { type: 'string' } } },
responseSchema: {padIDs: {type: 'array', items: {type: 'string'}}},
},
createPad: {
operationId: 'createGroupPad',
@ -80,12 +80,12 @@ const resources = {
listSessions: {
operationId: 'listSessionsOfGroup',
summary: '',
responseSchema: { sessions: { type: 'array', items: { $ref: '#/components/schemas/SessionInfo' } } },
responseSchema: {sessions: {type: 'array', items: {$ref: '#/components/schemas/SessionInfo'}}},
},
list: {
operationId: 'listAllGroups',
summary: '',
responseSchema: { groupIDs: { type: 'array', items: { type: 'string' } } },
responseSchema: {groupIDs: {type: 'array', items: {type: 'string'}}},
},
},
@ -94,28 +94,28 @@ const resources = {
create: {
operationId: 'createAuthor',
summary: 'creates a new author',
responseSchema: { authorID: { type: 'string' } },
responseSchema: {authorID: {type: 'string'}},
},
createIfNotExistsFor: {
operationId: 'createAuthorIfNotExistsFor',
summary: 'this functions helps you to map your application author ids to Etherpad author ids',
responseSchema: { authorID: { type: 'string' } },
responseSchema: {authorID: {type: 'string'}},
},
listPads: {
operationId: 'listPadsOfAuthor',
summary: 'returns an array of all pads this author contributed to',
responseSchema: { padIDs: { type: 'array', items: { type: 'string' } } },
responseSchema: {padIDs: {type: 'array', items: {type: 'string'}}},
},
listSessions: {
operationId: 'listSessionsOfAuthor',
summary: 'returns all sessions of an author',
responseSchema: { sessions: { type: 'array', items: { $ref: '#/components/schemas/SessionInfo' } } },
responseSchema: {sessions: {type: 'array', items: {$ref: '#/components/schemas/SessionInfo'}}},
},
// We need an operation that return a UserInfo so it can be picked up by the codegen :(
getName: {
operationId: 'getAuthorName',
summary: 'Returns the Author Name of the author',
responseSchema: { info: { $ref: '#/components/schemas/UserInfo' } },
responseSchema: {info: {$ref: '#/components/schemas/UserInfo'}},
},
},
@ -124,7 +124,7 @@ const resources = {
create: {
operationId: 'createSession',
summary: 'creates a new session. validUntil is an unix timestamp in seconds',
responseSchema: { sessionID: { type: 'string' } },
responseSchema: {sessionID: {type: 'string'}},
},
delete: {
operationId: 'deleteSession',
@ -134,7 +134,7 @@ const resources = {
info: {
operationId: 'getSessionInfo',
summary: 'returns informations about a session',
responseSchema: { info: { $ref: '#/components/schemas/SessionInfo' } },
responseSchema: {info: {$ref: '#/components/schemas/SessionInfo'}},
},
},
@ -143,7 +143,7 @@ const resources = {
listAll: {
operationId: 'listAllPads',
summary: 'list all the pads',
responseSchema: { padIDs: { type: 'array', items: { type: 'string' } } },
responseSchema: {padIDs: {type: 'array', items: {type: 'string'}}},
},
createDiffHTML: {
operationId: 'createDiffHTML',
@ -158,7 +158,7 @@ const resources = {
getText: {
operationId: 'getText',
summary: 'returns the text of a pad',
responseSchema: { text: { type: 'string' } },
responseSchema: {text: {type: 'string'}},
},
setText: {
operationId: 'setText',
@ -167,7 +167,7 @@ const resources = {
getHTML: {
operationId: 'getHTML',
summary: 'returns the text of a pad formatted as HTML',
responseSchema: { html: { type: 'string' } },
responseSchema: {html: {type: 'string'}},
},
setHTML: {
operationId: 'setHTML',
@ -176,12 +176,12 @@ const resources = {
getRevisionsCount: {
operationId: 'getRevisionsCount',
summary: 'returns the number of revisions of this pad',
responseSchema: { revisions: { type: 'integer' } },
responseSchema: {revisions: {type: 'integer'}},
},
getLastEdited: {
operationId: 'getLastEdited',
summary: 'returns the timestamp of the last revision of the pad',
responseSchema: { lastEdited: { type: 'integer' } },
responseSchema: {lastEdited: {type: 'integer'}},
},
delete: {
operationId: 'deletePad',
@ -190,7 +190,7 @@ const resources = {
getReadOnlyID: {
operationId: 'getReadOnlyID',
summary: 'returns the read only link of a pad',
responseSchema: { readOnlyID: { type: 'string' } },
responseSchema: {readOnlyID: {type: 'string'}},
},
setPublicStatus: {
operationId: 'setPublicStatus',
@ -199,22 +199,22 @@ const resources = {
getPublicStatus: {
operationId: 'getPublicStatus',
summary: 'return true of false',
responseSchema: { publicStatus: { type: 'boolean' } },
responseSchema: {publicStatus: {type: 'boolean'}},
},
authors: {
operationId: 'listAuthorsOfPad',
summary: 'returns an array of authors who contributed to this pad',
responseSchema: { authorIDs: { type: 'array', items: { type: 'string' } } },
responseSchema: {authorIDs: {type: 'array', items: {type: 'string'}}},
},
usersCount: {
operationId: 'padUsersCount',
summary: 'returns the number of user that are currently editing this pad',
responseSchema: { padUsersCount: { type: 'integer' } },
responseSchema: {padUsersCount: {type: 'integer'}},
},
users: {
operationId: 'padUsers',
summary: 'returns the list of users that are currently editing this pad',
responseSchema: { padUsers: { type: 'array', items: { $ref: '#/components/schemas/UserInfo' } } },
responseSchema: {padUsers: {type: 'array', items: {$ref: '#/components/schemas/UserInfo'}}},
},
sendClientsMessage: {
operationId: 'sendClientsMessage',
@ -227,13 +227,13 @@ const resources = {
getChatHistory: {
operationId: 'getChatHistory',
summary: 'returns the chat history',
responseSchema: { messages: { type: 'array', items: { $ref: '#/components/schemas/Message' } } },
responseSchema: {messages: {type: 'array', items: {$ref: '#/components/schemas/Message'}}},
},
// We need an operation that returns a Message so it can be picked up by the codegen :(
getChatHead: {
operationId: 'getChatHead',
summary: 'returns the chatHead (chat-message) of the pad',
responseSchema: { chatHead: { $ref: '#/components/schemas/Message' } },
responseSchema: {chatHead: {$ref: '#/components/schemas/Message'}},
},
appendChatMessage: {
operationId: 'appendChatMessage',
@ -384,10 +384,10 @@ const defaultResponseRefs = {
const operations = {};
for (const resource in resources) {
for (const action in resources[resource]) {
const { operationId, responseSchema, ...operation } = resources[resource][action];
const {operationId, responseSchema, ...operation} = resources[resource][action];
// add response objects
const responses = { ...defaultResponseRefs };
const responses = {...defaultResponseRefs};
if (responseSchema) {
responses[200] = cloneDeep(defaultResponses.Success);
responses[200].content['application/json'].schema.properties.data = {
@ -478,14 +478,14 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
},
},
},
security: [{ ApiKey: [] }],
security: [{ApiKey: []}],
};
// build operations
for (const funcName in apiHandler.version[version]) {
let operation = {};
if (operations[funcName]) {
operation = { ...operations[funcName] };
operation = {...operations[funcName]};
} else {
// console.warn(`No operation found for function: ${funcName}`);
operation = {
@ -497,7 +497,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
// set parameters
operation.parameters = operation.parameters || [];
for (const paramName of apiHandler.version[version][funcName]) {
operation.parameters.push({ $ref: `#/components/parameters/${paramName}` });
operation.parameters.push({$ref: `#/components/parameters/${paramName}`});
if (!definition.components.parameters[paramName]) {
definition.components.parameters[paramName] = {
name: paramName,
@ -533,7 +533,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
};
exports.expressCreateServer = (hookName, args, cb) => {
const { app } = args;
const {app} = args;
// create openapi-backend handlers for each api version under /api/{version}/*
for (const version in apiHandler.version) {
@ -550,7 +550,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
app.get(`${apiRoot}/openapi.json`, (req, res) => {
// For openapi definitions, wide CORS is probably fine
res.header('Access-Control-Allow-Origin', '*');
res.json({ ...definition, servers: [generateServerForApiVersion(apiRoot, req)] });
res.json({...definition, servers: [generateServerForApiVersion(apiRoot, req)]});
});
// serve latest openapi definition file under /api/openapi.json
@ -558,7 +558,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
if (isLatestAPIVersion) {
app.get(`/${style}/openapi.json`, (req, res) => {
res.header('Access-Control-Allow-Origin', '*');
res.json({ ...definition, servers: [generateServerForApiVersion(apiRoot, req)] });
res.json({...definition, servers: [generateServerForApiVersion(apiRoot, req)]});
});
}
@ -586,7 +586,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
for (const funcName in apiHandler.version[version]) {
const handler = async (c, req, res) => {
// parse fields from request
const { header, params, query } = c.request;
const {header, params, query} = c.request;
// read form data if method was POST
let formData = {};
@ -602,7 +602,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
apiLogger.info(`REQUEST, v${version}:${funcName}, ${JSON.stringify(fields)}`);
// pass to api handler
let data = await apiHandler.handle(version, funcName, fields, req, res).catch((err) => {
const data = await apiHandler.handle(version, funcName, fields, req, res).catch((err) => {
// convert all errors to http errors
if (createHTTPError.isHttpError(err)) {
// pass http errors thrown by handler forward
@ -620,7 +620,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
});
// return in common format
let response = { code: 0, message: 'ok', data: data || null };
const response = {code: 0, message: 'ok', data: data || null};
// log response
apiLogger.info(`RESPONSE, ${funcName}, ${JSON.stringify(response)}`);
@ -654,24 +654,24 @@ exports.expressCreateServer = (hookName, args, cb) => {
// https://github.com/ether/etherpad-lite/tree/master/doc/api/http_api.md#response-format
switch (res.statusCode) {
case 403: // forbidden
response = { code: 4, message: err.message, data: null };
response = {code: 4, message: err.message, data: null};
break;
case 401: // unauthorized (no or wrong api key)
response = { code: 4, message: err.message, data: null };
response = {code: 4, message: err.message, data: null};
break;
case 404: // not found (no such function)
response = { code: 3, message: err.message, data: null };
response = {code: 3, message: err.message, data: null};
break;
case 500: // server error (internal error)
response = { code: 2, message: err.message, data: null };
response = {code: 2, message: err.message, data: null};
break;
case 400: // bad request (wrong parameters)
// respond with 200 OK to keep old behavior and pass tests
res.statusCode = 200; // @TODO: this is bad api design
response = { code: 1, message: err.message, data: null };
response = {code: 1, message: err.message, data: null};
break;
default:
response = { code: 1, message: err.message, data: null };
response = {code: 1, message: err.message, data: null};
break;
}
}

View file

@ -1,13 +1,12 @@
var readOnlyManager = require("../../db/ReadOnlyManager");
var hasPadAccess = require("../../padaccess");
var exporthtml = require("../../utils/ExportHtml");
const readOnlyManager = require('../../db/ReadOnlyManager');
const hasPadAccess = require('../../padaccess');
const exporthtml = require('../../utils/ExportHtml');
exports.expressCreateServer = function (hook_name, args, cb) {
// serve read only pad
args.app.get('/ro/:id', async function(req, res) {
args.app.get('/ro/:id', async (req, res) => {
// translate the read only pad to a padId
let padId = await readOnlyManager.getPadId(req.params.id);
const padId = await readOnlyManager.getPadId(req.params.id);
if (padId == null) {
res.status(404).send('404 - Not Found');
return;
@ -18,9 +17,9 @@ exports.expressCreateServer = function (hook_name, args, cb) {
if (await hasPadAccess(req, res)) {
// render the html document
let html = await exporthtml.getPadHTMLDocument(padId, null);
const html = await exporthtml.getPadHTMLDocument(padId, null);
res.send(html);
}
});
return cb();
}
};

View file

@ -1,30 +1,29 @@
var padManager = require('../../db/PadManager');
var url = require('url');
const padManager = require('../../db/PadManager');
const url = require('url');
exports.expressCreateServer = function (hook_name, args, cb) {
// redirects browser to the pad's sanitized url if needed. otherwise, renders the html
args.app.param('pad', async function (req, res, next, padId) {
args.app.param('pad', async (req, res, next, padId) => {
// ensure the padname is valid and the url doesn't end with a /
if (!padManager.isValidPadId(padId) || /\/$/.test(req.url)) {
res.status(404).send('Such a padname is forbidden');
return;
}
let sanitizedPadId = await padManager.sanitizePadId(padId);
const sanitizedPadId = await padManager.sanitizePadId(padId);
if (sanitizedPadId === padId) {
// the pad id was fine, so just render it
next();
} else {
// the pad id was sanitized, so we redirect to the sanitized version
var real_url = sanitizedPadId;
let real_url = sanitizedPadId;
real_url = encodeURIComponent(real_url);
var query = url.parse(req.url).query;
if ( query ) real_url += '?' + query;
const query = url.parse(req.url).query;
if (query) real_url += `?${query}`;
res.header('Location', real_url);
res.status(302).send('You should be redirected to <a href="' + real_url + '">' + real_url + '</a>');
res.status(302).send(`You should be redirected to <a href="${real_url}">${real_url}</a>`);
}
});
return cb();
}
};

View file

@ -1,19 +1,19 @@
const express = require("../express");
const express = require('../express');
const proxyaddr = require('proxy-addr');
var settings = require('../../utils/Settings');
var socketio = require('socket.io');
var socketIORouter = require("../../handler/SocketIORouter");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
const settings = require('../../utils/Settings');
const socketio = require('socket.io');
const socketIORouter = require('../../handler/SocketIORouter');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var padMessageHandler = require("../../handler/PadMessageHandler");
const padMessageHandler = require('../../handler/PadMessageHandler');
exports.expressCreateServer = function (hook_name, args, cb) {
//init socket.io and redirect all requests to the MessageHandler
// init socket.io and redirect all requests to the MessageHandler
// there shouldn't be a browser that isn't compatible to all
// transports in this list at once
// e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling
var io = socketio({
transports: settings.socketTransportProtocols
const io = socketio({
transports: settings.socketTransportProtocols,
}).listen(args.server, {
/*
* Do not set the "io" cookie.
@ -61,17 +61,17 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// https://github.com/Automattic/socket.io/wiki/Migrating-to-1.0
// This debug logging environment is set in Settings.js
//minify socket.io javascript
// minify socket.io javascript
// Due to a shitty decision by the SocketIO team minification is
// no longer available, details available at:
// http://stackoverflow.com/questions/23981741/minify-socket-io-socket-io-js-with-1-0
// if(settings.minify) io.enable('browser client minification');
//Initalize the Socket.IO Router
// Initalize the Socket.IO Router
socketIORouter.setSocketIO(io);
socketIORouter.addComponent("pad", padMessageHandler);
socketIORouter.addComponent('pad', padMessageHandler);
hooks.callAll("socketio", {"app": args.app, "io": io, "server": args.server});
hooks.callAll('socketio', {app: args.app, io, server: args.server});
return cb();
}
};

View file

@ -1,83 +1,81 @@
var path = require('path');
var eejs = require('ep_etherpad-lite/node/eejs');
var toolbar = require("ep_etherpad-lite/node/utils/toolbar");
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var settings = require('../../utils/Settings');
const path = require('path');
const eejs = require('ep_etherpad-lite/node/eejs');
const toolbar = require('ep_etherpad-lite/node/utils/toolbar');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
const settings = require('../../utils/Settings');
const webaccess = require('./webaccess');
exports.expressCreateServer = function (hook_name, args, cb) {
// expose current stats
args.app.get('/stats', function(req, res) {
res.json(require('ep_etherpad-lite/node/stats').toJSON())
})
args.app.get('/stats', (req, res) => {
res.json(require('ep_etherpad-lite/node/stats').toJSON());
});
//serve index.html under /
args.app.get('/', function(req, res) {
// serve index.html under /
args.app.get('/', (req, res) => {
res.send(eejs.require('ep_etherpad-lite/templates/index.html', {req}));
});
//serve javascript.html
args.app.get('/javascript', function(req, res) {
// serve javascript.html
args.app.get('/javascript', (req, res) => {
res.send(eejs.require('ep_etherpad-lite/templates/javascript.html', {req}));
});
//serve robots.txt
args.app.get('/robots.txt', function(req, res) {
var filePath = path.join(settings.root, "src", "static", "skins", settings.skinName, "robots.txt");
res.sendFile(filePath, function(err) {
//there is no custom robots.txt, send the default robots.txt which dissallows all
if(err)
{
filePath = path.join(settings.root, "src", "static", "robots.txt");
// serve robots.txt
args.app.get('/robots.txt', (req, res) => {
let filePath = path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'robots.txt');
res.sendFile(filePath, (err) => {
// there is no custom robots.txt, send the default robots.txt which dissallows all
if (err) {
filePath = path.join(settings.root, 'src', 'static', 'robots.txt');
res.sendFile(filePath);
}
});
});
//serve pad.html under /p
args.app.get('/p/:pad', function(req, res, next) {
// serve pad.html under /p
args.app.get('/p/:pad', (req, res, next) => {
// The below might break for pads being rewritten
const isReadOnly =
req.url.indexOf("/p/r.") === 0 || !webaccess.userCanModify(req.params.pad, req);
req.url.indexOf('/p/r.') === 0 || !webaccess.userCanModify(req.params.pad, req);
hooks.callAll("padInitToolbar", {
toolbar: toolbar,
isReadOnly: isReadOnly
hooks.callAll('padInitToolbar', {
toolbar,
isReadOnly,
});
res.send(eejs.require("ep_etherpad-lite/templates/pad.html", {
req: req,
toolbar: toolbar,
isReadOnly: isReadOnly
res.send(eejs.require('ep_etherpad-lite/templates/pad.html', {
req,
toolbar,
isReadOnly,
}));
});
//serve timeslider.html under /p/$padname/timeslider
args.app.get('/p/:pad/timeslider', function(req, res, next) {
hooks.callAll("padInitToolbar", {
toolbar: toolbar
// serve timeslider.html under /p/$padname/timeslider
args.app.get('/p/:pad/timeslider', (req, res, next) => {
hooks.callAll('padInitToolbar', {
toolbar,
});
res.send(eejs.require("ep_etherpad-lite/templates/timeslider.html", {
req: req,
toolbar: toolbar
res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', {
req,
toolbar,
}));
});
//serve favicon.ico from all path levels except as a pad name
args.app.get( /\/favicon.ico$/, function(req, res) {
var filePath = path.join(settings.root, "src", "static", "skins", settings.skinName, "favicon.ico");
// serve favicon.ico from all path levels except as a pad name
args.app.get(/\/favicon.ico$/, (req, res) => {
let filePath = path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico');
res.sendFile(filePath, function(err) {
//there is no custom favicon, send the default favicon
if(err)
{
filePath = path.join(settings.root, "src", "static", "favicon.ico");
res.sendFile(filePath, (err) => {
// there is no custom favicon, send the default favicon
if (err) {
filePath = path.join(settings.root, 'src', 'static', 'favicon.ico');
res.sendFile(filePath);
}
});
});
return cb();
}
};

View file

@ -1,14 +1,13 @@
var minify = require('../../utils/Minify');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugin_defs");
var CachingMiddleware = require('../../utils/caching_middleware');
var settings = require("../../utils/Settings");
var Yajsml = require('etherpad-yajsml');
var _ = require("underscore");
const minify = require('../../utils/Minify');
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
const CachingMiddleware = require('../../utils/caching_middleware');
const settings = require('../../utils/Settings');
const Yajsml = require('etherpad-yajsml');
const _ = require('underscore');
exports.expressCreateServer = function (hook_name, args, cb) {
// Cache both minified and static.
var assetCache = new CachingMiddleware;
const assetCache = new CachingMiddleware();
args.app.all(/\/javascripts\/(.*)/, assetCache.handle);
// Minify will serve static files compressed (minify enabled). It also has
@ -18,43 +17,42 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// Setup middleware that will package JavaScript files served by minify for
// CommonJS loader on the client-side.
// Hostname "invalid.invalid" is a dummy value to allow parsing as a URI.
var jsServer = new (Yajsml.Server)({
rootPath: 'javascripts/src/'
, rootURI: 'http://invalid.invalid/static/js/'
, libraryPath: 'javascripts/lib/'
, libraryURI: 'http://invalid.invalid/static/plugins/'
, requestURIs: minify.requestURIs // Loop-back is causing problems, this is a workaround.
const jsServer = new (Yajsml.Server)({
rootPath: 'javascripts/src/',
rootURI: 'http://invalid.invalid/static/js/',
libraryPath: 'javascripts/lib/',
libraryURI: 'http://invalid.invalid/static/plugins/',
requestURIs: minify.requestURIs, // Loop-back is causing problems, this is a workaround.
});
var StaticAssociator = Yajsml.associators.StaticAssociator;
var associations =
const StaticAssociator = Yajsml.associators.StaticAssociator;
const associations =
Yajsml.associators.associationsForSimpleMapping(minify.tar);
var associator = new StaticAssociator(associations);
const associator = new StaticAssociator(associations);
jsServer.setAssociator(associator);
args.app.use(jsServer.handle.bind(jsServer));
// serve plugin definitions
// not very static, but served here so that client can do require("pluginfw/static/js/plugin-definitions.js");
args.app.get('/pluginfw/plugin-definitions.json', function (req, res, next) {
args.app.get('/pluginfw/plugin-definitions.json', (req, res, next) => {
const clientParts = _(plugins.parts)
.filter((part) => _(part).has('client_hooks'));
var clientParts = _(plugins.parts)
.filter(function(part){ return _(part).has('client_hooks') });
var clientPlugins = {};
const clientPlugins = {};
_(clientParts).chain()
.map(function(part){ return part.plugin })
.uniq()
.each(function(name){
clientPlugins[name] = _(plugins.plugins[name]).clone();
delete clientPlugins[name]['package'];
});
.map((part) => part.plugin)
.uniq()
.each((name) => {
clientPlugins[name] = _(plugins.plugins[name]).clone();
delete clientPlugins[name].package;
});
res.header("Content-Type","application/json; charset=utf-8");
res.write(JSON.stringify({"plugins": clientPlugins, "parts": clientParts}));
res.header('Content-Type', 'application/json; charset=utf-8');
res.write(JSON.stringify({plugins: clientPlugins, parts: clientParts}));
res.end();
});
return cb();
}
};

View file

@ -1,13 +1,13 @@
var path = require("path")
, npm = require("npm")
, fs = require("fs")
, util = require("util");
const path = require('path');
const npm = require('npm');
const fs = require('fs');
const util = require('util');
exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/tests/frontend/specs_list.js', async function(req, res) {
let [coreTests, pluginTests] = await Promise.all([
args.app.get('/tests/frontend/specs_list.js', async (req, res) => {
const [coreTests, pluginTests] = await Promise.all([
exports.getCoreTests(),
exports.getPluginTests()
exports.getPluginTests(),
]);
// merge the two sets of results
@ -16,79 +16,77 @@ exports.expressCreateServer = function (hook_name, args, cb) {
// Keep only *.js files
files = files.filter((f) => f.endsWith('.js'));
console.debug("Sent browser the following test specs:", files);
console.debug('Sent browser the following test specs:', files);
res.setHeader('content-type', 'text/javascript');
res.end("var specs_list = " + JSON.stringify(files) + ";\n");
res.end(`var specs_list = ${JSON.stringify(files)};\n`);
});
// path.join seems to normalize by default, but we'll just be explicit
var rootTestFolder = path.normalize(path.join(npm.root, "../tests/frontend/"));
const rootTestFolder = path.normalize(path.join(npm.root, '../tests/frontend/'));
var url2FilePath = function(url) {
var subPath = url.substr("/tests/frontend".length);
if (subPath == "") {
subPath = "index.html"
const url2FilePath = function (url) {
let subPath = url.substr('/tests/frontend'.length);
if (subPath == '') {
subPath = 'index.html';
}
subPath = subPath.split("?")[0];
subPath = subPath.split('?')[0];
var filePath = path.normalize(path.join(rootTestFolder, subPath));
let filePath = path.normalize(path.join(rootTestFolder, subPath));
// make sure we jail the paths to the test folder, otherwise serve index
if (filePath.indexOf(rootTestFolder) !== 0) {
filePath = path.join(rootTestFolder, "index.html");
filePath = path.join(rootTestFolder, 'index.html');
}
return filePath;
}
};
args.app.get('/tests/frontend/specs/*', function (req, res) {
var specFilePath = url2FilePath(req.url);
var specFileName = path.basename(specFilePath);
args.app.get('/tests/frontend/specs/*', (req, res) => {
const specFilePath = url2FilePath(req.url);
const specFileName = path.basename(specFilePath);
fs.readFile(specFilePath, function(err, content) {
fs.readFile(specFilePath, (err, content) => {
if (err) { return res.send(500); }
content = "describe(" + JSON.stringify(specFileName) + ", function(){ " + content + " });";
content = `describe(${JSON.stringify(specFileName)}, function(){ ${content} });`;
res.send(content);
});
});
args.app.get('/tests/frontend/*', function (req, res) {
var filePath = url2FilePath(req.url);
args.app.get('/tests/frontend/*', (req, res) => {
const filePath = url2FilePath(req.url);
res.sendFile(filePath);
});
args.app.get('/tests/frontend', function (req, res) {
args.app.get('/tests/frontend', (req, res) => {
res.redirect('/tests/frontend/index.html');
});
return cb();
}
};
const readdir = util.promisify(fs.readdir);
exports.getPluginTests = async function(callback) {
const moduleDir = "node_modules/";
const specPath = "/static/tests/frontend/specs/";
const staticDir = "/static/plugins/";
exports.getPluginTests = async function (callback) {
const moduleDir = 'node_modules/';
const specPath = '/static/tests/frontend/specs/';
const staticDir = '/static/plugins/';
let pluginSpecs = [];
const pluginSpecs = [];
let plugins = await readdir(moduleDir);
let promises = plugins
.map(plugin => [ plugin, moduleDir + plugin + specPath] )
.filter(([plugin, specDir]) => fs.existsSync(specDir)) // check plugin exists
.map(([plugin, specDir]) => {
return readdir(specDir)
.then(specFiles => specFiles.map(spec => {
pluginSpecs.push(staticDir + plugin + specPath + spec);
}));
});
const plugins = await readdir(moduleDir);
const promises = plugins
.map((plugin) => [plugin, moduleDir + plugin + specPath])
.filter(([plugin, specDir]) => fs.existsSync(specDir)) // check plugin exists
.map(([plugin, specDir]) => readdir(specDir)
.then((specFiles) => specFiles.map((spec) => {
pluginSpecs.push(staticDir + plugin + specPath + spec);
})));
return Promise.all(promises).then(() => pluginSpecs);
}
};
exports.getCoreTests = function() {
exports.getCoreTests = function () {
// get the core test specs
return readdir('tests/frontend/specs');
}
};

View file

@ -1,58 +1,58 @@
var languages = require('languages4translatewiki')
, fs = require('fs')
, path = require('path')
, _ = require('underscore')
, npm = require('npm')
, plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs.js').plugins
, semver = require('semver')
, existsSync = require('../utils/path_exists')
, settings = require('../utils/Settings')
const languages = require('languages4translatewiki');
const fs = require('fs');
const path = require('path');
const _ = require('underscore');
const npm = require('npm');
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs.js').plugins;
const semver = require('semver');
const existsSync = require('../utils/path_exists');
const settings = require('../utils/Settings')
;
// returns all existing messages merged together and grouped by langcode
// {es: {"foo": "string"}, en:...}
function getAllLocales() {
var locales2paths = {};
const locales2paths = {};
// Puts the paths of all locale files contained in a given directory
// into `locales2paths` (files from various dirs are grouped by lang code)
// (only json files with valid language code as name)
function extractLangs(dir) {
if(!existsSync(dir)) return;
var stat = fs.lstatSync(dir);
if (!existsSync(dir)) return;
let stat = fs.lstatSync(dir);
if (!stat.isDirectory() || stat.isSymbolicLink()) return;
fs.readdirSync(dir).forEach(function(file) {
fs.readdirSync(dir).forEach((file) => {
file = path.resolve(dir, file);
stat = fs.lstatSync(file);
if (stat.isDirectory() || stat.isSymbolicLink()) return;
var ext = path.extname(file)
, locale = path.basename(file, ext).toLowerCase();
const ext = path.extname(file);
const locale = path.basename(file, ext).toLowerCase();
if ((ext == '.json') && languages.isValid(locale)) {
if(!locales2paths[locale]) locales2paths[locale] = [];
if (!locales2paths[locale]) locales2paths[locale] = [];
locales2paths[locale].push(file);
}
});
}
//add core supported languages first
extractLangs(npm.root+"/ep_etherpad-lite/locales");
// add core supported languages first
extractLangs(`${npm.root}/ep_etherpad-lite/locales`);
//add plugins languages (if any)
for(var pluginName in plugins) extractLangs(path.join(npm.root, pluginName, 'locales'));
// add plugins languages (if any)
for (const pluginName in plugins) extractLangs(path.join(npm.root, pluginName, 'locales'));
// Build a locale index (merge all locale data other than user-supplied overrides)
var locales = {}
_.each (locales2paths, function(files, langcode) {
locales[langcode]={};
const locales = {};
_.each(locales2paths, (files, langcode) => {
locales[langcode] = {};
files.forEach(function(file) {
files.forEach((file) => {
let fileContents;
try {
fileContents = JSON.parse(fs.readFileSync(file,'utf8'));
fileContents = JSON.parse(fs.readFileSync(file, 'utf8'));
} catch (err) {
console.error(`failed to read JSON file ${file}: ${err}`);
throw err;
@ -64,17 +64,17 @@ function getAllLocales() {
// Add custom strings from settings.json
// Since this is user-supplied, we'll do some extra sanity checks
const wrongFormatErr = Error(
"customLocaleStrings in wrong format. See documentation " +
"for Customization for Administrators, under Localization.")
'customLocaleStrings in wrong format. See documentation ' +
'for Customization for Administrators, under Localization.');
if (settings.customLocaleStrings) {
if (typeof settings.customLocaleStrings !== "object") throw wrongFormatErr
_.each(settings.customLocaleStrings, function(overrides, langcode) {
if (typeof overrides !== "object") throw wrongFormatErr
_.each(overrides, function(localeString, key) {
if (typeof localeString !== "string") throw wrongFormatErr
locales[langcode][key] = localeString
})
})
if (typeof settings.customLocaleStrings !== 'object') throw wrongFormatErr;
_.each(settings.customLocaleStrings, (overrides, langcode) => {
if (typeof overrides !== 'object') throw wrongFormatErr;
_.each(overrides, (localeString, key) => {
if (typeof localeString !== 'string') throw wrongFormatErr;
locales[langcode][key] = localeString;
});
});
}
return locales;
@ -83,45 +83,44 @@ function getAllLocales() {
// returns a hash of all available languages availables with nativeName and direction
// e.g. { es: {nativeName: "español", direction: "ltr"}, ... }
function getAvailableLangs(locales) {
var result = {};
_.each(_.keys(locales), function(langcode) {
const result = {};
_.each(_.keys(locales), (langcode) => {
result[langcode] = languages.getLanguageInfo(langcode);
});
return result;
}
// returns locale index that will be served in /locales.json
var generateLocaleIndex = function (locales) {
var result = _.clone(locales) // keep English strings
_.each(_.keys(locales), function(langcode) {
if (langcode != 'en') result[langcode]='locales/'+langcode+'.json';
const generateLocaleIndex = function (locales) {
const result = _.clone(locales); // keep English strings
_.each(_.keys(locales), (langcode) => {
if (langcode != 'en') result[langcode] = `locales/${langcode}.json`;
});
return JSON.stringify(result);
}
};
exports.expressCreateServer = function(n, args, cb) {
//regenerate locales on server restart
var locales = getAllLocales();
var localeIndex = generateLocaleIndex(locales);
exports.expressCreateServer = function (n, args, cb) {
// regenerate locales on server restart
const locales = getAllLocales();
const localeIndex = generateLocaleIndex(locales);
exports.availableLangs = getAvailableLangs(locales);
args.app.get ('/locales/:locale', function(req, res) {
//works with /locale/en and /locale/en.json requests
var locale = req.params.locale.split('.')[0];
args.app.get('/locales/:locale', (req, res) => {
// works with /locale/en and /locale/en.json requests
const locale = req.params.locale.split('.')[0];
if (exports.availableLangs.hasOwnProperty(locale)) {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.send('{"'+locale+'":'+JSON.stringify(locales[locale])+'}');
res.send(`{"${locale}":${JSON.stringify(locales[locale])}}`);
} else {
res.status(404).send('Language not available');
}
})
});
args.app.get('/locales.json', function(req, res) {
args.app.get('/locales.json', (req, res) => {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.send(localeIndex);
})
});
return cb();
}
};

View file

@ -1,4 +1,4 @@
var securityManager = require('./db/SecurityManager');
const securityManager = require('./db/SecurityManager');
// checks for padAccess
module.exports = async function (req, res) {
@ -7,7 +7,7 @@ module.exports = async function (req, res) {
const accessObj = await securityManager.checkAccess(
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
if (accessObj.accessStatus === "grant") {
if (accessObj.accessStatus === 'grant') {
// there is access, continue
return true;
} else {
@ -19,4 +19,4 @@ module.exports = async function (req, res) {
// @TODO - send internal server error here?
throw err;
}
}
};

View file

@ -61,13 +61,13 @@ exports.start = async () => {
try {
await db.init();
await plugins.update();
console.info('Installed plugins: ' + plugins.formatPluginsWithVersion());
console.debug('Installed parts:\n' + plugins.formatParts());
console.debug('Installed hooks:\n' + plugins.formatHooks());
console.info(`Installed plugins: ${plugins.formatPluginsWithVersion()}`);
console.debug(`Installed parts:\n${plugins.formatParts()}`);
console.debug(`Installed hooks:\n${plugins.formatHooks()}`);
await hooks.aCallAll('loadSettings', {settings});
await hooks.aCallAll('createServer');
} catch (e) {
console.error('exception thrown: ' + e.message);
console.error(`exception thrown: ${e.message}`);
if (e.stack) console.log(e.stack);
process.exit(1);
}

View file

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

View file

@ -18,41 +18,39 @@
* limitations under the License.
*/
var spawn = require('child_process').spawn;
var async = require("async");
var settings = require("./Settings");
var os = require('os');
const spawn = require('child_process').spawn;
const async = require('async');
const settings = require('./Settings');
const os = require('os');
var doConvertTask;
let doConvertTask;
//on windows we have to spawn a process for each convertion, cause the plugin abicommand doesn't exist on this platform
if(os.type().indexOf("Windows") > -1)
{
var stdoutBuffer = "";
// on windows we have to spawn a process for each convertion, cause the plugin abicommand doesn't exist on this platform
if (os.type().indexOf('Windows') > -1) {
let stdoutBuffer = '';
doConvertTask = function(task, callback) {
//span an abiword process to perform the conversion
var abiword = spawn(settings.abiword, ["--to=" + task.destFile, task.srcFile]);
doConvertTask = function (task, callback) {
// span an abiword process to perform the conversion
const abiword = spawn(settings.abiword, [`--to=${task.destFile}`, task.srcFile]);
//delegate the processing of stdout to another function
abiword.stdout.on('data', function (data) {
//add data to buffer
stdoutBuffer+=data.toString();
});
//append error messages to the buffer
abiword.stderr.on('data', function (data) {
// delegate the processing of stdout to another function
abiword.stdout.on('data', (data) => {
// add data to buffer
stdoutBuffer += data.toString();
});
//throw exceptions if abiword is dieing
abiword.on('exit', function (code) {
if(code != 0) {
// append error messages to the buffer
abiword.stderr.on('data', (data) => {
stdoutBuffer += data.toString();
});
// throw exceptions if abiword is dieing
abiword.on('exit', (code) => {
if (code != 0) {
return callback(`Abiword died with exit code ${code}`);
}
if(stdoutBuffer != "")
{
if (stdoutBuffer != '') {
console.log(stdoutBuffer);
}
@ -60,51 +58,48 @@ if(os.type().indexOf("Windows") > -1)
});
};
exports.convertFile = function(srcFile, destFile, type, callback) {
doConvertTask({"srcFile": srcFile, "destFile": destFile, "type": type}, callback);
exports.convertFile = function (srcFile, destFile, type, callback) {
doConvertTask({srcFile, destFile, type}, callback);
};
}
//on unix operating systems, we can start abiword with abicommand and communicate with it via stdin/stdout
//thats much faster, about factor 10
else
{
//spawn the abiword process
var abiword;
var stdoutCallback = null;
var spawnAbiword = function (){
abiword = spawn(settings.abiword, ["--plugin", "AbiCommand"]);
var stdoutBuffer = "";
var firstPrompt = true;
// on unix operating systems, we can start abiword with abicommand and communicate with it via stdin/stdout
// thats much faster, about factor 10
else {
// spawn the abiword process
let abiword;
let stdoutCallback = null;
var spawnAbiword = function () {
abiword = spawn(settings.abiword, ['--plugin', 'AbiCommand']);
let stdoutBuffer = '';
let firstPrompt = true;
//append error messages to the buffer
abiword.stderr.on('data', function (data) {
// append error messages to the buffer
abiword.stderr.on('data', (data) => {
stdoutBuffer += data.toString();
});
//abiword died, let's restart abiword and return an error with the callback
abiword.on('exit', function (code) {
// abiword died, let's restart abiword and return an error with the callback
abiword.on('exit', (code) => {
spawnAbiword();
stdoutCallback(`Abiword died with exit code ${code}`);
});
//delegate the processing of stdout to a other function
abiword.stdout.on('data',function (data) {
//add data to buffer
stdoutBuffer+=data.toString();
// delegate the processing of stdout to a other function
abiword.stdout.on('data', (data) => {
// add data to buffer
stdoutBuffer += data.toString();
//we're searching for the prompt, cause this means everything we need is in the buffer
if(stdoutBuffer.search("AbiWord:>") != -1)
{
//filter the feedback message
var err = stdoutBuffer.search("OK") != -1 ? null : stdoutBuffer;
// we're searching for the prompt, cause this means everything we need is in the buffer
if (stdoutBuffer.search('AbiWord:>') != -1) {
// filter the feedback message
const err = stdoutBuffer.search('OK') != -1 ? null : stdoutBuffer;
//reset the buffer
stdoutBuffer = "";
// reset the buffer
stdoutBuffer = '';
//call the callback with the error message
//skip the first prompt
if(stdoutCallback != null && !firstPrompt)
{
// call the callback with the error message
// skip the first prompt
if (stdoutCallback != null && !firstPrompt) {
stdoutCallback(err);
stdoutCallback = null;
}
@ -115,23 +110,23 @@ else
};
spawnAbiword();
doConvertTask = function(task, callback) {
abiword.stdin.write("convert " + task.srcFile + " " + task.destFile + " " + task.type + "\n");
//create a callback that calls the task callback and the caller callback
doConvertTask = function (task, callback) {
abiword.stdin.write(`convert ${task.srcFile} ${task.destFile} ${task.type}\n`);
// create a callback that calls the task callback and the caller callback
stdoutCallback = function (err) {
callback();
console.log("queue continue");
try{
console.log('queue continue');
try {
task.callback(err);
}catch(e){
console.error("Abiword File failed to convert", e);
} catch (e) {
console.error('Abiword File failed to convert', e);
}
};
};
//Queue with the converts we have to do
var queue = async.queue(doConvertTask, 1);
exports.convertFile = function(srcFile, destFile, type, callback) {
queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback});
// Queue with the converts we have to do
const queue = async.queue(doConvertTask, 1);
exports.convertFile = function (srcFile, destFile, type, callback) {
queue.push({srcFile, destFile, type, callback});
};
}

View file

@ -18,17 +18,17 @@
* limitations under the License.
*/
var log4js = require('log4js');
var path = require('path');
var _ = require('underscore');
const log4js = require('log4js');
const path = require('path');
const _ = require('underscore');
var absPathLogger = log4js.getLogger('AbsolutePaths');
const absPathLogger = log4js.getLogger('AbsolutePaths');
/*
* findEtherpadRoot() computes its value only on first invocation.
* Subsequent invocations are served from this variable.
*/
var etherpadRoot = null;
let etherpadRoot = null;
/**
* If stringArray's last elements are exactly equal to lastDesiredElements,
@ -40,9 +40,9 @@ var etherpadRoot = null;
* @return {string[]|boolean} The shortened array, or false if there was no
* overlap.
*/
var popIfEndsWith = function(stringArray, lastDesiredElements) {
const popIfEndsWith = function (stringArray, lastDesiredElements) {
if (stringArray.length <= lastDesiredElements.length) {
absPathLogger.debug(`In order to pop "${lastDesiredElements.join(path.sep)}" from "${stringArray.join(path.sep)}", it should contain at least ${lastDesiredElements.length + 1 } elements`);
absPathLogger.debug(`In order to pop "${lastDesiredElements.join(path.sep)}" from "${stringArray.join(path.sep)}", it should contain at least ${lastDesiredElements.length + 1} elements`);
return false;
}
@ -72,7 +72,7 @@ var popIfEndsWith = function(stringArray, lastDesiredElements) {
* @return {string} The identified absolute base path. If such path cannot be
* identified, prints a log and exits the application.
*/
exports.findEtherpadRoot = function() {
exports.findEtherpadRoot = function () {
if (etherpadRoot !== null) {
return etherpadRoot;
}
@ -87,7 +87,7 @@ exports.findEtherpadRoot = function() {
*
* <BASE_DIR>\src
*/
var maybeEtherpadRoot = popIfEndsWith(splitFoundRoot, ['src']);
let maybeEtherpadRoot = popIfEndsWith(splitFoundRoot, ['src']);
if ((maybeEtherpadRoot === false) && (process.platform === 'win32')) {
/*
@ -126,7 +126,7 @@ exports.findEtherpadRoot = function() {
* it is returned unchanged. Otherwise it is interpreted
* relative to exports.root.
*/
exports.makeAbsolute = function(somePath) {
exports.makeAbsolute = function (somePath) {
if (path.isAbsolute(somePath)) {
return somePath;
}
@ -145,7 +145,7 @@ exports.makeAbsolute = function(somePath) {
* a subdirectory of the base one
* @return {boolean}
*/
exports.isSubdir = function(parent, arbitraryDir) {
exports.isSubdir = function (parent, arbitraryDir) {
// modified from: https://stackoverflow.com/questions/37521893/determine-if-a-path-is-subdirectory-of-another-in-node-js#45242825
const relative = path.relative(parent, arbitraryDir);
const isSubdir = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);

View file

@ -22,30 +22,30 @@
// An object containing the parsed command-line options
exports.argv = {};
var argv = process.argv.slice(2);
var arg, prevArg;
const argv = process.argv.slice(2);
let arg, prevArg;
// Loop through args
for ( var i = 0; i < argv.length; i++ ) {
for (let i = 0; i < argv.length; i++) {
arg = argv[i];
// Override location of settings.json file
if ( prevArg == '--settings' || prevArg == '-s' ) {
if (prevArg == '--settings' || prevArg == '-s') {
exports.argv.settings = arg;
}
// Override location of credentials.json file
if ( prevArg == '--credentials' ) {
if (prevArg == '--credentials') {
exports.argv.credentials = arg;
}
// Override location of settings.json file
if ( prevArg == '--sessionkey' ) {
if (prevArg == '--sessionkey') {
exports.argv.sessionkey = arg;
}
// Override location of settings.json file
if ( prevArg == '--apikey' ) {
if (prevArg == '--apikey') {
exports.argv.apikey = arg;
}

View file

@ -15,41 +15,39 @@
*/
let db = require("../db/DB");
let hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
const db = require('../db/DB');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
exports.getPadRaw = async function(padId) {
exports.getPadRaw = async function (padId) {
const padKey = `pad:${padId}`;
const padcontent = await db.get(padKey);
let padKey = "pad:" + padId;
let padcontent = await db.get(padKey);
let records = [ padKey ];
const records = [padKey];
for (let i = 0; i <= padcontent.head; i++) {
records.push(padKey + ":revs:" + i);
records.push(`${padKey}:revs:${i}`);
}
for (let i = 0; i <= padcontent.chatHead; i++) {
records.push(padKey + ":chat:" + i);
records.push(`${padKey}:chat:${i}`);
}
let data = {};
for (let key of records) {
const data = {};
for (const key of records) {
// For each piece of info about a pad.
let entry = data[key] = await db.get(key);
const entry = data[key] = await db.get(key);
// Get the Pad Authors
if (entry.pool && entry.pool.numToAttrib) {
let authors = entry.pool.numToAttrib;
const authors = entry.pool.numToAttrib;
for (let k of Object.keys(authors)) {
if (authors[k][0] === "author") {
let authorId = authors[k][1];
for (const k of Object.keys(authors)) {
if (authors[k][0] === 'author') {
const authorId = authors[k][1];
// Get the author info
let authorEntry = await db.get("globalAuthor:" + authorId);
const authorEntry = await db.get(`globalAuthor:${authorId}`);
if (authorEntry) {
data["globalAuthor:" + authorId] = authorEntry;
data[`globalAuthor:${authorId}`] = authorEntry;
if (authorEntry.padIDs) {
authorEntry.padIDs = padId;
}
@ -68,4 +66,4 @@ exports.getPadRaw = async function(padId) {
}));
return data;
}
};

View file

@ -18,24 +18,23 @@
* limitations under the License.
*/
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
exports.getPadPlainText = function(pad, revNum){
var _analyzeLine = exports._analyzeLine;
var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext);
var textLines = atext.text.slice(0, -1).split('\n');
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
var apool = pad.pool;
exports.getPadPlainText = function (pad, revNum) {
const _analyzeLine = exports._analyzeLine;
const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext);
const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
const apool = pad.pool;
var pieces = [];
for (var i = 0; i < textLines.length; i++){
var line = _analyzeLine(textLines[i], attribLines[i], apool);
if (line.listLevel){
var numSpaces = line.listLevel * 2 - 1;
var bullet = '*';
const pieces = [];
for (let i = 0; i < textLines.length; i++) {
const line = _analyzeLine(textLines[i], attribLines[i], apool);
if (line.listLevel) {
const numSpaces = line.listLevel * 2 - 1;
const bullet = '*';
pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n');
}
else{
} else {
pieces.push(line.text, '\n');
}
}
@ -44,38 +43,37 @@ exports.getPadPlainText = function(pad, revNum){
};
exports._analyzeLine = function(text, aline, apool){
var line = {};
exports._analyzeLine = function (text, aline, apool) {
const line = {};
// identify list
var lineMarker = 0;
let lineMarker = 0;
line.listLevel = 0;
if (aline){
var opIter = Changeset.opIterator(aline);
if (opIter.hasNext()){
var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
if (listType){
if (aline) {
const opIter = Changeset.opIterator(aline);
if (opIter.hasNext()) {
let listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
if (listType) {
lineMarker = 1;
listType = /([a-z]+)([0-9]+)/.exec(listType);
if (listType){
if (listType) {
line.listTypeName = listType[1];
line.listLevel = Number(listType[2]);
}
}
}
var opIter2 = Changeset.opIterator(aline);
if (opIter2.hasNext()){
var start = Changeset.opAttributeValue(opIter2.next(), 'start', apool);
if (start){
line.start = start;
const opIter2 = Changeset.opIterator(aline);
if (opIter2.hasNext()) {
const start = Changeset.opAttributeValue(opIter2.next(), 'start', apool);
if (start) {
line.start = start;
}
}
}
if (lineMarker){
if (lineMarker) {
line.text = text.substring(1);
line.aline = Changeset.subattribution(aline, 1);
}
else{
} else {
line.text = text;
line.aline = aline;
}
@ -83,8 +81,6 @@ exports._analyzeLine = function(text, aline, apool){
};
exports._encodeWhitespace = function(s){
return s.replace(/[^\x21-\x7E\s\t\n\r]/gu, function(c){
return "&#" +c.codePointAt(0) + ";";
});
exports._encodeWhitespace = function (s) {
return s.replace(/[^\x21-\x7E\s\t\n\r]/gu, (c) => `&#${c.codePointAt(0)};`);
};

View file

@ -14,14 +14,14 @@
* limitations under the License.
*/
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var padManager = require("../db/PadManager");
var _ = require('underscore');
var Security = require('ep_etherpad-lite/static/js/security');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var eejs = require('ep_etherpad-lite/node/eejs');
var _analyzeLine = require('./ExportHelper')._analyzeLine;
var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const padManager = require('../db/PadManager');
const _ = require('underscore');
const Security = require('ep_etherpad-lite/static/js/security');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
const eejs = require('ep_etherpad-lite/node/eejs');
const _analyzeLine = require('./ExportHelper')._analyzeLine;
const _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
async function getPadHTML(pad, revNum) {
let atext = pad.atext;
@ -39,12 +39,12 @@ exports.getPadHTML = getPadHTML;
exports.getHTMLFromAtext = getHTMLFromAtext;
async function getHTMLFromAtext(pad, atext, authorColors) {
var apool = pad.apool();
var textLines = atext.text.slice(0, -1).split('\n');
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
const apool = pad.apool();
const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
var tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
const tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
await Promise.all([
// prepare tags stored as ['tag', true] to be exported
@ -68,56 +68,55 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// and maps them to an index in props
// *3:2 -> the attribute *3 means strong
// *2:5 -> the attribute *2 means s(trikethrough)
var anumMap = {};
var css = "";
const anumMap = {};
let css = '';
var stripDotFromAuthorID = function(id){
return id.replace(/\./g,'_');
const stripDotFromAuthorID = function (id) {
return id.replace(/\./g, '_');
};
if(authorColors){
css+="<style>\n";
if (authorColors) {
css += '<style>\n';
for (var a in apool.numToAttrib) {
var attr = apool.numToAttrib[a];
for (const a in apool.numToAttrib) {
const attr = apool.numToAttrib[a];
//skip non author attributes
if(attr[0] === "author" && attr[1] !== ""){
//add to props array
var propName = "author" + stripDotFromAuthorID(attr[1]);
// skip non author attributes
if (attr[0] === 'author' && attr[1] !== '') {
// add to props array
var propName = `author${stripDotFromAuthorID(attr[1])}`;
var newLength = props.push(propName);
anumMap[a] = newLength -1;
anumMap[a] = newLength - 1;
css+="." + propName + " {background-color: " + authorColors[attr[1]]+ "}\n";
} else if(attr[0] === "removed") {
var propName = "removed";
css += `.${propName} {background-color: ${authorColors[attr[1]]}}\n`;
} else if (attr[0] === 'removed') {
var propName = 'removed';
var newLength = props.push(propName);
anumMap[a] = newLength -1;
anumMap[a] = newLength - 1;
css+=".removed {text-decoration: line-through; " +
"-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; "+
"filter: alpha(opacity=80); "+
"opacity: 0.8; "+
"}\n";
css += '.removed {text-decoration: line-through; ' +
"-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; " +
'filter: alpha(opacity=80); ' +
'opacity: 0.8; ' +
'}\n';
}
}
css+="</style>";
css += '</style>';
}
// iterates over all props(h1,h2,strong,...), checks if it is used in
// this pad, and if yes puts its attrib id->props value into anumMap
props.forEach(function (propName, i) {
var attrib = [propName, true];
props.forEach((propName, i) => {
let attrib = [propName, true];
if (_.isArray(propName)) {
// propName can be in the form of ['color', 'red'],
// see hook exportHtmlAdditionalTagsWithData
attrib = propName;
}
var propTrueNum = apool.putAttrib(attrib, true);
if (propTrueNum >= 0)
{
const propTrueNum = apool.putAttrib(attrib, true);
if (propTrueNum >= 0) {
anumMap[propTrueNum] = i;
}
});
@ -128,15 +127,15 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
// becomes
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
var taker = Changeset.stringIterator(text);
var assem = Changeset.stringAssembler();
var openTags = [];
const taker = Changeset.stringIterator(text);
const assem = Changeset.stringAssembler();
const openTags = [];
function getSpanClassFor(i){
//return if author colors are disabled
function getSpanClassFor(i) {
// return if author colors are disabled
if (!authorColors) return false;
var property = props[i];
const property = props[i];
// we are not insterested on properties in the form of ['color', 'red'],
// see hook exportHtmlAdditionalTagsWithData
@ -144,12 +143,12 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
return false;
}
if(property.substr(0,6) === "author"){
if (property.substr(0, 6) === 'author') {
return stripDotFromAuthorID(property);
}
if(property === "removed"){
return "removed";
if (property === 'removed') {
return 'removed';
}
return false;
@ -157,16 +156,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// tags added by exportHtmlAdditionalTagsWithData will be exported as <span> with
// data attributes
function isSpanWithData(i){
var property = props[i];
function isSpanWithData(i) {
const property = props[i];
return _.isArray(property);
}
function emitOpenTag(i) {
openTags.unshift(i);
var spanClass = getSpanClassFor(i);
const spanClass = getSpanClassFor(i);
if(spanClass){
if (spanClass) {
assem.append('<span class="');
assem.append(spanClass);
assem.append('">');
@ -180,10 +179,10 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// this closes an open tag and removes its reference from openTags
function emitCloseTag(i) {
openTags.shift();
var spanClass = getSpanClassFor(i);
var spanWithData = isSpanWithData(i);
const spanClass = getSpanClassFor(i);
const spanWithData = isSpanWithData(i);
if(spanClass || spanWithData){
if (spanClass || spanWithData) {
assem.append('</span>');
} else {
assem.append('</');
@ -192,90 +191,78 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
}
var urls = _findURLs(text);
const urls = _findURLs(text);
var idx = 0;
let idx = 0;
function processNextChars(numChars) {
if (numChars <= 0)
{
if (numChars <= 0) {
return;
}
var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
const iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
idx += numChars;
// this iterates over every op string and decides which tags to open or to close
// based on the attribs used
while (iter.hasNext())
{
var o = iter.next();
while (iter.hasNext()) {
const o = iter.next();
var usedAttribs = [];
// mark all attribs as used
Changeset.eachAttribNumber(o.attribs, function (a) {
if (a in anumMap)
{
Changeset.eachAttribNumber(o.attribs, (a) => {
if (a in anumMap) {
usedAttribs.push(anumMap[a]); // i = 0 => bold, etc.
}
});
var outermostTag = -1;
let outermostTag = -1;
// find the outer most open tag that is no longer used
for (var i = openTags.length - 1; i >= 0; i--)
{
if (usedAttribs.indexOf(openTags[i]) === -1)
{
for (var i = openTags.length - 1; i >= 0; i--) {
if (usedAttribs.indexOf(openTags[i]) === -1) {
outermostTag = i;
break;
}
}
// close all tags upto the outer most
if (outermostTag !== -1)
{
while ( outermostTag >= 0 )
{
if (outermostTag !== -1) {
while (outermostTag >= 0) {
emitCloseTag(openTags[0]);
outermostTag--;
}
}
// open all tags that are used but not open
for (i=0; i < usedAttribs.length; i++)
{
if (openTags.indexOf(usedAttribs[i]) === -1)
{
for (i = 0; i < usedAttribs.length; i++) {
if (openTags.indexOf(usedAttribs[i]) === -1) {
emitOpenTag(usedAttribs[i]);
}
}
var chars = o.chars;
if (o.lines)
{
let chars = o.chars;
if (o.lines) {
chars--; // exclude newline at end of line, if present
}
var s = taker.take(chars);
let s = taker.take(chars);
//removes the characters with the code 12. Don't know where they come
//from but they break the abiword parser and are completly useless
s = s.replace(String.fromCharCode(12), "");
// removes the characters with the code 12. Don't know where they come
// from but they break the abiword parser and are completly useless
s = s.replace(String.fromCharCode(12), '');
assem.append(_encodeWhitespace(Security.escapeHTML(s)));
} // end iteration over spans in line
// close all the tags that are open after the last op
while (openTags.length > 0)
{
while (openTags.length > 0) {
emitCloseTag(openTags[0]);
}
} // end processNextChars
if (urls)
{
urls.forEach(function (urlData) {
var startIndex = urlData[0];
var url = urlData[1];
var urlLength = url.length;
if (urls) {
urls.forEach((urlData) => {
const startIndex = urlData[0];
const url = urlData[1];
const urlLength = url.length;
processNextChars(startIndex - idx);
// Using rel="noreferrer" stops leaking the URL/location of the exported HTML when clicking links in the document.
// Not all browsers understand this attribute, but it's part of the HTML5 standard.
@ -284,16 +271,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
// https://mathiasbynens.github.io/rel-noopener/
// https://github.com/ether/etherpad-lite/pull/3636
assem.append('<a href="' + Security.escapeHTMLAttribute(url) + '" rel="noreferrer noopener">');
assem.append(`<a href="${Security.escapeHTMLAttribute(url)}" rel="noreferrer noopener">`);
processNextChars(urlLength);
assem.append('</a>');
});
}
processNextChars(text.length - idx);
return _processSpaces(assem.toString());
} // end getLineHTML
var pieces = [css];
const pieces = [css];
// Need to deal with constraints imposed on HTML lists; can
// only gain one level of nesting at once, can't change type
@ -302,56 +289,48 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// so we want to do something reasonable there. We also
// want to deal gracefully with blank lines.
// => keeps track of the parents level of indentation
var openLists = [];
for (var i = 0; i < textLines.length; i++)
{
let openLists = [];
for (let i = 0; i < textLines.length; i++) {
var context;
var line = _analyzeLine(textLines[i], attribLines[i], apool);
var lineContent = getLineHTML(line.text, line.aline);
if (line.listLevel)//If we are inside a list
const lineContent = getLineHTML(line.text, line.aline);
if (line.listLevel)// If we are inside a list
{
context = {
line: line,
lineContent: lineContent,
apool: apool,
line,
lineContent,
apool,
attribLine: attribLines[i],
text: textLines[i],
padId: pad.id
padId: pad.id,
};
var prevLine = null;
var nextLine = null;
if (i > 0)
{
prevLine = _analyzeLine(textLines[i -1], attribLines[i -1], apool);
let prevLine = null;
let nextLine = null;
if (i > 0) {
prevLine = _analyzeLine(textLines[i - 1], attribLines[i - 1], apool);
}
if (i < textLines.length)
{
if (i < textLines.length) {
nextLine = _analyzeLine(textLines[i + 1], attribLines[i + 1], apool);
}
await hooks.aCallAll('getLineHTMLForExport', context);
//To create list parent elements
if ((!prevLine || prevLine.listLevel !== line.listLevel) || (prevLine && line.listTypeName !== prevLine.listTypeName))
{
var exists = _.find(openLists, function (item) {
return (item.level === line.listLevel && item.type === line.listTypeName);
});
// To create list parent elements
if ((!prevLine || prevLine.listLevel !== line.listLevel) || (prevLine && line.listTypeName !== prevLine.listTypeName)) {
const exists = _.find(openLists, (item) => (item.level === line.listLevel && item.type === line.listTypeName));
if (!exists) {
var prevLevel = 0;
let prevLevel = 0;
if (prevLine && prevLine.listLevel) {
prevLevel = prevLine.listLevel;
}
if (prevLine && line.listTypeName !== prevLine.listTypeName)
{
if (prevLine && line.listTypeName !== prevLine.listTypeName) {
prevLevel = 0;
}
for (var diff = prevLevel; diff < line.listLevel; diff++) {
openLists.push({level: diff, type: line.listTypeName});
var prevPiece = pieces[pieces.length - 1];
const prevPiece = pieces[pieces.length - 1];
if (prevPiece.indexOf("<ul") === 0 || prevPiece.indexOf("<ol") === 0 || prevPiece.indexOf("</li>") === 0)
{
/*
if (prevPiece.indexOf('<ul') === 0 || prevPiece.indexOf('<ol') === 0 || prevPiece.indexOf('</li>') === 0) {
/*
uncommenting this breaks nested ols..
if the previous item is NOT a ul, NOT an ol OR closing li then close the list
so we consider this HTML, I inserted ** where it throws a problem in Example Wrong..
@ -367,19 +346,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// pieces.push("</li>");
*/
if( (nextLine.listTypeName === 'number') && (nextLine.text === '') ){
if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) {
// is the listTypeName check needed here? null text might be completely fine!
// TODO Check against Uls
// don't do anything because the next item is a nested ol openener so we need to keep the li open
}else{
pieces.push("<li>");
} else {
pieces.push('<li>');
}
}
if (line.listTypeName === "number")
{
if (line.listTypeName === 'number') {
// We introduce line.start here, this is useful for continuing Ordered list line numbers
// in case you have a bullet in a list IE you Want
// 1. hello
@ -390,80 +366,66 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
// TODO: This logic could also be used to continue OL with indented content
// but that's a job for another day....
if(line.start){
pieces.push("<ol start=\""+Number(line.start)+"\" class=\"" + line.listTypeName + "\">");
}else{
pieces.push("<ol class=\"" + line.listTypeName + "\">");
if (line.start) {
pieces.push(`<ol start="${Number(line.start)}" class="${line.listTypeName}">`);
} else {
pieces.push(`<ol class="${line.listTypeName}">`);
}
}
else
{
pieces.push("<ul class=\"" + line.listTypeName + "\">");
} else {
pieces.push(`<ul class="${line.listTypeName}">`);
}
}
}
}
// if we're going up a level we shouldn't be adding..
if(context.lineContent){
pieces.push("<li>", context.lineContent);
if (context.lineContent) {
pieces.push('<li>', context.lineContent);
}
// To close list elements
if (nextLine && nextLine.listLevel === line.listLevel && line.listTypeName === nextLine.listTypeName)
{
if(context.lineContent){
if( (nextLine.listTypeName === 'number') && (nextLine.text === '') ){
if (nextLine && nextLine.listLevel === line.listLevel && line.listTypeName === nextLine.listTypeName) {
if (context.lineContent) {
if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) {
// is the listTypeName check needed here? null text might be completely fine!
// TODO Check against Uls
// don't do anything because the next item is a nested ol openener so we need to keep the li open
}else{
pieces.push("</li>");
} else {
pieces.push('</li>');
}
}
}
if ((!nextLine || !nextLine.listLevel || nextLine.listLevel < line.listLevel) || (nextLine && line.listTypeName !== nextLine.listTypeName))
{
var nextLevel = 0;
if ((!nextLine || !nextLine.listLevel || nextLine.listLevel < line.listLevel) || (nextLine && line.listTypeName !== nextLine.listTypeName)) {
let nextLevel = 0;
if (nextLine && nextLine.listLevel) {
nextLevel = nextLine.listLevel;
}
if (nextLine && line.listTypeName !== nextLine.listTypeName)
{
if (nextLine && line.listTypeName !== nextLine.listTypeName) {
nextLevel = 0;
}
for (var diff = nextLevel; diff < line.listLevel; diff++)
{
openLists = openLists.filter(function(el) {
return el.level !== diff && el.type !== line.listTypeName;
});
for (var diff = nextLevel; diff < line.listLevel; diff++) {
openLists = openLists.filter((el) => el.level !== diff && el.type !== line.listTypeName);
if (pieces[pieces.length - 1].indexOf("</ul") === 0 || pieces[pieces.length - 1].indexOf("</ol") === 0)
{
pieces.push("</li>");
if (pieces[pieces.length - 1].indexOf('</ul') === 0 || pieces[pieces.length - 1].indexOf('</ol') === 0) {
pieces.push('</li>');
}
if (line.listTypeName === "number")
{
pieces.push("</ol>");
}
else
{
pieces.push("</ul>");
if (line.listTypeName === 'number') {
pieces.push('</ol>');
} else {
pieces.push('</ul>');
}
}
}
}
else//outside any list, need to close line.listLevel of lists
} else// outside any list, need to close line.listLevel of lists
{
context = {
line: line,
lineContent: lineContent,
apool: apool,
line,
lineContent,
apool,
attribLine: attribLines[i],
text: textLines[i],
padId: pad.id
padId: pad.id,
};
await hooks.aCallAll('getLineHTMLForExport', context);
@ -475,46 +437,45 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
}
exports.getPadHTMLDocument = async function (padId, revNum) {
let pad = await padManager.getPad(padId);
const pad = await padManager.getPad(padId);
// Include some Styles into the Head for Export
let stylesForExportCSS = "";
let stylesForExport = await hooks.aCallAll("stylesForExport", padId);
stylesForExport.forEach(function(css){
let stylesForExportCSS = '';
const stylesForExport = await hooks.aCallAll('stylesForExport', padId);
stylesForExport.forEach((css) => {
stylesForExportCSS += css;
});
let html = await getPadHTML(pad, revNum);
for (const hookHtml of await hooks.aCallAll("exportHTMLAdditionalContent", {padId})) {
for (const hookHtml of await hooks.aCallAll('exportHTMLAdditionalContent', {padId})) {
html += hookHtml;
}
return eejs.require("ep_etherpad-lite/templates/export_html.html", {
return eejs.require('ep_etherpad-lite/templates/export_html.html', {
body: html,
padId: Security.escapeHTML(padId),
extraCSS: stylesForExportCSS
extraCSS: stylesForExportCSS,
});
}
};
// copied from ACE
var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
var _REGEX_SPACE = /\s/;
var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')');
var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g');
const _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
const _REGEX_SPACE = /\s/;
const _REGEX_URLCHAR = new RegExp(`(${/[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source}|${_REGEX_WORDCHAR.source})`);
const _REGEX_URL = new RegExp(`${/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source}*(?![:.,;])${_REGEX_URLCHAR.source}`, 'g');
// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
function _findURLs(text) {
_REGEX_URL.lastIndex = 0;
var urls = null;
var execResult;
while ((execResult = _REGEX_URL.exec(text)))
{
let urls = null;
let execResult;
while ((execResult = _REGEX_URL.exec(text))) {
urls = (urls || []);
var startIndex = execResult.index;
var url = execResult[0];
const startIndex = execResult.index;
const url = execResult[0];
urls.push([startIndex, url]);
}
@ -523,50 +484,46 @@ function _findURLs(text) {
// copied from ACE
function _processSpaces(s){
var doesWrap = true;
if (s.indexOf("<") < 0 && !doesWrap){
function _processSpaces(s) {
const doesWrap = true;
if (s.indexOf('<') < 0 && !doesWrap) {
// short-cut
return s.replace(/ /g, '&nbsp;');
}
var parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, function (m){
const parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, (m) => {
parts.push(m);
});
if (doesWrap){
var endOfLine = true;
var beforeSpace = false;
if (doesWrap) {
let endOfLine = true;
let beforeSpace = false;
// last space in a run is normal, others are nbsp,
// end of line is nbsp
for (var i = parts.length - 1; i >= 0; i--){
for (var i = parts.length - 1; i >= 0; i--) {
var p = parts[i];
if (p == " "){
if (p == ' ') {
if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false;
beforeSpace = true;
}
else if (p.charAt(0) != "<"){
} else if (p.charAt(0) != '<') {
endOfLine = false;
beforeSpace = false;
}
}
// beginning of line is nbsp
for (i = 0; i < parts.length; i++){
for (i = 0; i < parts.length; i++) {
p = parts[i];
if (p == " "){
if (p == ' ') {
parts[i] = '&nbsp;';
break;
}
else if (p.charAt(0) != "<"){
} else if (p.charAt(0) != '<') {
break;
}
}
}
else
{
for (i = 0; i < parts.length; i++){
} else {
for (i = 0; i < parts.length; i++) {
p = parts[i];
if (p == " "){
if (p == ' ') {
parts[i] = '&nbsp;';
}
}

View file

@ -18,12 +18,12 @@
* limitations under the License.
*/
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var padManager = require("../db/PadManager");
var _analyzeLine = require('./ExportHelper')._analyzeLine;
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const padManager = require('../db/PadManager');
const _analyzeLine = require('./ExportHelper')._analyzeLine;
// This is slightly different than the HTML method as it passes the output to getTXTFromAText
var getPadTXT = async function(pad, revNum) {
const getPadTXT = async function (pad, revNum) {
let atext = pad.atext;
if (revNum != undefined) {
@ -33,57 +33,57 @@ var getPadTXT = async function(pad, revNum) {
// convert atext to html
return getTXTFromAtext(pad, atext);
}
};
// This is different than the functionality provided in ExportHtml as it provides formatting
// functionality that is designed specifically for TXT exports
function getTXTFromAtext(pad, atext, authorColors) {
var apool = pad.apool();
var textLines = atext.text.slice(0, -1).split('\n');
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
const apool = pad.apool();
const textLines = atext.text.slice(0, -1).split('\n');
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
var anumMap = {};
var css = "";
const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
const anumMap = {};
const css = '';
props.forEach(function(propName, i) {
var propTrueNum = apool.putAttrib([propName, true], true);
props.forEach((propName, i) => {
const propTrueNum = apool.putAttrib([propName, true], true);
if (propTrueNum >= 0) {
anumMap[propTrueNum] = i;
}
});
function getLineTXT(text, attribs) {
var propVals = [false, false, false];
var ENTER = 1;
var STAY = 2;
var LEAVE = 0;
const propVals = [false, false, false];
const ENTER = 1;
const STAY = 2;
const LEAVE = 0;
// Use order of tags (b/i/u) as order of nesting, for simplicity
// and decent nesting. For example,
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
// becomes
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
var taker = Changeset.stringIterator(text);
var assem = Changeset.stringAssembler();
const taker = Changeset.stringIterator(text);
const assem = Changeset.stringAssembler();
var idx = 0;
let idx = 0;
function processNextChars(numChars) {
if (numChars <= 0) {
return;
}
var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
const iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
idx += numChars;
while (iter.hasNext()) {
var o = iter.next();
const o = iter.next();
var propChanged = false;
Changeset.eachAttribNumber(o.attribs, function(a) {
Changeset.eachAttribNumber(o.attribs, (a) => {
if (a in anumMap) {
var i = anumMap[a]; // i = 0 => bold, etc.
const i = anumMap[a]; // i = 0 => bold, etc.
if (!propVals[i]) {
propVals[i] = ENTER;
@ -108,20 +108,18 @@ function getTXTFromAtext(pad, atext, authorColors) {
// according to what happens at start of span
if (propChanged) {
// leaving bold (e.g.) also leaves italics, etc.
var left = false;
let left = false;
for (var i = 0; i < propVals.length; i++) {
var v = propVals[i];
const v = propVals[i];
if (!left) {
if (v === LEAVE) {
left = true;
}
} else {
if (v === true) {
// tag will be closed and re-opened
propVals[i] = STAY;
}
} else if (v === true) {
// tag will be closed and re-opened
propVals[i] = STAY;
}
}
@ -129,11 +127,11 @@ function getTXTFromAtext(pad, atext, authorColors) {
for (var i = propVals.length - 1; i >= 0; i--) {
if (propVals[i] === LEAVE) {
//emitCloseTag(i);
// emitCloseTag(i);
tags2close.push(i);
propVals[i] = false;
} else if (propVals[i] === STAY) {
//emitCloseTag(i);
// emitCloseTag(i);
tags2close.push(i);
}
}
@ -146,13 +144,13 @@ function getTXTFromAtext(pad, atext, authorColors) {
// propVals is now all {true,false} again
} // end if (propChanged)
var chars = o.chars;
let chars = o.chars;
if (o.lines) {
// exclude newline at end of line, if present
chars--;
}
var s = taker.take(chars);
const s = taker.take(chars);
// removes the characters with the code 12. Don't know where they come
// from but they break the abiword parser and are completly useless
@ -172,14 +170,13 @@ function getTXTFromAtext(pad, atext, authorColors) {
propVals[i] = false;
}
}
} // end processNextChars
processNextChars(text.length - idx);
return(assem.toString());
return (assem.toString());
} // end getLineHTML
var pieces = [css];
const pieces = [css];
// Need to deal with constraints imposed on HTML lists; can
// only gain one level of nesting at once, can't change type
@ -189,34 +186,33 @@ function getTXTFromAtext(pad, atext, authorColors) {
// want to deal gracefully with blank lines.
// => keeps track of the parents level of indentation
var listNumbers = {};
var prevListLevel;
const listNumbers = {};
let prevListLevel;
for (var i = 0; i < textLines.length; i++) {
for (let i = 0; i < textLines.length; i++) {
const line = _analyzeLine(textLines[i], attribLines[i], apool);
let lineContent = getLineTXT(line.text, line.aline);
var line = _analyzeLine(textLines[i], attribLines[i], apool);
var lineContent = getLineTXT(line.text, line.aline);
if (line.listTypeName == "bullet") {
lineContent = "* " + lineContent; // add a bullet
if (line.listTypeName == 'bullet') {
lineContent = `* ${lineContent}`; // add a bullet
}
if (line.listTypeName !== "number") {
if (line.listTypeName !== 'number') {
// We're no longer in an OL so we can reset counting
for (var key in listNumbers) {
for (const key in listNumbers) {
delete listNumbers[key];
}
}
if (line.listLevel > 0) {
for (var j = line.listLevel - 1; j >= 0; j--) {
for (let j = line.listLevel - 1; j >= 0; j--) {
pieces.push('\t'); // tab indent list numbers..
if(!listNumbers[line.listLevel]){
if (!listNumbers[line.listLevel]) {
listNumbers[line.listLevel] = 0;
}
}
if (line.listTypeName == "number") {
if (line.listTypeName == 'number') {
/*
* listLevel == amount of indentation
* listNumber(s) == item number
@ -230,19 +226,19 @@ function getTXTFromAtext(pad, atext, authorColors) {
* To handle going back to 2.1 when prevListLevel is lower number
* than current line.listLevel then reset the object value
*/
if(line.listLevel < prevListLevel){
if (line.listLevel < prevListLevel) {
delete listNumbers[prevListLevel];
}
listNumbers[line.listLevel]++;
if(line.listLevel > 1){
var x = 1;
while(x <= line.listLevel-1){
pieces.push(listNumbers[x]+".")
if (line.listLevel > 1) {
let x = 1;
while (x <= line.listLevel - 1) {
pieces.push(`${listNumbers[x]}.`);
x++;
}
}
pieces.push(listNumbers[line.listLevel]+". ")
pieces.push(`${listNumbers[line.listLevel]}. `);
prevListLevel = line.listLevel;
}
@ -257,7 +253,7 @@ function getTXTFromAtext(pad, atext, authorColors) {
exports.getTXTFromAtext = getTXTFromAtext;
exports.getPadTXTDocument = async function(padId, revNum) {
let pad = await padManager.getPad(padId);
exports.getPadTXTDocument = async function (padId, revNum) {
const pad = await padManager.getPad(padId);
return getPadTXT(pad, revNum);
}
};

View file

@ -14,14 +14,14 @@
* limitations under the License.
*/
var log4js = require('log4js');
const db = require("../db/DB");
const log4js = require('log4js');
const db = require('../db/DB');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
exports.setPadRaw = function(padId, records) {
exports.setPadRaw = function (padId, records) {
records = JSON.parse(records);
Object.keys(records).forEach(async function(key) {
Object.keys(records).forEach(async (key) => {
let value = records[key];
if (!value) {
@ -36,7 +36,7 @@ exports.setPadRaw = function(padId, records) {
newKey = key;
// Does this author already exist?
let author = await db.get(key);
const author = await db.get(key);
if (author) {
// Yes, add the padID to the author
@ -47,20 +47,20 @@ exports.setPadRaw = function(padId, records) {
value = author;
} else {
// No, create a new array with the author info in
value.padIDs = [ padId ];
value.padIDs = [padId];
}
} else {
// Not author data, probably pad data
// we can split it to look to see if it's pad data
let oldPadId = key.split(":");
const oldPadId = key.split(':');
// we know it's pad data
if (oldPadId[0] === "pad") {
if (oldPadId[0] === 'pad') {
// so set the new pad id for the author
oldPadId[1] = padId;
// and create the value
newKey = oldPadId.join(":"); // create the new key
newKey = oldPadId.join(':'); // create the new key
}
// is this a key that is supported through a plugin?
@ -74,4 +74,4 @@ exports.setPadRaw = function(padId, records) {
// Write the value to the server
await db.set(newKey, value);
});
}
};

View file

@ -14,75 +14,75 @@
* limitations under the License.
*/
const log4js = require('log4js');
const Changeset = require("ep_etherpad-lite/static/js/Changeset");
const contentcollector = require("ep_etherpad-lite/static/js/contentcollector");
const cheerio = require("cheerio");
const rehype = require("rehype")
const format = require("rehype-format")
const log4js = require('log4js');
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
const contentcollector = require('ep_etherpad-lite/static/js/contentcollector');
const cheerio = require('cheerio');
const rehype = require('rehype');
const format = require('rehype-format');
exports.setPadHTML = async (pad, html) => {
var apiLogger = log4js.getLogger("ImportHtml");
const apiLogger = log4js.getLogger('ImportHtml');
var opts = {
const opts = {
indentInitial: false,
indent: -1
}
indent: -1,
};
rehype()
.use(format, opts)
.process(html, function(err, output){
html = String(output).replace(/(\r\n|\n|\r)/gm,"");
})
.use(format, opts)
.process(html, (err, output) => {
html = String(output).replace(/(\r\n|\n|\r)/gm, '');
});
var $ = cheerio.load(html);
const $ = cheerio.load(html);
// Appends a line break, used by Etherpad to ensure a caret is available
// below the last line of an import
$('body').append("<p></p>");
$('body').append('<p></p>');
var doc = $('html')[0];
const doc = $('html')[0];
apiLogger.debug('html:');
apiLogger.debug(html);
// Convert a dom tree into a list of lines and attribute liens
// using the content collector object
var cc = contentcollector.makeContentCollector(true, null, pad.pool);
const cc = contentcollector.makeContentCollector(true, null, pad.pool);
try {
// we use a try here because if the HTML is bad it will blow up
cc.collectContent(doc);
} catch(e) {
apiLogger.warn("HTML was not properly formed", e);
} catch (e) {
apiLogger.warn('HTML was not properly formed', e);
// don't process the HTML because it was bad
throw e;
}
var result = cc.finish();
const result = cc.finish();
apiLogger.debug('Lines:');
var i;
let i;
for (i = 0; i < result.lines.length; i++) {
apiLogger.debug('Line ' + (i + 1) + ' text: ' + result.lines[i]);
apiLogger.debug('Line ' + (i + 1) + ' attributes: ' + result.lineAttribs[i]);
apiLogger.debug(`Line ${i + 1} text: ${result.lines[i]}`);
apiLogger.debug(`Line ${i + 1} attributes: ${result.lineAttribs[i]}`);
}
// Get the new plain text and its attributes
var newText = result.lines.join('\n');
const newText = result.lines.join('\n');
apiLogger.debug('newText:');
apiLogger.debug(newText);
var newAttribs = result.lineAttribs.join('|1+1') + '|1+1';
const newAttribs = `${result.lineAttribs.join('|1+1')}|1+1`;
function eachAttribRun(attribs, func /*(startInNewText, endInNewText, attribs)*/ ) {
var attribsIter = Changeset.opIterator(attribs);
var textIndex = 0;
var newTextStart = 0;
var newTextEnd = newText.length;
function eachAttribRun(attribs, func /* (startInNewText, endInNewText, attribs)*/) {
const attribsIter = Changeset.opIterator(attribs);
let textIndex = 0;
const newTextStart = 0;
const newTextEnd = newText.length;
while (attribsIter.hasNext()) {
var op = attribsIter.next();
var nextIndex = textIndex + op.chars;
const op = attribsIter.next();
const nextIndex = textIndex + op.chars;
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
}
@ -91,19 +91,19 @@ exports.setPadHTML = async (pad, html) => {
}
// create a new changeset with a helper builder object
var builder = Changeset.builder(1);
const builder = Changeset.builder(1);
// assemble each line into the builder
eachAttribRun(newAttribs, function(start, end, attribs) {
eachAttribRun(newAttribs, (start, end, attribs) => {
builder.insert(newText.substring(start, end), attribs);
});
// the changeset is ready!
var theChangeset = builder.toString();
const theChangeset = builder.toString();
apiLogger.debug('The changeset: ' + theChangeset);
apiLogger.debug(`The changeset: ${theChangeset}`);
await Promise.all([
pad.setText('\n'),
pad.appendRevision(theChangeset),
]);
}
};

View file

@ -16,18 +16,18 @@
* limitations under the License.
*/
var async = require("async");
var fs = require("fs");
var log4js = require('log4js');
var os = require("os");
var path = require("path");
var settings = require("./Settings");
var spawn = require("child_process").spawn;
const async = require('async');
const fs = require('fs');
const log4js = require('log4js');
const os = require('os');
const path = require('path');
const settings = require('./Settings');
const spawn = require('child_process').spawn;
// Conversion tasks will be queued up, so we don't overload the system
var queue = async.queue(doConvertTask, 1);
const queue = async.queue(doConvertTask, 1);
var libreOfficeLogger = log4js.getLogger('LibreOffice');
const libreOfficeLogger = log4js.getLogger('LibreOffice');
/**
* Convert a file from one type to another
@ -37,18 +37,18 @@ var libreOfficeLogger = log4js.getLogger('LibreOffice');
* @param {String} type The type to convert into
* @param {Function} callback Standard callback function
*/
exports.convertFile = function(srcFile, destFile, type, callback) {
exports.convertFile = function (srcFile, destFile, type, callback) {
// Used for the moving of the file, not the conversion
var fileExtension = type;
const fileExtension = type;
if (type === "html") {
if (type === 'html') {
// "html:XHTML Writer File:UTF8" does a better job than normal html exports
if (path.extname(srcFile).toLowerCase() === ".doc") {
type = "html";
if (path.extname(srcFile).toLowerCase() === '.doc') {
type = 'html';
}
// PDF files need to be converted with LO Draw ref https://github.com/ether/etherpad-lite/issues/4151
if (path.extname(srcFile).toLowerCase() === ".pdf") {
type = "html:XHTML Draw File"
if (path.extname(srcFile).toLowerCase() === '.pdf') {
type = 'html:XHTML Draw File';
}
}
@ -57,58 +57,60 @@ exports.convertFile = function(srcFile, destFile, type, callback) {
// to avoid `Error: no export filter for /tmp/xxxx.doc` error
if (type === 'doc') {
queue.push({
"srcFile": srcFile,
"destFile": destFile.replace(/\.doc$/, '.odt'),
"type": 'odt',
"callback": function () {
queue.push({"srcFile": srcFile.replace(/\.html$/, '.odt'), "destFile": destFile, "type": type, "callback": callback, "fileExtension": fileExtension });
}
srcFile,
destFile: destFile.replace(/\.doc$/, '.odt'),
type: 'odt',
callback() {
queue.push({srcFile: srcFile.replace(/\.html$/, '.odt'), destFile, type, callback, fileExtension});
},
});
} else {
queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback, "fileExtension": fileExtension});
queue.push({srcFile, destFile, type, callback, fileExtension});
}
};
function doConvertTask(task, callback) {
var tmpDir = os.tmpdir();
const tmpDir = os.tmpdir();
async.series([
/*
* use LibreOffice to convert task.srcFile to another format, given in
* task.type
*/
function(callback) {
function (callback) {
libreOfficeLogger.debug(`Converting ${task.srcFile} to format ${task.type}. The result will be put in ${tmpDir}`);
var soffice = spawn(settings.soffice, [
const soffice = spawn(settings.soffice, [
'--headless',
'--invisible',
'--nologo',
'--nolockcheck',
'--writer',
'--convert-to', task.type,
'--convert-to',
task.type,
task.srcFile,
'--outdir', tmpDir
'--outdir',
tmpDir,
]);
// Soffice/libreoffice is buggy and often hangs.
// To remedy this we kill the spawned process after a while.
const hangTimeout = setTimeout(() => {
soffice.stdin.pause(); // required to kill hanging threads
soffice.kill();
}, 120000);
var stdoutBuffer = '';
}, 120000);
let stdoutBuffer = '';
// Delegate the processing of stdout to another function
soffice.stdout.on('data', function(data) {
soffice.stdout.on('data', (data) => {
stdoutBuffer += data.toString();
});
// Append error messages to the buffer
soffice.stderr.on('data', function(data) {
soffice.stderr.on('data', (data) => {
stdoutBuffer += data.toString();
});
soffice.on('exit', function(code) {
soffice.on('exit', (code) => {
clearTimeout(hangTimeout);
if (code != 0) {
// Throw an exception if libreoffice failed
@ -117,18 +119,18 @@ function doConvertTask(task, callback) {
// if LibreOffice exited succesfully, go on with processing
callback();
})
});
},
// Move the converted file to the correct place
function(callback) {
var filename = path.basename(task.srcFile);
var sourceFilename = filename.substr(0, filename.lastIndexOf('.')) + '.' + task.fileExtension;
var sourcePath = path.join(tmpDir, sourceFilename);
function (callback) {
const filename = path.basename(task.srcFile);
const sourceFilename = `${filename.substr(0, filename.lastIndexOf('.'))}.${task.fileExtension}`;
const sourcePath = path.join(tmpDir, sourceFilename);
libreOfficeLogger.debug(`Renaming ${sourcePath} to ${task.destFile}`);
fs.rename(sourcePath, task.destFile, callback);
}
], function(err) {
},
], (err) => {
// Invoke the callback for the local queue
callback();

View file

@ -19,31 +19,29 @@
* limitations under the License.
*/
var ERR = require("async-stacktrace");
var settings = require('./Settings');
var async = require('async');
var fs = require('fs');
var StringDecoder = require('string_decoder').StringDecoder;
var CleanCSS = require('clean-css');
var path = require('path');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugin_defs");
var RequireKernel = require('etherpad-require-kernel');
var urlutil = require('url');
var mime = require('mime-types')
var Threads = require('threads')
var log4js = require('log4js');
const ERR = require('async-stacktrace');
const settings = require('./Settings');
const async = require('async');
const fs = require('fs');
const StringDecoder = require('string_decoder').StringDecoder;
const CleanCSS = require('clean-css');
const path = require('path');
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
const RequireKernel = require('etherpad-require-kernel');
const urlutil = require('url');
const mime = require('mime-types');
const Threads = require('threads');
const log4js = require('log4js');
var logger = log4js.getLogger("Minify");
const logger = log4js.getLogger('Minify');
var ROOT_DIR = path.normalize(__dirname + "/../../static/");
var TAR_PATH = path.join(__dirname, 'tar.json');
var tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8'));
const ROOT_DIR = path.normalize(`${__dirname}/../../static/`);
const TAR_PATH = path.join(__dirname, 'tar.json');
const tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8'));
var threadsPool = Threads.Pool(function () {
return Threads.spawn(new Threads.Worker("./MinifyWorker"))
}, 2)
const threadsPool = Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2);
var LIBRARY_WHITELIST = [
const LIBRARY_WHITELIST = [
'async',
'js-cookie',
'security',
@ -53,71 +51,68 @@ var LIBRARY_WHITELIST = [
];
// Rewrite tar to include modules with no extensions and proper rooted paths.
var LIBRARY_PREFIX = 'ep_etherpad-lite/static/js';
const LIBRARY_PREFIX = 'ep_etherpad-lite/static/js';
exports.tar = {};
function prefixLocalLibraryPath(path) {
if (path.charAt(0) == '$') {
return path.slice(1);
} else {
return LIBRARY_PREFIX + '/' + path;
return `${LIBRARY_PREFIX}/${path}`;
}
}
for (var key in tar) {
for (const key in tar) {
exports.tar[prefixLocalLibraryPath(key)] =
tar[key].map(prefixLocalLibraryPath).concat(
tar[key].map(prefixLocalLibraryPath).map(function (p) {
return p.replace(/\.js$/, '');
})
tar[key].map(prefixLocalLibraryPath).map((p) => p.replace(/\.js$/, '')),
).concat(
tar[key].map(prefixLocalLibraryPath).map(function (p) {
return p.replace(/\.js$/, '') + '/index.js';
})
tar[key].map(prefixLocalLibraryPath).map((p) => `${p.replace(/\.js$/, '')}/index.js`),
);
}
// What follows is a terrible hack to avoid loop-back within the server.
// TODO: Serve files from another service, or directly from the file system.
function requestURI(url, method, headers, callback) {
var parsedURL = urlutil.parse(url);
const parsedURL = urlutil.parse(url);
var status = 500, headers = {}, content = [];
let status = 500; var headers = {}; const
content = [];
var mockRequest = {
url: url
, method: method
, params: {filename: parsedURL.path.replace(/^\/static\//, '')}
, headers: headers
const mockRequest = {
url,
method,
params: {filename: parsedURL.path.replace(/^\/static\//, '')},
headers,
};
var mockResponse = {
writeHead: function (_status, _headers) {
const mockResponse = {
writeHead(_status, _headers) {
status = _status;
for (var header in _headers) {
for (const header in _headers) {
if (Object.prototype.hasOwnProperty.call(_headers, header)) {
headers[header] = _headers[header];
}
}
}
, setHeader: function (header, value) {
},
setHeader(header, value) {
headers[header.toLowerCase()] = value.toString();
}
, header: function (header, value) {
},
header(header, value) {
headers[header.toLowerCase()] = value.toString();
}
, write: function (_content) {
_content && content.push(_content);
}
, end: function (_content) {
},
write(_content) {
_content && content.push(_content);
},
end(_content) {
_content && content.push(_content);
callback(status, headers, content.join(''));
}
},
};
minify(mockRequest, mockResponse);
}
function requestURIs(locations, method, headers, callback) {
var pendingRequests = locations.length;
var responses = [];
let pendingRequests = locations.length;
const responses = [];
function respondFor(i) {
return function (status, headers, content) {
@ -128,14 +123,14 @@ function requestURIs(locations, method, headers, callback) {
};
}
for (var i = 0, ii = locations.length; i < ii; i++) {
for (let i = 0, ii = locations.length; i < ii; i++) {
requestURI(locations[i], method, headers, respondFor(i));
}
function completed() {
var statuss = responses.map(function (x) {return x[0];});
var headerss = responses.map(function (x) {return x[1];});
var contentss = responses.map(function (x) {return x[2];});
const statuss = responses.map((x) => x[0]);
const headerss = responses.map((x) => x[1]);
const contentss = responses.map((x) => x[2]);
callback(statuss, headerss, contentss);
}
}
@ -146,15 +141,15 @@ function requestURIs(locations, method, headers, callback) {
* @param res the Express response
*/
function minify(req, res) {
var filename = req.params['filename'];
let filename = req.params.filename;
// No relative paths, especially if they may go up the file hierarchy.
filename = path.normalize(path.join(ROOT_DIR, filename));
filename = filename.replace(/\.\./g, '')
filename = filename.replace(/\.\./g, '');
if (filename.indexOf(ROOT_DIR) == 0) {
filename = filename.slice(ROOT_DIR.length);
filename = filename.replace(/\\/g, '/')
filename = filename.replace(/\\/g, '/');
} else {
res.writeHead(404, {});
res.end();
@ -166,36 +161,36 @@ function minify(req, res) {
are rewritten into ROOT_PATH_OF_MYPLUGIN/static/js/test.js,
commonly ETHERPAD_ROOT/node_modules/ep_myplugin/static/js/test.js
*/
var match = filename.match(/^plugins\/([^\/]+)(\/(?:(static\/.*)|.*))?$/);
const match = filename.match(/^plugins\/([^\/]+)(\/(?:(static\/.*)|.*))?$/);
if (match) {
var library = match[1];
var libraryPath = match[2] || '';
const library = match[1];
const libraryPath = match[2] || '';
if (plugins.plugins[library] && match[3]) {
var plugin = plugins.plugins[library];
var pluginPath = plugin.package.realPath;
const plugin = plugins.plugins[library];
const pluginPath = plugin.package.realPath;
filename = path.relative(ROOT_DIR, pluginPath + libraryPath);
filename = filename.replace(/\\/g, '/'); // windows path fix
} else if (LIBRARY_WHITELIST.indexOf(library) != -1) {
// Go straight into node_modules
// Avoid `require.resolve()`, since 'mustache' and 'mustache/index.js'
// would end up resolving to logically distinct resources.
filename = '../node_modules/' + library + libraryPath;
filename = `../node_modules/${library}${libraryPath}`;
}
}
var contentType = mime.lookup(filename);
const contentType = mime.lookup(filename);
statFile(filename, function (error, date, exists) {
statFile(filename, (error, date, exists) => {
if (date) {
date = new Date(date);
date.setMilliseconds(0);
res.setHeader('last-modified', date.toUTCString());
res.setHeader('date', (new Date()).toUTCString());
if (settings.maxAge !== undefined) {
var expiresDate = new Date(Date.now()+settings.maxAge*1000);
const expiresDate = new Date(Date.now() + settings.maxAge * 1000);
res.setHeader('expires', expiresDate.toUTCString());
res.setHeader('cache-control', 'max-age=' + settings.maxAge);
res.setHeader('cache-control', `max-age=${settings.maxAge}`);
}
}
@ -208,37 +203,35 @@ function minify(req, res) {
} else if (new Date(req.headers['if-modified-since']) >= date) {
res.writeHead(304, {});
res.end();
} else {
if (req.method == 'HEAD') {
res.header("Content-Type", contentType);
res.writeHead(200, {});
res.end();
} else if (req.method == 'GET') {
getFileCompressed(filename, contentType, function (error, content) {
if(ERR(error, function(){
res.writeHead(500, {});
res.end();
})) return;
res.header("Content-Type", contentType);
res.writeHead(200, {});
res.write(content);
} else if (req.method == 'HEAD') {
res.header('Content-Type', contentType);
res.writeHead(200, {});
res.end();
} else if (req.method == 'GET') {
getFileCompressed(filename, contentType, (error, content) => {
if (ERR(error, () => {
res.writeHead(500, {});
res.end();
});
} else {
res.writeHead(405, {'allow': 'HEAD, GET'});
})) return;
res.header('Content-Type', contentType);
res.writeHead(200, {});
res.write(content);
res.end();
}
});
} else {
res.writeHead(405, {allow: 'HEAD, GET'});
res.end();
}
}, 3);
}
// find all includes in ace.js and embed them.
function getAceFile(callback) {
fs.readFile(ROOT_DIR + 'js/ace.js', "utf8", function(err, data) {
if(ERR(err, callback)) return;
fs.readFile(`${ROOT_DIR}js/ace.js`, 'utf8', (err, data) => {
if (ERR(err, callback)) return;
// Find all includes in ace.js and embed them
var founds = data.match(/\$\$INCLUDE_[a-zA-Z_]+\("[^"]*"\)/gi);
let founds = data.match(/\$\$INCLUDE_[a-zA-Z_]+\("[^"]*"\)/gi);
if (!settings.minify) {
founds = [];
}
@ -250,25 +243,25 @@ function getAceFile(callback) {
// Request the contents of the included file on the server-side and write
// them into the file.
async.forEach(founds, function (item, callback) {
var filename = item.match(/"([^"]*)"/)[1];
async.forEach(founds, (item, callback) => {
const filename = item.match(/"([^"]*)"/)[1];
// Hostname "invalid.invalid" is a dummy value to allow parsing as a URI.
var baseURI = 'http://invalid.invalid';
var resourceURI = baseURI + path.normalize(path.join('/static/', filename));
const baseURI = 'http://invalid.invalid';
let resourceURI = baseURI + path.normalize(path.join('/static/', filename));
resourceURI = resourceURI.replace(/\\/g, '/'); // Windows (safe generally?)
requestURI(resourceURI, 'GET', {}, function (status, headers, body) {
var error = !(status == 200 || status == 404);
requestURI(resourceURI, 'GET', {}, (status, headers, body) => {
const error = !(status == 200 || status == 404);
if (!error) {
data += 'Ace2Editor.EMBEDED[' + JSON.stringify(filename) + '] = '
+ JSON.stringify(status == 200 ? body || '' : null) + ';\n';
data += `Ace2Editor.EMBEDED[${JSON.stringify(filename)}] = ${
JSON.stringify(status == 200 ? body || '' : null)};\n`;
} else {
console.error(`getAceFile(): error getting ${resourceURI}. Status code: ${status}`);
}
callback();
});
}, function(error) {
}, (error) => {
callback(error, data);
});
});
@ -289,19 +282,19 @@ function statFile(filename, callback, dirStatLimit) {
} else if (filename == 'js/ace.js') {
// Sometimes static assets are inlined into this file, so we have to stat
// everything.
lastModifiedDateOfEverything(function (error, date) {
lastModifiedDateOfEverything((error, date) => {
callback(error, date, !error);
});
} else if (filename == 'js/require-kernel.js') {
callback(null, requireLastModified(), true);
} else {
fs.stat(ROOT_DIR + filename, function (error, stats) {
fs.stat(ROOT_DIR + filename, (error, stats) => {
if (error) {
if (error.code == "ENOENT") {
if (error.code == 'ENOENT') {
// Stat the directory instead.
statFile(path.dirname(filename), function (error, date, exists) {
statFile(path.dirname(filename), (error, date, exists) => {
callback(error, date, false);
}, dirStatLimit-1);
}, dirStatLimit - 1);
} else {
callback(error);
}
@ -314,29 +307,28 @@ function statFile(filename, callback, dirStatLimit) {
}
}
function lastModifiedDateOfEverything(callback) {
var folders2check = [ROOT_DIR + 'js/', ROOT_DIR + 'css/'];
var latestModification = 0;
//go trough this two folders
async.forEach(folders2check, function(path, callback) {
//read the files in the folder
fs.readdir(path, function(err, files) {
if(ERR(err, callback)) return;
const folders2check = [`${ROOT_DIR}js/`, `${ROOT_DIR}css/`];
let latestModification = 0;
// go trough this two folders
async.forEach(folders2check, (path, callback) => {
// read the files in the folder
fs.readdir(path, (err, files) => {
if (ERR(err, callback)) return;
//we wanna check the directory itself for changes too
files.push(".");
// we wanna check the directory itself for changes too
files.push('.');
//go trough all files in this folder
async.forEach(files, function(filename, callback) {
//get the stat data of this file
fs.stat(path + "/" + filename, function(err, stats) {
if(ERR(err, callback)) return;
// go trough all files in this folder
async.forEach(files, (filename, callback) => {
// get the stat data of this file
fs.stat(`${path}/${filename}`, (err, stats) => {
if (ERR(err, callback)) return;
//get the modification time
var modificationTime = stats.mtime.getTime();
// get the modification time
const modificationTime = stats.mtime.getTime();
//compare the modification time to the highest found
if(modificationTime > latestModification)
{
// compare the modification time to the highest found
if (modificationTime > latestModification) {
latestModification = modificationTime;
}
@ -344,29 +336,29 @@ function lastModifiedDateOfEverything(callback) {
});
}, callback);
});
}, function () {
}, () => {
callback(null, latestModification);
});
}
// This should be provided by the module, but until then, just use startup
// time.
var _requireLastModified = new Date();
const _requireLastModified = new Date();
function requireLastModified() {
return _requireLastModified.toUTCString();
}
function requireDefinition() {
return 'var require = ' + RequireKernel.kernelSource + ';\n';
return `var require = ${RequireKernel.kernelSource};\n`;
}
function getFileCompressed(filename, contentType, callback) {
getFile(filename, function (error, content) {
getFile(filename, (error, content) => {
if (error || !content || !settings.minify) {
callback(error, content);
} else if (contentType == 'application/javascript') {
threadsPool.queue(async ({ compressJS }) => {
threadsPool.queue(async ({compressJS}) => {
try {
logger.info('Compress JS file %s.', filename)
logger.info('Compress JS file %s.', filename);
content = content.toString();
const compressResult = await compressJS(content);
@ -381,11 +373,11 @@ function getFileCompressed(filename, contentType, callback) {
}
callback(null, content);
})
});
} else if (contentType == 'text/css') {
threadsPool.queue(async ({ compressCSS }) => {
threadsPool.queue(async ({compressCSS}) => {
try {
logger.info('Compress CSS file %s.', filename)
logger.info('Compress CSS file %s.', filename);
content = await compressCSS(filename, ROOT_DIR);
} catch (error) {
@ -393,7 +385,7 @@ function getFileCompressed(filename, contentType, callback) {
}
callback(null, content);
})
});
} else {
callback(null, content);
}

View file

@ -2,10 +2,10 @@
* Worker thread to minify JS & CSS files out of the main NodeJS thread
*/
var CleanCSS = require('clean-css');
var Terser = require("terser");
var path = require('path');
var Threads = require('threads')
const CleanCSS = require('clean-css');
const Terser = require('terser');
const path = require('path');
const Threads = require('threads');
function compressJS(content) {
return Terser.minify(content);
@ -46,20 +46,20 @@ function compressCSS(filename, ROOT_DIR) {
new CleanCSS({
rebase: true,
rebaseTo: basePath,
}).minify([absPath], function (errors, minified) {
if (errors) return rej(errors)
}).minify([absPath], (errors, minified) => {
if (errors) return rej(errors);
return res(minified.styles)
return res(minified.styles);
});
} catch (error) {
// on error, just yield the un-minified original, but write a log message
console.error(`Unexpected error minifying ${filename} (${absPath}): ${error}`);
callback(null, content);
}
})
});
}
Threads.expose({
compressJS,
compressCSS
})
compressCSS,
});

View file

@ -25,17 +25,17 @@ const semver = require('semver');
*
* @param {String} minNodeVersion Minimum required Node version
*/
exports.enforceMinNodeVersion = function(minNodeVersion) {
exports.enforceMinNodeVersion = function (minNodeVersion) {
const currentNodeVersion = process.version;
// we cannot use template literals, since we still do not know if we are
// running under Node >= 4.0
if (semver.lt(currentNodeVersion, minNodeVersion)) {
console.error('Running Etherpad on Node ' + currentNodeVersion + ' is not supported. Please upgrade at least to Node ' + minNodeVersion);
console.error(`Running Etherpad on Node ${currentNodeVersion} is not supported. Please upgrade at least to Node ${minNodeVersion}`);
process.exit(1);
}
console.debug('Running on Node ' + currentNodeVersion + ' (minimum required Node version: ' + minNodeVersion + ')');
console.debug(`Running on Node ${currentNodeVersion} (minimum required Node version: ${minNodeVersion})`);
};
/**
@ -44,7 +44,7 @@ exports.enforceMinNodeVersion = function(minNodeVersion) {
* @param {String} lowestNonDeprecatedNodeVersion all Node version less than this one are deprecated
* @param {Function} epRemovalVersion Etherpad version that will remove support for deprecated Node releases
*/
exports.checkDeprecationStatus = function(lowestNonDeprecatedNodeVersion, epRemovalVersion) {
exports.checkDeprecationStatus = function (lowestNonDeprecatedNodeVersion, epRemovalVersion) {
const currentNodeVersion = process.version;
if (semver.lt(currentNodeVersion, lowestNonDeprecatedNodeVersion)) {

View file

@ -26,17 +26,17 @@
* limitations under the License.
*/
var absolutePaths = require('./AbsolutePaths');
var fs = require("fs");
var os = require("os");
var path = require('path');
var argv = require('./Cli').argv;
var npm = require("npm/lib/npm.js");
var jsonminify = require("jsonminify");
var log4js = require("log4js");
var randomString = require("./randomstring");
var suppressDisableMsg = " -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n";
var _ = require("underscore");
const absolutePaths = require('./AbsolutePaths');
const fs = require('fs');
const os = require('os');
const path = require('path');
const argv = require('./Cli').argv;
const npm = require('npm/lib/npm.js');
const jsonminify = require('jsonminify');
const log4js = require('log4js');
const randomString = require('./randomstring');
const suppressDisableMsg = ' -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n';
const _ = require('underscore');
/* Root path of the installation */
exports.root = absolutePaths.findEtherpadRoot();
@ -59,14 +59,14 @@ console.log(`Random string used for versioning assets: ${exports.randomVersionSt
/**
* The app title, visible e.g. in the browser window
*/
exports.title = "Etherpad";
exports.title = 'Etherpad';
/**
* The app favicon fully specified url, visible e.g. in the browser window
*/
exports.favicon = "favicon.ico";
exports.faviconPad = "../" + exports.favicon;
exports.faviconTimeslider = "../../" + exports.favicon;
exports.favicon = 'favicon.ico';
exports.faviconPad = `../${exports.favicon}`;
exports.faviconTimeslider = `../../${exports.favicon}`;
/*
* Skin name.
@ -76,12 +76,12 @@ exports.faviconTimeslider = "../../" + exports.favicon;
*/
exports.skinName = null;
exports.skinVariants = "super-light-toolbar super-light-editor light-background";
exports.skinVariants = 'super-light-toolbar super-light-editor light-background';
/**
* The IP ep-lite should listen to
*/
exports.ip = "0.0.0.0";
exports.ip = '0.0.0.0';
/**
* The Port ep-lite should listen to
@ -107,60 +107,60 @@ exports.socketTransportProtocols = ['xhr-polling', 'jsonp-polling', 'htmlfile'];
/*
* The Type of the database
*/
exports.dbType = "dirty";
exports.dbType = 'dirty';
/**
* This setting is passed with dbType to ueberDB to set up the database
*/
exports.dbSettings = { "filename" : path.join(exports.root, "var/dirty.db") };
exports.dbSettings = {filename: path.join(exports.root, 'var/dirty.db')};
/**
* The default Text of a new pad
*/
exports.defaultPadText = "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad on Github: https:\/\/github.com\/ether\/etherpad-lite\n";
exports.defaultPadText = 'Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad on Github: https:\/\/github.com\/ether\/etherpad-lite\n';
/**
* The default Pad Settings for a user (Can be overridden by changing the setting
*/
exports.padOptions = {
"noColors": false,
"showControls": true,
"showChat": true,
"showLineNumbers": true,
"useMonospaceFont": false,
"userName": false,
"userColor": false,
"rtl": false,
"alwaysShowChat": false,
"chatAndUsers": false,
"lang": "en-gb"
noColors: false,
showControls: true,
showChat: true,
showLineNumbers: true,
useMonospaceFont: false,
userName: false,
userColor: false,
rtl: false,
alwaysShowChat: false,
chatAndUsers: false,
lang: 'en-gb',
},
/**
* Whether certain shortcut keys are enabled for a user in the pad
*/
exports.padShortcutEnabled = {
"altF9" : true,
"altC" : true,
"delete" : true,
"cmdShift2" : true,
"return" : true,
"esc" : true,
"cmdS" : true,
"tab" : true,
"cmdZ" : true,
"cmdY" : true,
"cmdB" : true,
"cmdI" : true,
"cmdU" : true,
"cmd5" : true,
"cmdShiftL" : true,
"cmdShiftN" : true,
"cmdShift1" : true,
"cmdShiftC" : true,
"cmdH" : true,
"ctrlHome" : true,
"pageUp" : true,
"pageDown" : true,
altF9: true,
altC: true,
delete: true,
cmdShift2: true,
return: true,
esc: true,
cmdS: true,
tab: true,
cmdZ: true,
cmdY: true,
cmdB: true,
cmdI: true,
cmdU: true,
cmd5: true,
cmdShiftL: true,
cmdShiftN: true,
cmdShift1: true,
cmdShiftC: true,
cmdH: true,
ctrlHome: true,
pageUp: true,
pageDown: true,
},
/**
@ -168,20 +168,20 @@ exports.padShortcutEnabled = {
*/
exports.toolbar = {
left: [
["bold", "italic", "underline", "strikethrough"],
["orderedlist", "unorderedlist", "indent", "outdent"],
["undo", "redo"],
["clearauthorship"]
['bold', 'italic', 'underline', 'strikethrough'],
['orderedlist', 'unorderedlist', 'indent', 'outdent'],
['undo', 'redo'],
['clearauthorship'],
],
right: [
["importexport", "timeslider", "savedrevision"],
["settings", "embed"],
["showusers"]
['importexport', 'timeslider', 'savedrevision'],
['settings', 'embed'],
['showusers'],
],
timeslider: [
["timeslider_export", "timeslider_settings", "timeslider_returnToPad"]
]
}
['timeslider_export', 'timeslider_settings', 'timeslider_returnToPad'],
],
};
/**
* A flag that requires any user to have a valid session (via the api) before accessing a pad
@ -196,7 +196,7 @@ exports.editOnly = false;
/**
* Max age that responses will have (affects caching layer).
*/
exports.maxAge = 1000*60*60*6; // 6 hours
exports.maxAge = 1000 * 60 * 60 * 6; // 6 hours
/**
* A flag that shows if minification is enabled or not
@ -226,7 +226,7 @@ exports.allowUnknownFileEnds = true;
/**
* The log level of log4js
*/
exports.loglevel = "INFO";
exports.loglevel = 'INFO';
/**
* Disable IP logging
@ -251,7 +251,7 @@ exports.indentationOnNewLine = true;
/*
* log4js appender configuration
*/
exports.logconfig = { appenders: [{ type: "console" }]};
exports.logconfig = {appenders: [{type: 'console'}]};
/*
* Session Key, do not sure this.
@ -303,28 +303,28 @@ exports.scrollWhenFocusLineIsOutOfViewport = {
/*
* Percentage of viewport height to be additionally scrolled.
*/
"percentage": {
"editionAboveViewport": 0,
"editionBelowViewport": 0
percentage: {
editionAboveViewport: 0,
editionBelowViewport: 0,
},
/*
* Time (in milliseconds) used to animate the scroll transition. Set to 0 to
* disable animation
*/
"duration": 0,
duration: 0,
/*
* Percentage of viewport height to be additionally scrolled when user presses arrow up
* in the line of the top of the viewport.
*/
"percentageToScrollWhenUserPressesArrowUp": 0,
percentageToScrollWhenUserPressesArrowUp: 0,
/*
* Flag to control if it should scroll when user places the caret in the last
* line of the viewport
*/
"scrollWhenCaretIsInTheLastLineOfViewport": false
scrollWhenCaretIsInTheLastLineOfViewport: false,
};
/*
@ -350,10 +350,10 @@ exports.customLocaleStrings = {};
*/
exports.importExportRateLimiting = {
// duration of the rate limit window (milliseconds)
"windowMs": 90000,
windowMs: 90000,
// maximum number of requests per IP to allow during the rate limit window
"max": 10
max: 10,
};
/*
@ -366,10 +366,10 @@ exports.importExportRateLimiting = {
*/
exports.commitRateLimiting = {
// duration of the rate limit window (seconds)
"duration": 1,
duration: 1,
// maximum number of chanes per IP to allow during the rate limit window
"points": 10
points: 10,
};
/*
@ -381,64 +381,64 @@ exports.commitRateLimiting = {
exports.importMaxFileSize = 50 * 1024 * 1024;
// checks if abiword is avaiable
exports.abiwordAvailable = function() {
exports.abiwordAvailable = function () {
if (exports.abiword != null) {
return os.type().indexOf("Windows") != -1 ? "withoutPDF" : "yes";
return os.type().indexOf('Windows') != -1 ? 'withoutPDF' : 'yes';
} else {
return "no";
return 'no';
}
};
exports.sofficeAvailable = function() {
exports.sofficeAvailable = function () {
if (exports.soffice != null) {
return os.type().indexOf("Windows") != -1 ? "withoutPDF": "yes";
return os.type().indexOf('Windows') != -1 ? 'withoutPDF' : 'yes';
} else {
return "no";
return 'no';
}
};
exports.exportAvailable = function() {
var abiword = exports.abiwordAvailable();
var soffice = exports.sofficeAvailable();
exports.exportAvailable = function () {
const abiword = exports.abiwordAvailable();
const soffice = exports.sofficeAvailable();
if (abiword == "no" && soffice == "no") {
return "no";
} else if ((abiword == "withoutPDF" && soffice == "no") || (abiword == "no" && soffice == "withoutPDF")) {
return "withoutPDF";
if (abiword == 'no' && soffice == 'no') {
return 'no';
} else if ((abiword == 'withoutPDF' && soffice == 'no') || (abiword == 'no' && soffice == 'withoutPDF')) {
return 'withoutPDF';
} else {
return "yes";
return 'yes';
}
};
// Provide git version if available
exports.getGitCommit = function() {
var version = "";
exports.getGitCommit = function () {
let version = '';
try {
var rootPath = exports.root;
if (fs.lstatSync(rootPath + '/.git').isFile()) {
rootPath = fs.readFileSync(rootPath + '/.git', "utf8");
let rootPath = exports.root;
if (fs.lstatSync(`${rootPath}/.git`).isFile()) {
rootPath = fs.readFileSync(`${rootPath}/.git`, 'utf8');
rootPath = rootPath.split(' ').pop().trim();
} else {
rootPath += '/.git';
}
var ref = fs.readFileSync(rootPath + "/HEAD", "utf-8");
if (ref.startsWith("ref: ")) {
var refPath = rootPath + "/" + ref.substring(5, ref.indexOf("\n"));
version = fs.readFileSync(refPath, "utf-8");
const ref = fs.readFileSync(`${rootPath}/HEAD`, 'utf-8');
if (ref.startsWith('ref: ')) {
const refPath = `${rootPath}/${ref.substring(5, ref.indexOf('\n'))}`;
version = fs.readFileSync(refPath, 'utf-8');
} else {
version = ref;
}
version = version.substring(0, 7);
} catch(e) {
console.warn("Can't get git version for server header\n" + e.message)
} catch (e) {
console.warn(`Can't get git version for server header\n${e.message}`);
}
return version;
}
};
// Return etherpad version from package.json
exports.getEpVersion = function() {
exports.getEpVersion = function () {
return require('ep_etherpad-lite/package.json').version;
}
};
/**
* Receives a settingsObj and, if the property name is a valid configuration
@ -448,9 +448,9 @@ exports.getEpVersion = function() {
* both "settings.json" and "credentials.json".
*/
function storeSettings(settingsObj) {
for (var i in settingsObj) {
for (const i in settingsObj) {
// test if the setting starts with a lowercase character
if (i.charAt(0).search("[a-z]") !== 0) {
if (i.charAt(0).search('[a-z]') !== 0) {
console.warn(`Settings should start with a lowercase character: '${i}'`);
}
@ -482,26 +482,26 @@ function storeSettings(settingsObj) {
* in the literal string "null", instead.
*/
function coerceValue(stringValue) {
// cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number
const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue));
// cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number
const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue));
if (isNumeric) {
// detected numeric string. Coerce to a number
if (isNumeric) {
// detected numeric string. Coerce to a number
return +stringValue;
}
return +stringValue;
}
// the boolean literal case is easy.
if (stringValue === "true" ) {
return true;
}
// the boolean literal case is easy.
if (stringValue === 'true') {
return true;
}
if (stringValue === "false") {
return false;
}
if (stringValue === 'false') {
return false;
}
// otherwise, return this value as-is
return stringValue;
// otherwise, return this value as-is
return stringValue;
}
/**
@ -624,24 +624,24 @@ function lookupEnvironmentVariables(obj) {
* The isSettings variable only controls the error logging.
*/
function parseSettings(settingsFilename, isSettings) {
let settingsStr = "";
let settingsStr = '';
let settingsType, notFoundMessage, notFoundFunction;
if (isSettings) {
settingsType = "settings";
notFoundMessage = "Continuing using defaults!";
settingsType = 'settings';
notFoundMessage = 'Continuing using defaults!';
notFoundFunction = console.warn;
} else {
settingsType = "credentials";
notFoundMessage = "Ignoring.";
settingsType = 'credentials';
notFoundMessage = 'Ignoring.';
notFoundFunction = console.info;
}
try {
//read the settings file
// read the settings file
settingsStr = fs.readFileSync(settingsFilename).toString();
} catch(e) {
} catch (e) {
notFoundFunction(`No ${settingsType} file found in ${settingsFilename}. ${notFoundMessage}`);
// or maybe undefined!
@ -649,7 +649,7 @@ function parseSettings(settingsFilename, isSettings) {
}
try {
settingsStr = jsonminify(settingsStr).replace(",]","]").replace(",}","}");
settingsStr = jsonminify(settingsStr).replace(',]', ']').replace(',}', '}');
const settings = JSON.parse(settingsStr);
@ -658,7 +658,7 @@ function parseSettings(settingsFilename, isSettings) {
const replacedSettings = lookupEnvironmentVariables(settings);
return replacedSettings;
} catch(e) {
} catch (e) {
console.error(`There was an error processing your ${settingsType} file from ${settingsFilename}: ${e.message}`);
process.exit(1);
@ -667,55 +667,55 @@ function parseSettings(settingsFilename, isSettings) {
exports.reloadSettings = function reloadSettings() {
// Discover where the settings file lives
var settingsFilename = absolutePaths.makeAbsolute(argv.settings || "settings.json");
const settingsFilename = absolutePaths.makeAbsolute(argv.settings || 'settings.json');
// Discover if a credential file exists
var credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || "credentials.json");
const credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || 'credentials.json');
// try to parse the settings
var settings = parseSettings(settingsFilename, true);
const settings = parseSettings(settingsFilename, true);
// try to parse the credentials
var credentials = parseSettings(credentialsFilename, false);
const credentials = parseSettings(credentialsFilename, false);
storeSettings(settings);
storeSettings(credentials);
log4js.configure(exports.logconfig);//Configure the logging appenders
log4js.setGlobalLogLevel(exports.loglevel);//set loglevel
process.env['DEBUG'] = 'socket.io:' + exports.loglevel; // Used by SocketIO for Debug
log4js.configure(exports.logconfig);// Configure the logging appenders
log4js.setGlobalLogLevel(exports.loglevel);// set loglevel
process.env.DEBUG = `socket.io:${exports.loglevel}`; // Used by SocketIO for Debug
log4js.replaceConsole();
if (!exports.skinName) {
console.warn(`No "skinName" parameter found. Please check out settings.json.template and update your settings.json. Falling back to the default "colibris".`);
exports.skinName = "colibris";
console.warn('No "skinName" parameter found. Please check out settings.json.template and update your settings.json. Falling back to the default "colibris".');
exports.skinName = 'colibris';
}
// checks if skinName has an acceptable value, otherwise falls back to "colibris"
if (exports.skinName) {
const skinBasePath = path.join(exports.root, "src", "static", "skins");
const skinBasePath = path.join(exports.root, 'src', 'static', 'skins');
const countPieces = exports.skinName.split(path.sep).length;
if (countPieces != 1) {
console.error(`skinName must be the name of a directory under "${skinBasePath}". This is not valid: "${exports.skinName}". Falling back to the default "colibris".`);
exports.skinName = "colibris";
exports.skinName = 'colibris';
}
// informative variable, just for the log messages
var skinPath = path.normalize(path.join(skinBasePath, exports.skinName));
let skinPath = path.normalize(path.join(skinBasePath, exports.skinName));
// what if someone sets skinName == ".." or "."? We catch him!
if (absolutePaths.isSubdir(skinBasePath, skinPath) === false) {
console.error(`Skin path ${skinPath} must be a subdirectory of ${skinBasePath}. Falling back to the default "colibris".`);
exports.skinName = "colibris";
exports.skinName = 'colibris';
skinPath = path.join(skinBasePath, exports.skinName);
}
if (fs.existsSync(skinPath) === false) {
console.error(`Skin path ${skinPath} does not exist. Falling back to the default "colibris".`);
exports.skinName = "colibris";
exports.skinName = 'colibris';
skinPath = path.join(skinBasePath, exports.skinName);
}
@ -725,13 +725,13 @@ exports.reloadSettings = function reloadSettings() {
if (exports.abiword) {
// Check abiword actually exists
if (exports.abiword != null) {
fs.exists(exports.abiword, function(exists) {
fs.exists(exports.abiword, (exists) => {
if (!exists) {
var abiwordError = "Abiword does not exist at this path, check your settings file.";
const abiwordError = 'Abiword does not exist at this path, check your settings file.';
if (!exports.suppressErrorsInPadText) {
exports.defaultPadText = exports.defaultPadText + "\nError: " + abiwordError + suppressDisableMsg;
exports.defaultPadText = `${exports.defaultPadText}\nError: ${abiwordError}${suppressDisableMsg}`;
}
console.error(abiwordError + ` File location: ${exports.abiword}`);
console.error(`${abiwordError} File location: ${exports.abiword}`);
exports.abiword = null;
}
});
@ -739,46 +739,46 @@ exports.reloadSettings = function reloadSettings() {
}
if (exports.soffice) {
fs.exists(exports.soffice, function(exists) {
fs.exists(exports.soffice, (exists) => {
if (!exists) {
var sofficeError = "soffice (libreoffice) does not exist at this path, check your settings file.";
const sofficeError = 'soffice (libreoffice) does not exist at this path, check your settings file.';
if (!exports.suppressErrorsInPadText) {
exports.defaultPadText = exports.defaultPadText + "\nError: " + sofficeError + suppressDisableMsg;
exports.defaultPadText = `${exports.defaultPadText}\nError: ${sofficeError}${suppressDisableMsg}`;
}
console.error(sofficeError + ` File location: ${exports.soffice}`);
console.error(`${sofficeError} File location: ${exports.soffice}`);
exports.soffice = null;
}
});
}
if (!exports.sessionKey) {
var sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || "./SESSIONKEY.txt");
const sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || './SESSIONKEY.txt');
try {
exports.sessionKey = fs.readFileSync(sessionkeyFilename,"utf8");
exports.sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8');
console.info(`Session key loaded from: ${sessionkeyFilename}`);
} catch(e) {
} catch (e) {
console.info(`Session key file "${sessionkeyFilename}" not found. Creating with random contents.`);
exports.sessionKey = randomString(32);
fs.writeFileSync(sessionkeyFilename,exports.sessionKey,"utf8");
fs.writeFileSync(sessionkeyFilename, exports.sessionKey, 'utf8');
}
} else {
console.warn("Declaring the sessionKey in the settings.json is deprecated. This value is auto-generated now. Please remove the setting from the file. -- If you are seeing this error after restarting using the Admin User Interface then you can ignore this message.");
console.warn('Declaring the sessionKey in the settings.json is deprecated. This value is auto-generated now. Please remove the setting from the file. -- If you are seeing this error after restarting using the Admin User Interface then you can ignore this message.');
}
if (exports.dbType === "dirty") {
var dirtyWarning = "DirtyDB is used. This is fine for testing but not recommended for production.";
if (exports.dbType === 'dirty') {
const dirtyWarning = 'DirtyDB is used. This is fine for testing but not recommended for production.';
if (!exports.suppressErrorsInPadText) {
exports.defaultPadText = exports.defaultPadText + "\nWarning: " + dirtyWarning + suppressDisableMsg;
exports.defaultPadText = `${exports.defaultPadText}\nWarning: ${dirtyWarning}${suppressDisableMsg}`;
}
exports.dbSettings.filename = absolutePaths.makeAbsolute(exports.dbSettings.filename);
console.warn(dirtyWarning + ` File location: ${exports.dbSettings.filename}`);
console.warn(`${dirtyWarning} File location: ${exports.dbSettings.filename}`);
}
if (exports.ip === "") {
if (exports.ip === '') {
// using Unix socket for connectivity
console.warn(`The settings file contains an empty string ("") for the "ip" parameter. The "port" parameter will be interpreted as the path to a Unix socket to bind at.`);
console.warn('The settings file contains an empty string ("") for the "ip" parameter. The "port" parameter will be interpreted as the path to a Unix socket to bind at.');
}
};

View file

@ -2,42 +2,41 @@
* Tidy up the HTML in a given file
*/
var log4js = require('log4js');
var settings = require('./Settings');
var spawn = require('child_process').spawn;
const log4js = require('log4js');
const settings = require('./Settings');
const spawn = require('child_process').spawn;
exports.tidy = function(srcFile) {
var logger = log4js.getLogger('TidyHtml');
exports.tidy = function (srcFile) {
const logger = log4js.getLogger('TidyHtml');
return new Promise((resolve, reject) => {
// Don't do anything if Tidy hasn't been enabled
if (!settings.tidyHtml) {
logger.debug('tidyHtml has not been configured yet, ignoring tidy request');
return resolve(null);
}
var errMessage = '';
let errMessage = '';
// Spawn a new tidy instance that cleans up the file inline
logger.debug('Tidying ' + srcFile);
var tidy = spawn(settings.tidyHtml, ['-modify', srcFile]);
logger.debug(`Tidying ${srcFile}`);
const tidy = spawn(settings.tidyHtml, ['-modify', srcFile]);
// Keep track of any error messages
tidy.stderr.on('data', function (data) {
tidy.stderr.on('data', (data) => {
errMessage += data.toString();
});
tidy.on('close', function(code) {
tidy.on('close', (code) => {
// Tidy returns a 0 when no errors occur and a 1 exit code when
// the file could be tidied but a few warnings were generated
if (code === 0 || code === 1) {
logger.debug('Tidied ' + srcFile + ' successfully');
logger.debug(`Tidied ${srcFile} successfully`);
resolve(null);
} else {
logger.error('Failed to tidy ' + srcFile + '\n' + errMessage);
reject('Tidy died with exit code ' + code);
logger.error(`Failed to tidy ${srcFile}\n${errMessage}`);
reject(`Tidy died with exit code ${code}`);
}
});
});
}
};

View file

@ -5,8 +5,8 @@ const request = require('request');
let infos;
function loadEtherpadInformations() {
return new Promise(function(resolve, reject) {
request('https://static.etherpad.org/info.json', function (er, response, body) {
return new Promise((resolve, reject) => {
request('https://static.etherpad.org/info.json', (er, response, body) => {
if (er) return reject(er);
try {
@ -16,29 +16,29 @@ function loadEtherpadInformations() {
return reject(err);
}
});
})
});
}
exports.getLatestVersion = function() {
exports.getLatestVersion = function () {
exports.needsUpdate();
return infos.latestVersion;
}
};
exports.needsUpdate = function(cb) {
loadEtherpadInformations().then(function(info) {
exports.needsUpdate = function (cb) {
loadEtherpadInformations().then((info) => {
if (semver.gt(info.latestVersion, settings.getEpVersion())) {
if (cb) return cb(true);
}
}).catch(function (err) {
console.error('Can not perform Etherpad update check: ' + err)
}).catch((err) => {
console.error(`Can not perform Etherpad update check: ${err}`);
if (cb) return cb(false);
})
}
});
};
exports.check = function() {
exports.needsUpdate(function (needsUpdate) {
exports.check = function () {
exports.needsUpdate((needsUpdate) => {
if (needsUpdate) {
console.warn('Update available: Download the actual version ' + infos.latestVersion)
console.warn(`Update available: Download the actual version ${infos.latestVersion}`);
}
})
}
});
};

View file

@ -14,13 +14,13 @@
* limitations under the License.
*/
var async = require('async');
var Buffer = require('buffer').Buffer;
var fs = require('fs');
var path = require('path');
var zlib = require('zlib');
var settings = require('./Settings');
var existsSync = require('./path_exists');
const async = require('async');
const Buffer = require('buffer').Buffer;
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
const settings = require('./Settings');
const existsSync = require('./path_exists');
/*
* The crypto module can be absent on reduced node installations.
@ -42,13 +42,13 @@ try {
_crypto = undefined;
}
var CACHE_DIR = path.normalize(path.join(settings.root, 'var/'));
let CACHE_DIR = path.normalize(path.join(settings.root, 'var/'));
CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
var responseCache = {};
const responseCache = {};
function djb2Hash(data) {
const chars = data.split("").map(str => str.charCodeAt(0));
const chars = data.split('').map((str) => str.charCodeAt(0));
return `${chars.reduce((prev, curr) => ((prev << 5) + prev) + curr, 5381)}`;
}
@ -81,23 +81,23 @@ function CachingMiddleware() {
}
CachingMiddleware.prototype = new function () {
function handle(req, res, next) {
if (!(req.method == "GET" || req.method == "HEAD") || !CACHE_DIR) {
if (!(req.method == 'GET' || req.method == 'HEAD') || !CACHE_DIR) {
return next(undefined, req, res);
}
var old_req = {};
var old_res = {};
const old_req = {};
const old_res = {};
var supportsGzip =
const supportsGzip =
(req.get('Accept-Encoding') || '').indexOf('gzip') != -1;
var path = require('url').parse(req.url).path;
var cacheKey = generateCacheKey(path);
const path = require('url').parse(req.url).path;
const cacheKey = generateCacheKey(path);
fs.stat(CACHE_DIR + 'minified_' + cacheKey, function (error, stats) {
var modifiedSince = (req.headers['if-modified-since']
&& new Date(req.headers['if-modified-since']));
var lastModifiedCache = !error && stats.mtime;
fs.stat(`${CACHE_DIR}minified_${cacheKey}`, (error, stats) => {
const modifiedSince = (req.headers['if-modified-since'] &&
new Date(req.headers['if-modified-since']));
const lastModifiedCache = !error && stats.mtime;
if (lastModifiedCache && responseCache[cacheKey]) {
req.headers['if-modified-since'] = lastModifiedCache.toUTCString();
} else {
@ -108,13 +108,13 @@ CachingMiddleware.prototype = new function () {
old_req.method = req.method;
req.method = 'GET';
var expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {})['expires']);
const expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {}).expires);
if (expirationDate > new Date()) {
// Our cached version is still valid.
return respond();
}
var _headers = {};
const _headers = {};
old_res.setHeader = res.setHeader;
res.setHeader = function (key, value) {
// Don't set cookies, see issue #707
@ -126,46 +126,46 @@ CachingMiddleware.prototype = new function () {
old_res.writeHead = res.writeHead;
res.writeHead = function (status, headers) {
var lastModified = (res.getHeader('last-modified')
&& new Date(res.getHeader('last-modified')));
const lastModified = (res.getHeader('last-modified') &&
new Date(res.getHeader('last-modified')));
res.writeHead = old_res.writeHead;
if (status == 200) {
// Update cache
var buffer = '';
let buffer = '';
Object.keys(headers || {}).forEach(function (key) {
Object.keys(headers || {}).forEach((key) => {
res.setHeader(key, headers[key]);
});
headers = _headers;
old_res.write = res.write;
old_res.end = res.end;
res.write = function(data, encoding) {
res.write = function (data, encoding) {
buffer += data.toString(encoding);
};
res.end = function(data, encoding) {
res.end = function (data, encoding) {
async.parallel([
function (callback) {
var path = CACHE_DIR + 'minified_' + cacheKey;
fs.writeFile(path, buffer, function (error, stats) {
const path = `${CACHE_DIR}minified_${cacheKey}`;
fs.writeFile(path, buffer, (error, stats) => {
callback();
});
}
, function (callback) {
var path = CACHE_DIR + 'minified_' + cacheKey + '.gz';
zlib.gzip(buffer, function(error, content) {
},
function (callback) {
const path = `${CACHE_DIR}minified_${cacheKey}.gz`;
zlib.gzip(buffer, (error, content) => {
if (error) {
callback();
} else {
fs.writeFile(path, content, function (error, stats) {
fs.writeFile(path, content, (error, stats) => {
callback();
});
}
});
}
], function () {
responseCache[cacheKey] = {statusCode: status, headers: headers};
},
], () => {
responseCache[cacheKey] = {statusCode: status, headers};
respond();
});
};
@ -173,8 +173,8 @@ CachingMiddleware.prototype = new function () {
// Nothing new changed from the cached version.
old_res.write = res.write;
old_res.end = res.end;
res.write = function(data, encoding) {};
res.end = function(data, encoding) { respond(); };
res.write = function (data, encoding) {};
res.end = function (data, encoding) { respond(); };
} else {
res.writeHead(status, headers);
}
@ -191,24 +191,24 @@ CachingMiddleware.prototype = new function () {
res.write = old_res.write || res.write;
res.end = old_res.end || res.end;
let headers = {};
const headers = {};
Object.assign(headers, (responseCache[cacheKey].headers || {}));
var statusCode = responseCache[cacheKey].statusCode;
const statusCode = responseCache[cacheKey].statusCode;
var pathStr = CACHE_DIR + 'minified_' + cacheKey;
let pathStr = `${CACHE_DIR}minified_${cacheKey}`;
if (supportsGzip && /application\/javascript/.test(headers['content-type'])) {
pathStr = pathStr + '.gz';
pathStr += '.gz';
headers['content-encoding'] = 'gzip';
}
var lastModified = (headers['last-modified']
&& new Date(headers['last-modified']));
const lastModified = (headers['last-modified'] &&
new Date(headers['last-modified']));
if (statusCode == 200 && lastModified <= modifiedSince) {
res.writeHead(304, headers);
res.end();
} else if (req.method == 'GET') {
var readStream = fs.createReadStream(pathStr);
const readStream = fs.createReadStream(pathStr);
res.writeHead(statusCode, headers);
readStream.pipe(res);
} else {

View file

@ -1,17 +1,17 @@
var Changeset = require("../../static/js/Changeset");
var exportHtml = require('./ExportHtml');
const Changeset = require('../../static/js/Changeset');
const exportHtml = require('./ExportHtml');
function PadDiff (pad, fromRev, toRev) {
function PadDiff(pad, fromRev, toRev) {
// check parameters
if (!pad || !pad.id || !pad.atext || !pad.pool) {
throw new Error('Invalid pad');
}
var range = pad.getValidRevisionRange(fromRev, toRev);
const range = pad.getValidRevisionRange(fromRev, toRev);
if (!range) {
throw new Error('Invalid revision range.' +
' startRev: ' + fromRev +
' endRev: ' + toRev);
throw new Error(`${'Invalid revision range.' +
' startRev: '}${fromRev
} endRev: ${toRev}`);
}
this._pad = pad;
@ -21,12 +21,12 @@ function PadDiff (pad, fromRev, toRev) {
this._authors = [];
}
PadDiff.prototype._isClearAuthorship = function(changeset) {
PadDiff.prototype._isClearAuthorship = function (changeset) {
// unpack
var unpacked = Changeset.unpack(changeset);
const unpacked = Changeset.unpack(changeset);
// check if there is nothing in the charBank
if (unpacked.charBank !== "") {
if (unpacked.charBank !== '') {
return false;
}
@ -36,10 +36,10 @@ PadDiff.prototype._isClearAuthorship = function(changeset) {
}
// lets iterator over the operators
var iterator = Changeset.opIterator(unpacked.ops);
const iterator = Changeset.opIterator(unpacked.ops);
// get the first operator, this should be a clear operator
var clearOperator = iterator.next();
const clearOperator = iterator.next();
// check if there is only one operator
if (iterator.hasNext() === true) {
@ -47,18 +47,18 @@ PadDiff.prototype._isClearAuthorship = function(changeset) {
}
// check if this operator doesn't change text
if (clearOperator.opcode !== "=") {
if (clearOperator.opcode !== '=') {
return false;
}
// check that this operator applys to the complete text
// if the text ends with a new line, its exactly one character less, else it has the same length
if (clearOperator.chars !== unpacked.oldLen-1 && clearOperator.chars !== unpacked.oldLen) {
if (clearOperator.chars !== unpacked.oldLen - 1 && clearOperator.chars !== unpacked.oldLen) {
return false;
}
var attributes = [];
Changeset.eachAttribNumber(changeset, function(attrNum) {
const attributes = [];
Changeset.eachAttribNumber(changeset, (attrNum) => {
attributes.push(attrNum);
});
@ -67,90 +67,84 @@ PadDiff.prototype._isClearAuthorship = function(changeset) {
return false;
}
var appliedAttribute = this._pad.pool.getAttrib(attributes[0]);
const appliedAttribute = this._pad.pool.getAttrib(attributes[0]);
// check if the applied attribute is an anonymous author attribute
if (appliedAttribute[0] !== "author" || appliedAttribute[1] !== "") {
if (appliedAttribute[0] !== 'author' || appliedAttribute[1] !== '') {
return false;
}
return true;
};
PadDiff.prototype._createClearAuthorship = async function(rev) {
let atext = await this._pad.getInternalRevisionAText(rev);
PadDiff.prototype._createClearAuthorship = async function (rev) {
const atext = await this._pad.getInternalRevisionAText(rev);
// build clearAuthorship changeset
var builder = Changeset.builder(atext.text.length);
builder.keepText(atext.text, [['author','']], this._pad.pool);
var changeset = builder.toString();
const builder = Changeset.builder(atext.text.length);
builder.keepText(atext.text, [['author', '']], this._pad.pool);
const changeset = builder.toString();
return changeset;
}
PadDiff.prototype._createClearStartAtext = async function(rev) {
};
PadDiff.prototype._createClearStartAtext = async function (rev) {
// get the atext of this revision
let atext = this._pad.getInternalRevisionAText(rev);
const atext = this._pad.getInternalRevisionAText(rev);
// create the clearAuthorship changeset
let changeset = await this._createClearAuthorship(rev);
const changeset = await this._createClearAuthorship(rev);
// apply the clearAuthorship changeset
let newAText = Changeset.applyToAText(changeset, atext, this._pad.pool);
const newAText = Changeset.applyToAText(changeset, atext, this._pad.pool);
return newAText;
}
PadDiff.prototype._getChangesetsInBulk = async function(startRev, count) {
};
PadDiff.prototype._getChangesetsInBulk = async function (startRev, count) {
// find out which revisions we need
let revisions = [];
const revisions = [];
for (let i = startRev; i < (startRev + count) && i <= this._pad.head; i++) {
revisions.push(i);
}
// get all needed revisions (in parallel)
let changesets = [], authors = [];
await Promise.all(revisions.map(rev => {
return this._pad.getRevision(rev).then(revision => {
let arrayNum = rev - startRev;
changesets[arrayNum] = revision.changeset;
authors[arrayNum] = revision.meta.author;
});
}));
const changesets = []; const
authors = [];
await Promise.all(revisions.map((rev) => this._pad.getRevision(rev).then((revision) => {
const arrayNum = rev - startRev;
changesets[arrayNum] = revision.changeset;
authors[arrayNum] = revision.meta.author;
})));
return { changesets, authors };
}
return {changesets, authors};
};
PadDiff.prototype._addAuthors = function(authors) {
var self = this;
PadDiff.prototype._addAuthors = function (authors) {
const self = this;
// add to array if not in the array
authors.forEach(function(author) {
authors.forEach((author) => {
if (self._authors.indexOf(author) == -1) {
self._authors.push(author);
}
});
};
PadDiff.prototype._createDiffAtext = async function() {
let bulkSize = 100;
PadDiff.prototype._createDiffAtext = async function () {
const bulkSize = 100;
// get the cleaned startAText
let atext = await this._createClearStartAtext(this._fromRev);
let superChangeset = null;
let rev = this._fromRev + 1;
const rev = this._fromRev + 1;
for (let rev = this._fromRev + 1; rev <= this._toRev; rev += bulkSize) {
// get the bulk
let { changesets, authors } = await this._getChangesetsInBulk(rev, bulkSize);
const {changesets, authors} = await this._getChangesetsInBulk(rev, bulkSize);
let addedAuthors = [];
const addedAuthors = [];
// run through all changesets
for (let i = 0; i < changesets.length && (rev + i) <= this._toRev; ++i) {
@ -180,7 +174,7 @@ PadDiff.prototype._createDiffAtext = async function() {
// if there are only clearAuthorship changesets, we don't get a superChangeset, so we can skip this step
if (superChangeset) {
let deletionChangeset = this._createDeletionChangeset(superChangeset, atext, this._pad.pool);
const deletionChangeset = this._createDeletionChangeset(superChangeset, atext, this._pad.pool);
// apply the superChangeset, which includes all addings
atext = Changeset.applyToAText(superChangeset, atext, this._pad.pool);
@ -190,59 +184,57 @@ PadDiff.prototype._createDiffAtext = async function() {
}
return atext;
}
PadDiff.prototype.getHtml = async function() {
};
PadDiff.prototype.getHtml = async function () {
// cache the html
if (this._html != null) {
return this._html;
}
// get the diff atext
let atext = await this._createDiffAtext();
const atext = await this._createDiffAtext();
// get the authorColor table
let authorColors = await this._pad.getAllAuthorColors();
const authorColors = await this._pad.getAllAuthorColors();
// convert the atext to html
this._html = await exportHtml.getHTMLFromAtext(this._pad, atext, authorColors);
return this._html;
}
PadDiff.prototype.getAuthors = async function() {
};
PadDiff.prototype.getAuthors = async function () {
// check if html was already produced, if not produce it, this generates the author array at the same time
if (this._html == null) {
await this.getHtml();
}
return self._authors;
}
};
PadDiff.prototype._extendChangesetWithAuthor = function(changeset, author, apool) {
PadDiff.prototype._extendChangesetWithAuthor = function (changeset, author, apool) {
// unpack
var unpacked = Changeset.unpack(changeset);
const unpacked = Changeset.unpack(changeset);
var iterator = Changeset.opIterator(unpacked.ops);
var assem = Changeset.opAssembler();
const iterator = Changeset.opIterator(unpacked.ops);
const assem = Changeset.opAssembler();
// create deleted attribs
var authorAttrib = apool.putAttrib(["author", author || ""]);
var deletedAttrib = apool.putAttrib(["removed", true]);
var attribs = "*" + Changeset.numToString(authorAttrib) + "*" + Changeset.numToString(deletedAttrib);
const authorAttrib = apool.putAttrib(['author', author || '']);
const deletedAttrib = apool.putAttrib(['removed', true]);
const attribs = `*${Changeset.numToString(authorAttrib)}*${Changeset.numToString(deletedAttrib)}`;
// iteratore over the operators of the changeset
while(iterator.hasNext()) {
var operator = iterator.next();
while (iterator.hasNext()) {
const operator = iterator.next();
if (operator.opcode === "-") {
if (operator.opcode === '-') {
// this is a delete operator, extend it with the author
operator.attribs = attribs;
} else if (operator.opcode === "=" && operator.attribs) {
} else if (operator.opcode === '=' && operator.attribs) {
// this is operator changes only attributes, let's mark which author did that
operator.attribs+="*"+Changeset.numToString(authorAttrib);
operator.attribs += `*${Changeset.numToString(authorAttrib)}`;
}
// append the new operator to our assembler
@ -254,9 +246,9 @@ PadDiff.prototype._extendChangesetWithAuthor = function(changeset, author, apool
};
// this method is 80% like Changeset.inverse. I just changed so instead of reverting, it adds deletions and attribute changes to to the atext.
PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
var lines = Changeset.splitTextLines(startAText.text);
var alines = Changeset.splitAttributionLines(startAText.attribs, startAText.text);
PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
const lines = Changeset.splitTextLines(startAText.text);
const alines = Changeset.splitAttributionLines(startAText.attribs, startAText.text);
// lines and alines are what the exports is meant to apply to.
// They may be arrays or objects with .get(i) and .length methods.
@ -278,24 +270,23 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
}
}
var curLine = 0;
var curChar = 0;
var curLineOpIter = null;
var curLineOpIterLine;
var curLineNextOp = Changeset.newOp('+');
let curLine = 0;
let curChar = 0;
let curLineOpIter = null;
let curLineOpIterLine;
const curLineNextOp = Changeset.newOp('+');
var unpacked = Changeset.unpack(cs);
var csIter = Changeset.opIterator(unpacked.ops);
var builder = Changeset.builder(unpacked.newLen);
function consumeAttribRuns(numChars, func /*(len, attribs, endsLine)*/ ) {
const unpacked = Changeset.unpack(cs);
const csIter = Changeset.opIterator(unpacked.ops);
const builder = Changeset.builder(unpacked.newLen);
function consumeAttribRuns(numChars, func /* (len, attribs, endsLine)*/) {
if ((!curLineOpIter) || (curLineOpIterLine != curLine)) {
// create curLineOpIter and advance it to curChar
curLineOpIter = Changeset.opIterator(alines_get(curLine));
curLineOpIterLine = curLine;
var indexIntoLine = 0;
var done = false;
let indexIntoLine = 0;
let done = false;
while (!done) {
curLineOpIter.next(curLineNextOp);
if (indexIntoLine + curLineNextOp.chars >= curChar) {
@ -320,7 +311,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
curLineOpIter.next(curLineNextOp);
}
var charsToUse = Math.min(numChars, curLineNextOp.chars);
const charsToUse = Math.min(numChars, curLineNextOp.chars);
func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0);
numChars -= charsToUse;
@ -338,26 +329,24 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
if (L) {
curLine += L;
curChar = 0;
} else if (curLineOpIter && curLineOpIterLine == curLine) {
consumeAttribRuns(N, () => {});
} else {
if (curLineOpIter && curLineOpIterLine == curLine) {
consumeAttribRuns(N, function () {});
} else {
curChar += N;
}
curChar += N;
}
}
function nextText(numChars) {
var len = 0;
var assem = Changeset.stringAssembler();
var firstString = lines_get(curLine).substring(curChar);
let len = 0;
const assem = Changeset.stringAssembler();
const firstString = lines_get(curLine).substring(curChar);
len += firstString.length;
assem.append(firstString);
var lineNum = curLine + 1;
let lineNum = curLine + 1;
while (len < numChars) {
var nextString = lines_get(lineNum);
const nextString = lines_get(lineNum);
len += nextString.length;
assem.append(nextString);
lineNum++;
@ -367,7 +356,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
}
function cachedStrFunc(func) {
var cache = {};
const cache = {};
return function (s) {
if (!cache[s]) {
@ -377,8 +366,8 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
};
}
var attribKeys = [];
var attribValues = [];
const attribKeys = [];
const attribValues = [];
// iterate over all operators of this changeset
while (csIter.hasNext()) {
@ -389,27 +378,27 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
// decide if this equal operator is an attribution change or not. We can see this by checkinf if attribs is set.
// If the text this operator applies to is only a star, than this is a false positive and should be ignored
if (csOp.attribs && textBank != "*") {
var deletedAttrib = apool.putAttrib(["removed", true]);
var authorAttrib = apool.putAttrib(["author", ""]);
if (csOp.attribs && textBank != '*') {
const deletedAttrib = apool.putAttrib(['removed', true]);
var authorAttrib = apool.putAttrib(['author', '']);
attribKeys.length = 0;
attribValues.length = 0;
Changeset.eachAttribNumber(csOp.attribs, function (n) {
Changeset.eachAttribNumber(csOp.attribs, (n) => {
attribKeys.push(apool.getAttribKey(n));
attribValues.push(apool.getAttribValue(n));
if (apool.getAttribKey(n) === "author") {
if (apool.getAttribKey(n) === 'author') {
authorAttrib = n;
}
});
var undoBackToAttribs = cachedStrFunc(function (attribs) {
var backAttribs = [];
for (var i = 0; i < attribKeys.length; i++) {
var appliedKey = attribKeys[i];
var appliedValue = attribValues[i];
var oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, apool);
var undoBackToAttribs = cachedStrFunc((attribs) => {
const backAttribs = [];
for (let i = 0; i < attribKeys.length; i++) {
const appliedKey = attribKeys[i];
const appliedValue = attribValues[i];
const oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, apool);
if (appliedValue != oldValue) {
backAttribs.push([appliedKey, oldValue]);
@ -419,21 +408,21 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
return Changeset.makeAttribsString('=', backAttribs, apool);
});
var oldAttribsAddition = "*" + Changeset.numToString(deletedAttrib) + "*" + Changeset.numToString(authorAttrib);
var oldAttribsAddition = `*${Changeset.numToString(deletedAttrib)}*${Changeset.numToString(authorAttrib)}`;
var textLeftToProcess = textBank;
let textLeftToProcess = textBank;
while(textLeftToProcess.length > 0) {
while (textLeftToProcess.length > 0) {
// process till the next line break or process only one line break
var lengthToProcess = textLeftToProcess.indexOf("\n");
var lineBreak = false;
switch(lengthToProcess) {
let lengthToProcess = textLeftToProcess.indexOf('\n');
let lineBreak = false;
switch (lengthToProcess) {
case -1:
lengthToProcess=textLeftToProcess.length;
lengthToProcess = textLeftToProcess.length;
break;
case 0:
lineBreak = true;
lengthToProcess=1;
lengthToProcess = 1;
break;
}
@ -446,13 +435,13 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
builder.keep(1, 1); // just skip linebreaks, don't do a insert + keep for a linebreak
// consume the attributes of this linebreak
consumeAttribRuns(1, function() {});
consumeAttribRuns(1, () => {});
} else {
// add the old text via an insert, but add a deletion attribute + the author attribute of the author who deleted it
var textBankIndex = 0;
consumeAttribRuns(lengthToProcess, function (len, attribs, endsLine) {
consumeAttribRuns(lengthToProcess, (len, attribs, endsLine) => {
// get the old attributes back
var attribs = (undoBackToAttribs(attribs) || "") + oldAttribsAddition;
var attribs = (undoBackToAttribs(attribs) || '') + oldAttribsAddition;
builder.insert(processText.substr(textBankIndex, len), attribs);
textBankIndex += len;
@ -471,7 +460,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
var textBank = nextText(csOp.chars);
var textBankIndex = 0;
consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) {
consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => {
builder.insert(textBank.substr(textBankIndex, len), attribs + csOp.attribs);
textBankIndex += len;
});

View file

@ -1,15 +1,15 @@
var fs = require('fs');
const fs = require('fs');
var check = function(path) {
var existsSync = fs.statSync || fs.existsSync || path.existsSync;
const check = function (path) {
const existsSync = fs.statSync || fs.existsSync || path.existsSync;
var result;
let result;
try {
result = existsSync(path);
} catch (e) {
result = false;
}
return result;
}
};
module.exports = check;

View file

@ -13,7 +13,7 @@ exports.firstSatisfies = (promises, predicate) => {
// value does not satisfy `predicate`. These transformed Promises will be passed to Promise.race,
// yielding the first resolved value that satisfies `predicate`.
const newPromises = promises.map(
(p) => new Promise((resolve, reject) => p.then((v) => predicate(v) && resolve(v), reject)));
(p) => new Promise((resolve, reject) => p.then((v) => predicate(v) && resolve(v), reject)));
// If `promises` is an empty array or if none of them resolve to a value that satisfies
// `predicate`, then `Promise.race(newPromises)` will never resolve. To handle that, add another
@ -48,8 +48,8 @@ exports.timesLimit = async (total, concurrency, promiseCreator) => {
if (next < total) return addAnother();
});
const promises = [];
for (var i = 0; i < concurrency && i < total; i++) {
for (let i = 0; i < concurrency && i < total; i++) {
promises.push(addAnother());
}
await Promise.all(promises);
}
};

View file

@ -1,10 +1,10 @@
/**
* Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
*/
var crypto = require('crypto');
const crypto = require('crypto');
var randomString = function(len) {
return crypto.randomBytes(len).toString('hex')
const randomString = function (len) {
return crypto.randomBytes(len).toString('hex');
};
module.exports = randomString;

View file

@ -1,53 +1,50 @@
/**
* The Toolbar Module creates and renders the toolbars and buttons
*/
var _ = require("underscore")
, tagAttributes
, tag
, Button
, ButtonsGroup
, Separator
, defaultButtonAttributes
, removeItem;
const _ = require('underscore');
let tagAttributes;
let tag;
let Button;
let ButtonsGroup;
let Separator;
let defaultButtonAttributes;
let removeItem;
removeItem = function(array,what) {
var ax;
removeItem = function (array, what) {
let ax;
while ((ax = array.indexOf(what)) !== -1) {
array.splice(ax, 1);
}
return array;
return array;
};
defaultButtonAttributes = function (name, overrides) {
return {
command: name,
localizationId: "pad.toolbar." + name + ".title",
class: "buttonicon buttonicon-" + name
localizationId: `pad.toolbar.${name}.title`,
class: `buttonicon buttonicon-${name}`,
};
};
tag = function (name, attributes, contents) {
var aStr = tagAttributes(attributes);
const aStr = tagAttributes(attributes);
if (_.isString(contents) && contents.length > 0) {
return '<' + name + aStr + '>' + contents + '</' + name + '>';
}
else {
return '<' + name + aStr + '></' + name + '>';
return `<${name}${aStr}>${contents}</${name}>`;
} else {
return `<${name}${aStr}></${name}>`;
}
};
tagAttributes = function (attributes) {
attributes = _.reduce(attributes || {}, function (o, val, name) {
attributes = _.reduce(attributes || {}, (o, val, name) => {
if (!_.isUndefined(val)) {
o[name] = val;
}
return o;
}, {});
return " " + _.map(attributes, function (val, name) {
return "" + name + '="' + _.escape(val) + '"';
}).join(" ");
return ` ${_.map(attributes, (val, name) => `${name}="${_.escape(val)}"`).join(' ')}`;
};
ButtonsGroup = function () {
@ -55,8 +52,8 @@ ButtonsGroup = function () {
};
ButtonsGroup.fromArray = function (array) {
var btnGroup = new this;
_.each(array, function (btnName) {
const btnGroup = new this();
_.each(array, (btnName) => {
btnGroup.addButton(Button.load(btnName));
});
return btnGroup;
@ -69,19 +66,18 @@ ButtonsGroup.prototype.addButton = function (button) {
ButtonsGroup.prototype.render = function () {
if (this.buttons && this.buttons.length == 1) {
this.buttons[0].grouping = "";
}
else {
_.first(this.buttons).grouping = "grouped-left";
_.last(this.buttons).grouping = "grouped-right";
_.each(this.buttons.slice(1, -1), function (btn) {
btn.grouping = "grouped-middle"
this.buttons[0].grouping = '';
} else {
_.first(this.buttons).grouping = 'grouped-left';
_.last(this.buttons).grouping = 'grouped-right';
_.each(this.buttons.slice(1, -1), (btn) => {
btn.grouping = 'grouped-middle';
});
}
return _.map(this.buttons, function (btn) {
if(btn) return btn.render();
}).join("\n");
return _.map(this.buttons, (btn) => {
if (btn) return btn.render();
}).join('\n');
};
Button = function (attributes) {
@ -89,165 +85,163 @@ Button = function (attributes) {
};
Button.load = function (btnName) {
var button = module.exports.availableButtons[btnName];
try{
const button = module.exports.availableButtons[btnName];
try {
if (button.constructor === Button || button.constructor === SelectButton) {
return button;
}
else {
} else {
return new Button(button);
}
}catch(e){
console.warn("Error loading button", btnName);
} catch (e) {
console.warn('Error loading button', btnName);
return false;
}
};
_.extend(Button.prototype, {
grouping: "",
grouping: '',
render: function () {
var liAttributes = {
"data-type": "button",
"data-key": this.attributes.command,
render() {
const liAttributes = {
'data-type': 'button',
'data-key': this.attributes.command,
};
return tag("li", liAttributes,
tag("a", { "class": this.grouping, "data-l10n-id": this.attributes.localizationId },
tag("button", { "class": " "+ this.attributes.class, "data-l10n-id": this.attributes.localizationId })
)
return tag('li', liAttributes,
tag('a', {'class': this.grouping, 'data-l10n-id': this.attributes.localizationId},
tag('button', {'class': ` ${this.attributes.class}`, 'data-l10n-id': this.attributes.localizationId}),
),
);
}
},
});
var SelectButton = function (attributes) {
this.attributes = attributes;
this.options = [];
};
_.extend(SelectButton.prototype, Button.prototype, {
addOption: function (value, text, attributes) {
addOption(value, text, attributes) {
this.options.push({
value: value,
text: text,
attributes: attributes
value,
text,
attributes,
});
return this;
},
select: function (attributes) {
var options = [];
select(attributes) {
const options = [];
_.each(this.options, function (opt) {
var a = _.extend({
value: opt.value
_.each(this.options, (opt) => {
const a = _.extend({
value: opt.value,
}, opt.attributes);
options.push( tag("option", a, opt.text) );
options.push(tag('option', a, opt.text));
});
return tag("select", attributes, options.join(""));
return tag('select', attributes, options.join(''));
},
render: function () {
var attributes = {
id: this.attributes.id,
"data-key": this.attributes.command,
"data-type": "select"
render() {
const attributes = {
'id': this.attributes.id,
'data-key': this.attributes.command,
'data-type': 'select',
};
return tag("li", attributes,
this.select({ id: this.attributes.selectId })
return tag('li', attributes,
this.select({id: this.attributes.selectId}),
);
}
},
});
Separator = function () {};
Separator.prototype.render = function () {
return tag("li", { "class": "separator" });
return tag('li', {class: 'separator'});
};
module.exports = {
availableButtons: {
bold: defaultButtonAttributes("bold"),
italic: defaultButtonAttributes("italic"),
underline: defaultButtonAttributes("underline"),
strikethrough: defaultButtonAttributes("strikethrough"),
bold: defaultButtonAttributes('bold'),
italic: defaultButtonAttributes('italic'),
underline: defaultButtonAttributes('underline'),
strikethrough: defaultButtonAttributes('strikethrough'),
orderedlist: {
command: "insertorderedlist",
localizationId: "pad.toolbar.ol.title",
class: "buttonicon buttonicon-insertorderedlist"
command: 'insertorderedlist',
localizationId: 'pad.toolbar.ol.title',
class: 'buttonicon buttonicon-insertorderedlist',
},
unorderedlist: {
command: "insertunorderedlist",
localizationId: "pad.toolbar.ul.title",
class: "buttonicon buttonicon-insertunorderedlist"
command: 'insertunorderedlist',
localizationId: 'pad.toolbar.ul.title',
class: 'buttonicon buttonicon-insertunorderedlist',
},
indent: defaultButtonAttributes("indent"),
indent: defaultButtonAttributes('indent'),
outdent: {
command: "outdent",
localizationId: "pad.toolbar.unindent.title",
class: "buttonicon buttonicon-outdent"
command: 'outdent',
localizationId: 'pad.toolbar.unindent.title',
class: 'buttonicon buttonicon-outdent',
},
undo: defaultButtonAttributes("undo"),
redo: defaultButtonAttributes("redo"),
undo: defaultButtonAttributes('undo'),
redo: defaultButtonAttributes('redo'),
clearauthorship: {
command: "clearauthorship",
localizationId: "pad.toolbar.clearAuthorship.title",
class: "buttonicon buttonicon-clearauthorship"
command: 'clearauthorship',
localizationId: 'pad.toolbar.clearAuthorship.title',
class: 'buttonicon buttonicon-clearauthorship',
},
importexport: {
command: "import_export",
localizationId: "pad.toolbar.import_export.title",
class: "buttonicon buttonicon-import_export"
command: 'import_export',
localizationId: 'pad.toolbar.import_export.title',
class: 'buttonicon buttonicon-import_export',
},
timeslider: {
command: "showTimeSlider",
localizationId: "pad.toolbar.timeslider.title",
class: "buttonicon buttonicon-history"
command: 'showTimeSlider',
localizationId: 'pad.toolbar.timeslider.title',
class: 'buttonicon buttonicon-history',
},
savedrevision: defaultButtonAttributes("savedRevision"),
settings: defaultButtonAttributes("settings"),
embed: defaultButtonAttributes("embed"),
showusers: defaultButtonAttributes("showusers"),
savedrevision: defaultButtonAttributes('savedRevision'),
settings: defaultButtonAttributes('settings'),
embed: defaultButtonAttributes('embed'),
showusers: defaultButtonAttributes('showusers'),
timeslider_export: {
command: "import_export",
localizationId: "timeslider.toolbar.exportlink.title",
class: "buttonicon buttonicon-import_export"
command: 'import_export',
localizationId: 'timeslider.toolbar.exportlink.title',
class: 'buttonicon buttonicon-import_export',
},
timeslider_settings: {
command: "settings",
localizationId: "pad.toolbar.settings.title",
class: "buttonicon buttonicon-settings"
command: 'settings',
localizationId: 'pad.toolbar.settings.title',
class: 'buttonicon buttonicon-settings',
},
timeslider_returnToPad: {
command: "timeslider_returnToPad",
localizationId: "timeslider.toolbar.returnbutton",
class: "buttontext"
}
command: 'timeslider_returnToPad',
localizationId: 'timeslider.toolbar.returnbutton',
class: 'buttontext',
},
},
registerButton: function (buttonName, buttonInfo) {
registerButton(buttonName, buttonInfo) {
this.availableButtons[buttonName] = buttonInfo;
},
button: function (attributes) {
button(attributes) {
return new Button(attributes);
},
separator: function () {
return (new Separator).render();
separator() {
return (new Separator()).render();
},
selectButton: function (attributes) {
selectButton(attributes) {
return new SelectButton(attributes);
},
@ -255,15 +249,15 @@ module.exports = {
* Valid values for whichMenu: 'left' | 'right' | 'timeslider-right'
* Valid values for page: 'pad' | 'timeslider'
*/
menu: function (buttons, isReadOnly, whichMenu, page) {
menu(buttons, isReadOnly, whichMenu, page) {
if (isReadOnly) {
// The best way to detect if it's the left editbar is to check for a bold button
if (buttons[0].indexOf("bold") !== -1) {
if (buttons[0].indexOf('bold') !== -1) {
// Clear all formatting buttons
buttons = [];
} else {
// Remove Save Revision from the right menu
removeItem(buttons[0],"savedrevision");
removeItem(buttons[0], 'savedrevision');
}
} else {
/*
@ -277,14 +271,12 @@ module.exports = {
* sufficient to visit a single read only pad to cause the disappearence
* of the star button from all the pads.
*/
if ((buttons[0].indexOf("savedrevision") === -1) && (whichMenu === "right") && (page === "pad")) {
buttons[0].push("savedrevision");
if ((buttons[0].indexOf('savedrevision') === -1) && (whichMenu === 'right') && (page === 'pad')) {
buttons[0].push('savedrevision');
}
}
var groups = _.map(buttons, function (group) {
return ButtonsGroup.fromArray(group).render();
});
const groups = _.map(buttons, (group) => ButtonsGroup.fromArray(group).render());
return groups.join(this.separator());
}
},
};

View file

@ -1,18 +1,18 @@
var Changeset = require('./Changeset');
var ChangesetUtils = require('./ChangesetUtils');
var _ = require('./underscore');
const Changeset = require('./Changeset');
const ChangesetUtils = require('./ChangesetUtils');
const _ = require('./underscore');
var lineMarkerAttribute = 'lmkr';
const lineMarkerAttribute = 'lmkr';
// Some of these attributes are kept for compatibility purposes.
// Not sure if we need all of them
var DEFAULT_LINE_ATTRIBUTES = ['author', 'lmkr', 'insertorder', 'start'];
const DEFAULT_LINE_ATTRIBUTES = ['author', 'lmkr', 'insertorder', 'start'];
// If one of these attributes are set to the first character of a
// line it is considered as a line attribute marker i.e. attributes
// set on this marker are applied to the whole line.
// The list attribute is only maintained for compatibility reasons
var lineAttributes = [lineMarkerAttribute,'list'];
const lineAttributes = [lineMarkerAttribute, 'list'];
/*
The Attribute manager builds changesets based on a document
@ -29,7 +29,7 @@ var lineAttributes = [lineMarkerAttribute,'list'];
- a SkipList `lines` containing the text lines of the document.
*/
var AttributeManager = function(rep, applyChangesetCallback) {
const AttributeManager = function (rep, applyChangesetCallback) {
this.rep = rep;
this.applyChangesetCallback = applyChangesetCallback;
this.author = '';
@ -43,12 +43,11 @@ AttributeManager.lineAttributes = lineAttributes;
AttributeManager.prototype = _(AttributeManager.prototype).extend({
applyChangeset: function(changeset){
if(!this.applyChangesetCallback) return changeset;
applyChangeset(changeset) {
if (!this.applyChangesetCallback) return changeset;
var cs = changeset.toString();
if (!Changeset.isIdentity(cs))
{
const cs = changeset.toString();
if (!Changeset.isIdentity(cs)) {
this.applyChangesetCallback(cs);
}
@ -61,17 +60,17 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
@param end [row, col] tuple pointing to the end of the range
@param attribs: an array of attributes
*/
setAttributesOnRange: function(start, end, attribs) {
setAttributesOnRange(start, end, attribs) {
// instead of applying the attributes to the whole range at once, we need to apply them
// line by line, to be able to disregard the "*" used as line marker. For more details,
// see https://github.com/ether/etherpad-lite/issues/2772
var allChangesets;
for(var row = start[0]; row <= end[0]; row++) {
var rowRange = this._findRowRange(row, start, end);
var startCol = rowRange[0];
var endCol = rowRange[1];
let allChangesets;
for (let row = start[0]; row <= end[0]; row++) {
const rowRange = this._findRowRange(row, start, end);
const startCol = rowRange[0];
const endCol = rowRange[1];
var rowChangeset = this._setAttributesOnRangeByLine(row, startCol, endCol, attribs);
const rowChangeset = this._setAttributesOnRangeByLine(row, startCol, endCol, attribs);
// compose changesets of all rows into a single changeset, as the range might not be continuous
// due to the presence of line markers on the rows
@ -85,12 +84,12 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
return this.applyChangeset(allChangesets);
},
_findRowRange: function(row, start, end) {
var startCol, endCol;
_findRowRange(row, start, end) {
let startCol, endCol;
var startLineOffset = this.rep.lines.offsetOfIndex(row);
var endLineOffset = this.rep.lines.offsetOfIndex(row+1);
var lineLength = endLineOffset - startLineOffset;
const startLineOffset = this.rep.lines.offsetOfIndex(row);
const endLineOffset = this.rep.lines.offsetOfIndex(row + 1);
const lineLength = endLineOffset - startLineOffset;
// find column where range on this row starts
if (row === start[0]) { // are we on the first row of range?
@ -116,8 +115,8 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
@param endCol column where range ends
@param attribs: an array of attributes
*/
_setAttributesOnRangeByLine: function(row, startCol, endCol, attribs) {
var builder = Changeset.builder(this.rep.lines.totalWidth());
_setAttributesOnRangeByLine(row, startCol, endCol, attribs) {
const builder = Changeset.builder(this.rep.lines.totalWidth());
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [row, startCol]);
ChangesetUtils.buildKeepRange(this.rep, builder, [row, startCol], [row, endCol], attribs, this.rep.apool);
return builder;
@ -127,12 +126,10 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
Returns if the line already has a line marker
@param lineNum: the number of the line
*/
lineHasMarker: function(lineNum){
var that = this;
lineHasMarker(lineNum) {
const that = this;
return _.find(lineAttributes, function(attribute){
return that.getAttributeOnLine(lineNum, attribute) != '';
}) !== undefined;
return _.find(lineAttributes, (attribute) => that.getAttributeOnLine(lineNum, attribute) != '') !== undefined;
},
/*
@ -140,14 +137,12 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
@param lineNum: the number of the line to set the attribute for
@param attributeKey: the name of the attribute to get, e.g. list
*/
getAttributeOnLine: function(lineNum, attributeName){
getAttributeOnLine(lineNum, attributeName) {
// get `attributeName` attribute of first char of line
var aline = this.rep.alines[lineNum];
if (aline)
{
var opIter = Changeset.opIterator(aline);
if (opIter.hasNext())
{
const aline = this.rep.alines[lineNum];
if (aline) {
const opIter = Changeset.opIterator(aline);
if (opIter.hasNext()) {
return Changeset.opAttributeValue(opIter.next(), attributeName, this.rep.apool) || '';
}
}
@ -158,96 +153,94 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
Gets all attributes on a line
@param lineNum: the number of the line to get the attribute for
*/
getAttributesOnLine: function(lineNum){
getAttributesOnLine(lineNum) {
// get attributes of first char of line
var aline = this.rep.alines[lineNum];
var attributes = []
if (aline)
{
var opIter = Changeset.opIterator(aline)
, op
if (opIter.hasNext())
{
op = opIter.next()
if(!op.attribs) return []
const aline = this.rep.alines[lineNum];
const attributes = [];
if (aline) {
const opIter = Changeset.opIterator(aline);
let op;
if (opIter.hasNext()) {
op = opIter.next();
if (!op.attribs) return [];
Changeset.eachAttribNumber(op.attribs, function(n) {
attributes.push([this.rep.apool.getAttribKey(n), this.rep.apool.getAttribValue(n)])
}.bind(this))
Changeset.eachAttribNumber(op.attribs, (n) => {
attributes.push([this.rep.apool.getAttribKey(n), this.rep.apool.getAttribValue(n)]);
});
return attributes;
}
}
return [];
},
/*
/*
Gets a given attribute on a selection
@param attributeName
@param prevChar
returns true or false if an attribute is visible in range
*/
getAttributeOnSelection: function(attributeName, prevChar){
var rep = this.rep;
if (!(rep.selStart && rep.selEnd)) return
getAttributeOnSelection(attributeName, prevChar) {
const rep = this.rep;
if (!(rep.selStart && rep.selEnd)) return;
// If we're looking for the caret attribute not the selection
// has the user already got a selection or is this purely a caret location?
var isNotSelection = (rep.selStart[0] == rep.selEnd[0] && rep.selEnd[1] === rep.selStart[1]);
if(isNotSelection){
if(prevChar){
const isNotSelection = (rep.selStart[0] == rep.selEnd[0] && rep.selEnd[1] === rep.selStart[1]);
if (isNotSelection) {
if (prevChar) {
// If it's not the start of the line
if(rep.selStart[1] !== 0){
if (rep.selStart[1] !== 0) {
rep.selStart[1]--;
}
}
}
var withIt = Changeset.makeAttribsString('+', [
[attributeName, 'true']
const withIt = Changeset.makeAttribsString('+', [
[attributeName, 'true'],
], rep.apool);
var withItRegex = new RegExp(withIt.replace(/\*/g, '\\*') + "(\\*|$)");
const withItRegex = new RegExp(`${withIt.replace(/\*/g, '\\*')}(\\*|$)`);
function hasIt(attribs) {
return withItRegex.test(attribs);
}
return rangeHasAttrib(rep.selStart, rep.selEnd)
return rangeHasAttrib(rep.selStart, rep.selEnd);
function rangeHasAttrib(selStart, selEnd) {
// if range is collapsed -> no attribs in range
if(selStart[1] == selEnd[1] && selStart[0] == selEnd[0]) return false
if (selStart[1] == selEnd[1] && selStart[0] == selEnd[0]) return false;
if(selStart[0] != selEnd[0]) { // -> More than one line selected
var hasAttrib = true
if (selStart[0] != selEnd[0]) { // -> More than one line selected
var hasAttrib = true;
// from selStart to the end of the first line
hasAttrib = hasAttrib && rangeHasAttrib(selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length])
hasAttrib = hasAttrib && rangeHasAttrib(selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length]);
// for all lines in between
for(var n=selStart[0]+1; n < selEnd[0]; n++) {
hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length])
for (let n = selStart[0] + 1; n < selEnd[0]; n++) {
hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length]);
}
// for the last, potentially partial, line
hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]])
hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]]);
return hasAttrib
return hasAttrib;
}
// Logic tells us we now have a range on a single line
var lineNum = selStart[0]
, start = selStart[1]
, end = selEnd[1]
, hasAttrib = true
const lineNum = selStart[0];
const start = selStart[1];
const end = selEnd[1];
var hasAttrib = true;
// Iterate over attribs on this line
var opIter = Changeset.opIterator(rep.alines[lineNum])
, indexIntoLine = 0
const opIter = Changeset.opIterator(rep.alines[lineNum]);
let indexIntoLine = 0;
while (opIter.hasNext()) {
var op = opIter.next();
var opStartInLine = indexIntoLine;
var opEndInLine = opStartInLine + op.chars;
const op = opIter.next();
const opStartInLine = indexIntoLine;
const opEndInLine = opStartInLine + op.chars;
if (!hasIt(op.attribs)) {
// does op overlap selection?
if (!(opEndInLine <= start || opStartInLine >= end)) {
@ -258,7 +251,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
indexIntoLine = opEndInLine;
}
return hasAttrib
return hasAttrib;
}
},
@ -269,40 +262,39 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
returns a list of attributes in the format
[ ["key","value"], ["key","value"], ... ]
*/
getAttributesOnPosition: function(lineNumber, column){
getAttributesOnPosition(lineNumber, column) {
// get all attributes of the line
var aline = this.rep.alines[lineNumber];
const aline = this.rep.alines[lineNumber];
if (!aline) {
return [];
return [];
}
// iterate through all operations of a line
var opIter = Changeset.opIterator(aline);
const opIter = Changeset.opIterator(aline);
// we need to sum up how much characters each operations take until the wanted position
var currentPointer = 0;
var attributes = [];
var currentOperation;
let currentPointer = 0;
const attributes = [];
let currentOperation;
while (opIter.hasNext()) {
currentOperation = opIter.next();
currentPointer = currentPointer + currentOperation.chars;
currentPointer += currentOperation.chars;
if (currentPointer > column) {
// we got the operation of the wanted position, now collect all its attributes
Changeset.eachAttribNumber(currentOperation.attribs, function (n) {
Changeset.eachAttribNumber(currentOperation.attribs, (n) => {
attributes.push([
this.rep.apool.getAttribKey(n),
this.rep.apool.getAttribValue(n)
this.rep.apool.getAttribValue(n),
]);
}.bind(this));
});
// skip the loop
return attributes;
}
}
return attributes;
},
/*
@ -311,7 +303,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
returns a list of attributes in the format
[ ["key","value"], ["key","value"], ... ]
*/
getAttributesOnCaret: function(){
getAttributesOnCaret() {
return this.getAttributesOnPosition(this.rep.selStart[0], this.rep.selStart[1]);
},
@ -322,72 +314,72 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
@param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
*/
setAttributeOnLine: function(lineNum, attributeName, attributeValue){
var loc = [0,0];
var builder = Changeset.builder(this.rep.lines.totalWidth());
var hasMarker = this.lineHasMarker(lineNum);
setAttributeOnLine(lineNum, attributeName, attributeValue) {
let loc = [0, 0];
const builder = Changeset.builder(this.rep.lines.totalWidth());
const hasMarker = this.lineHasMarker(lineNum);
ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
if(hasMarker){
if (hasMarker) {
ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), [
[attributeName, attributeValue]
[attributeName, attributeValue],
], this.rep.apool);
} else {
// add a line marker
builder.insert('*', [
['author', this.author],
['insertorder', 'first'],
[lineMarkerAttribute, '1'],
[attributeName, attributeValue],
], this.rep.apool);
}else{
// add a line marker
builder.insert('*', [
['author', this.author],
['insertorder', 'first'],
[lineMarkerAttribute, '1'],
[attributeName, attributeValue]
], this.rep.apool);
}
return this.applyChangeset(builder);
},
/**
/**
* Removes a specified attribute on a line
* @param lineNum the number of the affected line
* @param attributeName the name of the attribute to remove, e.g. list
* @param attributeValue if given only attributes with equal value will be removed
*/
removeAttributeOnLine: function(lineNum, attributeName, attributeValue){
var builder = Changeset.builder(this.rep.lines.totalWidth());
var hasMarker = this.lineHasMarker(lineNum);
var found = false;
removeAttributeOnLine(lineNum, attributeName, attributeValue) {
const builder = Changeset.builder(this.rep.lines.totalWidth());
const hasMarker = this.lineHasMarker(lineNum);
let found = false;
var attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) {
if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)){
found = true;
return [attributeName, ''];
}else if (attrib[0] === 'author'){
// update last author to make changes to line attributes on this line
return [attributeName, this.author];
}
return attrib;
});
const attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) {
if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)) {
found = true;
return [attributeName, ''];
} else if (attrib[0] === 'author') {
// update last author to make changes to line attributes on this line
return [attributeName, this.author];
}
return attrib;
});
if (!found) {
return;
}
if (!found) {
return;
}
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]);
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]);
var countAttribsWithMarker = _.chain(attribs).filter(function(a){return !!a[1];})
.map(function(a){return a[0];}).difference(DEFAULT_LINE_ATTRIBUTES).size().value();
const countAttribsWithMarker = _.chain(attribs).filter((a) => !!a[1])
.map((a) => a[0]).difference(DEFAULT_LINE_ATTRIBUTES).size().value();
//if we have marker and any of attributes don't need to have marker. we need delete it
if(hasMarker && !countAttribsWithMarker){
ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]);
}else{
ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool);
}
// if we have marker and any of attributes don't need to have marker. we need delete it
if (hasMarker && !countAttribsWithMarker) {
ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]);
} else {
ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool);
}
return this.applyChangeset(builder);
},
return this.applyChangeset(builder);
},
/*
/*
Toggles a line attribute for the specified line number
If a line attribute with the specified name exists with any value it will be removed
Otherwise it will be set to the given value
@ -395,20 +387,19 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
@param attributeKey: the name of the attribute to toggle, e.g. list
@param attributeValue: the value to pass to the attribute (e.g. indention level)
*/
toggleAttributeOnLine: function(lineNum, attributeName, attributeValue) {
return this.getAttributeOnLine(lineNum, attributeName) ?
this.removeAttributeOnLine(lineNum, attributeName) :
this.setAttributeOnLine(lineNum, attributeName, attributeValue);
toggleAttributeOnLine(lineNum, attributeName, attributeValue) {
return this.getAttributeOnLine(lineNum, attributeName)
? this.removeAttributeOnLine(lineNum, attributeName)
: this.setAttributeOnLine(lineNum, attributeName, attributeValue);
},
hasAttributeOnSelectionOrCaretPosition: function(attributeName) {
var hasSelection = ((this.rep.selStart[0] !== this.rep.selEnd[0]) || (this.rep.selEnd[1] !== this.rep.selStart[1]));
var hasAttrib;
hasAttributeOnSelectionOrCaretPosition(attributeName) {
const hasSelection = ((this.rep.selStart[0] !== this.rep.selEnd[0]) || (this.rep.selEnd[1] !== this.rep.selStart[1]));
let hasAttrib;
if (hasSelection) {
hasAttrib = this.getAttributeOnSelection(attributeName);
}else {
var attributesOnCaretPosition = this.getAttributesOnCaret();
} else {
const attributesOnCaretPosition = this.getAttributesOnCaret();
hasAttrib = _.contains(_.flatten(attributesOnCaretPosition), attributeName);
}
return hasAttrib;

View file

@ -28,28 +28,28 @@
used to reference Attributes in Changesets.
*/
var AttributePool = function () {
const AttributePool = function () {
this.numToAttrib = {}; // e.g. {0: ['foo','bar']}
this.attribToNum = {}; // e.g. {'foo,bar': 0}
this.nextNum = 0;
};
AttributePool.prototype.putAttrib = function (attrib, dontAddIfAbsent) {
var str = String(attrib);
const str = String(attrib);
if (str in this.attribToNum) {
return this.attribToNum[str];
}
if (dontAddIfAbsent) {
return -1;
}
var num = this.nextNum++;
const num = this.nextNum++;
this.attribToNum[str] = num;
this.numToAttrib[num] = [String(attrib[0] || ''), String(attrib[1] || '')];
return num;
};
AttributePool.prototype.getAttrib = function (num) {
var pair = this.numToAttrib[num];
const pair = this.numToAttrib[num];
if (!pair) {
return pair;
}
@ -57,20 +57,20 @@ AttributePool.prototype.getAttrib = function (num) {
};
AttributePool.prototype.getAttribKey = function (num) {
var pair = this.numToAttrib[num];
const pair = this.numToAttrib[num];
if (!pair) return '';
return pair[0];
};
AttributePool.prototype.getAttribValue = function (num) {
var pair = this.numToAttrib[num];
const pair = this.numToAttrib[num];
if (!pair) return '';
return pair[1];
};
AttributePool.prototype.eachAttrib = function (func) {
for (var n in this.numToAttrib) {
var pair = this.numToAttrib[n];
for (const n in this.numToAttrib) {
const pair = this.numToAttrib[n];
func(pair[0], pair[1]);
}
};
@ -78,7 +78,7 @@ AttributePool.prototype.eachAttrib = function (func) {
AttributePool.prototype.toJsonable = function () {
return {
numToAttrib: this.numToAttrib,
nextNum: this.nextNum
nextNum: this.nextNum,
};
};
@ -86,7 +86,7 @@ AttributePool.prototype.fromJsonable = function (obj) {
this.numToAttrib = obj.numToAttrib;
this.nextNum = obj.nextNum;
this.attribToNum = {};
for (var n in this.numToAttrib) {
for (const n in this.numToAttrib) {
this.attribToNum[String(this.numToAttrib[n])] = Number(n);
}
return this;

File diff suppressed because it is too large Load diff

View file

@ -18,40 +18,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
exports.buildRemoveRange = function(rep, builder, start, end) {
var startLineOffset = rep.lines.offsetOfIndex(start[0]);
var endLineOffset = rep.lines.offsetOfIndex(end[0]);
exports.buildRemoveRange = function (rep, builder, start, end) {
const startLineOffset = rep.lines.offsetOfIndex(start[0]);
const endLineOffset = rep.lines.offsetOfIndex(end[0]);
if (end[0] > start[0])
{
if (end[0] > start[0]) {
builder.remove(endLineOffset - startLineOffset - start[1], end[0] - start[0]);
builder.remove(end[1]);
}
else
{
} else {
builder.remove(end[1] - start[1]);
}
}
};
exports.buildKeepRange = function(rep, builder, start, end, attribs, pool) {
var startLineOffset = rep.lines.offsetOfIndex(start[0]);
var endLineOffset = rep.lines.offsetOfIndex(end[0]);
exports.buildKeepRange = function (rep, builder, start, end, attribs, pool) {
const startLineOffset = rep.lines.offsetOfIndex(start[0]);
const endLineOffset = rep.lines.offsetOfIndex(end[0]);
if (end[0] > start[0])
{
if (end[0] > start[0]) {
builder.keep(endLineOffset - startLineOffset - start[1], end[0] - start[0], attribs, pool);
builder.keep(end[1], 0, attribs, pool);
}
else
{
} else {
builder.keep(end[1] - start[1], 0, attribs, pool);
}
}
};
exports.buildKeepToStartOfRange = function(rep, builder, start) {
var startLineOffset = rep.lines.offsetOfIndex(start[0]);
exports.buildKeepToStartOfRange = function (rep, builder, start) {
const startLineOffset = rep.lines.offsetOfIndex(start[0]);
builder.keep(startLineOffset, start[0]);
builder.keep(start[1]);
}
};

View file

@ -23,61 +23,57 @@
// requires: top
// requires: undefined
var KERNEL_SOURCE = '../static/js/require-kernel.js';
const KERNEL_SOURCE = '../static/js/require-kernel.js';
Ace2Editor.registry = {
nextId: 1
nextId: 1,
};
var hooks = require('./pluginfw/hooks');
var pluginUtils = require('./pluginfw/shared');
var _ = require('./underscore');
const hooks = require('./pluginfw/hooks');
const pluginUtils = require('./pluginfw/shared');
const _ = require('./underscore');
function scriptTag(source) {
return (
'<script type="text/javascript">\n'
+ source.replace(/<\//g, '<\\/') +
'</script>'
)
`<script type="text/javascript">\n${
source.replace(/<\//g, '<\\/')
}</script>`
);
}
function Ace2Editor() {
var ace2 = Ace2Editor;
const ace2 = Ace2Editor;
var editor = {};
var info = {
editor: editor,
id: (ace2.registry.nextId++)
const editor = {};
let info = {
editor,
id: (ace2.registry.nextId++),
};
var loaded = false;
let loaded = false;
var actionsPendingInit = [];
let actionsPendingInit = [];
function pendingInit(func, optDoNow) {
return function() {
var that = this;
var args = arguments;
var action = function() {
return function () {
const that = this;
const args = arguments;
const action = function () {
func.apply(that, args);
}
if (optDoNow)
{
};
if (optDoNow) {
optDoNow.apply(that, args);
}
if (loaded)
{
if (loaded) {
action();
}
else
{
} else {
actionsPendingInit.push(action);
}
};
}
function doActionsPendingInit() {
_.each(actionsPendingInit, function(fn,i){
fn()
_.each(actionsPendingInit, (fn, i) => {
fn();
});
actionsPendingInit = [];
}
@ -86,43 +82,56 @@ function Ace2Editor() {
// The following functions (prefixed by 'ace_') are exposed by editor, but
// execution is delayed until init is complete
var aceFunctionsPendingInit = ['importText', 'importAText', 'focus',
'setEditable', 'getFormattedCode', 'setOnKeyPress', 'setOnKeyDown',
'setNotifyDirty', 'setProperty', 'setBaseText', 'setBaseAttributedText',
'applyChangesToBase', 'applyPreparedChangesetToBase',
'setUserChangeNotificationCallback', 'setAuthorInfo',
'setAuthorSelectionRange', 'callWithAce', 'execCommand', 'replaceRange'];
const aceFunctionsPendingInit = ['importText',
'importAText',
'focus',
'setEditable',
'getFormattedCode',
'setOnKeyPress',
'setOnKeyDown',
'setNotifyDirty',
'setProperty',
'setBaseText',
'setBaseAttributedText',
'applyChangesToBase',
'applyPreparedChangesetToBase',
'setUserChangeNotificationCallback',
'setAuthorInfo',
'setAuthorSelectionRange',
'callWithAce',
'execCommand',
'replaceRange'];
_.each(aceFunctionsPendingInit, function(fnName,i){
var prefix = 'ace_';
var name = prefix + fnName;
editor[fnName] = pendingInit(function(){
if(fnName === "setAuthorInfo"){
if(!arguments[0]){
_.each(aceFunctionsPendingInit, (fnName, i) => {
const prefix = 'ace_';
const name = prefix + fnName;
editor[fnName] = pendingInit(function () {
if (fnName === 'setAuthorInfo') {
if (!arguments[0]) {
// setAuthorInfo AuthorId not set for some reason
}else{
} else {
info[prefix + fnName].apply(this, arguments);
}
}else{
} else {
info[prefix + fnName].apply(this, arguments);
}
});
});
editor.exportText = function() {
if (!loaded) return "(awaiting init)\n";
editor.exportText = function () {
if (!loaded) return '(awaiting init)\n';
return info.ace_exportText();
};
editor.getFrame = function() {
editor.getFrame = function () {
return info.frame || null;
};
editor.getDebugProperty = function(prop) {
editor.getDebugProperty = function (prop) {
return info.ace_getDebugProperty(prop);
};
editor.getInInternationalComposition = function() {
editor.getInInternationalComposition = function () {
if (!loaded) return false;
return info.ace_getInInternationalComposition();
};
@ -136,26 +145,25 @@ function Ace2Editor() {
// to prepareUserChangeset will return an updated changeset that takes into account the
// latest user changes, and modify the changeset to be applied by applyPreparedChangesetToBase
// accordingly.
editor.prepareUserChangeset = function() {
editor.prepareUserChangeset = function () {
if (!loaded) return null;
return info.ace_prepareUserChangeset();
};
editor.getUnhandledErrors = function() {
editor.getUnhandledErrors = function () {
if (!loaded) return [];
// returns array of {error: <browser Error object>, time: +new Date()}
return info.ace_getUnhandledErrors();
};
function sortFilesByEmbeded(files) {
var embededFiles = [];
var remoteFiles = [];
const embededFiles = [];
let remoteFiles = [];
if (Ace2Editor.EMBEDED) {
for (var i = 0, ii = files.length; i < ii; i++) {
var file = files[i];
for (let i = 0, ii = files.length; i < ii; i++) {
const file = files[i];
if (Object.prototype.hasOwnProperty.call(Ace2Editor.EMBEDED, file)) {
embededFiles.push(file);
} else {
@ -169,9 +177,9 @@ function Ace2Editor() {
return {embeded: embededFiles, remote: remoteFiles};
}
function pushStyleTagsFor(buffer, files) {
var sorted = sortFilesByEmbeded(files);
var embededFiles = sorted.embeded;
var remoteFiles = sorted.remote;
const sorted = sortFilesByEmbeded(files);
const embededFiles = sorted.embeded;
const remoteFiles = sorted.remote;
if (embededFiles.length > 0) {
buffer.push('<style type="text/css">');
@ -183,67 +191,66 @@ function Ace2Editor() {
}
for (var i = 0, ii = remoteFiles.length; i < ii; i++) {
var file = remoteFiles[i];
buffer.push('<link rel="stylesheet" type="text/css" href="' + encodeURI(file) + '"\/>');
buffer.push(`<link rel="stylesheet" type="text/css" href="${encodeURI(file)}"\/>`);
}
}
editor.destroy = pendingInit(function() {
editor.destroy = pendingInit(() => {
info.ace_dispose();
info.frame.parentNode.removeChild(info.frame);
delete ace2.registry[info.id];
info = null; // prevent IE 6 closure memory leaks
});
editor.init = function(containerId, initialCode, doneFunc) {
editor.init = function (containerId, initialCode, doneFunc) {
editor.importText(initialCode);
info.onEditorReady = function() {
info.onEditorReady = function () {
loaded = true;
doActionsPendingInit();
doneFunc();
};
(function() {
var doctype = "<!doctype html>";
(function () {
const doctype = '<!doctype html>';
var iframeHTML = [];
const iframeHTML = [];
iframeHTML.push(doctype);
iframeHTML.push("<html class='inner-editor " + clientVars.skinVariants + "'><head>");
iframeHTML.push(`<html class='inner-editor ${clientVars.skinVariants}'><head>`);
// calls to these functions ($$INCLUDE_...) are replaced when this file is processed
// and compressed, putting the compressed code from the named file directly into the
// source here.
// these lines must conform to a specific format because they are passed by the build script:
var includedCSS = [];
var $$INCLUDE_CSS = function(filename) {includedCSS.push(filename)};
$$INCLUDE_CSS("../static/css/iframe_editor.css");
var $$INCLUDE_CSS = function (filename) { includedCSS.push(filename); };
$$INCLUDE_CSS('../static/css/iframe_editor.css');
// disableCustomScriptsAndStyles can be used to disable loading of custom scripts
if(!clientVars.disableCustomScriptsAndStyles){
$$INCLUDE_CSS("../static/css/pad.css?v=" + clientVars.randomVersionString);
if (!clientVars.disableCustomScriptsAndStyles) {
$$INCLUDE_CSS(`../static/css/pad.css?v=${clientVars.randomVersionString}`);
}
var additionalCSS = _(hooks.callAll("aceEditorCSS")).map(function(path){
var additionalCSS = _(hooks.callAll('aceEditorCSS')).map((path) => {
if (path.match(/\/\//)) { // Allow urls to external CSS - http(s):// and //some/path.css
return path;
}
return '../static/plugins/' + path;
return `../static/plugins/${path}`;
});
includedCSS = includedCSS.concat(additionalCSS);
$$INCLUDE_CSS("../static/skins/" + clientVars.skinName + "/pad.css?v=" + clientVars.randomVersionString);
$$INCLUDE_CSS(`../static/skins/${clientVars.skinName}/pad.css?v=${clientVars.randomVersionString}`);
pushStyleTagsFor(iframeHTML, includedCSS);
if (!Ace2Editor.EMBEDED && Ace2Editor.EMBEDED[KERNEL_SOURCE]) {
// Remotely src'd script tag will not work in IE; it must be embedded, so
// throw an error if it is not.
throw new Error("Require kernel could not be found.");
throw new Error('Require kernel could not be found.');
}
iframeHTML.push(scriptTag(
Ace2Editor.EMBEDED[KERNEL_SOURCE] + '\n\
`${Ace2Editor.EMBEDED[KERNEL_SOURCE]}\n\
require.setRootURI("../javascripts/src");\n\
require.setLibraryURI("../javascripts/lib");\n\
require.setGlobalKeyPath("require");\n\
@ -257,23 +264,23 @@ var Ace2Inner = require("ep_etherpad-lite/static/js/ace2_inner");\n\
plugins.ensure(function () {\n\
Ace2Inner.init();\n\
});\n\
'));
`));
iframeHTML.push('<style type="text/css" title="dynamicsyntax"></style>');
hooks.callAll("aceInitInnerdocbodyHead", {
iframeHTML: iframeHTML
hooks.callAll('aceInitInnerdocbodyHead', {
iframeHTML,
});
iframeHTML.push('</head><body id="innerdocbody" class="innerdocbody" role="application" class="syntax" spellcheck="false">&nbsp;</body></html>');
// Expose myself to global for my child frame.
var thisFunctionsName = "ChildAccessibleAce2Editor";
(function () {return this}())[thisFunctionsName] = Ace2Editor;
const thisFunctionsName = 'ChildAccessibleAce2Editor';
(function () { return this; }())[thisFunctionsName] = Ace2Editor;
var outerScript = '\
editorId = ' + JSON.stringify(info.id) + ';\n\
editorInfo = parent[' + JSON.stringify(thisFunctionsName) + '].registry[editorId];\n\
const outerScript = `\
editorId = ${JSON.stringify(info.id)};\n\
editorInfo = parent[${JSON.stringify(thisFunctionsName)}].registry[editorId];\n\
window.onload = function () {\n\
window.onload = null;\n\
setTimeout(function () {\n\
@ -293,34 +300,35 @@ window.onload = function () {\n\
};\n\
var doc = iframe.contentWindow.document;\n\
doc.open();\n\
var text = (' + JSON.stringify(iframeHTML.join('\n')) + ');\n\
var text = (${JSON.stringify(iframeHTML.join('\n'))});\n\
doc.write(text);\n\
doc.close();\n\
}, 0);\n\
}';
}`;
var outerHTML = [doctype, '<html class="inner-editor outerdoc ' + clientVars.skinVariants + '"><head>']
const outerHTML = [doctype, `<html class="inner-editor outerdoc ${clientVars.skinVariants}"><head>`];
var includedCSS = [];
var $$INCLUDE_CSS = function(filename) {includedCSS.push(filename)};
$$INCLUDE_CSS("../static/css/iframe_editor.css");
$$INCLUDE_CSS("../static/css/pad.css?v=" + clientVars.randomVersionString);
var $$INCLUDE_CSS = function (filename) { includedCSS.push(filename); };
$$INCLUDE_CSS('../static/css/iframe_editor.css');
$$INCLUDE_CSS(`../static/css/pad.css?v=${clientVars.randomVersionString}`);
var additionalCSS = _(hooks.callAll("aceEditorCSS")).map(function(path){
var additionalCSS = _(hooks.callAll('aceEditorCSS')).map((path) => {
if (path.match(/\/\//)) { // Allow urls to external CSS - http(s):// and //some/path.css
return path;
}
return '../static/plugins/' + path }
return `../static/plugins/${path}`;
},
);
includedCSS = includedCSS.concat(additionalCSS);
$$INCLUDE_CSS("../static/skins/" + clientVars.skinName + "/pad.css?v=" + clientVars.randomVersionString);
$$INCLUDE_CSS(`../static/skins/${clientVars.skinName}/pad.css?v=${clientVars.randomVersionString}`);
pushStyleTagsFor(outerHTML, includedCSS);
// bizarrely, in FF2, a file with no "external" dependencies won't finish loading properly
// (throbs busy while typing)
var pluginNames = pluginUtils.clientPluginNames();
const pluginNames = pluginUtils.clientPluginNames();
outerHTML.push(
'<style type="text/css" title="dynamicsyntax"></style>',
'<link rel="stylesheet" type="text/css" href="data:text/css,"/>',
@ -331,14 +339,14 @@ window.onload = function () {\n\
'<div id="linemetricsdiv">x</div>',
'</body></html>');
var outerFrame = document.createElement("IFRAME");
outerFrame.name = "ace_outer";
const outerFrame = document.createElement('IFRAME');
outerFrame.name = 'ace_outer';
outerFrame.frameBorder = 0; // for IE
outerFrame.title = "Ether";
outerFrame.title = 'Ether';
info.frame = outerFrame;
document.getElementById(containerId).appendChild(outerFrame);
var editorDocument = outerFrame.contentWindow.document;
const editorDocument = outerFrame.contentWindow.document;
editorDocument.open();
editorDocument.write(outerHTML.join(''));

View file

@ -20,27 +20,27 @@
* limitations under the License.
*/
var Security = require('./security');
const Security = require('./security');
function isNodeText(node) {
return (node.nodeType == 3);
}
function object(o) {
var f = function(){};
const f = function () {};
f.prototype = o;
return new f();
}
function getAssoc(obj, name) {
return obj["_magicdom_" + name];
return obj[`_magicdom_${name}`];
}
function setAssoc(obj, name, value) {
// note that in IE designMode, properties of a node can get
// copied to new nodes that are spawned during editing; also,
// properties representable in HTML text can survive copy-and-paste
obj["_magicdom_" + name] = value;
obj[`_magicdom_${name}`] = value;
}
// "func" is a function over 0..(numItems-1) that is monotonically
@ -52,11 +52,10 @@ function binarySearch(numItems, func) {
if (numItems < 1) return 0;
if (func(0)) return 0;
if (!func(numItems - 1)) return numItems;
var low = 0; // func(low) is always false
var high = numItems - 1; // func(high) is always true
while ((high - low) > 1)
{
var x = Math.floor((low + high) / 2); // x != low, x != high
let low = 0; // func(low) is always false
let high = numItems - 1; // func(high) is always true
while ((high - low) > 1) {
const x = Math.floor((low + high) / 2); // x != low, x != high
if (func(x)) high = x;
else low = x;
}
@ -64,7 +63,7 @@ function binarySearch(numItems, func) {
}
function binarySearchInfinite(expectedLength, func) {
var i = 0;
let i = 0;
while (!func(i)) i += expectedLength;
return binarySearch(i, func);
}
@ -73,7 +72,7 @@ function htmlPrettyEscape(str) {
return Security.escapeHTML(str).replace(/\r?\n/g, '\\n');
}
var noop = function(){};
const noop = function () {};
exports.isNodeText = isNodeText;
exports.object = object;

File diff suppressed because it is too large Load diff

View file

@ -1,271 +1,262 @@
$(document).ready(function () {
$(document).ready(() => {
let socket;
const loc = document.location;
const port = loc.port == '' ? (loc.protocol == 'https:' ? 443 : 80) : loc.port;
const url = `${loc.protocol}//${loc.hostname}:${port}/`;
const pathComponents = location.pathname.split('/');
// Strip admin/plugins
const baseURL = `${pathComponents.slice(0, pathComponents.length - 2).join('/')}/`;
const resource = `${baseURL.substring(1)}socket.io`;
var socket,
loc = document.location,
port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port,
url = loc.protocol + "//" + loc.hostname + ":" + port + "/",
pathComponents = location.pathname.split('/'),
// Strip admin/plugins
baseURL = pathComponents.slice(0,pathComponents.length-2).join('/') + '/',
resource = baseURL.substring(1) + "socket.io";
//connect
var room = url + "pluginfw/installer";
socket = io.connect(room, {path: baseURL + "socket.io", resource : resource});
// connect
const room = `${url}pluginfw/installer`;
socket = io.connect(room, {path: `${baseURL}socket.io`, resource});
function search(searchTerm, limit) {
if(search.searchTerm != searchTerm) {
search.offset = 0
search.results = []
search.end = false
if (search.searchTerm != searchTerm) {
search.offset = 0;
search.results = [];
search.end = false;
}
limit = limit? limit : search.limit
limit = limit ? limit : search.limit;
search.searchTerm = searchTerm;
socket.emit("search", {searchTerm: searchTerm, offset:search.offset, limit: limit, sortBy: search.sortBy, sortDir: search.sortDir});
socket.emit('search', {searchTerm, offset: search.offset, limit, sortBy: search.sortBy, sortDir: search.sortDir});
search.offset += limit;
$('#search-progress').show()
search.messages.show('fetching')
search.searching = true
$('#search-progress').show();
search.messages.show('fetching');
search.searching = true;
}
search.searching = false;
search.offset = 0;
search.limit = 999;
search.results = [];
search.sortBy = 'name';
search.sortDir = /*DESC?*/true;
search.sortDir = /* DESC?*/true;
search.end = true;// have we received all results already?
search.messages = {
show: function(msg) {
//$('.search-results .messages').show()
$('.search-results .messages .'+msg+'').show()
$('.search-results .messages .'+msg+' *').show()
show(msg) {
// $('.search-results .messages').show()
$(`.search-results .messages .${msg}`).show();
$(`.search-results .messages .${msg} *`).show();
},
hide: function(msg) {
$('.search-results .messages').hide()
$('.search-results .messages .'+msg+'').hide()
$('.search-results .messages .'+msg+' *').hide()
}
}
hide(msg) {
$('.search-results .messages').hide();
$(`.search-results .messages .${msg}`).hide();
$(`.search-results .messages .${msg} *`).hide();
},
};
var installed = {
const installed = {
progress: {
show: function(plugin, msg) {
$('.installed-results .'+plugin+' .progress').show()
$('.installed-results .'+plugin+' .progress .message').text(msg)
if($(window).scrollTop() > $('.'+plugin).offset().top)$(window).scrollTop($('.'+plugin).offset().top-100)
show(plugin, msg) {
$(`.installed-results .${plugin} .progress`).show();
$(`.installed-results .${plugin} .progress .message`).text(msg);
if ($(window).scrollTop() > $(`.${plugin}`).offset().top)$(window).scrollTop($(`.${plugin}`).offset().top - 100);
},
hide(plugin) {
$(`.installed-results .${plugin} .progress`).hide();
$(`.installed-results .${plugin} .progress .message`).text('');
},
hide: function(plugin) {
$('.installed-results .'+plugin+' .progress').hide()
$('.installed-results .'+plugin+' .progress .message').text('')
}
},
messages: {
show: function(msg) {
$('.installed-results .messages').show()
$('.installed-results .messages .'+msg+'').show()
show(msg) {
$('.installed-results .messages').show();
$(`.installed-results .messages .${msg}`).show();
},
hide(msg) {
$('.installed-results .messages').hide();
$(`.installed-results .messages .${msg}`).hide();
},
hide: function(msg) {
$('.installed-results .messages').hide()
$('.installed-results .messages .'+msg+'').hide()
}
},
list: []
}
list: [],
};
function displayPluginList(plugins, container, template) {
plugins.forEach(function(plugin) {
var row = template.clone();
plugins.forEach((plugin) => {
const row = template.clone();
for (attr in plugin) {
if(attr == "name"){ // Hack to rewrite URLS into name
var link = $('<a>');
link.attr('href', 'https://npmjs.org/package/'+plugin['name']);
if (attr == 'name') { // Hack to rewrite URLS into name
const link = $('<a>');
link.attr('href', `https://npmjs.org/package/${plugin.name}`);
link.attr('plugin', 'Plugin details');
link.attr('target', '_blank');
link.text(plugin['name'].substr(3));
link.text(plugin.name.substr(3));
row.find('.name').append(link);
} else {
row.find("." + attr).text(plugin[attr]);
row.find(`.${attr}`).text(plugin[attr]);
}
}
row.find(".version").text(plugin.version);
row.addClass(plugin.name)
row.data('plugin', plugin.name)
row.find('.version').text(plugin.version);
row.addClass(plugin.name);
row.data('plugin', plugin.name);
container.append(row);
})
});
updateHandlers();
}
function sortPluginList(plugins, property, /*ASC?*/dir) {
return plugins.sort(function(a, b) {
if (a[property] < b[property])
return dir? -1 : 1;
if (a[property] > b[property])
return dir? 1 : -1;
function sortPluginList(plugins, property, /* ASC?*/dir) {
return plugins.sort((a, b) => {
if (a[property] < b[property]) return dir ? -1 : 1;
if (a[property] > b[property]) return dir ? 1 : -1;
// a must be equal to b
return 0;
})
});
}
function updateHandlers() {
// Search
$("#search-query").unbind('keyup').keyup(function () {
search($("#search-query").val());
$('#search-query').unbind('keyup').keyup(() => {
search($('#search-query').val());
});
// Prevent form submit
$('#search-query').parent().bind('submit', function() {
return false;
});
$('#search-query').parent().bind('submit', () => false);
// update & install
$(".do-install, .do-update").unbind('click').click(function (e) {
var $row = $(e.target).closest("tr")
, plugin = $row.data('plugin');
if($(this).hasClass('do-install')) {
$row.remove().appendTo('#installed-plugins')
installed.progress.show(plugin, 'Installing')
}else{
installed.progress.show(plugin, 'Updating')
$('.do-install, .do-update').unbind('click').click(function (e) {
const $row = $(e.target).closest('tr');
const plugin = $row.data('plugin');
if ($(this).hasClass('do-install')) {
$row.remove().appendTo('#installed-plugins');
installed.progress.show(plugin, 'Installing');
} else {
installed.progress.show(plugin, 'Updating');
}
socket.emit("install", plugin);
installed.messages.hide("nothing-installed")
socket.emit('install', plugin);
installed.messages.hide('nothing-installed');
});
// uninstall
$(".do-uninstall").unbind('click').click(function (e) {
var $row = $(e.target).closest("tr")
, pluginName = $row.data('plugin');
socket.emit("uninstall", pluginName);
installed.progress.show(pluginName, 'Uninstalling')
installed.list = installed.list.filter(function(plugin) {
return plugin.name != pluginName
})
$('.do-uninstall').unbind('click').click((e) => {
const $row = $(e.target).closest('tr');
const pluginName = $row.data('plugin');
socket.emit('uninstall', pluginName);
installed.progress.show(pluginName, 'Uninstalling');
installed.list = installed.list.filter((plugin) => plugin.name != pluginName);
});
// Sort
$('.sort.up').unbind('click').click(function() {
$('.sort.up').unbind('click').click(function () {
search.sortBy = $(this).attr('data-label').toLowerCase();
search.sortDir = false;
search.offset = 0;
search(search.searchTerm, search.results.length);
search.results = [];
})
$('.sort.down, .sort.none').unbind('click').click(function() {
});
$('.sort.down, .sort.none').unbind('click').click(function () {
search.sortBy = $(this).attr('data-label').toLowerCase();
search.sortDir = true;
search.offset = 0;
search(search.searchTerm, search.results.length);
search.results = [];
})
});
}
socket.on('results:search', function (data) {
if(!data.results.length) search.end = true;
if(data.query.offset == 0) search.results = [];
search.messages.hide('nothing-found')
search.messages.hide('fetching')
$("#search-query").removeAttr('disabled')
socket.on('results:search', (data) => {
if (!data.results.length) search.end = true;
if (data.query.offset == 0) search.results = [];
search.messages.hide('nothing-found');
search.messages.hide('fetching');
$('#search-query').removeAttr('disabled');
console.log('got search results', data)
console.log('got search results', data);
// add to results
search.results = search.results.concat(data.results);
// Update sorting head
$('.sort')
.removeClass('up down')
.addClass('none');
$('.search-results thead th[data-label='+data.query.sortBy+']')
.removeClass('none')
.addClass(data.query.sortDir? 'up' : 'down');
.removeClass('up down')
.addClass('none');
$(`.search-results thead th[data-label=${data.query.sortBy}]`)
.removeClass('none')
.addClass(data.query.sortDir ? 'up' : 'down');
// re-render search results
var searchWidget = $(".search-results");
searchWidget.find(".results *").remove();
if(search.results.length > 0) {
displayPluginList(search.results, searchWidget.find(".results"), searchWidget.find(".template tr"))
}else {
search.messages.show('nothing-found')
const searchWidget = $('.search-results');
searchWidget.find('.results *').remove();
if (search.results.length > 0) {
displayPluginList(search.results, searchWidget.find('.results'), searchWidget.find('.template tr'));
} else {
search.messages.show('nothing-found');
}
search.messages.hide('fetching')
$('#search-progress').hide()
search.searching = false
search.messages.hide('fetching');
$('#search-progress').hide();
search.searching = false;
});
socket.on('results:installed', function (data) {
installed.messages.hide("fetching")
installed.messages.hide("nothing-installed")
socket.on('results:installed', (data) => {
installed.messages.hide('fetching');
installed.messages.hide('nothing-installed');
installed.list = data.installed
sortPluginList(installed.list, 'name', /*ASC?*/true);
installed.list = data.installed;
sortPluginList(installed.list, 'name', /* ASC?*/true);
// filter out epl
installed.list = installed.list.filter(function(plugin) {
return plugin.name != 'ep_etherpad-lite'
})
installed.list = installed.list.filter((plugin) => plugin.name != 'ep_etherpad-lite');
// remove all installed plugins (leave plugins that are still being installed)
installed.list.forEach(function(plugin) {
$('#installed-plugins .'+plugin.name).remove()
})
installed.list.forEach((plugin) => {
$(`#installed-plugins .${plugin.name}`).remove();
});
if(installed.list.length > 0) {
displayPluginList(installed.list, $("#installed-plugins"), $("#installed-plugin-template"));
if (installed.list.length > 0) {
displayPluginList(installed.list, $('#installed-plugins'), $('#installed-plugin-template'));
socket.emit('checkUpdates');
}else {
installed.messages.show("nothing-installed")
} else {
installed.messages.show('nothing-installed');
}
});
socket.on('results:updatable', function(data) {
data.updatable.forEach(function(pluginName) {
var $row = $('#installed-plugins > tr.'+pluginName)
, actions = $row.find('.actions')
actions.append('<input class="do-update" type="button" value="Update" />')
})
socket.on('results:updatable', (data) => {
data.updatable.forEach((pluginName) => {
const $row = $(`#installed-plugins > tr.${pluginName}`);
const actions = $row.find('.actions');
actions.append('<input class="do-update" type="button" value="Update" />');
});
updateHandlers();
})
});
socket.on('finished:install', function(data) {
if(data.error) {
if(data.code === "EPEERINVALID"){
socket.on('finished:install', (data) => {
if (data.error) {
if (data.code === 'EPEERINVALID') {
alert("This plugin requires that you update Etherpad so it can operate in it's true glory");
}
alert('An error occurred while installing '+data.plugin+' \n'+data.error)
$('#installed-plugins .'+data.plugin).remove()
alert(`An error occurred while installing ${data.plugin} \n${data.error}`);
$(`#installed-plugins .${data.plugin}`).remove();
}
socket.emit("getInstalled");
socket.emit('getInstalled');
// update search results
search.offset = 0;
search(search.searchTerm, search.results.length);
search.results = [];
})
});
socket.on('finished:uninstall', function(data) {
if(data.error) alert('An error occurred while uninstalling the '+data.plugin+' \n'+data.error)
socket.on('finished:uninstall', (data) => {
if (data.error) alert(`An error occurred while uninstalling the ${data.plugin} \n${data.error}`);
// remove plugin from installed list
$('#installed-plugins .'+data.plugin).remove()
$(`#installed-plugins .${data.plugin}`).remove();
socket.emit("getInstalled");
socket.emit('getInstalled');
// update search results
search.offset = 0;
search(search.searchTerm, search.results.length);
search.results = [];
})
});
// init
updateHandlers();
socket.emit("getInstalled");
socket.emit('getInstalled');
search('');
// check for updates every 5mins
setInterval(function() {
setInterval(() => {
socket.emit('checkUpdates');
}, 1000*60*5)
}, 1000 * 60 * 5);
});

View file

@ -1,81 +1,75 @@
$(document).ready(function () {
var socket,
loc = document.location,
port = loc.port == "" ? (loc.protocol == "https:" ? 443 : 80) : loc.port,
url = loc.protocol + "//" + loc.hostname + ":" + port + "/",
pathComponents = location.pathname.split('/'),
// Strip admin/plugins
baseURL = pathComponents.slice(0,pathComponents.length-2).join('/') + '/',
resource = baseURL.substring(1) + "socket.io";
$(document).ready(() => {
let socket;
const loc = document.location;
const port = loc.port == '' ? (loc.protocol == 'https:' ? 443 : 80) : loc.port;
const url = `${loc.protocol}//${loc.hostname}:${port}/`;
const pathComponents = location.pathname.split('/');
// Strip admin/plugins
const baseURL = `${pathComponents.slice(0, pathComponents.length - 2).join('/')}/`;
const resource = `${baseURL.substring(1)}socket.io`;
//connect
var room = url + "settings";
socket = io.connect(room, {path: baseURL + "socket.io", resource : resource});
socket.on('settings', function (settings) {
// connect
const room = `${url}settings`;
socket = io.connect(room, {path: `${baseURL}socket.io`, resource});
socket.on('settings', (settings) => {
/* Check whether the settings.json is authorized to be viewed */
if(settings.results === 'NOT_ALLOWED') {
if (settings.results === 'NOT_ALLOWED') {
$('.innerwrapper').hide();
$('.innerwrapper-err').show();
$('.err-message').html("Settings json is not authorized to be viewed in Admin page!!");
$('.err-message').html('Settings json is not authorized to be viewed in Admin page!!');
return;
}
/* Check to make sure the JSON is clean before proceeding */
if(isJSONClean(settings.results))
{
if (isJSONClean(settings.results)) {
$('.settings').append(settings.results);
$('.settings').focus();
$('.settings').autosize();
}
else{
alert("YOUR JSON IS BAD AND YOU SHOULD FEEL BAD");
} else {
alert('YOUR JSON IS BAD AND YOU SHOULD FEEL BAD');
}
});
/* When the admin clicks save Settings check the JSON then send the JSON back to the server */
$('#saveSettings').on('click', function(){
var editedSettings = $('.settings').val();
if(isJSONClean(editedSettings)){
$('#saveSettings').on('click', () => {
const editedSettings = $('.settings').val();
if (isJSONClean(editedSettings)) {
// JSON is clean so emit it to the server
socket.emit("saveSettings", $('.settings').val());
}else{
alert("YOUR JSON IS BAD AND YOU SHOULD FEEL BAD")
socket.emit('saveSettings', $('.settings').val());
} else {
alert('YOUR JSON IS BAD AND YOU SHOULD FEEL BAD');
$('.settings').focus();
}
});
/* Tell Etherpad Server to restart */
$('#restartEtherpad').on('click', function(){
socket.emit("restartServer");
$('#restartEtherpad').on('click', () => {
socket.emit('restartServer');
});
socket.on('saveprogress', function(progress){
socket.on('saveprogress', (progress) => {
$('#response').show();
$('#response').text(progress);
$('#response').fadeOut('slow');
});
socket.emit("load"); // Load the JSON from the server
socket.emit('load'); // Load the JSON from the server
});
function isJSONClean(data){
var cleanSettings = JSON.minify(data);
function isJSONClean(data) {
let cleanSettings = JSON.minify(data);
// this is a bit naive. In theory some key/value might contain the sequences ',]' or ',}'
cleanSettings = cleanSettings.replace(",]","]").replace(",}","}");
try{
cleanSettings = cleanSettings.replace(',]', ']').replace(',}', '}');
try {
var response = jQuery.parseJSON(cleanSettings);
}
catch(e){
} catch (e) {
return false; // the JSON failed to be parsed
}
if(typeof response !== 'object'){
if (typeof response !== 'object') {
return false;
}else{
} else {
return true;
}
}

View file

@ -20,32 +20,30 @@
* limitations under the License.
*/
var makeCSSManager = require('./cssmanager').makeCSSManager;
var domline = require('./domline').domline;
var AttribPool = require('./AttributePool');
var Changeset = require('./Changeset');
var linestylefilter = require('./linestylefilter').linestylefilter;
var colorutils = require('./colorutils').colorutils;
var _ = require('./underscore');
var hooks = require('./pluginfw/hooks');
const makeCSSManager = require('./cssmanager').makeCSSManager;
const domline = require('./domline').domline;
const AttribPool = require('./AttributePool');
const Changeset = require('./Changeset');
const linestylefilter = require('./linestylefilter').linestylefilter;
const colorutils = require('./colorutils').colorutils;
const _ = require('./underscore');
const hooks = require('./pluginfw/hooks');
// These parameters were global, now they are injected. A reference to the
// Timeslider controller would probably be more appropriate.
function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider) {
var changesetLoader = undefined;
let changesetLoader = undefined;
// Below Array#indexOf code was direct pasted by AppJet/Etherpad, licence unknown. Possible source: http://www.tutorialspoint.com/javascript/array_indexof.htm
if (!Array.prototype.indexOf)
{
Array.prototype.indexOf = function(elt /*, from*/ ) {
var len = this.length >>> 0;
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (elt /* , from*/) {
const len = this.length >>> 0;
var from = Number(arguments[1]) || 0;
let from = Number(arguments[1]) || 0;
from = (from < 0) ? Math.ceil(from) : Math.floor(from);
if (from < 0) from += len;
for (; from < len; from++)
{
for (; from < len; from++) {
if (from in this && this[from] === elt) return from;
}
return -1;
@ -53,22 +51,19 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
}
function debugLog() {
try
{
try {
if (window.console) console.log.apply(console, arguments);
}
catch (e)
{
if (window.console) console.log("error printing: ", e);
} catch (e) {
if (window.console) console.log('error printing: ', e);
}
}
//var socket;
var channelState = "DISCONNECTED";
// var socket;
const channelState = 'DISCONNECTED';
var appLevelDisconnectReason = null;
const appLevelDisconnectReason = null;
var padContents = {
const padContents = {
currentRevision: clientVars.collab_client_vars.rev,
currentTime: clientVars.collab_client_vars.time,
currentLines: Changeset.splitTextLines(clientVars.collab_client_vars.initialAttributedText.text),
@ -76,13 +71,13 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
// to be filled in once the dom loads
apool: (new AttribPool()).fromJsonable(clientVars.collab_client_vars.apool),
alines: Changeset.splitAttributionLines(
clientVars.collab_client_vars.initialAttributedText.attribs, clientVars.collab_client_vars.initialAttributedText.text),
clientVars.collab_client_vars.initialAttributedText.attribs, clientVars.collab_client_vars.initialAttributedText.text),
// generates a jquery element containing HTML for a line
lineToElement: function(line, aline) {
var element = document.createElement("div");
var emptyLine = (line == '\n');
var domInfo = domline.createDomLine(!emptyLine, true);
lineToElement(line, aline) {
const element = document.createElement('div');
const emptyLine = (line == '\n');
const domInfo = domline.createDomLine(!emptyLine, true);
linestylefilter.populateDomLine(line, aline, this.apool, domInfo);
domInfo.prepareForAdd();
element.className = domInfo.node.className;
@ -91,35 +86,29 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
return $(element);
},
applySpliceToDivs: function(start, numRemoved, newLines) {
applySpliceToDivs(start, numRemoved, newLines) {
// remove spliced-out lines from DOM
for (var i = start; i < start + numRemoved && i < this.currentDivs.length; i++)
{
for (var i = start; i < start + numRemoved && i < this.currentDivs.length; i++) {
this.currentDivs[i].remove();
}
// remove spliced-out line divs from currentDivs array
this.currentDivs.splice(start, numRemoved);
var newDivs = [];
for (var i = 0; i < newLines.length; i++)
{
const newDivs = [];
for (var i = 0; i < newLines.length; i++) {
newDivs.push(this.lineToElement(newLines[i], this.alines[start + i]));
}
// grab the div just before the first one
var startDiv = this.currentDivs[start - 1] || null;
let startDiv = this.currentDivs[start - 1] || null;
// insert the div elements into the correct place, in the correct order
for (var i = 0; i < newDivs.length; i++)
{
if (startDiv)
{
for (var i = 0; i < newDivs.length; i++) {
if (startDiv) {
startDiv.after(newDivs[i]);
}
else
{
$("#innerdocbody").prepend(newDivs[i]);
} else {
$('#innerdocbody').prepend(newDivs[i]);
}
startDiv = newDivs[i];
}
@ -132,10 +121,8 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
},
// splice the lines
splice: function(start, numRemoved, newLinesVA) {
var newLines = _.map(Array.prototype.slice.call(arguments, 2), function(s) {
return s;
});
splice(start, numRemoved, newLinesVA) {
const newLines = _.map(Array.prototype.slice.call(arguments, 2), (s) => s);
// apply this splice to the divs
this.applySpliceToDivs(start, numRemoved, newLines);
@ -146,30 +133,26 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
this.currentLines.splice.apply(this.currentLines, arguments);
},
// returns the contents of the specified line I
get: function(i) {
get(i) {
return this.currentLines[i];
},
// returns the number of lines in the document
length: function() {
length() {
return this.currentLines.length;
},
getActiveAuthors: function() {
var self = this;
var authors = [];
var seenNums = {};
var alines = self.alines;
for (var i = 0; i < alines.length; i++)
{
Changeset.eachAttribNumber(alines[i], function(n) {
if (!seenNums[n])
{
getActiveAuthors() {
const self = this;
const authors = [];
const seenNums = {};
const alines = self.alines;
for (let i = 0; i < alines.length; i++) {
Changeset.eachAttribNumber(alines[i], (n) => {
if (!seenNums[n]) {
seenNums[n] = true;
if (self.apool.getAttribKey(n) == 'author')
{
var a = self.apool.getAttribValue(n);
if (a)
{
if (self.apool.getAttribKey(n) == 'author') {
const a = self.apool.getAttribValue(n);
if (a) {
authors.push(a);
}
}
@ -178,27 +161,21 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
}
authors.sort();
return authors;
}
},
};
function callCatchingErrors(catcher, func) {
try
{
try {
wrapRecordingErrors(catcher, func)();
}
catch (e)
{ /*absorb*/
} catch (e) { /* absorb*/
}
}
function wrapRecordingErrors(catcher, func) {
return function() {
try
{
return function () {
try {
return func.apply(this, Array.prototype.slice.call(arguments));
}
catch (e)
{
} catch (e) {
// caughtErrors.push(e);
// caughtErrorCatchers.push(catcher);
// caughtErrorTimes.push(+new Date());
@ -210,13 +187,13 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
}
function loadedNewChangeset(changesetForward, changesetBackward, revision, timeDelta) {
var broadcasting = (BroadcastSlider.getSliderPosition() == revisionInfo.latest);
const broadcasting = (BroadcastSlider.getSliderPosition() == revisionInfo.latest);
revisionInfo.addChangeset(revision, revision + 1, changesetForward, changesetBackward, timeDelta);
BroadcastSlider.setSliderLength(revisionInfo.latest);
if (broadcasting) applyChangeset(changesetForward, revision + 1, false, timeDelta);
}
/*
/*
At this point, we must be certain that the changeset really does map from
the current revision to the specified revision. Any mistakes here will
cause the whole slider to get out of sync.
@ -224,38 +201,34 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
function applyChangeset(changeset, revision, preventSliderMovement, timeDelta) {
// disable the next 'gotorevision' call handled by a timeslider update
if (!preventSliderMovement)
{
if (!preventSliderMovement) {
goToRevisionIfEnabledCount++;
BroadcastSlider.setSliderPosition(revision);
}
let oldAlines = padContents.alines.slice();
try
{
const oldAlines = padContents.alines.slice();
try {
// must mutate attribution lines before text lines
Changeset.mutateAttributionLines(changeset, padContents.alines, padContents.apool);
}
catch (e)
{
} catch (e) {
debugLog(e);
}
// scroll to the area that is changed before the lines are mutated
if($('#options-followContents').is(":checked") || $('#options-followContents').prop("checked")){
if ($('#options-followContents').is(':checked') || $('#options-followContents').prop('checked')) {
// get the index of the first line that has mutated attributes
// the last line in `oldAlines` should always equal to "|1+1", ie newline without attributes
// so it should be safe to assume this line has changed attributes when inserting content at
// the bottom of a pad
let lineChanged;
_.some(oldAlines, function(line, index){
if(line !== padContents.alines[index]){
_.some(oldAlines, (line, index) => {
if (line !== padContents.alines[index]) {
lineChanged = index;
return true; // break
}
})
});
// deal with someone is the author of a line and changes one character, so the alines won't change
if(lineChanged === undefined) {
if (lineChanged === undefined) {
lineChanged = Changeset.opIterator(Changeset.unpack(changeset).ops).next().lines;
}
@ -268,106 +241,94 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
updateTimer();
var authors = _.map(padContents.getActiveAuthors(), function(name) {
return authorData[name];
});
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors);
}
function updateTimer() {
var zpad = function(str, length) {
str = str + "";
while (str.length < length)
str = '0' + str;
return str;
}
var date = new Date(padContents.currentTime);
var dateFormat = function() {
var month = zpad(date.getMonth() + 1, 2);
var day = zpad(date.getDate(), 2);
var year = (date.getFullYear());
var hours = zpad(date.getHours(), 2);
var minutes = zpad(date.getMinutes(), 2);
var seconds = zpad(date.getSeconds(), 2);
return (html10n.get("timeslider.dateformat", {
"day": day,
"month": month,
"year": year,
"hours": hours,
"minutes": minutes,
"seconds": seconds
}));
}
const zpad = function (str, length) {
str = `${str}`;
while (str.length < length) str = `0${str}`;
return str;
};
const date = new Date(padContents.currentTime);
const dateFormat = function () {
const month = zpad(date.getMonth() + 1, 2);
const day = zpad(date.getDate(), 2);
const year = (date.getFullYear());
const hours = zpad(date.getHours(), 2);
const minutes = zpad(date.getMinutes(), 2);
const seconds = zpad(date.getSeconds(), 2);
return (html10n.get('timeslider.dateformat', {
day,
month,
year,
hours,
minutes,
seconds,
}));
};
$('#timer').html(dateFormat());
var revisionDate = html10n.get("timeslider.saved", {
"day": date.getDate(),
"month": [
html10n.get("timeslider.month.january"),
html10n.get("timeslider.month.february"),
html10n.get("timeslider.month.march"),
html10n.get("timeslider.month.april"),
html10n.get("timeslider.month.may"),
html10n.get("timeslider.month.june"),
html10n.get("timeslider.month.july"),
html10n.get("timeslider.month.august"),
html10n.get("timeslider.month.september"),
html10n.get("timeslider.month.october"),
html10n.get("timeslider.month.november"),
html10n.get("timeslider.month.december")
][date.getMonth()],
"year": date.getFullYear()
const revisionDate = html10n.get('timeslider.saved', {
day: date.getDate(),
month: [
html10n.get('timeslider.month.january'),
html10n.get('timeslider.month.february'),
html10n.get('timeslider.month.march'),
html10n.get('timeslider.month.april'),
html10n.get('timeslider.month.may'),
html10n.get('timeslider.month.june'),
html10n.get('timeslider.month.july'),
html10n.get('timeslider.month.august'),
html10n.get('timeslider.month.september'),
html10n.get('timeslider.month.october'),
html10n.get('timeslider.month.november'),
html10n.get('timeslider.month.december'),
][date.getMonth()],
year: date.getFullYear(),
});
$('#revision_date').html(revisionDate)
$('#revision_date').html(revisionDate);
}
updateTimer();
function goToRevision(newRevision) {
padContents.targetRevision = newRevision;
var self = this;
var path = revisionInfo.getPath(padContents.currentRevision, newRevision);
const self = this;
const path = revisionInfo.getPath(padContents.currentRevision, newRevision);
hooks.aCallAll('goToRevisionEvent', {
rev: newRevision
rev: newRevision,
});
if (path.status == 'complete')
{
if (path.status == 'complete') {
var cs = path.changesets;
var changeset = cs[0];
var timeDelta = path.times[0];
for (var i = 1; i < cs.length; i++)
{
for (var i = 1; i < cs.length; i++) {
changeset = Changeset.compose(changeset, cs[i], padContents.apool);
timeDelta += path.times[i];
}
if (changeset) applyChangeset(changeset, path.rev, true, timeDelta);
}
else if (path.status == "partial")
{
var sliderLocation = padContents.currentRevision;
} else if (path.status == 'partial') {
const sliderLocation = padContents.currentRevision;
// callback is called after changeset information is pulled from server
// this may never get called, if the changeset has already been loaded
var update = function(start, end) {
// if we've called goToRevision in the time since, don't goToRevision
goToRevision(padContents.targetRevision);
};
const update = function (start, end) {
// if we've called goToRevision in the time since, don't goToRevision
goToRevision(padContents.targetRevision);
};
// do our best with what we have...
var cs = path.changesets;
var changeset = cs[0];
var timeDelta = path.times[0];
for (var i = 1; i < cs.length; i++)
{
for (var i = 1; i < cs.length; i++) {
changeset = Changeset.compose(changeset, cs[i], padContents.apool);
timeDelta += path.times[i];
}
@ -379,21 +340,17 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
loadChangesetsForRevision(padContents.currentRevision - 1);
}
var authors = _.map(padContents.getActiveAuthors(), function(name){
return authorData[name];
});
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors);
}
function loadChangesetsForRevision(revision, callback) {
if (BroadcastSlider.getSliderLength() > 10000)
{
if (BroadcastSlider.getSliderLength() > 10000) {
var start = (Math.floor((revision) / 10000) * 10000); // revision 0 to 10
changesetLoader.queueUp(start, 100);
}
if (BroadcastSlider.getSliderLength() > 1000)
{
if (BroadcastSlider.getSliderLength() > 1000) {
var start = (Math.floor((revision) / 1000) * 1000); // (start from -1, go to 19) + 1
changesetLoader.queueUp(start, 10);
}
@ -410,167 +367,146 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
requestQueue2: [],
requestQueue3: [],
reqCallbacks: [],
queueUp: function(revision, width, callback) {
queueUp(revision, width, callback) {
if (revision < 0) revision = 0;
// if(changesetLoader.requestQueue.indexOf(revision) != -1)
// return; // already in the queue.
if (changesetLoader.resolved.indexOf(revision + "_" + width) != -1) return; // already loaded from the server
changesetLoader.resolved.push(revision + "_" + width);
if (changesetLoader.resolved.indexOf(`${revision}_${width}`) != -1) return; // already loaded from the server
changesetLoader.resolved.push(`${revision}_${width}`);
var requestQueue = width == 1 ? changesetLoader.requestQueue3 : width == 10 ? changesetLoader.requestQueue2 : changesetLoader.requestQueue1;
const requestQueue = width == 1 ? changesetLoader.requestQueue3 : width == 10 ? changesetLoader.requestQueue2 : changesetLoader.requestQueue1;
requestQueue.push(
{
'rev': revision,
'res': width,
'callback': callback
});
if (!changesetLoader.running)
{
{
rev: revision,
res: width,
callback,
});
if (!changesetLoader.running) {
changesetLoader.running = true;
setTimeout(changesetLoader.loadFromQueue, 10);
}
},
loadFromQueue: function() {
var self = changesetLoader;
var requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : self.requestQueue2.length > 0 ? self.requestQueue2 : self.requestQueue3.length > 0 ? self.requestQueue3 : null;
loadFromQueue() {
const self = changesetLoader;
const requestQueue = self.requestQueue1.length > 0 ? self.requestQueue1 : self.requestQueue2.length > 0 ? self.requestQueue2 : self.requestQueue3.length > 0 ? self.requestQueue3 : null;
if (!requestQueue)
{
if (!requestQueue) {
self.running = false;
return;
}
var request = requestQueue.pop();
var granularity = request.res;
var callback = request.callback;
var start = request.rev;
var requestID = Math.floor(Math.random() * 100000);
const request = requestQueue.pop();
const granularity = request.res;
const callback = request.callback;
const start = request.rev;
const requestID = Math.floor(Math.random() * 100000);
sendSocketMsg("CHANGESET_REQ", {
"start": start,
"granularity": granularity,
"requestID": requestID
sendSocketMsg('CHANGESET_REQ', {
start,
granularity,
requestID,
});
self.reqCallbacks[requestID] = callback;
},
handleSocketResponse: function(message) {
var self = changesetLoader;
handleSocketResponse(message) {
const self = changesetLoader;
var start = message.data.start;
var granularity = message.data.granularity;
var callback = self.reqCallbacks[message.data.requestID];
const start = message.data.start;
const granularity = message.data.granularity;
const callback = self.reqCallbacks[message.data.requestID];
delete self.reqCallbacks[message.data.requestID];
self.handleResponse(message.data, start, granularity, callback);
setTimeout(self.loadFromQueue, 10);
},
handleResponse: function(data, start, granularity, callback) {
var pool = (new AttribPool()).fromJsonable(data.apool);
for (var i = 0; i < data.forwardsChangesets.length; i++)
{
var astart = start + i * granularity - 1; // rev -1 is a blank single line
var aend = start + (i + 1) * granularity - 1; // totalRevs is the most recent revision
handleResponse(data, start, granularity, callback) {
const pool = (new AttribPool()).fromJsonable(data.apool);
for (let i = 0; i < data.forwardsChangesets.length; i++) {
const astart = start + i * granularity - 1; // rev -1 is a blank single line
let aend = start + (i + 1) * granularity - 1; // totalRevs is the most recent revision
if (aend > data.actualEndNum - 1) aend = data.actualEndNum - 1;
//debugLog("adding changeset:", astart, aend);
var forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool);
var backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool);
// debugLog("adding changeset:", astart, aend);
const forwardcs = Changeset.moveOpsToNewPool(data.forwardsChangesets[i], pool, padContents.apool);
const backwardcs = Changeset.moveOpsToNewPool(data.backwardsChangesets[i], pool, padContents.apool);
revisionInfo.addChangeset(astart, aend, forwardcs, backwardcs, data.timeDeltas[i]);
}
if (callback) callback(start - 1, start + data.forwardsChangesets.length * granularity - 1);
},
handleMessageFromServer: function (obj) {
if (obj.type == "COLLABROOM")
{
handleMessageFromServer(obj) {
if (obj.type == 'COLLABROOM') {
obj = obj.data;
if (obj.type == "NEW_CHANGES")
{
var changeset = Changeset.moveOpsToNewPool(
obj.changeset, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
if (obj.type == 'NEW_CHANGES') {
const changeset = Changeset.moveOpsToNewPool(
obj.changeset, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
var changesetBack = Changeset.inverse(
obj.changeset, padContents.currentLines, padContents.alines, padContents.apool);
obj.changeset, padContents.currentLines, padContents.alines, padContents.apool);
var changesetBack = Changeset.moveOpsToNewPool(
changesetBack, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
changesetBack, (new AttribPool()).fromJsonable(obj.apool), padContents.apool);
loadedNewChangeset(changeset, changesetBack, obj.newRev - 1, obj.timeDelta);
}
else if (obj.type == "NEW_AUTHORDATA")
{
var authorMap = {};
} else if (obj.type == 'NEW_AUTHORDATA') {
const authorMap = {};
authorMap[obj.author] = obj.data;
receiveAuthorData(authorMap);
var authors = _.map(padContents.getActiveAuthors(), function(name) {
return authorData[name];
});
const authors = _.map(padContents.getActiveAuthors(), (name) => authorData[name]);
BroadcastSlider.setAuthors(authors);
}
else if (obj.type == "NEW_SAVEDREV")
{
var savedRev = obj.savedRev;
} else if (obj.type == 'NEW_SAVEDREV') {
const savedRev = obj.savedRev;
BroadcastSlider.addSavedRevision(savedRev.revNum, savedRev);
}
hooks.callAll('handleClientTimesliderMessage_' + obj.type, {payload: obj});
}
else if(obj.type == "CHANGESET_REQ")
{
hooks.callAll(`handleClientTimesliderMessage_${obj.type}`, {payload: obj});
} else if (obj.type == 'CHANGESET_REQ') {
changesetLoader.handleSocketResponse(obj);
} else {
debugLog(`Unknown message type: ${obj.type}`);
}
else
{
debugLog("Unknown message type: " + obj.type);
}
}
},
};
// to start upon window load, just push a function onto this array
//window['onloadFuncts'].push(setUpSocket);
//window['onloadFuncts'].push(function ()
fireWhenAllScriptsAreLoaded.push(function() {
// window['onloadFuncts'].push(setUpSocket);
// window['onloadFuncts'].push(function ()
fireWhenAllScriptsAreLoaded.push(() => {
// set up the currentDivs and DOM
padContents.currentDivs = [];
$("#innerdocbody").html("");
for (var i = 0; i < padContents.currentLines.length; i++)
{
var div = padContents.lineToElement(padContents.currentLines[i], padContents.alines[i]);
$('#innerdocbody').html('');
for (let i = 0; i < padContents.currentLines.length; i++) {
const div = padContents.lineToElement(padContents.currentLines[i], padContents.alines[i]);
padContents.currentDivs.push(div);
$("#innerdocbody").append(div);
$('#innerdocbody').append(div);
}
});
// this is necessary to keep infinite loops of events firing,
// since goToRevision changes the slider position
var goToRevisionIfEnabledCount = 0;
var goToRevisionIfEnabled = function() {
if (goToRevisionIfEnabledCount > 0)
{
const goToRevisionIfEnabled = function () {
if (goToRevisionIfEnabledCount > 0) {
goToRevisionIfEnabledCount--;
}
else
{
} else {
goToRevision.apply(goToRevision, arguments);
}
}
};
BroadcastSlider.onSlider(goToRevisionIfEnabled);
var dynamicCSS = makeCSSManager('dynamicsyntax');
const dynamicCSS = makeCSSManager('dynamicsyntax');
var authorData = {};
function receiveAuthorData(newAuthorData) {
for (var author in newAuthorData)
{
var data = newAuthorData[author];
var bgcolor = typeof data.colorId == "number" ? clientVars.colorPalette[data.colorId] : data.colorId;
if (bgcolor && dynamicCSS)
{
var selector = dynamicCSS.selectorStyle('.' + linestylefilter.getAuthorClassName(author));
selector.backgroundColor = bgcolor
selector.color = (colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5) ? '#ffffff' : '#000000'; //see ace2_inner.js for the other part
for (const author in newAuthorData) {
const data = newAuthorData[author];
const bgcolor = typeof data.colorId === 'number' ? clientVars.colorPalette[data.colorId] : data.colorId;
if (bgcolor && dynamicCSS) {
const selector = dynamicCSS.selectorStyle(`.${linestylefilter.getAuthorClassName(author)}`);
selector.backgroundColor = bgcolor;
selector.color = (colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5) ? '#ffffff' : '#000000'; // see ace2_inner.js for the other part
}
authorData[author] = data;
}
@ -580,15 +516,15 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro
return changesetLoader;
function goToLineNumber(lineNumber){
function goToLineNumber(lineNumber) {
// Sets the Y scrolling of the browser to go to this line
var line = $('#innerdocbody').find("div:nth-child("+(lineNumber+1)+")");
var newY = $(line)[0].offsetTop;
var ecb = document.getElementById('editorcontainerbox');
const line = $('#innerdocbody').find(`div:nth-child(${lineNumber + 1})`);
const newY = $(line)[0].offsetTop;
const ecb = document.getElementById('editorcontainerbox');
// Chrome 55 - 59 bugfix
if(ecb.scrollTo){
if (ecb.scrollTo) {
ecb.scrollTo({top: newY, behavior: 'smooth'});
}else{
} else {
$('#editorcontainerbox').scrollTop(newY);
}
}

View file

@ -29,68 +29,60 @@ function loadBroadcastRevisionsJS() {
this.changesets = [];
}
Revision.prototype.addChangeset = function(destIndex, changeset, timeDelta) {
var changesetWrapper = {
Revision.prototype.addChangeset = function (destIndex, changeset, timeDelta) {
const changesetWrapper = {
deltaRev: destIndex - this.rev,
deltaTime: timeDelta,
getValue: function() {
getValue() {
return changeset;
}
},
};
this.changesets.push(changesetWrapper);
this.changesets.sort(function(a, b) {
return (b.deltaRev - a.deltaRev)
});
}
this.changesets.sort((a, b) => (b.deltaRev - a.deltaRev));
};
revisionInfo = {};
revisionInfo.addChangeset = function(fromIndex, toIndex, changeset, backChangeset, timeDelta) {
var startRevision = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
var endRevision = revisionInfo[toIndex] || revisionInfo.createNew(toIndex);
revisionInfo.addChangeset = function (fromIndex, toIndex, changeset, backChangeset, timeDelta) {
const startRevision = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
const endRevision = revisionInfo[toIndex] || revisionInfo.createNew(toIndex);
startRevision.addChangeset(toIndex, changeset, timeDelta);
endRevision.addChangeset(fromIndex, backChangeset, -1 * timeDelta);
}
};
revisionInfo.latest = clientVars.collab_client_vars.rev || -1;
revisionInfo.createNew = function(index) {
revisionInfo.createNew = function (index) {
revisionInfo[index] = new Revision(index);
if (index > revisionInfo.latest)
{
if (index > revisionInfo.latest) {
revisionInfo.latest = index;
}
return revisionInfo[index];
}
};
// assuming that there is a path from fromIndex to toIndex, and that the links
// are laid out in a skip-list format
revisionInfo.getPath = function(fromIndex, toIndex) {
var changesets = [];
var spans = [];
var times = [];
var elem = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
if (elem.changesets.length != 0 && fromIndex != toIndex)
{
var reverse = !(fromIndex < toIndex)
while (((elem.rev < toIndex) && !reverse) || ((elem.rev > toIndex) && reverse))
{
var couldNotContinue = false;
var oldRev = elem.rev;
revisionInfo.getPath = function (fromIndex, toIndex) {
const changesets = [];
const spans = [];
const times = [];
let elem = revisionInfo[fromIndex] || revisionInfo.createNew(fromIndex);
if (elem.changesets.length != 0 && fromIndex != toIndex) {
const reverse = !(fromIndex < toIndex);
while (((elem.rev < toIndex) && !reverse) || ((elem.rev > toIndex) && reverse)) {
let couldNotContinue = false;
const oldRev = elem.rev;
for (var i = reverse ? elem.changesets.length - 1 : 0;
reverse ? i >= 0 : i < elem.changesets.length;
i += reverse ? -1 : 1)
{
if (((elem.changesets[i].deltaRev < 0) && !reverse) || ((elem.changesets[i].deltaRev > 0) && reverse))
{
for (let i = reverse ? elem.changesets.length - 1 : 0;
reverse ? i >= 0 : i < elem.changesets.length;
i += reverse ? -1 : 1) {
if (((elem.changesets[i].deltaRev < 0) && !reverse) || ((elem.changesets[i].deltaRev > 0) && reverse)) {
couldNotContinue = true;
break;
}
if (((elem.rev + elem.changesets[i].deltaRev <= toIndex) && !reverse) || ((elem.rev + elem.changesets[i].deltaRev >= toIndex) && reverse))
{
var topush = elem.changesets[i];
if (((elem.rev + elem.changesets[i].deltaRev <= toIndex) && !reverse) || ((elem.rev + elem.changesets[i].deltaRev >= toIndex) && reverse)) {
const topush = elem.changesets[i];
changesets.push(topush.getValue());
spans.push(elem.changesets[i].deltaRev);
times.push(topush.deltaTime);
@ -103,18 +95,18 @@ function loadBroadcastRevisionsJS() {
}
}
var status = 'partial';
let status = 'partial';
if (elem.rev == toIndex) status = 'complete';
return {
'fromRev': fromIndex,
'rev': elem.rev,
'status': status,
'changesets': changesets,
'spans': spans,
'times': times
fromRev: fromIndex,
rev: elem.rev,
status,
changesets,
spans,
times,
};
}
};
}
exports.loadBroadcastRevisionsJS = loadBroadcastRevisionsJS;

View file

@ -20,62 +20,60 @@
* limitations under the License.
*/
// These parameters were global, now they are injected. A reference to the
// Timeslider controller would probably be more appropriate.
var _ = require('./underscore');
var padmodals = require('./pad_modals').padmodals;
var colorutils = require('./colorutils').colorutils;
// These parameters were global, now they are injected. A reference to the
// Timeslider controller would probably be more appropriate.
const _ = require('./underscore');
const padmodals = require('./pad_modals').padmodals;
const colorutils = require('./colorutils').colorutils;
function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
var BroadcastSlider;
let BroadcastSlider;
// Hack to ensure timeslider i18n values are in
$("[data-key='timeslider_returnToPad'] > a > span").html(html10n.get("timeslider.toolbar.returnbutton"));
$("[data-key='timeslider_returnToPad'] > a > span").html(html10n.get('timeslider.toolbar.returnbutton'));
(function() { // wrap this code in its own namespace
var sliderLength = 1000;
var sliderPos = 0;
var sliderActive = false;
var slidercallbacks = [];
var savedRevisions = [];
var sliderPlaying = false;
(function () { // wrap this code in its own namespace
let sliderLength = 1000;
let sliderPos = 0;
let sliderActive = false;
const slidercallbacks = [];
const savedRevisions = [];
let sliderPlaying = false;
var _callSliderCallbacks = function(newval) {
sliderPos = newval;
for (var i = 0; i < slidercallbacks.length; i++)
{
slidercallbacks[i](newval);
}
const _callSliderCallbacks = function (newval) {
sliderPos = newval;
for (let i = 0; i < slidercallbacks.length; i++) {
slidercallbacks[i](newval);
}
};
var updateSliderElements = function() {
for (var i = 0; i < savedRevisions.length; i++)
{
var position = parseInt(savedRevisions[i].attr('pos'));
savedRevisions[i].css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1);
}
$("#ui-slider-handle").css('left', sliderPos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0));
const updateSliderElements = function () {
for (let i = 0; i < savedRevisions.length; i++) {
const position = parseInt(savedRevisions[i].attr('pos'));
savedRevisions[i].css('left', (position * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0)) - 1);
}
$('#ui-slider-handle').css('left', sliderPos * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0));
};
var addSavedRevision = function(position, info) {
var newSavedRevision = $('<div></div>');
newSavedRevision.addClass("star");
const addSavedRevision = function (position, info) {
const newSavedRevision = $('<div></div>');
newSavedRevision.addClass('star');
newSavedRevision.attr('pos', position);
newSavedRevision.css('left', (position * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0)) - 1);
$("#ui-slider-bar").append(newSavedRevision);
newSavedRevision.mouseup(function(evt) {
BroadcastSlider.setSliderPosition(position);
});
savedRevisions.push(newSavedRevision);
};
newSavedRevision.attr('pos', position);
newSavedRevision.css('left', (position * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0)) - 1);
$('#ui-slider-bar').append(newSavedRevision);
newSavedRevision.mouseup((evt) => {
BroadcastSlider.setSliderPosition(position);
});
savedRevisions.push(newSavedRevision);
};
var removeSavedRevision = function(position) {
var element = $("div.star [pos=" + position + "]");
savedRevisions.remove(element);
element.remove();
return element;
};
const removeSavedRevision = function (position) {
const element = $(`div.star [pos=${position}]`);
savedRevisions.remove(element);
element.remove();
return element;
};
/* Begin small 'API' */
@ -90,19 +88,19 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
function setSliderPosition(newpos) {
newpos = Number(newpos);
if (newpos < 0 || newpos > sliderLength) return;
if(!newpos){
if (!newpos) {
newpos = 0; // stops it from displaying NaN if newpos isn't set
}
window.location.hash = "#" + newpos;
$("#ui-slider-handle").css('left', newpos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0));
$("a.tlink").map(function() {
$(this).attr('href', $(this).attr('thref').replace("%revision%", newpos));
window.location.hash = `#${newpos}`;
$('#ui-slider-handle').css('left', newpos * ($('#ui-slider-bar').width() - 2) / (sliderLength * 1.0));
$('a.tlink').map(function () {
$(this).attr('href', $(this).attr('thref').replace('%revision%', newpos));
});
$("#revision_label").html(html10n.get("timeslider.version", { "version": newpos}));
$('#revision_label').html(html10n.get('timeslider.version', {version: newpos}));
$("#leftstar, #leftstep").toggleClass('disabled', newpos == 0);
$("#rightstar, #rightstep").toggleClass('disabled', newpos == sliderLength);
$('#leftstar, #leftstep').toggleClass('disabled', newpos == 0);
$('#rightstar, #rightstep').toggleClass('disabled', newpos == sliderLength);
sliderPos = newpos;
_callSliderCallbacks(newpos);
@ -120,89 +118,80 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
// just take over the whole slider screen with a reconnect message
function showReconnectUI() {
padmodals.showModal("disconnected");
padmodals.showModal('disconnected');
}
function setAuthors(authors) {
var authorsList = $("#authorsList");
const authorsList = $('#authorsList');
authorsList.empty();
var numAnonymous = 0;
var numNamed = 0;
var colorsAnonymous = [];
_.each(authors, function(author) {
if(author)
{
var authorColor = clientVars.colorPalette[author.colorId] || author.colorId;
if (author.name)
{
let numAnonymous = 0;
let numNamed = 0;
const colorsAnonymous = [];
_.each(authors, (author) => {
if (author) {
const authorColor = clientVars.colorPalette[author.colorId] || author.colorId;
if (author.name) {
if (numNamed !== 0) authorsList.append(', ');
var textColor = colorutils.textColorFromBackgroundColor(authorColor, clientVars.skinName)
const textColor = colorutils.textColorFromBackgroundColor(authorColor, clientVars.skinName);
$('<span />')
.text(author.name || "unnamed")
.css('background-color', authorColor)
.css('color', textColor)
.addClass('author')
.appendTo(authorsList);
.text(author.name || 'unnamed')
.css('background-color', authorColor)
.css('color', textColor)
.addClass('author')
.appendTo(authorsList);
numNamed++;
}
else
{
} else {
numAnonymous++;
if(authorColor) colorsAnonymous.push(authorColor);
if (authorColor) colorsAnonymous.push(authorColor);
}
}
});
if (numAnonymous > 0)
{
var anonymousAuthorString = html10n.get("timeslider.unnamedauthors", { num: numAnonymous });
if (numAnonymous > 0) {
const anonymousAuthorString = html10n.get('timeslider.unnamedauthors', {num: numAnonymous});
if (numNamed !== 0){
authorsList.append(' + ' + anonymousAuthorString);
if (numNamed !== 0) {
authorsList.append(` + ${anonymousAuthorString}`);
} else {
authorsList.append(anonymousAuthorString);
}
if(colorsAnonymous.length > 0){
if (colorsAnonymous.length > 0) {
authorsList.append(' (');
_.each(colorsAnonymous, function(color, i){
if( i > 0 ) authorsList.append(' ');
_.each(colorsAnonymous, (color, i) => {
if (i > 0) authorsList.append(' ');
$('<span>&nbsp;</span>')
.css('background-color', color)
.addClass('author author-anonymous')
.appendTo(authorsList);
.css('background-color', color)
.addClass('author author-anonymous')
.appendTo(authorsList);
});
authorsList.append(')');
}
}
if (authors.length == 0)
{
authorsList.append(html10n.get("timeslider.toolbar.authorsList"));
if (authors.length == 0) {
authorsList.append(html10n.get('timeslider.toolbar.authorsList'));
}
}
BroadcastSlider = {
onSlider: onSlider,
getSliderPosition: getSliderPosition,
setSliderPosition: setSliderPosition,
getSliderLength: getSliderLength,
setSliderLength: setSliderLength,
isSliderActive: function() {
onSlider,
getSliderPosition,
setSliderPosition,
getSliderLength,
setSliderLength,
isSliderActive() {
return sliderActive;
},
playpause: playpause,
addSavedRevision: addSavedRevision,
showReconnectUI: showReconnectUI,
setAuthors: setAuthors
}
playpause,
addSavedRevision,
showReconnectUI,
setAuthors,
};
function playButtonUpdater() {
if (sliderPlaying)
{
if (getSliderPosition() + 1 > sliderLength)
{
$("#playpause_button_icon").toggleClass('pause');
if (sliderPlaying) {
if (getSliderPosition() + 1 > sliderLength) {
$('#playpause_button_icon').toggleClass('pause');
sliderPlaying = false;
return;
}
@ -213,156 +202,142 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded) {
}
function playpause() {
$("#playpause_button_icon").toggleClass('pause');
$('#playpause_button_icon').toggleClass('pause');
if (!sliderPlaying)
{
if (!sliderPlaying) {
if (getSliderPosition() == sliderLength) setSliderPosition(0);
sliderPlaying = true;
playButtonUpdater();
}
else
{
} else {
sliderPlaying = false;
}
}
// assign event handlers to html UI elements after page load
fireWhenAllScriptsAreLoaded.push(function() {
$(document).keyup(function(e) {
fireWhenAllScriptsAreLoaded.push(() => {
$(document).keyup((e) => {
if (!e) var e = window.event;
var code = e.keyCode || e.which;
const code = e.keyCode || e.which;
if (code == 37)
{ // left
if (code == 37) { // left
if (e.shiftKey) {
$('#leftstar').click();
} else {
$('#leftstep').click();
}
}
else if (code == 39)
{ // right
} else if (code == 39) { // right
if (e.shiftKey) {
$('#rightstar').click();
} else {
$('#rightstep').click();
}
}
else if (code == 32)
{ // spacebar
$("#playpause_button_icon").trigger('click');
} else if (code == 32) { // spacebar
$('#playpause_button_icon').trigger('click');
}
});
// Resize
$(window).resize(function() {
$(window).resize(() => {
updateSliderElements();
});
// Slider click
$("#ui-slider-bar").mousedown(function(evt) {
$("#ui-slider-handle").css('left', (evt.clientX - $("#ui-slider-bar").offset().left));
$("#ui-slider-handle").trigger(evt);
$('#ui-slider-bar').mousedown((evt) => {
$('#ui-slider-handle').css('left', (evt.clientX - $('#ui-slider-bar').offset().left));
$('#ui-slider-handle').trigger(evt);
});
// Slider dragging
$("#ui-slider-handle").mousedown(function(evt) {
$('#ui-slider-handle').mousedown(function (evt) {
this.startLoc = evt.clientX;
this.currentLoc = parseInt($(this).css('left'));
var self = this;
const self = this;
sliderActive = true;
$(document).mousemove(function(evt2) {
$(self).css('pointer', 'move')
var newloc = self.currentLoc + (evt2.clientX - self.startLoc);
$(document).mousemove((evt2) => {
$(self).css('pointer', 'move');
let newloc = self.currentLoc + (evt2.clientX - self.startLoc);
if (newloc < 0) newloc = 0;
if (newloc > ($("#ui-slider-bar").width() - 2)) newloc = ($("#ui-slider-bar").width() - 2);
$("#revision_label").html(html10n.get("timeslider.version", { "version": Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))}));
if (newloc > ($('#ui-slider-bar').width() - 2)) newloc = ($('#ui-slider-bar').width() - 2);
$('#revision_label').html(html10n.get('timeslider.version', {version: Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2))}));
$(self).css('left', newloc);
if (getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2))) _callSliderCallbacks(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2)))
if (getSliderPosition() != Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2))) _callSliderCallbacks(Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2)));
});
$(document).mouseup(function(evt2) {
$(document).mouseup((evt2) => {
$(document).unbind('mousemove');
$(document).unbind('mouseup');
sliderActive = false;
var newloc = self.currentLoc + (evt2.clientX - self.startLoc);
let newloc = self.currentLoc + (evt2.clientX - self.startLoc);
if (newloc < 0) newloc = 0;
if (newloc > ($("#ui-slider-bar").width() - 2)) newloc = ($("#ui-slider-bar").width() - 2);
if (newloc > ($('#ui-slider-bar').width() - 2)) newloc = ($('#ui-slider-bar').width() - 2);
$(self).css('left', newloc);
// if(getSliderPosition() != Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width()-2)))
setSliderPosition(Math.floor(newloc * sliderLength / ($("#ui-slider-bar").width() - 2)))
if(parseInt($(self).css('left')) < 2){
setSliderPosition(Math.floor(newloc * sliderLength / ($('#ui-slider-bar').width() - 2)));
if (parseInt($(self).css('left')) < 2) {
$(self).css('left', '2px');
}else{
} else {
self.currentLoc = parseInt($(self).css('left'));
}
});
})
});
// play/pause toggling
$("#playpause_button_icon").click(function(evt) {
$('#playpause_button_icon').click((evt) => {
BroadcastSlider.playpause();
});
// next/prev saved revision and changeset
$('.stepper').click(function(evt) {
switch ($(this).attr("id")) {
case "leftstep":
$('.stepper').click(function (evt) {
switch ($(this).attr('id')) {
case 'leftstep':
setSliderPosition(getSliderPosition() - 1);
break;
case "rightstep":
case 'rightstep':
setSliderPosition(getSliderPosition() + 1);
break;
case "leftstar":
case 'leftstar':
var nextStar = 0; // default to first revision in document
for (var i = 0; i < savedRevisions.length; i++)
{
for (var i = 0; i < savedRevisions.length; i++) {
var pos = parseInt(savedRevisions[i].attr('pos'));
if (pos < getSliderPosition() && nextStar < pos) nextStar = pos;
}
setSliderPosition(nextStar);
break;
case "rightstar":
case 'rightstar':
var nextStar = sliderLength; // default to last revision in document
for (var i = 0; i < savedRevisions.length; i++)
{
for (var i = 0; i < savedRevisions.length; i++) {
var pos = parseInt(savedRevisions[i].attr('pos'));
if (pos > getSliderPosition() && nextStar > pos) nextStar = pos;
}
setSliderPosition(nextStar);
break;
}
})
});
if (clientVars)
{
$("#timeslider-wrapper").show();
if (clientVars) {
$('#timeslider-wrapper').show();
var startPos = clientVars.collab_client_vars.rev;
if(window.location.hash.length > 1)
{
var hashRev = Number(window.location.hash.substr(1));
if(!isNaN(hashRev))
{
const startPos = clientVars.collab_client_vars.rev;
if (window.location.hash.length > 1) {
const hashRev = Number(window.location.hash.substr(1));
if (!isNaN(hashRev)) {
// this is necessary because of the socket.io-event which loads the changesets
setTimeout(function() { setSliderPosition(hashRev); }, 1);
setTimeout(() => { setSliderPosition(hashRev); }, 1);
}
}
setSliderLength(clientVars.collab_client_vars.rev);
setSliderPosition(clientVars.collab_client_vars.rev);
_.each(clientVars.savedRevisions, function(revision) {
_.each(clientVars.savedRevisions, (revision) => {
addSavedRevision(revision.revNum, revision);
})
});
}
});
})();
BroadcastSlider.onSlider(function(loc) {
$("#viewlatest").html(loc == BroadcastSlider.getSliderLength() ? "Viewing latest content" : "View latest content");
})
BroadcastSlider.onSlider((loc) => {
$('#viewlatest').html(loc == BroadcastSlider.getSliderLength() ? 'Viewing latest content' : 'View latest content');
});
return BroadcastSlider;
}

View file

@ -2,39 +2,39 @@
// This function is useful to get the caret position of the line as
// is represented by the browser
exports.getPosition = function () {
var rect, line;
var editor = $('#innerdocbody')[0];
var range = getSelectionRange();
var isSelectionInsideTheEditor = range && $(range.endContainer).closest('body')[0].id === 'innerdocbody';
let rect, line;
const editor = $('#innerdocbody')[0];
const range = getSelectionRange();
const isSelectionInsideTheEditor = range && $(range.endContainer).closest('body')[0].id === 'innerdocbody';
if(isSelectionInsideTheEditor){
if (isSelectionInsideTheEditor) {
// when we have the caret in an empty line, e.g. a line with only a <br>,
// getBoundingClientRect() returns all dimensions value as 0
var selectionIsInTheBeginningOfLine = range.endOffset > 0;
const selectionIsInTheBeginningOfLine = range.endOffset > 0;
if (selectionIsInTheBeginningOfLine) {
var clonedRange = createSelectionRange(range);
line = getPositionOfElementOrSelection(clonedRange);
clonedRange.detach()
clonedRange.detach();
}
// when there's a <br> or any element that has no height, we can't get
// the dimension of the element where the caret is
if(!rect || rect.height === 0){
if (!rect || rect.height === 0) {
var clonedRange = createSelectionRange(range);
// as we can't get the element height, we create a text node to get the dimensions
// on the position
var shadowCaret = $(document.createTextNode("|"));
const shadowCaret = $(document.createTextNode('|'));
clonedRange.insertNode(shadowCaret[0]);
clonedRange.selectNode(shadowCaret[0]);
line = getPositionOfElementOrSelection(clonedRange);
clonedRange.detach()
clonedRange.detach();
shadowCaret.remove();
}
}
return line;
}
};
var createSelectionRange = function (range) {
clonedRange = range.cloneRange();
@ -46,9 +46,9 @@ var createSelectionRange = function (range) {
clonedRange.setStart(range.endContainer, range.endOffset);
clonedRange.setEnd(range.endContainer, range.endOffset);
return clonedRange;
}
};
var getPositionOfRepLineAtOffset = function (node, offset) {
const getPositionOfRepLineAtOffset = function (node, offset) {
// it is not a text node, so we cannot make a selection
if (node.tagName === 'BR' || node.tagName === 'EMPTY') {
return getPositionOfElementOrSelection(node);
@ -58,21 +58,21 @@ var getPositionOfRepLineAtOffset = function (node, offset) {
node = node.nextSibling;
}
var newRange = new Range();
const newRange = new Range();
newRange.setStart(node, offset);
newRange.setEnd(node, offset);
var linePosition = getPositionOfElementOrSelection(newRange);
const linePosition = getPositionOfElementOrSelection(newRange);
newRange.detach(); // performance sake
return linePosition;
}
};
function getPositionOfElementOrSelection(element) {
var rect = element.getBoundingClientRect();
var linePosition = {
const rect = element.getBoundingClientRect();
const linePosition = {
bottom: rect.bottom,
height: rect.height,
top: rect.top
}
top: rect.top,
};
return linePosition;
}
@ -82,67 +82,66 @@ function getPositionOfElementOrSelection(element) {
// of the previous line
// [2] the line before is part of another rep line. It's possible this line has different margins
// height. So we have to get the exactly position of the line
exports.getPositionTopOfPreviousBrowserLine = function(caretLinePosition, rep) {
var previousLineTop = caretLinePosition.top - caretLinePosition.height; // [1]
var isCaretLineFirstBrowserLine = caretLineIsFirstBrowserLine(caretLinePosition.top, rep);
exports.getPositionTopOfPreviousBrowserLine = function (caretLinePosition, rep) {
let previousLineTop = caretLinePosition.top - caretLinePosition.height; // [1]
const isCaretLineFirstBrowserLine = caretLineIsFirstBrowserLine(caretLinePosition.top, rep);
// the caret is in the beginning of a rep line, so the previous browser line
// is the last line browser line of the a rep line
if (isCaretLineFirstBrowserLine) { //[2]
var lineBeforeCaretLine = rep.selStart[0] - 1;
var firstLineVisibleBeforeCaretLine = getPreviousVisibleLine(lineBeforeCaretLine, rep);
var linePosition = getDimensionOfLastBrowserLineOfRepLine(firstLineVisibleBeforeCaretLine, rep);
if (isCaretLineFirstBrowserLine) { // [2]
const lineBeforeCaretLine = rep.selStart[0] - 1;
const firstLineVisibleBeforeCaretLine = getPreviousVisibleLine(lineBeforeCaretLine, rep);
const linePosition = getDimensionOfLastBrowserLineOfRepLine(firstLineVisibleBeforeCaretLine, rep);
previousLineTop = linePosition.top;
}
return previousLineTop;
}
};
function caretLineIsFirstBrowserLine(caretLineTop, rep) {
var caretRepLine = rep.selStart[0];
var lineNode = rep.lines.atIndex(caretRepLine).lineNode;
var firstRootNode = getFirstRootChildNode(lineNode);
const caretRepLine = rep.selStart[0];
const lineNode = rep.lines.atIndex(caretRepLine).lineNode;
const firstRootNode = getFirstRootChildNode(lineNode);
// to get the position of the node we get the position of the first char
var positionOfFirstRootNode = getPositionOfRepLineAtOffset(firstRootNode, 1);
const positionOfFirstRootNode = getPositionOfRepLineAtOffset(firstRootNode, 1);
return positionOfFirstRootNode.top === caretLineTop;
}
// find the first root node, usually it is a text node
function getFirstRootChildNode(node) {
if(!node.firstChild){
if (!node.firstChild) {
return node;
}else{
} else {
return getFirstRootChildNode(node.firstChild);
}
}
function getPreviousVisibleLine(line, rep) {
if (line < 0) {
return 0;
}else if (isLineVisible(line, rep)) {
} else if (isLineVisible(line, rep)) {
return line;
}else{
} else {
return getPreviousVisibleLine(line - 1, rep);
}
}
function getDimensionOfLastBrowserLineOfRepLine(line, rep) {
var lineNode = rep.lines.atIndex(line).lineNode;
var lastRootChildNode = getLastRootChildNode(lineNode);
const lineNode = rep.lines.atIndex(line).lineNode;
const lastRootChildNode = getLastRootChildNode(lineNode);
// we get the position of the line in the last char of it
var lastRootChildNodePosition = getPositionOfRepLineAtOffset(lastRootChildNode.node, lastRootChildNode.length);
const lastRootChildNodePosition = getPositionOfRepLineAtOffset(lastRootChildNode.node, lastRootChildNode.length);
return lastRootChildNodePosition;
}
function getLastRootChildNode(node) {
if(!node.lastChild){
if (!node.lastChild) {
return {
node: node,
length: node.length
node,
length: node.length,
};
}else{
} else {
return getLastRootChildNode(node.lastChild);
}
}
@ -152,50 +151,50 @@ function getLastRootChildNode(node) {
// So, we can use the caret line to calculate the bottom of the line.
// [2] the next line is part of another rep line. It's possible this line has different dimensions, so we
// have to get the exactly dimension of it
exports.getBottomOfNextBrowserLine = function(caretLinePosition, rep) {
var nextLineBottom = caretLinePosition.bottom + caretLinePosition.height; //[1]
var isCaretLineLastBrowserLine = caretLineIsLastBrowserLineOfRepLine(caretLinePosition.top, rep);
exports.getBottomOfNextBrowserLine = function (caretLinePosition, rep) {
let nextLineBottom = caretLinePosition.bottom + caretLinePosition.height; // [1]
const isCaretLineLastBrowserLine = caretLineIsLastBrowserLineOfRepLine(caretLinePosition.top, rep);
// the caret is at the end of a rep line, so we can get the next browser line dimension
// using the position of the first char of the next rep line
if(isCaretLineLastBrowserLine){ //[2]
var nextLineAfterCaretLine = rep.selStart[0] + 1;
var firstNextLineVisibleAfterCaretLine = getNextVisibleLine(nextLineAfterCaretLine, rep);
var linePosition = getDimensionOfFirstBrowserLineOfRepLine(firstNextLineVisibleAfterCaretLine, rep);
if (isCaretLineLastBrowserLine) { // [2]
const nextLineAfterCaretLine = rep.selStart[0] + 1;
const firstNextLineVisibleAfterCaretLine = getNextVisibleLine(nextLineAfterCaretLine, rep);
const linePosition = getDimensionOfFirstBrowserLineOfRepLine(firstNextLineVisibleAfterCaretLine, rep);
nextLineBottom = linePosition.bottom;
}
return nextLineBottom;
}
};
function caretLineIsLastBrowserLineOfRepLine(caretLineTop, rep) {
var caretRepLine = rep.selStart[0];
var lineNode = rep.lines.atIndex(caretRepLine).lineNode;
var lastRootChildNode = getLastRootChildNode(lineNode);
const caretRepLine = rep.selStart[0];
const lineNode = rep.lines.atIndex(caretRepLine).lineNode;
const lastRootChildNode = getLastRootChildNode(lineNode);
// we take a rep line and get the position of the last char of it
var lastRootChildNodePosition = getPositionOfRepLineAtOffset(lastRootChildNode.node, lastRootChildNode.length);
const lastRootChildNodePosition = getPositionOfRepLineAtOffset(lastRootChildNode.node, lastRootChildNode.length);
return lastRootChildNodePosition.top === caretLineTop;
}
function getPreviousVisibleLine(line, rep) {
var firstLineOfPad = 0;
const firstLineOfPad = 0;
if (line <= firstLineOfPad) {
return firstLineOfPad;
}else if (isLineVisible(line,rep)) {
} else if (isLineVisible(line, rep)) {
return line;
}else{
} else {
return getPreviousVisibleLine(line - 1, rep);
}
}
exports.getPreviousVisibleLine = getPreviousVisibleLine;
function getNextVisibleLine(line, rep) {
var lastLineOfThePad = rep.lines.length() - 1;
const lastLineOfThePad = rep.lines.length() - 1;
if (line >= lastLineOfThePad) {
return lastLineOfThePad;
}else if (isLineVisible(line,rep)) {
} else if (isLineVisible(line, rep)) {
return line;
}else{
} else {
return getNextVisibleLine(line + 1, rep);
}
}
@ -206,23 +205,23 @@ function isLineVisible(line, rep) {
}
function getDimensionOfFirstBrowserLineOfRepLine(line, rep) {
var lineNode = rep.lines.atIndex(line).lineNode;
var firstRootChildNode = getFirstRootChildNode(lineNode);
const lineNode = rep.lines.atIndex(line).lineNode;
const firstRootChildNode = getFirstRootChildNode(lineNode);
// we can get the position of the line, getting the position of the first char of the rep line
var firstRootChildNodePosition = getPositionOfRepLineAtOffset(firstRootChildNode, 1);
const firstRootChildNodePosition = getPositionOfRepLineAtOffset(firstRootChildNode, 1);
return firstRootChildNodePosition;
}
function getSelectionRange() {
var selection;
let selection;
if (!window.getSelection) {
return;
return;
}
selection = window.getSelection();
if (selection.rangeCount > 0) {
return selection.getRangeAt(0);
return selection.getRangeAt(0);
} else {
return null;
return null;
}
}

View file

@ -20,80 +20,70 @@
* limitations under the License.
*/
var AttributePool = require('./AttributePool');
var Changeset = require('./Changeset');
const AttributePool = require('./AttributePool');
const Changeset = require('./Changeset');
function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) {
// latest official text from server
var baseAText = Changeset.makeAText("\n");
let baseAText = Changeset.makeAText('\n');
// changes applied to baseText that have been submitted
var submittedChangeset = null;
let submittedChangeset = null;
// changes applied to submittedChangeset since it was prepared
var userChangeset = Changeset.identity(1);
let userChangeset = Changeset.identity(1);
// is the changesetTracker enabled
var tracking = false;
let tracking = false;
// stack state flag so that when we change the rep we don't
// handle the notification recursively. When setting, always
// unset in a "finally" block. When set to true, the setter
// takes change of userChangeset.
var applyingNonUserChanges = false;
let applyingNonUserChanges = false;
var changeCallback = null;
let changeCallback = null;
var changeCallbackTimeout = null;
let changeCallbackTimeout = null;
function setChangeCallbackTimeout() {
// can call this multiple times per call-stack, because
// we only schedule a call to changeCallback if it exists
// and if there isn't a timeout already scheduled.
if (changeCallback && changeCallbackTimeout === null)
{
changeCallbackTimeout = scheduler.setTimeout(function() {
try
{
if (changeCallback && changeCallbackTimeout === null) {
changeCallbackTimeout = scheduler.setTimeout(() => {
try {
changeCallback();
}
catch(pseudoError) {}
finally
{
} catch (pseudoError) {} finally {
changeCallbackTimeout = null;
}
}, 0);
}
}
var self;
let self;
return self = {
isTracking: function() {
isTracking() {
return tracking;
},
setBaseText: function(text) {
setBaseText(text) {
self.setBaseAttributedText(Changeset.makeAText(text), null);
},
setBaseAttributedText: function(atext, apoolJsonObj) {
aceCallbacksProvider.withCallbacks("setBaseText", function(callbacks) {
setBaseAttributedText(atext, apoolJsonObj) {
aceCallbacksProvider.withCallbacks('setBaseText', (callbacks) => {
tracking = true;
baseAText = Changeset.cloneAText(atext);
if (apoolJsonObj)
{
var wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
if (apoolJsonObj) {
const wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
baseAText.attribs = Changeset.moveOpsToNewPool(baseAText.attribs, wireApool, apool);
}
submittedChangeset = null;
userChangeset = Changeset.identity(atext.text.length);
applyingNonUserChanges = true;
try
{
try {
callbacks.setDocumentAttributedText(atext);
}
finally
{
} finally {
applyingNonUserChanges = false;
}
});
},
composeUserChangeset: function(c) {
composeUserChangeset(c) {
if (!tracking) return;
if (applyingNonUserChanges) return;
if (Changeset.isIdentity(c)) return;
@ -101,149 +91,132 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) {
setChangeCallbackTimeout();
},
applyChangesToBase: function(c, optAuthor, apoolJsonObj) {
applyChangesToBase(c, optAuthor, apoolJsonObj) {
if (!tracking) return;
aceCallbacksProvider.withCallbacks("applyChangesToBase", function(callbacks) {
if (apoolJsonObj)
{
var wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
aceCallbacksProvider.withCallbacks('applyChangesToBase', (callbacks) => {
if (apoolJsonObj) {
const wireApool = (new AttributePool()).fromJsonable(apoolJsonObj);
c = Changeset.moveOpsToNewPool(c, wireApool, apool);
}
baseAText = Changeset.applyToAText(c, baseAText, apool);
var c2 = c;
if (submittedChangeset)
{
var oldSubmittedChangeset = submittedChangeset;
let c2 = c;
if (submittedChangeset) {
const oldSubmittedChangeset = submittedChangeset;
submittedChangeset = Changeset.follow(c, oldSubmittedChangeset, false, apool);
c2 = Changeset.follow(oldSubmittedChangeset, c, true, apool);
}
var preferInsertingAfterUserChanges = true;
var oldUserChangeset = userChangeset;
const preferInsertingAfterUserChanges = true;
const oldUserChangeset = userChangeset;
userChangeset = Changeset.follow(c2, oldUserChangeset, preferInsertingAfterUserChanges, apool);
var postChange = Changeset.follow(oldUserChangeset, c2, !preferInsertingAfterUserChanges, apool);
const postChange = Changeset.follow(oldUserChangeset, c2, !preferInsertingAfterUserChanges, apool);
var preferInsertionAfterCaret = true; //(optAuthor && optAuthor > thisAuthor);
const preferInsertionAfterCaret = true; // (optAuthor && optAuthor > thisAuthor);
applyingNonUserChanges = true;
try
{
try {
callbacks.applyChangesetToDocument(postChange, preferInsertionAfterCaret);
}
finally
{
} finally {
applyingNonUserChanges = false;
}
});
},
prepareUserChangeset: function() {
prepareUserChangeset() {
// If there are user changes to submit, 'changeset' will be the
// changeset, else it will be null.
var toSubmit;
if (submittedChangeset)
{
let toSubmit;
if (submittedChangeset) {
// submission must have been canceled, prepare new changeset
// that includes old submittedChangeset
toSubmit = Changeset.compose(submittedChangeset, userChangeset, apool);
}
else
{
} else {
// add forEach function to Array.prototype for IE8
if (!('forEach' in Array.prototype)) {
Array.prototype.forEach= function(action, that /*opt*/) {
for (var i= 0, n= this.length; i<n; i++)
if (i in this)
action.call(that, this[i], i, this);
Array.prototype.forEach = function (action, that /* opt*/) {
for (let i = 0, n = this.length; i < n; i++) if (i in this) action.call(that, this[i], i, this);
};
}
// Get my authorID
var authorId = parent.parent.pad.myUserInfo.userId;
const authorId = parent.parent.pad.myUserInfo.userId;
// Sanitize authorship
// We need to replace all author attribs with thisSession.author, in case they copy/pasted or otherwise inserted other peoples changes
if(apool.numToAttrib){
for (var attr in apool.numToAttrib){
if (apool.numToAttrib[attr][0] == 'author' && apool.numToAttrib[attr][1] == authorId) var authorAttr = Number(attr).toString(36)
if (apool.numToAttrib) {
for (const attr in apool.numToAttrib) {
if (apool.numToAttrib[attr][0] == 'author' && apool.numToAttrib[attr][1] == authorId) var authorAttr = Number(attr).toString(36);
}
// Replace all added 'author' attribs with the value of the current user
var cs = Changeset.unpack(userChangeset)
, iterator = Changeset.opIterator(cs.ops)
, op
, assem = Changeset.mergingOpAssembler();
var cs = Changeset.unpack(userChangeset);
const iterator = Changeset.opIterator(cs.ops);
let op;
const assem = Changeset.mergingOpAssembler();
while(iterator.hasNext()) {
op = iterator.next()
if(op.opcode == '+') {
var newAttrs = ''
while (iterator.hasNext()) {
op = iterator.next();
if (op.opcode == '+') {
var newAttrs = '';
op.attribs.split('*').forEach(function(attrNum) {
if(!attrNum) return
var attr = apool.getAttrib(parseInt(attrNum, 36))
if(!attr) return
if('author' == attr[0]) {
op.attribs.split('*').forEach((attrNum) => {
if (!attrNum) return;
const attr = apool.getAttrib(parseInt(attrNum, 36));
if (!attr) return;
if ('author' == attr[0]) {
// replace that author with the current one
newAttrs += '*'+authorAttr;
}
else newAttrs += '*'+attrNum // overtake all other attribs as is
})
op.attribs = newAttrs
newAttrs += `*${authorAttr}`;
} else { newAttrs += `*${attrNum}`; } // overtake all other attribs as is
});
op.attribs = newAttrs;
}
assem.append(op)
assem.append(op);
}
assem.endDocument();
userChangeset = Changeset.pack(cs.oldLen, cs.newLen, assem.toString(), cs.charBank)
Changeset.checkRep(userChangeset)
userChangeset = Changeset.pack(cs.oldLen, cs.newLen, assem.toString(), cs.charBank);
Changeset.checkRep(userChangeset);
}
if (Changeset.isIdentity(userChangeset)) toSubmit = null;
else toSubmit = userChangeset;
}
var cs = null;
if (toSubmit)
{
if (toSubmit) {
submittedChangeset = toSubmit;
userChangeset = Changeset.identity(Changeset.newLen(toSubmit));
cs = toSubmit;
}
var wireApool = null;
if (cs)
{
var forWire = Changeset.prepareForWire(cs, apool);
let wireApool = null;
if (cs) {
const forWire = Changeset.prepareForWire(cs, apool);
wireApool = forWire.pool.toJsonable();
cs = forWire.translated;
}
var data = {
const data = {
changeset: cs,
apool: wireApool
apool: wireApool,
};
return data;
},
applyPreparedChangesetToBase: function() {
if (!submittedChangeset)
{
applyPreparedChangesetToBase() {
if (!submittedChangeset) {
// violation of protocol; use prepareUserChangeset first
throw new Error("applySubmittedChangesToBase: no submitted changes to apply");
throw new Error('applySubmittedChangesToBase: no submitted changes to apply');
}
//bumpDebug("applying committed changeset: "+submittedChangeset.encodeToString(false));
// bumpDebug("applying committed changeset: "+submittedChangeset.encodeToString(false));
baseAText = Changeset.applyToAText(submittedChangeset, baseAText, apool);
submittedChangeset = null;
},
setUserChangeNotificationCallback: function(callback) {
setUserChangeNotificationCallback(callback) {
changeCallback = callback;
},
hasUncommittedChanges: function() {
hasUncommittedChanges() {
return !!(submittedChangeset || (!Changeset.isIdentity(userChangeset)));
}
},
};
}
exports.makeChangesetTracker = makeChangesetTracker;

View file

@ -14,174 +14,165 @@
* limitations under the License.
*/
var padutils = require('./pad_utils').padutils;
var padcookie = require('./pad_cookie').padcookie;
var Tinycon = require('tinycon/tinycon');
var hooks = require('./pluginfw/hooks');
var padeditor = require('./pad_editor').padeditor;
const padutils = require('./pad_utils').padutils;
const padcookie = require('./pad_cookie').padcookie;
const Tinycon = require('tinycon/tinycon');
const hooks = require('./pluginfw/hooks');
const padeditor = require('./pad_editor').padeditor;
var chat = (function() {
var isStuck = false;
var userAndChat = false;
var gotInitialMessages = false;
var historyPointer = 0;
var chatMentions = 0;
var chat = (function () {
let isStuck = false;
let userAndChat = false;
const gotInitialMessages = false;
const historyPointer = 0;
let chatMentions = 0;
var self = {
show: function () {
$("#chaticon").removeClass('visible');
$("#chatbox").addClass('visible');
show() {
$('#chaticon').removeClass('visible');
$('#chatbox').addClass('visible');
self.scrollDown(true);
chatMentions = 0;
Tinycon.setBubble(0);
$('.chat-gritter-msg').each(function() {
$('.chat-gritter-msg').each(function () {
$.gritter.remove(this.id);
});
},
focus: function () {
setTimeout(function(){
$("#chatinput").focus();
},100);
focus() {
setTimeout(() => {
$('#chatinput').focus();
}, 100);
},
stickToScreen: function(fromInitialCall) { // Make chat stick to right hand side of screen
if(pad.settings.hideChat){
stickToScreen(fromInitialCall) { // Make chat stick to right hand side of screen
if (pad.settings.hideChat) {
return;
}
chat.show();
isStuck = (!isStuck || fromInitialCall);
$('#chatbox').hide();
// Add timeout to disable the chatbox animations
setTimeout(function() {
$('#chatbox, .sticky-container').toggleClass("stickyChat", isStuck);
setTimeout(() => {
$('#chatbox, .sticky-container').toggleClass('stickyChat', isStuck);
$('#chatbox').css('display', 'flex');
}, 0);
padcookie.setPref("chatAlwaysVisible", isStuck);
padcookie.setPref('chatAlwaysVisible', isStuck);
$('#options-stickychat').prop('checked', isStuck);
},
chatAndUsers: function(fromInitialCall) {
var toEnable = $('#options-chatandusers').is(":checked");
if(toEnable || !userAndChat || fromInitialCall){
chatAndUsers(fromInitialCall) {
const toEnable = $('#options-chatandusers').is(':checked');
if (toEnable || !userAndChat || fromInitialCall) {
chat.stickToScreen(true);
$('#options-stickychat').prop('checked', true)
$('#options-chatandusers').prop('checked', true)
$('#options-stickychat').prop("disabled", "disabled");
$('#options-stickychat').prop('checked', true);
$('#options-chatandusers').prop('checked', true);
$('#options-stickychat').prop('disabled', 'disabled');
userAndChat = true;
}else{
$('#options-stickychat').prop("disabled", false);
} else {
$('#options-stickychat').prop('disabled', false);
userAndChat = false;
}
padcookie.setPref("chatAndUsers", userAndChat);
$('#users, .sticky-container').toggleClass("chatAndUsers popup-show stickyUsers", userAndChat);
$("#chatbox").toggleClass("chatAndUsersChat", userAndChat);
padcookie.setPref('chatAndUsers', userAndChat);
$('#users, .sticky-container').toggleClass('chatAndUsers popup-show stickyUsers', userAndChat);
$('#chatbox').toggleClass('chatAndUsersChat', userAndChat);
},
hide: function () {
hide() {
// decide on hide logic based on chat window being maximized or not
if ($('#options-stickychat').prop('checked')) {
chat.stickToScreen();
$('#options-stickychat').prop('checked', false);
}
else {
$("#chatcounter").text("0");
$("#chaticon").addClass('visible');
$("#chatbox").removeClass('visible');
} else {
$('#chatcounter').text('0');
$('#chaticon').addClass('visible');
$('#chatbox').removeClass('visible');
}
},
scrollDown: function(force) {
scrollDown(force) {
if ($('#chatbox').hasClass('visible')) {
if (force || !self.lastMessage || !self.lastMessage.position() || self.lastMessage.position().top < ($('#chattext').outerHeight() + 20)) {
// if we use a slow animate here we can have a race condition when a users focus can not be moved away
// from the last message recieved.
$('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, { duration: 400, queue: false });
$('#chattext').animate({scrollTop: $('#chattext')[0].scrollHeight}, {duration: 400, queue: false});
self.lastMessage = $('#chattext > p').eq(-1);
}
}
},
send: function() {
var text = $("#chatinput").val();
if(text.replace(/\s+/,'').length == 0)
return;
this._pad.collabClient.sendMessage({"type": "CHAT_MESSAGE", "text": text});
$("#chatinput").val("");
send() {
const text = $('#chatinput').val();
if (text.replace(/\s+/, '').length == 0) return;
this._pad.collabClient.sendMessage({type: 'CHAT_MESSAGE', text});
$('#chatinput').val('');
},
addMessage: function(msg, increment, isHistoryAdd) {
//correct the time
addMessage(msg, increment, isHistoryAdd) {
// correct the time
msg.time += this._pad.clientTimeOffset;
//create the time string
var minutes = "" + new Date(msg.time).getMinutes();
var hours = "" + new Date(msg.time).getHours();
if(minutes.length == 1)
minutes = "0" + minutes ;
if(hours.length == 1)
hours = "0" + hours ;
var timeStr = hours + ":" + minutes;
// create the time string
let minutes = `${new Date(msg.time).getMinutes()}`;
let hours = `${new Date(msg.time).getHours()}`;
if (minutes.length == 1) minutes = `0${minutes}`;
if (hours.length == 1) hours = `0${hours}`;
const timeStr = `${hours}:${minutes}`;
//create the authorclass
// create the authorclass
if (!msg.userId) {
/*
* If, for a bug or a database corruption, the message coming from the
* server does not contain the userId field (see for example #3731),
* let's be defensive and replace it with "unknown".
*/
msg.userId = "unknown";
msg.userId = 'unknown';
console.warn('The "userId" field of a chat message coming from the server was not present. Replacing with "unknown". This may be a bug or a database corruption.');
}
var authorClass = "author-" + msg.userId.replace(/[^a-y0-9]/g, function(c) {
if (c == ".") return "-";
return 'z' + c.charCodeAt(0) + 'z';
});
const authorClass = `author-${msg.userId.replace(/[^a-y0-9]/g, (c) => {
if (c == '.') return '-';
return `z${c.charCodeAt(0)}z`;
})}`;
var text = padutils.escapeHtmlWithClickableLinks(msg.text, "_blank");
const text = padutils.escapeHtmlWithClickableLinks(msg.text, '_blank');
var authorName = msg.userName == null ? _('pad.userlist.unnamed') : padutils.escapeHtml(msg.userName);
const authorName = msg.userName == null ? _('pad.userlist.unnamed') : padutils.escapeHtml(msg.userName);
// the hook args
var ctx = {
"authorName" : authorName,
"author" : msg.userId,
"text" : text,
"sticky" : false,
"timestamp" : msg.time,
"timeStr" : timeStr,
"duration" : 4000
}
const ctx = {
authorName,
author: msg.userId,
text,
sticky: false,
timestamp: msg.time,
timeStr,
duration: 4000,
};
// is the users focus already in the chatbox?
var alreadyFocused = $("#chatinput").is(":focus");
const alreadyFocused = $('#chatinput').is(':focus');
// does the user already have the chatbox open?
var chatOpen = $("#chatbox").hasClass("visible");
const chatOpen = $('#chatbox').hasClass('visible');
// does this message contain this user's name? (is the curretn user mentioned?)
var myName = $('#myusernameedit').val();
var wasMentioned = (text.toLowerCase().indexOf(myName.toLowerCase()) !== -1 && myName != "undefined");
const myName = $('#myusernameedit').val();
const wasMentioned = (text.toLowerCase().indexOf(myName.toLowerCase()) !== -1 && myName != 'undefined');
if(wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen)
{ // If the user was mentioned, make the message sticky
if (wasMentioned && !alreadyFocused && !isHistoryAdd && !chatOpen) { // If the user was mentioned, make the message sticky
chatMentions++;
Tinycon.setBubble(chatMentions);
ctx.sticky = true;
}
// Call chat message hook
hooks.aCallAll("chatNewMessage", ctx, function() {
hooks.aCallAll('chatNewMessage', ctx, () => {
const html = `<p data-authorId='${msg.userId}' class='${authorClass}'><b>${authorName}:</b><span class='time ${authorClass}'>${ctx.timeStr}</span> ${ctx.text}</p>`;
if (isHistoryAdd) $(html).insertAfter('#chatloadmessagesbutton');
else $('#chattext').append(html);
var html = "<p data-authorId='" + msg.userId + "' class='" + authorClass + "'><b>" + authorName + ":</b><span class='time " + authorClass + "'>" + ctx.timeStr + "</span> " + ctx.text + "</p>";
if(isHistoryAdd)
$(html).insertAfter('#chatloadmessagesbutton');
else
$("#chattext").append(html);
//should we increment the counter??
if(increment && !isHistoryAdd)
{
// should we increment the counter??
if (increment && !isHistoryAdd) {
// Update the counter of unread messages
var count = Number($("#chatcounter").text());
let count = Number($('#chatcounter').text());
count++;
$("#chatcounter").text(count);
$('#chatcounter').text(count);
if(!chatOpen && ctx.duration > 0) {
if (!chatOpen && ctx.duration > 0) {
$.gritter.add({
// Note: ctx.authorName and ctx.text are already HTML-escaped.
text: $('<p>')
@ -190,26 +181,25 @@ var chat = (function() {
sticky: ctx.sticky,
time: 5000,
position: 'bottom',
class_name: 'chat-gritter-msg'
class_name: 'chat-gritter-msg',
});
}
}
});
// Clear the chat mentions when the user clicks on the chat input box
$('#chatinput').click(function(){
$('#chatinput').click(() => {
chatMentions = 0;
Tinycon.setBubble(0);
});
if(!isHistoryAdd)
self.scrollDown();
if (!isHistoryAdd) self.scrollDown();
},
init: function(pad) {
init(pad) {
this._pad = pad;
$("#chatinput").on("keydown", function(evt){
$('#chatinput').on('keydown', (evt) => {
// If the event is Alt C or Escape & we're already in the chat menu
// Send the users focus back to the pad
if((evt.altKey == true && evt.which === 67) || evt.which === 27){
if ((evt.altKey == true && evt.which === 67) || evt.which === 27) {
// If we're in chat already..
$(':focus').blur(); // required to do not try to remove!
padeditor.ace.focus(); // Sends focus back to pad
@ -218,20 +208,19 @@ var chat = (function() {
}
});
$('body:not(#chatinput)').on("keypress", function(evt){
if (evt.altKey && evt.which == 67){
$('body:not(#chatinput)').on('keypress', function (evt) {
if (evt.altKey && evt.which == 67) {
// Alt c focuses on the Chat window
$(this).blur();
chat.show();
$("#chatinput").focus();
$('#chatinput').focus();
evt.preventDefault();
}
});
$("#chatinput").keypress(function(evt){
//if the user typed enter, fire the send
if(evt.which == 13 || evt.which == 10)
{
$('#chatinput').keypress((evt) => {
// if the user typed enter, fire the send
if (evt.which == 13 || evt.which == 10) {
evt.preventDefault();
self.send();
}
@ -239,25 +228,24 @@ var chat = (function() {
// initial messages are loaded in pad.js' _afterHandshake
$("#chatcounter").text(0);
$("#chatloadmessagesbutton").click(function() {
var start = Math.max(self.historyPointer - 20, 0);
var end = self.historyPointer;
$('#chatcounter').text(0);
$('#chatloadmessagesbutton').click(() => {
const start = Math.max(self.historyPointer - 20, 0);
const end = self.historyPointer;
if(start == end) // nothing to load
return;
if (start == end) // nothing to load
{ return; }
$("#chatloadmessagesbutton").css("display", "none");
$("#chatloadmessagesball").css("display", "block");
$('#chatloadmessagesbutton').css('display', 'none');
$('#chatloadmessagesball').css('display', 'block');
pad.collabClient.sendMessage({"type": "GET_CHAT_MESSAGES", "start": start, "end": end});
pad.collabClient.sendMessage({type: 'GET_CHAT_MESSAGES', start, end});
self.historyPointer = start;
});
}
}
},
};
return self;
}());
exports.chat = chat;

View file

@ -20,12 +20,12 @@
* limitations under the License.
*/
var chat = require('./chat').chat;
var hooks = require('./pluginfw/hooks');
const chat = require('./chat').chat;
const hooks = require('./pluginfw/hooks');
// Dependency fill on init. This exists for `pad.socket` only.
// TODO: bind directly to the socket.
var pad = undefined;
let pad = undefined;
function getSocket() {
return pad && pad.socket;
}
@ -34,134 +34,118 @@ function getSocket() {
ACE's ready callback does not need to have fired yet.
"serverVars" are from calling doc.getCollabClientVars() on the server. */
function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) {
var editor = ace2editor;
const editor = ace2editor;
pad = _pad; // Inject pad to avoid a circular dependency.
var rev = serverVars.rev;
var padId = serverVars.padId;
let rev = serverVars.rev;
const padId = serverVars.padId;
var state = "IDLE";
var stateMessage;
var channelState = "CONNECTING";
var appLevelDisconnectReason = null;
let state = 'IDLE';
let stateMessage;
let channelState = 'CONNECTING';
let appLevelDisconnectReason = null;
var lastCommitTime = 0;
var initialStartConnectTime = 0;
let lastCommitTime = 0;
let initialStartConnectTime = 0;
var userId = initialUserInfo.userId;
//var socket;
var userSet = {}; // userId -> userInfo
const userId = initialUserInfo.userId;
// var socket;
const userSet = {}; // userId -> userInfo
userSet[userId] = initialUserInfo;
var caughtErrors = [];
var caughtErrorCatchers = [];
var caughtErrorTimes = [];
var debugMessages = [];
var msgQueue = [];
const caughtErrors = [];
const caughtErrorCatchers = [];
const caughtErrorTimes = [];
const debugMessages = [];
const msgQueue = [];
var isPendingRevision = false;
let isPendingRevision = false;
tellAceAboutHistoricalAuthors(serverVars.historicalAuthorData);
tellAceActiveAuthorInfo(initialUserInfo);
var callbacks = {
onUserJoin: function() {},
onUserLeave: function() {},
onUpdateUserInfo: function() {},
onChannelStateChange: function() {},
onClientMessage: function() {},
onInternalAction: function() {},
onConnectionTrouble: function() {},
onServerMessage: function() {}
const callbacks = {
onUserJoin() {},
onUserLeave() {},
onUpdateUserInfo() {},
onChannelStateChange() {},
onClientMessage() {},
onInternalAction() {},
onConnectionTrouble() {},
onServerMessage() {},
};
if (browser.firefox)
{
if (browser.firefox) {
// Prevent "escape" from taking effect and canceling a comet connection;
// doesn't work if focus is on an iframe.
$(window).bind("keydown", function(evt) {
if (evt.which == 27)
{
evt.preventDefault()
$(window).bind('keydown', (evt) => {
if (evt.which == 27) {
evt.preventDefault();
}
});
}
editor.setProperty("userAuthor", userId);
editor.setProperty('userAuthor', userId);
editor.setBaseAttributedText(serverVars.initialAttributedText, serverVars.apool);
editor.setUserChangeNotificationCallback(wrapRecordingErrors("handleUserChanges", handleUserChanges));
editor.setUserChangeNotificationCallback(wrapRecordingErrors('handleUserChanges', handleUserChanges));
function dmesg(str) {
if (typeof window.ajlog == "string") window.ajlog += str + '\n';
if (typeof window.ajlog === 'string') window.ajlog += `${str}\n`;
debugMessages.push(str);
}
function handleUserChanges() {
if (editor.getInInternationalComposition()) return;
if ((!getSocket()) || channelState == "CONNECTING")
{
if (channelState == "CONNECTING" && (((+new Date()) - initialStartConnectTime) > 20000))
{
setChannelState("DISCONNECTED", "initsocketfail");
}
else
{
if ((!getSocket()) || channelState == 'CONNECTING') {
if (channelState == 'CONNECTING' && (((+new Date()) - initialStartConnectTime) > 20000)) {
setChannelState('DISCONNECTED', 'initsocketfail');
} else {
// check again in a bit
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 1000);
setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), 1000);
}
return;
}
var t = (+new Date());
const t = (+new Date());
if (state != "IDLE")
{
if (state == "COMMITTING" && msgQueue.length == 0 && (t - lastCommitTime) > 20000)
{
if (state != 'IDLE') {
if (state == 'COMMITTING' && msgQueue.length == 0 && (t - lastCommitTime) > 20000) {
// a commit is taking too long
setChannelState("DISCONNECTED", "slowcommit");
}
else if (state == "COMMITTING" && msgQueue.length == 0 && (t - lastCommitTime) > 5000)
{
callbacks.onConnectionTrouble("SLOW");
}
else
{
setChannelState('DISCONNECTED', 'slowcommit');
} else if (state == 'COMMITTING' && msgQueue.length == 0 && (t - lastCommitTime) > 5000) {
callbacks.onConnectionTrouble('SLOW');
} else {
// run again in a few seconds, to detect a disconnect
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000);
setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), 3000);
}
return;
}
var earliestCommit = lastCommitTime + 500;
if (t < earliestCommit)
{
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), earliestCommit - t);
const earliestCommit = lastCommitTime + 500;
if (t < earliestCommit) {
setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), earliestCommit - t);
return;
}
// apply msgQueue changeset.
if (msgQueue.length != 0) {
var msg;
let msg;
while (msg = msgQueue.shift()) {
var newRev = msg.newRev;
rev=newRev;
if (msg.type == "ACCEPT_COMMIT")
{
const newRev = msg.newRev;
rev = newRev;
if (msg.type == 'ACCEPT_COMMIT') {
editor.applyPreparedChangesetToBase();
setStateIdle();
callCatchingErrors("onInternalAction", function() {
callbacks.onInternalAction("commitAcceptedByServer");
callCatchingErrors('onInternalAction', () => {
callbacks.onInternalAction('commitAcceptedByServer');
});
callCatchingErrors("onConnectionTrouble", function() {
callbacks.onConnectionTrouble("OK");
callCatchingErrors('onConnectionTrouble', () => {
callbacks.onConnectionTrouble('OK');
});
handleUserChanges();
}
else if (msg.type == "NEW_CHANGES")
{
var changeset = msg.changeset;
var author = (msg.author || '');
var apool = msg.apool;
} else if (msg.type == 'NEW_CHANGES') {
const changeset = msg.changeset;
const author = (msg.author || '');
const apool = msg.apool;
editor.applyChangesToBase(changeset, author, apool);
}
@ -171,43 +155,38 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
}
}
var sentMessage = false;
let sentMessage = false;
// Check if there are any pending revisions to be received from server.
// Allow only if there are no pending revisions to be received from server
if (!isPendingRevision)
{
var userChangesData = editor.prepareUserChangeset();
if (userChangesData.changeset)
{
lastCommitTime = t;
state = "COMMITTING";
stateMessage = {
type: "USER_CHANGES",
baseRev: rev,
changeset: userChangesData.changeset,
apool: userChangesData.apool
};
sendMessage(stateMessage);
sentMessage = true;
callbacks.onInternalAction("commitPerformed");
}
}
else
{
// run again in a few seconds, to check if there was a reconnection attempt
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000);
if (!isPendingRevision) {
const userChangesData = editor.prepareUserChangeset();
if (userChangesData.changeset) {
lastCommitTime = t;
state = 'COMMITTING';
stateMessage = {
type: 'USER_CHANGES',
baseRev: rev,
changeset: userChangesData.changeset,
apool: userChangesData.apool,
};
sendMessage(stateMessage);
sentMessage = true;
callbacks.onInternalAction('commitPerformed');
}
} else {
// run again in a few seconds, to check if there was a reconnection attempt
setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), 3000);
}
if (sentMessage)
{
if (sentMessage) {
// run again in a few seconds, to detect a disconnect
setTimeout(wrapRecordingErrors("setTimeout(handleUserChanges)", handleUserChanges), 3000);
setTimeout(wrapRecordingErrors('setTimeout(handleUserChanges)', handleUserChanges), 3000);
}
}
function setUpSocket() {
hiccupCount = 0;
setChannelState("CONNECTED");
setChannelState('CONNECTED');
doDeferredActions();
initialStartConnectTime = +new Date();
@ -217,49 +196,42 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
function sendMessage(msg) {
getSocket().json.send(
{
type: "COLLABROOM",
component: "pad",
data: msg
});
{
type: 'COLLABROOM',
component: 'pad',
data: msg,
});
}
function wrapRecordingErrors(catcher, func) {
return function() {
try
{
return function () {
try {
return func.apply(this, Array.prototype.slice.call(arguments));
}
catch (e)
{
} catch (e) {
caughtErrors.push(e);
caughtErrorCatchers.push(catcher);
caughtErrorTimes.push(+new Date());
//console.dir({catcher: catcher, e: e});
// console.dir({catcher: catcher, e: e});
throw e;
}
};
}
function callCatchingErrors(catcher, func) {
try
{
try {
wrapRecordingErrors(catcher, func)();
}
catch (e)
{ /*absorb*/
} catch (e) { /* absorb*/
}
}
function handleMessageFromServer(evt) {
if (!getSocket()) return;
if (!evt.data) return;
var wrapper = evt;
if (wrapper.type != "COLLABROOM" && wrapper.type != "CUSTOM") return;
var msg = wrapper.data;
const wrapper = evt;
if (wrapper.type != 'COLLABROOM' && wrapper.type != 'CUSTOM') return;
const msg = wrapper.data;
if (msg.type == "NEW_CHANGES")
{
if (msg.type == 'NEW_CHANGES') {
var newRev = msg.newRev;
var changeset = msg.changeset;
var author = (msg.author || '');
@ -270,9 +242,8 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
if (msgQueue.length > 0) var oldRev = msgQueue[msgQueue.length - 1].newRev;
else oldRev = rev;
if (newRev != (oldRev + 1))
{
window.console.warn("bad message revision on NEW_CHANGES: " + newRev + " not " + (oldRev + 1));
if (newRev != (oldRev + 1)) {
window.console.warn(`bad message revision on NEW_CHANGES: ${newRev} not ${oldRev + 1}`);
// setChannelState("DISCONNECTED", "badmessage_newchanges");
return;
}
@ -280,24 +251,19 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
return;
}
if (newRev != (rev + 1))
{
window.console.warn("bad message revision on NEW_CHANGES: " + newRev + " not " + (rev + 1));
if (newRev != (rev + 1)) {
window.console.warn(`bad message revision on NEW_CHANGES: ${newRev} not ${rev + 1}`);
// setChannelState("DISCONNECTED", "badmessage_newchanges");
return;
}
rev = newRev;
editor.applyChangesToBase(changeset, author, apool);
}
else if (msg.type == "ACCEPT_COMMIT")
{
} else if (msg.type == 'ACCEPT_COMMIT') {
var newRev = msg.newRev;
if (msgQueue.length > 0)
{
if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1))
{
window.console.warn("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (msgQueue[msgQueue.length - 1][0] + 1));
if (msgQueue.length > 0) {
if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1)) {
window.console.warn(`bad message revision on ACCEPT_COMMIT: ${newRev} not ${msgQueue[msgQueue.length - 1][0] + 1}`);
// setChannelState("DISCONNECTED", "badmessage_acceptcommit");
return;
}
@ -305,178 +271,140 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
return;
}
if (newRev != (rev + 1))
{
window.console.warn("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (rev + 1));
if (newRev != (rev + 1)) {
window.console.warn(`bad message revision on ACCEPT_COMMIT: ${newRev} not ${rev + 1}`);
// setChannelState("DISCONNECTED", "badmessage_acceptcommit");
return;
}
rev = newRev;
editor.applyPreparedChangesetToBase();
setStateIdle();
callCatchingErrors("onInternalAction", function() {
callbacks.onInternalAction("commitAcceptedByServer");
callCatchingErrors('onInternalAction', () => {
callbacks.onInternalAction('commitAcceptedByServer');
});
callCatchingErrors("onConnectionTrouble", function() {
callbacks.onConnectionTrouble("OK");
callCatchingErrors('onConnectionTrouble', () => {
callbacks.onConnectionTrouble('OK');
});
handleUserChanges();
}
else if (msg.type == 'CLIENT_RECONNECT')
{
} else if (msg.type == 'CLIENT_RECONNECT') {
// Server sends a CLIENT_RECONNECT message when there is a client reconnect. Server also returns
// all pending revisions along with this CLIENT_RECONNECT message
if (msg.noChanges)
{
if (msg.noChanges) {
// If no revisions are pending, just make everything normal
setIsPendingRevision(false);
return;
}
var headRev = msg.headRev;
const headRev = msg.headRev;
var newRev = msg.newRev;
var changeset = msg.changeset;
var author = (msg.author || '');
var apool = msg.apool;
if (msgQueue.length > 0)
{
if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1))
{
window.console.warn("bad message revision on CLIENT_RECONNECT: " + newRev + " not " + (msgQueue[msgQueue.length - 1][0] + 1));
if (msgQueue.length > 0) {
if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1)) {
window.console.warn(`bad message revision on CLIENT_RECONNECT: ${newRev} not ${msgQueue[msgQueue.length - 1][0] + 1}`);
// setChannelState("DISCONNECTED", "badmessage_acceptcommit");
return;
}
msg.type = "NEW_CHANGES";
msg.type = 'NEW_CHANGES';
msgQueue.push(msg);
return;
}
if (newRev != (rev + 1))
{
window.console.warn("bad message revision on CLIENT_RECONNECT: " + newRev + " not " + (rev + 1));
if (newRev != (rev + 1)) {
window.console.warn(`bad message revision on CLIENT_RECONNECT: ${newRev} not ${rev + 1}`);
// setChannelState("DISCONNECTED", "badmessage_acceptcommit");
return;
}
rev = newRev;
if (author == pad.getUserId())
{
if (author == pad.getUserId()) {
editor.applyPreparedChangesetToBase();
setStateIdle();
callCatchingErrors("onInternalAction", function() {
callbacks.onInternalAction("commitAcceptedByServer");
callCatchingErrors('onInternalAction', () => {
callbacks.onInternalAction('commitAcceptedByServer');
});
callCatchingErrors("onConnectionTrouble", function() {
callbacks.onConnectionTrouble("OK");
callCatchingErrors('onConnectionTrouble', () => {
callbacks.onConnectionTrouble('OK');
});
handleUserChanges();
}
else
{
} else {
editor.applyChangesToBase(changeset, author, apool);
}
if (newRev == headRev)
{
if (newRev == headRev) {
// Once we have applied all pending revisions, make everything normal
setIsPendingRevision(false);
}
}
else if (msg.type == "NO_COMMIT_PENDING")
{
if (state == "COMMITTING")
{
} else if (msg.type == 'NO_COMMIT_PENDING') {
if (state == 'COMMITTING') {
// server missed our commit message; abort that commit
setStateIdle();
handleUserChanges();
}
}
else if (msg.type == "USER_NEWINFO")
{
} else if (msg.type == 'USER_NEWINFO') {
var userInfo = msg.userInfo;
var id = userInfo.userId;
// Avoid a race condition when setting colors. If our color was set by a
// query param, ignore our own "new user" message's color value.
if (id === initialUserInfo.userId && initialUserInfo.globalUserColor)
{
if (id === initialUserInfo.userId && initialUserInfo.globalUserColor) {
msg.userInfo.colorId = initialUserInfo.globalUserColor;
}
if (userSet[id])
{
if (userSet[id]) {
userSet[id] = userInfo;
callbacks.onUpdateUserInfo(userInfo);
}
else
{
} else {
userSet[id] = userInfo;
callbacks.onUserJoin(userInfo);
}
tellAceActiveAuthorInfo(userInfo);
}
else if (msg.type == "USER_LEAVE")
{
} else if (msg.type == 'USER_LEAVE') {
var userInfo = msg.userInfo;
var id = userInfo.userId;
if (userSet[id])
{
if (userSet[id]) {
delete userSet[userInfo.userId];
fadeAceAuthorInfo(userInfo);
callbacks.onUserLeave(userInfo);
}
}
else if (msg.type == "DISCONNECT_REASON")
{
} else if (msg.type == 'DISCONNECT_REASON') {
appLevelDisconnectReason = msg.reason;
}
else if (msg.type == "CLIENT_MESSAGE")
{
} else if (msg.type == 'CLIENT_MESSAGE') {
callbacks.onClientMessage(msg.payload);
}
else if (msg.type == "CHAT_MESSAGE")
{
} else if (msg.type == 'CHAT_MESSAGE') {
chat.addMessage(msg, true, false);
}
else if (msg.type == "CHAT_MESSAGES")
{
for(var i = msg.messages.length - 1; i >= 0; i--)
{
} else if (msg.type == 'CHAT_MESSAGES') {
for (let i = msg.messages.length - 1; i >= 0; i--) {
chat.addMessage(msg.messages[i], true, true);
}
if(!chat.gotInitalMessages)
{
if (!chat.gotInitalMessages) {
chat.scrollDown();
chat.gotInitalMessages = true;
chat.historyPointer = clientVars.chatHead - msg.messages.length;
}
// messages are loaded, so hide the loading-ball
$("#chatloadmessagesball").css("display", "none");
$('#chatloadmessagesball').css('display', 'none');
// there are less than 100 messages or we reached the top
if(chat.historyPointer <= 0)
$("#chatloadmessagesbutton").css("display", "none");
else // there are still more messages, re-show the load-button
$("#chatloadmessagesbutton").css("display", "block");
}
else if (msg.type == "SERVER_MESSAGE")
{
if (chat.historyPointer <= 0) { $('#chatloadmessagesbutton').css('display', 'none'); } else // there are still more messages, re-show the load-button
{ $('#chatloadmessagesbutton').css('display', 'block'); }
} else if (msg.type == 'SERVER_MESSAGE') {
callbacks.onServerMessage(msg.payload);
}
//HACKISH: User messages do not have "payload" but "userInfo", so that all "handleClientMessage_USER_" hooks would work, populate payload
//FIXME: USER_* messages to have "payload" property instead of "userInfo", seems like a quite a big work
if(msg.type.indexOf("USER_") > -1) {
// HACKISH: User messages do not have "payload" but "userInfo", so that all "handleClientMessage_USER_" hooks would work, populate payload
// FIXME: USER_* messages to have "payload" property instead of "userInfo", seems like a quite a big work
if (msg.type.indexOf('USER_') > -1) {
msg.payload = msg.userInfo;
}
// Similar for NEW_CHANGES
if(msg.type === "NEW_CHANGES") msg.payload = msg;
if (msg.type === 'NEW_CHANGES') msg.payload = msg;
hooks.callAll('handleClientMessage_' + msg.type, {payload: msg.payload});
hooks.callAll(`handleClientMessage_${msg.type}`, {payload: msg.payload});
}
function updateUserInfo(userInfo) {
@ -485,10 +413,10 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
tellAceActiveAuthorInfo(userInfo);
if (!getSocket()) return;
sendMessage(
{
type: "USERINFO_UPDATE",
userInfo: userInfo
});
{
type: 'USERINFO_UPDATE',
userInfo,
});
}
function tellAceActiveAuthorInfo(userInfo) {
@ -496,23 +424,19 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
}
function tellAceAuthorInfo(userId, colorId, inactive) {
if(typeof colorId == "number")
{
if (typeof colorId === 'number') {
colorId = clientVars.colorPalette[colorId];
}
var cssColor = colorId;
if (inactive)
{
const cssColor = colorId;
if (inactive) {
editor.setAuthorInfo(userId, {
bgcolor: cssColor,
fade: 0.5
fade: 0.5,
});
}
else
{
} else {
editor.setAuthorInfo(userId, {
bgcolor: cssColor
bgcolor: cssColor,
});
}
}
@ -526,27 +450,24 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
}
function tellAceAboutHistoricalAuthors(hadata) {
for (var author in hadata)
{
var data = hadata[author];
if (!userSet[author])
{
for (const author in hadata) {
const data = hadata[author];
if (!userSet[author]) {
tellAceAuthorInfo(author, data.colorId, true);
}
}
}
function setChannelState(newChannelState, moreInfo) {
if (newChannelState != channelState)
{
if (newChannelState != channelState) {
channelState = newChannelState;
callbacks.onChannelStateChange(channelState, moreInfo);
}
}
function valuesArray(obj) {
var array = [];
$.each(obj, function(k, v) {
const array = [];
$.each(obj, (k, v) => {
array.push(v);
});
return array;
@ -554,39 +475,32 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
// We need to present a working interface even before the socket
// is connected for the first time.
var deferredActions = [];
let deferredActions = [];
function defer(func, tag) {
return function() {
var that = this;
var args = arguments;
return function () {
const that = this;
const args = arguments;
function action() {
func.apply(that, args);
}
action.tag = tag;
if (channelState == "CONNECTING")
{
if (channelState == 'CONNECTING') {
deferredActions.push(action);
}
else
{
} else {
action();
}
}
};
}
function doDeferredActions(tag) {
var newArray = [];
for (var i = 0; i < deferredActions.length; i++)
{
var a = deferredActions[i];
if ((!tag) || (tag == a.tag))
{
const newArray = [];
for (let i = 0; i < deferredActions.length; i++) {
const a = deferredActions[i];
if ((!tag) || (tag == a.tag)) {
a();
}
else
{
} else {
newArray.push(a);
}
}
@ -595,10 +509,10 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
function sendClientMessage(msg) {
sendMessage(
{
type: "CLIENT_MESSAGE",
payload: msg
});
{
type: 'CLIENT_MESSAGE',
payload: msg,
});
}
function getCurrentRevisionNumber() {
@ -606,18 +520,16 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
}
function getMissedChanges() {
var obj = {};
const obj = {};
obj.userInfo = userSet[userId];
obj.baseRev = rev;
if (state == "COMMITTING" && stateMessage)
{
if (state == 'COMMITTING' && stateMessage) {
obj.committedChangeset = stateMessage.changeset;
obj.committedChangesetAPool = stateMessage.apool;
editor.applyPreparedChangesetToBase();
}
var userChangesData = editor.prepareUserChangeset();
if (userChangesData.changeset)
{
const userChangesData = editor.prepareUserChangeset();
if (userChangesData.changeset) {
obj.furtherChangeset = userChangesData.changeset;
obj.furtherChangesetAPool = userChangesData.apool;
}
@ -625,8 +537,8 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
}
function setStateIdle() {
state = "IDLE";
callbacks.onInternalAction("newlyIdle");
state = 'IDLE';
callbacks.onInternalAction('newlyIdle');
schedulePerhapsCallIdleFuncs();
}
@ -642,55 +554,53 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
var idleFuncs = [];
function schedulePerhapsCallIdleFuncs() {
setTimeout(function() {
if (state == "IDLE")
{
while (idleFuncs.length > 0)
{
var f = idleFuncs.shift();
setTimeout(() => {
if (state == 'IDLE') {
while (idleFuncs.length > 0) {
const f = idleFuncs.shift();
f();
}
}
}, 0);
}
var self = {
setOnUserJoin: function(cb) {
const self = {
setOnUserJoin(cb) {
callbacks.onUserJoin = cb;
},
setOnUserLeave: function(cb) {
setOnUserLeave(cb) {
callbacks.onUserLeave = cb;
},
setOnUpdateUserInfo: function(cb) {
setOnUpdateUserInfo(cb) {
callbacks.onUpdateUserInfo = cb;
},
setOnChannelStateChange: function(cb) {
setOnChannelStateChange(cb) {
callbacks.onChannelStateChange = cb;
},
setOnClientMessage: function(cb) {
setOnClientMessage(cb) {
callbacks.onClientMessage = cb;
},
setOnInternalAction: function(cb) {
setOnInternalAction(cb) {
callbacks.onInternalAction = cb;
},
setOnConnectionTrouble: function(cb) {
setOnConnectionTrouble(cb) {
callbacks.onConnectionTrouble = cb;
},
setOnServerMessage: function(cb) {
setOnServerMessage(cb) {
callbacks.onServerMessage = cb;
},
updateUserInfo: defer(updateUserInfo),
handleMessageFromServer: handleMessageFromServer,
getConnectedUsers: getConnectedUsers,
sendClientMessage: sendClientMessage,
sendMessage: sendMessage,
getCurrentRevisionNumber: getCurrentRevisionNumber,
getMissedChanges: getMissedChanges,
callWhenNotCommitting: callWhenNotCommitting,
handleMessageFromServer,
getConnectedUsers,
sendClientMessage,
sendMessage,
getCurrentRevisionNumber,
getMissedChanges,
callWhenNotCommitting,
addHistoricalAuthors: tellAceAboutHistoricalAuthors,
setChannelState: setChannelState,
setStateIdle: setStateIdle,
setIsPendingRevision: setIsPendingRevision
setChannelState,
setStateIdle,
setIsPendingRevision,
};
setUpSocket();

View file

@ -22,111 +22,110 @@
* limitations under the License.
*/
var colorutils = {};
const colorutils = {};
// Check that a given value is a css hex color value, e.g.
// "#ffffff" or "#fff"
colorutils.isCssHex = function(cssColor) {
colorutils.isCssHex = function (cssColor) {
return /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(cssColor);
}
};
// "#ffffff" or "#fff" or "ffffff" or "fff" to [1.0, 1.0, 1.0]
colorutils.css2triple = function(cssColor) {
var sixHex = colorutils.css2sixhex(cssColor);
colorutils.css2triple = function (cssColor) {
const sixHex = colorutils.css2sixhex(cssColor);
function hexToFloat(hh) {
return Number("0x" + hh) / 255;
return Number(`0x${hh}`) / 255;
}
return [hexToFloat(sixHex.substr(0, 2)), hexToFloat(sixHex.substr(2, 2)), hexToFloat(sixHex.substr(4, 2))];
}
};
// "#ffffff" or "#fff" or "ffffff" or "fff" to "ffffff"
colorutils.css2sixhex = function(cssColor) {
var h = /[0-9a-fA-F]+/.exec(cssColor)[0];
if (h.length != 6)
{
var a = h.charAt(0);
var b = h.charAt(1);
var c = h.charAt(2);
colorutils.css2sixhex = function (cssColor) {
let h = /[0-9a-fA-F]+/.exec(cssColor)[0];
if (h.length != 6) {
const a = h.charAt(0);
const b = h.charAt(1);
const c = h.charAt(2);
h = a + a + b + b + c + c;
}
return h;
}
};
// [1.0, 1.0, 1.0] -> "#ffffff"
colorutils.triple2css = function(triple) {
colorutils.triple2css = function (triple) {
function floatToHex(n) {
var n2 = colorutils.clamp(Math.round(n * 255), 0, 255);
return ("0" + n2.toString(16)).slice(-2);
const n2 = colorutils.clamp(Math.round(n * 255), 0, 255);
return (`0${n2.toString(16)}`).slice(-2);
}
return "#" + floatToHex(triple[0]) + floatToHex(triple[1]) + floatToHex(triple[2]);
}
return `#${floatToHex(triple[0])}${floatToHex(triple[1])}${floatToHex(triple[2])}`;
};
colorutils.clamp = function(v, bot, top) {
colorutils.clamp = function (v, bot, top) {
return v < bot ? bot : (v > top ? top : v);
};
colorutils.min3 = function(a, b, c) {
colorutils.min3 = function (a, b, c) {
return (a < b) ? (a < c ? a : c) : (b < c ? b : c);
};
colorutils.max3 = function(a, b, c) {
colorutils.max3 = function (a, b, c) {
return (a > b) ? (a > c ? a : c) : (b > c ? b : c);
};
colorutils.colorMin = function(c) {
colorutils.colorMin = function (c) {
return colorutils.min3(c[0], c[1], c[2]);
};
colorutils.colorMax = function(c) {
colorutils.colorMax = function (c) {
return colorutils.max3(c[0], c[1], c[2]);
};
colorutils.scale = function(v, bot, top) {
colorutils.scale = function (v, bot, top) {
return colorutils.clamp(bot + v * (top - bot), 0, 1);
};
colorutils.unscale = function(v, bot, top) {
colorutils.unscale = function (v, bot, top) {
return colorutils.clamp((v - bot) / (top - bot), 0, 1);
};
colorutils.scaleColor = function(c, bot, top) {
colorutils.scaleColor = function (c, bot, top) {
return [colorutils.scale(c[0], bot, top), colorutils.scale(c[1], bot, top), colorutils.scale(c[2], bot, top)];
}
};
colorutils.unscaleColor = function(c, bot, top) {
colorutils.unscaleColor = function (c, bot, top) {
return [colorutils.unscale(c[0], bot, top), colorutils.unscale(c[1], bot, top), colorutils.unscale(c[2], bot, top)];
}
};
colorutils.luminosity = function(c) {
colorutils.luminosity = function (c) {
// rule of thumb for RGB brightness; 1.0 is white
return c[0] * 0.30 + c[1] * 0.59 + c[2] * 0.11;
}
};
colorutils.saturate = function(c) {
var min = colorutils.colorMin(c);
var max = colorutils.colorMax(c);
colorutils.saturate = function (c) {
const min = colorutils.colorMin(c);
const max = colorutils.colorMax(c);
if (max - min <= 0) return [1.0, 1.0, 1.0];
return colorutils.unscaleColor(c, min, max);
}
};
colorutils.blend = function(c1, c2, t) {
colorutils.blend = function (c1, c2, t) {
return [colorutils.scale(t, c1[0], c2[0]), colorutils.scale(t, c1[1], c2[1]), colorutils.scale(t, c1[2], c2[2])];
}
};
colorutils.invert = function(c) {
return [1 - c[0], 1 - c[1], 1- c[2]];
}
colorutils.invert = function (c) {
return [1 - c[0], 1 - c[1], 1 - c[2]];
};
colorutils.complementary = function(c) {
var inv = colorutils.invert(c);
colorutils.complementary = function (c) {
const inv = colorutils.invert(c);
return [
(inv[0] >= c[0]) ? Math.min(inv[0] * 1.30, 1) : (c[0] * 0.30),
(inv[1] >= c[1]) ? Math.min(inv[1] * 1.59, 1) : (c[1] * 0.59),
(inv[2] >= c[2]) ? Math.min(inv[2] * 1.11, 1) : (c[2] * 0.11)
(inv[2] >= c[2]) ? Math.min(inv[2] * 1.11, 1) : (c[2] * 0.11),
];
}
};
colorutils.textColorFromBackgroundColor = function(bgcolor, skinName) {
var white = skinName == 'colibris' ? 'var(--super-light-color)' : '#fff';
var black = skinName == 'colibris' ? 'var(--super-dark-color)' : '#222';
colorutils.textColorFromBackgroundColor = function (bgcolor, skinName) {
const white = skinName == 'colibris' ? 'var(--super-light-color)' : '#fff';
const black = skinName == 'colibris' ? 'var(--super-dark-color)' : '#222';
return colorutils.luminosity(colorutils.css2triple(bgcolor)) < 0.5 ? white : black;
}
};
exports.colorutils = colorutils;

File diff suppressed because it is too large Load diff

View file

@ -21,41 +21,34 @@
*/
function makeCSSManager(emptyStylesheetTitle, doc) {
if (doc === true)
{
if (doc === true) {
doc = 'parent';
} else if (!doc) {
doc = 'inner';
}
function getSheetByTitle(title) {
if (doc === 'parent')
{
if (doc === 'parent') {
win = window.parent.parent;
}
else if (doc === 'inner') {
} else if (doc === 'inner') {
win = window;
}
else if (doc === 'outer') {
} else if (doc === 'outer') {
win = window.parent;
} else {
throw 'Unknown dynamic style container';
}
else {
throw "Unknown dynamic style container";
}
var allSheets = win.document.styleSheets;
const allSheets = win.document.styleSheets;
for (var i = 0; i < allSheets.length; i++)
{
var s = allSheets[i];
if (s.title == title)
{
for (let i = 0; i < allSheets.length; i++) {
const s = allSheets[i];
if (s.title == title) {
return s;
}
}
return null;
}
var browserSheet = getSheetByTitle(emptyStylesheetTitle);
const browserSheet = getSheetByTitle(emptyStylesheetTitle);
function browserRules() {
return (browserSheet.cssRules || browserSheet.rules);
@ -67,16 +60,14 @@ function makeCSSManager(emptyStylesheetTitle, doc) {
}
function browserInsertRule(i, selector) {
if (browserSheet.insertRule) browserSheet.insertRule(selector + ' {}', i);
if (browserSheet.insertRule) browserSheet.insertRule(`${selector} {}`, i);
else browserSheet.addRule(selector, null, i);
}
var selectorList = [];
const selectorList = [];
function indexOfSelector(selector) {
for (var i = 0; i < selectorList.length; i++)
{
if (selectorList[i] == selector)
{
for (let i = 0; i < selectorList.length; i++) {
if (selectorList[i] == selector) {
return i;
}
}
@ -84,9 +75,8 @@ function makeCSSManager(emptyStylesheetTitle, doc) {
}
function selectorStyle(selector) {
var i = indexOfSelector(selector);
if (i < 0)
{
let i = indexOfSelector(selector);
if (i < 0) {
// add selector
browserInsertRule(0, selector);
selectorList.splice(0, 0, selector);
@ -96,20 +86,19 @@ function makeCSSManager(emptyStylesheetTitle, doc) {
}
function removeSelectorStyle(selector) {
var i = indexOfSelector(selector);
if (i >= 0)
{
const i = indexOfSelector(selector);
if (i >= 0) {
browserDeleteRule(i);
selectorList.splice(i, 1);
}
}
return {
selectorStyle: selectorStyle,
removeSelectorStyle: removeSelectorStyle,
info: function() {
return selectorList.length + ":" + browserRules().length;
}
selectorStyle,
removeSelectorStyle,
info() {
return `${selectorList.length}:${browserRules().length}`;
},
};
}

View file

@ -26,167 +26,150 @@
// requires: plugins
// requires: undefined
var Security = require('./security');
var hooks = require('./pluginfw/hooks');
var _ = require('./underscore');
var lineAttributeMarker = require('./linestylefilter').lineAttributeMarker;
var noop = function(){};
const Security = require('./security');
const hooks = require('./pluginfw/hooks');
const _ = require('./underscore');
const lineAttributeMarker = require('./linestylefilter').lineAttributeMarker;
const noop = function () {};
var domline = {};
const domline = {};
domline.addToLineClass = function(lineClass, cls) {
domline.addToLineClass = function (lineClass, cls) {
// an "empty span" at any point can be used to add classes to
// the line, using line:className. otherwise, we ignore
// the span.
cls.replace(/\S+/g, function(c) {
if (c.indexOf("line:") == 0)
{
cls.replace(/\S+/g, (c) => {
if (c.indexOf('line:') == 0) {
// add class to line
lineClass = (lineClass ? lineClass + ' ' : '') + c.substring(5);
lineClass = (lineClass ? `${lineClass} ` : '') + c.substring(5);
}
});
return lineClass;
}
};
// if "document" is falsy we don't create a DOM node, just
// an object with innerHTML and className
domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) {
var result = {
domline.createDomLine = function (nonEmpty, doesWrap, optBrowser, optDocument) {
const result = {
node: null,
appendSpan: noop,
prepareForAdd: noop,
notifyAdded: noop,
clearSpans: noop,
finishUpdate: noop,
lineMarker: 0
lineMarker: 0,
};
var document = optDocument;
const document = optDocument;
if (document)
{
result.node = document.createElement("div");
}
else
{
if (document) {
result.node = document.createElement('div');
} else {
result.node = {
innerHTML: '',
className: ''
className: '',
};
}
var html = [];
var preHtml = '',
postHtml = '';
var curHTML = null;
let html = [];
let preHtml = '';
let postHtml = '';
let curHTML = null;
function processSpaces(s) {
return domline.processSpaces(s, doesWrap);
}
var perTextNodeProcess = (doesWrap ? _.identity : processSpaces);
var perHtmlLineProcess = (doesWrap ? processSpaces : _.identity);
var lineClass = 'ace-line';
const perTextNodeProcess = (doesWrap ? _.identity : processSpaces);
const perHtmlLineProcess = (doesWrap ? processSpaces : _.identity);
let lineClass = 'ace-line';
result.appendSpan = function(txt, cls) {
var processedMarker = false;
result.appendSpan = function (txt, cls) {
let processedMarker = false;
// Handle lineAttributeMarker, if present
if (cls.indexOf(lineAttributeMarker) >= 0)
{
var listType = /(?:^| )list:(\S+)/.exec(cls);
var start = /(?:^| )start:(\S+)/.exec(cls);
if (cls.indexOf(lineAttributeMarker) >= 0) {
let listType = /(?:^| )list:(\S+)/.exec(cls);
const start = /(?:^| )start:(\S+)/.exec(cls);
_.map(hooks.callAll("aceDomLinePreProcessLineAttributes", {
domline: domline,
cls: cls
}), function(modifier) {
_.map(hooks.callAll('aceDomLinePreProcessLineAttributes', {
domline,
cls,
}), (modifier) => {
preHtml += modifier.preHtml;
postHtml += modifier.postHtml;
processedMarker |= modifier.processedMarker;
});
if (listType)
{
if (listType) {
listType = listType[1];
if (listType)
{
if(listType.indexOf("number") < 0)
{
preHtml += '<ul class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>';
postHtml = '</li></ul>' + postHtml;
}
else
{
if(start){ // is it a start of a list with more than one item in?
if(start[1] == 1){ // if its the first one at this level?
lineClass = lineClass + " " + "list-start-" + listType; // Add start class to DIV node
if (listType) {
if (listType.indexOf('number') < 0) {
preHtml += `<ul class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
postHtml = `</li></ul>${postHtml}`;
} else {
if (start) { // is it a start of a list with more than one item in?
if (start[1] == 1) { // if its the first one at this level?
lineClass = `${lineClass} ` + `list-start-${listType}`; // Add start class to DIV node
}
preHtml += '<ol start='+start[1]+' class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>';
}else{
preHtml += '<ol class="list-' + Security.escapeHTMLAttribute(listType) + '"><li>'; // Handles pasted contents into existing lists
preHtml += `<ol start=${start[1]} class="list-${Security.escapeHTMLAttribute(listType)}"><li>`;
} else {
preHtml += `<ol class="list-${Security.escapeHTMLAttribute(listType)}"><li>`; // Handles pasted contents into existing lists
}
postHtml += '</li></ol>';
}
}
processedMarker = true;
}
_.map(hooks.callAll("aceDomLineProcessLineAttributes", {
domline: domline,
cls: cls
}), function(modifier) {
_.map(hooks.callAll('aceDomLineProcessLineAttributes', {
domline,
cls,
}), (modifier) => {
preHtml += modifier.preHtml;
postHtml += modifier.postHtml;
processedMarker |= modifier.processedMarker;
});
if( processedMarker ){
if (processedMarker) {
result.lineMarker += txt.length;
return; // don't append any text
}
}
var href = null;
var simpleTags = null;
if (cls.indexOf('url') >= 0)
{
cls = cls.replace(/(^| )url:(\S+)/g, function(x0, space, url) {
let href = null;
let simpleTags = null;
if (cls.indexOf('url') >= 0) {
cls = cls.replace(/(^| )url:(\S+)/g, (x0, space, url) => {
href = url;
return space + "url";
return `${space}url`;
});
}
if (cls.indexOf('tag') >= 0)
{
cls = cls.replace(/(^| )tag:(\S+)/g, function(x0, space, tag) {
if (cls.indexOf('tag') >= 0) {
cls = cls.replace(/(^| )tag:(\S+)/g, (x0, space, tag) => {
if (!simpleTags) simpleTags = [];
simpleTags.push(tag.toLowerCase());
return space + tag;
});
}
var extraOpenTags = "";
var extraCloseTags = "";
let extraOpenTags = '';
let extraCloseTags = '';
_.map(hooks.callAll("aceCreateDomLine", {
domline: domline,
cls: cls
}), function(modifier) {
_.map(hooks.callAll('aceCreateDomLine', {
domline,
cls,
}), (modifier) => {
cls = modifier.cls;
extraOpenTags = extraOpenTags + modifier.extraOpenTags;
extraOpenTags += modifier.extraOpenTags;
extraCloseTags = modifier.extraCloseTags + extraCloseTags;
});
if ((!txt) && cls)
{
if ((!txt) && cls) {
lineClass = domline.addToLineClass(lineClass, cls);
}
else if (txt)
{
if (href)
{
urn_schemes = new RegExp("^(about|geo|mailto|tel):");
if(!~href.indexOf("://") && !urn_schemes.test(href)) // if the url doesn't include a protocol prefix, assume http
} else if (txt) {
if (href) {
urn_schemes = new RegExp('^(about|geo|mailto|tel):');
if (!~href.indexOf('://') && !urn_schemes.test(href)) // if the url doesn't include a protocol prefix, assume http
{
href = "http://"+href;
href = `http://${href}`;
}
// Using rel="noreferrer" stops leaking the URL/location of the pad when clicking links in the document.
// Not all browsers understand this attribute, but it's part of the HTML5 standard.
@ -195,115 +178,94 @@ domline.createDomLine = function(nonEmpty, doesWrap, optBrowser, optDocument) {
// 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
extraOpenTags = extraOpenTags + '<a href="' + Security.escapeHTMLAttribute(href) + '" rel="noreferrer noopener">';
extraCloseTags = '</a>' + extraCloseTags;
extraOpenTags = `${extraOpenTags}<a href="${Security.escapeHTMLAttribute(href)}" rel="noreferrer noopener">`;
extraCloseTags = `</a>${extraCloseTags}`;
}
if (simpleTags)
{
if (simpleTags) {
simpleTags.sort();
extraOpenTags = extraOpenTags + '<' + simpleTags.join('><') + '>';
extraOpenTags = `${extraOpenTags}<${simpleTags.join('><')}>`;
simpleTags.reverse();
extraCloseTags = '</' + simpleTags.join('></') + '>' + extraCloseTags;
extraCloseTags = `</${simpleTags.join('></')}>${extraCloseTags}`;
}
html.push('<span class="', Security.escapeHTMLAttribute(cls || ''), '">', extraOpenTags, perTextNodeProcess(Security.escapeHTML(txt)), extraCloseTags, '</span>');
}
};
result.clearSpans = function() {
result.clearSpans = function () {
html = [];
lineClass = 'ace-line';
result.lineMarker = 0;
};
function writeHTML() {
var newHTML = perHtmlLineProcess(html.join(''));
if (!newHTML)
{
if ((!document) || (!optBrowser))
{
let newHTML = perHtmlLineProcess(html.join(''));
if (!newHTML) {
if ((!document) || (!optBrowser)) {
newHTML += '&nbsp;';
}
else if (!optBrowser.msie)
{
} else if (!optBrowser.msie) {
newHTML += '<br/>';
}
}
if (nonEmpty)
{
if (nonEmpty) {
newHTML = (preHtml || '') + newHTML + (postHtml || '');
}
html = preHtml = postHtml = ''; // free memory
if (newHTML !== curHTML)
{
if (newHTML !== curHTML) {
curHTML = newHTML;
result.node.innerHTML = curHTML;
}
if (lineClass !== null) result.node.className = lineClass;
hooks.callAll("acePostWriteDomLineHTML", {
node: result.node
hooks.callAll('acePostWriteDomLineHTML', {
node: result.node,
});
}
result.prepareForAdd = writeHTML;
result.finishUpdate = writeHTML;
result.getInnerHTML = function() {
result.getInnerHTML = function () {
return curHTML || '';
};
return result;
};
domline.processSpaces = function(s, doesWrap) {
if (s.indexOf("<") < 0 && !doesWrap)
{
domline.processSpaces = function (s, doesWrap) {
if (s.indexOf('<') < 0 && !doesWrap) {
// short-cut
return s.replace(/ /g, '&nbsp;');
}
var parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, function(m) {
const parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, (m) => {
parts.push(m);
});
if (doesWrap)
{
var endOfLine = true;
var beforeSpace = false;
if (doesWrap) {
let endOfLine = true;
let beforeSpace = false;
// last space in a run is normal, others are nbsp,
// end of line is nbsp
for (var i = parts.length - 1; i >= 0; i--)
{
for (var i = parts.length - 1; i >= 0; i--) {
var p = parts[i];
if (p == " ")
{
if (p == ' ') {
if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false;
beforeSpace = true;
}
else if (p.charAt(0) != "<")
{
} else if (p.charAt(0) != '<') {
endOfLine = false;
beforeSpace = false;
}
}
// beginning of line is nbsp
for (var i = 0; i < parts.length; i++)
{
for (var i = 0; i < parts.length; i++) {
var p = parts[i];
if (p == " ")
{
if (p == ' ') {
parts[i] = '&nbsp;';
break;
}
else if (p.charAt(0) != "<")
{
} else if (p.charAt(0) != '<') {
break;
}
}
}
else
{
for (var i = 0; i < parts.length; i++)
{
} else {
for (var i = 0; i < parts.length; i++) {
var p = parts[i];
if (p == " ")
{
if (p == ' ') {
parts[i] = '&nbsp;';
}
}

View file

@ -33,25 +33,25 @@ function randomPadName() {
for (let i = 0; i < string_length; i++) {
// instead of writing "Math.floor(randomarray[i]/256*64)"
// we can save some cycles.
const rnum = Math.floor(randomarray[i]/4);
const rnum = Math.floor(randomarray[i] / 4);
randomstring += chars.substring(rnum, rnum + 1);
}
return randomstring;
}
$(() => {
$('#go2Name').submit(function() {
$('#go2Name').submit(() => {
const padname = $('#padname').val();
if (padname.length > 0) {
window.location = 'p/' + encodeURIComponent(padname.trim());
window.location = `p/${encodeURIComponent(padname.trim())}`;
} else {
alert('Please enter a name');
}
return false;
});
$('#button').click(function() {
window.location = 'p/' + randomPadName();
$('#button').click(() => {
window.location = `p/${randomPadName()}`;
});
// start the custom js

View file

@ -1,14 +1,14 @@
(function(document) {
(function (document) {
// Set language for l10n
var language = document.cookie.match(/language=((\w{2,3})(-\w+)?)/);
if(language) language = language[1];
let language = document.cookie.match(/language=((\w{2,3})(-\w+)?)/);
if (language) language = language[1];
html10n.bind('indexed', function() {
html10n.localize([language, navigator.language, navigator.userLanguage, 'en'])
})
html10n.bind('indexed', () => {
html10n.localize([language, navigator.language, navigator.userLanguage, 'en']);
});
html10n.bind('localized', function() {
document.documentElement.lang = html10n.getLanguage()
document.documentElement.dir = html10n.getDirection()
})
})(document)
html10n.bind('localized', () => {
document.documentElement.lang = html10n.getLanguage();
document.documentElement.dir = html10n.getDirection();
});
})(document);

View file

@ -28,33 +28,32 @@
// requires: plugins
// requires: undefined
var Changeset = require('./Changeset');
var hooks = require('./pluginfw/hooks');
var linestylefilter = {};
var _ = require('./underscore');
var AttributeManager = require('./AttributeManager');
const Changeset = require('./Changeset');
const hooks = require('./pluginfw/hooks');
const linestylefilter = {};
const _ = require('./underscore');
const AttributeManager = require('./AttributeManager');
linestylefilter.ATTRIB_CLASSES = {
'bold': 'tag:b',
'italic': 'tag:i',
'underline': 'tag:u',
'strikethrough': 'tag:s'
bold: 'tag:b',
italic: 'tag:i',
underline: 'tag:u',
strikethrough: 'tag:s',
};
var lineAttributeMarker = 'lineAttribMarker';
const lineAttributeMarker = 'lineAttribMarker';
exports.lineAttributeMarker = lineAttributeMarker;
linestylefilter.getAuthorClassName = function(author) {
return "author-" + author.replace(/[^a-y0-9]/g, function(c) {
if (c == ".") return "-";
return 'z' + c.charCodeAt(0) + 'z';
});
linestylefilter.getAuthorClassName = function (author) {
return `author-${author.replace(/[^a-y0-9]/g, (c) => {
if (c == '.') return '-';
return `z${c.charCodeAt(0)}z`;
})}`;
};
// lineLength is without newline; aline includes newline,
// but may be falsy if lineLength == 0
linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFunc, apool) {
linestylefilter.getLineStyleFilter = function (lineLength, aline, textAndClassFunc, apool) {
// Plugin Hook to add more Attrib Classes
for (const attribClasses of hooks.callAll('aceAttribClasses', linestylefilter.ATTRIB_CLASSES)) {
Object.assign(linestylefilter.ATTRIB_CLASSES, attribClasses);
@ -62,64 +61,54 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
if (lineLength == 0) return textAndClassFunc;
var nextAfterAuthorColors = textAndClassFunc;
const nextAfterAuthorColors = textAndClassFunc;
var authorColorFunc = (function() {
var lineEnd = lineLength;
var curIndex = 0;
var extraClasses;
var leftInAuthor;
const authorColorFunc = (function () {
const lineEnd = lineLength;
let curIndex = 0;
let extraClasses;
let leftInAuthor;
function attribsToClasses(attribs) {
var classes = '';
var isLineAttribMarker = false;
let classes = '';
let isLineAttribMarker = false;
// For each attribute number
Changeset.eachAttribNumber(attribs, function(n) {
Changeset.eachAttribNumber(attribs, (n) => {
// Give us this attributes key
var key = apool.getAttribKey(n);
if (key)
{
var value = apool.getAttribValue(n);
if (value)
{
if (!isLineAttribMarker && _.indexOf(AttributeManager.lineAttributes, key) >= 0){
const key = apool.getAttribKey(n);
if (key) {
const value = apool.getAttribValue(n);
if (value) {
if (!isLineAttribMarker && _.indexOf(AttributeManager.lineAttributes, key) >= 0) {
isLineAttribMarker = true;
}
if (key == 'author')
{
classes += ' ' + linestylefilter.getAuthorClassName(value);
}
else if (key == 'list')
{
classes += ' list:' + value;
}
else if (key == 'start'){
if (key == 'author') {
classes += ` ${linestylefilter.getAuthorClassName(value)}`;
} else if (key == 'list') {
classes += ` list:${value}`;
} else if (key == 'start') {
// Needed to introduce the correct Ordered list item start number on import
classes += ' start:' + value;
}
else if (linestylefilter.ATTRIB_CLASSES[key])
{
classes += ' ' + linestylefilter.ATTRIB_CLASSES[key];
}
else
{
classes += hooks.callAllStr("aceAttribsToClasses", {
linestylefilter: linestylefilter,
key: key,
value: value
}, " ", " ", "");
classes += ` start:${value}`;
} else if (linestylefilter.ATTRIB_CLASSES[key]) {
classes += ` ${linestylefilter.ATTRIB_CLASSES[key]}`;
} else {
classes += hooks.callAllStr('aceAttribsToClasses', {
linestylefilter,
key,
value,
}, ' ', ' ', '');
}
}
}
});
if(isLineAttribMarker) classes += ' ' + lineAttributeMarker;
if (isLineAttribMarker) classes += ` ${lineAttributeMarker}`;
return classes.substring(1);
}
var attributionIter = Changeset.opIterator(aline);
var nextOp, nextOpClasses;
const attributionIter = Changeset.opIterator(aline);
let nextOp, nextOpClasses;
function goNextOp() {
nextOp = attributionIter.next();
@ -128,13 +117,11 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
goNextOp();
function nextClasses() {
if (curIndex < lineEnd)
{
if (curIndex < lineEnd) {
extraClasses = nextOpClasses;
leftInAuthor = nextOp.chars;
goNextOp();
while (nextOp.opcode && nextOpClasses == extraClasses)
{
while (nextOp.opcode && nextOpClasses == extraClasses) {
leftInAuthor += nextOp.chars;
goNextOp();
}
@ -142,33 +129,28 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
}
nextClasses();
return function(txt, cls) {
var disableAuthColorForThisLine = hooks.callAll("disableAuthorColorsForThisLine", {
linestylefilter: linestylefilter,
return function (txt, cls) {
const disableAuthColorForThisLine = hooks.callAll('disableAuthorColorsForThisLine', {
linestylefilter,
text: txt,
"class": cls
}, " ", " ", "");
var disableAuthors = (disableAuthColorForThisLine==null||disableAuthColorForThisLine.length==0)?false:disableAuthColorForThisLine[0];
while (txt.length > 0)
{
if (leftInAuthor <= 0 || disableAuthors)
{
class: cls,
}, ' ', ' ', '');
const disableAuthors = (disableAuthColorForThisLine == null || disableAuthColorForThisLine.length == 0) ? false : disableAuthColorForThisLine[0];
while (txt.length > 0) {
if (leftInAuthor <= 0 || disableAuthors) {
// prevent infinite loop if something funny's going on
return nextAfterAuthorColors(txt, cls);
}
var spanSize = txt.length;
if (spanSize > leftInAuthor)
{
let spanSize = txt.length;
if (spanSize > leftInAuthor) {
spanSize = leftInAuthor;
}
var curTxt = txt.substring(0, spanSize);
const curTxt = txt.substring(0, spanSize);
txt = txt.substring(spanSize);
nextAfterAuthorColors(curTxt, (cls && cls + " ") + extraClasses);
nextAfterAuthorColors(curTxt, (cls && `${cls} `) + extraClasses);
curIndex += spanSize;
leftInAuthor -= spanSize;
if (leftInAuthor == 0)
{
if (leftInAuthor == 0) {
nextClasses();
}
}
@ -177,15 +159,13 @@ linestylefilter.getLineStyleFilter = function(lineLength, aline, textAndClassFun
return authorColorFunc;
};
linestylefilter.getAtSignSplitterFilter = function(lineText, textAndClassFunc) {
var at = /@/g;
linestylefilter.getAtSignSplitterFilter = function (lineText, textAndClassFunc) {
const at = /@/g;
at.lastIndex = 0;
var splitPoints = null;
var execResult;
while ((execResult = at.exec(lineText)))
{
if (!splitPoints)
{
let splitPoints = null;
let execResult;
while ((execResult = at.exec(lineText))) {
if (!splitPoints) {
splitPoints = [];
}
splitPoints.push(execResult.index);
@ -196,21 +176,19 @@ linestylefilter.getAtSignSplitterFilter = function(lineText, textAndClassFunc) {
return linestylefilter.textAndClassFuncSplitter(textAndClassFunc, splitPoints);
};
linestylefilter.getRegexpFilter = function(regExp, tag) {
return function(lineText, textAndClassFunc) {
linestylefilter.getRegexpFilter = function (regExp, tag) {
return function (lineText, textAndClassFunc) {
regExp.lastIndex = 0;
var regExpMatchs = null;
var splitPoints = null;
var execResult;
while ((execResult = regExp.exec(lineText)))
{
if (!regExpMatchs)
{
let regExpMatchs = null;
let splitPoints = null;
let execResult;
while ((execResult = regExp.exec(lineText))) {
if (!regExpMatchs) {
regExpMatchs = [];
splitPoints = [];
}
var startIndex = execResult.index;
var regExpMatch = execResult[0];
const startIndex = execResult.index;
const regExpMatch = execResult[0];
regExpMatchs.push([startIndex, regExpMatch]);
splitPoints.push(startIndex, startIndex + regExpMatch.length);
}
@ -218,26 +196,23 @@ linestylefilter.getRegexpFilter = function(regExp, tag) {
if (!regExpMatchs) return textAndClassFunc;
function regExpMatchForIndex(idx) {
for (var k = 0; k < regExpMatchs.length; k++)
{
var u = regExpMatchs[k];
if (idx >= u[0] && idx < u[0] + u[1].length)
{
for (let k = 0; k < regExpMatchs.length; k++) {
const u = regExpMatchs[k];
if (idx >= u[0] && idx < u[0] + u[1].length) {
return u[1];
}
}
return false;
}
var handleRegExpMatchsAfterSplit = (function() {
var curIndex = 0;
return function(txt, cls) {
var txtlen = txt.length;
var newCls = cls;
var regExpMatch = regExpMatchForIndex(curIndex);
if (regExpMatch)
{
newCls += " " + tag + ":" + regExpMatch;
const handleRegExpMatchsAfterSplit = (function () {
let curIndex = 0;
return function (txt, cls) {
const txtlen = txt.length;
let newCls = cls;
const regExpMatch = regExpMatchForIndex(curIndex);
if (regExpMatch) {
newCls += ` ${tag}:${regExpMatch}`;
}
textAndClassFunc(txt, newCls);
curIndex += txtlen;
@ -250,45 +225,36 @@ linestylefilter.getRegexpFilter = function(regExp, tag) {
linestylefilter.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]/;
linestylefilter.REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#!;()$]/.source + '|' + linestylefilter.REGEX_WORDCHAR.source + ')');
linestylefilter.REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|(about|geo|mailto|tel):|www\.)/.source + linestylefilter.REGEX_URLCHAR.source + '*(?![:.,;])' + linestylefilter.REGEX_URLCHAR.source, 'g');
linestylefilter.REGEX_URLCHAR = new RegExp(`(${/[-:@a-zA-Z0-9_.,~%+\/\\?=&#!;()$]/.source}|${linestylefilter.REGEX_WORDCHAR.source})`);
linestylefilter.REGEX_URL = new RegExp(`${/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|(about|geo|mailto|tel):|www\.)/.source + linestylefilter.REGEX_URLCHAR.source}*(?![:.,;])${linestylefilter.REGEX_URLCHAR.source}`, 'g');
linestylefilter.getURLFilter = linestylefilter.getRegexpFilter(
linestylefilter.REGEX_URL, 'url');
linestylefilter.REGEX_URL, 'url');
linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt) {
var nextPointIndex = 0;
var idx = 0;
linestylefilter.textAndClassFuncSplitter = function (func, splitPointsOpt) {
let nextPointIndex = 0;
let idx = 0;
// don't split at 0
while (splitPointsOpt && nextPointIndex < splitPointsOpt.length && splitPointsOpt[nextPointIndex] == 0)
{
while (splitPointsOpt && nextPointIndex < splitPointsOpt.length && splitPointsOpt[nextPointIndex] == 0) {
nextPointIndex++;
}
function spanHandler(txt, cls) {
if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length)
{
if ((!splitPointsOpt) || nextPointIndex >= splitPointsOpt.length) {
func(txt, cls);
idx += txt.length;
}
else
{
var splitPoints = splitPointsOpt;
var pointLocInSpan = splitPoints[nextPointIndex] - idx;
var txtlen = txt.length;
if (pointLocInSpan >= txtlen)
{
} else {
const splitPoints = splitPointsOpt;
const pointLocInSpan = splitPoints[nextPointIndex] - idx;
const txtlen = txt.length;
if (pointLocInSpan >= txtlen) {
func(txt, cls);
idx += txt.length;
if (pointLocInSpan == txtlen)
{
if (pointLocInSpan == txtlen) {
nextPointIndex++;
}
}
else
{
if (pointLocInSpan > 0)
{
} else {
if (pointLocInSpan > 0) {
func(txt.substring(0, pointLocInSpan), cls);
idx += pointLocInSpan;
}
@ -301,34 +267,32 @@ linestylefilter.textAndClassFuncSplitter = function(func, splitPointsOpt) {
return spanHandler;
};
linestylefilter.getFilterStack = function(lineText, textAndClassFunc, abrowser) {
var func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
linestylefilter.getFilterStack = function (lineText, textAndClassFunc, abrowser) {
let func = linestylefilter.getURLFilter(lineText, textAndClassFunc);
var hookFilters = hooks.callAll("aceGetFilterStack", {
linestylefilter: linestylefilter,
browser: abrowser
const hookFilters = hooks.callAll('aceGetFilterStack', {
linestylefilter,
browser: abrowser,
});
_.map(hookFilters ,function(hookFilter) {
_.map(hookFilters, (hookFilter) => {
func = hookFilter(lineText, func);
});
if (abrowser !== undefined && abrowser.msie)
{
if (abrowser !== undefined && abrowser.msie) {
// IE7+ will take an e-mail address like <foo@bar.com> and linkify it to foo@bar.com.
// We then normalize it back to text with no angle brackets. It's weird. So always
// break spans at an "at" sign.
func = linestylefilter.getAtSignSplitterFilter(
lineText, func);
lineText, func);
}
return func;
};
// domLineObj is like that returned by domline.createDomLine
linestylefilter.populateDomLine = function(textLine, aline, apool, domLineObj) {
linestylefilter.populateDomLine = function (textLine, aline, apool, domLineObj) {
// remove final newline from text if any
var text = textLine;
if (text.slice(-1) == '\n')
{
let text = textLine;
if (text.slice(-1) == '\n') {
text = text.substring(0, text.length - 1);
}
@ -336,7 +300,7 @@ linestylefilter.populateDomLine = function(textLine, aline, apool, domLineObj) {
domLineObj.appendSpan(tokenText, tokenClass);
}
var func = linestylefilter.getFilterStack(text, textAndClassFunc);
let func = linestylefilter.getFilterStack(text, textAndClassFunc);
func = linestylefilter.getLineStyleFilter(text.length, aline, func, apool);
func(text, '');
};

File diff suppressed because it is too large Load diff

View file

@ -1,24 +1,23 @@
exports.showCountDownTimerToReconnectOnModal = function($modal, pad) {
exports.showCountDownTimerToReconnectOnModal = function ($modal, pad) {
if (clientVars.automaticReconnectionTimeout && $modal.is('.with_reconnect_timer')) {
createCountDownElementsIfNecessary($modal);
var timer = createTimerForModal($modal, pad);
const timer = createTimerForModal($modal, pad);
$modal.find('#cancelreconnect').one('click', function() {
$modal.find('#cancelreconnect').one('click', () => {
timer.cancel();
disableAutomaticReconnection($modal);
});
enableAutomaticReconnection($modal);
}
}
};
var createCountDownElementsIfNecessary = function($modal) {
var elementsDoNotExist = $modal.find('#cancelreconnect').length === 0;
var createCountDownElementsIfNecessary = function ($modal) {
const elementsDoNotExist = $modal.find('#cancelreconnect').length === 0;
if (elementsDoNotExist) {
var $defaultMessage = $modal.find('#defaulttext');
var $reconnectButton = $modal.find('#forcereconnect');
const $defaultMessage = $modal.find('#defaulttext');
const $reconnectButton = $modal.find('#forcereconnect');
// create extra DOM elements, if they don't exist
const $reconnectTimerMessage =
@ -44,100 +43,100 @@ var createCountDownElementsIfNecessary = function($modal) {
$reconnectTimerMessage.insertAfter($defaultMessage);
$cancelReconnect.insertAfter($reconnectButton);
}
}
};
var localize = function($element) {
var localize = function ($element) {
html10n.translateElement(html10n.translations, $element.get(0));
};
var createTimerForModal = function($modal, pad) {
var timeUntilReconnection = clientVars.automaticReconnectionTimeout * reconnectionTries.nextTry();
var timer = new CountDownTimer(timeUntilReconnection);
var createTimerForModal = function ($modal, pad) {
const timeUntilReconnection = clientVars.automaticReconnectionTimeout * reconnectionTries.nextTry();
const timer = new CountDownTimer(timeUntilReconnection);
timer.onTick(function(minutes, seconds) {
timer.onTick((minutes, seconds) => {
updateCountDownTimerMessage($modal, minutes, seconds);
}).onExpire(function() {
var wasANetworkError = $modal.is('.disconnected');
}).onExpire(() => {
const wasANetworkError = $modal.is('.disconnected');
if (wasANetworkError) {
// cannot simply reconnect, client is having issues to establish connection to server
waitUntilClientCanConnectToServerAndThen(function() { forceReconnection($modal); }, pad);
waitUntilClientCanConnectToServerAndThen(() => { forceReconnection($modal); }, pad);
} else {
forceReconnection($modal);
}
}).start();
return timer;
}
};
var disableAutomaticReconnection = function($modal) {
var disableAutomaticReconnection = function ($modal) {
toggleAutomaticReconnectionOption($modal, true);
}
var enableAutomaticReconnection = function($modal) {
};
var enableAutomaticReconnection = function ($modal) {
toggleAutomaticReconnectionOption($modal, false);
}
var toggleAutomaticReconnectionOption = function($modal, disableAutomaticReconnect) {
};
var toggleAutomaticReconnectionOption = function ($modal, disableAutomaticReconnect) {
$modal.find('#cancelreconnect, .reconnecttimer').toggleClass('hidden', disableAutomaticReconnect);
$modal.find('#defaulttext').toggleClass('hidden', !disableAutomaticReconnect);
}
};
var waitUntilClientCanConnectToServerAndThen = function(callback, pad) {
var waitUntilClientCanConnectToServerAndThen = function (callback, pad) {
whenConnectionIsRestablishedWithServer(callback, pad);
pad.socket.connect();
}
};
var whenConnectionIsRestablishedWithServer = function(callback, pad) {
var whenConnectionIsRestablishedWithServer = function (callback, pad) {
// only add listener for the first try, don't need to add another listener
// on every unsuccessful try
if (reconnectionTries.counter === 1) {
pad.socket.once('connect', callback);
}
}
};
var forceReconnection = function($modal) {
var forceReconnection = function ($modal) {
$modal.find('#forcereconnect').click();
}
};
var updateCountDownTimerMessage = function($modal, minutes, seconds) {
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
var updateCountDownTimerMessage = function ($modal, minutes, seconds) {
minutes = minutes < 10 ? `0${minutes}` : minutes;
seconds = seconds < 10 ? `0${seconds}` : seconds;
$modal.find('.timetoexpire').text(minutes + ':' + seconds);
}
$modal.find('.timetoexpire').text(`${minutes}:${seconds}`);
};
// store number of tries to reconnect to server, in order to increase time to wait
// until next try
var reconnectionTries = {
counter: 0,
nextTry: function() {
nextTry() {
// double the time to try to reconnect on every time reconnection fails
var nextCounterFactor = Math.pow(2, this.counter);
const nextCounterFactor = Math.pow(2, this.counter);
this.counter++;
return nextCounterFactor;
}
}
},
};
// Timer based on http://stackoverflow.com/a/20618517.
// duration: how many **seconds** until the timer ends
// granularity (optional): how many **milliseconds** between each 'tick' of timer. Default: 1000ms (1s)
var CountDownTimer = function(duration, granularity) {
this.duration = duration;
var CountDownTimer = function (duration, granularity) {
this.duration = duration;
this.granularity = granularity || 1000;
this.running = false;
this.running = false;
this.onTickCallbacks = [];
this.onTickCallbacks = [];
this.onExpireCallbacks = [];
}
};
CountDownTimer.prototype.start = function() {
CountDownTimer.prototype.start = function () {
if (this.running) {
return;
}
this.running = true;
var start = Date.now(),
that = this,
diff;
const start = Date.now();
const that = this;
let diff;
(function timer() {
diff = that.duration - Math.floor((Date.now() - start) / 1000);
@ -153,41 +152,41 @@ CountDownTimer.prototype.start = function() {
}());
};
CountDownTimer.prototype.tick = function(diff) {
var obj = CountDownTimer.parse(diff);
this.onTickCallbacks.forEach(function(callback) {
CountDownTimer.prototype.tick = function (diff) {
const obj = CountDownTimer.parse(diff);
this.onTickCallbacks.forEach(function (callback) {
callback.call(this, obj.minutes, obj.seconds);
}, this);
}
CountDownTimer.prototype.expire = function() {
this.onExpireCallbacks.forEach(function(callback) {
};
CountDownTimer.prototype.expire = function () {
this.onExpireCallbacks.forEach(function (callback) {
callback.call(this);
}, this);
}
};
CountDownTimer.prototype.onTick = function(callback) {
CountDownTimer.prototype.onTick = function (callback) {
if (typeof callback === 'function') {
this.onTickCallbacks.push(callback);
}
return this;
};
CountDownTimer.prototype.onExpire = function(callback) {
CountDownTimer.prototype.onExpire = function (callback) {
if (typeof callback === 'function') {
this.onExpireCallbacks.push(callback);
}
return this;
};
CountDownTimer.prototype.cancel = function() {
CountDownTimer.prototype.cancel = function () {
this.running = false;
clearTimeout(this.timeoutId);
return this;
};
CountDownTimer.parse = function(seconds) {
CountDownTimer.parse = function (seconds) {
return {
'minutes': (seconds / 60) | 0,
'seconds': (seconds % 60) | 0
minutes: (seconds / 60) | 0,
seconds: (seconds % 60) | 0,
};
};

View file

@ -20,42 +20,40 @@
* limitations under the License.
*/
var padmodals = require('./pad_modals').padmodals;
const padmodals = require('./pad_modals').padmodals;
var padconnectionstatus = (function() {
var status = {
what: 'connecting'
const padconnectionstatus = (function () {
let status = {
what: 'connecting',
};
var self = {
init: function() {
$('button#forcereconnect').click(function() {
const self = {
init() {
$('button#forcereconnect').click(() => {
window.location.reload();
});
},
connected: function() {
connected() {
status = {
what: 'connected'
what: 'connected',
};
padmodals.showModal('connected');
padmodals.hideOverlay();
},
reconnecting: function() {
reconnecting() {
status = {
what: 'reconnecting'
what: 'reconnecting',
};
padmodals.showModal('reconnecting');
padmodals.showOverlay();
},
disconnected: function(msg) {
if(status.what == "disconnected")
return;
disconnected(msg) {
if (status.what == 'disconnected') return;
status = {
what: 'disconnected',
why: msg
why: msg,
};
// These message IDs correspond to localized strings that are presented to the user. If a new
@ -83,12 +81,12 @@ var padconnectionstatus = (function() {
padmodals.showModal(k);
padmodals.showOverlay();
},
isFullyConnected: function() {
isFullyConnected() {
return status.what == 'connected';
},
getStatus: function() {
getStatus() {
return status;
}
},
};
return self;
}());

View file

@ -20,150 +20,139 @@
* limitations under the License.
*/
var browser = require('./browser');
var hooks = require('./pluginfw/hooks');
var padutils = require('./pad_utils').padutils;
var padeditor = require('./pad_editor').padeditor;
var padsavedrevs = require('./pad_savedrevs');
var _ = require('ep_etherpad-lite/static/js/underscore');
const browser = require('./browser');
const hooks = require('./pluginfw/hooks');
const padutils = require('./pad_utils').padutils;
const padeditor = require('./pad_editor').padeditor;
const padsavedrevs = require('./pad_savedrevs');
const _ = require('ep_etherpad-lite/static/js/underscore');
require('ep_etherpad-lite/static/js/vendors/nice-select');
var ToolbarItem = function (element) {
const ToolbarItem = function (element) {
this.$el = element;
};
ToolbarItem.prototype.getCommand = function () {
return this.$el.attr("data-key");
return this.$el.attr('data-key');
};
ToolbarItem.prototype.getValue = function () {
if (this.isSelect()) {
return this.$el.find("select").val();
return this.$el.find('select').val();
}
};
ToolbarItem.prototype.setValue = function (val) {
if (this.isSelect()) {
return this.$el.find("select").val(val);
return this.$el.find('select').val(val);
}
};
ToolbarItem.prototype.getType = function () {
return this.$el.attr("data-type");
return this.$el.attr('data-type');
};
ToolbarItem.prototype.isSelect = function () {
return this.getType() == "select";
return this.getType() == 'select';
};
ToolbarItem.prototype.isButton = function () {
return this.getType() == "button";
return this.getType() == 'button';
};
ToolbarItem.prototype.bind = function (callback) {
var self = this;
const self = this;
if (self.isButton()) {
self.$el.click(function (event) {
self.$el.click((event) => {
$(':focus').blur();
callback(self.getCommand(), self);
event.preventDefault();
});
}
else if (self.isSelect()) {
self.$el.find("select").change(function () {
} else if (self.isSelect()) {
self.$el.find('select').change(() => {
callback(self.getCommand(), self);
});
}
};
var padeditbar = (function() {
var syncAnimation = (function() {
var SYNCING = -100;
var DONE = 100;
var state = DONE;
var fps = 25;
var step = 1 / fps;
var T_START = -0.5;
var T_FADE = 1.0;
var T_GONE = 1.5;
var animator = padutils.makeAnimationScheduler(function() {
if (state == SYNCING || state == DONE)
{
var padeditbar = (function () {
const syncAnimation = (function () {
const SYNCING = -100;
const DONE = 100;
let state = DONE;
const fps = 25;
const step = 1 / fps;
const T_START = -0.5;
const T_FADE = 1.0;
const T_GONE = 1.5;
const animator = padutils.makeAnimationScheduler(() => {
if (state == SYNCING || state == DONE) {
return false;
}
else if (state >= T_GONE)
{
} else if (state >= T_GONE) {
state = DONE;
$("#syncstatussyncing").css('display', 'none');
$("#syncstatusdone").css('display', 'none');
$('#syncstatussyncing').css('display', 'none');
$('#syncstatusdone').css('display', 'none');
return false;
}
else if (state < 0)
{
} else if (state < 0) {
state += step;
if (state >= 0)
{
$("#syncstatussyncing").css('display', 'none');
$("#syncstatusdone").css('display', 'block').css('opacity', 1);
if (state >= 0) {
$('#syncstatussyncing').css('display', 'none');
$('#syncstatusdone').css('display', 'block').css('opacity', 1);
}
return true;
}
else
{
} else {
state += step;
if (state >= T_FADE)
{
$("#syncstatusdone").css('opacity', (T_GONE - state) / (T_GONE - T_FADE));
if (state >= T_FADE) {
$('#syncstatusdone').css('opacity', (T_GONE - state) / (T_GONE - T_FADE));
}
return true;
}
}, step * 1000);
return {
syncing: function() {
syncing() {
state = SYNCING;
$("#syncstatussyncing").css('display', 'block');
$("#syncstatusdone").css('display', 'none');
$('#syncstatussyncing').css('display', 'block');
$('#syncstatusdone').css('display', 'none');
},
done: function() {
done() {
state = T_START;
animator.scheduleAnimation();
}
},
};
}());
var self = {
init: function() {
var self = this;
init() {
const self = this;
self.dropdowns = [];
$("#editbar .editbarbutton").attr("unselectable", "on"); // for IE
$('#editbar .editbarbutton').attr('unselectable', 'on'); // for IE
this.enable();
$("#editbar [data-key]").each(function () {
$(this).unbind("click");
(new ToolbarItem($(this))).bind(function (command, item) {
$('#editbar [data-key]').each(function () {
$(this).unbind('click');
(new ToolbarItem($(this))).bind((command, item) => {
self.triggerCommand(command, item);
});
});
$('body:not(#editorcontainerbox)').on("keydown", function(evt){
$('body:not(#editorcontainerbox)').on('keydown', (evt) => {
bodyKeyEvent(evt);
});
$('.show-more-icon-btn').click(function() {
$('.show-more-icon-btn').click(() => {
$('.toolbar').toggleClass('full-icons');
});
self.checkAllIconsAreDisplayedInToolbar();
$(window).resize(_.debounce( self.checkAllIconsAreDisplayedInToolbar, 100 ) );
$(window).resize(_.debounce(self.checkAllIconsAreDisplayedInToolbar, 100));
registerDefaultCommands(self);
hooks.callAll("postToolbarInit", {
hooks.callAll('postToolbarInit', {
toolbar: self,
ace: padeditor.ace
ace: padeditor.ace,
});
/*
@ -173,101 +162,91 @@ var padeditbar = (function() {
* overflow:hidden on parent
*/
if (!browser.safari) {
$('select').niceSelect();
$('select').niceSelect();
}
// When editor is scrolled, we add a class to style the editbar differently
$('iframe[name="ace_outer"]').contents().scroll(function() {
$('iframe[name="ace_outer"]').contents().scroll(function () {
$('#editbar').toggleClass('editor-scrolled', $(this).scrollTop() > 2);
})
});
},
isEnabled: function() {
isEnabled() {
return true;
},
disable: function() {
$("#editbar").addClass('disabledtoolbar').removeClass("enabledtoolbar");
disable() {
$('#editbar').addClass('disabledtoolbar').removeClass('enabledtoolbar');
},
enable: function() {
enable() {
$('#editbar').addClass('enabledtoolbar').removeClass('disabledtoolbar');
},
commands: {},
registerCommand: function (cmd, callback) {
registerCommand(cmd, callback) {
this.commands[cmd] = callback;
return this;
},
registerDropdownCommand: function (cmd, dropdown) {
registerDropdownCommand(cmd, dropdown) {
dropdown = dropdown || cmd;
self.dropdowns.push(dropdown)
this.registerCommand(cmd, function () {
self.dropdowns.push(dropdown);
this.registerCommand(cmd, () => {
self.toggleDropDown(dropdown);
});
},
registerAceCommand: function (cmd, callback) {
this.registerCommand(cmd, function (cmd, ace, item) {
ace.callWithAce(function (ace) {
registerAceCommand(cmd, callback) {
this.registerCommand(cmd, (cmd, ace, item) => {
ace.callWithAce((ace) => {
callback(cmd, ace, item);
}, cmd, true);
});
},
triggerCommand: function (cmd, item) {
triggerCommand(cmd, item) {
if (self.isEnabled() && this.commands[cmd]) {
this.commands[cmd](cmd, padeditor.ace, item);
}
if(padeditor.ace) padeditor.ace.focus();
if (padeditor.ace) padeditor.ace.focus();
},
toggleDropDown: function(moduleName, cb) {
toggleDropDown(moduleName, cb) {
// do nothing if users are sticked
if (moduleName === "users" && $('#users').hasClass('stickyUsers')) {
if (moduleName === 'users' && $('#users').hasClass('stickyUsers')) {
return;
}
$('.nice-select').removeClass('open');
$('.toolbar-popup').removeClass("popup-show");
$('.toolbar-popup').removeClass('popup-show');
// hide all modules and remove highlighting of all buttons
if(moduleName == "none")
{
var returned = false;
for(var i=0;i<self.dropdowns.length;i++)
{
if (moduleName == 'none') {
const returned = false;
for (var i = 0; i < self.dropdowns.length; i++) {
var thisModuleName = self.dropdowns[i];
//skip the userlist
if(thisModuleName == "users")
continue;
// skip the userlist
if (thisModuleName == 'users') continue;
var module = $("#" + thisModuleName);
var module = $(`#${thisModuleName}`);
//skip any "force reconnect" message
var isAForceReconnectMessage = module.find('button#forcereconnect:visible').length > 0;
if(isAForceReconnectMessage)
continue;
// skip any "force reconnect" message
const isAForceReconnectMessage = module.find('button#forcereconnect:visible').length > 0;
if (isAForceReconnectMessage) continue;
if (module.hasClass('popup-show')) {
$("li[data-key=" + thisModuleName + "] > a").removeClass("selected");
module.removeClass("popup-show");
$(`li[data-key=${thisModuleName}] > a`).removeClass('selected');
module.removeClass('popup-show');
}
}
if(!returned && cb) return cb();
}
else
{
if (!returned && cb) return cb();
} else {
// hide all modules that are not selected and remove highlighting
// respectively add highlighting to the corresponding button
for(var i=0;i<self.dropdowns.length;i++)
{
for (var i = 0; i < self.dropdowns.length; i++) {
var thisModuleName = self.dropdowns[i];
var module = $("#" + thisModuleName);
var module = $(`#${thisModuleName}`);
if(module.hasClass('popup-show'))
{
$("li[data-key=" + thisModuleName + "] > a").removeClass("selected");
module.removeClass("popup-show");
}
else if(thisModuleName==moduleName)
{
$("li[data-key=" + thisModuleName + "] > a").addClass("selected");
module.addClass("popup-show");
if (module.hasClass('popup-show')) {
$(`li[data-key=${thisModuleName}] > a`).removeClass('selected');
module.removeClass('popup-show');
} else if (thisModuleName == moduleName) {
$(`li[data-key=${thisModuleName}] > a`).addClass('selected');
module.addClass('popup-show');
if (cb) {
cb();
}
@ -275,113 +254,105 @@ var padeditbar = (function() {
}
}
},
setSyncStatus: function(status) {
if (status == "syncing")
{
setSyncStatus(status) {
if (status == 'syncing') {
syncAnimation.syncing();
}
else if (status == "done")
{
} else if (status == 'done') {
syncAnimation.done();
}
},
setEmbedLinks: function() {
var padUrl = window.location.href.split("?")[0];
setEmbedLinks() {
const padUrl = window.location.href.split('?')[0];
if ($('#readonlyinput').is(':checked'))
{
var urlParts = padUrl.split("/");
if ($('#readonlyinput').is(':checked')) {
const urlParts = padUrl.split('/');
urlParts.pop();
var readonlyLink = urlParts.join("/") + "/" + clientVars.readOnlyId;
$('#embedinput').val('<iframe name="embed_readonly" src="' + readonlyLink + '?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false" width="100%" height="600" frameborder="0"></iframe>');
const readonlyLink = `${urlParts.join('/')}/${clientVars.readOnlyId}`;
$('#embedinput').val(`<iframe name="embed_readonly" src="${readonlyLink}?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false" width="100%" height="600" frameborder="0"></iframe>`);
$('#linkinput').val(readonlyLink);
}
else
{
$('#embedinput').val('<iframe name="embed_readwrite" src="' + padUrl + '?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false" width="100%" height="600" frameborder="0"></iframe>');
} else {
$('#embedinput').val(`<iframe name="embed_readwrite" src="${padUrl}?showControls=true&showChat=true&showLineNumbers=true&useMonospaceFont=false" width="100%" height="600" frameborder="0"></iframe>`);
$('#linkinput').val(padUrl);
}
},
checkAllIconsAreDisplayedInToolbar: function() {
checkAllIconsAreDisplayedInToolbar() {
// reset style
$('.toolbar').removeClass('cropped')
$('.toolbar').removeClass('cropped');
$('body').removeClass('mobile-layout');
var menu_left = $('.toolbar .menu_left')[0];
const menu_left = $('.toolbar .menu_left')[0];
var menuRightWidth = 280; // this is approximate, we cannot measure it because on mobileLayour it takes the full width on the bottom of the page
const menuRightWidth = 280; // this is approximate, we cannot measure it because on mobileLayour it takes the full width on the bottom of the page
if (menu_left && menu_left.scrollWidth > $('.toolbar').width() - menuRightWidth || $('.toolbar').width() < 1000) {
$('body').addClass('mobile-layout');
}
if (menu_left && menu_left.scrollWidth > $('.toolbar').width()) {
$('.toolbar').addClass('cropped');
}
}
},
};
var editbarPosition = 0;
function bodyKeyEvent(evt){
let editbarPosition = 0;
function bodyKeyEvent(evt) {
// If the event is Alt F9 or Escape & we're already in the editbar menu
// Send the users focus back to the pad
if((evt.keyCode === 120 && evt.altKey) || evt.keyCode === 27){
if($(':focus').parents(".toolbar").length === 1){
if ((evt.keyCode === 120 && evt.altKey) || evt.keyCode === 27) {
if ($(':focus').parents('.toolbar').length === 1) {
// If we're in the editbar already..
// Close any dropdowns we have open..
padeditbar.toggleDropDown("none");
padeditbar.toggleDropDown('none');
// Check we're on a pad and not on the timeslider
// Or some other window I haven't thought about!
if(typeof pad === 'undefined'){
if (typeof pad === 'undefined') {
// Timeslider probably..
// Shift focus away from any drop downs
$(':focus').blur(); // required to do not try to remove!
$('#editorcontainerbox').focus(); // Focus back onto the pad
}else{
} else {
// Shift focus away from any drop downs
$(':focus').blur(); // required to do not try to remove!
padeditor.ace.focus(); // Sends focus back to pad
// The above focus doesn't always work in FF, you have to hit enter afterwards
evt.preventDefault();
}
}else{
} else {
// Focus on the editbar :)
var firstEditbarElement = parent.parent.$('#editbar').children("ul").first().children().first().children().first().children().first();
const firstEditbarElement = parent.parent.$('#editbar').children('ul').first().children().first().children().first().children().first();
$(this).blur();
firstEditbarElement.focus();
evt.preventDefault();
}
}
// Are we in the toolbar??
if($(':focus').parents(".toolbar").length === 1){
if ($(':focus').parents('.toolbar').length === 1) {
// On arrow keys go to next/previous button item in editbar
if(evt.keyCode !== 39 && evt.keyCode !== 37) return;
if (evt.keyCode !== 39 && evt.keyCode !== 37) return;
// Get all the focusable items in the editbar
var focusItems = $('#editbar').find('button, select');
const focusItems = $('#editbar').find('button, select');
// On left arrow move to next button in editbar
if(evt.keyCode === 37){
if (evt.keyCode === 37) {
// If a dropdown is visible or we're in an input don't move to the next button
if($('.popup').is(":visible") || evt.target.localName === "input") return;
if ($('.popup').is(':visible') || evt.target.localName === 'input') return;
editbarPosition--;
// Allow focus to shift back to end of row and start of row
if(editbarPosition === -1) editbarPosition = focusItems.length -1;
$(focusItems[editbarPosition]).focus()
if (editbarPosition === -1) editbarPosition = focusItems.length - 1;
$(focusItems[editbarPosition]).focus();
}
// On right arrow move to next button in editbar
if(evt.keyCode === 39){
if (evt.keyCode === 39) {
// If a dropdown is visible or we're in an input don't move to the next button
if($('.popup').is(":visible") || evt.target.localName === "input") return;
if ($('.popup').is(':visible') || evt.target.localName === 'input') return;
editbarPosition++;
// Allow focus to shift back to end of row and start of row
if(editbarPosition >= focusItems.length) editbarPosition = 0;
if (editbarPosition >= focusItems.length) editbarPosition = 0;
$(focusItems[editbarPosition]).focus();
}
}
}
function aceAttributeCommand(cmd, ace) {
@ -389,91 +360,91 @@ var padeditbar = (function() {
}
function registerDefaultCommands(toolbar) {
toolbar.registerDropdownCommand("showusers", "users");
toolbar.registerDropdownCommand("settings");
toolbar.registerDropdownCommand("connectivity");
toolbar.registerDropdownCommand("import_export");
toolbar.registerDropdownCommand("embed");
toolbar.registerDropdownCommand('showusers', 'users');
toolbar.registerDropdownCommand('settings');
toolbar.registerDropdownCommand('connectivity');
toolbar.registerDropdownCommand('import_export');
toolbar.registerDropdownCommand('embed');
toolbar.registerCommand("settings", function () {
toolbar.toggleDropDown("settings", function(){
toolbar.registerCommand('settings', () => {
toolbar.toggleDropDown('settings', () => {
$('#options-stickychat').focus();
});
});
toolbar.registerCommand("import_export", function () {
toolbar.toggleDropDown("import_export", function(){
toolbar.registerCommand('import_export', () => {
toolbar.toggleDropDown('import_export', () => {
// If Import file input exists then focus on it..
if($('#importfileinput').length !== 0){
setTimeout(function(){
if ($('#importfileinput').length !== 0) {
setTimeout(() => {
$('#importfileinput').focus();
}, 100);
}else{
} else {
$('.exportlink').first().focus();
}
});
});
toolbar.registerCommand("showusers", function () {
toolbar.toggleDropDown("users", function(){
toolbar.registerCommand('showusers', () => {
toolbar.toggleDropDown('users', () => {
$('#myusernameedit').focus();
});
});
toolbar.registerCommand("embed", function () {
toolbar.registerCommand('embed', () => {
toolbar.setEmbedLinks();
toolbar.toggleDropDown("embed", function(){
toolbar.toggleDropDown('embed', () => {
$('#linkinput').focus().select();
});
});
toolbar.registerCommand("savedRevision", function () {
toolbar.registerCommand('savedRevision', () => {
padsavedrevs.saveNow();
});
toolbar.registerCommand("showTimeSlider", function () {
document.location = document.location.pathname+ '/timeslider';
toolbar.registerCommand('showTimeSlider', () => {
document.location = `${document.location.pathname}/timeslider`;
});
toolbar.registerAceCommand("bold", aceAttributeCommand);
toolbar.registerAceCommand("italic", aceAttributeCommand);
toolbar.registerAceCommand("underline", aceAttributeCommand);
toolbar.registerAceCommand("strikethrough", aceAttributeCommand);
toolbar.registerAceCommand('bold', aceAttributeCommand);
toolbar.registerAceCommand('italic', aceAttributeCommand);
toolbar.registerAceCommand('underline', aceAttributeCommand);
toolbar.registerAceCommand('strikethrough', aceAttributeCommand);
toolbar.registerAceCommand("undo", function (cmd, ace) {
toolbar.registerAceCommand('undo', (cmd, ace) => {
ace.ace_doUndoRedo(cmd);
});
toolbar.registerAceCommand("redo", function (cmd, ace) {
toolbar.registerAceCommand('redo', (cmd, ace) => {
ace.ace_doUndoRedo(cmd);
});
toolbar.registerAceCommand("insertunorderedlist", function (cmd, ace) {
toolbar.registerAceCommand('insertunorderedlist', (cmd, ace) => {
ace.ace_doInsertUnorderedList();
});
toolbar.registerAceCommand("insertorderedlist", function (cmd, ace) {
toolbar.registerAceCommand('insertorderedlist', (cmd, ace) => {
ace.ace_doInsertOrderedList();
});
toolbar.registerAceCommand("indent", function (cmd, ace) {
toolbar.registerAceCommand('indent', (cmd, ace) => {
if (!ace.ace_doIndentOutdent(false)) {
ace.ace_doInsertUnorderedList();
}
});
toolbar.registerAceCommand("outdent", function (cmd, ace) {
toolbar.registerAceCommand('outdent', (cmd, ace) => {
ace.ace_doIndentOutdent(true);
});
toolbar.registerAceCommand("clearauthorship", function (cmd, ace) {
toolbar.registerAceCommand('clearauthorship', (cmd, ace) => {
// If we have the whole document selected IE control A has been hit
var rep = ace.ace_getRep();
var lastChar = rep.lines.atIndex(rep.lines.length()-1).width-1;
var lastLineIndex = rep.lines.length()-1;
if(rep.selStart[0] === 0 && rep.selStart[1] === 0){
const rep = ace.ace_getRep();
const lastChar = rep.lines.atIndex(rep.lines.length() - 1).width - 1;
const lastLineIndex = rep.lines.length() - 1;
if (rep.selStart[0] === 0 && rep.selStart[1] === 0) {
// nesting intentionally here to make things readable
if(rep.selEnd[0] === lastLineIndex && rep.selEnd[1] === lastChar){
if (rep.selEnd[0] === lastLineIndex && rep.selEnd[1] === lastChar) {
var doPrompt = true;
}
}
@ -488,22 +459,21 @@ var padeditbar = (function() {
// if we don't have any text selected, we have a caret or we have already said to prompt
if ((!(rep.selStart && rep.selEnd)) || ace.ace_isCaret() || doPrompt) {
if (window.confirm(html10n.get("pad.editbar.clearcolors"))) {
if (window.confirm(html10n.get('pad.editbar.clearcolors'))) {
ace.ace_performDocumentApplyAttributesToCharRange(0, ace.ace_getRep().alltext.length, [
['author', '']
['author', ''],
]);
}
}
else {
} else {
ace.ace_setAttributeOnSelection('author', '');
}
});
toolbar.registerCommand('timeslider_returnToPad', function(cmd) {
if( document.referrer.length > 0 && document.referrer.substring(document.referrer.lastIndexOf("/")-1, document.referrer.lastIndexOf("/")) === "p") {
toolbar.registerCommand('timeslider_returnToPad', (cmd) => {
if (document.referrer.length > 0 && document.referrer.substring(document.referrer.lastIndexOf('/') - 1, document.referrer.lastIndexOf('/')) === 'p') {
document.location = document.referrer;
} else {
document.location = document.location.href.substring(0,document.location.href.lastIndexOf("/"));
document.location = document.location.href.substring(0, document.location.href.lastIndexOf('/'));
}
});
}

View file

@ -21,145 +21,140 @@
*/
const Cookies = require('./pad_utils').Cookies;
var padcookie = require('./pad_cookie').padcookie;
var padutils = require('./pad_utils').padutils;
const padcookie = require('./pad_cookie').padcookie;
const padutils = require('./pad_utils').padutils;
var padeditor = (function() {
var Ace2Editor = undefined;
var pad = undefined;
var settings = undefined;
const padeditor = (function () {
let Ace2Editor = undefined;
let pad = undefined;
let settings = undefined;
var self = {
ace: null,
// this is accessed directly from other files
viewZoom: 100,
init: function(readyFunc, initialViewOptions, _pad) {
init(readyFunc, initialViewOptions, _pad) {
Ace2Editor = require('./ace').Ace2Editor;
pad = _pad;
settings = pad.settings;
function aceReady() {
$("#editorloadingbox").hide();
if (readyFunc)
{
$('#editorloadingbox').hide();
if (readyFunc) {
readyFunc();
}
}
self.ace = new Ace2Editor();
self.ace.init("editorcontainer", "", aceReady);
self.ace.setProperty("wraps", true);
if (pad.getIsDebugEnabled())
{
self.ace.setProperty("dmesg", pad.dmesg);
self.ace.init('editorcontainer', '', aceReady);
self.ace.setProperty('wraps', true);
if (pad.getIsDebugEnabled()) {
self.ace.setProperty('dmesg', pad.dmesg);
}
self.initViewOptions();
self.setViewOptions(initialViewOptions);
// view bar
$("#viewbarcontents").show();
$('#viewbarcontents').show();
},
initViewOptions: function() {
initViewOptions() {
// Line numbers
padutils.bindCheckboxChange($("#options-linenoscheck"), function() {
pad.changeViewOption('showLineNumbers', padutils.getCheckbox($("#options-linenoscheck")));
padutils.bindCheckboxChange($('#options-linenoscheck'), () => {
pad.changeViewOption('showLineNumbers', padutils.getCheckbox($('#options-linenoscheck')));
});
// Author colors
padutils.bindCheckboxChange($("#options-colorscheck"), function() {
padcookie.setPref('showAuthorshipColors', padutils.getCheckbox("#options-colorscheck"));
pad.changeViewOption('showAuthorColors', padutils.getCheckbox("#options-colorscheck"));
padutils.bindCheckboxChange($('#options-colorscheck'), () => {
padcookie.setPref('showAuthorshipColors', padutils.getCheckbox('#options-colorscheck'));
pad.changeViewOption('showAuthorColors', padutils.getCheckbox('#options-colorscheck'));
});
// Right to left
padutils.bindCheckboxChange($("#options-rtlcheck"), function() {
pad.changeViewOption('rtlIsTrue', padutils.getCheckbox($("#options-rtlcheck")))
padutils.bindCheckboxChange($('#options-rtlcheck'), () => {
pad.changeViewOption('rtlIsTrue', padutils.getCheckbox($('#options-rtlcheck')));
});
html10n.bind('localized', function() {
html10n.bind('localized', () => {
pad.changeViewOption('rtlIsTrue', ('rtl' == html10n.getDirection()));
padutils.setCheckbox($("#options-rtlcheck"), ('rtl' == html10n.getDirection()));
})
padutils.setCheckbox($('#options-rtlcheck'), ('rtl' == html10n.getDirection()));
});
// font family change
$("#viewfontmenu").change(function() {
pad.changeViewOption('padFontFamily', $("#viewfontmenu").val());
$('#viewfontmenu').change(() => {
pad.changeViewOption('padFontFamily', $('#viewfontmenu').val());
});
// Language
html10n.bind('localized', function() {
$("#languagemenu").val(html10n.getLanguage());
html10n.bind('localized', () => {
$('#languagemenu').val(html10n.getLanguage());
// translate the value of 'unnamed' and 'Enter your name' textboxes in the userlist
// this does not interfere with html10n's normal value-setting because html10n just ingores <input>s
// also, a value which has been set by the user will be not overwritten since a user-edited <input>
// does *not* have the editempty-class
$('input[data-l10n-id]').each(function(key, input){
$('input[data-l10n-id]').each((key, input) => {
input = $(input);
if(input.hasClass("editempty")){
input.val(html10n.get(input.attr("data-l10n-id")));
if (input.hasClass('editempty')) {
input.val(html10n.get(input.attr('data-l10n-id')));
}
});
})
$("#languagemenu").val(html10n.getLanguage());
$("#languagemenu").change(function() {
});
$('#languagemenu').val(html10n.getLanguage());
$('#languagemenu').change(() => {
Cookies.set('language', $('#languagemenu').val());
window.html10n.localize([$("#languagemenu").val(), 'en']);
window.html10n.localize([$('#languagemenu').val(), 'en']);
});
},
setViewOptions: function(newOptions) {
setViewOptions(newOptions) {
function getOption(key, defaultValue) {
var value = String(newOptions[key]);
if (value == "true") return true;
if (value == "false") return false;
const value = String(newOptions[key]);
if (value == 'true') return true;
if (value == 'false') return false;
return defaultValue;
}
var v;
let v;
v = getOption('rtlIsTrue', ('rtl' == html10n.getDirection()));
self.ace.setProperty("rtlIsTrue", v);
padutils.setCheckbox($("#options-rtlcheck"), v);
self.ace.setProperty('rtlIsTrue', v);
padutils.setCheckbox($('#options-rtlcheck'), v);
v = getOption('showLineNumbers', true);
self.ace.setProperty("showslinenumbers", v);
padutils.setCheckbox($("#options-linenoscheck"), v);
self.ace.setProperty('showslinenumbers', v);
padutils.setCheckbox($('#options-linenoscheck'), v);
v = getOption('showAuthorColors', true);
self.ace.setProperty("showsauthorcolors", v);
self.ace.setProperty('showsauthorcolors', v);
$('#chattext').toggleClass('authorColors', v);
$('iframe[name="ace_outer"]').contents().find('#sidedivinner').toggleClass('authorColors', v);
padutils.setCheckbox($("#options-colorscheck"), v);
padutils.setCheckbox($('#options-colorscheck'), v);
// Override from parameters if true
if (settings.noColors !== false){
self.ace.setProperty("showsauthorcolors", !settings.noColors);
if (settings.noColors !== false) {
self.ace.setProperty('showsauthorcolors', !settings.noColors);
}
self.ace.setProperty("textface", newOptions['padFontFamily'] || "");
self.ace.setProperty('textface', newOptions.padFontFamily || '');
},
dispose: function() {
if (self.ace)
{
dispose() {
if (self.ace) {
self.ace.destroy();
self.ace = null;
}
},
enable: function() {
if (self.ace)
{
enable() {
if (self.ace) {
self.ace.setEditable(true);
}
},
disable: function() {
if (self.ace)
{
self.ace.setProperty("grayedOut", true);
disable() {
if (self.ace) {
self.ace.setProperty('grayedOut', true);
self.ace.setEditable(false);
}
},
restoreRevisionText: function(dataFromServer) {
restoreRevisionText(dataFromServer) {
pad.addHistoricalAuthors(dataFromServer.historicalAuthorData);
self.ace.importAText(dataFromServer.atext, dataFromServer.apool, true);
}
},
};
return self;
}());

View file

@ -20,14 +20,13 @@
* limitations under the License.
*/
var padimpexp = (function() {
///// import
var currentImportTimer = null;
const padimpexp = (function () {
// /// import
let currentImportTimer = null;
function addImportFrames() {
$("#import .importframe").remove();
var iframe = $('<iframe style="display: none;" name="importiframe" class="importframe"></iframe>');
$('#import .importframe').remove();
const iframe = $('<iframe style="display: none;" name="importiframe" class="importframe"></iframe>');
$('#import').append(iframe);
}
@ -39,29 +38,27 @@ var padimpexp = (function() {
}
function fileInputSubmit() {
$('#importmessagefail').fadeOut("fast");
var ret = window.confirm(html10n.get("pad.impexp.confirmimport"));
if (ret)
{
currentImportTimer = window.setTimeout(function() {
if (!currentImportTimer)
{
$('#importmessagefail').fadeOut('fast');
const ret = window.confirm(html10n.get('pad.impexp.confirmimport'));
if (ret) {
currentImportTimer = window.setTimeout(() => {
if (!currentImportTimer) {
return;
}
currentImportTimer = null;
importFailed("Request timed out.");
importFailed('Request timed out.');
importDone();
}, 25000); // time out after some number of seconds
$('#importsubmitinput').attr(
{
disabled: true
}).val(html10n.get("pad.impexp.importing"));
{
disabled: true,
}).val(html10n.get('pad.impexp.importing'));
window.setTimeout(function() {
window.setTimeout(() => {
$('#importfileinput').attr(
{
disabled: true
});
{
disabled: true,
});
}, 0);
$('#importarrow').stop(true, true).hide();
$('#importstatusball').show();
@ -74,8 +71,8 @@ var padimpexp = (function() {
}
function importDone() {
$('#importsubmitinput').removeAttr('disabled').val(html10n.get("pad.impexp.importbutton"));
window.setTimeout(function() {
$('#importsubmitinput').removeAttr('disabled').val(html10n.get('pad.impexp.importbutton'));
window.setTimeout(() => {
$('#importfileinput').removeAttr('disabled');
}, 0);
$('#importstatusball').hide();
@ -84,155 +81,136 @@ var padimpexp = (function() {
}
function importClearTimeout() {
if (currentImportTimer)
{
if (currentImportTimer) {
window.clearTimeout(currentImportTimer);
currentImportTimer = null;
}
}
function importErrorMessage(status) {
var msg="";
let msg = '';
if(status === "convertFailed"){
msg = html10n.get("pad.impexp.convertFailed");
} else if(status === "uploadFailed"){
msg = html10n.get("pad.impexp.uploadFailed");
} else if(status === "padHasData"){
msg = html10n.get("pad.impexp.padHasData");
} else if(status === "maxFileSize"){
msg = html10n.get("pad.impexp.maxFileSize");
} else if(status === "permission"){
msg = html10n.get("pad.impexp.permission");
if (status === 'convertFailed') {
msg = html10n.get('pad.impexp.convertFailed');
} else if (status === 'uploadFailed') {
msg = html10n.get('pad.impexp.uploadFailed');
} else if (status === 'padHasData') {
msg = html10n.get('pad.impexp.padHasData');
} else if (status === 'maxFileSize') {
msg = html10n.get('pad.impexp.maxFileSize');
} else if (status === 'permission') {
msg = html10n.get('pad.impexp.permission');
}
function showError(fade) {
$('#importmessagefail').html('<strong style="color: red">'+html10n.get('pad.impexp.importfailed')+':</strong> ' + (msg || html10n.get('pad.impexp.copypaste','')))[(fade ? "fadeIn" : "show")]();
$('#importmessagefail').html(`<strong style="color: red">${html10n.get('pad.impexp.importfailed')}:</strong> ${msg || html10n.get('pad.impexp.copypaste', '')}`)[(fade ? 'fadeIn' : 'show')]();
}
if ($('#importexport .importmessage').is(':visible'))
{
$('#importmessagesuccess').fadeOut("fast");
$('#importmessagefail').fadeOut("fast", function() {
if ($('#importexport .importmessage').is(':visible')) {
$('#importmessagesuccess').fadeOut('fast');
$('#importmessagefail').fadeOut('fast', () => {
showError(true);
});
}
else
{
} else {
showError();
}
}
function importSuccessful(token) {
$.ajax(
{
type: 'post',
url: '/ep/pad/impexp/import2',
data: {
token: token,
padId: pad.getPadId()
},
success: importApplicationSuccessful,
error: importApplicationFailed,
timeout: 25000
});
{
type: 'post',
url: '/ep/pad/impexp/import2',
data: {
token,
padId: pad.getPadId(),
},
success: importApplicationSuccessful,
error: importApplicationFailed,
timeout: 25000,
});
addImportFrames();
}
function importApplicationFailed(xhr, textStatus, errorThrown) {
importErrorMessage("Error during conversion.");
importErrorMessage('Error during conversion.');
importDone();
}
///// export
// /// export
function cantExport() {
var type = $(this);
if (type.hasClass("exporthrefpdf"))
{
type = "PDF";
let type = $(this);
if (type.hasClass('exporthrefpdf')) {
type = 'PDF';
} else if (type.hasClass('exporthrefdoc')) {
type = 'Microsoft Word';
} else if (type.hasClass('exporthrefodt')) {
type = 'OpenDocument';
} else {
type = 'this file';
}
else if (type.hasClass("exporthrefdoc"))
{
type = "Microsoft Word";
}
else if (type.hasClass("exporthrefodt"))
{
type = "OpenDocument";
}
else
{
type = "this file";
}
alert(html10n.get("pad.impexp.exportdisabled", {type:type}));
alert(html10n.get('pad.impexp.exportdisabled', {type}));
return false;
}
/////
// ///
var pad = undefined;
var self = {
init: function(_pad) {
const self = {
init(_pad) {
pad = _pad;
//get /p/padname
// get /p/padname
// if /p/ isn't available due to a rewrite we use the clientVars padId
var pad_root_path = new RegExp(/.*\/p\/[^\/]+/).exec(document.location.pathname) || clientVars.padId;
//get http://example.com/p/padname without Params
var pad_root_url = document.location.protocol + '//' + document.location.host + document.location.pathname;
const pad_root_path = new RegExp(/.*\/p\/[^\/]+/).exec(document.location.pathname) || clientVars.padId;
// get http://example.com/p/padname without Params
const pad_root_url = `${document.location.protocol}//${document.location.host}${document.location.pathname}`;
//i10l buttom import
$('#importsubmitinput').val(html10n.get("pad.impexp.importbutton"));
html10n.bind('localized', function() {
$('#importsubmitinput').val(html10n.get("pad.impexp.importbutton"));
})
// i10l buttom import
$('#importsubmitinput').val(html10n.get('pad.impexp.importbutton'));
html10n.bind('localized', () => {
$('#importsubmitinput').val(html10n.get('pad.impexp.importbutton'));
});
// build the export links
$("#exporthtmla").attr("href", pad_root_path + "/export/html");
$("#exportetherpada").attr("href", pad_root_path + "/export/etherpad");
$("#exportplaina").attr("href", pad_root_path + "/export/txt");
$('#exporthtmla').attr('href', `${pad_root_path}/export/html`);
$('#exportetherpada').attr('href', `${pad_root_path}/export/etherpad`);
$('#exportplaina').attr('href', `${pad_root_path}/export/txt`);
// activate action to import in the form
$("#importform").attr('action', pad_root_url + "/import");
$('#importform').attr('action', `${pad_root_url}/import`);
//hide stuff thats not avaible if abiword/soffice is disabled
if(clientVars.exportAvailable == "no")
{
$("#exportworda").remove();
$("#exportpdfa").remove();
$("#exportopena").remove();
// hide stuff thats not avaible if abiword/soffice is disabled
if (clientVars.exportAvailable == 'no') {
$('#exportworda').remove();
$('#exportpdfa').remove();
$('#exportopena').remove();
$("#importmessageabiword").show();
}
else if(clientVars.exportAvailable == "withoutPDF")
{
$("#exportpdfa").remove();
$('#importmessageabiword').show();
} else if (clientVars.exportAvailable == 'withoutPDF') {
$('#exportpdfa').remove();
$("#exportworda").attr("href", pad_root_path + "/export/doc");
$("#exportopena").attr("href", pad_root_path + "/export/odt");
$('#exportworda').attr('href', `${pad_root_path}/export/doc`);
$('#exportopena').attr('href', `${pad_root_path}/export/odt`);
$("#importexport").css({"height":"142px"});
$("#importexportline").css({"height":"142px"});
}
else
{
$("#exportworda").attr("href", pad_root_path + "/export/doc");
$("#exportpdfa").attr("href", pad_root_path + "/export/pdf");
$("#exportopena").attr("href", pad_root_path + "/export/odt");
$('#importexport').css({height: '142px'});
$('#importexportline').css({height: '142px'});
} else {
$('#exportworda').attr('href', `${pad_root_path}/export/doc`);
$('#exportpdfa').attr('href', `${pad_root_path}/export/pdf`);
$('#exportopena').attr('href', `${pad_root_path}/export/odt`);
}
addImportFrames();
$("#importfileinput").change(fileInputUpdated);
$('#importform').unbind("submit").submit(fileInputSubmit);
$('#importfileinput').change(fileInputUpdated);
$('#importform').unbind('submit').submit(fileInputSubmit);
$('.disabledexport').click(cantExport);
},
handleFrameCall: function(directDatabaseAccess, status) {
if(directDatabaseAccess === "undefined") directDatabaseAccess = false;
if (status !== "ok")
{
handleFrameCall(directDatabaseAccess, status) {
if (directDatabaseAccess === 'undefined') directDatabaseAccess = false;
if (status !== 'ok') {
importFailed(status);
}
else
{
} else {
$('#import_export').removeClass('popup-show');
}
@ -244,16 +222,16 @@ var padimpexp = (function() {
importDone();
},
disable: function() {
$("#impexp-disabled-clickcatcher").show();
$("#import").css('opacity', 0.5);
$("#impexp-export").css('opacity', 0.5);
disable() {
$('#impexp-disabled-clickcatcher').show();
$('#import').css('opacity', 0.5);
$('#impexp-export').css('opacity', 0.5);
},
enable() {
$('#impexp-disabled-clickcatcher').hide();
$('#import').css('opacity', 1);
$('#impexp-export').css('opacity', 1);
},
enable: function() {
$("#impexp-disabled-clickcatcher").hide();
$("#import").css('opacity', 1);
$("#impexp-export").css('opacity', 1);
}
};
return self;
}());

View file

@ -20,33 +20,33 @@
* limitations under the License.
*/
var padeditbar = require('./pad_editbar').padeditbar;
var automaticReconnect = require('./pad_automatic_reconnect');
const padeditbar = require('./pad_editbar').padeditbar;
const automaticReconnect = require('./pad_automatic_reconnect');
var padmodals = (function() {
var pad = undefined;
var self = {
init: function(_pad) {
const padmodals = (function () {
let pad = undefined;
const self = {
init(_pad) {
pad = _pad;
},
showModal: function(messageId) {
padeditbar.toggleDropDown("none", function() {
$("#connectivity .visible").removeClass('visible');
$("#connectivity ."+messageId).addClass('visible');
showModal(messageId) {
padeditbar.toggleDropDown('none', () => {
$('#connectivity .visible').removeClass('visible');
$(`#connectivity .${messageId}`).addClass('visible');
var $modal = $('#connectivity .' + messageId);
const $modal = $(`#connectivity .${messageId}`);
automaticReconnect.showCountDownTimerToReconnectOnModal($modal, pad);
padeditbar.toggleDropDown("connectivity");
padeditbar.toggleDropDown('connectivity');
});
},
showOverlay: function() {
showOverlay() {
// Prevent the user to interact with the toolbar. Useful when user is disconnected for example
$("#toolbar-overlay").show();
$('#toolbar-overlay').show();
},
hideOverlay() {
$('#toolbar-overlay').hide();
},
hideOverlay: function() {
$("#toolbar-overlay").hide();
}
};
return self;
}());

View file

@ -14,22 +14,22 @@
* limitations under the License.
*/
var pad;
let pad;
exports.saveNow = function(){
pad.collabClient.sendMessage({"type": "SAVE_REVISION"});
exports.saveNow = function () {
pad.collabClient.sendMessage({type: 'SAVE_REVISION'});
$.gritter.add({
// (string | mandatory) the heading of the notification
title: _("pad.savedrevs.marked"),
title: _('pad.savedrevs.marked'),
// (string | mandatory) the text inside the notification
text: _("pad.savedrevs.timeslider") || "You can view saved revisions in the timeslider",
text: _('pad.savedrevs.timeslider') || 'You can view saved revisions in the timeslider',
// (bool | optional) if you want it to fade out on its own or just sit there
sticky: false,
time: 3000,
class_name: "saved-revision",
class_name: 'saved-revision',
});
}
};
exports.init = function(_pad){
exports.init = function (_pad) {
pad = _pad;
}
};

View file

@ -20,56 +20,55 @@
* limitations under the License.
*/
var padutils = require('./pad_utils').padutils;
var hooks = require('./pluginfw/hooks');
const padutils = require('./pad_utils').padutils;
const hooks = require('./pluginfw/hooks');
var myUserInfo = {};
let myUserInfo = {};
var colorPickerOpen = false;
var colorPickerSetup = false;
var previousColorId = 0;
let colorPickerOpen = false;
let colorPickerSetup = false;
let previousColorId = 0;
var paduserlist = (function() {
var rowManager = (function() {
const paduserlist = (function () {
const rowManager = (function () {
// The row manager handles rendering rows of the user list and animating
// their insertion, removal, and reordering. It manipulates TD height
// and TD opacity.
function nextRowId() {
return "usertr" + (nextRowId.counter++);
return `usertr${nextRowId.counter++}`;
}
nextRowId.counter = 1;
// objects are shared; fields are "domId","data","animationStep"
var rowsFadingOut = []; // unordered set
var rowsFadingIn = []; // unordered set
var rowsPresent = []; // in order
var ANIMATION_START = -12; // just starting to fade in
var ANIMATION_END = 12; // just finishing fading out
const rowsFadingOut = []; // unordered set
const rowsFadingIn = []; // unordered set
const rowsPresent = []; // in order
const ANIMATION_START = -12; // just starting to fade in
const ANIMATION_END = 12; // just finishing fading out
function getAnimationHeight(step, power) {
var a = Math.abs(step / 12);
if (power == 2) a = a * a;
let a = Math.abs(step / 12);
if (power == 2) a *= a;
else if (power == 3) a = a * a * a;
else if (power == 4) a = a * a * a * a;
else if (power >= 5) a = a * a * a * a * a;
return Math.round(26 * (1 - a));
}
var OPACITY_STEPS = 6;
const OPACITY_STEPS = 6;
var ANIMATION_STEP_TIME = 20;
var LOWER_FRAMERATE_FACTOR = 2;
var scheduleAnimation = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME, LOWER_FRAMERATE_FACTOR).scheduleAnimation;
const ANIMATION_STEP_TIME = 20;
const LOWER_FRAMERATE_FACTOR = 2;
const scheduleAnimation = padutils.makeAnimationScheduler(animateStep, ANIMATION_STEP_TIME, LOWER_FRAMERATE_FACTOR).scheduleAnimation;
var NUMCOLS = 4;
const NUMCOLS = 4;
// we do lots of manipulation of table rows and stuff that JQuery makes ok, despite
// IE's poor handling when manipulating the DOM directly.
function getEmptyRowHtml(height) {
return '<td colspan="' + NUMCOLS + '" style="border:0;height:' + height + 'px"><!-- --></td>';
return `<td colspan="${NUMCOLS}" style="border:0;height:${height}px"><!-- --></td>`;
}
function isNameEditable(data) {
@ -77,84 +76,68 @@ var paduserlist = (function() {
}
function replaceUserRowContents(tr, height, data) {
var tds = getUserRowHtml(height, data).match(/<td.*?<\/td>/gi);
if (isNameEditable(data) && tr.find("td.usertdname input:enabled").length > 0)
{
const tds = getUserRowHtml(height, data).match(/<td.*?<\/td>/gi);
if (isNameEditable(data) && tr.find('td.usertdname input:enabled').length > 0) {
// preserve input field node
for (var i = 0; i < tds.length; i++)
{
var oldTd = $(tr.find("td").get(i));
if (!oldTd.hasClass('usertdname'))
{
for (let i = 0; i < tds.length; i++) {
const oldTd = $(tr.find('td').get(i));
if (!oldTd.hasClass('usertdname')) {
oldTd.replaceWith(tds[i]);
}
}
}
else
{
} else {
tr.html(tds.join(''));
}
return tr;
}
function getUserRowHtml(height, data) {
var nameHtml;
if (data.name)
{
let nameHtml;
if (data.name) {
nameHtml = padutils.escapeHtml(data.name);
}
else
{
nameHtml = '<input data-l10n-id="pad.userlist.unnamed" type="text" class="editempty newinput" value="'+_('pad.userlist.unnamed')+'" ' + (isNameEditable(data) ? '' : 'disabled="disabled" ') + '/>';
} else {
nameHtml = `<input data-l10n-id="pad.userlist.unnamed" type="text" class="editempty newinput" value="${_('pad.userlist.unnamed')}" ${isNameEditable(data) ? '' : 'disabled="disabled" '}/>`;
}
return ['<td style="height:', height, 'px" class="usertdswatch"><div class="swatch" style="background:' + padutils.escapeHtml(data.color) + '">&nbsp;</div></td>', '<td style="height:', height, 'px" class="usertdname">', nameHtml, '</td>', '<td style="height:', height, 'px" class="activity">', padutils.escapeHtml(data.activity), '</td>'].join('');
return ['<td style="height:', height, `px" class="usertdswatch"><div class="swatch" style="background:${padutils.escapeHtml(data.color)}">&nbsp;</div></td>`, '<td style="height:', height, 'px" class="usertdname">', nameHtml, '</td>', '<td style="height:', height, 'px" class="activity">', padutils.escapeHtml(data.activity), '</td>'].join('');
}
function getRowHtml(id, innerHtml, authorId) {
return '<tr data-authorId="'+authorId+'" id="' + id + '">' + innerHtml + '</tr>';
return `<tr data-authorId="${authorId}" id="${id}">${innerHtml}</tr>`;
}
function rowNode(row) {
return $("#" + row.domId);
return $(`#${row.domId}`);
}
function handleRowData(row) {
if (row.data && row.data.status == 'Disconnected')
{
if (row.data && row.data.status == 'Disconnected') {
row.opacity = 0.5;
}
else
{
} else {
delete row.opacity;
}
}
function handleRowNode(tr, data) {
if (data.titleText)
{
var titleText = data.titleText;
window.setTimeout(function() {
if (data.titleText) {
const titleText = data.titleText;
window.setTimeout(() => {
/* tr.attr('title', titleText)*/
}, 0);
}
else
{
} else {
tr.removeAttr('title');
}
}
function handleOtherUserInputs() {
// handle 'INPUT' elements for naming other unnamed users
$("#otheruserstable input.newinput").each(function() {
var input = $(this);
var tr = input.closest("tr");
if (tr.length > 0)
{
var index = tr.parent().children().index(tr);
if (index >= 0)
{
var userId = rowsPresent[index].data.id;
$('#otheruserstable input.newinput').each(function () {
const input = $(this);
const tr = input.closest('tr');
if (tr.length > 0) {
const index = tr.parent().children().index(tr);
if (index >= 0) {
const userId = rowsPresent[index].data.id;
rowManagerMakeNameEditor($(this), userId);
}
}
@ -168,41 +151,34 @@ var paduserlist = (function() {
position = Math.max(0, Math.min(rowsPresent.length, position));
animationPower = (animationPower === undefined ? 4 : animationPower);
var domId = nextRowId();
var row = {
data: data,
const domId = nextRowId();
const row = {
data,
animationStep: ANIMATION_START,
domId: domId,
animationPower: animationPower
domId,
animationPower,
};
var authorId = data.id;
const authorId = data.id;
handleRowData(row);
rowsPresent.splice(position, 0, row);
var tr;
if (animationPower == 0)
{
let tr;
if (animationPower == 0) {
tr = $(getRowHtml(domId, getUserRowHtml(getAnimationHeight(0), data), authorId));
row.animationStep = 0;
}
else
{
} else {
rowsFadingIn.push(row);
tr = $(getRowHtml(domId, getEmptyRowHtml(getAnimationHeight(ANIMATION_START)), authorId));
}
handleRowNode(tr, data);
$("table#otheruserstable").show();
if (position == 0)
{
$("table#otheruserstable").prepend(tr);
}
else
{
$('table#otheruserstable').show();
if (position == 0) {
$('table#otheruserstable').prepend(tr);
} else {
rowNode(rowsPresent[position - 1]).after(tr);
}
if (animationPower != 0)
{
if (animationPower != 0) {
scheduleAnimation();
}
@ -212,16 +188,14 @@ var paduserlist = (function() {
}
function updateRow(position, data) {
var row = rowsPresent[position];
if (row)
{
const row = rowsPresent[position];
if (row) {
row.data = data;
handleRowData(row);
if (row.animationStep == 0)
{
if (row.animationStep == 0) {
// not currently animating
var tr = rowNode(row);
replaceUserRowContents(tr, getAnimationHeight(0), row.data).find("td").css('opacity', (row.opacity === undefined ? 1 : row.opacity));
const tr = rowNode(row);
replaceUserRowContents(tr, getAnimationHeight(0), row.data).find('td').css('opacity', (row.opacity === undefined ? 1 : row.opacity));
handleRowNode(tr, data);
handleOtherUserInputs();
}
@ -230,16 +204,12 @@ var paduserlist = (function() {
function removeRow(position, animationPower) {
animationPower = (animationPower === undefined ? 4 : animationPower);
var row = rowsPresent[position];
if (row)
{
const row = rowsPresent[position];
if (row) {
rowsPresent.splice(position, 1); // remove
if (animationPower == 0)
{
if (animationPower == 0) {
rowNode(row).remove();
}
else
{
} else {
row.animationStep = -row.animationStep; // use symmetry
row.animationPower = animationPower;
rowsFadingOut.push(row);
@ -247,7 +217,7 @@ var paduserlist = (function() {
}
}
if (rowsPresent.length === 0) {
$("table#otheruserstable").hide();
$('table#otheruserstable').hide();
}
}
@ -256,10 +226,9 @@ var paduserlist = (function() {
function moveRow(oldPosition, newPosition, animationPower) {
animationPower = (animationPower === undefined ? 1 : animationPower); // linear is best
var row = rowsPresent[oldPosition];
if (row && oldPosition != newPosition)
{
var rowData = row.data;
const row = rowsPresent[oldPosition];
if (row && oldPosition != newPosition) {
const rowData = row.data;
removeRow(oldPosition, animationPower);
insertRow(newPosition, rowData, animationPower);
}
@ -267,55 +236,39 @@ var paduserlist = (function() {
function animateStep() {
// animation must be symmetrical
for (var i = rowsFadingIn.length - 1; i >= 0; i--)
{ // backwards to allow removal
for (var i = rowsFadingIn.length - 1; i >= 0; i--) { // backwards to allow removal
var row = rowsFadingIn[i];
var step = ++row.animationStep;
var animHeight = getAnimationHeight(step, row.animationPower);
var node = rowNode(row);
var baseOpacity = (row.opacity === undefined ? 1 : row.opacity);
if (step <= -OPACITY_STEPS)
{
node.find("td").height(animHeight);
}
else if (step == -OPACITY_STEPS + 1)
{
node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1 / OPACITY_STEPS);
if (step <= -OPACITY_STEPS) {
node.find('td').height(animHeight);
} else if (step == -OPACITY_STEPS + 1) {
node.html(getUserRowHtml(animHeight, row.data)).find('td').css('opacity', baseOpacity * 1 / OPACITY_STEPS);
handleRowNode(node, row.data);
}
else if (step < 0)
{
node.find("td").css('opacity', baseOpacity * (OPACITY_STEPS - (-step)) / OPACITY_STEPS).height(animHeight);
}
else if (step == 0)
{
} else if (step < 0) {
node.find('td').css('opacity', baseOpacity * (OPACITY_STEPS - (-step)) / OPACITY_STEPS).height(animHeight);
} else if (step == 0) {
// set HTML in case modified during animation
node.html(getUserRowHtml(animHeight, row.data)).find("td").css('opacity', baseOpacity * 1).height(animHeight);
node.html(getUserRowHtml(animHeight, row.data)).find('td').css('opacity', baseOpacity * 1).height(animHeight);
handleRowNode(node, row.data);
rowsFadingIn.splice(i, 1); // remove from set
}
}
for (var i = rowsFadingOut.length - 1; i >= 0; i--)
{ // backwards to allow removal
for (var i = rowsFadingOut.length - 1; i >= 0; i--) { // backwards to allow removal
var row = rowsFadingOut[i];
var step = ++row.animationStep;
var node = rowNode(row);
var animHeight = getAnimationHeight(step, row.animationPower);
var baseOpacity = (row.opacity === undefined ? 1 : row.opacity);
if (step < OPACITY_STEPS)
{
node.find("td").css('opacity', baseOpacity * (OPACITY_STEPS - step) / OPACITY_STEPS).height(animHeight);
}
else if (step == OPACITY_STEPS)
{
if (step < OPACITY_STEPS) {
node.find('td').css('opacity', baseOpacity * (OPACITY_STEPS - step) / OPACITY_STEPS).height(animHeight);
} else if (step == OPACITY_STEPS) {
node.html(getEmptyRowHtml(animHeight));
}
else if (step <= ANIMATION_END)
{
node.find("td").height(animHeight);
}
else
{
} else if (step <= ANIMATION_END) {
node.find('td').height(animHeight);
} else {
rowsFadingOut.splice(i, 1); // remove from set
node.remove();
}
@ -326,36 +279,30 @@ var paduserlist = (function() {
return (rowsFadingIn.length > 0) || (rowsFadingOut.length > 0); // is more to do
}
var self = {
insertRow: insertRow,
removeRow: removeRow,
moveRow: moveRow,
updateRow: updateRow
const self = {
insertRow,
removeRow,
moveRow,
updateRow,
};
return self;
}()); ////////// rowManager
var otherUsersInfo = [];
var otherUsersData = [];
}()); // //////// rowManager
const otherUsersInfo = [];
const otherUsersData = [];
function rowManagerMakeNameEditor(jnode, userId) {
setUpEditable(jnode, function() {
var existingIndex = findExistingIndex(userId);
if (existingIndex >= 0)
{
setUpEditable(jnode, () => {
const existingIndex = findExistingIndex(userId);
if (existingIndex >= 0) {
return otherUsersInfo[existingIndex].name || '';
}
else
{
} else {
return '';
}
}, function(newName) {
if (!newName)
{
jnode.addClass("editempty");
}, (newName) => {
if (!newName) {
jnode.addClass('editempty');
jnode.val(_('pad.userlist.unnamed'));
}
else
{
} else {
jnode.attr('disabled', 'disabled');
pad.suggestUserName(userId, newName);
}
@ -363,11 +310,9 @@ var paduserlist = (function() {
}
function findExistingIndex(userId) {
var existingIndex = -1;
for (var i = 0; i < otherUsersInfo.length; i++)
{
if (otherUsersInfo[i].userId == userId)
{
let existingIndex = -1;
for (let i = 0; i < otherUsersInfo.length; i++) {
if (otherUsersInfo[i].userId == userId) {
existingIndex = i;
break;
}
@ -376,144 +321,134 @@ var paduserlist = (function() {
}
function setUpEditable(jqueryNode, valueGetter, valueSetter) {
jqueryNode.bind('focus', function(evt) {
var oldValue = valueGetter();
if (jqueryNode.val() !== oldValue)
{
jqueryNode.bind('focus', (evt) => {
const oldValue = valueGetter();
if (jqueryNode.val() !== oldValue) {
jqueryNode.val(oldValue);
}
jqueryNode.addClass("editactive").removeClass("editempty");
jqueryNode.addClass('editactive').removeClass('editempty');
});
jqueryNode.bind('blur', function(evt) {
var newValue = jqueryNode.removeClass("editactive").val();
jqueryNode.bind('blur', (evt) => {
const newValue = jqueryNode.removeClass('editactive').val();
valueSetter(newValue);
});
padutils.bindEnterAndEscape(jqueryNode, function onEnter() {
padutils.bindEnterAndEscape(jqueryNode, () => {
jqueryNode.blur();
}, function onEscape() {
}, () => {
jqueryNode.val(valueGetter()).blur();
});
jqueryNode.removeAttr('disabled').addClass('editable');
}
var knocksToIgnore = {};
var guestPromptFlashState = 0;
var guestPromptFlash = padutils.makeAnimationScheduler(
const knocksToIgnore = {};
let guestPromptFlashState = 0;
const guestPromptFlash = padutils.makeAnimationScheduler(
function() {
var prompts = $("#guestprompts .guestprompt");
if (prompts.length == 0)
{
return false; // no more to do
}
() => {
const prompts = $('#guestprompts .guestprompt');
if (prompts.length == 0) {
return false; // no more to do
}
guestPromptFlashState = 1 - guestPromptFlashState;
if (guestPromptFlashState)
{
prompts.css('background', '#ffa');
}
else
{
prompts.css('background', '#ffe');
}
guestPromptFlashState = 1 - guestPromptFlashState;
if (guestPromptFlashState) {
prompts.css('background', '#ffa');
} else {
prompts.css('background', '#ffe');
}
return true;
}, 1000);
return true;
}, 1000);
var pad = undefined;
var self = {
init: function(myInitialUserInfo, _pad) {
init(myInitialUserInfo, _pad) {
pad = _pad;
self.setMyUserInfo(myInitialUserInfo);
if($('#online_count').length === 0) $('#editbar [data-key=showusers] > a').append('<span id="online_count">1</span>');
if ($('#online_count').length === 0) $('#editbar [data-key=showusers] > a').append('<span id="online_count">1</span>');
$("#otheruserstable tr").remove();
$('#otheruserstable tr').remove();
if (pad.getUserIsGuest())
{
$("#myusernameedit").addClass('myusernameedithoverable');
setUpEditable($("#myusernameedit"), function() {
return myUserInfo.name || '';
}, function(newValue) {
if (pad.getUserIsGuest()) {
$('#myusernameedit').addClass('myusernameedithoverable');
setUpEditable($('#myusernameedit'), () => myUserInfo.name || '', (newValue) => {
myUserInfo.name = newValue;
pad.notifyChangeName(newValue);
// wrap with setTimeout to do later because we get
// a double "blur" fire in IE...
window.setTimeout(function() {
window.setTimeout(() => {
self.renderMyUserInfo();
}, 0);
});
}
// color picker
$("#myswatchbox").click(showColorPicker);
$("#mycolorpicker .pickerswatchouter").click(function() {
$("#mycolorpicker .pickerswatchouter").removeClass('picked');
$('#myswatchbox').click(showColorPicker);
$('#mycolorpicker .pickerswatchouter').click(function () {
$('#mycolorpicker .pickerswatchouter').removeClass('picked');
$(this).addClass('picked');
});
$("#mycolorpickersave").click(function() {
$('#mycolorpickersave').click(() => {
closeColorPicker(true);
});
$("#mycolorpickercancel").click(function() {
$('#mycolorpickercancel').click(() => {
closeColorPicker(false);
});
//
},
usersOnline: function() {
usersOnline() {
// Returns an object of users who are currently online on this pad
var userList = [].concat(otherUsersInfo); // Make a copy of the otherUsersInfo, otherwise every call to users modifies the referenced array
const userList = [].concat(otherUsersInfo); // Make a copy of the otherUsersInfo, otherwise every call to users modifies the referenced array
// Now we need to add ourselves..
userList.push(myUserInfo);
return userList;
},
users: function(){
users() {
// Returns an object of users who have been on this pad
var userList = self.usersOnline();
const userList = self.usersOnline();
// Now we add historical authors
var historical = clientVars.collab_client_vars.historicalAuthorData;
for (var key in historical){
const historical = clientVars.collab_client_vars.historicalAuthorData;
for (const key in historical) {
var userId = historical[key].userId;
// Check we don't already have this author in our array
var exists = false;
userList.forEach(function(user){
if(user.userId === userId) exists = true;
userList.forEach((user) => {
if (user.userId === userId) exists = true;
});
if(exists === false){
if (exists === false) {
userList.push(historical[key]);
}
}
return userList;
},
setMyUserInfo: function(info) {
//translate the colorId
if(typeof info.colorId == "number")
{
setMyUserInfo(info) {
// translate the colorId
if (typeof info.colorId === 'number') {
info.colorId = clientVars.colorPalette[info.colorId];
}
myUserInfo = $.extend(
{}, info);
{}, info);
self.renderMyUserInfo();
},
userJoinOrUpdate: function(info) {
if ((!info.userId) || (info.userId == myUserInfo.userId))
{
userJoinOrUpdate(info) {
if ((!info.userId) || (info.userId == myUserInfo.userId)) {
// not sure how this would happen
return;
}
hooks.callAll('userJoinOrUpdate', {
userInfo: info
userInfo: info,
});
var userData = {};
userData.color = typeof info.colorId == "number" ? clientVars.colorPalette[info.colorId] : info.colorId;
const userData = {};
userData.color = typeof info.colorId === 'number' ? clientVars.colorPalette[info.colorId] : info.colorId;
userData.name = info.name;
userData.status = '';
userData.activity = '';
@ -521,38 +456,32 @@ var paduserlist = (function() {
// Firefox ignores \n in title text; Safari does a linebreak
userData.titleText = [info.userAgent || '', info.ip || ''].join(' \n');
var existingIndex = findExistingIndex(info.userId);
const existingIndex = findExistingIndex(info.userId);
var numUsersBesides = otherUsersInfo.length;
if (existingIndex >= 0)
{
let numUsersBesides = otherUsersInfo.length;
if (existingIndex >= 0) {
numUsersBesides--;
}
var newIndex = padutils.binarySearch(numUsersBesides, function(n) {
if (existingIndex >= 0 && n >= existingIndex)
{
const newIndex = padutils.binarySearch(numUsersBesides, (n) => {
if (existingIndex >= 0 && n >= existingIndex) {
// pretend existingIndex isn't there
n++;
}
var infoN = otherUsersInfo[n];
var nameN = (infoN.name || '').toLowerCase();
var nameThis = (info.name || '').toLowerCase();
var idN = infoN.userId;
var idThis = info.userId;
const infoN = otherUsersInfo[n];
const nameN = (infoN.name || '').toLowerCase();
const nameThis = (info.name || '').toLowerCase();
const idN = infoN.userId;
const idThis = info.userId;
return (nameN > nameThis) || (nameN == nameThis && idN > idThis);
});
if (existingIndex >= 0)
{
if (existingIndex >= 0) {
// update
if (existingIndex == newIndex)
{
if (existingIndex == newIndex) {
otherUsersInfo[existingIndex] = info;
otherUsersData[existingIndex] = userData;
rowManager.updateRow(existingIndex, userData);
}
else
{
} else {
otherUsersInfo.splice(existingIndex, 1);
otherUsersData.splice(existingIndex, 1);
otherUsersInfo.splice(newIndex, 0, info);
@ -560,9 +489,7 @@ var paduserlist = (function() {
rowManager.updateRow(existingIndex, userData);
rowManager.moveRow(existingIndex, newIndex);
}
}
else
{
} else {
otherUsersInfo.splice(newIndex, 0, info);
otherUsersData.splice(newIndex, 0, userData);
rowManager.insertRow(newIndex, userData);
@ -570,12 +497,10 @@ var paduserlist = (function() {
self.updateNumberOfOnlineUsers();
},
updateNumberOfOnlineUsers: function() {
var online = 1; // you are always online!
for (var i = 0; i < otherUsersData.length; i++)
{
if (otherUsersData[i].status == "")
{
updateNumberOfOnlineUsers() {
let online = 1; // you are always online!
for (let i = 0; i < otherUsersData.length; i++) {
if (otherUsersData[i].status == '') {
online++;
}
}
@ -584,33 +509,29 @@ var paduserlist = (function() {
return online;
},
userLeave: function(info) {
var existingIndex = findExistingIndex(info.userId);
if (existingIndex >= 0)
{
var userData = otherUsersData[existingIndex];
userLeave(info) {
const existingIndex = findExistingIndex(info.userId);
if (existingIndex >= 0) {
const userData = otherUsersData[existingIndex];
userData.status = 'Disconnected';
rowManager.updateRow(existingIndex, userData);
if (userData.leaveTimer)
{
if (userData.leaveTimer) {
window.clearTimeout(userData.leaveTimer);
}
// set up a timer that will only fire if no leaves,
// joins, or updates happen for this user in the
// next N seconds, to remove the user from the list.
var thisUserId = info.userId;
var thisLeaveTimer = window.setTimeout(function() {
var newExistingIndex = findExistingIndex(thisUserId);
if (newExistingIndex >= 0)
{
var newUserData = otherUsersData[newExistingIndex];
if (newUserData.status == 'Disconnected' && newUserData.leaveTimer == thisLeaveTimer)
{
const thisUserId = info.userId;
var thisLeaveTimer = window.setTimeout(() => {
const newExistingIndex = findExistingIndex(thisUserId);
if (newExistingIndex >= 0) {
const newUserData = otherUsersData[newExistingIndex];
if (newUserData.status == 'Disconnected' && newUserData.leaveTimer == thisLeaveTimer) {
otherUsersInfo.splice(newExistingIndex, 1);
otherUsersData.splice(newExistingIndex, 1);
rowManager.removeRow(newExistingIndex);
hooks.callAll('userLeave', {
userInfo: info
userInfo: info,
});
}
}
@ -620,163 +541,143 @@ var paduserlist = (function() {
self.updateNumberOfOnlineUsers();
},
showGuestPrompt: function(userId, displayName) {
if (knocksToIgnore[userId])
{
showGuestPrompt(userId, displayName) {
if (knocksToIgnore[userId]) {
return;
}
var encodedUserId = padutils.encodeUserId(userId);
const encodedUserId = padutils.encodeUserId(userId);
var actionName = 'hide-guest-prompt-' + encodedUserId;
const actionName = `hide-guest-prompt-${encodedUserId}`;
padutils.cancelActions(actionName);
var box = $("#guestprompt-" + encodedUserId);
if (box.length == 0)
{
let box = $(`#guestprompt-${encodedUserId}`);
if (box.length == 0) {
// make guest prompt box
box = $('<div id="'+padutils.escapeHtml('guestprompt-' + encodedUserId) + '" class="guestprompt"><div class="choices"><a href="' + padutils.escapeHtml('javascript:void(require('+JSON.stringify(module.id)+').paduserlist.answerGuestPrompt(' + JSON.stringify(encodedUserId) + ',false))')+'">'+_('pad.userlist.deny')+'</a> <a href="' + padutils.escapeHtml('javascript:void(require('+JSON.stringify(module.id)+').paduserlist.answerGuestPrompt(' + JSON.stringify(encodedUserId) + ',true))') + '">'+_('pad.userlist.approve')+'</a></div><div class="guestname"><strong>'+_('pad.userlist.guest')+':</strong> ' + padutils.escapeHtml(displayName) + '</div></div>');
$("#guestprompts").append(box);
}
else
{
box = $(`<div id="${padutils.escapeHtml(`guestprompt-${encodedUserId}`)}" class="guestprompt"><div class="choices"><a href="${padutils.escapeHtml(`javascript:void(require(${JSON.stringify(module.id)}).paduserlist.answerGuestPrompt(${JSON.stringify(encodedUserId)},false))`)}">${_('pad.userlist.deny')}</a> <a href="${padutils.escapeHtml(`javascript:void(require(${JSON.stringify(module.id)}).paduserlist.answerGuestPrompt(${JSON.stringify(encodedUserId)},true))`)}">${_('pad.userlist.approve')}</a></div><div class="guestname"><strong>${_('pad.userlist.guest')}:</strong> ${padutils.escapeHtml(displayName)}</div></div>`);
$('#guestprompts').append(box);
} else {
// update display name
box.find(".guestname").html('<strong>'+_('pad.userlist.guest')+':</strong> ' + padutils.escapeHtml(displayName));
box.find('.guestname').html(`<strong>${_('pad.userlist.guest')}:</strong> ${padutils.escapeHtml(displayName)}`);
}
var hideLater = padutils.getCancellableAction(actionName, function() {
const hideLater = padutils.getCancellableAction(actionName, () => {
self.removeGuestPrompt(userId);
});
window.setTimeout(hideLater, 15000); // time-out with no knock
guestPromptFlash.scheduleAnimation();
},
removeGuestPrompt: function(userId) {
var box = $("#guestprompt-" + padutils.encodeUserId(userId));
removeGuestPrompt(userId) {
const box = $(`#guestprompt-${padutils.encodeUserId(userId)}`);
// remove ID now so a new knock by same user gets new, unfaded box
box.removeAttr('id').fadeOut("fast", function() {
box.removeAttr('id').fadeOut('fast', () => {
box.remove();
});
knocksToIgnore[userId] = true;
window.setTimeout(function() {
window.setTimeout(() => {
delete knocksToIgnore[userId];
}, 5000);
},
answerGuestPrompt: function(encodedUserId, approve) {
var guestId = padutils.decodeUserId(encodedUserId);
answerGuestPrompt(encodedUserId, approve) {
const guestId = padutils.decodeUserId(encodedUserId);
var msg = {
const msg = {
type: 'guestanswer',
authId: pad.getUserId(),
guestId: guestId,
answer: (approve ? "approved" : "denied")
guestId,
answer: (approve ? 'approved' : 'denied'),
};
pad.sendClientMessage(msg);
self.removeGuestPrompt(guestId);
},
renderMyUserInfo: function() {
if (myUserInfo.name)
{
$("#myusernameedit").removeClass("editempty").val(myUserInfo.name);
renderMyUserInfo() {
if (myUserInfo.name) {
$('#myusernameedit').removeClass('editempty').val(myUserInfo.name);
} else {
$('#myusernameedit').attr('placeholder', html10n.get('pad.userlist.entername'));
}
else
{
$("#myusernameedit").attr("placeholder", html10n.get("pad.userlist.entername"));
}
if (colorPickerOpen)
{
$("#myswatchbox").addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable');
}
else
{
$("#myswatchbox").addClass('myswatchboxhoverable').removeClass('myswatchboxunhoverable');
if (colorPickerOpen) {
$('#myswatchbox').addClass('myswatchboxunhoverable').removeClass('myswatchboxhoverable');
} else {
$('#myswatchbox').addClass('myswatchboxhoverable').removeClass('myswatchboxunhoverable');
}
$("#myswatch").css({'background-color': myUserInfo.colorId});
$('#myswatch').css({'background-color': myUserInfo.colorId});
if (browser.msie && parseInt(browser.version) <= 8) {
$("li[data-key=showusers] > a").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId,'background-color': myUserInfo.colorId});
$('li[data-key=showusers] > a').css({'box-shadow': `inset 0 0 30px ${myUserInfo.colorId}`, 'background-color': myUserInfo.colorId});
} else {
$('li[data-key=showusers] > a').css({'box-shadow': `inset 0 0 30px ${myUserInfo.colorId}`});
}
else
{
$("li[data-key=showusers] > a").css({'box-shadow': 'inset 0 0 30px ' + myUserInfo.colorId});
}
}
},
};
return self;
}());
function getColorPickerSwatchIndex(jnode) {
// return Number(jnode.get(0).className.match(/\bn([0-9]+)\b/)[1])-1;
return $("#colorpickerswatches li").index(jnode);
return $('#colorpickerswatches li').index(jnode);
}
function closeColorPicker(accept) {
if (accept)
{
var newColor = $("#mycolorpickerpreview").css("background-color");
var parts = newColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
if (accept) {
var newColor = $('#mycolorpickerpreview').css('background-color');
const parts = newColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
// parts now should be ["rgb(0, 70, 255", "0", "70", "255"]
if (parts) {
delete (parts[0]);
for (var i = 1; i <= 3; ++i) {
parts[i] = parseInt(parts[i]).toString(16);
if (parts[i].length == 1) parts[i] = '0' + parts[i];
for (let i = 1; i <= 3; ++i) {
parts[i] = parseInt(parts[i]).toString(16);
if (parts[i].length == 1) parts[i] = `0${parts[i]}`;
}
var newColor = "#" +parts.join(''); // "0070ff"
var newColor = `#${parts.join('')}`; // "0070ff"
}
myUserInfo.colorId = newColor;
pad.notifyChangeColor(newColor);
paduserlist.renderMyUserInfo();
}
else
{
//pad.notifyChangeColor(previousColorId);
//paduserlist.renderMyUserInfo();
} else {
// pad.notifyChangeColor(previousColorId);
// paduserlist.renderMyUserInfo();
}
colorPickerOpen = false;
$("#mycolorpicker").removeClass('popup-show');
$('#mycolorpicker').removeClass('popup-show');
}
function showColorPicker() {
previousColorId = myUserInfo.colorId;
$.farbtastic('#colorpicker').setColor(myUserInfo.colorId)
$.farbtastic('#colorpicker').setColor(myUserInfo.colorId);
if (!colorPickerOpen)
{
var palette = pad.getColorPalette();
if (!colorPickerOpen) {
const palette = pad.getColorPalette();
if (!colorPickerSetup)
{
var colorsList = $("#colorpickerswatches")
for (var i = 0; i < palette.length; i++)
{
var li = $('<li>', {
style: 'background: ' + palette[i] + ';'
if (!colorPickerSetup) {
const colorsList = $('#colorpickerswatches');
for (let i = 0; i < palette.length; i++) {
const li = $('<li>', {
style: `background: ${palette[i]};`,
});
li.appendTo(colorsList);
li.bind('click', function(event) {
$("#colorpickerswatches li").removeClass('picked');
$(event.target).addClass("picked");
li.bind('click', (event) => {
$('#colorpickerswatches li').removeClass('picked');
$(event.target).addClass('picked');
var newColorId = getColorPickerSwatchIndex($("#colorpickerswatches .picked"));
const newColorId = getColorPickerSwatchIndex($('#colorpickerswatches .picked'));
pad.notifyChangeColor(newColorId);
});
}
colorPickerSetup = true;
}
$("#mycolorpicker").addClass('popup-show')
$('#mycolorpicker').addClass('popup-show');
colorPickerOpen = true;
$("#colorpickerswatches li").removeClass('picked');
$($("#colorpickerswatches li")[myUserInfo.colorId]).addClass("picked"); //seems weird
$('#colorpickerswatches li').removeClass('picked');
$($('#colorpickerswatches li')[myUserInfo.colorId]).addClass('picked'); // seems weird
}
}

View file

@ -20,129 +20,119 @@
* limitations under the License.
*/
var Security = require('./security');
const Security = require('./security');
/**
* Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
*/
function randomString(len) {
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var randomstring = '';
len = len || 20
for (var i = 0; i < len; i++)
{
var rnum = Math.floor(Math.random() * chars.length);
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
let randomstring = '';
len = len || 20;
for (let i = 0; i < len; i++) {
const rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum, rnum + 1);
}
return randomstring;
}
var padutils = {
escapeHtml: function(x) {
escapeHtml(x) {
return Security.escapeHTML(String(x));
},
uniqueId: function() {
var pad = require('./pad').pad; // Sidestep circular dependency
uniqueId() {
const pad = require('./pad').pad; // Sidestep circular dependency
function encodeNum(n, width) {
// returns string that is exactly 'width' chars, padding with zeros
// and taking rightmost digits
return (Array(width + 1).join('0') + Number(n).toString(35)).slice(-width);
}
return [pad.getClientIp(), encodeNum(+new Date, 7), encodeNum(Math.floor(Math.random() * 1e9), 4)].join('.');
return [pad.getClientIp(), encodeNum(+new Date(), 7), encodeNum(Math.floor(Math.random() * 1e9), 4)].join('.');
},
uaDisplay: function(ua) {
var m;
uaDisplay(ua) {
let m;
function clean(a) {
var maxlen = 16;
const maxlen = 16;
a = a.replace(/[^a-zA-Z0-9\.]/g, '');
if (a.length > maxlen)
{
if (a.length > maxlen) {
a = a.substr(0, maxlen);
}
return a;
}
function checkver(name) {
var m = ua.match(RegExp(name + '\\/([\\d\\.]+)'));
if (m && m.length > 1)
{
const m = ua.match(RegExp(`${name}\\/([\\d\\.]+)`));
if (m && m.length > 1) {
return clean(name + m[1]);
}
return null;
}
// firefox
if (checkver('Firefox'))
{
if (checkver('Firefox')) {
return checkver('Firefox');
}
// misc browsers, including IE
m = ua.match(/compatible; ([^;]+);/);
if (m && m.length > 1)
{
if (m && m.length > 1) {
return clean(m[1]);
}
// iphone
if (ua.match(/\(iPhone;/))
{
if (ua.match(/\(iPhone;/)) {
return 'iPhone';
}
// chrome
if (checkver('Chrome'))
{
if (checkver('Chrome')) {
return checkver('Chrome');
}
// safari
m = ua.match(/Safari\/[\d\.]+/);
if (m)
{
var v = '?';
if (m) {
let v = '?';
m = ua.match(/Version\/([\d\.]+)/);
if (m && m.length > 1)
{
if (m && m.length > 1) {
v = m[1];
}
return clean('Safari' + v);
return clean(`Safari${v}`);
}
// everything else
var x = ua.split(' ')[0];
const x = ua.split(' ')[0];
return clean(x);
},
// e.g. "Thu Jun 18 2009 13:09"
simpleDateTime: function(date) {
var d = new Date(+date); // accept either number or date
var dayOfWeek = (['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])[d.getDay()];
var month = (['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])[d.getMonth()];
var dayOfMonth = d.getDate();
var year = d.getFullYear();
var hourmin = d.getHours() + ":" + ("0" + d.getMinutes()).slice(-2);
return dayOfWeek + ' ' + month + ' ' + dayOfMonth + ' ' + year + ' ' + hourmin;
simpleDateTime(date) {
const d = new Date(+date); // accept either number or date
const dayOfWeek = (['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])[d.getDay()];
const month = (['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'])[d.getMonth()];
const dayOfMonth = d.getDate();
const year = d.getFullYear();
const hourmin = `${d.getHours()}:${(`0${d.getMinutes()}`).slice(-2)}`;
return `${dayOfWeek} ${month} ${dayOfMonth} ${year} ${hourmin}`;
},
findURLs: function(text) {
findURLs(text) {
// 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_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')');
var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|(about|geo|mailto|tel):)/.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_URLCHAR = new RegExp(`(${/[-:@a-zA-Z0-9_.,~%+\/?=&#;()$]/.source}|${_REGEX_WORDCHAR.source})`);
const _REGEX_URL = new RegExp(`${/(?:(?:https?|s?ftp|ftps|file|nfs):\/\/|(about|geo|mailto|tel):)/.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]);
}
@ -151,24 +141,21 @@ var padutils = {
return _findURLs(text);
},
escapeHtmlWithClickableLinks: function(text, target) {
var idx = 0;
var pieces = [];
var urls = padutils.findURLs(text);
escapeHtmlWithClickableLinks(text, target) {
let idx = 0;
const pieces = [];
const urls = padutils.findURLs(text);
function advanceTo(i) {
if (i > idx)
{
if (i > idx) {
pieces.push(Security.escapeHTML(text.substring(idx, i)));
idx = i;
}
}
if (urls)
{
for (var j = 0; j < urls.length; j++)
{
var startIndex = urls[j][0];
var href = urls[j][1];
if (urls) {
for (let j = 0; j < urls.length; j++) {
const startIndex = urls[j][0];
const href = urls[j][1];
advanceTo(startIndex);
// Using rel="noreferrer" stops leaking the URL/location of the pad when clicking links in the document.
// Not all browsers understand this attribute, but it's part of the HTML5 standard.
@ -177,7 +164,7 @@ var padutils = {
// 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
pieces.push('<a ', (target ? 'target="' + Security.escapeHTMLAttribute(target) + '" ' : ''), 'href="', Security.escapeHTMLAttribute(href), '" rel="noreferrer noopener">');
pieces.push('<a ', (target ? `target="${Security.escapeHTMLAttribute(target)}" ` : ''), 'href="', Security.escapeHTMLAttribute(href), '" rel="noreferrer noopener">');
advanceTo(startIndex + href.length);
pieces.push('</a>');
}
@ -185,78 +172,66 @@ var padutils = {
advanceTo(text.length);
return pieces.join('');
},
bindEnterAndEscape: function(node, onEnter, onEscape) {
bindEnterAndEscape(node, onEnter, onEscape) {
// Use keypress instead of keyup in bindEnterAndEscape
// Keyup event is fired on enter in IME (Input Method Editor), But
// keypress is not. So, I changed to use keypress instead of keyup.
// It is work on Windows (IE8, Chrome 6.0.472), CentOs (Firefox 3.0) and Mac OSX (Firefox 3.6.10, Chrome 6.0.472, Safari 5.0).
if (onEnter)
{
node.keypress(function(evt) {
if (evt.which == 13)
{
if (onEnter) {
node.keypress((evt) => {
if (evt.which == 13) {
onEnter(evt);
}
});
}
if (onEscape)
{
node.keydown(function(evt) {
if (evt.which == 27)
{
if (onEscape) {
node.keydown((evt) => {
if (evt.which == 27) {
onEscape(evt);
}
});
}
},
timediff: function(d) {
var pad = require('./pad').pad; // Sidestep circular dependency
timediff(d) {
const pad = require('./pad').pad; // Sidestep circular dependency
function format(n, word) {
n = Math.round(n);
return ('' + n + ' ' + word + (n != 1 ? 's' : '') + ' ago');
return (`${n} ${word}${n != 1 ? 's' : ''} ago`);
}
d = Math.max(0, (+(new Date) - (+d) - pad.clientTimeOffset) / 1000);
if (d < 60)
{
d = Math.max(0, (+(new Date()) - (+d) - pad.clientTimeOffset) / 1000);
if (d < 60) {
return format(d, 'second');
}
d /= 60;
if (d < 60)
{
if (d < 60) {
return format(d, 'minute');
}
d /= 60;
if (d < 24)
{
if (d < 24) {
return format(d, 'hour');
}
d /= 24;
return format(d, 'day');
},
makeAnimationScheduler: function(funcToAnimateOneStep, stepTime, stepsAtOnce) {
if (stepsAtOnce === undefined)
{
makeAnimationScheduler(funcToAnimateOneStep, stepTime, stepsAtOnce) {
if (stepsAtOnce === undefined) {
stepsAtOnce = 1;
}
var animationTimer = null;
let animationTimer = null;
function scheduleAnimation() {
if (!animationTimer)
{
animationTimer = window.setTimeout(function() {
if (!animationTimer) {
animationTimer = window.setTimeout(() => {
animationTimer = null;
var n = stepsAtOnce;
var moreToDo = true;
while (moreToDo && n > 0)
{
let n = stepsAtOnce;
let moreToDo = true;
while (moreToDo && n > 0) {
moreToDo = funcToAnimateOneStep();
n--;
}
if (moreToDo)
{
if (moreToDo) {
// more to do
scheduleAnimation();
}
@ -264,15 +239,15 @@ var padutils = {
}
}
return {
scheduleAnimation: scheduleAnimation
scheduleAnimation,
};
},
makeShowHideAnimator: function(funcToArriveAtState, initiallyShown, fps, totalMs) {
var animationState = (initiallyShown ? 0 : -2); // -2 hidden, -1 to 0 fade in, 0 to 1 fade out
var animationFrameDelay = 1000 / fps;
var animationStep = animationFrameDelay / totalMs;
makeShowHideAnimator(funcToArriveAtState, initiallyShown, fps, totalMs) {
let animationState = (initiallyShown ? 0 : -2); // -2 hidden, -1 to 0 fade in, 0 to 1 fade out
const animationFrameDelay = 1000 / fps;
const animationStep = animationFrameDelay / totalMs;
var scheduleAnimation = padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation;
const scheduleAnimation = padutils.makeAnimationScheduler(animateOneStep, animationFrameDelay).scheduleAnimation;
function doShow() {
animationState = -1;
@ -281,12 +256,9 @@ var padutils = {
}
function doQuickShow() { // start showing without losing any fade-in progress
if (animationState < -1)
{
if (animationState < -1) {
animationState = -1;
}
else if (animationState > 0)
{
} else if (animationState > 0) {
animationState = Math.max(-1, Math.min(0, -animationState));
}
funcToArriveAtState(animationState);
@ -294,47 +266,35 @@ var padutils = {
}
function doHide() {
if (animationState >= -1 && animationState <= 0)
{
if (animationState >= -1 && animationState <= 0) {
animationState = 1e-6;
scheduleAnimation();
}
}
function animateOneStep() {
if (animationState < -1 || animationState == 0)
{
if (animationState < -1 || animationState == 0) {
return false;
}
else if (animationState < 0)
{
} else if (animationState < 0) {
// animate show
animationState += animationStep;
if (animationState >= 0)
{
if (animationState >= 0) {
animationState = 0;
funcToArriveAtState(animationState);
return false;
}
else
{
} else {
funcToArriveAtState(animationState);
return true;
}
}
else if (animationState > 0)
{
} else if (animationState > 0) {
// animate hide
animationState += animationStep;
if (animationState >= 1)
{
if (animationState >= 1) {
animationState = 1;
funcToArriveAtState(animationState);
animationState = -2;
return false;
}
else
{
} else {
funcToArriveAtState(animationState);
return true;
}
@ -344,95 +304,83 @@ var padutils = {
return {
show: doShow,
hide: doHide,
quickShow: doQuickShow
quickShow: doQuickShow,
};
},
_nextActionId: 1,
uncanceledActions: {},
getCancellableAction: function(actionType, actionFunc) {
var o = padutils.uncanceledActions[actionType];
if (!o)
{
getCancellableAction(actionType, actionFunc) {
let o = padutils.uncanceledActions[actionType];
if (!o) {
o = {};
padutils.uncanceledActions[actionType] = o;
}
var actionId = (padutils._nextActionId++);
const actionId = (padutils._nextActionId++);
o[actionId] = true;
return function() {
var p = padutils.uncanceledActions[actionType];
if (p && p[actionId])
{
return function () {
const p = padutils.uncanceledActions[actionType];
if (p && p[actionId]) {
actionFunc();
}
};
},
cancelActions: function(actionType) {
var o = padutils.uncanceledActions[actionType];
if (o)
{
cancelActions(actionType) {
const o = padutils.uncanceledActions[actionType];
if (o) {
// clear it
delete padutils.uncanceledActions[actionType];
}
},
makeFieldLabeledWhenEmpty: function(field, labelText) {
makeFieldLabeledWhenEmpty(field, labelText) {
field = $(field);
function clear() {
field.addClass('editempty');
field.val(labelText);
}
field.focus(function() {
if (field.hasClass('editempty'))
{
field.focus(() => {
if (field.hasClass('editempty')) {
field.val('');
}
field.removeClass('editempty');
});
field.blur(function() {
if (!field.val())
{
field.blur(() => {
if (!field.val()) {
clear();
}
});
return {
clear: clear
clear,
};
},
getCheckbox: function(node) {
getCheckbox(node) {
return $(node).is(':checked');
},
setCheckbox: function(node, value) {
if (value)
{
setCheckbox(node, value) {
if (value) {
$(node).attr('checked', 'checked');
}
else
{
} else {
$(node).removeAttr('checked');
}
},
bindCheckboxChange: function(node, func) {
bindCheckboxChange(node, func) {
$(node).change(func);
},
encodeUserId: function(userId) {
return userId.replace(/[^a-y0-9]/g, function(c) {
if (c == ".") return "-";
return 'z' + c.charCodeAt(0) + 'z';
encodeUserId(userId) {
return userId.replace(/[^a-y0-9]/g, (c) => {
if (c == '.') return '-';
return `z${c.charCodeAt(0)}z`;
});
},
decodeUserId: function(encodedUserId) {
return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, function(cc) {
if (cc == '-') return '.';
else if (cc.charAt(0) == 'z')
{
decodeUserId(encodedUserId) {
return encodedUserId.replace(/[a-y0-9]+|-|z.+?z/g, (cc) => {
if (cc == '-') { return '.'; } else if (cc.charAt(0) == 'z') {
return String.fromCharCode(Number(cc.slice(1, -1)));
}
else
{
} else {
return cc;
}
});
}
},
};
let globalExceptionHandler = null;
@ -453,12 +401,12 @@ padutils.setupGlobalExceptionHandler = () => {
} else {
throw new Error(`unknown event: ${e.toString()}`);
}
var errorId = randomString(20);
const errorId = randomString(20);
var msgAlreadyVisible = false;
$('.gritter-item .error-msg').each(function() {
let msgAlreadyVisible = false;
$('.gritter-item .error-msg').each(function () {
if ($(this).text() === msg) {
msgAlreadyVisible = true;
msgAlreadyVisible = true;
}
});
@ -479,9 +427,9 @@ padutils.setupGlobalExceptionHandler = () => {
];
$.gritter.add({
title: "An error occurred",
title: 'An error occurred',
text: errorMsg,
class_name: "error",
class_name: 'error',
position: 'bottom',
sticky: true,
});
@ -505,7 +453,7 @@ padutils.setupGlobalExceptionHandler = () => {
window.addEventListener('error', globalExceptionHandler);
window.addEventListener('unhandledrejection', globalExceptionHandler);
}
}
};
padutils.binarySearch = require('./ace2_common').binarySearch;

View file

@ -1,43 +1,41 @@
var $, jQuery;
$ = jQuery = require("ep_etherpad-lite/static/js/rjquery").$;
var _ = require("underscore");
let $, jQuery;
$ = jQuery = require('ep_etherpad-lite/static/js/rjquery').$;
const _ = require('underscore');
var pluginUtils = require('./shared');
var defs = require('./plugin_defs');
const pluginUtils = require('./shared');
const defs = require('./plugin_defs');
exports.baseURL = '';
exports.ensure = function (cb) {
if (!defs.loaded)
exports.update(cb);
else
cb();
if (!defs.loaded) exports.update(cb);
else cb();
};
exports.update = function (cb) {
// It appears that this response (see #620) may interrupt the current thread
// of execution on Firefox. This schedules the response in the run-loop,
// which appears to fix the issue.
var callback = function () {setTimeout(cb, 0);};
$.ajaxSetup({ cache: false });
jQuery.getJSON(exports.baseURL + 'pluginfw/plugin-definitions.json').done(function(data) {
const callback = function () { setTimeout(cb, 0); };
$.ajaxSetup({cache: false});
jQuery.getJSON(`${exports.baseURL}pluginfw/plugin-definitions.json`).done((data) => {
defs.plugins = data.plugins;
defs.parts = data.parts;
defs.hooks = pluginUtils.extractHooks(defs.parts, "client_hooks");
defs.hooks = pluginUtils.extractHooks(defs.parts, 'client_hooks');
defs.loaded = true;
callback();
}).fail(function(e){
console.error("Failed to load plugin-definitions: " + err);
}).fail((e) => {
console.error(`Failed to load plugin-definitions: ${err}`);
callback();
});
};
function adoptPluginsFromAncestorsOf(frame) {
// Bind plugins with parent;
var parentRequire = null;
let parentRequire = null;
try {
while (frame = frame.parent) {
if (typeof (frame.require) !== "undefined") {
if (typeof (frame.require) !== 'undefined') {
parentRequire = frame.require;
break;
}
@ -46,17 +44,17 @@ function adoptPluginsFromAncestorsOf(frame) {
// Silence (this can only be a XDomain issue).
}
if (parentRequire) {
var ancestorPluginDefs = parentRequire("ep_etherpad-lite/static/js/pluginfw/plugin_defs");
const ancestorPluginDefs = parentRequire('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
defs.hooks = ancestorPluginDefs.hooks;
defs.loaded = ancestorPluginDefs.loaded;
defs.parts = ancestorPluginDefs.parts;
defs.plugins = ancestorPluginDefs.plugins;
var ancestorPlugins = parentRequire("ep_etherpad-lite/static/js/pluginfw/client_plugins");
const ancestorPlugins = parentRequire('ep_etherpad-lite/static/js/pluginfw/client_plugins');
exports.baseURL = ancestorPlugins.baseURL;
exports.ensure = ancestorPlugins.ensure;
exports.update = ancestorPlugins.update;
} else {
throw new Error("Parent plugins could not be found.")
throw new Error('Parent plugins could not be found.');
}
}

View file

@ -1,7 +1,7 @@
/* global exports, require */
var _ = require("underscore");
var pluginDefs = require('./plugin_defs');
const _ = require('underscore');
const pluginDefs = require('./plugin_defs');
// Maps the name of a server-side hook to a string explaining the deprecation
// (e.g., 'use the foo hook instead').
@ -24,26 +24,24 @@ function checkDeprecation(hook) {
deprecationWarned[hook.hook_fn_name] = true;
}
exports.bubbleExceptions = true
exports.bubbleExceptions = true;
var hookCallWrapper = function (hook, hook_name, args, cb) {
const hookCallWrapper = function (hook, hook_name, args, cb) {
if (cb === undefined) cb = function (x) { return x; };
checkDeprecation(hook);
// Normalize output to list for both sync and async cases
var normalize = function(x) {
const normalize = function (x) {
if (x === undefined) return [];
return x;
}
var normalizedhook = function () {
return normalize(hook.hook_fn(hook_name, args, function (x) {
return cb(normalize(x));
}));
}
};
const normalizedhook = function () {
return normalize(hook.hook_fn(hook_name, args, (x) => cb(normalize(x))));
};
if (exports.bubbleExceptions) {
return normalizedhook();
return normalizedhook();
} else {
try {
return normalizedhook();
@ -51,32 +49,32 @@ var hookCallWrapper = function (hook, hook_name, args, cb) {
console.error([hook_name, hook.part.full_name, ex.stack || ex]);
}
}
}
};
exports.syncMapFirst = function (lst, fn) {
var i;
var result;
let i;
let result;
for (i = 0; i < lst.length; i++) {
result = fn(lst[i])
result = fn(lst[i]);
if (result.length) return result;
}
return [];
}
};
exports.mapFirst = function (lst, fn, cb, predicate) {
if (predicate == null) predicate = (x) => (x != null && x.length > 0);
var i = 0;
let i = 0;
var next = function () {
if (i >= lst.length) return cb(null, []);
fn(lst[i++], function (err, result) {
fn(lst[i++], (err, result) => {
if (err) return cb(err);
if (predicate(result)) return cb(null, result);
next();
});
}
};
next();
}
};
// Calls the hook function synchronously and returns the value provided by the hook function (via
// callback or return value).
@ -350,11 +348,9 @@ async function callHookFnAsync(hook, context) {
exports.aCallAll = async (hookName, context, cb) => {
if (context == null) context = {};
const hooks = pluginDefs.hooks[hookName] || [];
let resultsPromise = Promise.all(hooks.map((hook) => {
return callHookFnAsync(hook, context)
// `undefined` (but not `null`!) is treated the same as [].
.then((result) => (result === undefined) ? [] : result);
})).then((results) => _.flatten(results, 1));
let resultsPromise = Promise.all(hooks.map((hook) => callHookFnAsync(hook, context)
// `undefined` (but not `null`!) is treated the same as [].
.then((result) => (result === undefined) ? [] : result))).then((results) => _.flatten(results, 1));
if (cb != null) resultsPromise = resultsPromise.then((val) => cb(null, val), cb);
return await resultsPromise;
};
@ -362,49 +358,45 @@ exports.aCallAll = async (hookName, context, cb) => {
exports.callFirst = function (hook_name, args) {
if (!args) args = {};
if (pluginDefs.hooks[hook_name] === undefined) return [];
return exports.syncMapFirst(pluginDefs.hooks[hook_name], function(hook) {
return hookCallWrapper(hook, hook_name, args);
});
}
return exports.syncMapFirst(pluginDefs.hooks[hook_name], (hook) => hookCallWrapper(hook, hook_name, args));
};
function aCallFirst(hook_name, args, cb, predicate) {
if (!args) args = {};
if (!cb) cb = function () {};
if (pluginDefs.hooks[hook_name] === undefined) return cb(null, []);
exports.mapFirst(
pluginDefs.hooks[hook_name],
function (hook, cb) {
hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); });
},
cb,
predicate
pluginDefs.hooks[hook_name],
(hook, cb) => {
hookCallWrapper(hook, hook_name, args, (res) => { cb(null, res); });
},
cb,
predicate,
);
}
/* return a Promise if cb is not supplied */
exports.aCallFirst = function (hook_name, args, cb, predicate) {
if (cb === undefined) {
return new Promise(function(resolve, reject) {
aCallFirst(hook_name, args, function(err, res) {
return err ? reject(err) : resolve(res);
}, predicate);
return new Promise((resolve, reject) => {
aCallFirst(hook_name, args, (err, res) => err ? reject(err) : resolve(res), predicate);
});
} else {
return aCallFirst(hook_name, args, cb, predicate);
}
}
};
exports.callAllStr = function(hook_name, args, sep, pre, post) {
exports.callAllStr = function (hook_name, args, sep, pre, post) {
if (sep == undefined) sep = '';
if (pre == undefined) pre = '';
if (post == undefined) post = '';
var newCallhooks = [];
var callhooks = exports.callAll(hook_name, args);
for (var i = 0, ii = callhooks.length; i < ii; i++) {
const newCallhooks = [];
const callhooks = exports.callAll(hook_name, args);
for (let i = 0, ii = callhooks.length; i < ii; i++) {
newCallhooks[i] = pre + callhooks[i] + post;
}
return newCallhooks.join(sep || "");
}
return newCallhooks.join(sep || '');
};
exports.exportedForTestingOnly = {
callHookFnAsync,

View file

@ -1,8 +1,8 @@
const log4js = require('log4js');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var npm = require("npm");
var request = require("request");
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins');
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
const npm = require('npm');
const request = require('request');
const util = require('util');
let npmIsLoaded = false;
@ -13,20 +13,20 @@ const loadNpm = async () => {
npm.on('log', log4js.getLogger('npm').log);
};
var tasks = 0
let tasks = 0;
function wrapTaskCb(cb) {
tasks++;
return function() {
return function () {
cb && cb.apply(this, arguments);
tasks--;
if (tasks == 0) onAllTasksFinished();
}
};
}
function onAllTasksFinished() {
hooks.aCallAll("restartServer", {}, function() {});
hooks.aCallAll('restartServer', {}, () => {});
}
exports.uninstall = async (pluginName, cb = null) => {
@ -58,18 +58,18 @@ exports.install = async (pluginName, cb = null) => {
};
exports.availablePlugins = null;
var cacheTimestamp = 0;
let cacheTimestamp = 0;
exports.getAvailablePlugins = function(maxCacheAge) {
var nowTimestamp = Math.round(Date.now() / 1000);
exports.getAvailablePlugins = function (maxCacheAge) {
const nowTimestamp = Math.round(Date.now() / 1000);
return new Promise(function(resolve, reject) {
return new Promise((resolve, reject) => {
// check cache age before making any request
if (exports.availablePlugins && maxCacheAge && (nowTimestamp - cacheTimestamp) <= maxCacheAge) {
return resolve(exports.availablePlugins);
}
request("https://static.etherpad.org/plugins.json", function(er, response, plugins) {
request('https://static.etherpad.org/plugins.json', (er, response, plugins) => {
if (er) return reject(er);
try {
@ -87,26 +87,26 @@ exports.getAvailablePlugins = function(maxCacheAge) {
};
exports.search = function(searchTerm, maxCacheAge) {
return exports.getAvailablePlugins(maxCacheAge).then(function(results) {
var res = {};
exports.search = function (searchTerm, maxCacheAge) {
return exports.getAvailablePlugins(maxCacheAge).then((results) => {
const res = {};
if (searchTerm) {
searchTerm = searchTerm.toLowerCase();
}
for (var pluginName in results) {
for (const pluginName in results) {
// for every available plugin
if (pluginName.indexOf(plugins.prefix) != 0) continue; // TODO: Also search in keywords here!
if (searchTerm && !~results[pluginName].name.toLowerCase().indexOf(searchTerm)
&& (typeof results[pluginName].description != "undefined" && !~results[pluginName].description.toLowerCase().indexOf(searchTerm) )
) {
if (typeof results[pluginName].description === "undefined") {
console.debug('plugin without Description: %s', results[pluginName].name);
}
if (searchTerm && !~results[pluginName].name.toLowerCase().indexOf(searchTerm) &&
(typeof results[pluginName].description !== 'undefined' && !~results[pluginName].description.toLowerCase().indexOf(searchTerm))
) {
if (typeof results[pluginName].description === 'undefined') {
console.debug('plugin without Description: %s', results[pluginName].name);
}
continue;
continue;
}
res[pluginName] = results[pluginName];

View file

@ -1,61 +1,61 @@
const fs = require('fs').promises;
const hooks = require('./hooks');
var npm = require("npm/lib/npm.js");
var readInstalled = require("./read-installed.js");
var path = require("path");
var tsort = require("./tsort");
var util = require("util");
var _ = require("underscore");
var settings = require('../../../node/utils/Settings');
const npm = require('npm/lib/npm.js');
const readInstalled = require('./read-installed.js');
const path = require('path');
const tsort = require('./tsort');
const util = require('util');
const _ = require('underscore');
const settings = require('../../../node/utils/Settings');
var pluginUtils = require('./shared');
var defs = require('./plugin_defs');
const pluginUtils = require('./shared');
const defs = require('./plugin_defs');
exports.prefix = 'ep_';
exports.formatPlugins = function () {
return _.keys(defs.plugins).join(", ");
return _.keys(defs.plugins).join(', ');
};
exports.formatPluginsWithVersion = function () {
var plugins = [];
_.forEach(defs.plugins, function(plugin) {
if(plugin.package.name !== "ep_etherpad-lite"){
var pluginStr = plugin.package.name + "@" + plugin.package.version;
const plugins = [];
_.forEach(defs.plugins, (plugin) => {
if (plugin.package.name !== 'ep_etherpad-lite') {
const pluginStr = `${plugin.package.name}@${plugin.package.version}`;
plugins.push(pluginStr);
}
});
return plugins.join(", ");
return plugins.join(', ');
};
exports.formatParts = function () {
return _.map(defs.parts, function(part) { return part.full_name; }).join('\n');
return _.map(defs.parts, (part) => part.full_name).join('\n');
};
exports.formatHooks = function (hook_set_name) {
var res = [];
var hooks = pluginUtils.extractHooks(defs.parts, hook_set_name || 'hooks');
const res = [];
const hooks = pluginUtils.extractHooks(defs.parts, hook_set_name || 'hooks');
_.chain(hooks).keys().forEach(function (hook_name) {
_.forEach(hooks[hook_name], function (hook) {
res.push("<dt>" + hook.hook_name + "</dt><dd>" + hook.hook_fn_name + " from " + hook.part.full_name + "</dd>");
_.chain(hooks).keys().forEach((hook_name) => {
_.forEach(hooks[hook_name], (hook) => {
res.push(`<dt>${hook.hook_name}</dt><dd>${hook.hook_fn_name} from ${hook.part.full_name}</dd>`);
});
});
return "<dl>" + res.join("\n") + "</dl>";
return `<dl>${res.join('\n')}</dl>`;
};
const callInit = async () => {
await Promise.all(Object.keys(defs.plugins).map(async (plugin_name) => {
let plugin = defs.plugins[plugin_name];
let ep_init = path.normalize(path.join(plugin.package.path, ".ep_initialized"));
const plugin = defs.plugins[plugin_name];
const ep_init = path.normalize(path.join(plugin.package.path, '.ep_initialized'));
try {
await fs.stat(ep_init);
} catch (err) {
await fs.writeFile(ep_init, 'done');
await hooks.aCallAll("init_" + plugin_name, {});
await hooks.aCallAll(`init_${plugin_name}`, {});
}
}));
}
};
exports.pathNormalization = function (part, hook_fn_name, hook_name) {
const tmp = hook_fn_name.split(':'); // hook_fn_name might be something like 'C:\\foo.js:myFunc'.
@ -65,32 +65,32 @@ exports.pathNormalization = function (part, hook_fn_name, hook_name) {
const packageDir = path.dirname(defs.plugins[part.plugin].package.path);
const fileName = path.normalize(path.join(packageDir, moduleName));
return `${fileName}:${functionName}`;
}
};
exports.update = async function () {
let packages = await exports.getPackages();
var parts = {}; // Key is full name. sortParts converts this into a topologically sorted array.
var plugins = {};
const packages = await exports.getPackages();
const parts = {}; // Key is full name. sortParts converts this into a topologically sorted array.
const plugins = {};
// Load plugin metadata ep.json
await Promise.all(Object.keys(packages).map(
async (pluginName) => await loadPlugin(packages, pluginName, plugins, parts)));
async (pluginName) => await loadPlugin(packages, pluginName, plugins, parts)));
defs.plugins = plugins;
defs.parts = sortParts(parts);
defs.hooks = pluginUtils.extractHooks(defs.parts, 'hooks', exports.pathNormalization);
defs.loaded = true;
await callInit();
}
};
exports.getPackages = async function () {
// Load list of installed NPM packages, flatten it to a list, and filter out only packages with names that
var dir = settings.root;
let data = await util.promisify(readInstalled)(dir);
const dir = settings.root;
const data = await util.promisify(readInstalled)(dir);
var packages = {};
const packages = {};
function flatten(deps) {
_.chain(deps).keys().each(function (name) {
_.chain(deps).keys().each((name) => {
if (name.indexOf(exports.prefix) === 0) {
packages[name] = _.clone(deps[name]);
// Delete anything that creates loops so that the plugin
@ -100,48 +100,48 @@ exports.getPackages = async function () {
}
// I don't think we need recursion
//if (deps[name].dependencies !== undefined) flatten(deps[name].dependencies);
// if (deps[name].dependencies !== undefined) flatten(deps[name].dependencies);
});
}
var tmp = {};
const tmp = {};
tmp[data.name] = data;
flatten(tmp[data.name].dependencies);
return packages;
};
async function loadPlugin(packages, plugin_name, plugins, parts) {
var plugin_path = path.resolve(packages[plugin_name].path, "ep.json");
const plugin_path = path.resolve(packages[plugin_name].path, 'ep.json');
try {
let data = await fs.readFile(plugin_path);
const data = await fs.readFile(plugin_path);
try {
var plugin = JSON.parse(data);
plugin['package'] = packages[plugin_name];
const plugin = JSON.parse(data);
plugin.package = packages[plugin_name];
plugins[plugin_name] = plugin;
_.each(plugin.parts, function (part) {
_.each(plugin.parts, (part) => {
part.plugin = plugin_name;
part.full_name = plugin_name + "/" + part.name;
part.full_name = `${plugin_name}/${part.name}`;
parts[part.full_name] = part;
});
} catch (ex) {
console.error("Unable to parse plugin definition file " + plugin_path + ": " + ex.toString());
console.error(`Unable to parse plugin definition file ${plugin_path}: ${ex.toString()}`);
}
} catch (er) {
console.error("Unable to load plugin definition file " + plugin_path);
console.error(`Unable to load plugin definition file ${plugin_path}`);
}
}
function partsToParentChildList(parts) {
var res = [];
_.chain(parts).keys().forEach(function (name) {
_.each(parts[name].post || [], function (child_name) {
const res = [];
_.chain(parts).keys().forEach((name) => {
_.each(parts[name].post || [], (child_name) => {
res.push([name, child_name]);
});
_.each(parts[name].pre || [], function (parent_name) {
_.each(parts[name].pre || [], (parent_name) => {
res.push([parent_name, name]);
});
if (!parts[name].pre && !parts[name].post) {
res.push([name, ":" + name]); // Include apps with no dependency info
res.push([name, `:${name}`]); // Include apps with no dependency info
}
});
return res;
@ -150,10 +150,10 @@ function partsToParentChildList(parts) {
// Used only in Node, so no need for _
function sortParts(parts) {
return tsort(
partsToParentChildList(parts)
partsToParentChildList(parts),
).filter(
function (name) { return parts[name] !== undefined; }
(name) => parts[name] !== undefined,
).map(
function (name) { return parts[name]; }
(name) => parts[name],
);
}

View file

@ -89,248 +89,251 @@ as far as the left-most node_modules folder.
*/
var npm = require("npm/lib/npm.js")
, fs = require("graceful-fs")
, path = require("path")
, asyncMap = require("slide").asyncMap
, semver = require("semver")
, log = require("log4js").getLogger('pluginfw')
const npm = require('npm/lib/npm.js');
const fs = require('graceful-fs');
const path = require('path');
const asyncMap = require('slide').asyncMap;
const semver = require('semver');
const log = require('log4js').getLogger('pluginfw');
function readJson(file, callback) {
fs.readFile(file, function(er, buf) {
if(er) {
fs.readFile(file, (er, buf) => {
if (er) {
callback(er);
return;
}
try {
callback( null, JSON.parse(buf.toString()) )
} catch(er) {
callback(er)
callback(null, JSON.parse(buf.toString()));
} catch (er) {
callback(er);
}
})
});
}
module.exports = readInstalled
module.exports = readInstalled;
function readInstalled (folder, cb) {
function readInstalled(folder, cb) {
/* This is where we clear the cache, these three lines are all the
* new code there is */
rpSeen = {};
riSeen = [];
var fuSeen = [];
const fuSeen = [];
var d = npm.config.get("depth")
readInstalled_(folder, null, null, null, 0, d, function (er, obj) {
if (er) return cb(er)
const d = npm.config.get('depth');
readInstalled_(folder, null, null, null, 0, d, (er, obj) => {
if (er) return cb(er);
// now obj has all the installed things, where they're installed
// figure out the inheritance links, now that the object is built.
resolveInheritance(obj)
cb(null, obj)
})
resolveInheritance(obj);
cb(null, obj);
});
}
var rpSeen = {}
function readInstalled_ (folder, parent, name, reqver, depth, maxDepth, cb) {
//console.error(folder, name)
var rpSeen = {};
function readInstalled_(folder, parent, name, reqver, depth, maxDepth, cb) {
// console.error(folder, name)
var installed
, obj
, real
, link
let installed,
obj,
real,
link;
fs.readdir(path.resolve(folder, "node_modules"), function (er, i) {
fs.readdir(path.resolve(folder, 'node_modules'), (er, i) => {
// error indicates that nothing is installed here
if (er) i = []
installed = i.filter(function (f) { return f.charAt(0) !== "." })
next()
})
if (er) i = [];
installed = i.filter((f) => f.charAt(0) !== '.');
next();
});
readJson(path.resolve(folder, "package.json"), function (er, data) {
obj = copy(data)
readJson(path.resolve(folder, 'package.json'), (er, data) => {
obj = copy(data);
if (!parent) {
obj = obj || true
er = null
obj = obj || true;
er = null;
}
return next(er)
})
return next(er);
});
fs.lstat(folder, function (er, st) {
fs.lstat(folder, (er, st) => {
if (er) {
if (!parent) real = true
return next(er)
if (!parent) real = true;
return next(er);
}
fs.realpath(folder, function (er, rp) {
//console.error("realpath(%j) = %j", folder, rp)
real = rp
if (st.isSymbolicLink()) link = rp
next(er)
})
})
fs.realpath(folder, (er, rp) => {
// console.error("realpath(%j) = %j", folder, rp)
real = rp;
if (st.isSymbolicLink()) link = rp;
next(er);
});
});
var errState = null
, called = false
function next (er) {
if (errState) return
let errState = null;
let called = false;
function next(er) {
if (errState) return;
if (er) {
errState = er
return cb(null, [])
errState = er;
return cb(null, []);
}
//console.error('next', installed, obj && typeof obj, name, real)
if (!installed || !obj || !real || called) return
called = true
if (rpSeen[real]) return cb(null, rpSeen[real])
// console.error('next', installed, obj && typeof obj, name, real)
if (!installed || !obj || !real || called) return;
called = true;
if (rpSeen[real]) return cb(null, rpSeen[real]);
if (obj === true) {
obj = {dependencies:{}, path:folder}
installed.forEach(function (i) { obj.dependencies[i] = "*" })
obj = {dependencies: {}, path: folder};
installed.forEach((i) => { obj.dependencies[i] = '*'; });
}
if (name && obj.name !== name) obj.invalid = true
obj.realName = name || obj.name
obj.dependencies = obj.dependencies || {}
if (name && obj.name !== name) obj.invalid = true;
obj.realName = name || obj.name;
obj.dependencies = obj.dependencies || {};
// "foo":"http://blah" is always presumed valid
if (reqver
&& semver.validRange(reqver)
&& !semver.satisfies(obj.version, reqver)) {
obj.invalid = true
if (reqver &&
semver.validRange(reqver) &&
!semver.satisfies(obj.version, reqver)) {
obj.invalid = true;
}
if (parent
&& !(name in parent.dependencies)
&& !(name in (parent.devDependencies || {}))) {
obj.extraneous = true
if (parent &&
!(name in parent.dependencies) &&
!(name in (parent.devDependencies || {}))) {
obj.extraneous = true;
}
obj.path = obj.path || folder
obj.realPath = real
obj.link = link
if (parent && !obj.link) obj.parent = parent
rpSeen[real] = obj
obj.depth = depth
if (depth >= maxDepth) return cb(null, obj)
asyncMap(installed, function (pkg, cb) {
var rv = obj.dependencies[pkg]
if (!rv && obj.devDependencies) rv = obj.devDependencies[pkg]
readInstalled_( path.resolve(folder, "node_modules/"+pkg)
, obj, pkg, obj.dependencies[pkg], depth + 1, maxDepth
, cb )
}, function (er, installedData) {
if (er) return cb(er)
installedData.forEach(function (dep) {
obj.dependencies[dep.realName] = dep
})
obj.path = obj.path || folder;
obj.realPath = real;
obj.link = link;
if (parent && !obj.link) obj.parent = parent;
rpSeen[real] = obj;
obj.depth = depth;
if (depth >= maxDepth) return cb(null, obj);
asyncMap(installed, (pkg, cb) => {
let rv = obj.dependencies[pkg];
if (!rv && obj.devDependencies) rv = obj.devDependencies[pkg];
readInstalled_(path.resolve(folder, `node_modules/${pkg}`)
, obj, pkg, obj.dependencies[pkg], depth + 1, maxDepth
, cb);
}, (er, installedData) => {
if (er) return cb(er);
installedData.forEach((dep) => {
obj.dependencies[dep.realName] = dep;
});
// any strings here are unmet things. however, if it's
// optional, then that's fine, so just delete it.
if (obj.optionalDependencies) {
Object.keys(obj.optionalDependencies).forEach(function (dep) {
if (typeof obj.dependencies[dep] === "string") {
delete obj.dependencies[dep]
Object.keys(obj.optionalDependencies).forEach((dep) => {
if (typeof obj.dependencies[dep] === 'string') {
delete obj.dependencies[dep];
}
})
});
}
return cb(null, obj)
})
return cb(null, obj);
});
}
}
// starting from a root object, call findUnmet on each layer of children
var riSeen = []
function resolveInheritance (obj) {
if (typeof obj !== "object") return
if (riSeen.indexOf(obj) !== -1) return
riSeen.push(obj)
if (typeof obj.dependencies !== "object") {
obj.dependencies = {}
var riSeen = [];
function resolveInheritance(obj) {
if (typeof obj !== 'object') return;
if (riSeen.indexOf(obj) !== -1) return;
riSeen.push(obj);
if (typeof obj.dependencies !== 'object') {
obj.dependencies = {};
}
Object.keys(obj.dependencies).forEach(function (dep) {
findUnmet(obj.dependencies[dep])
})
Object.keys(obj.dependencies).forEach(function (dep) {
resolveInheritance(obj.dependencies[dep])
})
Object.keys(obj.dependencies).forEach((dep) => {
findUnmet(obj.dependencies[dep]);
});
Object.keys(obj.dependencies).forEach((dep) => {
resolveInheritance(obj.dependencies[dep]);
});
}
// find unmet deps by walking up the tree object.
// No I/O
var fuSeen = []
function findUnmet (obj) {
if (fuSeen.indexOf(obj) !== -1) return
fuSeen.push(obj)
//console.error("find unmet", obj.name, obj.parent && obj.parent.name)
var deps = obj.dependencies = obj.dependencies || {}
//console.error(deps)
const fuSeen = [];
function findUnmet(obj) {
if (fuSeen.indexOf(obj) !== -1) return;
fuSeen.push(obj);
// console.error("find unmet", obj.name, obj.parent && obj.parent.name)
const deps = obj.dependencies = obj.dependencies || {};
// console.error(deps)
Object.keys(deps)
.filter(function (d) { return typeof deps[d] === "string" })
.forEach(function (d) {
//console.error("find unmet", obj.name, d, deps[d])
var r = obj.parent
, found = null
while (r && !found && typeof deps[d] === "string") {
.filter((d) => typeof deps[d] === 'string')
.forEach((d) => {
// console.error("find unmet", obj.name, d, deps[d])
let r = obj.parent;
let found = null;
while (r && !found && typeof deps[d] === 'string') {
// if r is a valid choice, then use that.
found = r.dependencies[d]
if (!found && r.realName === d) found = r
found = r.dependencies[d];
if (!found && r.realName === d) found = r;
if (!found) {
r = r.link ? null : r.parent
continue
}
if ( typeof deps[d] === "string"
&& !semver.satisfies(found.version, deps[d])) {
if (!found) {
r = r.link ? null : r.parent;
continue;
}
if (typeof deps[d] === 'string' &&
!semver.satisfies(found.version, deps[d])) {
// the bad thing will happen
log.warn(obj.path + " requires "+d+"@'"+deps[d]
+"' but will load\n"
+found.path+",\nwhich is version "+found.version
,"unmet dependency")
found.invalid = true
log.warn(`${obj.path} requires ${d}@'${deps[d]
}' but will load\n${
found.path},\nwhich is version ${found.version}`
, 'unmet dependency');
found.invalid = true;
}
deps[d] = found;
}
deps[d] = found
}
})
return obj
});
return obj;
}
function copy (obj) {
if (!obj || typeof obj !== 'object') return obj
if (Array.isArray(obj)) return obj.map(copy)
function copy(obj) {
if (!obj || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) return obj.map(copy);
var o = {}
for (var i in obj) o[i] = copy(obj[i])
return o
const o = {};
for (const i in obj) o[i] = copy(obj[i]);
return o;
}
if (module === require.main) {
var util = require("util")
console.error("testing")
const util = require('util');
console.error('testing');
var called = 0
readInstalled(process.cwd(), function (er, map) {
console.error(called ++)
if (er) return console.error(er.stack || er.message)
cleanup(map)
console.error(util.inspect(map, true, 10, true))
})
let called = 0;
readInstalled(process.cwd(), (er, map) => {
console.error(called++);
if (er) return console.error(er.stack || er.message);
cleanup(map);
console.error(util.inspect(map, true, 10, true));
});
var seen = []
function cleanup (map) {
if (seen.indexOf(map) !== -1) return
seen.push(map)
for (var i in map) switch (i) {
case "_id":
case "path":
case "extraneous": case "invalid":
case "dependencies": case "name":
continue
default: delete map[i]
}
var dep = map.dependencies
// delete map.dependencies
if (dep) {
// map.dependencies = dep
for (var i in dep) if (typeof dep[i] === "object") {
cleanup(dep[i])
const seen = [];
function cleanup(map) {
if (seen.indexOf(map) !== -1) return;
seen.push(map);
for (var i in map) {
switch (i) {
case '_id':
case 'path':
case 'extraneous': case 'invalid':
case 'dependencies': case 'name':
continue;
default: delete map[i];
}
}
return map
const dep = map.dependencies;
// delete map.dependencies
if (dep) {
// map.dependencies = dep
for (var i in dep) {
if (typeof dep[i] === 'object') {
cleanup(dep[i]);
}
}
}
return map;
}
}

View file

@ -1,5 +1,5 @@
var _ = require("underscore");
var defs = require('./plugin_defs');
const _ = require('underscore');
const defs = require('./plugin_defs');
const disabledHookReasons = {
hooks: {
@ -9,70 +9,70 @@ const disabledHookReasons = {
};
function loadFn(path, hookName) {
var functionName
, parts = path.split(":");
let functionName;
const parts = path.split(':');
// on windows: C:\foo\bar:xyz
if (parts[0].length == 1) {
if (parts.length == 3) {
functionName = parts.pop();
}
path = parts.join(":");
path = parts.join(':');
} else {
path = parts[0];
functionName = parts[1];
}
var fn = require(path);
let fn = require(path);
functionName = functionName ? functionName : hookName;
_.each(functionName.split("."), function (name) {
_.each(functionName.split('.'), (name) => {
fn = fn[name];
});
return fn;
};
}
function extractHooks(parts, hook_set_name, normalizer) {
var hooks = {};
_.each(parts,function (part) {
const hooks = {};
_.each(parts, (part) => {
_.chain(part[hook_set_name] || {})
.keys()
.each(function (hook_name) {
var hook_fn_name = part[hook_set_name][hook_name];
.keys()
.each((hook_name) => {
let hook_fn_name = part[hook_set_name][hook_name];
/* On the server side, you can't just
/* On the server side, you can't just
* require("pluginname/whatever") if the plugin is installed as
* a dependency of another plugin! Bah, pesky little details of
* npm... */
if (normalizer) {
hook_fn_name = normalizer(part, hook_fn_name, hook_name);
}
if (normalizer) {
hook_fn_name = normalizer(part, hook_fn_name, hook_name);
}
const disabledReason = (disabledHookReasons[hook_set_name] || {})[hook_name];
if (disabledReason) {
console.error(`Hook ${hook_set_name}/${hook_name} is disabled. Reason: ${disabledReason}`);
console.error(`The hook function ${hook_fn_name} from plugin ${part.plugin} ` +
const disabledReason = (disabledHookReasons[hook_set_name] || {})[hook_name];
if (disabledReason) {
console.error(`Hook ${hook_set_name}/${hook_name} is disabled. Reason: ${disabledReason}`);
console.error(`The hook function ${hook_fn_name} from plugin ${part.plugin} ` +
'will never be called, which may cause the plugin to fail');
console.error(`Please update the ${part.plugin} plugin to not use the ${hook_name} hook`);
return;
}
console.error(`Please update the ${part.plugin} plugin to not use the ${hook_name} hook`);
return;
}
try {
var hook_fn = loadFn(hook_fn_name, hook_name);
if (!hook_fn) {
throw "Not a function";
}
} catch (exc) {
console.error("Failed to load '" + hook_fn_name + "' for '" + part.full_name + "/" + hook_set_name + "/" + hook_name + "': " + exc.toString())
}
if (hook_fn) {
if (hooks[hook_name] == null) hooks[hook_name] = [];
hooks[hook_name].push({"hook_name": hook_name, "hook_fn": hook_fn, "hook_fn_name": hook_fn_name, "part": part});
}
});
try {
var hook_fn = loadFn(hook_fn_name, hook_name);
if (!hook_fn) {
throw 'Not a function';
}
} catch (exc) {
console.error(`Failed to load '${hook_fn_name}' for '${part.full_name}/${hook_set_name}/${hook_name}': ${exc.toString()}`);
}
if (hook_fn) {
if (hooks[hook_name] == null) hooks[hook_name] = [];
hooks[hook_name].push({hook_name, hook_fn, hook_fn_name, part});
}
});
});
return hooks;
};
}
exports.extractHooks = extractHooks;
@ -88,12 +88,12 @@ exports.extractHooks = extractHooks;
* No plugins: []
* Some plugins: [ 'ep_adminpads', 'ep_add_buttons', 'ep_activepads' ]
*/
exports.clientPluginNames = function() {
var client_plugin_names = _.uniq(
defs.parts
.filter(function(part) { return part.hasOwnProperty('client_hooks'); })
.map(function(part) { return 'plugin-' + part['plugin']; })
exports.clientPluginNames = function () {
const client_plugin_names = _.uniq(
defs.parts
.filter((part) => part.hasOwnProperty('client_hooks'))
.map((part) => `plugin-${part.plugin}`),
);
return client_plugin_names;
}
};

View file

@ -8,27 +8,28 @@
**/
function tsort(edges) {
var nodes = {}, // hash: stringified id of the node => { id: id, afters: lisf of ids }
sorted = [], // sorted list of IDs ( returned value )
visited = {}; // hash: id of already visited node => true
const nodes = {}; // hash: stringified id of the node => { id: id, afters: lisf of ids }
const sorted = []; // sorted list of IDs ( returned value )
const visited = {}; // hash: id of already visited node => true
var Node = function(id) {
const Node = function (id) {
this.id = id;
this.afters = [];
}
};
// 1. build data structures
edges.forEach(function(v) {
var from = v[0], to = v[1];
edges.forEach((v) => {
const from = v[0]; const
to = v[1];
if (!nodes[from]) nodes[from] = new Node(from);
if (!nodes[to]) nodes[to] = new Node(to);
if (!nodes[to]) nodes[to] = new Node(to);
nodes[from].afters.push(to);
});
// 2. topological sort
Object.keys(nodes).forEach(function visit(idstr, ancestors) {
var node = nodes[idstr],
id = node.id;
const node = nodes[idstr];
const id = node.id;
// if already exists, do nothing
if (visited[idstr]) return;
@ -39,11 +40,11 @@ function tsort(edges) {
visited[idstr] = true;
node.afters.forEach(function(afterID) {
if (ancestors.indexOf(afterID) >= 0) // if already in ancestors, a closed chain exists.
throw new Error('closed chain : ' + afterID + ' is in ' + id);
node.afters.forEach((afterID) => {
if (ancestors.indexOf(afterID) >= 0) // if already in ancestors, a closed chain exists.
{ throw new Error(`closed chain : ${afterID} is in ${id}`); }
visit(afterID.toString(), ancestors.map(function(v) { return v })); // recursive call
visit(afterID.toString(), ancestors.map((v) => v)); // recursive call
});
sorted.unshift(id);
@ -56,57 +57,55 @@ function tsort(edges) {
* TEST
**/
function tsortTest() {
// example 1: success
var edges = [
let edges = [
[1, 2],
[1, 3],
[2, 4],
[3, 4]
[3, 4],
];
var sorted = tsort(edges);
let sorted = tsort(edges);
console.log(sorted);
// example 2: failure ( A > B > C > A )
edges = [
['A', 'B'],
['B', 'C'],
['C', 'A']
['C', 'A'],
];
try {
sorted = tsort(edges);
}
catch (e) {
} catch (e) {
console.log(e.message);
}
// example 3: generate random edges
var max = 100, iteration = 30;
const max = 100; const
iteration = 30;
function randomInt(max) {
return Math.floor(Math.random() * max) + 1;
}
edges = (function() {
var ret = [], i = 0;
while (i++ < iteration) ret.push( [randomInt(max), randomInt(max)] );
edges = (function () {
const ret = []; let
i = 0;
while (i++ < iteration) ret.push([randomInt(max), randomInt(max)]);
return ret;
})();
try {
sorted = tsort(edges);
console.log("succeeded", sorted);
console.log('succeeded', sorted);
} catch (e) {
console.log('failed', e.message);
}
catch (e) {
console.log("failed", e.message);
}
}
// for node.js
if (typeof exports == 'object' && exports === this) {
if (typeof exports === 'object' && exports === this) {
module.exports = tsort;
if (process.argv[1] === __filename) tsortTest();
}

View file

@ -1,5 +1,4 @@
// Proviedes a require'able version of jQuery without leaking $ and jQuery;
window.$ = require('./jquery');
var jq = window.$.noConflict(true);
const jq = window.$.noConflict(true);
exports.jQuery = exports.$ = jq;

View file

@ -5,7 +5,7 @@
Browser Line = each vertical line. A <div> can be break into more than one
browser line.
*/
var caretPosition = require('./caretPosition');
const caretPosition = require('./caretPosition');
function Scroll(outerWin) {
// scroll settings
@ -20,317 +20,313 @@ function Scroll(outerWin) {
Scroll.prototype.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary = function (rep, isScrollableEvent, innerHeight) {
// are we placing the caret on the line at the bottom of viewport?
// And if so, do we need to scroll the editor, as defined on the settings.json?
var shouldScrollWhenCaretIsAtBottomOfViewport = this.scrollSettings.scrollWhenCaretIsInTheLastLineOfViewport;
const shouldScrollWhenCaretIsAtBottomOfViewport = this.scrollSettings.scrollWhenCaretIsInTheLastLineOfViewport;
if (shouldScrollWhenCaretIsAtBottomOfViewport) {
// avoid scrolling when selection includes multiple lines -- user can potentially be selecting more lines
// than it fits on viewport
var multipleLinesSelected = rep.selStart[0] !== rep.selEnd[0];
const multipleLinesSelected = rep.selStart[0] !== rep.selEnd[0];
// avoid scrolling when pad loads
if (isScrollableEvent && !multipleLinesSelected && this._isCaretAtTheBottomOfViewport(rep)) {
// when scrollWhenFocusLineIsOutOfViewport.percentage is 0, pixelsToScroll is 0
var pixelsToScroll = this._getPixelsRelativeToPercentageOfViewport(innerHeight);
const pixelsToScroll = this._getPixelsRelativeToPercentageOfViewport(innerHeight);
this._scrollYPage(pixelsToScroll);
}
}
}
};
Scroll.prototype.scrollWhenPressArrowKeys = function(arrowUp, rep, innerHeight) {
Scroll.prototype.scrollWhenPressArrowKeys = function (arrowUp, rep, innerHeight) {
// if percentageScrollArrowUp is 0, let the scroll to be handled as default, put the previous
// rep line on the top of the viewport
if(this._arrowUpWasPressedInTheFirstLineOfTheViewport(arrowUp, rep)){
var pixelsToScroll = this._getPixelsToScrollWhenUserPressesArrowUp(innerHeight);
if (this._arrowUpWasPressedInTheFirstLineOfTheViewport(arrowUp, rep)) {
const pixelsToScroll = this._getPixelsToScrollWhenUserPressesArrowUp(innerHeight);
// by default, the browser scrolls to the middle of the viewport. To avoid the twist made
// when we apply a second scroll, we made it immediately (without animation)
this._scrollYPageWithoutAnimation(-pixelsToScroll);
}else{
} else {
this.scrollNodeVerticallyIntoView(rep, innerHeight);
}
}
};
// Some plugins might set a minimum height to the editor (ex: ep_page_view), so checking
// if (caretLine() === rep.lines.length() - 1) is not enough. We need to check if there are
// other lines after caretLine(), and all of them are out of viewport.
Scroll.prototype._isCaretAtTheBottomOfViewport = function(rep) {
Scroll.prototype._isCaretAtTheBottomOfViewport = function (rep) {
// computing a line position using getBoundingClientRect() is expensive.
// (obs: getBoundingClientRect() is called on caretPosition.getPosition())
// To avoid that, we only call this function when it is possible that the
// caret is in the bottom of viewport
var caretLine = rep.selStart[0];
var lineAfterCaretLine = caretLine + 1;
var firstLineVisibleAfterCaretLine = caretPosition.getNextVisibleLine(lineAfterCaretLine, rep);
var caretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(caretLine, rep);
var lineAfterCaretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(firstLineVisibleAfterCaretLine, rep);
const caretLine = rep.selStart[0];
const lineAfterCaretLine = caretLine + 1;
const firstLineVisibleAfterCaretLine = caretPosition.getNextVisibleLine(lineAfterCaretLine, rep);
const caretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(caretLine, rep);
const lineAfterCaretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(firstLineVisibleAfterCaretLine, rep);
if (caretLineIsPartiallyVisibleOnViewport || lineAfterCaretLineIsPartiallyVisibleOnViewport) {
// check if the caret is in the bottom of the viewport
var caretLinePosition = caretPosition.getPosition();
var viewportBottom = this._getViewPortTopBottom().bottom;
var nextLineBottom = caretPosition.getBottomOfNextBrowserLine(caretLinePosition, rep);
var nextLineIsBelowViewportBottom = nextLineBottom > viewportBottom;
const caretLinePosition = caretPosition.getPosition();
const viewportBottom = this._getViewPortTopBottom().bottom;
const nextLineBottom = caretPosition.getBottomOfNextBrowserLine(caretLinePosition, rep);
const nextLineIsBelowViewportBottom = nextLineBottom > viewportBottom;
return nextLineIsBelowViewportBottom;
}
return false;
}
};
Scroll.prototype._isLinePartiallyVisibleOnViewport = function(lineNumber, rep) {
var lineNode = rep.lines.atIndex(lineNumber);
var linePosition = this._getLineEntryTopBottom(lineNode);
var lineTop = linePosition.top;
var lineBottom = linePosition.bottom;
var viewport = this._getViewPortTopBottom();
var viewportBottom = viewport.bottom;
var viewportTop = viewport.top;
Scroll.prototype._isLinePartiallyVisibleOnViewport = function (lineNumber, rep) {
const lineNode = rep.lines.atIndex(lineNumber);
const linePosition = this._getLineEntryTopBottom(lineNode);
const lineTop = linePosition.top;
const lineBottom = linePosition.bottom;
const viewport = this._getViewPortTopBottom();
const viewportBottom = viewport.bottom;
const viewportTop = viewport.top;
var topOfLineIsAboveOfViewportBottom = lineTop < viewportBottom;
var bottomOfLineIsOnOrBelowOfViewportBottom = lineBottom >= viewportBottom;
var topOfLineIsBelowViewportTop = lineTop >= viewportTop;
var topOfLineIsAboveViewportBottom = lineTop <= viewportBottom;
var bottomOfLineIsAboveViewportBottom = lineBottom <= viewportBottom;
var bottomOfLineIsBelowViewportTop = lineBottom >= viewportTop;
const topOfLineIsAboveOfViewportBottom = lineTop < viewportBottom;
const bottomOfLineIsOnOrBelowOfViewportBottom = lineBottom >= viewportBottom;
const topOfLineIsBelowViewportTop = lineTop >= viewportTop;
const topOfLineIsAboveViewportBottom = lineTop <= viewportBottom;
const bottomOfLineIsAboveViewportBottom = lineBottom <= viewportBottom;
const bottomOfLineIsBelowViewportTop = lineBottom >= viewportTop;
return (topOfLineIsAboveOfViewportBottom && bottomOfLineIsOnOrBelowOfViewportBottom) ||
(topOfLineIsBelowViewportTop && topOfLineIsAboveViewportBottom) ||
(bottomOfLineIsAboveViewportBottom && bottomOfLineIsBelowViewportTop);
}
};
Scroll.prototype._getViewPortTopBottom = function() {
var theTop = this.getScrollY();
var doc = this.doc;
var height = doc.documentElement.clientHeight; // includes padding
Scroll.prototype._getViewPortTopBottom = function () {
const theTop = this.getScrollY();
const doc = this.doc;
const height = doc.documentElement.clientHeight; // includes padding
// we have to get the exactly height of the viewport. So it has to subtract all the values which changes
// the viewport height (E.g. padding, position top)
var viewportExtraSpacesAndPosition = this._getEditorPositionTop() + this._getPaddingTopAddedWhenPageViewIsEnable();
const viewportExtraSpacesAndPosition = this._getEditorPositionTop() + this._getPaddingTopAddedWhenPageViewIsEnable();
return {
top: theTop,
bottom: (theTop + height - viewportExtraSpacesAndPosition)
bottom: (theTop + height - viewportExtraSpacesAndPosition),
};
}
};
Scroll.prototype._getEditorPositionTop = function() {
var editor = parent.document.getElementsByTagName('iframe');
var editorPositionTop = editor[0].offsetTop;
Scroll.prototype._getEditorPositionTop = function () {
const editor = parent.document.getElementsByTagName('iframe');
const editorPositionTop = editor[0].offsetTop;
return editorPositionTop;
}
};
// ep_page_view adds padding-top, which makes the viewport smaller
Scroll.prototype._getPaddingTopAddedWhenPageViewIsEnable = function() {
var aceOuter = this.rootDocument.getElementsByName("ace_outer");
var aceOuterPaddingTop = parseInt($(aceOuter).css("padding-top"));
Scroll.prototype._getPaddingTopAddedWhenPageViewIsEnable = function () {
const aceOuter = this.rootDocument.getElementsByName('ace_outer');
const aceOuterPaddingTop = parseInt($(aceOuter).css('padding-top'));
return aceOuterPaddingTop;
}
};
Scroll.prototype._getScrollXY = function() {
var win = this.outerWin;
var odoc = this.doc;
if (typeof(win.pageYOffset) == "number")
{
Scroll.prototype._getScrollXY = function () {
const win = this.outerWin;
const odoc = this.doc;
if (typeof (win.pageYOffset) === 'number') {
return {
x: win.pageXOffset,
y: win.pageYOffset
y: win.pageYOffset,
};
}
var docel = odoc.documentElement;
if (docel && typeof(docel.scrollTop) == "number")
{
const docel = odoc.documentElement;
if (docel && typeof (docel.scrollTop) === 'number') {
return {
x: docel.scrollLeft,
y: docel.scrollTop
y: docel.scrollTop,
};
}
}
};
Scroll.prototype.getScrollX = function() {
Scroll.prototype.getScrollX = function () {
return this._getScrollXY().x;
}
};
Scroll.prototype.getScrollY = function() {
Scroll.prototype.getScrollY = function () {
return this._getScrollXY().y;
}
};
Scroll.prototype.setScrollX = function(x) {
Scroll.prototype.setScrollX = function (x) {
this.outerWin.scrollTo(x, this.getScrollY());
}
};
Scroll.prototype.setScrollY = function(y) {
Scroll.prototype.setScrollY = function (y) {
this.outerWin.scrollTo(this.getScrollX(), y);
}
};
Scroll.prototype.setScrollXY = function(x, y) {
Scroll.prototype.setScrollXY = function (x, y) {
this.outerWin.scrollTo(x, y);
}
};
Scroll.prototype._isCaretAtTheTopOfViewport = function(rep) {
var caretLine = rep.selStart[0];
var linePrevCaretLine = caretLine - 1;
var firstLineVisibleBeforeCaretLine = caretPosition.getPreviousVisibleLine(linePrevCaretLine, rep);
var caretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(caretLine, rep);
var lineBeforeCaretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(firstLineVisibleBeforeCaretLine, rep);
Scroll.prototype._isCaretAtTheTopOfViewport = function (rep) {
const caretLine = rep.selStart[0];
const linePrevCaretLine = caretLine - 1;
const firstLineVisibleBeforeCaretLine = caretPosition.getPreviousVisibleLine(linePrevCaretLine, rep);
const caretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(caretLine, rep);
const lineBeforeCaretLineIsPartiallyVisibleOnViewport = this._isLinePartiallyVisibleOnViewport(firstLineVisibleBeforeCaretLine, rep);
if (caretLineIsPartiallyVisibleOnViewport || lineBeforeCaretLineIsPartiallyVisibleOnViewport) {
var caretLinePosition = caretPosition.getPosition(); // get the position of the browser line
var viewportPosition = this._getViewPortTopBottom();
var viewportTop = viewportPosition.top;
var viewportBottom = viewportPosition.bottom;
var caretLineIsBelowViewportTop = caretLinePosition.bottom >= viewportTop;
var caretLineIsAboveViewportBottom = caretLinePosition.top < viewportBottom;
var caretLineIsInsideOfViewport = caretLineIsBelowViewportTop && caretLineIsAboveViewportBottom;
const caretLinePosition = caretPosition.getPosition(); // get the position of the browser line
const viewportPosition = this._getViewPortTopBottom();
const viewportTop = viewportPosition.top;
const viewportBottom = viewportPosition.bottom;
const caretLineIsBelowViewportTop = caretLinePosition.bottom >= viewportTop;
const caretLineIsAboveViewportBottom = caretLinePosition.top < viewportBottom;
const caretLineIsInsideOfViewport = caretLineIsBelowViewportTop && caretLineIsAboveViewportBottom;
if (caretLineIsInsideOfViewport) {
var prevLineTop = caretPosition.getPositionTopOfPreviousBrowserLine(caretLinePosition, rep);
var previousLineIsAboveViewportTop = prevLineTop < viewportTop;
const prevLineTop = caretPosition.getPositionTopOfPreviousBrowserLine(caretLinePosition, rep);
const previousLineIsAboveViewportTop = prevLineTop < viewportTop;
return previousLineIsAboveViewportTop;
}
}
return false;
}
};
// By default, when user makes an edition in a line out of viewport, this line goes
// to the edge of viewport. This function gets the extra pixels necessary to get the
// caret line in a position X relative to Y% viewport.
Scroll.prototype._getPixelsRelativeToPercentageOfViewport = function(innerHeight, aboveOfViewport) {
var pixels = 0;
var scrollPercentageRelativeToViewport = this._getPercentageToScroll(aboveOfViewport);
if(scrollPercentageRelativeToViewport > 0 && scrollPercentageRelativeToViewport <= 1){
Scroll.prototype._getPixelsRelativeToPercentageOfViewport = function (innerHeight, aboveOfViewport) {
let pixels = 0;
const scrollPercentageRelativeToViewport = this._getPercentageToScroll(aboveOfViewport);
if (scrollPercentageRelativeToViewport > 0 && scrollPercentageRelativeToViewport <= 1) {
pixels = parseInt(innerHeight * scrollPercentageRelativeToViewport);
}
return pixels;
}
};
// we use different percentages when change selection. It depends on if it is
// either above the top or below the bottom of the page
Scroll.prototype._getPercentageToScroll = function(aboveOfViewport) {
var percentageToScroll = this.scrollSettings.percentage.editionBelowViewport;
if(aboveOfViewport){
Scroll.prototype._getPercentageToScroll = function (aboveOfViewport) {
let percentageToScroll = this.scrollSettings.percentage.editionBelowViewport;
if (aboveOfViewport) {
percentageToScroll = this.scrollSettings.percentage.editionAboveViewport;
}
return percentageToScroll;
}
};
Scroll.prototype._getPixelsToScrollWhenUserPressesArrowUp = function(innerHeight) {
var pixels = 0;
var percentageToScrollUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp;
if(percentageToScrollUp > 0 && percentageToScrollUp <= 1){
Scroll.prototype._getPixelsToScrollWhenUserPressesArrowUp = function (innerHeight) {
let pixels = 0;
const percentageToScrollUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp;
if (percentageToScrollUp > 0 && percentageToScrollUp <= 1) {
pixels = parseInt(innerHeight * percentageToScrollUp);
}
return pixels;
}
};
Scroll.prototype._scrollYPage = function(pixelsToScroll) {
var durationOfAnimationToShowFocusline = this.scrollSettings.duration;
if(durationOfAnimationToShowFocusline){
Scroll.prototype._scrollYPage = function (pixelsToScroll) {
const durationOfAnimationToShowFocusline = this.scrollSettings.duration;
if (durationOfAnimationToShowFocusline) {
this._scrollYPageWithAnimation(pixelsToScroll, durationOfAnimationToShowFocusline);
}else{
} else {
this._scrollYPageWithoutAnimation(pixelsToScroll);
}
}
};
Scroll.prototype._scrollYPageWithoutAnimation = function(pixelsToScroll) {
Scroll.prototype._scrollYPageWithoutAnimation = function (pixelsToScroll) {
this.outerWin.scrollBy(0, pixelsToScroll);
}
};
Scroll.prototype._scrollYPageWithAnimation = function(pixelsToScroll, durationOfAnimationToShowFocusline) {
var outerDocBody = this.doc.getElementById("outerdocbody");
Scroll.prototype._scrollYPageWithAnimation = function (pixelsToScroll, durationOfAnimationToShowFocusline) {
const outerDocBody = this.doc.getElementById('outerdocbody');
// it works on later versions of Chrome
var $outerDocBody = $(outerDocBody);
const $outerDocBody = $(outerDocBody);
this._triggerScrollWithAnimation($outerDocBody, pixelsToScroll, durationOfAnimationToShowFocusline);
// it works on Firefox and earlier versions of Chrome
var $outerDocBodyParent = $outerDocBody.parent();
const $outerDocBodyParent = $outerDocBody.parent();
this._triggerScrollWithAnimation($outerDocBodyParent, pixelsToScroll, durationOfAnimationToShowFocusline);
}
};
// using a custom queue and clearing it, we avoid creating a queue of scroll animations. So if this function
// is called twice quickly, only the last one runs.
Scroll.prototype._triggerScrollWithAnimation = function($elem, pixelsToScroll, durationOfAnimationToShowFocusline) {
Scroll.prototype._triggerScrollWithAnimation = function ($elem, pixelsToScroll, durationOfAnimationToShowFocusline) {
// clear the queue of animation
$elem.stop("scrollanimation");
$elem.stop('scrollanimation');
$elem.animate({
scrollTop: '+=' + pixelsToScroll
scrollTop: `+=${pixelsToScroll}`,
}, {
duration: durationOfAnimationToShowFocusline,
queue: "scrollanimation"
}).dequeue("scrollanimation");
}
queue: 'scrollanimation',
}).dequeue('scrollanimation');
};
// scrollAmountWhenFocusLineIsOutOfViewport is set to 0 (default), scroll it the minimum distance
// needed to be completely in view. If the value is greater than 0 and less than or equal to 1,
// besides of scrolling the minimum needed to be visible, it scrolls additionally
// (viewport height * scrollAmountWhenFocusLineIsOutOfViewport) pixels
Scroll.prototype.scrollNodeVerticallyIntoView = function(rep, innerHeight) {
var viewport = this._getViewPortTopBottom();
var isPartOfRepLineOutOfViewport = this._partOfRepLineIsOutOfViewport(viewport, rep);
Scroll.prototype.scrollNodeVerticallyIntoView = function (rep, innerHeight) {
const viewport = this._getViewPortTopBottom();
const isPartOfRepLineOutOfViewport = this._partOfRepLineIsOutOfViewport(viewport, rep);
// when the selection changes outside of the viewport the browser automatically scrolls the line
// to inside of the viewport. Tested on IE, Firefox, Chrome in releases from 2015 until now
// So, when the line scrolled gets outside of the viewport we let the browser handle it.
var linePosition = caretPosition.getPosition();
if(linePosition){
var distanceOfTopOfViewport = linePosition.top - viewport.top;
var distanceOfBottomOfViewport = viewport.bottom - linePosition.bottom;
var caretIsAboveOfViewport = distanceOfTopOfViewport < 0;
var caretIsBelowOfViewport = distanceOfBottomOfViewport < 0;
if(caretIsAboveOfViewport){
const linePosition = caretPosition.getPosition();
if (linePosition) {
const distanceOfTopOfViewport = linePosition.top - viewport.top;
const distanceOfBottomOfViewport = viewport.bottom - linePosition.bottom;
const caretIsAboveOfViewport = distanceOfTopOfViewport < 0;
const caretIsBelowOfViewport = distanceOfBottomOfViewport < 0;
if (caretIsAboveOfViewport) {
var pixelsToScroll = distanceOfTopOfViewport - this._getPixelsRelativeToPercentageOfViewport(innerHeight, true);
this._scrollYPage(pixelsToScroll);
}else if(caretIsBelowOfViewport){
} else if (caretIsBelowOfViewport) {
var pixelsToScroll = -distanceOfBottomOfViewport + this._getPixelsRelativeToPercentageOfViewport(innerHeight);
this._scrollYPage(pixelsToScroll);
}else{
} else {
this.scrollWhenCaretIsInTheLastLineOfViewportWhenNecessary(rep, true, innerHeight);
}
}
}
};
Scroll.prototype._partOfRepLineIsOutOfViewport = function(viewportPosition, rep) {
var focusLine = (rep.selFocusAtStart ? rep.selStart[0] : rep.selEnd[0]);
var line = rep.lines.atIndex(focusLine);
var linePosition = this._getLineEntryTopBottom(line);
var lineIsAboveOfViewport = linePosition.top < viewportPosition.top;
var lineIsBelowOfViewport = linePosition.bottom > viewportPosition.bottom;
Scroll.prototype._partOfRepLineIsOutOfViewport = function (viewportPosition, rep) {
const focusLine = (rep.selFocusAtStart ? rep.selStart[0] : rep.selEnd[0]);
const line = rep.lines.atIndex(focusLine);
const linePosition = this._getLineEntryTopBottom(line);
const lineIsAboveOfViewport = linePosition.top < viewportPosition.top;
const lineIsBelowOfViewport = linePosition.bottom > viewportPosition.bottom;
return lineIsBelowOfViewport || lineIsAboveOfViewport;
}
};
Scroll.prototype._getLineEntryTopBottom = function(entry, destObj) {
var dom = entry.lineNode;
var top = dom.offsetTop;
var height = dom.offsetHeight;
var obj = (destObj || {});
Scroll.prototype._getLineEntryTopBottom = function (entry, destObj) {
const dom = entry.lineNode;
const top = dom.offsetTop;
const height = dom.offsetHeight;
const obj = (destObj || {});
obj.top = top;
obj.bottom = (top + height);
return obj;
}
};
Scroll.prototype._arrowUpWasPressedInTheFirstLineOfTheViewport = function(arrowUp, rep) {
var percentageScrollArrowUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp;
Scroll.prototype._arrowUpWasPressedInTheFirstLineOfTheViewport = function (arrowUp, rep) {
const percentageScrollArrowUp = this.scrollSettings.percentageToScrollWhenUserPressesArrowUp;
return percentageScrollArrowUp && arrowUp && this._isCaretAtTheTopOfViewport(rep);
}
};
Scroll.prototype.getVisibleLineRange = function(rep) {
var viewport = this._getViewPortTopBottom();
//console.log("viewport top/bottom: %o", viewport);
var obj = {};
var self = this;
var start = rep.lines.search(function(e) {
return self._getLineEntryTopBottom(e, obj).bottom > viewport.top;
});
var end = rep.lines.search(function(e) {
Scroll.prototype.getVisibleLineRange = function (rep) {
const viewport = this._getViewPortTopBottom();
// console.log("viewport top/bottom: %o", viewport);
const obj = {};
const self = this;
const start = rep.lines.search((e) => self._getLineEntryTopBottom(e, obj).bottom > viewport.top);
let end = rep.lines.search((e) =>
// return the first line that the top position is greater or equal than
// the viewport. That is the first line that is below the viewport bottom.
// So the line that is in the bottom of the viewport is the very previous one.
return self._getLineEntryTopBottom(e, obj).top >= viewport.bottom;
});
self._getLineEntryTopBottom(e, obj).top >= viewport.bottom,
);
if (end < start) end = start; // unlikely
// top.console.log(start+","+(end -1));
return [start, end - 1];
}
};
Scroll.prototype.getVisibleCharRange = function(rep) {
var lineRange = this.getVisibleLineRange(rep);
Scroll.prototype.getVisibleCharRange = function (rep) {
const lineRange = this.getVisibleLineRange(rep);
return [rep.lines.offsetOfIndex(lineRange[0]), rep.lines.offsetOfIndex(lineRange[1])];
}
};
exports.init = function(outerWin) {
exports.init = function (outerWin) {
return new Scroll(outerWin);
}
};

Some files were not shown because too many files have changed in this diff Show more