mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-22 08:26:16 -04:00
lint: Run eslint --fix
on src/
This commit is contained in:
parent
b8d07a42eb
commit
8e5fd19db2
109 changed files with 9061 additions and 10572 deletions
|
@ -18,19 +18,19 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
var customError = require("../utils/customError");
|
||||
var padManager = require("./PadManager");
|
||||
var padMessageHandler = require("../handler/PadMessageHandler");
|
||||
var readOnlyManager = require("./ReadOnlyManager");
|
||||
var groupManager = require("./GroupManager");
|
||||
var authorManager = require("./AuthorManager");
|
||||
var sessionManager = require("./SessionManager");
|
||||
var exportHtml = require("../utils/ExportHtml");
|
||||
var exportTxt = require("../utils/ExportTxt");
|
||||
var importHtml = require("../utils/ImportHtml");
|
||||
var cleanText = require("./Pad").cleanText;
|
||||
var PadDiff = require("../utils/padDiff");
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
const customError = require('../utils/customError');
|
||||
const padManager = require('./PadManager');
|
||||
const padMessageHandler = require('../handler/PadMessageHandler');
|
||||
const readOnlyManager = require('./ReadOnlyManager');
|
||||
const groupManager = require('./GroupManager');
|
||||
const authorManager = require('./AuthorManager');
|
||||
const sessionManager = require('./SessionManager');
|
||||
const exportHtml = require('../utils/ExportHtml');
|
||||
const exportTxt = require('../utils/ExportTxt');
|
||||
const importHtml = require('../utils/ImportHtml');
|
||||
const cleanText = require('./Pad').cleanText;
|
||||
const PadDiff = require('../utils/padDiff');
|
||||
|
||||
/* ********************
|
||||
* GROUP FUNCTIONS ****
|
||||
|
@ -101,10 +101,10 @@ Example returns:
|
|||
}
|
||||
|
||||
*/
|
||||
exports.getAttributePool = async function(padID) {
|
||||
let pad = await getPadSafe(padID, true);
|
||||
return { pool: pad.pool };
|
||||
}
|
||||
exports.getAttributePool = async function (padID) {
|
||||
const pad = await getPadSafe(padID, true);
|
||||
return {pool: pad.pool};
|
||||
};
|
||||
|
||||
/**
|
||||
getRevisionChangeset (padID, [rev])
|
||||
|
@ -119,22 +119,21 @@ Example returns:
|
|||
}
|
||||
|
||||
*/
|
||||
exports.getRevisionChangeset = async function(padID, rev) {
|
||||
exports.getRevisionChangeset = async function (padID, rev) {
|
||||
// try to parse the revision number
|
||||
if (rev !== undefined) {
|
||||
rev = checkValidRev(rev);
|
||||
}
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
let head = pad.getHeadRevisionNumber();
|
||||
const pad = await getPadSafe(padID, true);
|
||||
const head = pad.getHeadRevisionNumber();
|
||||
|
||||
// the client asked for a special revision
|
||||
if (rev !== undefined) {
|
||||
|
||||
// check if this is a valid revision
|
||||
if (rev > head) {
|
||||
throw new customError("rev is higher than the head revision of the pad", "apierror");
|
||||
throw new customError('rev is higher than the head revision of the pad', 'apierror');
|
||||
}
|
||||
|
||||
// get the changeset for this revision
|
||||
|
@ -143,7 +142,7 @@ exports.getRevisionChangeset = async function(padID, rev) {
|
|||
|
||||
// the client wants the latest changeset, lets return it to him
|
||||
return pad.getRevisionChangeset(head);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
getText(padID, [rev]) returns the text of a pad
|
||||
|
@ -153,33 +152,32 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {text:"Welcome Text"}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getText = async function(padID, rev) {
|
||||
exports.getText = async function (padID, rev) {
|
||||
// try to parse the revision number
|
||||
if (rev !== undefined) {
|
||||
rev = checkValidRev(rev);
|
||||
}
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
let head = pad.getHeadRevisionNumber();
|
||||
const pad = await getPadSafe(padID, true);
|
||||
const head = pad.getHeadRevisionNumber();
|
||||
|
||||
// the client asked for a special revision
|
||||
if (rev !== undefined) {
|
||||
|
||||
// check if this is a valid revision
|
||||
if (rev > head) {
|
||||
throw new customError("rev is higher than the head revision of the pad", "apierror");
|
||||
throw new customError('rev is higher than the head revision of the pad', 'apierror');
|
||||
}
|
||||
|
||||
// get the text of this revision
|
||||
let text = await pad.getInternalRevisionAText(rev);
|
||||
return { text };
|
||||
const text = await pad.getInternalRevisionAText(rev);
|
||||
return {text};
|
||||
}
|
||||
|
||||
// the client wants the latest text, lets return it to him
|
||||
let text = exportTxt.getTXTFromAtext(pad, pad.atext);
|
||||
return { text };
|
||||
}
|
||||
const text = exportTxt.getTXTFromAtext(pad, pad.atext);
|
||||
return {text};
|
||||
};
|
||||
|
||||
/**
|
||||
setText(padID, text) sets the text of a pad
|
||||
|
@ -190,20 +188,20 @@ Example returns:
|
|||
{code: 1, message:"padID does not exist", data: null}
|
||||
{code: 1, message:"text too long", data: null}
|
||||
*/
|
||||
exports.setText = async function(padID, text) {
|
||||
exports.setText = async function (padID, text) {
|
||||
// text is required
|
||||
if (typeof text !== "string") {
|
||||
throw new customError("text is not a string", "apierror");
|
||||
if (typeof text !== 'string') {
|
||||
throw new customError('text is not a string', 'apierror');
|
||||
}
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
|
||||
await Promise.all([
|
||||
pad.setText(text),
|
||||
padMessageHandler.updatePadClients(pad),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
appendText(padID, text) appends text to a pad
|
||||
|
@ -214,18 +212,18 @@ Example returns:
|
|||
{code: 1, message:"padID does not exist", data: null}
|
||||
{code: 1, message:"text too long", data: null}
|
||||
*/
|
||||
exports.appendText = async function(padID, text) {
|
||||
exports.appendText = async function (padID, text) {
|
||||
// text is required
|
||||
if (typeof text !== "string") {
|
||||
throw new customError("text is not a string", "apierror");
|
||||
if (typeof text !== 'string') {
|
||||
throw new customError('text is not a string', 'apierror');
|
||||
}
|
||||
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
await Promise.all([
|
||||
pad.appendText(text),
|
||||
padMessageHandler.updatePadClients(pad),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
getHTML(padID, [rev]) returns the html of a pad
|
||||
|
@ -235,19 +233,19 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {text:"Welcome <strong>Text</strong>"}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getHTML = async function(padID, rev) {
|
||||
exports.getHTML = async function (padID, rev) {
|
||||
if (rev !== undefined) {
|
||||
rev = checkValidRev(rev);
|
||||
}
|
||||
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
|
||||
// the client asked for a special revision
|
||||
if (rev !== undefined) {
|
||||
// check if this is a valid revision
|
||||
let head = pad.getHeadRevisionNumber();
|
||||
const head = pad.getHeadRevisionNumber();
|
||||
if (rev > head) {
|
||||
throw new customError("rev is higher than the head revision of the pad", "apierror");
|
||||
throw new customError('rev is higher than the head revision of the pad', 'apierror');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,9 +253,9 @@ exports.getHTML = async function(padID, rev) {
|
|||
let html = await exportHtml.getPadHTML(pad, rev);
|
||||
|
||||
// wrap the HTML
|
||||
html = "<!DOCTYPE HTML><html><body>" + html + "</body></html>";
|
||||
return { html };
|
||||
}
|
||||
html = `<!DOCTYPE HTML><html><body>${html}</body></html>`;
|
||||
return {html};
|
||||
};
|
||||
|
||||
/**
|
||||
setHTML(padID, html) sets the text of a pad based on HTML
|
||||
|
@ -267,20 +265,20 @@ Example returns:
|
|||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.setHTML = async function(padID, html) {
|
||||
exports.setHTML = async function (padID, html) {
|
||||
// html string is required
|
||||
if (typeof html !== "string") {
|
||||
throw new customError("html is not a string", "apierror");
|
||||
if (typeof html !== 'string') {
|
||||
throw new customError('html is not a string', 'apierror');
|
||||
}
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
|
||||
// add a new changeset with the new html to the pad
|
||||
try {
|
||||
await importHtml.setPadHTML(pad, cleanText(html));
|
||||
} catch (e) {
|
||||
throw new customError("HTML is malformed", "apierror");
|
||||
throw new customError('HTML is malformed', 'apierror');
|
||||
}
|
||||
|
||||
// update the clients on the pad
|
||||
|
@ -303,23 +301,23 @@ Example returns:
|
|||
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getChatHistory = async function(padID, start, end) {
|
||||
exports.getChatHistory = async function (padID, start, end) {
|
||||
if (start && end) {
|
||||
if (start < 0) {
|
||||
throw new customError("start is below zero", "apierror");
|
||||
throw new customError('start is below zero', 'apierror');
|
||||
}
|
||||
if (end < 0) {
|
||||
throw new customError("end is below zero", "apierror");
|
||||
throw new customError('end is below zero', 'apierror');
|
||||
}
|
||||
if (start > end) {
|
||||
throw new customError("start is higher than end", "apierror");
|
||||
throw new customError('start is higher than end', 'apierror');
|
||||
}
|
||||
}
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
|
||||
var chatHead = pad.chatHead;
|
||||
const chatHead = pad.chatHead;
|
||||
|
||||
// fall back to getting the whole chat-history if a parameter is missing
|
||||
if (!start || !end) {
|
||||
|
@ -328,17 +326,17 @@ exports.getChatHistory = async function(padID, start, end) {
|
|||
}
|
||||
|
||||
if (start > chatHead) {
|
||||
throw new customError("start is higher than the current chatHead", "apierror");
|
||||
throw new customError('start is higher than the current chatHead', 'apierror');
|
||||
}
|
||||
if (end > chatHead) {
|
||||
throw new customError("end is higher than the current chatHead", "apierror");
|
||||
throw new customError('end is higher than the current chatHead', 'apierror');
|
||||
}
|
||||
|
||||
// the the whole message-log and return it to the client
|
||||
let messages = await pad.getChatMessages(start, end);
|
||||
const messages = await pad.getChatMessages(start, end);
|
||||
|
||||
return { messages };
|
||||
}
|
||||
return {messages};
|
||||
};
|
||||
|
||||
/**
|
||||
appendChatMessage(padID, text, authorID, time), creates a chat message for the pad id, time is a timestamp
|
||||
|
@ -348,10 +346,10 @@ Example returns:
|
|||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.appendChatMessage = async function(padID, text, authorID, time) {
|
||||
exports.appendChatMessage = async function (padID, text, authorID, time) {
|
||||
// text is required
|
||||
if (typeof text !== "string") {
|
||||
throw new customError("text is not a string", "apierror");
|
||||
if (typeof text !== 'string') {
|
||||
throw new customError('text is not a string', 'apierror');
|
||||
}
|
||||
|
||||
// if time is not an integer value set time to current timestamp
|
||||
|
@ -363,7 +361,7 @@ exports.appendChatMessage = async function(padID, text, authorID, time) {
|
|||
|
||||
// save chat message to database and send message to all connected clients
|
||||
await padMessageHandler.sendChatMessageToPadClients(time, authorID, text, padID);
|
||||
}
|
||||
};
|
||||
|
||||
/* ***************
|
||||
* PAD FUNCTIONS *
|
||||
|
@ -377,11 +375,11 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {revisions: 56}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getRevisionsCount = async function(padID) {
|
||||
exports.getRevisionsCount = async function (padID) {
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
return { revisions: pad.getHeadRevisionNumber() };
|
||||
}
|
||||
const pad = await getPadSafe(padID, true);
|
||||
return {revisions: pad.getHeadRevisionNumber()};
|
||||
};
|
||||
|
||||
/**
|
||||
getSavedRevisionsCount(padID) returns the number of saved revisions of this pad
|
||||
|
@ -391,11 +389,11 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {savedRevisions: 42}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getSavedRevisionsCount = async function(padID) {
|
||||
exports.getSavedRevisionsCount = async function (padID) {
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
return { savedRevisions: pad.getSavedRevisionsNumber() };
|
||||
}
|
||||
const pad = await getPadSafe(padID, true);
|
||||
return {savedRevisions: pad.getSavedRevisionsNumber()};
|
||||
};
|
||||
|
||||
/**
|
||||
listSavedRevisions(padID) returns the list of saved revisions of this pad
|
||||
|
@ -405,11 +403,11 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.listSavedRevisions = async function(padID) {
|
||||
exports.listSavedRevisions = async function (padID) {
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
return { savedRevisions: pad.getSavedRevisionsList() };
|
||||
}
|
||||
const pad = await getPadSafe(padID, true);
|
||||
return {savedRevisions: pad.getSavedRevisionsList()};
|
||||
};
|
||||
|
||||
/**
|
||||
saveRevision(padID) returns the list of saved revisions of this pad
|
||||
|
@ -419,28 +417,28 @@ Example returns:
|
|||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.saveRevision = async function(padID, rev) {
|
||||
exports.saveRevision = async function (padID, rev) {
|
||||
// check if rev is a number
|
||||
if (rev !== undefined) {
|
||||
rev = checkValidRev(rev);
|
||||
}
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
let head = pad.getHeadRevisionNumber();
|
||||
const pad = await getPadSafe(padID, true);
|
||||
const head = pad.getHeadRevisionNumber();
|
||||
|
||||
// the client asked for a special revision
|
||||
if (rev !== undefined) {
|
||||
if (rev > head) {
|
||||
throw new customError("rev is higher than the head revision of the pad", "apierror");
|
||||
throw new customError('rev is higher than the head revision of the pad', 'apierror');
|
||||
}
|
||||
} else {
|
||||
rev = pad.getHeadRevisionNumber();
|
||||
}
|
||||
|
||||
let author = await authorManager.createAuthor('API');
|
||||
const author = await authorManager.createAuthor('API');
|
||||
await pad.addSavedRevision(rev, author.authorID, 'Saved through API call');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
getLastEdited(padID) returns the timestamp of the last revision of the pad
|
||||
|
@ -450,12 +448,12 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {lastEdited: 1340815946602}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getLastEdited = async function(padID) {
|
||||
exports.getLastEdited = async function (padID) {
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
let lastEdited = await pad.getLastEdit();
|
||||
return { lastEdited };
|
||||
}
|
||||
const pad = await getPadSafe(padID, true);
|
||||
const lastEdited = await pad.getLastEdit();
|
||||
return {lastEdited};
|
||||
};
|
||||
|
||||
/**
|
||||
createPad(padName [, text]) creates a new pad in this group
|
||||
|
@ -465,22 +463,22 @@ Example returns:
|
|||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"pad does already exist", data: null}
|
||||
*/
|
||||
exports.createPad = async function(padID, text) {
|
||||
exports.createPad = async function (padID, text) {
|
||||
if (padID) {
|
||||
// ensure there is no $ in the padID
|
||||
if (padID.indexOf("$") !== -1) {
|
||||
throw new customError("createPad can't create group pads", "apierror");
|
||||
if (padID.indexOf('$') !== -1) {
|
||||
throw new customError("createPad can't create group pads", 'apierror');
|
||||
}
|
||||
|
||||
// check for url special characters
|
||||
if (padID.match(/(\/|\?|&|#)/)) {
|
||||
throw new customError("malformed padID: Remove special characters", "apierror");
|
||||
throw new customError('malformed padID: Remove special characters', 'apierror');
|
||||
}
|
||||
}
|
||||
|
||||
// create pad
|
||||
await getPadSafe(padID, false, text);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
deletePad(padID) deletes a pad
|
||||
|
@ -490,10 +488,10 @@ Example returns:
|
|||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.deletePad = async function(padID) {
|
||||
let pad = await getPadSafe(padID, true);
|
||||
exports.deletePad = async function (padID) {
|
||||
const pad = await getPadSafe(padID, true);
|
||||
await pad.remove();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
restoreRevision(padID, [rev]) Restores revision from past as new changeset
|
||||
|
@ -503,34 +501,34 @@ exports.deletePad = async function(padID) {
|
|||
{code:0, message:"ok", data:null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.restoreRevision = async function(padID, rev) {
|
||||
exports.restoreRevision = async function (padID, rev) {
|
||||
// check if rev is a number
|
||||
if (rev === undefined) {
|
||||
throw new customError("rev is not defined", "apierror");
|
||||
throw new customError('rev is not defined', 'apierror');
|
||||
}
|
||||
rev = checkValidRev(rev);
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
|
||||
// check if this is a valid revision
|
||||
if (rev > pad.getHeadRevisionNumber()) {
|
||||
throw new customError("rev is higher than the head revision of the pad", "apierror");
|
||||
throw new customError('rev is higher than the head revision of the pad', 'apierror');
|
||||
}
|
||||
|
||||
let atext = await pad.getInternalRevisionAText(rev);
|
||||
const atext = await pad.getInternalRevisionAText(rev);
|
||||
|
||||
var oldText = pad.text();
|
||||
atext.text += "\n";
|
||||
const oldText = pad.text();
|
||||
atext.text += '\n';
|
||||
|
||||
function eachAttribRun(attribs, func) {
|
||||
var attribsIter = Changeset.opIterator(attribs);
|
||||
var textIndex = 0;
|
||||
var newTextStart = 0;
|
||||
var newTextEnd = atext.text.length;
|
||||
const attribsIter = Changeset.opIterator(attribs);
|
||||
let textIndex = 0;
|
||||
const newTextStart = 0;
|
||||
const newTextEnd = atext.text.length;
|
||||
while (attribsIter.hasNext()) {
|
||||
var op = attribsIter.next();
|
||||
var nextIndex = textIndex + op.chars;
|
||||
const op = attribsIter.next();
|
||||
const nextIndex = textIndex + op.chars;
|
||||
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
|
||||
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
|
||||
}
|
||||
|
@ -539,14 +537,14 @@ exports.restoreRevision = async function(padID, rev) {
|
|||
}
|
||||
|
||||
// create a new changeset with a helper builder object
|
||||
var builder = Changeset.builder(oldText.length);
|
||||
const builder = Changeset.builder(oldText.length);
|
||||
|
||||
// assemble each line into the builder
|
||||
eachAttribRun(atext.attribs, function(start, end, attribs) {
|
||||
eachAttribRun(atext.attribs, (start, end, attribs) => {
|
||||
builder.insert(atext.text.substring(start, end), attribs);
|
||||
});
|
||||
|
||||
var lastNewlinePos = oldText.lastIndexOf('\n');
|
||||
const lastNewlinePos = oldText.lastIndexOf('\n');
|
||||
if (lastNewlinePos < 0) {
|
||||
builder.remove(oldText.length - 1, 0);
|
||||
} else {
|
||||
|
@ -554,13 +552,13 @@ exports.restoreRevision = async function(padID, rev) {
|
|||
builder.remove(oldText.length - lastNewlinePos - 1, 0);
|
||||
}
|
||||
|
||||
var changeset = builder.toString();
|
||||
const changeset = builder.toString();
|
||||
|
||||
await Promise.all([
|
||||
pad.appendRevision(changeset),
|
||||
padMessageHandler.updatePadClients(pad),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true,
|
||||
|
@ -571,10 +569,10 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {padID: destinationID}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.copyPad = async function(sourceID, destinationID, force) {
|
||||
let pad = await getPadSafe(sourceID, true);
|
||||
exports.copyPad = async function (sourceID, destinationID, force) {
|
||||
const pad = await getPadSafe(sourceID, true);
|
||||
await pad.copy(destinationID, force);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
copyPadWithoutHistory(sourceID, destinationID[, force=false]) copies a pad. If force is true,
|
||||
|
@ -585,10 +583,10 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {padID: destinationID}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.copyPadWithoutHistory = async function(sourceID, destinationID, force) {
|
||||
let pad = await getPadSafe(sourceID, true);
|
||||
exports.copyPadWithoutHistory = async function (sourceID, destinationID, force) {
|
||||
const pad = await getPadSafe(sourceID, true);
|
||||
await pad.copyPadWithoutHistory(destinationID, force);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
movePad(sourceID, destinationID[, force=false]) moves a pad. If force is true,
|
||||
|
@ -599,11 +597,11 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {padID: destinationID}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.movePad = async function(sourceID, destinationID, force) {
|
||||
let pad = await getPadSafe(sourceID, true);
|
||||
exports.movePad = async function (sourceID, destinationID, force) {
|
||||
const pad = await getPadSafe(sourceID, true);
|
||||
await pad.copy(destinationID, force);
|
||||
await pad.remove();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
getReadOnlyLink(padID) returns the read only link of a pad
|
||||
|
@ -613,15 +611,15 @@ Example returns:
|
|||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getReadOnlyID = async function(padID) {
|
||||
exports.getReadOnlyID = async function (padID) {
|
||||
// we don't need the pad object, but this function does all the security stuff for us
|
||||
await getPadSafe(padID, true);
|
||||
|
||||
// get the readonlyId
|
||||
let readOnlyID = await readOnlyManager.getReadOnlyId(padID);
|
||||
const readOnlyID = await readOnlyManager.getReadOnlyId(padID);
|
||||
|
||||
return { readOnlyID };
|
||||
}
|
||||
return {readOnlyID};
|
||||
};
|
||||
|
||||
/**
|
||||
getPadID(roID) returns the padID of a pad based on the readonlyID(roID)
|
||||
|
@ -631,15 +629,15 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {padID: padID}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getPadID = async function(roID) {
|
||||
exports.getPadID = async function (roID) {
|
||||
// get the PadId
|
||||
let padID = await readOnlyManager.getPadId(roID);
|
||||
const padID = await readOnlyManager.getPadId(roID);
|
||||
if (padID === null) {
|
||||
throw new customError("padID does not exist", "apierror");
|
||||
throw new customError('padID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
return { padID };
|
||||
}
|
||||
return {padID};
|
||||
};
|
||||
|
||||
/**
|
||||
setPublicStatus(padID, publicStatus) sets a boolean for the public status of a pad
|
||||
|
@ -649,20 +647,20 @@ Example returns:
|
|||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.setPublicStatus = async function(padID, publicStatus) {
|
||||
exports.setPublicStatus = async function (padID, publicStatus) {
|
||||
// ensure this is a group pad
|
||||
checkGroupPad(padID, "publicStatus");
|
||||
checkGroupPad(padID, 'publicStatus');
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
|
||||
// convert string to boolean
|
||||
if (typeof publicStatus === "string") {
|
||||
publicStatus = (publicStatus.toLowerCase() === "true");
|
||||
if (typeof publicStatus === 'string') {
|
||||
publicStatus = (publicStatus.toLowerCase() === 'true');
|
||||
}
|
||||
|
||||
await pad.setPublicStatus(publicStatus);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
getPublicStatus(padID) return true of false
|
||||
|
@ -672,14 +670,14 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {publicStatus: true}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getPublicStatus = async function(padID) {
|
||||
exports.getPublicStatus = async function (padID) {
|
||||
// ensure this is a group pad
|
||||
checkGroupPad(padID, "publicStatus");
|
||||
checkGroupPad(padID, 'publicStatus');
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
return { publicStatus: pad.getPublicStatus() };
|
||||
}
|
||||
const pad = await getPadSafe(padID, true);
|
||||
return {publicStatus: pad.getPublicStatus()};
|
||||
};
|
||||
|
||||
/**
|
||||
listAuthorsOfPad(padID) returns an array of authors who contributed to this pad
|
||||
|
@ -689,12 +687,12 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.listAuthorsOfPad = async function(padID) {
|
||||
exports.listAuthorsOfPad = async function (padID) {
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
let authorIDs = pad.getAllAuthors();
|
||||
return { authorIDs };
|
||||
}
|
||||
const pad = await getPadSafe(padID, true);
|
||||
const authorIDs = pad.getAllAuthors();
|
||||
return {authorIDs};
|
||||
};
|
||||
|
||||
/**
|
||||
sendClientsMessage(padID, msg) sends a message to all clients connected to the
|
||||
|
@ -719,10 +717,10 @@ Example returns:
|
|||
{code: 1, message:"padID does not exist"}
|
||||
*/
|
||||
|
||||
exports.sendClientsMessage = async function(padID, msg) {
|
||||
let pad = await getPadSafe(padID, true);
|
||||
exports.sendClientsMessage = async function (padID, msg) {
|
||||
const pad = await getPadSafe(padID, true);
|
||||
padMessageHandler.handleCustomMessage(padID, msg);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
checkToken() returns ok when the current api token is valid
|
||||
|
@ -732,8 +730,8 @@ Example returns:
|
|||
{"code":0,"message":"ok","data":null}
|
||||
{"code":4,"message":"no or wrong API Key","data":null}
|
||||
*/
|
||||
exports.checkToken = async function() {
|
||||
}
|
||||
exports.checkToken = async function () {
|
||||
};
|
||||
|
||||
/**
|
||||
getChatHead(padID) returns the chatHead (last number of the last chat-message) of the pad
|
||||
|
@ -743,11 +741,11 @@ Example returns:
|
|||
{code: 0, message:"ok", data: {chatHead: 42}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getChatHead = async function(padID) {
|
||||
exports.getChatHead = async function (padID) {
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
return { chatHead: pad.chatHead };
|
||||
}
|
||||
const pad = await getPadSafe(padID, true);
|
||||
return {chatHead: pad.chatHead};
|
||||
};
|
||||
|
||||
/**
|
||||
createDiffHTML(padID, startRev, endRev) returns an object of diffs from 2 points in a pad
|
||||
|
@ -757,8 +755,7 @@ Example returns:
|
|||
{"code":0,"message":"ok","data":{"html":"<style>\n.authora_HKIv23mEbachFYfH {background-color: #a979d9}\n.authora_n4gEeMLsv1GivNeh {background-color: #a9b5d9}\n.removed {text-decoration: line-through; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; filter: alpha(opacity=80); opacity: 0.8; }\n</style>Welcome to Etherpad!<br><br>This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!<br><br>Get involved with Etherpad at <a href=\"http://etherpad.org\">http://etherpad.org</a><br><span class=\"authora_HKIv23mEbachFYfH\">aw</span><br><br>","authors":["a.HKIv23mEbachFYfH",""]}}
|
||||
{"code":4,"message":"no or wrong API Key","data":null}
|
||||
*/
|
||||
exports.createDiffHTML = async function(padID, startRev, endRev) {
|
||||
|
||||
exports.createDiffHTML = async function (padID, startRev, endRev) {
|
||||
// check if startRev is a number
|
||||
if (startRev !== undefined) {
|
||||
startRev = checkValidRev(startRev);
|
||||
|
@ -770,18 +767,18 @@ exports.createDiffHTML = async function(padID, startRev, endRev) {
|
|||
}
|
||||
|
||||
// get the pad
|
||||
let pad = await getPadSafe(padID, true);
|
||||
const pad = await getPadSafe(padID, true);
|
||||
try {
|
||||
var padDiff = new PadDiff(pad, startRev, endRev);
|
||||
} catch (e) {
|
||||
throw { stop: e.message };
|
||||
throw {stop: e.message};
|
||||
}
|
||||
|
||||
let html = await padDiff.getHtml();
|
||||
let authors = await padDiff.getAuthors();
|
||||
const html = await padDiff.getHtml();
|
||||
const authors = await padDiff.getAuthors();
|
||||
|
||||
return { html, authors };
|
||||
}
|
||||
return {html, authors};
|
||||
};
|
||||
|
||||
/* ********************
|
||||
** GLOBAL FUNCTIONS **
|
||||
|
@ -796,20 +793,20 @@ exports.createDiffHTML = async function(padID, startRev, endRev) {
|
|||
{"code":4,"message":"no or wrong API Key","data":null}
|
||||
*/
|
||||
|
||||
exports.getStats = async function() {
|
||||
exports.getStats = async function () {
|
||||
const sessionInfos = padMessageHandler.sessioninfos;
|
||||
|
||||
const sessionKeys = Object.keys(sessionInfos);
|
||||
const activePads = new Set(Object.entries(sessionInfos).map(k => k[1].padId));
|
||||
const activePads = new Set(Object.entries(sessionInfos).map((k) => k[1].padId));
|
||||
|
||||
const { padIDs } = await padManager.listAllPads();
|
||||
const {padIDs} = await padManager.listAllPads();
|
||||
|
||||
return {
|
||||
totalPads: padIDs.length,
|
||||
totalSessions: sessionKeys.length,
|
||||
totalActivePads: activePads.size,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/* ****************************
|
||||
** INTERNAL HELPER FUNCTIONS *
|
||||
|
@ -817,32 +814,32 @@ exports.getStats = async function() {
|
|||
|
||||
// checks if a number is an int
|
||||
function is_int(value) {
|
||||
return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value)
|
||||
return (parseFloat(value) == parseInt(value, 10)) && !isNaN(value);
|
||||
}
|
||||
|
||||
// gets a pad safe
|
||||
async function getPadSafe(padID, shouldExist, text) {
|
||||
// check if padID is a string
|
||||
if (typeof padID !== "string") {
|
||||
throw new customError("padID is not a string", "apierror");
|
||||
if (typeof padID !== 'string') {
|
||||
throw new customError('padID is not a string', 'apierror');
|
||||
}
|
||||
|
||||
// check if the padID maches the requirements
|
||||
if (!padManager.isValidPadId(padID)) {
|
||||
throw new customError("padID did not match requirements", "apierror");
|
||||
throw new customError('padID did not match requirements', 'apierror');
|
||||
}
|
||||
|
||||
// check if the pad exists
|
||||
let exists = await padManager.doesPadExists(padID);
|
||||
const exists = await padManager.doesPadExists(padID);
|
||||
|
||||
if (!exists && shouldExist) {
|
||||
// does not exist, but should
|
||||
throw new customError("padID does not exist", "apierror");
|
||||
throw new customError('padID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
if (exists && !shouldExist) {
|
||||
// does exist, but shouldn't
|
||||
throw new customError("padID does already exist", "apierror");
|
||||
throw new customError('padID does already exist', 'apierror');
|
||||
}
|
||||
|
||||
// pad exists, let's get it
|
||||
|
@ -852,23 +849,23 @@ async function getPadSafe(padID, shouldExist, text) {
|
|||
// checks if a rev is a legal number
|
||||
// pre-condition is that `rev` is not undefined
|
||||
function checkValidRev(rev) {
|
||||
if (typeof rev !== "number") {
|
||||
if (typeof rev !== 'number') {
|
||||
rev = parseInt(rev, 10);
|
||||
}
|
||||
|
||||
// check if rev is a number
|
||||
if (isNaN(rev)) {
|
||||
throw new customError("rev is not a number", "apierror");
|
||||
throw new customError('rev is not a number', 'apierror');
|
||||
}
|
||||
|
||||
// ensure this is not a negative number
|
||||
if (rev < 0) {
|
||||
throw new customError("rev is not a negative number", "apierror");
|
||||
throw new customError('rev is not a negative number', 'apierror');
|
||||
}
|
||||
|
||||
// ensure this is not a float value
|
||||
if (!is_int(rev)) {
|
||||
throw new customError("rev is a float value", "apierror");
|
||||
throw new customError('rev is a float value', 'apierror');
|
||||
}
|
||||
|
||||
return rev;
|
||||
|
@ -877,7 +874,7 @@ function checkValidRev(rev) {
|
|||
// checks if a padID is part of a group
|
||||
function checkGroupPad(padID, field) {
|
||||
// ensure this is a group pad
|
||||
if (padID && padID.indexOf("$") === -1) {
|
||||
throw new customError(`You can only get/set the ${field} of pads that belong to a group`, "apierror");
|
||||
if (padID && padID.indexOf('$') === -1) {
|
||||
throw new customError(`You can only get/set the ${field} of pads that belong to a group`, 'apierror');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,31 +18,87 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var db = require("./DB");
|
||||
var customError = require("../utils/customError");
|
||||
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
const db = require('./DB');
|
||||
const customError = require('../utils/customError');
|
||||
const randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
|
||||
exports.getColorPalette = function() {
|
||||
exports.getColorPalette = function () {
|
||||
return [
|
||||
"#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1",
|
||||
"#ffa8a8", "#ffe699", "#cfff9e", "#99ffb3", "#a3ffff", "#99b3ff", "#cc99ff", "#ff99e5",
|
||||
"#e7b1b1", "#e9dcAf", "#cde9af", "#bfedcc", "#b1e7e7", "#c3cdee", "#d2b8ea", "#eec3e6",
|
||||
"#e9cece", "#e7e0ca", "#d3e5c7", "#bce1c5", "#c1e2e2", "#c1c9e2", "#cfc1e2", "#e0bdd9",
|
||||
"#baded3", "#a0f8eb", "#b1e7e0", "#c3c8e4", "#cec5e2", "#b1d5e7", "#cda8f0", "#f0f0a8",
|
||||
"#f2f2a6", "#f5a8eb", "#c5f9a9", "#ececbb", "#e7c4bc", "#daf0b2", "#b0a0fd", "#bce2e7",
|
||||
"#cce2bb", "#ec9afe", "#edabbd", "#aeaeea", "#c4e7b1", "#d722bb", "#f3a5e7", "#ffa8a8",
|
||||
"#d8c0c5", "#eaaedd", "#adc6eb", "#bedad1", "#dee9af", "#e9afc2", "#f8d2a0", "#b3b3e6"
|
||||
'#ffc7c7',
|
||||
'#fff1c7',
|
||||
'#e3ffc7',
|
||||
'#c7ffd5',
|
||||
'#c7ffff',
|
||||
'#c7d5ff',
|
||||
'#e3c7ff',
|
||||
'#ffc7f1',
|
||||
'#ffa8a8',
|
||||
'#ffe699',
|
||||
'#cfff9e',
|
||||
'#99ffb3',
|
||||
'#a3ffff',
|
||||
'#99b3ff',
|
||||
'#cc99ff',
|
||||
'#ff99e5',
|
||||
'#e7b1b1',
|
||||
'#e9dcAf',
|
||||
'#cde9af',
|
||||
'#bfedcc',
|
||||
'#b1e7e7',
|
||||
'#c3cdee',
|
||||
'#d2b8ea',
|
||||
'#eec3e6',
|
||||
'#e9cece',
|
||||
'#e7e0ca',
|
||||
'#d3e5c7',
|
||||
'#bce1c5',
|
||||
'#c1e2e2',
|
||||
'#c1c9e2',
|
||||
'#cfc1e2',
|
||||
'#e0bdd9',
|
||||
'#baded3',
|
||||
'#a0f8eb',
|
||||
'#b1e7e0',
|
||||
'#c3c8e4',
|
||||
'#cec5e2',
|
||||
'#b1d5e7',
|
||||
'#cda8f0',
|
||||
'#f0f0a8',
|
||||
'#f2f2a6',
|
||||
'#f5a8eb',
|
||||
'#c5f9a9',
|
||||
'#ececbb',
|
||||
'#e7c4bc',
|
||||
'#daf0b2',
|
||||
'#b0a0fd',
|
||||
'#bce2e7',
|
||||
'#cce2bb',
|
||||
'#ec9afe',
|
||||
'#edabbd',
|
||||
'#aeaeea',
|
||||
'#c4e7b1',
|
||||
'#d722bb',
|
||||
'#f3a5e7',
|
||||
'#ffa8a8',
|
||||
'#d8c0c5',
|
||||
'#eaaedd',
|
||||
'#adc6eb',
|
||||
'#bedad1',
|
||||
'#dee9af',
|
||||
'#e9afc2',
|
||||
'#f8d2a0',
|
||||
'#b3b3e6',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the author exists
|
||||
*/
|
||||
exports.doesAuthorExist = async function(authorID) {
|
||||
let author = await db.get("globalAuthor:" + authorID);
|
||||
exports.doesAuthorExist = async function (authorID) {
|
||||
const author = await db.get(`globalAuthor:${authorID}`);
|
||||
|
||||
return author !== null;
|
||||
}
|
||||
};
|
||||
|
||||
/* exported for backwards compatibility */
|
||||
exports.doesAuthorExists = exports.doesAuthorExist;
|
||||
|
@ -51,20 +107,20 @@ exports.doesAuthorExists = exports.doesAuthorExist;
|
|||
* Returns the AuthorID for a token.
|
||||
* @param {String} token The token
|
||||
*/
|
||||
exports.getAuthor4Token = async function(token) {
|
||||
let author = await mapAuthorWithDBKey("token2author", token);
|
||||
exports.getAuthor4Token = async function (token) {
|
||||
const author = await mapAuthorWithDBKey('token2author', token);
|
||||
|
||||
// return only the sub value authorID
|
||||
return author ? author.authorID : author;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the AuthorID for a mapper.
|
||||
* @param {String} token The mapper
|
||||
* @param {String} name The name of the author (optional)
|
||||
*/
|
||||
exports.createAuthorIfNotExistsFor = async function(authorMapper, name) {
|
||||
let author = await mapAuthorWithDBKey("mapper2author", authorMapper);
|
||||
exports.createAuthorIfNotExistsFor = async function (authorMapper, name) {
|
||||
const author = await mapAuthorWithDBKey('mapper2author', authorMapper);
|
||||
|
||||
if (name) {
|
||||
// set the name of this author
|
||||
|
@ -80,16 +136,16 @@ exports.createAuthorIfNotExistsFor = async function(authorMapper, name) {
|
|||
* @param {String} mapperkey The database key name for this mapper
|
||||
* @param {String} mapper The mapper
|
||||
*/
|
||||
async function mapAuthorWithDBKey (mapperkey, mapper) {
|
||||
async function mapAuthorWithDBKey(mapperkey, mapper) {
|
||||
// try to map to an author
|
||||
let author = await db.get(mapperkey + ":" + mapper);
|
||||
const author = await db.get(`${mapperkey}:${mapper}`);
|
||||
|
||||
if (author === null) {
|
||||
// there is no author with this mapper, so create one
|
||||
let author = await exports.createAuthor(null);
|
||||
const author = await exports.createAuthor(null);
|
||||
|
||||
// create the token2author relation
|
||||
await db.set(mapperkey + ":" + mapper, author.authorID);
|
||||
await db.set(`${mapperkey}:${mapper}`, author.authorID);
|
||||
|
||||
// return the author
|
||||
return author;
|
||||
|
@ -97,109 +153,109 @@ async function mapAuthorWithDBKey (mapperkey, mapper) {
|
|||
|
||||
// there is an author with this mapper
|
||||
// update the timestamp of this author
|
||||
await db.setSub("globalAuthor:" + author, ["timestamp"], Date.now());
|
||||
await db.setSub(`globalAuthor:${author}`, ['timestamp'], Date.now());
|
||||
|
||||
// return the author
|
||||
return { authorID: author};
|
||||
return {authorID: author};
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function that creates the database entry for an author
|
||||
* @param {String} name The name of the author
|
||||
*/
|
||||
exports.createAuthor = function(name) {
|
||||
exports.createAuthor = function (name) {
|
||||
// create the new author name
|
||||
let author = "a." + randomString(16);
|
||||
const author = `a.${randomString(16)}`;
|
||||
|
||||
// create the globalAuthors db entry
|
||||
let authorObj = {
|
||||
"colorId": Math.floor(Math.random() * (exports.getColorPalette().length)),
|
||||
"name": name,
|
||||
"timestamp": Date.now()
|
||||
const authorObj = {
|
||||
colorId: Math.floor(Math.random() * (exports.getColorPalette().length)),
|
||||
name,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
// set the global author db entry
|
||||
// NB: no await, since we're not waiting for the DB set to finish
|
||||
db.set("globalAuthor:" + author, authorObj);
|
||||
db.set(`globalAuthor:${author}`, authorObj);
|
||||
|
||||
return { authorID: author };
|
||||
}
|
||||
return {authorID: author};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the Author Obj of the author
|
||||
* @param {String} author The id of the author
|
||||
*/
|
||||
exports.getAuthor = function(author) {
|
||||
exports.getAuthor = function (author) {
|
||||
// NB: result is already a Promise
|
||||
return db.get("globalAuthor:" + author);
|
||||
}
|
||||
return db.get(`globalAuthor:${author}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the color Id of the author
|
||||
* @param {String} author The id of the author
|
||||
*/
|
||||
exports.getAuthorColorId = function(author) {
|
||||
return db.getSub("globalAuthor:" + author, ["colorId"]);
|
||||
}
|
||||
exports.getAuthorColorId = function (author) {
|
||||
return db.getSub(`globalAuthor:${author}`, ['colorId']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the color Id of the author
|
||||
* @param {String} author The id of the author
|
||||
* @param {String} colorId The color id of the author
|
||||
*/
|
||||
exports.setAuthorColorId = function(author, colorId) {
|
||||
return db.setSub("globalAuthor:" + author, ["colorId"], colorId);
|
||||
}
|
||||
exports.setAuthorColorId = function (author, colorId) {
|
||||
return db.setSub(`globalAuthor:${author}`, ['colorId'], colorId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the name of the author
|
||||
* @param {String} author The id of the author
|
||||
*/
|
||||
exports.getAuthorName = function(author) {
|
||||
return db.getSub("globalAuthor:" + author, ["name"]);
|
||||
}
|
||||
exports.getAuthorName = function (author) {
|
||||
return db.getSub(`globalAuthor:${author}`, ['name']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the name of the author
|
||||
* @param {String} author The id of the author
|
||||
* @param {String} name The name of the author
|
||||
*/
|
||||
exports.setAuthorName = function(author, name) {
|
||||
return db.setSub("globalAuthor:" + author, ["name"], name);
|
||||
}
|
||||
exports.setAuthorName = function (author, name) {
|
||||
return db.setSub(`globalAuthor:${author}`, ['name'], name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of all pads this author contributed to
|
||||
* @param {String} author The id of the author
|
||||
*/
|
||||
exports.listPadsOfAuthor = async function(authorID) {
|
||||
exports.listPadsOfAuthor = async function (authorID) {
|
||||
/* There are two other places where this array is manipulated:
|
||||
* (1) When the author is added to a pad, the author object is also updated
|
||||
* (2) When a pad is deleted, each author of that pad is also updated
|
||||
*/
|
||||
|
||||
// get the globalAuthor
|
||||
let author = await db.get("globalAuthor:" + authorID);
|
||||
const author = await db.get(`globalAuthor:${authorID}`);
|
||||
|
||||
if (author === null) {
|
||||
// author does not exist
|
||||
throw new customError("authorID does not exist", "apierror");
|
||||
throw new customError('authorID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// everything is fine, return the pad IDs
|
||||
let padIDs = Object.keys(author.padIDs || {});
|
||||
const padIDs = Object.keys(author.padIDs || {});
|
||||
|
||||
return { padIDs };
|
||||
}
|
||||
return {padIDs};
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new pad to the list of contributions
|
||||
* @param {String} author The id of the author
|
||||
* @param {String} padID The id of the pad the author contributes to
|
||||
*/
|
||||
exports.addPad = async function(authorID, padID) {
|
||||
exports.addPad = async function (authorID, padID) {
|
||||
// get the entry
|
||||
let author = await db.get("globalAuthor:" + authorID);
|
||||
const author = await db.get(`globalAuthor:${authorID}`);
|
||||
|
||||
if (author === null) return;
|
||||
|
||||
|
@ -216,22 +272,22 @@ exports.addPad = async function(authorID, padID) {
|
|||
author.padIDs[padID] = 1; // anything, because value is not used
|
||||
|
||||
// save the new element back
|
||||
db.set("globalAuthor:" + authorID, author);
|
||||
}
|
||||
db.set(`globalAuthor:${authorID}`, author);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a pad from the list of contributions
|
||||
* @param {String} author The id of the author
|
||||
* @param {String} padID The id of the pad the author contributes to
|
||||
*/
|
||||
exports.removePad = async function(authorID, padID) {
|
||||
let author = await db.get("globalAuthor:" + authorID);
|
||||
exports.removePad = async function (authorID, padID) {
|
||||
const author = await db.get(`globalAuthor:${authorID}`);
|
||||
|
||||
if (author === null) return;
|
||||
|
||||
if (author.padIDs !== null) {
|
||||
// remove pad from author
|
||||
delete author.padIDs[padID];
|
||||
await db.set('globalAuthor:' + authorID, author);
|
||||
await db.set(`globalAuthor:${authorID}`, author);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var ueberDB = require("ueberdb2");
|
||||
var settings = require("../utils/Settings");
|
||||
var log4js = require('log4js');
|
||||
const util = require("util");
|
||||
const ueberDB = require('ueberdb2');
|
||||
const settings = require('../utils/Settings');
|
||||
const log4js = require('log4js');
|
||||
const util = require('util');
|
||||
|
||||
// set database settings
|
||||
let db = new ueberDB.database(settings.dbType, settings.dbSettings, null, log4js.getLogger("ueberDB"));
|
||||
const db = new ueberDB.database(settings.dbType, settings.dbSettings, null, log4js.getLogger('ueberDB'));
|
||||
|
||||
/**
|
||||
* The UeberDB Object that provides the database functions
|
||||
|
@ -36,32 +36,32 @@ exports.db = null;
|
|||
* Initalizes the database with the settings provided by the settings module
|
||||
* @param {Function} callback
|
||||
*/
|
||||
exports.init = function() {
|
||||
exports.init = function () {
|
||||
// initalize the database async
|
||||
return new Promise((resolve, reject) => {
|
||||
db.init(function(err) {
|
||||
db.init((err) => {
|
||||
if (err) {
|
||||
// there was an error while initializing the database, output it and stop
|
||||
console.error("ERROR: Problem while initalizing the database");
|
||||
console.error('ERROR: Problem while initalizing the database');
|
||||
console.error(err.stack ? err.stack : err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// everything ok, set up Promise-based methods
|
||||
['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove', 'doShutdown'].forEach(fn => {
|
||||
['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove', 'doShutdown'].forEach((fn) => {
|
||||
exports[fn] = util.promisify(db[fn].bind(db));
|
||||
});
|
||||
|
||||
// set up wrappers for get and getSub that can't return "undefined"
|
||||
let get = exports.get;
|
||||
exports.get = async function(key) {
|
||||
let result = await get(key);
|
||||
const get = exports.get;
|
||||
exports.get = async function (key) {
|
||||
const result = await get(key);
|
||||
return (result === undefined) ? null : result;
|
||||
};
|
||||
|
||||
let getSub = exports.getSub;
|
||||
exports.getSub = async function(key, sub) {
|
||||
let result = await getSub(key, sub);
|
||||
const getSub = exports.getSub;
|
||||
exports.getSub = async function (key, sub) {
|
||||
const result = await getSub(key, sub);
|
||||
return (result === undefined) ? null : result;
|
||||
};
|
||||
|
||||
|
@ -70,7 +70,7 @@ exports.init = function() {
|
|||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.shutdown = async (hookName, context) => {
|
||||
await exports.doShutdown();
|
||||
|
|
|
@ -18,52 +18,48 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var customError = require("../utils/customError");
|
||||
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
var db = require("./DB");
|
||||
var padManager = require("./PadManager");
|
||||
var sessionManager = require("./SessionManager");
|
||||
const customError = require('../utils/customError');
|
||||
const randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
|
||||
const db = require('./DB');
|
||||
const padManager = require('./PadManager');
|
||||
const sessionManager = require('./SessionManager');
|
||||
|
||||
exports.listAllGroups = async function() {
|
||||
let groups = await db.get("groups");
|
||||
exports.listAllGroups = async function () {
|
||||
let groups = await db.get('groups');
|
||||
groups = groups || {};
|
||||
|
||||
let groupIDs = Object.keys(groups);
|
||||
return { groupIDs };
|
||||
}
|
||||
const groupIDs = Object.keys(groups);
|
||||
return {groupIDs};
|
||||
};
|
||||
|
||||
exports.deleteGroup = async function(groupID) {
|
||||
let group = await db.get("group:" + groupID);
|
||||
exports.deleteGroup = async function (groupID) {
|
||||
const group = await db.get(`group:${groupID}`);
|
||||
|
||||
// ensure group exists
|
||||
if (group == null) {
|
||||
// group does not exist
|
||||
throw new customError("groupID does not exist", "apierror");
|
||||
throw new customError('groupID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// iterate through all pads of this group and delete them (in parallel)
|
||||
await Promise.all(Object.keys(group.pads).map(padID => {
|
||||
return padManager.getPad(padID).then(pad => pad.remove());
|
||||
}));
|
||||
await Promise.all(Object.keys(group.pads).map((padID) => padManager.getPad(padID).then((pad) => pad.remove())));
|
||||
|
||||
// iterate through group2sessions and delete all sessions
|
||||
let group2sessions = await db.get("group2sessions:" + groupID);
|
||||
let sessions = group2sessions ? group2sessions.sessionIDs : {};
|
||||
const group2sessions = await db.get(`group2sessions:${groupID}`);
|
||||
const sessions = group2sessions ? group2sessions.sessionIDs : {};
|
||||
|
||||
// loop through all sessions and delete them (in parallel)
|
||||
await Promise.all(Object.keys(sessions).map(session => {
|
||||
return sessionManager.deleteSession(session);
|
||||
}));
|
||||
await Promise.all(Object.keys(sessions).map((session) => sessionManager.deleteSession(session)));
|
||||
|
||||
// remove group and group2sessions entry
|
||||
await db.remove("group2sessions:" + groupID);
|
||||
await db.remove("group:" + groupID);
|
||||
await db.remove(`group2sessions:${groupID}`);
|
||||
await db.remove(`group:${groupID}`);
|
||||
|
||||
// unlist the group
|
||||
let groups = await exports.listAllGroups();
|
||||
groups = groups ? groups.groupIDs : [];
|
||||
|
||||
let index = groups.indexOf(groupID);
|
||||
const index = groups.indexOf(groupID);
|
||||
|
||||
if (index === -1) {
|
||||
// it's not listed
|
||||
|
@ -75,102 +71,102 @@ exports.deleteGroup = async function(groupID) {
|
|||
groups.splice(index, 1);
|
||||
|
||||
// regenerate group list
|
||||
var newGroups = {};
|
||||
groups.forEach(group => newGroups[group] = 1);
|
||||
await db.set("groups", newGroups);
|
||||
}
|
||||
const newGroups = {};
|
||||
groups.forEach((group) => newGroups[group] = 1);
|
||||
await db.set('groups', newGroups);
|
||||
};
|
||||
|
||||
exports.doesGroupExist = async function(groupID) {
|
||||
exports.doesGroupExist = async function (groupID) {
|
||||
// try to get the group entry
|
||||
let group = await db.get("group:" + groupID);
|
||||
const group = await db.get(`group:${groupID}`);
|
||||
|
||||
return (group != null);
|
||||
}
|
||||
};
|
||||
|
||||
exports.createGroup = async function() {
|
||||
exports.createGroup = async function () {
|
||||
// search for non existing groupID
|
||||
var groupID = "g." + randomString(16);
|
||||
const groupID = `g.${randomString(16)}`;
|
||||
|
||||
// create the group
|
||||
await db.set("group:" + groupID, {pads: {}});
|
||||
await db.set(`group:${groupID}`, {pads: {}});
|
||||
|
||||
// list the group
|
||||
let groups = await exports.listAllGroups();
|
||||
groups = groups? groups.groupIDs : [];
|
||||
groups = groups ? groups.groupIDs : [];
|
||||
groups.push(groupID);
|
||||
|
||||
// regenerate group list
|
||||
var newGroups = {};
|
||||
groups.forEach(group => newGroups[group] = 1);
|
||||
await db.set("groups", newGroups);
|
||||
const newGroups = {};
|
||||
groups.forEach((group) => newGroups[group] = 1);
|
||||
await db.set('groups', newGroups);
|
||||
|
||||
return { groupID };
|
||||
}
|
||||
return {groupID};
|
||||
};
|
||||
|
||||
exports.createGroupIfNotExistsFor = async function(groupMapper) {
|
||||
exports.createGroupIfNotExistsFor = async function (groupMapper) {
|
||||
// ensure mapper is optional
|
||||
if (typeof groupMapper !== "string") {
|
||||
throw new customError("groupMapper is not a string", "apierror");
|
||||
if (typeof groupMapper !== 'string') {
|
||||
throw new customError('groupMapper is not a string', 'apierror');
|
||||
}
|
||||
|
||||
// try to get a group for this mapper
|
||||
let groupID = await db.get("mapper2group:" + groupMapper);
|
||||
const groupID = await db.get(`mapper2group:${groupMapper}`);
|
||||
|
||||
if (groupID) {
|
||||
// there is a group for this mapper
|
||||
let exists = await exports.doesGroupExist(groupID);
|
||||
const exists = await exports.doesGroupExist(groupID);
|
||||
|
||||
if (exists) return { groupID };
|
||||
if (exists) return {groupID};
|
||||
}
|
||||
|
||||
// hah, the returned group doesn't exist, let's create one
|
||||
let result = await exports.createGroup();
|
||||
const result = await exports.createGroup();
|
||||
|
||||
// create the mapper entry for this group
|
||||
await db.set("mapper2group:" + groupMapper, result.groupID);
|
||||
await db.set(`mapper2group:${groupMapper}`, result.groupID);
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
exports.createGroupPad = async function(groupID, padName, text) {
|
||||
exports.createGroupPad = async function (groupID, padName, text) {
|
||||
// create the padID
|
||||
let padID = groupID + "$" + padName;
|
||||
const padID = `${groupID}$${padName}`;
|
||||
|
||||
// ensure group exists
|
||||
let groupExists = await exports.doesGroupExist(groupID);
|
||||
const groupExists = await exports.doesGroupExist(groupID);
|
||||
|
||||
if (!groupExists) {
|
||||
throw new customError("groupID does not exist", "apierror");
|
||||
throw new customError('groupID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// ensure pad doesn't exist already
|
||||
let padExists = await padManager.doesPadExists(padID);
|
||||
const padExists = await padManager.doesPadExists(padID);
|
||||
|
||||
if (padExists) {
|
||||
// pad exists already
|
||||
throw new customError("padName does already exist", "apierror");
|
||||
throw new customError('padName does already exist', 'apierror');
|
||||
}
|
||||
|
||||
// create the pad
|
||||
await padManager.getPad(padID, text);
|
||||
|
||||
//create an entry in the group for this pad
|
||||
await db.setSub("group:" + groupID, ["pads", padID], 1);
|
||||
// create an entry in the group for this pad
|
||||
await db.setSub(`group:${groupID}`, ['pads', padID], 1);
|
||||
|
||||
return { padID };
|
||||
}
|
||||
return {padID};
|
||||
};
|
||||
|
||||
exports.listPads = async function(groupID) {
|
||||
let exists = await exports.doesGroupExist(groupID);
|
||||
exports.listPads = async function (groupID) {
|
||||
const exists = await exports.doesGroupExist(groupID);
|
||||
|
||||
// ensure the group exists
|
||||
if (!exists) {
|
||||
throw new customError("groupID does not exist", "apierror");
|
||||
throw new customError('groupID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// group exists, let's get the pads
|
||||
let result = await db.getSub("group:" + groupID, ["pads"]);
|
||||
let padIDs = Object.keys(result);
|
||||
const result = await db.getSub(`group:${groupID}`, ['pads']);
|
||||
const padIDs = Object.keys(result);
|
||||
|
||||
return { padIDs };
|
||||
}
|
||||
return {padIDs};
|
||||
};
|
||||
|
|
|
@ -3,36 +3,36 @@
|
|||
*/
|
||||
|
||||
|
||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
var AttributePool = require("ep_etherpad-lite/static/js/AttributePool");
|
||||
var db = require("./DB");
|
||||
var settings = require('../utils/Settings');
|
||||
var authorManager = require("./AuthorManager");
|
||||
var padManager = require("./PadManager");
|
||||
var padMessageHandler = require("../handler/PadMessageHandler");
|
||||
var groupManager = require("./GroupManager");
|
||||
var customError = require("../utils/customError");
|
||||
var readOnlyManager = require("./ReadOnlyManager");
|
||||
var crypto = require("crypto");
|
||||
var randomString = require("../utils/randomstring");
|
||||
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
var promises = require('../utils/promises')
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
const AttributePool = require('ep_etherpad-lite/static/js/AttributePool');
|
||||
const db = require('./DB');
|
||||
const settings = require('../utils/Settings');
|
||||
const authorManager = require('./AuthorManager');
|
||||
const padManager = require('./PadManager');
|
||||
const padMessageHandler = require('../handler/PadMessageHandler');
|
||||
const groupManager = require('./GroupManager');
|
||||
const customError = require('../utils/customError');
|
||||
const readOnlyManager = require('./ReadOnlyManager');
|
||||
const crypto = require('crypto');
|
||||
const randomString = require('../utils/randomstring');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
const promises = require('../utils/promises');
|
||||
|
||||
// serialization/deserialization attributes
|
||||
var attributeBlackList = ["id"];
|
||||
var jsonableList = ["pool"];
|
||||
const attributeBlackList = ['id'];
|
||||
const jsonableList = ['pool'];
|
||||
|
||||
/**
|
||||
* Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces
|
||||
* @param txt
|
||||
*/
|
||||
exports.cleanText = function (txt) {
|
||||
return txt.replace(/\r\n/g,'\n').replace(/\r/g,'\n').replace(/\t/g, ' ').replace(/\xa0/g, ' ');
|
||||
return txt.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\t/g, ' ').replace(/\xa0/g, ' ');
|
||||
};
|
||||
|
||||
|
||||
let Pad = function Pad(id) {
|
||||
this.atext = Changeset.makeAText("\n");
|
||||
const Pad = function Pad(id) {
|
||||
this.atext = Changeset.makeAText('\n');
|
||||
this.pool = new AttributePool();
|
||||
this.head = -1;
|
||||
this.chatHead = -1;
|
||||
|
@ -56,13 +56,11 @@ Pad.prototype.getSavedRevisionsNumber = function getSavedRevisionsNumber() {
|
|||
};
|
||||
|
||||
Pad.prototype.getSavedRevisionsList = function getSavedRevisionsList() {
|
||||
var savedRev = new Array();
|
||||
for (var rev in this.savedRevisions) {
|
||||
const savedRev = new Array();
|
||||
for (const rev in this.savedRevisions) {
|
||||
savedRev.push(this.savedRevisions[rev].revNum);
|
||||
}
|
||||
savedRev.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
savedRev.sort((a, b) => a - b);
|
||||
return savedRev;
|
||||
};
|
||||
|
||||
|
@ -75,12 +73,12 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
|
|||
author = '';
|
||||
}
|
||||
|
||||
var newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool);
|
||||
const newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool);
|
||||
Changeset.copyAText(newAText, this.atext);
|
||||
|
||||
var newRev = ++this.head;
|
||||
const newRev = ++this.head;
|
||||
|
||||
var newRevData = {};
|
||||
const newRevData = {};
|
||||
newRevData.changeset = aChangeset;
|
||||
newRevData.meta = {};
|
||||
newRevData.meta.author = author;
|
||||
|
@ -97,7 +95,7 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
|
|||
}
|
||||
|
||||
const p = [
|
||||
db.set('pad:' + this.id + ':revs:' + newRev, newRevData),
|
||||
db.set(`pad:${this.id}:revs:${newRev}`, newRevData),
|
||||
this.saveToDatabase(),
|
||||
];
|
||||
|
||||
|
@ -107,9 +105,9 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
|
|||
}
|
||||
|
||||
if (this.head == 0) {
|
||||
hooks.callAll("padCreate", {'pad':this, 'author': author});
|
||||
hooks.callAll('padCreate', {pad: this, author});
|
||||
} else {
|
||||
hooks.callAll("padUpdate", {'pad':this, 'author': author, 'revs': newRev, 'changeset': aChangeset});
|
||||
hooks.callAll('padUpdate', {pad: this, author, revs: newRev, changeset: aChangeset});
|
||||
}
|
||||
|
||||
await Promise.all(p);
|
||||
|
@ -117,10 +115,10 @@ Pad.prototype.appendRevision = async function appendRevision(aChangeset, author)
|
|||
|
||||
// save all attributes to the database
|
||||
Pad.prototype.saveToDatabase = async function saveToDatabase() {
|
||||
var dbObject = {};
|
||||
const dbObject = {};
|
||||
|
||||
for (var attr in this) {
|
||||
if (typeof this[attr] === "function") continue;
|
||||
for (const attr in this) {
|
||||
if (typeof this[attr] === 'function') continue;
|
||||
if (attributeBlackList.indexOf(attr) !== -1) continue;
|
||||
|
||||
dbObject[attr] = this[attr];
|
||||
|
@ -130,32 +128,32 @@ Pad.prototype.saveToDatabase = async function saveToDatabase() {
|
|||
}
|
||||
}
|
||||
|
||||
await db.set('pad:' + this.id, dbObject);
|
||||
}
|
||||
await db.set(`pad:${this.id}`, dbObject);
|
||||
};
|
||||
|
||||
// get time of last edit (changeset application)
|
||||
Pad.prototype.getLastEdit = function getLastEdit() {
|
||||
var revNum = this.getHeadRevisionNumber();
|
||||
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "timestamp"]);
|
||||
}
|
||||
const revNum = this.getHeadRevisionNumber();
|
||||
return db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'timestamp']);
|
||||
};
|
||||
|
||||
Pad.prototype.getRevisionChangeset = function getRevisionChangeset(revNum) {
|
||||
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["changeset"]);
|
||||
}
|
||||
return db.getSub(`pad:${this.id}:revs:${revNum}`, ['changeset']);
|
||||
};
|
||||
|
||||
Pad.prototype.getRevisionAuthor = function getRevisionAuthor(revNum) {
|
||||
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "author"]);
|
||||
}
|
||||
return db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'author']);
|
||||
};
|
||||
|
||||
Pad.prototype.getRevisionDate = function getRevisionDate(revNum) {
|
||||
return db.getSub("pad:" + this.id + ":revs:" + revNum, ["meta", "timestamp"]);
|
||||
}
|
||||
return db.getSub(`pad:${this.id}:revs:${revNum}`, ['meta', 'timestamp']);
|
||||
};
|
||||
|
||||
Pad.prototype.getAllAuthors = function getAllAuthors() {
|
||||
var authors = [];
|
||||
const authors = [];
|
||||
|
||||
for(var key in this.pool.numToAttrib) {
|
||||
if (this.pool.numToAttrib[key][0] == "author" && this.pool.numToAttrib[key][1] != "") {
|
||||
for (const key in this.pool.numToAttrib) {
|
||||
if (this.pool.numToAttrib[key][0] == 'author' && this.pool.numToAttrib[key][1] != '') {
|
||||
authors.push(this.pool.numToAttrib[key][1]);
|
||||
}
|
||||
}
|
||||
|
@ -164,63 +162,59 @@ Pad.prototype.getAllAuthors = function getAllAuthors() {
|
|||
};
|
||||
|
||||
Pad.prototype.getInternalRevisionAText = async function getInternalRevisionAText(targetRev) {
|
||||
let keyRev = this.getKeyRevisionNumber(targetRev);
|
||||
const keyRev = this.getKeyRevisionNumber(targetRev);
|
||||
|
||||
// find out which changesets are needed
|
||||
let neededChangesets = [];
|
||||
for (let curRev = keyRev; curRev < targetRev; ) {
|
||||
const neededChangesets = [];
|
||||
for (let curRev = keyRev; curRev < targetRev;) {
|
||||
neededChangesets.push(++curRev);
|
||||
}
|
||||
|
||||
// get all needed data out of the database
|
||||
|
||||
// start to get the atext of the key revision
|
||||
let p_atext = db.getSub("pad:" + this.id + ":revs:" + keyRev, ["meta", "atext"]);
|
||||
const p_atext = db.getSub(`pad:${this.id}:revs:${keyRev}`, ['meta', 'atext']);
|
||||
|
||||
// get all needed changesets
|
||||
let changesets = [];
|
||||
await Promise.all(neededChangesets.map(item => {
|
||||
return this.getRevisionChangeset(item).then(changeset => {
|
||||
changesets[item] = changeset;
|
||||
});
|
||||
}));
|
||||
const changesets = [];
|
||||
await Promise.all(neededChangesets.map((item) => this.getRevisionChangeset(item).then((changeset) => {
|
||||
changesets[item] = changeset;
|
||||
})));
|
||||
|
||||
// we should have the atext by now
|
||||
let atext = await p_atext;
|
||||
atext = Changeset.cloneAText(atext);
|
||||
|
||||
// apply all changesets to the key changeset
|
||||
let apool = this.apool();
|
||||
for (let curRev = keyRev; curRev < targetRev; ) {
|
||||
let cs = changesets[++curRev];
|
||||
const apool = this.apool();
|
||||
for (let curRev = keyRev; curRev < targetRev;) {
|
||||
const cs = changesets[++curRev];
|
||||
atext = Changeset.applyToAText(cs, atext, apool);
|
||||
}
|
||||
|
||||
return atext;
|
||||
}
|
||||
};
|
||||
|
||||
Pad.prototype.getRevision = function getRevisionChangeset(revNum) {
|
||||
return db.get("pad:" + this.id + ":revs:" + revNum);
|
||||
}
|
||||
return db.get(`pad:${this.id}:revs:${revNum}`);
|
||||
};
|
||||
|
||||
Pad.prototype.getAllAuthorColors = async function getAllAuthorColors() {
|
||||
let authors = this.getAllAuthors();
|
||||
let returnTable = {};
|
||||
let colorPalette = authorManager.getColorPalette();
|
||||
const authors = this.getAllAuthors();
|
||||
const returnTable = {};
|
||||
const colorPalette = authorManager.getColorPalette();
|
||||
|
||||
await Promise.all(authors.map(author => {
|
||||
return authorManager.getAuthorColorId(author).then(colorId => {
|
||||
// colorId might be a hex color or an number out of the palette
|
||||
returnTable[author] = colorPalette[colorId] || colorId;
|
||||
});
|
||||
}));
|
||||
await Promise.all(authors.map((author) => authorManager.getAuthorColorId(author).then((colorId) => {
|
||||
// colorId might be a hex color or an number out of the palette
|
||||
returnTable[author] = colorPalette[colorId] || colorId;
|
||||
})));
|
||||
|
||||
return returnTable;
|
||||
}
|
||||
};
|
||||
|
||||
Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, endRev) {
|
||||
startRev = parseInt(startRev, 10);
|
||||
var head = this.getHeadRevisionNumber();
|
||||
const head = this.getHeadRevisionNumber();
|
||||
endRev = endRev ? parseInt(endRev, 10) : head;
|
||||
|
||||
if (isNaN(startRev) || startRev < 0 || startRev > head) {
|
||||
|
@ -234,7 +228,7 @@ Pad.prototype.getValidRevisionRange = function getValidRevisionRange(startRev, e
|
|||
}
|
||||
|
||||
if (startRev !== null && endRev !== null) {
|
||||
return { startRev: startRev , endRev: endRev }
|
||||
return {startRev, endRev};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
@ -251,16 +245,16 @@ Pad.prototype.setText = async function setText(newText) {
|
|||
// clean the new text
|
||||
newText = exports.cleanText(newText);
|
||||
|
||||
var oldText = this.text();
|
||||
const oldText = this.text();
|
||||
|
||||
// create the changeset
|
||||
// We want to ensure the pad still ends with a \n, but otherwise keep
|
||||
// getText() and setText() consistent.
|
||||
var changeset;
|
||||
let changeset;
|
||||
if (newText[newText.length - 1] == '\n') {
|
||||
changeset = Changeset.makeSplice(oldText, 0, oldText.length, newText);
|
||||
} else {
|
||||
changeset = Changeset.makeSplice(oldText, 0, oldText.length-1, newText);
|
||||
changeset = Changeset.makeSplice(oldText, 0, oldText.length - 1, newText);
|
||||
}
|
||||
|
||||
// append the changeset
|
||||
|
@ -271,10 +265,10 @@ Pad.prototype.appendText = async function appendText(newText) {
|
|||
// clean the new text
|
||||
newText = exports.cleanText(newText);
|
||||
|
||||
var oldText = this.text();
|
||||
const oldText = this.text();
|
||||
|
||||
// create the changeset
|
||||
var changeset = Changeset.makeSplice(oldText, oldText.length, 0, newText);
|
||||
const changeset = Changeset.makeSplice(oldText, oldText.length, 0, newText);
|
||||
|
||||
// append the changeset
|
||||
await this.appendRevision(changeset);
|
||||
|
@ -284,14 +278,14 @@ Pad.prototype.appendChatMessage = async function appendChatMessage(text, userId,
|
|||
this.chatHead++;
|
||||
// save the chat entry in the database
|
||||
await Promise.all([
|
||||
db.set('pad:' + this.id + ':chat:' + this.chatHead, {text, userId, time}),
|
||||
db.set(`pad:${this.id}:chat:${this.chatHead}`, {text, userId, time}),
|
||||
this.saveToDatabase(),
|
||||
]);
|
||||
};
|
||||
|
||||
Pad.prototype.getChatMessage = async function getChatMessage(entryNum) {
|
||||
// get the chat entry
|
||||
let entry = await db.get("pad:" + this.id + ":chat:" + entryNum);
|
||||
const entry = await db.get(`pad:${this.id}:chat:${entryNum}`);
|
||||
|
||||
// get the authorName if the entry exists
|
||||
if (entry != null) {
|
||||
|
@ -302,49 +296,45 @@ Pad.prototype.getChatMessage = async function getChatMessage(entryNum) {
|
|||
};
|
||||
|
||||
Pad.prototype.getChatMessages = async function getChatMessages(start, end) {
|
||||
|
||||
// collect the numbers of chat entries and in which order we need them
|
||||
let neededEntries = [];
|
||||
const neededEntries = [];
|
||||
for (let order = 0, entryNum = start; entryNum <= end; ++order, ++entryNum) {
|
||||
neededEntries.push({ entryNum, order });
|
||||
neededEntries.push({entryNum, order});
|
||||
}
|
||||
|
||||
// get all entries out of the database
|
||||
let entries = [];
|
||||
await Promise.all(neededEntries.map(entryObject => {
|
||||
return this.getChatMessage(entryObject.entryNum).then(entry => {
|
||||
entries[entryObject.order] = entry;
|
||||
});
|
||||
}));
|
||||
const entries = [];
|
||||
await Promise.all(neededEntries.map((entryObject) => this.getChatMessage(entryObject.entryNum).then((entry) => {
|
||||
entries[entryObject.order] = entry;
|
||||
})));
|
||||
|
||||
// sort out broken chat entries
|
||||
// it looks like in happened in the past that the chat head was
|
||||
// incremented, but the chat message wasn't added
|
||||
let cleanedEntries = entries.filter(entry => {
|
||||
let pass = (entry != null);
|
||||
const cleanedEntries = entries.filter((entry) => {
|
||||
const pass = (entry != null);
|
||||
if (!pass) {
|
||||
console.warn("WARNING: Found broken chat entry in pad " + this.id);
|
||||
console.warn(`WARNING: Found broken chat entry in pad ${this.id}`);
|
||||
}
|
||||
return pass;
|
||||
});
|
||||
|
||||
return cleanedEntries;
|
||||
}
|
||||
};
|
||||
|
||||
Pad.prototype.init = async function init(text) {
|
||||
|
||||
// replace text with default text if text isn't set
|
||||
if (text == null) {
|
||||
text = settings.defaultPadText;
|
||||
}
|
||||
|
||||
// try to load the pad
|
||||
let value = await db.get("pad:" + this.id);
|
||||
const value = await db.get(`pad:${this.id}`);
|
||||
|
||||
// if this pad exists, load it
|
||||
if (value != null) {
|
||||
// copy all attr. To a transfrom via fromJsonable if necassary
|
||||
for (var attr in value) {
|
||||
for (const attr in value) {
|
||||
if (jsonableList.indexOf(attr) !== -1) {
|
||||
this[attr] = this[attr].fromJsonable(value[attr]);
|
||||
} else {
|
||||
|
@ -353,17 +343,17 @@ Pad.prototype.init = async function init(text) {
|
|||
}
|
||||
} else {
|
||||
// this pad doesn't exist, so create it
|
||||
let firstChangeset = Changeset.makeSplice("\n", 0, 0, exports.cleanText(text));
|
||||
const firstChangeset = Changeset.makeSplice('\n', 0, 0, exports.cleanText(text));
|
||||
|
||||
await this.appendRevision(firstChangeset, '');
|
||||
}
|
||||
|
||||
hooks.callAll("padLoad", { 'pad': this });
|
||||
}
|
||||
hooks.callAll('padLoad', {pad: this});
|
||||
};
|
||||
|
||||
Pad.prototype.copy = async function copy(destinationID, force) {
|
||||
let destGroupID;
|
||||
let sourceID = this.id;
|
||||
const sourceID = this.id;
|
||||
|
||||
// Kick everyone from this pad.
|
||||
// This was commented due to https://github.com/ether/etherpad-lite/issues/3183.
|
||||
|
@ -380,32 +370,28 @@ Pad.prototype.copy = async function copy(destinationID, force) {
|
|||
|
||||
// if force is true and already exists a Pad with the same id, remove that Pad
|
||||
await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force);
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// copy the 'pad' entry
|
||||
let pad = await db.get("pad:" + sourceID);
|
||||
db.set("pad:" + destinationID, pad);
|
||||
const pad = await db.get(`pad:${sourceID}`);
|
||||
db.set(`pad:${destinationID}`, pad);
|
||||
|
||||
// copy all relations in parallel
|
||||
let promises = [];
|
||||
const promises = [];
|
||||
|
||||
// copy all chat messages
|
||||
let chatHead = this.chatHead;
|
||||
const chatHead = this.chatHead;
|
||||
for (let i = 0; i <= chatHead; ++i) {
|
||||
let p = db.get("pad:" + sourceID + ":chat:" + i).then(chat => {
|
||||
return db.set("pad:" + destinationID + ":chat:" + i, chat);
|
||||
});
|
||||
const p = db.get(`pad:${sourceID}:chat:${i}`).then((chat) => db.set(`pad:${destinationID}:chat:${i}`, chat));
|
||||
promises.push(p);
|
||||
}
|
||||
|
||||
// copy all revisions
|
||||
let revHead = this.head;
|
||||
const revHead = this.head;
|
||||
for (let i = 0; i <= revHead; ++i) {
|
||||
let p = db.get("pad:" + sourceID + ":revs:" + i).then(rev => {
|
||||
return db.set("pad:" + destinationID + ":revs:" + i, rev);
|
||||
});
|
||||
const p = db.get(`pad:${sourceID}:revs:${i}`).then((rev) => db.set(`pad:${destinationID}:revs:${i}`, rev));
|
||||
promises.push(p);
|
||||
}
|
||||
|
||||
|
@ -416,70 +402,69 @@ Pad.prototype.copy = async function copy(destinationID, force) {
|
|||
|
||||
// Group pad? Add it to the group's list
|
||||
if (destGroupID) {
|
||||
await db.setSub("group:" + destGroupID, ["pads", destinationID], 1);
|
||||
await db.setSub(`group:${destGroupID}`, ['pads', destinationID], 1);
|
||||
}
|
||||
|
||||
// delay still necessary?
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
// Initialize the new pad (will update the listAllPads cache)
|
||||
await padManager.getPad(destinationID, null); // this runs too early.
|
||||
|
||||
// let the plugins know the pad was copied
|
||||
hooks.callAll('padCopy', { 'originalPad': this, 'destinationID': destinationID });
|
||||
hooks.callAll('padCopy', {originalPad: this, destinationID});
|
||||
|
||||
return { padID: destinationID };
|
||||
}
|
||||
return {padID: destinationID};
|
||||
};
|
||||
|
||||
Pad.prototype.checkIfGroupExistAndReturnIt = async function checkIfGroupExistAndReturnIt(destinationID) {
|
||||
let destGroupID = false;
|
||||
|
||||
if (destinationID.indexOf("$") >= 0) {
|
||||
|
||||
destGroupID = destinationID.split("$")[0]
|
||||
let groupExists = await groupManager.doesGroupExist(destGroupID);
|
||||
if (destinationID.indexOf('$') >= 0) {
|
||||
destGroupID = destinationID.split('$')[0];
|
||||
const groupExists = await groupManager.doesGroupExist(destGroupID);
|
||||
|
||||
// group does not exist
|
||||
if (!groupExists) {
|
||||
throw new customError("groupID does not exist for destinationID", "apierror");
|
||||
throw new customError('groupID does not exist for destinationID', 'apierror');
|
||||
}
|
||||
}
|
||||
return destGroupID;
|
||||
}
|
||||
};
|
||||
|
||||
Pad.prototype.removePadIfForceIsTrueAndAlreadyExist = async function removePadIfForceIsTrueAndAlreadyExist(destinationID, force) {
|
||||
// if the pad exists, we should abort, unless forced.
|
||||
let exists = await padManager.doesPadExist(destinationID);
|
||||
const exists = await padManager.doesPadExist(destinationID);
|
||||
|
||||
// allow force to be a string
|
||||
if (typeof force === "string") {
|
||||
force = (force.toLowerCase() === "true");
|
||||
if (typeof force === 'string') {
|
||||
force = (force.toLowerCase() === 'true');
|
||||
} else {
|
||||
force = !!force;
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
if (!force) {
|
||||
console.error("erroring out without force");
|
||||
throw new customError("destinationID already exists", "apierror");
|
||||
console.error('erroring out without force');
|
||||
throw new customError('destinationID already exists', 'apierror');
|
||||
}
|
||||
|
||||
// exists and forcing
|
||||
let pad = await padManager.getPad(destinationID);
|
||||
const pad = await padManager.getPad(destinationID);
|
||||
await pad.remove();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Pad.prototype.copyAuthorInfoToDestinationPad = function copyAuthorInfoToDestinationPad(destinationID) {
|
||||
// add the new sourcePad to all authors who contributed to the old one
|
||||
this.getAllAuthors().forEach(authorID => {
|
||||
this.getAllAuthors().forEach((authorID) => {
|
||||
authorManager.addPad(authorID, destinationID);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Pad.prototype.copyPadWithoutHistory = async function copyPadWithoutHistory(destinationID, force) {
|
||||
let destGroupID;
|
||||
let sourceID = this.id;
|
||||
const sourceID = this.id;
|
||||
|
||||
// flush the source pad
|
||||
this.saveToDatabase();
|
||||
|
@ -490,53 +475,53 @@ Pad.prototype.copyPadWithoutHistory = async function copyPadWithoutHistory(desti
|
|||
|
||||
// if force is true and already exists a Pad with the same id, remove that Pad
|
||||
await this.removePadIfForceIsTrueAndAlreadyExist(destinationID, force);
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
let sourcePad = await padManager.getPad(sourceID);
|
||||
const sourcePad = await padManager.getPad(sourceID);
|
||||
|
||||
// add the new sourcePad to all authors who contributed to the old one
|
||||
this.copyAuthorInfoToDestinationPad(destinationID);
|
||||
|
||||
// Group pad? Add it to the group's list
|
||||
if (destGroupID) {
|
||||
await db.setSub("group:" + destGroupID, ["pads", destinationID], 1);
|
||||
await db.setSub(`group:${destGroupID}`, ['pads', destinationID], 1);
|
||||
}
|
||||
|
||||
// initialize the pad with a new line to avoid getting the defaultText
|
||||
let newPad = await padManager.getPad(destinationID, '\n');
|
||||
const newPad = await padManager.getPad(destinationID, '\n');
|
||||
|
||||
let oldAText = this.atext;
|
||||
let newPool = newPad.pool;
|
||||
const oldAText = this.atext;
|
||||
const newPool = newPad.pool;
|
||||
newPool.fromJsonable(sourcePad.pool.toJsonable()); // copy that sourceId pool to the new pad
|
||||
|
||||
// based on Changeset.makeSplice
|
||||
let assem = Changeset.smartOpAssembler();
|
||||
const assem = Changeset.smartOpAssembler();
|
||||
assem.appendOpWithText('=', '');
|
||||
Changeset.appendATextToAssembler(oldAText, assem);
|
||||
assem.endDocument();
|
||||
|
||||
// although we have instantiated the newPad with '\n', an additional '\n' is
|
||||
// added internally, so the pad text on the revision 0 is "\n\n"
|
||||
let oldLength = 2;
|
||||
const oldLength = 2;
|
||||
|
||||
let newLength = assem.getLengthChange();
|
||||
let newText = oldAText.text;
|
||||
const newLength = assem.getLengthChange();
|
||||
const newText = oldAText.text;
|
||||
|
||||
// create a changeset that removes the previous text and add the newText with
|
||||
// all atributes present on the source pad
|
||||
let changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
|
||||
const changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
|
||||
newPad.appendRevision(changeset);
|
||||
|
||||
hooks.callAll('padCopy', { 'originalPad': this, 'destinationID': destinationID });
|
||||
hooks.callAll('padCopy', {originalPad: this, destinationID});
|
||||
|
||||
return { padID: destinationID };
|
||||
}
|
||||
return {padID: destinationID};
|
||||
};
|
||||
|
||||
|
||||
Pad.prototype.remove = async function remove() {
|
||||
var padID = this.id;
|
||||
const padID = this.id;
|
||||
const p = [];
|
||||
|
||||
// kick everyone from this pad
|
||||
|
@ -548,45 +533,44 @@ Pad.prototype.remove = async function remove() {
|
|||
// run to completion
|
||||
|
||||
// is it a group pad? -> delete the entry of this pad in the group
|
||||
if (padID.indexOf("$") >= 0) {
|
||||
|
||||
if (padID.indexOf('$') >= 0) {
|
||||
// it is a group pad
|
||||
let groupID = padID.substring(0, padID.indexOf("$"));
|
||||
let group = await db.get("group:" + groupID);
|
||||
const groupID = padID.substring(0, padID.indexOf('$'));
|
||||
const group = await db.get(`group:${groupID}`);
|
||||
|
||||
// remove the pad entry
|
||||
delete group.pads[padID];
|
||||
|
||||
// set the new value
|
||||
p.push(db.set('group:' + groupID, group));
|
||||
p.push(db.set(`group:${groupID}`, group));
|
||||
}
|
||||
|
||||
// remove the readonly entries
|
||||
p.push(readOnlyManager.getReadOnlyId(padID).then(async (readonlyID) => {
|
||||
await db.remove('readonly2pad:' + readonlyID);
|
||||
await db.remove(`readonly2pad:${readonlyID}`);
|
||||
}));
|
||||
p.push(db.remove('pad2readonly:' + padID));
|
||||
p.push(db.remove(`pad2readonly:${padID}`));
|
||||
|
||||
// delete all chat messages
|
||||
p.push(promises.timesLimit(this.chatHead + 1, 500, async (i) => {
|
||||
await db.remove('pad:' + padID + ':chat:' + i, null);
|
||||
await db.remove(`pad:${padID}:chat:${i}`, null);
|
||||
}));
|
||||
|
||||
// delete all revisions
|
||||
p.push(promises.timesLimit(this.head + 1, 500, async (i) => {
|
||||
await db.remove('pad:' + padID + ':revs:' + i, null);
|
||||
await db.remove(`pad:${padID}:revs:${i}`, null);
|
||||
}));
|
||||
|
||||
// remove pad from all authors who contributed
|
||||
this.getAllAuthors().forEach(authorID => {
|
||||
this.getAllAuthors().forEach((authorID) => {
|
||||
p.push(authorManager.removePad(authorID, padID));
|
||||
});
|
||||
|
||||
// delete the pad entry and delete pad from padManager
|
||||
p.push(padManager.removePad(padID));
|
||||
hooks.callAll("padRemove", { padID });
|
||||
hooks.callAll('padRemove', {padID});
|
||||
await Promise.all(p);
|
||||
}
|
||||
};
|
||||
|
||||
// set in db
|
||||
Pad.prototype.setPublicStatus = async function setPublicStatus(publicStatus) {
|
||||
|
@ -596,17 +580,17 @@ Pad.prototype.setPublicStatus = async function setPublicStatus(publicStatus) {
|
|||
|
||||
Pad.prototype.addSavedRevision = async function addSavedRevision(revNum, savedById, label) {
|
||||
// if this revision is already saved, return silently
|
||||
for (var i in this.savedRevisions) {
|
||||
for (const i in this.savedRevisions) {
|
||||
if (this.savedRevisions[i] && this.savedRevisions[i].revNum === revNum) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// build the saved revision object
|
||||
var savedRevision = {};
|
||||
const savedRevision = {};
|
||||
savedRevision.revNum = revNum;
|
||||
savedRevision.savedById = savedById;
|
||||
savedRevision.label = label || "Revision " + revNum;
|
||||
savedRevision.label = label || `Revision ${revNum}`;
|
||||
savedRevision.timestamp = Date.now();
|
||||
savedRevision.id = randomString(10);
|
||||
|
||||
|
@ -618,4 +602,3 @@ Pad.prototype.addSavedRevision = async function addSavedRevision(revNum, savedBy
|
|||
Pad.prototype.getSavedRevisions = function getSavedRevisions() {
|
||||
return this.savedRevisions;
|
||||
};
|
||||
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var customError = require("../utils/customError");
|
||||
var Pad = require("../db/Pad").Pad;
|
||||
var db = require("./DB");
|
||||
const customError = require('../utils/customError');
|
||||
const Pad = require('../db/Pad').Pad;
|
||||
const db = require('./DB');
|
||||
|
||||
/**
|
||||
* A cache of all loaded Pads.
|
||||
|
@ -33,14 +33,14 @@ var db = require("./DB");
|
|||
* If this is needed in other places, it would be wise to make this a prototype
|
||||
* that's defined somewhere more sensible.
|
||||
*/
|
||||
var globalPads = {
|
||||
get: function(name) { return this[':'+name]; },
|
||||
set: function(name, value) {
|
||||
this[':'+name] = value;
|
||||
},
|
||||
remove: function(name) {
|
||||
delete this[':'+name];
|
||||
}
|
||||
const globalPads = {
|
||||
get(name) { return this[`:${name}`]; },
|
||||
set(name, value) {
|
||||
this[`:${name}`] = value;
|
||||
},
|
||||
remove(name) {
|
||||
delete this[`:${name}`];
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -48,24 +48,24 @@ var globalPads = {
|
|||
*
|
||||
* Updated without db access as new pads are created/old ones removed.
|
||||
*/
|
||||
let padList = {
|
||||
const padList = {
|
||||
list: new Set(),
|
||||
cachedList: undefined,
|
||||
initiated: false,
|
||||
init: async function() {
|
||||
let dbData = await db.findKeys("pad:*", "*:*:*");
|
||||
async init() {
|
||||
const dbData = await db.findKeys('pad:*', '*:*:*');
|
||||
|
||||
if (dbData != null) {
|
||||
this.initiated = true;
|
||||
|
||||
for (let val of dbData) {
|
||||
this.addPad(val.replace(/^pad:/,""), false);
|
||||
for (const val of dbData) {
|
||||
this.addPad(val.replace(/^pad:/, ''), false);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
load: async function() {
|
||||
async load() {
|
||||
if (!this.initiated) {
|
||||
return this.init();
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ let padList = {
|
|||
/**
|
||||
* Returns all pads in alphabetical order as array.
|
||||
*/
|
||||
getPads: async function() {
|
||||
async getPads() {
|
||||
await this.load();
|
||||
|
||||
if (!this.cachedList) {
|
||||
|
@ -84,7 +84,7 @@ let padList = {
|
|||
|
||||
return this.cachedList;
|
||||
},
|
||||
addPad: function(name) {
|
||||
addPad(name) {
|
||||
if (!this.initiated) return;
|
||||
|
||||
if (!this.list.has(name)) {
|
||||
|
@ -92,14 +92,14 @@ let padList = {
|
|||
this.cachedList = undefined;
|
||||
}
|
||||
},
|
||||
removePad: function(name) {
|
||||
removePad(name) {
|
||||
if (!this.initiated) return;
|
||||
|
||||
if (this.list.has(name)) {
|
||||
this.list.delete(name);
|
||||
this.cachedList = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// initialises the all-knowing data structure
|
||||
|
@ -109,22 +109,22 @@ let padList = {
|
|||
* @param id A String with the id of the pad
|
||||
* @param {Function} callback
|
||||
*/
|
||||
exports.getPad = async function(id, text) {
|
||||
exports.getPad = async function (id, text) {
|
||||
// check if this is a valid padId
|
||||
if (!exports.isValidPadId(id)) {
|
||||
throw new customError(id + " is not a valid padId", "apierror");
|
||||
throw new customError(`${id} is not a valid padId`, 'apierror');
|
||||
}
|
||||
|
||||
// check if this is a valid text
|
||||
if (text != null) {
|
||||
// check if text is a string
|
||||
if (typeof text != "string") {
|
||||
throw new customError("text is not a string", "apierror");
|
||||
if (typeof text !== 'string') {
|
||||
throw new customError('text is not a string', 'apierror');
|
||||
}
|
||||
|
||||
// check if text is less than 100k chars
|
||||
if (text.length > 100000) {
|
||||
throw new customError("text must be less than 100k chars", "apierror");
|
||||
throw new customError('text must be less than 100k chars', 'apierror');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,20 +144,20 @@ exports.getPad = async function(id, text) {
|
|||
padList.addPad(id);
|
||||
|
||||
return pad;
|
||||
}
|
||||
};
|
||||
|
||||
exports.listAllPads = async function() {
|
||||
let padIDs = await padList.getPads();
|
||||
exports.listAllPads = async function () {
|
||||
const padIDs = await padList.getPads();
|
||||
|
||||
return { padIDs };
|
||||
}
|
||||
return {padIDs};
|
||||
};
|
||||
|
||||
// checks if a pad exists
|
||||
exports.doesPadExist = async function(padId) {
|
||||
let value = await db.get("pad:" + padId);
|
||||
exports.doesPadExist = async function (padId) {
|
||||
const value = await db.get(`pad:${padId}`);
|
||||
|
||||
return (value != null && value.atext);
|
||||
}
|
||||
};
|
||||
|
||||
// alias for backwards compatibility
|
||||
exports.doesPadExists = exports.doesPadExist;
|
||||
|
@ -168,42 +168,42 @@ exports.doesPadExists = exports.doesPadExist;
|
|||
*/
|
||||
const padIdTransforms = [
|
||||
[/\s+/g, '_'],
|
||||
[/:+/g, '_']
|
||||
[/:+/g, '_'],
|
||||
];
|
||||
|
||||
// returns a sanitized padId, respecting legacy pad id formats
|
||||
exports.sanitizePadId = async function sanitizePadId(padId) {
|
||||
for (let i = 0, n = padIdTransforms.length; i < n; ++i) {
|
||||
let exists = await exports.doesPadExist(padId);
|
||||
const exists = await exports.doesPadExist(padId);
|
||||
|
||||
if (exists) {
|
||||
return padId;
|
||||
}
|
||||
|
||||
let [from, to] = padIdTransforms[i];
|
||||
const [from, to] = padIdTransforms[i];
|
||||
|
||||
padId = padId.replace(from, to);
|
||||
}
|
||||
|
||||
// we're out of possible transformations, so just return it
|
||||
return padId;
|
||||
}
|
||||
};
|
||||
|
||||
exports.isValidPadId = function(padId) {
|
||||
exports.isValidPadId = function (padId) {
|
||||
return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the pad from database and unloads it.
|
||||
*/
|
||||
exports.removePad = async (padId) => {
|
||||
const p = db.remove('pad:' + padId);
|
||||
const p = db.remove(`pad:${padId}`);
|
||||
exports.unloadPad(padId);
|
||||
padList.removePad(padId);
|
||||
await p;
|
||||
}
|
||||
};
|
||||
|
||||
// removes a pad from the cache
|
||||
exports.unloadPad = function(padId) {
|
||||
exports.unloadPad = function (padId) {
|
||||
globalPads.remove(padId);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,17 +19,17 @@
|
|||
*/
|
||||
|
||||
|
||||
var db = require("./DB");
|
||||
var randomString = require("../utils/randomstring");
|
||||
const db = require('./DB');
|
||||
const randomString = require('../utils/randomstring');
|
||||
|
||||
|
||||
/**
|
||||
* checks if the id pattern matches a read-only pad id
|
||||
* @param {String} the pad's id
|
||||
*/
|
||||
exports.isReadOnlyId = function(id) {
|
||||
return id.indexOf("r.") === 0;
|
||||
}
|
||||
exports.isReadOnlyId = function (id) {
|
||||
return id.indexOf('r.') === 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* returns a read only id for a pad
|
||||
|
@ -37,36 +37,36 @@ exports.isReadOnlyId = function(id) {
|
|||
*/
|
||||
exports.getReadOnlyId = async function (padId) {
|
||||
// check if there is a pad2readonly entry
|
||||
let readOnlyId = await db.get("pad2readonly:" + padId);
|
||||
let readOnlyId = await db.get(`pad2readonly:${padId}`);
|
||||
|
||||
// there is no readOnly Entry in the database, let's create one
|
||||
if (readOnlyId == null) {
|
||||
readOnlyId = "r." + randomString(16);
|
||||
db.set("pad2readonly:" + padId, readOnlyId);
|
||||
db.set("readonly2pad:" + readOnlyId, padId);
|
||||
readOnlyId = `r.${randomString(16)}`;
|
||||
db.set(`pad2readonly:${padId}`, readOnlyId);
|
||||
db.set(`readonly2pad:${readOnlyId}`, padId);
|
||||
}
|
||||
|
||||
return readOnlyId;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* returns the padId for a read only id
|
||||
* @param {String} readOnlyId read only id
|
||||
*/
|
||||
exports.getPadId = function(readOnlyId) {
|
||||
return db.get("readonly2pad:" + readOnlyId);
|
||||
}
|
||||
exports.getPadId = function (readOnlyId) {
|
||||
return db.get(`readonly2pad:${readOnlyId}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* returns the padId and readonlyPadId in an object for any id
|
||||
* @param {String} padIdOrReadonlyPadId read only id or real pad id
|
||||
*/
|
||||
exports.getIds = async function(id) {
|
||||
let readonly = (id.indexOf("r.") === 0);
|
||||
exports.getIds = async function (id) {
|
||||
const readonly = (id.indexOf('r.') === 0);
|
||||
|
||||
// Might be null, if this is an unknown read-only id
|
||||
let readOnlyPadId = readonly ? id : await exports.getReadOnlyId(id);
|
||||
let padId = readonly ? await exports.getPadId(id) : id;
|
||||
const readOnlyPadId = readonly ? id : await exports.getReadOnlyId(id);
|
||||
const padId = readonly ? await exports.getPadId(id) : id;
|
||||
|
||||
return { readOnlyPadId, padId, readonly };
|
||||
}
|
||||
return {readOnlyPadId, padId, readonly};
|
||||
};
|
||||
|
|
|
@ -18,14 +18,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var authorManager = require("./AuthorManager");
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
|
||||
var padManager = require("./PadManager");
|
||||
var sessionManager = require("./SessionManager");
|
||||
var settings = require("../utils/Settings");
|
||||
const authorManager = require('./AuthorManager');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
|
||||
const padManager = require('./PadManager');
|
||||
const sessionManager = require('./SessionManager');
|
||||
const settings = require('../utils/Settings');
|
||||
const webaccess = require('../hooks/express/webaccess');
|
||||
var log4js = require('log4js');
|
||||
var authLogger = log4js.getLogger("auth");
|
||||
const log4js = require('log4js');
|
||||
const authLogger = log4js.getLogger('auth');
|
||||
|
||||
const DENY = Object.freeze({accessStatus: 'deny'});
|
||||
|
||||
|
@ -47,7 +47,7 @@ const DENY = Object.freeze({accessStatus: 'deny'});
|
|||
* WARNING: Tokens and session IDs MUST be kept secret, otherwise users will be able to impersonate
|
||||
* each other (which might allow them to gain privileges).
|
||||
*/
|
||||
exports.checkAccess = async function(padID, sessionCookie, token, userSettings) {
|
||||
exports.checkAccess = async function (padID, sessionCookie, token, userSettings) {
|
||||
if (!padID) {
|
||||
authLogger.debug('access denied: missing padID');
|
||||
return DENY;
|
||||
|
|
|
@ -18,12 +18,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var customError = require("../utils/customError");
|
||||
const customError = require('../utils/customError');
|
||||
const promises = require('../utils/promises');
|
||||
var randomString = require("../utils/randomstring");
|
||||
var db = require("./DB");
|
||||
var groupManager = require("./GroupManager");
|
||||
var authorManager = require("./AuthorManager");
|
||||
const randomString = require('../utils/randomstring');
|
||||
const db = require('./DB');
|
||||
const groupManager = require('./GroupManager');
|
||||
const authorManager = require('./AuthorManager');
|
||||
|
||||
/**
|
||||
* Finds the author ID for a session with matching ID and group.
|
||||
|
@ -78,61 +78,61 @@ exports.findAuthorID = async (groupID, sessionCookie) => {
|
|||
return sessionInfo.authorID;
|
||||
};
|
||||
|
||||
exports.doesSessionExist = async function(sessionID) {
|
||||
//check if the database entry of this session exists
|
||||
let session = await db.get("session:" + sessionID);
|
||||
exports.doesSessionExist = async function (sessionID) {
|
||||
// check if the database entry of this session exists
|
||||
const session = await db.get(`session:${sessionID}`);
|
||||
return (session !== null);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new session between an author and a group
|
||||
*/
|
||||
exports.createSession = async function(groupID, authorID, validUntil) {
|
||||
exports.createSession = async function (groupID, authorID, validUntil) {
|
||||
// check if the group exists
|
||||
let groupExists = await groupManager.doesGroupExist(groupID);
|
||||
const groupExists = await groupManager.doesGroupExist(groupID);
|
||||
if (!groupExists) {
|
||||
throw new customError("groupID does not exist", "apierror");
|
||||
throw new customError('groupID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// check if the author exists
|
||||
let authorExists = await authorManager.doesAuthorExist(authorID);
|
||||
const authorExists = await authorManager.doesAuthorExist(authorID);
|
||||
if (!authorExists) {
|
||||
throw new customError("authorID does not exist", "apierror");
|
||||
throw new customError('authorID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// try to parse validUntil if it's not a number
|
||||
if (typeof validUntil !== "number") {
|
||||
if (typeof validUntil !== 'number') {
|
||||
validUntil = parseInt(validUntil);
|
||||
}
|
||||
|
||||
// check it's a valid number
|
||||
if (isNaN(validUntil)) {
|
||||
throw new customError("validUntil is not a number", "apierror");
|
||||
throw new customError('validUntil is not a number', 'apierror');
|
||||
}
|
||||
|
||||
// ensure this is not a negative number
|
||||
if (validUntil < 0) {
|
||||
throw new customError("validUntil is a negative number", "apierror");
|
||||
throw new customError('validUntil is a negative number', 'apierror');
|
||||
}
|
||||
|
||||
// ensure this is not a float value
|
||||
if (!is_int(validUntil)) {
|
||||
throw new customError("validUntil is a float value", "apierror");
|
||||
throw new customError('validUntil is a float value', 'apierror');
|
||||
}
|
||||
|
||||
// check if validUntil is in the future
|
||||
if (validUntil < Math.floor(Date.now() / 1000)) {
|
||||
throw new customError("validUntil is in the past", "apierror");
|
||||
throw new customError('validUntil is in the past', 'apierror');
|
||||
}
|
||||
|
||||
// generate sessionID
|
||||
let sessionID = "s." + randomString(16);
|
||||
const sessionID = `s.${randomString(16)}`;
|
||||
|
||||
// set the session into the database
|
||||
await db.set("session:" + sessionID, {"groupID": groupID, "authorID": authorID, "validUntil": validUntil});
|
||||
await db.set(`session:${sessionID}`, {groupID, authorID, validUntil});
|
||||
|
||||
// get the entry
|
||||
let group2sessions = await db.get("group2sessions:" + groupID);
|
||||
let group2sessions = await db.get(`group2sessions:${groupID}`);
|
||||
|
||||
/*
|
||||
* In some cases, the db layer could return "undefined" as well as "null".
|
||||
|
@ -144,115 +144,115 @@ exports.createSession = async function(groupID, authorID, validUntil) {
|
|||
*/
|
||||
if (!group2sessions || !group2sessions.sessionIDs) {
|
||||
// the entry doesn't exist so far, let's create it
|
||||
group2sessions = {sessionIDs : {}};
|
||||
group2sessions = {sessionIDs: {}};
|
||||
}
|
||||
|
||||
// add the entry for this session
|
||||
group2sessions.sessionIDs[sessionID] = 1;
|
||||
|
||||
// save the new element back
|
||||
await db.set("group2sessions:" + groupID, group2sessions);
|
||||
await db.set(`group2sessions:${groupID}`, group2sessions);
|
||||
|
||||
// get the author2sessions entry
|
||||
let author2sessions = await db.get("author2sessions:" + authorID);
|
||||
let author2sessions = await db.get(`author2sessions:${authorID}`);
|
||||
|
||||
if (author2sessions == null || author2sessions.sessionIDs == null) {
|
||||
// the entry doesn't exist so far, let's create it
|
||||
author2sessions = {sessionIDs : {}};
|
||||
author2sessions = {sessionIDs: {}};
|
||||
}
|
||||
|
||||
// add the entry for this session
|
||||
author2sessions.sessionIDs[sessionID] = 1;
|
||||
|
||||
//save the new element back
|
||||
await db.set("author2sessions:" + authorID, author2sessions);
|
||||
// save the new element back
|
||||
await db.set(`author2sessions:${authorID}`, author2sessions);
|
||||
|
||||
return { sessionID };
|
||||
}
|
||||
return {sessionID};
|
||||
};
|
||||
|
||||
exports.getSessionInfo = async function(sessionID) {
|
||||
exports.getSessionInfo = async function (sessionID) {
|
||||
// check if the database entry of this session exists
|
||||
let session = await db.get("session:" + sessionID);
|
||||
const session = await db.get(`session:${sessionID}`);
|
||||
|
||||
if (session == null) {
|
||||
// session does not exist
|
||||
throw new customError("sessionID does not exist", "apierror");
|
||||
throw new customError('sessionID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// everything is fine, return the sessioninfos
|
||||
return session;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes a session
|
||||
*/
|
||||
exports.deleteSession = async function(sessionID) {
|
||||
exports.deleteSession = async function (sessionID) {
|
||||
// ensure that the session exists
|
||||
let session = await db.get("session:" + sessionID);
|
||||
const session = await db.get(`session:${sessionID}`);
|
||||
if (session == null) {
|
||||
throw new customError("sessionID does not exist", "apierror");
|
||||
throw new customError('sessionID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
// everything is fine, use the sessioninfos
|
||||
let groupID = session.groupID;
|
||||
let authorID = session.authorID;
|
||||
const groupID = session.groupID;
|
||||
const authorID = session.authorID;
|
||||
|
||||
// get the group2sessions and author2sessions entries
|
||||
let group2sessions = await db.get("group2sessions:" + groupID);
|
||||
let author2sessions = await db.get("author2sessions:" + authorID);
|
||||
const group2sessions = await db.get(`group2sessions:${groupID}`);
|
||||
const author2sessions = await db.get(`author2sessions:${authorID}`);
|
||||
|
||||
// remove the session
|
||||
await db.remove("session:" + sessionID);
|
||||
await db.remove(`session:${sessionID}`);
|
||||
|
||||
// remove session from group2sessions
|
||||
if (group2sessions != null) { // Maybe the group was already deleted
|
||||
delete group2sessions.sessionIDs[sessionID];
|
||||
await db.set("group2sessions:" + groupID, group2sessions);
|
||||
await db.set(`group2sessions:${groupID}`, group2sessions);
|
||||
}
|
||||
|
||||
// remove session from author2sessions
|
||||
if (author2sessions != null) { // Maybe the author was already deleted
|
||||
delete author2sessions.sessionIDs[sessionID];
|
||||
await db.set("author2sessions:" + authorID, author2sessions);
|
||||
await db.set(`author2sessions:${authorID}`, author2sessions);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.listSessionsOfGroup = async function(groupID) {
|
||||
exports.listSessionsOfGroup = async function (groupID) {
|
||||
// check that the group exists
|
||||
let exists = await groupManager.doesGroupExist(groupID);
|
||||
const exists = await groupManager.doesGroupExist(groupID);
|
||||
if (!exists) {
|
||||
throw new customError("groupID does not exist", "apierror");
|
||||
throw new customError('groupID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
let sessions = await listSessionsWithDBKey("group2sessions:" + groupID);
|
||||
const sessions = await listSessionsWithDBKey(`group2sessions:${groupID}`);
|
||||
return sessions;
|
||||
}
|
||||
};
|
||||
|
||||
exports.listSessionsOfAuthor = async function(authorID) {
|
||||
exports.listSessionsOfAuthor = async function (authorID) {
|
||||
// check that the author exists
|
||||
let exists = await authorManager.doesAuthorExist(authorID)
|
||||
const exists = await authorManager.doesAuthorExist(authorID);
|
||||
if (!exists) {
|
||||
throw new customError("authorID does not exist", "apierror");
|
||||
throw new customError('authorID does not exist', 'apierror');
|
||||
}
|
||||
|
||||
let sessions = await listSessionsWithDBKey("author2sessions:" + authorID);
|
||||
const sessions = await listSessionsWithDBKey(`author2sessions:${authorID}`);
|
||||
return sessions;
|
||||
}
|
||||
};
|
||||
|
||||
// this function is basically the code listSessionsOfAuthor and listSessionsOfGroup has in common
|
||||
// required to return null rather than an empty object if there are none
|
||||
async function listSessionsWithDBKey(dbkey) {
|
||||
// get the group2sessions entry
|
||||
let sessionObject = await db.get(dbkey);
|
||||
let sessions = sessionObject ? sessionObject.sessionIDs : null;
|
||||
const sessionObject = await db.get(dbkey);
|
||||
const sessions = sessionObject ? sessionObject.sessionIDs : null;
|
||||
|
||||
// iterate through the sessions and get the sessioninfos
|
||||
for (let sessionID in sessions) {
|
||||
for (const sessionID in sessions) {
|
||||
try {
|
||||
let sessionInfo = await exports.getSessionInfo(sessionID);
|
||||
const sessionInfo = await exports.getSessionInfo(sessionID);
|
||||
sessions[sessionID] = sessionInfo;
|
||||
} catch (err) {
|
||||
if (err == "apierror: sessionID does not exist") {
|
||||
if (err == 'apierror: sessionID does not exist') {
|
||||
console.warn(`Found bad session ${sessionID} in ${dbkey}`);
|
||||
sessions[sessionID] = null;
|
||||
} else {
|
||||
|
|
|
@ -15,11 +15,11 @@ const logger = log4js.getLogger('SessionStore');
|
|||
|
||||
module.exports = class SessionStore extends Store {
|
||||
get(sid, fn) {
|
||||
logger.debug('GET ' + sid);
|
||||
DB.db.get('sessionstorage:' + sid, (err, sess) => {
|
||||
logger.debug(`GET ${sid}`);
|
||||
DB.db.get(`sessionstorage:${sid}`, (err, sess) => {
|
||||
if (sess) {
|
||||
sess.cookie.expires = ('string' == typeof sess.cookie.expires
|
||||
? new Date(sess.cookie.expires) : sess.cookie.expires);
|
||||
sess.cookie.expires = ('string' === typeof sess.cookie.expires
|
||||
? new Date(sess.cookie.expires) : sess.cookie.expires);
|
||||
if (!sess.cookie.expires || new Date() < sess.cookie.expires) {
|
||||
fn(null, sess);
|
||||
} else {
|
||||
|
@ -32,12 +32,12 @@ module.exports = class SessionStore extends Store {
|
|||
}
|
||||
|
||||
set(sid, sess, fn) {
|
||||
logger.debug('SET ' + sid);
|
||||
DB.db.set('sessionstorage:' + sid, sess, fn);
|
||||
logger.debug(`SET ${sid}`);
|
||||
DB.db.set(`sessionstorage:${sid}`, sess, fn);
|
||||
}
|
||||
|
||||
destroy(sid, fn) {
|
||||
logger.debug('DESTROY ' + sid);
|
||||
DB.db.remove('sessionstorage:' + sid, fn);
|
||||
logger.debug(`DESTROY ${sid}`);
|
||||
DB.db.remove(`sessionstorage:${sid}`, fn);
|
||||
}
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -19,84 +19,84 @@
|
|||
* require("./index").require("./examples/foo.ejs")
|
||||
*/
|
||||
|
||||
var ejs = require("ejs");
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
|
||||
var resolve = require("resolve");
|
||||
var settings = require('../utils/Settings');
|
||||
const ejs = require('ejs');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
|
||||
const resolve = require('resolve');
|
||||
const settings = require('../utils/Settings');
|
||||
|
||||
const templateCache = new Map()
|
||||
const templateCache = new Map();
|
||||
|
||||
exports.info = {
|
||||
__output_stack: [],
|
||||
block_stack: [],
|
||||
file_stack: [],
|
||||
args: []
|
||||
args: [],
|
||||
};
|
||||
|
||||
function getCurrentFile() {
|
||||
return exports.info.file_stack[exports.info.file_stack.length-1];
|
||||
return exports.info.file_stack[exports.info.file_stack.length - 1];
|
||||
}
|
||||
|
||||
function createBlockId(name) {
|
||||
return getCurrentFile().path + '|' + name;
|
||||
return `${getCurrentFile().path}|${name}`;
|
||||
}
|
||||
|
||||
exports._init = function (b, recursive) {
|
||||
exports.info.__output_stack.push(exports.info.__output);
|
||||
exports.info.__output = b;
|
||||
}
|
||||
};
|
||||
|
||||
exports._exit = function (b, recursive) {
|
||||
getCurrentFile().inherit.forEach(function (item) {
|
||||
getCurrentFile().inherit.forEach((item) => {
|
||||
exports._require(item.name, item.args);
|
||||
});
|
||||
exports.info.__output = exports.info.__output_stack.pop();
|
||||
}
|
||||
};
|
||||
|
||||
exports.begin_capture = function() {
|
||||
exports.begin_capture = function () {
|
||||
exports.info.__output_stack.push(exports.info.__output.concat());
|
||||
exports.info.__output.splice(0, exports.info.__output.length);
|
||||
}
|
||||
};
|
||||
|
||||
exports.end_capture = function () {
|
||||
var res = exports.info.__output.join("");
|
||||
const res = exports.info.__output.join('');
|
||||
exports.info.__output.splice.apply(
|
||||
exports.info.__output,
|
||||
[0, exports.info.__output.length].concat(exports.info.__output_stack.pop()));
|
||||
exports.info.__output,
|
||||
[0, exports.info.__output.length].concat(exports.info.__output_stack.pop()));
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
exports.begin_define_block = function (name) {
|
||||
exports.info.block_stack.push(name);
|
||||
exports.begin_capture();
|
||||
}
|
||||
};
|
||||
|
||||
exports.end_define_block = function () {
|
||||
var content = exports.end_capture();
|
||||
const content = exports.end_capture();
|
||||
return content;
|
||||
}
|
||||
};
|
||||
|
||||
exports.end_block = function () {
|
||||
var name = exports.info.block_stack.pop();
|
||||
var renderContext = exports.info.args[exports.info.args.length-1];
|
||||
var args = {content: exports.end_define_block(), renderContext: renderContext};
|
||||
hooks.callAll("eejsBlock_" + name, args);
|
||||
const name = exports.info.block_stack.pop();
|
||||
const renderContext = exports.info.args[exports.info.args.length - 1];
|
||||
const args = {content: exports.end_define_block(), renderContext};
|
||||
hooks.callAll(`eejsBlock_${name}`, args);
|
||||
exports.info.__output.push(args.content);
|
||||
}
|
||||
};
|
||||
|
||||
exports.begin_block = exports.begin_define_block;
|
||||
|
||||
exports.inherit = function (name, args) {
|
||||
getCurrentFile().inherit.push({name:name, args:args});
|
||||
}
|
||||
getCurrentFile().inherit.push({name, args});
|
||||
};
|
||||
|
||||
exports.require = function (name, args, mod) {
|
||||
if (args == undefined) args = {};
|
||||
|
||||
var basedir = __dirname;
|
||||
var paths = [];
|
||||
let basedir = __dirname;
|
||||
let paths = [];
|
||||
|
||||
if (exports.info.file_stack.length) {
|
||||
basedir = path.dirname(getCurrentFile().path);
|
||||
|
@ -106,43 +106,43 @@ exports.require = function (name, args, mod) {
|
|||
paths = mod.paths;
|
||||
}
|
||||
|
||||
var ejspath = resolve.sync(
|
||||
name,
|
||||
{
|
||||
paths : paths,
|
||||
basedir : basedir,
|
||||
extensions : [ '.html', '.ejs' ],
|
||||
}
|
||||
)
|
||||
const ejspath = resolve.sync(
|
||||
name,
|
||||
{
|
||||
paths,
|
||||
basedir,
|
||||
extensions: ['.html', '.ejs'],
|
||||
},
|
||||
);
|
||||
|
||||
args.e = exports;
|
||||
args.require = require;
|
||||
|
||||
let template
|
||||
if (settings.maxAge !== 0){ // don't cache if maxAge is 0
|
||||
let template;
|
||||
if (settings.maxAge !== 0) { // don't cache if maxAge is 0
|
||||
if (!templateCache.has(ejspath)) {
|
||||
template = '<% e._init(__output); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>';
|
||||
templateCache.set(ejspath, template)
|
||||
template = `<% e._init(__output); %>${fs.readFileSync(ejspath).toString()}<% e._exit(); %>`;
|
||||
templateCache.set(ejspath, template);
|
||||
} else {
|
||||
template = templateCache.get(ejspath)
|
||||
template = templateCache.get(ejspath);
|
||||
}
|
||||
}else{
|
||||
template = '<% e._init(__output); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>';
|
||||
} else {
|
||||
template = `<% e._init(__output); %>${fs.readFileSync(ejspath).toString()}<% e._exit(); %>`;
|
||||
}
|
||||
|
||||
exports.info.args.push(args);
|
||||
exports.info.file_stack.push({path: ejspath, inherit: []});
|
||||
if(settings.maxAge !== 0){
|
||||
var res = ejs.render(template, args, { cache: true, filename: ejspath });
|
||||
}else{
|
||||
var res = ejs.render(template, args, { cache: false, filename: ejspath });
|
||||
if (settings.maxAge !== 0) {
|
||||
var res = ejs.render(template, args, {cache: true, filename: ejspath});
|
||||
} else {
|
||||
var res = ejs.render(template, args, {cache: false, filename: ejspath});
|
||||
}
|
||||
exports.info.file_stack.pop();
|
||||
exports.info.args.pop();
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
exports._require = function (name, args) {
|
||||
exports.info.__output.push(exports.require(name, args));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -18,131 +18,118 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var absolutePaths = require('../utils/AbsolutePaths');
|
||||
var fs = require("fs");
|
||||
var api = require("../db/API");
|
||||
var log4js = require('log4js');
|
||||
var padManager = require("../db/PadManager");
|
||||
var randomString = require("../utils/randomstring");
|
||||
var argv = require('../utils/Cli').argv;
|
||||
var createHTTPError = require('http-errors');
|
||||
const absolutePaths = require('../utils/AbsolutePaths');
|
||||
const fs = require('fs');
|
||||
const api = require('../db/API');
|
||||
const log4js = require('log4js');
|
||||
const padManager = require('../db/PadManager');
|
||||
const randomString = require('../utils/randomstring');
|
||||
const argv = require('../utils/Cli').argv;
|
||||
const createHTTPError = require('http-errors');
|
||||
|
||||
var apiHandlerLogger = log4js.getLogger('APIHandler');
|
||||
const apiHandlerLogger = log4js.getLogger('APIHandler');
|
||||
|
||||
//ensure we have an apikey
|
||||
var apikey = null;
|
||||
var apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || "./APIKEY.txt");
|
||||
// ensure we have an apikey
|
||||
let apikey = null;
|
||||
const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt');
|
||||
|
||||
try {
|
||||
apikey = fs.readFileSync(apikeyFilename,"utf8");
|
||||
apikey = fs.readFileSync(apikeyFilename, 'utf8');
|
||||
apiHandlerLogger.info(`Api key file read from: "${apikeyFilename}"`);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
apiHandlerLogger.info(`Api key file "${apikeyFilename}" not found. Creating with random contents.`);
|
||||
apikey = randomString(32);
|
||||
fs.writeFileSync(apikeyFilename,apikey,"utf8");
|
||||
fs.writeFileSync(apikeyFilename, apikey, 'utf8');
|
||||
}
|
||||
|
||||
// a list of all functions
|
||||
var version = {};
|
||||
const version = {};
|
||||
|
||||
version["1"] = Object.assign({},
|
||||
{ "createGroup" : []
|
||||
, "createGroupIfNotExistsFor" : ["groupMapper"]
|
||||
, "deleteGroup" : ["groupID"]
|
||||
, "listPads" : ["groupID"]
|
||||
, "createPad" : ["padID", "text"]
|
||||
, "createGroupPad" : ["groupID", "padName", "text"]
|
||||
, "createAuthor" : ["name"]
|
||||
, "createAuthorIfNotExistsFor": ["authorMapper" , "name"]
|
||||
, "listPadsOfAuthor" : ["authorID"]
|
||||
, "createSession" : ["groupID", "authorID", "validUntil"]
|
||||
, "deleteSession" : ["sessionID"]
|
||||
, "getSessionInfo" : ["sessionID"]
|
||||
, "listSessionsOfGroup" : ["groupID"]
|
||||
, "listSessionsOfAuthor" : ["authorID"]
|
||||
, "getText" : ["padID", "rev"]
|
||||
, "setText" : ["padID", "text"]
|
||||
, "getHTML" : ["padID", "rev"]
|
||||
, "setHTML" : ["padID", "html"]
|
||||
, "getRevisionsCount" : ["padID"]
|
||||
, "getLastEdited" : ["padID"]
|
||||
, "deletePad" : ["padID"]
|
||||
, "getReadOnlyID" : ["padID"]
|
||||
, "setPublicStatus" : ["padID", "publicStatus"]
|
||||
, "getPublicStatus" : ["padID"]
|
||||
, "listAuthorsOfPad" : ["padID"]
|
||||
, "padUsersCount" : ["padID"]
|
||||
}
|
||||
version['1'] = Object.assign({},
|
||||
{createGroup: [],
|
||||
createGroupIfNotExistsFor: ['groupMapper'],
|
||||
deleteGroup: ['groupID'],
|
||||
listPads: ['groupID'],
|
||||
createPad: ['padID', 'text'],
|
||||
createGroupPad: ['groupID', 'padName', 'text'],
|
||||
createAuthor: ['name'],
|
||||
createAuthorIfNotExistsFor: ['authorMapper', 'name'],
|
||||
listPadsOfAuthor: ['authorID'],
|
||||
createSession: ['groupID', 'authorID', 'validUntil'],
|
||||
deleteSession: ['sessionID'],
|
||||
getSessionInfo: ['sessionID'],
|
||||
listSessionsOfGroup: ['groupID'],
|
||||
listSessionsOfAuthor: ['authorID'],
|
||||
getText: ['padID', 'rev'],
|
||||
setText: ['padID', 'text'],
|
||||
getHTML: ['padID', 'rev'],
|
||||
setHTML: ['padID', 'html'],
|
||||
getRevisionsCount: ['padID'],
|
||||
getLastEdited: ['padID'],
|
||||
deletePad: ['padID'],
|
||||
getReadOnlyID: ['padID'],
|
||||
setPublicStatus: ['padID', 'publicStatus'],
|
||||
getPublicStatus: ['padID'],
|
||||
listAuthorsOfPad: ['padID'],
|
||||
padUsersCount: ['padID']},
|
||||
);
|
||||
|
||||
version["1.1"] = Object.assign({}, version["1"],
|
||||
{ "getAuthorName" : ["authorID"]
|
||||
, "padUsers" : ["padID"]
|
||||
, "sendClientsMessage" : ["padID", "msg"]
|
||||
, "listAllGroups" : []
|
||||
}
|
||||
version['1.1'] = Object.assign({}, version['1'],
|
||||
{getAuthorName: ['authorID'],
|
||||
padUsers: ['padID'],
|
||||
sendClientsMessage: ['padID', 'msg'],
|
||||
listAllGroups: []},
|
||||
);
|
||||
|
||||
version["1.2"] = Object.assign({}, version["1.1"],
|
||||
{ "checkToken" : []
|
||||
}
|
||||
version['1.2'] = Object.assign({}, version['1.1'],
|
||||
{checkToken: []},
|
||||
);
|
||||
|
||||
version["1.2.1"] = Object.assign({}, version["1.2"],
|
||||
{ "listAllPads" : []
|
||||
}
|
||||
version['1.2.1'] = Object.assign({}, version['1.2'],
|
||||
{listAllPads: []},
|
||||
);
|
||||
|
||||
version["1.2.7"] = Object.assign({}, version["1.2.1"],
|
||||
{ "createDiffHTML" : ["padID", "startRev", "endRev"]
|
||||
, "getChatHistory" : ["padID", "start", "end"]
|
||||
, "getChatHead" : ["padID"]
|
||||
}
|
||||
version['1.2.7'] = Object.assign({}, version['1.2.1'],
|
||||
{createDiffHTML: ['padID', 'startRev', 'endRev'],
|
||||
getChatHistory: ['padID', 'start', 'end'],
|
||||
getChatHead: ['padID']},
|
||||
);
|
||||
|
||||
version["1.2.8"] = Object.assign({}, version["1.2.7"],
|
||||
{ "getAttributePool" : ["padID"]
|
||||
, "getRevisionChangeset" : ["padID", "rev"]
|
||||
}
|
||||
version['1.2.8'] = Object.assign({}, version['1.2.7'],
|
||||
{getAttributePool: ['padID'],
|
||||
getRevisionChangeset: ['padID', 'rev']},
|
||||
);
|
||||
|
||||
version["1.2.9"] = Object.assign({}, version["1.2.8"],
|
||||
{ "copyPad" : ["sourceID", "destinationID", "force"]
|
||||
, "movePad" : ["sourceID", "destinationID", "force"]
|
||||
}
|
||||
version['1.2.9'] = Object.assign({}, version['1.2.8'],
|
||||
{copyPad: ['sourceID', 'destinationID', 'force'],
|
||||
movePad: ['sourceID', 'destinationID', 'force']},
|
||||
);
|
||||
|
||||
version["1.2.10"] = Object.assign({}, version["1.2.9"],
|
||||
{ "getPadID" : ["roID"]
|
||||
}
|
||||
version['1.2.10'] = Object.assign({}, version['1.2.9'],
|
||||
{getPadID: ['roID']},
|
||||
);
|
||||
|
||||
version["1.2.11"] = Object.assign({}, version["1.2.10"],
|
||||
{ "getSavedRevisionsCount" : ["padID"]
|
||||
, "listSavedRevisions" : ["padID"]
|
||||
, "saveRevision" : ["padID", "rev"]
|
||||
, "restoreRevision" : ["padID", "rev"]
|
||||
}
|
||||
version['1.2.11'] = Object.assign({}, version['1.2.10'],
|
||||
{getSavedRevisionsCount: ['padID'],
|
||||
listSavedRevisions: ['padID'],
|
||||
saveRevision: ['padID', 'rev'],
|
||||
restoreRevision: ['padID', 'rev']},
|
||||
);
|
||||
|
||||
version["1.2.12"] = Object.assign({}, version["1.2.11"],
|
||||
{ "appendChatMessage" : ["padID", "text", "authorID", "time"]
|
||||
}
|
||||
version['1.2.12'] = Object.assign({}, version['1.2.11'],
|
||||
{appendChatMessage: ['padID', 'text', 'authorID', 'time']},
|
||||
);
|
||||
|
||||
version["1.2.13"] = Object.assign({}, version["1.2.12"],
|
||||
{ "appendText" : ["padID", "text"]
|
||||
}
|
||||
version['1.2.13'] = Object.assign({}, version['1.2.12'],
|
||||
{appendText: ['padID', 'text']},
|
||||
);
|
||||
|
||||
version["1.2.14"] = Object.assign({}, version["1.2.13"],
|
||||
{ "getStats" : []
|
||||
}
|
||||
version['1.2.14'] = Object.assign({}, version['1.2.13'],
|
||||
{getStats: []},
|
||||
);
|
||||
|
||||
version["1.2.15"] = Object.assign({}, version["1.2.14"],
|
||||
{ "copyPadWithoutHistory" : ["sourceID", "destinationID", "force"]
|
||||
}
|
||||
version['1.2.15'] = Object.assign({}, version['1.2.14'],
|
||||
{copyPadWithoutHistory: ['sourceID', 'destinationID', 'force']},
|
||||
);
|
||||
|
||||
// set the latest available API version here
|
||||
|
@ -158,7 +145,7 @@ exports.version = version;
|
|||
* @req express request object
|
||||
* @res express response object
|
||||
*/
|
||||
exports.handle = async function(apiVersion, functionName, fields, req, res) {
|
||||
exports.handle = async function (apiVersion, functionName, fields, req, res) {
|
||||
// say goodbye if this is an unknown API version
|
||||
if (!(apiVersion in version)) {
|
||||
throw new createHTTPError.NotFound('no such api version');
|
||||
|
@ -170,31 +157,29 @@ exports.handle = async function(apiVersion, functionName, fields, req, res) {
|
|||
}
|
||||
|
||||
// check the api key!
|
||||
fields["apikey"] = fields["apikey"] || fields["api_key"];
|
||||
fields.apikey = fields.apikey || fields.api_key;
|
||||
|
||||
if (fields["apikey"] !== apikey.trim()) {
|
||||
if (fields.apikey !== apikey.trim()) {
|
||||
throw new createHTTPError.Unauthorized('no or wrong API Key');
|
||||
}
|
||||
|
||||
// sanitize any padIDs before continuing
|
||||
if (fields["padID"]) {
|
||||
fields["padID"] = await padManager.sanitizePadId(fields["padID"]);
|
||||
if (fields.padID) {
|
||||
fields.padID = await padManager.sanitizePadId(fields.padID);
|
||||
}
|
||||
// there was an 'else' here before - removed it to ensure
|
||||
// that this sanitize step can't be circumvented by forcing
|
||||
// the first branch to be taken
|
||||
if (fields["padName"]) {
|
||||
fields["padName"] = await padManager.sanitizePadId(fields["padName"]);
|
||||
if (fields.padName) {
|
||||
fields.padName = await padManager.sanitizePadId(fields.padName);
|
||||
}
|
||||
|
||||
// put the function parameters in an array
|
||||
var functionParams = version[apiVersion][functionName].map(function (field) {
|
||||
return fields[field]
|
||||
});
|
||||
const functionParams = version[apiVersion][functionName].map((field) => fields[field]);
|
||||
|
||||
// call the api function
|
||||
return api[functionName].apply(this, functionParams);
|
||||
}
|
||||
};
|
||||
|
||||
exports.exportedForTestingOnly = {
|
||||
apiKey: apikey,
|
||||
|
|
|
@ -19,15 +19,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var exporthtml = require("../utils/ExportHtml");
|
||||
var exporttxt = require("../utils/ExportTxt");
|
||||
var exportEtherpad = require("../utils/ExportEtherpad");
|
||||
var fs = require("fs");
|
||||
var settings = require('../utils/Settings');
|
||||
var os = require('os');
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
|
||||
var TidyHtml = require('../utils/TidyHtml');
|
||||
const util = require("util");
|
||||
const exporthtml = require('../utils/ExportHtml');
|
||||
const exporttxt = require('../utils/ExportTxt');
|
||||
const exportEtherpad = require('../utils/ExportEtherpad');
|
||||
const fs = require('fs');
|
||||
const settings = require('../utils/Settings');
|
||||
const os = require('os');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
const TidyHtml = require('../utils/TidyHtml');
|
||||
const util = require('util');
|
||||
|
||||
const fsp_writeFile = util.promisify(fs.writeFile);
|
||||
const fsp_unlink = util.promisify(fs.unlink);
|
||||
|
@ -36,12 +36,12 @@ let convertor = null;
|
|||
|
||||
// load abiword only if it is enabled
|
||||
if (settings.abiword != null) {
|
||||
convertor = require("../utils/Abiword");
|
||||
convertor = require('../utils/Abiword');
|
||||
}
|
||||
|
||||
// Use LibreOffice if an executable has been defined in the settings
|
||||
if (settings.soffice != null) {
|
||||
convertor = require("../utils/LibreOffice");
|
||||
convertor = require('../utils/LibreOffice');
|
||||
}
|
||||
|
||||
const tempDirectory = os.tmpdir();
|
||||
|
@ -51,10 +51,10 @@ const tempDirectory = os.tmpdir();
|
|||
*/
|
||||
async function doExport(req, res, padId, readOnlyId, type) {
|
||||
// avoid naming the read-only file as the original pad's id
|
||||
var fileName = readOnlyId ? readOnlyId : padId;
|
||||
let fileName = readOnlyId ? readOnlyId : padId;
|
||||
|
||||
// allow fileName to be overwritten by a hook, the type type is kept static for security reasons
|
||||
let hookFileName = await hooks.aCallFirst("exportFileName", padId);
|
||||
const hookFileName = await hooks.aCallFirst('exportFileName', padId);
|
||||
|
||||
// if fileName is set then set it to the padId, note that fileName is returned as an array.
|
||||
if (hookFileName.length) {
|
||||
|
@ -62,15 +62,15 @@ async function doExport(req, res, padId, readOnlyId, type) {
|
|||
}
|
||||
|
||||
// tell the browser that this is a downloadable file
|
||||
res.attachment(fileName + "." + type);
|
||||
res.attachment(`${fileName}.${type}`);
|
||||
|
||||
// if this is a plain text export, we can do this directly
|
||||
// We have to over engineer this because tabs are stored as attributes and not plain text
|
||||
if (type === "etherpad") {
|
||||
let pad = await exportEtherpad.getPadRaw(padId);
|
||||
if (type === 'etherpad') {
|
||||
const pad = await exportEtherpad.getPadRaw(padId);
|
||||
res.send(pad);
|
||||
} else if (type === "txt") {
|
||||
let txt = await exporttxt.getPadTXTDocument(padId, req.params.rev);
|
||||
} else if (type === 'txt') {
|
||||
const txt = await exporttxt.getPadTXTDocument(padId, req.params.rev);
|
||||
res.send(txt);
|
||||
} else {
|
||||
// render the html document
|
||||
|
@ -79,17 +79,17 @@ async function doExport(req, res, padId, readOnlyId, type) {
|
|||
// decide what to do with the html export
|
||||
|
||||
// if this is a html export, we can send this from here directly
|
||||
if (type === "html") {
|
||||
if (type === 'html') {
|
||||
// do any final changes the plugin might want to make
|
||||
let newHTML = await hooks.aCallFirst("exportHTMLSend", html);
|
||||
const newHTML = await hooks.aCallFirst('exportHTMLSend', html);
|
||||
if (newHTML.length) html = newHTML;
|
||||
res.send(html);
|
||||
throw "stop";
|
||||
throw 'stop';
|
||||
}
|
||||
|
||||
// else write the html export to a file
|
||||
let randNum = Math.floor(Math.random()*0xFFFFFFFF);
|
||||
let srcFile = tempDirectory + "/etherpad_export_" + randNum + ".html";
|
||||
const randNum = Math.floor(Math.random() * 0xFFFFFFFF);
|
||||
const srcFile = `${tempDirectory}/etherpad_export_${randNum}.html`;
|
||||
await fsp_writeFile(srcFile, html);
|
||||
|
||||
// Tidy up the exported HTML
|
||||
|
@ -98,42 +98,42 @@ async function doExport(req, res, padId, readOnlyId, type) {
|
|||
await TidyHtml.tidy(srcFile);
|
||||
|
||||
// send the convert job to the convertor (abiword, libreoffice, ..)
|
||||
let destFile = tempDirectory + "/etherpad_export_" + randNum + "." + type;
|
||||
const destFile = `${tempDirectory}/etherpad_export_${randNum}.${type}`;
|
||||
|
||||
// Allow plugins to overwrite the convert in export process
|
||||
let result = await hooks.aCallAll("exportConvert", { srcFile, destFile, req, res });
|
||||
const result = await hooks.aCallAll('exportConvert', {srcFile, destFile, req, res});
|
||||
if (result.length > 0) {
|
||||
// console.log("export handled by plugin", destFile);
|
||||
handledByPlugin = true;
|
||||
} else {
|
||||
// @TODO no Promise interface for convertors (yet)
|
||||
await new Promise((resolve, reject) => {
|
||||
convertor.convertFile(srcFile, destFile, type, function(err) {
|
||||
err ? reject("convertFailed") : resolve();
|
||||
convertor.convertFile(srcFile, destFile, type, (err) => {
|
||||
err ? reject('convertFailed') : resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// send the file
|
||||
let sendFile = util.promisify(res.sendFile);
|
||||
const sendFile = util.promisify(res.sendFile);
|
||||
await res.sendFile(destFile, null);
|
||||
|
||||
// clean up temporary files
|
||||
await fsp_unlink(srcFile);
|
||||
|
||||
// 100ms delay to accommodate for slow windows fs
|
||||
if (os.type().indexOf("Windows") > -1) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
if (os.type().indexOf('Windows') > -1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
await fsp_unlink(destFile);
|
||||
}
|
||||
}
|
||||
|
||||
exports.doExport = function(req, res, padId, readOnlyId, type) {
|
||||
doExport(req, res, padId, readOnlyId, type).catch(err => {
|
||||
if (err !== "stop") {
|
||||
exports.doExport = function (req, res, padId, readOnlyId, type) {
|
||||
doExport(req, res, padId, readOnlyId, type).catch((err) => {
|
||||
if (err !== 'stop') {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -20,36 +20,36 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var padManager = require("../db/PadManager")
|
||||
, padMessageHandler = require("./PadMessageHandler")
|
||||
, fs = require("fs")
|
||||
, path = require("path")
|
||||
, settings = require('../utils/Settings')
|
||||
, formidable = require('formidable')
|
||||
, os = require("os")
|
||||
, importHtml = require("../utils/ImportHtml")
|
||||
, importEtherpad = require("../utils/ImportEtherpad")
|
||||
, log4js = require("log4js")
|
||||
, hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js")
|
||||
, util = require("util");
|
||||
const padManager = require('../db/PadManager');
|
||||
const padMessageHandler = require('./PadMessageHandler');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const settings = require('../utils/Settings');
|
||||
const formidable = require('formidable');
|
||||
const os = require('os');
|
||||
const importHtml = require('../utils/ImportHtml');
|
||||
const importEtherpad = require('../utils/ImportEtherpad');
|
||||
const log4js = require('log4js');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks.js');
|
||||
const util = require('util');
|
||||
|
||||
let fsp_exists = util.promisify(fs.exists);
|
||||
let fsp_rename = util.promisify(fs.rename);
|
||||
let fsp_readFile = util.promisify(fs.readFile);
|
||||
let fsp_unlink = util.promisify(fs.unlink)
|
||||
const fsp_exists = util.promisify(fs.exists);
|
||||
const fsp_rename = util.promisify(fs.rename);
|
||||
const fsp_readFile = util.promisify(fs.readFile);
|
||||
const fsp_unlink = util.promisify(fs.unlink);
|
||||
|
||||
let convertor = null;
|
||||
let exportExtension = "htm";
|
||||
let exportExtension = 'htm';
|
||||
|
||||
// load abiword only if it is enabled and if soffice is disabled
|
||||
if (settings.abiword != null && settings.soffice === null) {
|
||||
convertor = require("../utils/Abiword");
|
||||
convertor = require('../utils/Abiword');
|
||||
}
|
||||
|
||||
// load soffice only if it is enabled
|
||||
if (settings.soffice != null) {
|
||||
convertor = require("../utils/LibreOffice");
|
||||
exportExtension = "html";
|
||||
convertor = require('../utils/LibreOffice');
|
||||
exportExtension = 'html';
|
||||
}
|
||||
|
||||
const tmpDirectory = os.tmpdir();
|
||||
|
@ -58,28 +58,28 @@ const tmpDirectory = os.tmpdir();
|
|||
* do a requested import
|
||||
*/
|
||||
async function doImport(req, res, padId) {
|
||||
var apiLogger = log4js.getLogger("ImportHandler");
|
||||
const apiLogger = log4js.getLogger('ImportHandler');
|
||||
|
||||
// pipe to a file
|
||||
// convert file to html via abiword or soffice
|
||||
// set html in the pad
|
||||
var randNum = Math.floor(Math.random()*0xFFFFFFFF);
|
||||
const randNum = Math.floor(Math.random() * 0xFFFFFFFF);
|
||||
|
||||
// setting flag for whether to use convertor or not
|
||||
let useConvertor = (convertor != null);
|
||||
|
||||
let form = new formidable.IncomingForm();
|
||||
const form = new formidable.IncomingForm();
|
||||
form.keepExtensions = true;
|
||||
form.uploadDir = tmpDirectory;
|
||||
form.maxFileSize = settings.importMaxFileSize;
|
||||
|
||||
|
||||
// Ref: https://github.com/node-formidable/formidable/issues/469
|
||||
// Crash in Etherpad was Uploading Error: Error: Request aborted
|
||||
// [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed
|
||||
form.onPart = part => {
|
||||
form.onPart = (part) => {
|
||||
form.handlePart(part);
|
||||
if (part.filename !== undefined) {
|
||||
form.openedFiles[form.openedFiles.length - 1]._writeStream.on('error', err => {
|
||||
form.openedFiles[form.openedFiles.length - 1]._writeStream.on('error', (err) => {
|
||||
form.emit('error', err);
|
||||
});
|
||||
}
|
||||
|
@ -87,23 +87,23 @@ async function doImport(req, res, padId) {
|
|||
|
||||
// locally wrapped Promise, since form.parse requires a callback
|
||||
let srcFile = await new Promise((resolve, reject) => {
|
||||
form.parse(req, function(err, fields, files) {
|
||||
form.parse(req, (err, fields, files) => {
|
||||
if (err || files.file === undefined) {
|
||||
// the upload failed, stop at this point
|
||||
if (err) {
|
||||
console.warn("Uploading Error: " + err.stack);
|
||||
console.warn(`Uploading Error: ${err.stack}`);
|
||||
}
|
||||
|
||||
// I hate doing indexOf here but I can't see anything to use...
|
||||
if (err && err.stack && err.stack.indexOf("maxFileSize") !== -1) {
|
||||
reject("maxFileSize");
|
||||
if (err && err.stack && err.stack.indexOf('maxFileSize') !== -1) {
|
||||
reject('maxFileSize');
|
||||
}
|
||||
|
||||
reject("uploadFailed");
|
||||
reject('uploadFailed');
|
||||
}
|
||||
if(!files.file){ // might not be a graceful fix but it works
|
||||
reject("uploadFailed");
|
||||
}else{
|
||||
if (!files.file) { // might not be a graceful fix but it works
|
||||
reject('uploadFailed');
|
||||
} else {
|
||||
resolve(files.file.path);
|
||||
}
|
||||
});
|
||||
|
@ -111,47 +111,47 @@ async function doImport(req, res, padId) {
|
|||
|
||||
// ensure this is a file ending we know, else we change the file ending to .txt
|
||||
// this allows us to accept source code files like .c or .java
|
||||
let fileEnding = path.extname(srcFile).toLowerCase()
|
||||
, knownFileEndings = [".txt", ".doc", ".docx", ".pdf", ".odt", ".html", ".htm", ".etherpad", ".rtf"]
|
||||
, fileEndingUnknown = (knownFileEndings.indexOf(fileEnding) < 0);
|
||||
const fileEnding = path.extname(srcFile).toLowerCase();
|
||||
const knownFileEndings = ['.txt', '.doc', '.docx', '.pdf', '.odt', '.html', '.htm', '.etherpad', '.rtf'];
|
||||
const fileEndingUnknown = (knownFileEndings.indexOf(fileEnding) < 0);
|
||||
|
||||
if (fileEndingUnknown) {
|
||||
// the file ending is not known
|
||||
|
||||
if (settings.allowUnknownFileEnds === true) {
|
||||
// we need to rename this file with a .txt ending
|
||||
let oldSrcFile = srcFile;
|
||||
const oldSrcFile = srcFile;
|
||||
|
||||
srcFile = path.join(path.dirname(srcFile), path.basename(srcFile, fileEnding) + ".txt");
|
||||
srcFile = path.join(path.dirname(srcFile), `${path.basename(srcFile, fileEnding)}.txt`);
|
||||
await fsp_rename(oldSrcFile, srcFile);
|
||||
} else {
|
||||
console.warn("Not allowing unknown file type to be imported", fileEnding);
|
||||
throw "uploadFailed";
|
||||
console.warn('Not allowing unknown file type to be imported', fileEnding);
|
||||
throw 'uploadFailed';
|
||||
}
|
||||
}
|
||||
|
||||
let destFile = path.join(tmpDirectory, "etherpad_import_" + randNum + "." + exportExtension);
|
||||
const destFile = path.join(tmpDirectory, `etherpad_import_${randNum}.${exportExtension}`);
|
||||
|
||||
// Logic for allowing external Import Plugins
|
||||
let result = await hooks.aCallAll("import", { srcFile, destFile, fileEnding });
|
||||
let importHandledByPlugin = (result.length > 0); // This feels hacky and wrong..
|
||||
const result = await hooks.aCallAll('import', {srcFile, destFile, fileEnding});
|
||||
const importHandledByPlugin = (result.length > 0); // This feels hacky and wrong..
|
||||
|
||||
let fileIsEtherpad = (fileEnding === ".etherpad");
|
||||
let fileIsHTML = (fileEnding === ".html" || fileEnding === ".htm");
|
||||
let fileIsTXT = (fileEnding === ".txt");
|
||||
const fileIsEtherpad = (fileEnding === '.etherpad');
|
||||
const fileIsHTML = (fileEnding === '.html' || fileEnding === '.htm');
|
||||
const fileIsTXT = (fileEnding === '.txt');
|
||||
|
||||
if (fileIsEtherpad) {
|
||||
// we do this here so we can see if the pad has quite a few edits
|
||||
let _pad = await padManager.getPad(padId);
|
||||
let headCount = _pad.head;
|
||||
const _pad = await padManager.getPad(padId);
|
||||
const headCount = _pad.head;
|
||||
|
||||
if (headCount >= 10) {
|
||||
apiLogger.warn("Direct database Import attempt of a pad that already has content, we won't be doing this");
|
||||
throw "padHasData";
|
||||
throw 'padHasData';
|
||||
}
|
||||
|
||||
const fsp_readFile = util.promisify(fs.readFile);
|
||||
let _text = await fsp_readFile(srcFile, "utf8");
|
||||
const _text = await fsp_readFile(srcFile, 'utf8');
|
||||
req.directDatabaseAccess = true;
|
||||
await importEtherpad.setPadRaw(padId, _text);
|
||||
}
|
||||
|
@ -170,11 +170,11 @@ async function doImport(req, res, padId) {
|
|||
} else {
|
||||
// @TODO - no Promise interface for convertors (yet)
|
||||
await new Promise((resolve, reject) => {
|
||||
convertor.convertFile(srcFile, destFile, exportExtension, function(err) {
|
||||
convertor.convertFile(srcFile, destFile, exportExtension, (err) => {
|
||||
// catch convert errors
|
||||
if (err) {
|
||||
console.warn("Converting Error:", err);
|
||||
reject("convertFailed");
|
||||
console.warn('Converting Error:', err);
|
||||
reject('convertFailed');
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
|
@ -184,13 +184,13 @@ async function doImport(req, res, padId) {
|
|||
|
||||
if (!useConvertor && !req.directDatabaseAccess) {
|
||||
// Read the file with no encoding for raw buffer access.
|
||||
let buf = await fsp_readFile(destFile);
|
||||
const buf = await fsp_readFile(destFile);
|
||||
|
||||
// Check if there are only ascii chars in the uploaded file
|
||||
let isAscii = ! Array.prototype.some.call(buf, c => (c > 240));
|
||||
const isAscii = !Array.prototype.some.call(buf, (c) => (c > 240));
|
||||
|
||||
if (!isAscii) {
|
||||
throw "uploadFailed";
|
||||
throw 'uploadFailed';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,7 +201,7 @@ async function doImport(req, res, padId) {
|
|||
let text;
|
||||
|
||||
if (!req.directDatabaseAccess) {
|
||||
text = await fsp_readFile(destFile, "utf8");
|
||||
text = await fsp_readFile(destFile, 'utf8');
|
||||
|
||||
/*
|
||||
* The <title> tag needs to be stripped out, otherwise it is appended to the
|
||||
|
@ -211,13 +211,13 @@ async function doImport(req, res, padId) {
|
|||
* added to the <title> tag. This is a quick & dirty way of matching the
|
||||
* title and comment it out independently on the classes that are set on it.
|
||||
*/
|
||||
text = text.replace("<title", "<!-- <title");
|
||||
text = text.replace("</title>","</title>-->");
|
||||
text = text.replace('<title', '<!-- <title');
|
||||
text = text.replace('</title>', '</title>-->');
|
||||
|
||||
// node on windows has a delay on releasing of the file lock.
|
||||
// We add a 100ms delay to work around this
|
||||
if (os.type().indexOf("Windows") > -1){
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
if (os.type().indexOf('Windows') > -1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,7 +227,7 @@ async function doImport(req, res, padId) {
|
|||
try {
|
||||
await importHtml.setPadHTML(pad, text);
|
||||
} catch (e) {
|
||||
apiLogger.warn("Error importing, possibly caused by malformed HTML");
|
||||
apiLogger.warn('Error importing, possibly caused by malformed HTML');
|
||||
}
|
||||
} else {
|
||||
await pad.setText(text);
|
||||
|
@ -274,16 +274,16 @@ exports.doImport = function (req, res, padId) {
|
|||
* the function above there's no other way to return
|
||||
* a value to the caller.
|
||||
*/
|
||||
let status = "ok";
|
||||
doImport(req, res, padId).catch(err => {
|
||||
let status = 'ok';
|
||||
doImport(req, res, padId).catch((err) => {
|
||||
// check for known errors and replace the status
|
||||
if (err == "uploadFailed" || err == "convertFailed" || err == "padHasData" || err == "maxFileSize") {
|
||||
if (err == 'uploadFailed' || err == 'convertFailed' || err == 'padHasData' || err == 'maxFileSize') {
|
||||
status = err;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}).then(() => {
|
||||
// close the connection
|
||||
res.send("<script>document.addEventListener('DOMContentLoaded', function(){ var impexp = window.parent.padimpexp.handleFrameCall('" + req.directDatabaseAccess +"', '" + status + "'); })</script>");
|
||||
res.send(`<script>document.addEventListener('DOMContentLoaded', function(){ var impexp = window.parent.padimpexp.handleFrameCall('${req.directDatabaseAccess}', '${status}'); })</script>`);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -19,53 +19,53 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var log4js = require('log4js');
|
||||
var messageLogger = log4js.getLogger("message");
|
||||
var securityManager = require("../db/SecurityManager");
|
||||
var readOnlyManager = require("../db/ReadOnlyManager");
|
||||
var settings = require('../utils/Settings');
|
||||
const log4js = require('log4js');
|
||||
const messageLogger = log4js.getLogger('message');
|
||||
const securityManager = require('../db/SecurityManager');
|
||||
const readOnlyManager = require('../db/ReadOnlyManager');
|
||||
const settings = require('../utils/Settings');
|
||||
|
||||
/**
|
||||
* Saves all components
|
||||
* key is the component name
|
||||
* value is the component module
|
||||
*/
|
||||
var components = {};
|
||||
const components = {};
|
||||
|
||||
var socket;
|
||||
let socket;
|
||||
|
||||
/**
|
||||
* adds a component
|
||||
*/
|
||||
exports.addComponent = function(moduleName, module) {
|
||||
exports.addComponent = function (moduleName, module) {
|
||||
// save the component
|
||||
components[moduleName] = module;
|
||||
|
||||
// give the module the socket
|
||||
module.setSocketIO(socket);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* sets the socket.io and adds event functions for routing
|
||||
*/
|
||||
exports.setSocketIO = function(_socket) {
|
||||
exports.setSocketIO = function (_socket) {
|
||||
// save this socket internaly
|
||||
socket = _socket;
|
||||
|
||||
socket.sockets.on('connection', function(client) {
|
||||
socket.sockets.on('connection', (client) => {
|
||||
// wrap the original send function to log the messages
|
||||
client._send = client.send;
|
||||
client.send = function(message) {
|
||||
client.send = function (message) {
|
||||
messageLogger.debug(`to ${client.id}: ${JSON.stringify(message)}`);
|
||||
client._send(message);
|
||||
}
|
||||
};
|
||||
|
||||
// tell all components about this connect
|
||||
for (let i in components) {
|
||||
for (const i in components) {
|
||||
components[i].handleConnect(client);
|
||||
}
|
||||
|
||||
client.on('message', async function(message) {
|
||||
client.on('message', async (message) => {
|
||||
if (message.protocolVersion && message.protocolVersion != 2) {
|
||||
messageLogger.warn(`Protocolversion header is not correct: ${JSON.stringify(message)}`);
|
||||
return;
|
||||
|
@ -78,11 +78,11 @@ exports.setSocketIO = function(_socket) {
|
|||
await components[message.component].handleMessage(client, message);
|
||||
});
|
||||
|
||||
client.on('disconnect', function() {
|
||||
client.on('disconnect', () => {
|
||||
// tell all components about this disconnect
|
||||
for (let i in components) {
|
||||
for (const i in components) {
|
||||
components[i].handleDisconnect(client);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -18,7 +18,7 @@ let serverName;
|
|||
exports.server = null;
|
||||
|
||||
exports.createServer = async () => {
|
||||
console.log("Report bugs at https://github.com/ether/etherpad-lite/issues")
|
||||
console.log('Report bugs at https://github.com/ether/etherpad-lite/issues');
|
||||
|
||||
serverName = `Etherpad ${settings.getGitCommit()} (https://etherpad.org)`;
|
||||
|
||||
|
@ -26,7 +26,7 @@ exports.createServer = async () => {
|
|||
|
||||
await exports.restartServer();
|
||||
|
||||
if (settings.ip === "") {
|
||||
if (settings.ip === '') {
|
||||
// using Unix socket for connectivity
|
||||
console.log(`You can access your Etherpad instance using the Unix socket at ${settings.port}`);
|
||||
} else {
|
||||
|
@ -42,26 +42,26 @@ exports.createServer = async () => {
|
|||
const env = process.env.NODE_ENV || 'development';
|
||||
|
||||
if (env !== 'production') {
|
||||
console.warn("Etherpad is running in Development mode. This mode is slower for users and less secure than production mode. You should set the NODE_ENV environment variable to production by using: export NODE_ENV=production");
|
||||
console.warn('Etherpad is running in Development mode. This mode is slower for users and less secure than production mode. You should set the NODE_ENV environment variable to production by using: export NODE_ENV=production');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.restartServer = async () => {
|
||||
if (exports.server) {
|
||||
console.log("Restarting express server");
|
||||
console.log('Restarting express server');
|
||||
await util.promisify(exports.server.close).bind(exports.server)();
|
||||
}
|
||||
|
||||
const app = express(); // New syntax for express v3
|
||||
|
||||
if (settings.ssl) {
|
||||
console.log("SSL -- enabled");
|
||||
console.log('SSL -- enabled');
|
||||
console.log(`SSL -- server key file: ${settings.ssl.key}`);
|
||||
console.log(`SSL -- Certificate Authority's certificate file: ${settings.ssl.cert}`);
|
||||
|
||||
const options = {
|
||||
key: fs.readFileSync( settings.ssl.key ),
|
||||
cert: fs.readFileSync( settings.ssl.cert )
|
||||
key: fs.readFileSync(settings.ssl.key),
|
||||
cert: fs.readFileSync(settings.ssl.cert),
|
||||
};
|
||||
|
||||
if (settings.ssl.ca) {
|
||||
|
@ -79,16 +79,16 @@ exports.restartServer = async () => {
|
|||
exports.server = http.createServer(app);
|
||||
}
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
app.use((req, res, next) => {
|
||||
// res.header("X-Frame-Options", "deny"); // breaks embedded pads
|
||||
if (settings.ssl) {
|
||||
// we use SSL
|
||||
res.header("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
||||
res.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||
}
|
||||
|
||||
// Stop IE going into compatability mode
|
||||
// https://github.com/ether/etherpad-lite/issues/2547
|
||||
res.header("X-UA-Compatible", "IE=Edge,chrome=1");
|
||||
res.header('X-UA-Compatible', 'IE=Edge,chrome=1');
|
||||
|
||||
// Enable a strong referrer policy. Same-origin won't drop Referers when
|
||||
// loading local resources, but it will drop them when loading foreign resources.
|
||||
|
@ -97,11 +97,11 @@ exports.restartServer = async () => {
|
|||
// marked with <meta name="referrer" content="no-referrer">
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
|
||||
// https://github.com/ether/etherpad-lite/pull/3636
|
||||
res.header("Referrer-Policy", "same-origin");
|
||||
res.header('Referrer-Policy', 'same-origin');
|
||||
|
||||
// send git version in the Server response header if exposeVersion is true.
|
||||
if (settings.exposeVersion) {
|
||||
res.header("Server", serverName);
|
||||
res.header('Server', serverName);
|
||||
}
|
||||
|
||||
next();
|
||||
|
@ -165,13 +165,13 @@ exports.restartServer = async () => {
|
|||
//
|
||||
// reference: https://github.com/expressjs/session/blob/v1.17.0/README.md#cookiesecure
|
||||
secure: 'auto',
|
||||
}
|
||||
},
|
||||
});
|
||||
app.use(exports.sessionMiddleware);
|
||||
|
||||
app.use(cookieParser(settings.sessionKey, {}));
|
||||
|
||||
hooks.callAll("expressConfigure", {"app": app});
|
||||
hooks.callAll('expressConfigure', {app});
|
||||
hooks.callAll('expressCreateServer', {app, server: exports.server});
|
||||
|
||||
await util.promisify(exports.server.listen).bind(exports.server)(settings.port, settings.ip);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
var eejs = require('ep_etherpad-lite/node/eejs');
|
||||
const eejs = require('ep_etherpad-lite/node/eejs');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
args.app.get('/admin', function(req, res) {
|
||||
if('/' != req.path[req.path.length-1]) return res.redirect('./admin/');
|
||||
args.app.get('/admin', (req, res) => {
|
||||
if ('/' != req.path[req.path.length - 1]) return res.redirect('./admin/');
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/index.html', {req}));
|
||||
});
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
var eejs = require('ep_etherpad-lite/node/eejs');
|
||||
var settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
var installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
|
||||
var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
|
||||
var _ = require('underscore');
|
||||
var semver = require('semver');
|
||||
const eejs = require('ep_etherpad-lite/node/eejs');
|
||||
const settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
const installer = require('ep_etherpad-lite/static/js/pluginfw/installer');
|
||||
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
|
||||
const _ = require('underscore');
|
||||
const semver = require('semver');
|
||||
const UpdateCheck = require('ep_etherpad-lite/node/utils/UpdateCheck');
|
||||
|
||||
exports.expressCreateServer = function(hook_name, args, cb) {
|
||||
args.app.get('/admin/plugins', function(req, res) {
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
args.app.get('/admin/plugins', (req, res) => {
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins.html', {
|
||||
plugins: plugins.plugins,
|
||||
req,
|
||||
|
@ -16,114 +16,108 @@ exports.expressCreateServer = function(hook_name, args, cb) {
|
|||
}));
|
||||
});
|
||||
|
||||
args.app.get('/admin/plugins/info', function(req, res) {
|
||||
var gitCommit = settings.getGitCommit();
|
||||
var epVersion = settings.getEpVersion();
|
||||
args.app.get('/admin/plugins/info', (req, res) => {
|
||||
const gitCommit = settings.getGitCommit();
|
||||
const epVersion = settings.getEpVersion();
|
||||
|
||||
res.send(eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", {
|
||||
gitCommit: gitCommit,
|
||||
epVersion: epVersion,
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins-info.html', {
|
||||
gitCommit,
|
||||
epVersion,
|
||||
latestVersion: UpdateCheck.getLatestVersion(),
|
||||
req,
|
||||
}));
|
||||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
||||
exports.socketio = function(hook_name, args, cb) {
|
||||
var io = args.io.of("/pluginfw/installer");
|
||||
io.on('connection', function(socket) {
|
||||
exports.socketio = function (hook_name, args, cb) {
|
||||
const io = args.io.of('/pluginfw/installer');
|
||||
io.on('connection', (socket) => {
|
||||
if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return;
|
||||
|
||||
socket.on("getInstalled", function(query) {
|
||||
socket.on('getInstalled', (query) => {
|
||||
// send currently installed plugins
|
||||
var installed = Object.keys(plugins.plugins).map(function(plugin) {
|
||||
return plugins.plugins[plugin].package
|
||||
});
|
||||
const installed = Object.keys(plugins.plugins).map((plugin) => plugins.plugins[plugin].package);
|
||||
|
||||
socket.emit("results:installed", {installed: installed});
|
||||
socket.emit('results:installed', {installed});
|
||||
});
|
||||
|
||||
socket.on("checkUpdates", async function() {
|
||||
socket.on('checkUpdates', async () => {
|
||||
// Check plugins for updates
|
||||
try {
|
||||
let results = await installer.getAvailablePlugins(/*maxCacheAge:*/ 60 * 10);
|
||||
const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ 60 * 10);
|
||||
|
||||
var updatable = _(plugins.plugins).keys().filter(function(plugin) {
|
||||
const updatable = _(plugins.plugins).keys().filter((plugin) => {
|
||||
if (!results[plugin]) return false;
|
||||
|
||||
var latestVersion = results[plugin].version;
|
||||
var currentVersion = plugins.plugins[plugin].package.version;
|
||||
const latestVersion = results[plugin].version;
|
||||
const currentVersion = plugins.plugins[plugin].package.version;
|
||||
|
||||
return semver.gt(latestVersion, currentVersion);
|
||||
});
|
||||
|
||||
socket.emit("results:updatable", {updatable: updatable});
|
||||
socket.emit('results:updatable', {updatable});
|
||||
} catch (er) {
|
||||
console.warn(er);
|
||||
|
||||
socket.emit("results:updatable", {updatable: {}});
|
||||
socket.emit('results:updatable', {updatable: {}});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("getAvailable", async function(query) {
|
||||
socket.on('getAvailable', async (query) => {
|
||||
try {
|
||||
let results = await installer.getAvailablePlugins(/*maxCacheAge:*/ false);
|
||||
socket.emit("results:available", results);
|
||||
const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ false);
|
||||
socket.emit('results:available', results);
|
||||
} catch (er) {
|
||||
console.error(er);
|
||||
socket.emit("results:available", {});
|
||||
socket.emit('results:available', {});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("search", async function(query) {
|
||||
socket.on('search', async (query) => {
|
||||
try {
|
||||
let results = await installer.search(query.searchTerm, /*maxCacheAge:*/ 60 * 10);
|
||||
var res = Object.keys(results)
|
||||
.map(function(pluginName) {
|
||||
return results[pluginName];
|
||||
})
|
||||
.filter(function(plugin) {
|
||||
return !plugins.plugins[plugin.name];
|
||||
});
|
||||
const results = await installer.search(query.searchTerm, /* maxCacheAge:*/ 60 * 10);
|
||||
let res = Object.keys(results)
|
||||
.map((pluginName) => results[pluginName])
|
||||
.filter((plugin) => !plugins.plugins[plugin.name]);
|
||||
res = sortPluginList(res, query.sortBy, query.sortDir)
|
||||
.slice(query.offset, query.offset+query.limit);
|
||||
socket.emit("results:search", {results: res, query: query});
|
||||
.slice(query.offset, query.offset + query.limit);
|
||||
socket.emit('results:search', {results: res, query});
|
||||
} catch (er) {
|
||||
console.error(er);
|
||||
|
||||
socket.emit("results:search", {results: {}, query: query});
|
||||
socket.emit('results:search', {results: {}, query});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("install", function(plugin_name) {
|
||||
installer.install(plugin_name, function(er) {
|
||||
socket.on('install', (plugin_name) => {
|
||||
installer.install(plugin_name, (er) => {
|
||||
if (er) console.warn(er);
|
||||
|
||||
socket.emit("finished:install", {plugin: plugin_name, code: er? er.code : null, error: er? er.message : null});
|
||||
socket.emit('finished:install', {plugin: plugin_name, code: er ? er.code : null, error: er ? er.message : null});
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("uninstall", function(plugin_name) {
|
||||
installer.uninstall(plugin_name, function(er) {
|
||||
socket.on('uninstall', (plugin_name) => {
|
||||
installer.uninstall(plugin_name, (er) => {
|
||||
if (er) console.warn(er);
|
||||
|
||||
socket.emit("finished:uninstall", {plugin: plugin_name, error: er? er.message : null});
|
||||
socket.emit('finished:uninstall', {plugin: plugin_name, error: er ? er.message : null});
|
||||
});
|
||||
});
|
||||
});
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
||||
function sortPluginList(plugins, property, /*ASC?*/dir) {
|
||||
return plugins.sort(function(a, b) {
|
||||
function sortPluginList(plugins, property, /* ASC?*/dir) {
|
||||
return plugins.sort((a, b) => {
|
||||
if (a[property] < b[property]) {
|
||||
return dir? -1 : 1;
|
||||
return dir ? -1 : 1;
|
||||
}
|
||||
|
||||
if (a[property] > b[property]) {
|
||||
return dir? 1 : -1;
|
||||
return dir ? 1 : -1;
|
||||
}
|
||||
|
||||
// a must be equal to b
|
||||
|
|
|
@ -1,55 +1,52 @@
|
|||
var eejs = require('ep_etherpad-lite/node/eejs');
|
||||
var settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
|
||||
var fs = require('fs');
|
||||
const eejs = require('ep_etherpad-lite/node/eejs');
|
||||
const settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
const fs = require('fs');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
args.app.get('/admin/settings', function(req, res) {
|
||||
args.app.get('/admin/settings', (req, res) => {
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/settings.html', {
|
||||
req,
|
||||
settings: "",
|
||||
settings: '',
|
||||
search_results: {},
|
||||
errors: []
|
||||
errors: [],
|
||||
}));
|
||||
});
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
||||
exports.socketio = function (hook_name, args, cb) {
|
||||
var io = args.io.of("/settings");
|
||||
io.on('connection', function (socket) {
|
||||
|
||||
const io = args.io.of('/settings');
|
||||
io.on('connection', (socket) => {
|
||||
if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return;
|
||||
|
||||
socket.on("load", function (query) {
|
||||
fs.readFile('settings.json', 'utf8', function (err,data) {
|
||||
socket.on('load', (query) => {
|
||||
fs.readFile('settings.json', 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
return console.log(err);
|
||||
}
|
||||
|
||||
// if showSettingsInAdminPage is set to false, then return NOT_ALLOWED in the result
|
||||
if(settings.showSettingsInAdminPage === false) {
|
||||
socket.emit("settings", {results: 'NOT_ALLOWED'});
|
||||
}
|
||||
else {
|
||||
socket.emit("settings", {results: data});
|
||||
if (settings.showSettingsInAdminPage === false) {
|
||||
socket.emit('settings', {results: 'NOT_ALLOWED'});
|
||||
} else {
|
||||
socket.emit('settings', {results: data});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("saveSettings", function (settings) {
|
||||
fs.writeFile('settings.json', settings, function (err) {
|
||||
socket.on('saveSettings', (settings) => {
|
||||
fs.writeFile('settings.json', settings, (err) => {
|
||||
if (err) throw err;
|
||||
socket.emit("saveprogress", "saved");
|
||||
socket.emit('saveprogress', 'saved');
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('restartServer', async () => {
|
||||
console.log("Admin request to restart server through a socket on /admin/settings");
|
||||
console.log('Admin request to restart server through a socket on /admin/settings');
|
||||
settings.reloadSettings();
|
||||
await hooks.aCallAll('restartServer');
|
||||
});
|
||||
|
||||
});
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
var log4js = require('log4js');
|
||||
var clientLogger = log4js.getLogger("client");
|
||||
var formidable = require('formidable');
|
||||
var apiHandler = require('../../handler/APIHandler');
|
||||
const log4js = require('log4js');
|
||||
const clientLogger = log4js.getLogger('client');
|
||||
const formidable = require('formidable');
|
||||
const apiHandler = require('../../handler/APIHandler');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
//The Etherpad client side sends information about how a disconnect happened
|
||||
args.app.post('/ep/pad/connection-diagnostic-info', function(req, res) {
|
||||
new formidable.IncomingForm().parse(req, function(err, fields, files) {
|
||||
clientLogger.info("DIAGNOSTIC-INFO: " + fields.diagnosticInfo);
|
||||
res.end("OK");
|
||||
// The Etherpad client side sends information about how a disconnect happened
|
||||
args.app.post('/ep/pad/connection-diagnostic-info', (req, res) => {
|
||||
new formidable.IncomingForm().parse(req, (err, fields, files) => {
|
||||
clientLogger.info(`DIAGNOSTIC-INFO: ${fields.diagnosticInfo}`);
|
||||
res.end('OK');
|
||||
});
|
||||
});
|
||||
|
||||
//The Etherpad client side sends information about client side javscript errors
|
||||
args.app.post('/jserror', function(req, res) {
|
||||
new formidable.IncomingForm().parse(req, function(err, fields, files) {
|
||||
// The Etherpad client side sends information about client side javscript errors
|
||||
args.app.post('/jserror', (req, res) => {
|
||||
new formidable.IncomingForm().parse(req, (err, fields, files) => {
|
||||
try {
|
||||
var data = JSON.parse(fields.errorInfo)
|
||||
}catch(e){
|
||||
return res.end()
|
||||
var data = JSON.parse(fields.errorInfo);
|
||||
} catch (e) {
|
||||
return res.end();
|
||||
}
|
||||
clientLogger.warn(data.msg+' --', data);
|
||||
res.end("OK");
|
||||
clientLogger.warn(`${data.msg} --`, data);
|
||||
res.end('OK');
|
||||
});
|
||||
});
|
||||
|
||||
//Provide a possibility to query the latest available API version
|
||||
args.app.get('/api', function (req, res) {
|
||||
res.json({"currentVersion" : apiHandler.latestApiVersion});
|
||||
// Provide a possibility to query the latest available API version
|
||||
args.app.get('/api', (req, res) => {
|
||||
res.json({currentVersion: apiHandler.latestApiVersion});
|
||||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
var stats = require('ep_etherpad-lite/node/stats')
|
||||
const stats = require('ep_etherpad-lite/node/stats');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
exports.app = args.app;
|
||||
|
||||
// Handle errors
|
||||
args.app.use(function(err, req, res, next) {
|
||||
args.app.use((err, req, res, next) => {
|
||||
// if an error occurs Connect will pass it down
|
||||
// through these "error-handling" middleware
|
||||
// allowing you to respond however you like
|
||||
res.status(500).send({ error: 'Sorry, something bad happened!' });
|
||||
console.error(err.stack? err.stack : err.toString());
|
||||
stats.meter('http500').mark()
|
||||
res.status(500).send({error: 'Sorry, something bad happened!'});
|
||||
console.error(err.stack ? err.stack : err.toString());
|
||||
stats.meter('http500').mark();
|
||||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,44 +1,43 @@
|
|||
const assert = require('assert').strict;
|
||||
var hasPadAccess = require("../../padaccess");
|
||||
var settings = require('../../utils/Settings');
|
||||
var exportHandler = require('../../handler/ExportHandler');
|
||||
var importHandler = require('../../handler/ImportHandler');
|
||||
var padManager = require("../../db/PadManager");
|
||||
var readOnlyManager = require("../../db/ReadOnlyManager");
|
||||
var authorManager = require("../../db/AuthorManager");
|
||||
const rateLimit = require("express-rate-limit");
|
||||
const securityManager = require("../../db/SecurityManager");
|
||||
const webaccess = require("./webaccess");
|
||||
const hasPadAccess = require('../../padaccess');
|
||||
const settings = require('../../utils/Settings');
|
||||
const exportHandler = require('../../handler/ExportHandler');
|
||||
const importHandler = require('../../handler/ImportHandler');
|
||||
const padManager = require('../../db/PadManager');
|
||||
const readOnlyManager = require('../../db/ReadOnlyManager');
|
||||
const authorManager = require('../../db/AuthorManager');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const securityManager = require('../../db/SecurityManager');
|
||||
const webaccess = require('./webaccess');
|
||||
|
||||
settings.importExportRateLimiting.onLimitReached = function(req, res, options) {
|
||||
settings.importExportRateLimiting.onLimitReached = function (req, res, options) {
|
||||
// when the rate limiter triggers, write a warning in the logs
|
||||
console.warn(`Import/Export rate limiter triggered on "${req.originalUrl}" for IP address ${req.ip}`);
|
||||
}
|
||||
};
|
||||
|
||||
var limiter = rateLimit(settings.importExportRateLimiting);
|
||||
const limiter = rateLimit(settings.importExportRateLimiting);
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
|
||||
// handle export requests
|
||||
args.app.use('/p/:pad/:rev?/export/:type', limiter);
|
||||
args.app.get('/p/:pad/:rev?/export/:type', async function(req, res, next) {
|
||||
var types = ["pdf", "doc", "txt", "html", "odt", "etherpad"];
|
||||
//send a 404 if we don't support this filetype
|
||||
args.app.get('/p/:pad/:rev?/export/:type', async (req, res, next) => {
|
||||
const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad'];
|
||||
// send a 404 if we don't support this filetype
|
||||
if (types.indexOf(req.params.type) == -1) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// if abiword is disabled, and this is a format we only support with abiword, output a message
|
||||
if (settings.exportAvailable() == "no" &&
|
||||
["odt", "pdf", "doc"].indexOf(req.params.type) !== -1) {
|
||||
if (settings.exportAvailable() == 'no' &&
|
||||
['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) {
|
||||
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format. There is no converter configured`);
|
||||
|
||||
// ACHTUNG: do not include req.params.type in res.send() because there is no HTML escaping and it would lead to an XSS
|
||||
res.send("This export is not enabled at this Etherpad instance. Set the path to Abiword or soffice (LibreOffice) in settings.json to enable this feature");
|
||||
res.send('This export is not enabled at this Etherpad instance. Set the path to Abiword or soffice (LibreOffice) in settings.json to enable this feature');
|
||||
return;
|
||||
}
|
||||
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
|
||||
if (await hasPadAccess(req, res)) {
|
||||
let padId = req.params.pad;
|
||||
|
@ -49,7 +48,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
padId = await readOnlyManager.getPadId(readOnlyId);
|
||||
}
|
||||
|
||||
let exists = await padManager.doesPadExists(padId);
|
||||
const exists = await padManager.doesPadExists(padId);
|
||||
if (!exists) {
|
||||
console.warn(`Someone tried to export a pad that doesn't exist (${padId})`);
|
||||
return next();
|
||||
|
@ -62,7 +61,7 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
|
||||
// handle import requests
|
||||
args.app.use('/p/:pad/import', limiter);
|
||||
args.app.post('/p/:pad/import', async function(req, res, next) {
|
||||
args.app.post('/p/:pad/import', async (req, res, next) => {
|
||||
const {session: {user} = {}} = req;
|
||||
const {accessStatus} = await securityManager.checkAccess(
|
||||
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
|
||||
|
@ -73,4 +72,4 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -62,14 +62,14 @@ const RESERVED_WORDS = [
|
|||
'volatile',
|
||||
'while',
|
||||
'with',
|
||||
'yield'
|
||||
'yield',
|
||||
];
|
||||
|
||||
const regex = /^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:".+"|\'.+\'|\d+)\])*?$/;
|
||||
|
||||
module.exports.check = function(inputStr) {
|
||||
var isValid = true;
|
||||
inputStr.split(".").forEach(function(part) {
|
||||
module.exports.check = function (inputStr) {
|
||||
let isValid = true;
|
||||
inputStr.split('.').forEach((part) => {
|
||||
if (!regex.test(part)) {
|
||||
isValid = false;
|
||||
}
|
||||
|
@ -80,4 +80,4 @@ module.exports.check = function(inputStr) {
|
|||
});
|
||||
|
||||
return isValid;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
const OpenAPIBackend = require('openapi-backend').default;
|
||||
const formidable = require('formidable');
|
||||
const { promisify } = require('util');
|
||||
const {promisify} = require('util');
|
||||
const cloneDeep = require('lodash.clonedeep');
|
||||
const createHTTPError = require('http-errors');
|
||||
|
||||
|
@ -57,12 +57,12 @@ const resources = {
|
|||
create: {
|
||||
operationId: 'createGroup',
|
||||
summary: 'creates a new group',
|
||||
responseSchema: { groupID: { type: 'string' } },
|
||||
responseSchema: {groupID: {type: 'string'}},
|
||||
},
|
||||
createIfNotExistsFor: {
|
||||
operationId: 'createGroupIfNotExistsFor',
|
||||
summary: 'this functions helps you to map your application group ids to Etherpad group ids',
|
||||
responseSchema: { groupID: { type: 'string' } },
|
||||
responseSchema: {groupID: {type: 'string'}},
|
||||
},
|
||||
delete: {
|
||||
operationId: 'deleteGroup',
|
||||
|
@ -71,7 +71,7 @@ const resources = {
|
|||
listPads: {
|
||||
operationId: 'listPads',
|
||||
summary: 'returns all pads of this group',
|
||||
responseSchema: { padIDs: { type: 'array', items: { type: 'string' } } },
|
||||
responseSchema: {padIDs: {type: 'array', items: {type: 'string'}}},
|
||||
},
|
||||
createPad: {
|
||||
operationId: 'createGroupPad',
|
||||
|
@ -80,12 +80,12 @@ const resources = {
|
|||
listSessions: {
|
||||
operationId: 'listSessionsOfGroup',
|
||||
summary: '',
|
||||
responseSchema: { sessions: { type: 'array', items: { $ref: '#/components/schemas/SessionInfo' } } },
|
||||
responseSchema: {sessions: {type: 'array', items: {$ref: '#/components/schemas/SessionInfo'}}},
|
||||
},
|
||||
list: {
|
||||
operationId: 'listAllGroups',
|
||||
summary: '',
|
||||
responseSchema: { groupIDs: { type: 'array', items: { type: 'string' } } },
|
||||
responseSchema: {groupIDs: {type: 'array', items: {type: 'string'}}},
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -94,28 +94,28 @@ const resources = {
|
|||
create: {
|
||||
operationId: 'createAuthor',
|
||||
summary: 'creates a new author',
|
||||
responseSchema: { authorID: { type: 'string' } },
|
||||
responseSchema: {authorID: {type: 'string'}},
|
||||
},
|
||||
createIfNotExistsFor: {
|
||||
operationId: 'createAuthorIfNotExistsFor',
|
||||
summary: 'this functions helps you to map your application author ids to Etherpad author ids',
|
||||
responseSchema: { authorID: { type: 'string' } },
|
||||
responseSchema: {authorID: {type: 'string'}},
|
||||
},
|
||||
listPads: {
|
||||
operationId: 'listPadsOfAuthor',
|
||||
summary: 'returns an array of all pads this author contributed to',
|
||||
responseSchema: { padIDs: { type: 'array', items: { type: 'string' } } },
|
||||
responseSchema: {padIDs: {type: 'array', items: {type: 'string'}}},
|
||||
},
|
||||
listSessions: {
|
||||
operationId: 'listSessionsOfAuthor',
|
||||
summary: 'returns all sessions of an author',
|
||||
responseSchema: { sessions: { type: 'array', items: { $ref: '#/components/schemas/SessionInfo' } } },
|
||||
responseSchema: {sessions: {type: 'array', items: {$ref: '#/components/schemas/SessionInfo'}}},
|
||||
},
|
||||
// We need an operation that return a UserInfo so it can be picked up by the codegen :(
|
||||
getName: {
|
||||
operationId: 'getAuthorName',
|
||||
summary: 'Returns the Author Name of the author',
|
||||
responseSchema: { info: { $ref: '#/components/schemas/UserInfo' } },
|
||||
responseSchema: {info: {$ref: '#/components/schemas/UserInfo'}},
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -124,7 +124,7 @@ const resources = {
|
|||
create: {
|
||||
operationId: 'createSession',
|
||||
summary: 'creates a new session. validUntil is an unix timestamp in seconds',
|
||||
responseSchema: { sessionID: { type: 'string' } },
|
||||
responseSchema: {sessionID: {type: 'string'}},
|
||||
},
|
||||
delete: {
|
||||
operationId: 'deleteSession',
|
||||
|
@ -134,7 +134,7 @@ const resources = {
|
|||
info: {
|
||||
operationId: 'getSessionInfo',
|
||||
summary: 'returns informations about a session',
|
||||
responseSchema: { info: { $ref: '#/components/schemas/SessionInfo' } },
|
||||
responseSchema: {info: {$ref: '#/components/schemas/SessionInfo'}},
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -143,7 +143,7 @@ const resources = {
|
|||
listAll: {
|
||||
operationId: 'listAllPads',
|
||||
summary: 'list all the pads',
|
||||
responseSchema: { padIDs: { type: 'array', items: { type: 'string' } } },
|
||||
responseSchema: {padIDs: {type: 'array', items: {type: 'string'}}},
|
||||
},
|
||||
createDiffHTML: {
|
||||
operationId: 'createDiffHTML',
|
||||
|
@ -158,7 +158,7 @@ const resources = {
|
|||
getText: {
|
||||
operationId: 'getText',
|
||||
summary: 'returns the text of a pad',
|
||||
responseSchema: { text: { type: 'string' } },
|
||||
responseSchema: {text: {type: 'string'}},
|
||||
},
|
||||
setText: {
|
||||
operationId: 'setText',
|
||||
|
@ -167,7 +167,7 @@ const resources = {
|
|||
getHTML: {
|
||||
operationId: 'getHTML',
|
||||
summary: 'returns the text of a pad formatted as HTML',
|
||||
responseSchema: { html: { type: 'string' } },
|
||||
responseSchema: {html: {type: 'string'}},
|
||||
},
|
||||
setHTML: {
|
||||
operationId: 'setHTML',
|
||||
|
@ -176,12 +176,12 @@ const resources = {
|
|||
getRevisionsCount: {
|
||||
operationId: 'getRevisionsCount',
|
||||
summary: 'returns the number of revisions of this pad',
|
||||
responseSchema: { revisions: { type: 'integer' } },
|
||||
responseSchema: {revisions: {type: 'integer'}},
|
||||
},
|
||||
getLastEdited: {
|
||||
operationId: 'getLastEdited',
|
||||
summary: 'returns the timestamp of the last revision of the pad',
|
||||
responseSchema: { lastEdited: { type: 'integer' } },
|
||||
responseSchema: {lastEdited: {type: 'integer'}},
|
||||
},
|
||||
delete: {
|
||||
operationId: 'deletePad',
|
||||
|
@ -190,7 +190,7 @@ const resources = {
|
|||
getReadOnlyID: {
|
||||
operationId: 'getReadOnlyID',
|
||||
summary: 'returns the read only link of a pad',
|
||||
responseSchema: { readOnlyID: { type: 'string' } },
|
||||
responseSchema: {readOnlyID: {type: 'string'}},
|
||||
},
|
||||
setPublicStatus: {
|
||||
operationId: 'setPublicStatus',
|
||||
|
@ -199,22 +199,22 @@ const resources = {
|
|||
getPublicStatus: {
|
||||
operationId: 'getPublicStatus',
|
||||
summary: 'return true of false',
|
||||
responseSchema: { publicStatus: { type: 'boolean' } },
|
||||
responseSchema: {publicStatus: {type: 'boolean'}},
|
||||
},
|
||||
authors: {
|
||||
operationId: 'listAuthorsOfPad',
|
||||
summary: 'returns an array of authors who contributed to this pad',
|
||||
responseSchema: { authorIDs: { type: 'array', items: { type: 'string' } } },
|
||||
responseSchema: {authorIDs: {type: 'array', items: {type: 'string'}}},
|
||||
},
|
||||
usersCount: {
|
||||
operationId: 'padUsersCount',
|
||||
summary: 'returns the number of user that are currently editing this pad',
|
||||
responseSchema: { padUsersCount: { type: 'integer' } },
|
||||
responseSchema: {padUsersCount: {type: 'integer'}},
|
||||
},
|
||||
users: {
|
||||
operationId: 'padUsers',
|
||||
summary: 'returns the list of users that are currently editing this pad',
|
||||
responseSchema: { padUsers: { type: 'array', items: { $ref: '#/components/schemas/UserInfo' } } },
|
||||
responseSchema: {padUsers: {type: 'array', items: {$ref: '#/components/schemas/UserInfo'}}},
|
||||
},
|
||||
sendClientsMessage: {
|
||||
operationId: 'sendClientsMessage',
|
||||
|
@ -227,13 +227,13 @@ const resources = {
|
|||
getChatHistory: {
|
||||
operationId: 'getChatHistory',
|
||||
summary: 'returns the chat history',
|
||||
responseSchema: { messages: { type: 'array', items: { $ref: '#/components/schemas/Message' } } },
|
||||
responseSchema: {messages: {type: 'array', items: {$ref: '#/components/schemas/Message'}}},
|
||||
},
|
||||
// We need an operation that returns a Message so it can be picked up by the codegen :(
|
||||
getChatHead: {
|
||||
operationId: 'getChatHead',
|
||||
summary: 'returns the chatHead (chat-message) of the pad',
|
||||
responseSchema: { chatHead: { $ref: '#/components/schemas/Message' } },
|
||||
responseSchema: {chatHead: {$ref: '#/components/schemas/Message'}},
|
||||
},
|
||||
appendChatMessage: {
|
||||
operationId: 'appendChatMessage',
|
||||
|
@ -384,10 +384,10 @@ const defaultResponseRefs = {
|
|||
const operations = {};
|
||||
for (const resource in resources) {
|
||||
for (const action in resources[resource]) {
|
||||
const { operationId, responseSchema, ...operation } = resources[resource][action];
|
||||
const {operationId, responseSchema, ...operation} = resources[resource][action];
|
||||
|
||||
// add response objects
|
||||
const responses = { ...defaultResponseRefs };
|
||||
const responses = {...defaultResponseRefs};
|
||||
if (responseSchema) {
|
||||
responses[200] = cloneDeep(defaultResponses.Success);
|
||||
responses[200].content['application/json'].schema.properties.data = {
|
||||
|
@ -478,14 +478,14 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
|
|||
},
|
||||
},
|
||||
},
|
||||
security: [{ ApiKey: [] }],
|
||||
security: [{ApiKey: []}],
|
||||
};
|
||||
|
||||
// build operations
|
||||
for (const funcName in apiHandler.version[version]) {
|
||||
let operation = {};
|
||||
if (operations[funcName]) {
|
||||
operation = { ...operations[funcName] };
|
||||
operation = {...operations[funcName]};
|
||||
} else {
|
||||
// console.warn(`No operation found for function: ${funcName}`);
|
||||
operation = {
|
||||
|
@ -497,7 +497,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
|
|||
// set parameters
|
||||
operation.parameters = operation.parameters || [];
|
||||
for (const paramName of apiHandler.version[version][funcName]) {
|
||||
operation.parameters.push({ $ref: `#/components/parameters/${paramName}` });
|
||||
operation.parameters.push({$ref: `#/components/parameters/${paramName}`});
|
||||
if (!definition.components.parameters[paramName]) {
|
||||
definition.components.parameters[paramName] = {
|
||||
name: paramName,
|
||||
|
@ -533,7 +533,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
|
|||
};
|
||||
|
||||
exports.expressCreateServer = (hookName, args, cb) => {
|
||||
const { app } = args;
|
||||
const {app} = args;
|
||||
|
||||
// create openapi-backend handlers for each api version under /api/{version}/*
|
||||
for (const version in apiHandler.version) {
|
||||
|
@ -550,7 +550,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
app.get(`${apiRoot}/openapi.json`, (req, res) => {
|
||||
// For openapi definitions, wide CORS is probably fine
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.json({ ...definition, servers: [generateServerForApiVersion(apiRoot, req)] });
|
||||
res.json({...definition, servers: [generateServerForApiVersion(apiRoot, req)]});
|
||||
});
|
||||
|
||||
// serve latest openapi definition file under /api/openapi.json
|
||||
|
@ -558,7 +558,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
if (isLatestAPIVersion) {
|
||||
app.get(`/${style}/openapi.json`, (req, res) => {
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.json({ ...definition, servers: [generateServerForApiVersion(apiRoot, req)] });
|
||||
res.json({...definition, servers: [generateServerForApiVersion(apiRoot, req)]});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -586,7 +586,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
for (const funcName in apiHandler.version[version]) {
|
||||
const handler = async (c, req, res) => {
|
||||
// parse fields from request
|
||||
const { header, params, query } = c.request;
|
||||
const {header, params, query} = c.request;
|
||||
|
||||
// read form data if method was POST
|
||||
let formData = {};
|
||||
|
@ -602,7 +602,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
apiLogger.info(`REQUEST, v${version}:${funcName}, ${JSON.stringify(fields)}`);
|
||||
|
||||
// pass to api handler
|
||||
let data = await apiHandler.handle(version, funcName, fields, req, res).catch((err) => {
|
||||
const data = await apiHandler.handle(version, funcName, fields, req, res).catch((err) => {
|
||||
// convert all errors to http errors
|
||||
if (createHTTPError.isHttpError(err)) {
|
||||
// pass http errors thrown by handler forward
|
||||
|
@ -620,7 +620,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
});
|
||||
|
||||
// return in common format
|
||||
let response = { code: 0, message: 'ok', data: data || null };
|
||||
const response = {code: 0, message: 'ok', data: data || null};
|
||||
|
||||
// log response
|
||||
apiLogger.info(`RESPONSE, ${funcName}, ${JSON.stringify(response)}`);
|
||||
|
@ -654,24 +654,24 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
// https://github.com/ether/etherpad-lite/tree/master/doc/api/http_api.md#response-format
|
||||
switch (res.statusCode) {
|
||||
case 403: // forbidden
|
||||
response = { code: 4, message: err.message, data: null };
|
||||
response = {code: 4, message: err.message, data: null};
|
||||
break;
|
||||
case 401: // unauthorized (no or wrong api key)
|
||||
response = { code: 4, message: err.message, data: null };
|
||||
response = {code: 4, message: err.message, data: null};
|
||||
break;
|
||||
case 404: // not found (no such function)
|
||||
response = { code: 3, message: err.message, data: null };
|
||||
response = {code: 3, message: err.message, data: null};
|
||||
break;
|
||||
case 500: // server error (internal error)
|
||||
response = { code: 2, message: err.message, data: null };
|
||||
response = {code: 2, message: err.message, data: null};
|
||||
break;
|
||||
case 400: // bad request (wrong parameters)
|
||||
// respond with 200 OK to keep old behavior and pass tests
|
||||
res.statusCode = 200; // @TODO: this is bad api design
|
||||
response = { code: 1, message: err.message, data: null };
|
||||
response = {code: 1, message: err.message, data: null};
|
||||
break;
|
||||
default:
|
||||
response = { code: 1, message: err.message, data: null };
|
||||
response = {code: 1, message: err.message, data: null};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
var readOnlyManager = require("../../db/ReadOnlyManager");
|
||||
var hasPadAccess = require("../../padaccess");
|
||||
var exporthtml = require("../../utils/ExportHtml");
|
||||
const readOnlyManager = require('../../db/ReadOnlyManager');
|
||||
const hasPadAccess = require('../../padaccess');
|
||||
const exporthtml = require('../../utils/ExportHtml');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
// serve read only pad
|
||||
args.app.get('/ro/:id', async function(req, res) {
|
||||
|
||||
args.app.get('/ro/:id', async (req, res) => {
|
||||
// translate the read only pad to a padId
|
||||
let padId = await readOnlyManager.getPadId(req.params.id);
|
||||
const padId = await readOnlyManager.getPadId(req.params.id);
|
||||
if (padId == null) {
|
||||
res.status(404).send('404 - Not Found');
|
||||
return;
|
||||
|
@ -18,9 +17,9 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
|
||||
if (await hasPadAccess(req, res)) {
|
||||
// render the html document
|
||||
let html = await exporthtml.getPadHTMLDocument(padId, null);
|
||||
const html = await exporthtml.getPadHTMLDocument(padId, null);
|
||||
res.send(html);
|
||||
}
|
||||
});
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,30 +1,29 @@
|
|||
var padManager = require('../../db/PadManager');
|
||||
var url = require('url');
|
||||
const padManager = require('../../db/PadManager');
|
||||
const url = require('url');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
|
||||
// redirects browser to the pad's sanitized url if needed. otherwise, renders the html
|
||||
args.app.param('pad', async function (req, res, next, padId) {
|
||||
args.app.param('pad', async (req, res, next, padId) => {
|
||||
// ensure the padname is valid and the url doesn't end with a /
|
||||
if (!padManager.isValidPadId(padId) || /\/$/.test(req.url)) {
|
||||
res.status(404).send('Such a padname is forbidden');
|
||||
return;
|
||||
}
|
||||
|
||||
let sanitizedPadId = await padManager.sanitizePadId(padId);
|
||||
const sanitizedPadId = await padManager.sanitizePadId(padId);
|
||||
|
||||
if (sanitizedPadId === padId) {
|
||||
// the pad id was fine, so just render it
|
||||
next();
|
||||
} else {
|
||||
// the pad id was sanitized, so we redirect to the sanitized version
|
||||
var real_url = sanitizedPadId;
|
||||
let real_url = sanitizedPadId;
|
||||
real_url = encodeURIComponent(real_url);
|
||||
var query = url.parse(req.url).query;
|
||||
if ( query ) real_url += '?' + query;
|
||||
const query = url.parse(req.url).query;
|
||||
if (query) real_url += `?${query}`;
|
||||
res.header('Location', real_url);
|
||||
res.status(302).send('You should be redirected to <a href="' + real_url + '">' + real_url + '</a>');
|
||||
res.status(302).send(`You should be redirected to <a href="${real_url}">${real_url}</a>`);
|
||||
}
|
||||
});
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
const express = require("../express");
|
||||
const express = require('../express');
|
||||
const proxyaddr = require('proxy-addr');
|
||||
var settings = require('../../utils/Settings');
|
||||
var socketio = require('socket.io');
|
||||
var socketIORouter = require("../../handler/SocketIORouter");
|
||||
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
|
||||
const settings = require('../../utils/Settings');
|
||||
const socketio = require('socket.io');
|
||||
const socketIORouter = require('../../handler/SocketIORouter');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
|
||||
var padMessageHandler = require("../../handler/PadMessageHandler");
|
||||
const padMessageHandler = require('../../handler/PadMessageHandler');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
//init socket.io and redirect all requests to the MessageHandler
|
||||
// init socket.io and redirect all requests to the MessageHandler
|
||||
// there shouldn't be a browser that isn't compatible to all
|
||||
// transports in this list at once
|
||||
// e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling
|
||||
var io = socketio({
|
||||
transports: settings.socketTransportProtocols
|
||||
const io = socketio({
|
||||
transports: settings.socketTransportProtocols,
|
||||
}).listen(args.server, {
|
||||
/*
|
||||
* Do not set the "io" cookie.
|
||||
|
@ -61,17 +61,17 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
// https://github.com/Automattic/socket.io/wiki/Migrating-to-1.0
|
||||
// This debug logging environment is set in Settings.js
|
||||
|
||||
//minify socket.io javascript
|
||||
// minify socket.io javascript
|
||||
// Due to a shitty decision by the SocketIO team minification is
|
||||
// no longer available, details available at:
|
||||
// http://stackoverflow.com/questions/23981741/minify-socket-io-socket-io-js-with-1-0
|
||||
// if(settings.minify) io.enable('browser client minification');
|
||||
|
||||
//Initalize the Socket.IO Router
|
||||
// Initalize the Socket.IO Router
|
||||
socketIORouter.setSocketIO(io);
|
||||
socketIORouter.addComponent("pad", padMessageHandler);
|
||||
socketIORouter.addComponent('pad', padMessageHandler);
|
||||
|
||||
hooks.callAll("socketio", {"app": args.app, "io": io, "server": args.server});
|
||||
hooks.callAll('socketio', {app: args.app, io, server: args.server});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,83 +1,81 @@
|
|||
var path = require('path');
|
||||
var eejs = require('ep_etherpad-lite/node/eejs');
|
||||
var toolbar = require("ep_etherpad-lite/node/utils/toolbar");
|
||||
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
var settings = require('../../utils/Settings');
|
||||
const path = require('path');
|
||||
const eejs = require('ep_etherpad-lite/node/eejs');
|
||||
const toolbar = require('ep_etherpad-lite/node/utils/toolbar');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
const settings = require('../../utils/Settings');
|
||||
const webaccess = require('./webaccess');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
// expose current stats
|
||||
args.app.get('/stats', function(req, res) {
|
||||
res.json(require('ep_etherpad-lite/node/stats').toJSON())
|
||||
})
|
||||
args.app.get('/stats', (req, res) => {
|
||||
res.json(require('ep_etherpad-lite/node/stats').toJSON());
|
||||
});
|
||||
|
||||
//serve index.html under /
|
||||
args.app.get('/', function(req, res) {
|
||||
// serve index.html under /
|
||||
args.app.get('/', (req, res) => {
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/index.html', {req}));
|
||||
});
|
||||
|
||||
//serve javascript.html
|
||||
args.app.get('/javascript', function(req, res) {
|
||||
// serve javascript.html
|
||||
args.app.get('/javascript', (req, res) => {
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/javascript.html', {req}));
|
||||
});
|
||||
|
||||
|
||||
//serve robots.txt
|
||||
args.app.get('/robots.txt', function(req, res) {
|
||||
var filePath = path.join(settings.root, "src", "static", "skins", settings.skinName, "robots.txt");
|
||||
res.sendFile(filePath, function(err) {
|
||||
//there is no custom robots.txt, send the default robots.txt which dissallows all
|
||||
if(err)
|
||||
{
|
||||
filePath = path.join(settings.root, "src", "static", "robots.txt");
|
||||
// serve robots.txt
|
||||
args.app.get('/robots.txt', (req, res) => {
|
||||
let filePath = path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'robots.txt');
|
||||
res.sendFile(filePath, (err) => {
|
||||
// there is no custom robots.txt, send the default robots.txt which dissallows all
|
||||
if (err) {
|
||||
filePath = path.join(settings.root, 'src', 'static', 'robots.txt');
|
||||
res.sendFile(filePath);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//serve pad.html under /p
|
||||
args.app.get('/p/:pad', function(req, res, next) {
|
||||
// serve pad.html under /p
|
||||
args.app.get('/p/:pad', (req, res, next) => {
|
||||
// The below might break for pads being rewritten
|
||||
const isReadOnly =
|
||||
req.url.indexOf("/p/r.") === 0 || !webaccess.userCanModify(req.params.pad, req);
|
||||
req.url.indexOf('/p/r.') === 0 || !webaccess.userCanModify(req.params.pad, req);
|
||||
|
||||
hooks.callAll("padInitToolbar", {
|
||||
toolbar: toolbar,
|
||||
isReadOnly: isReadOnly
|
||||
hooks.callAll('padInitToolbar', {
|
||||
toolbar,
|
||||
isReadOnly,
|
||||
});
|
||||
|
||||
res.send(eejs.require("ep_etherpad-lite/templates/pad.html", {
|
||||
req: req,
|
||||
toolbar: toolbar,
|
||||
isReadOnly: isReadOnly
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/pad.html', {
|
||||
req,
|
||||
toolbar,
|
||||
isReadOnly,
|
||||
}));
|
||||
});
|
||||
|
||||
//serve timeslider.html under /p/$padname/timeslider
|
||||
args.app.get('/p/:pad/timeslider', function(req, res, next) {
|
||||
hooks.callAll("padInitToolbar", {
|
||||
toolbar: toolbar
|
||||
// serve timeslider.html under /p/$padname/timeslider
|
||||
args.app.get('/p/:pad/timeslider', (req, res, next) => {
|
||||
hooks.callAll('padInitToolbar', {
|
||||
toolbar,
|
||||
});
|
||||
|
||||
res.send(eejs.require("ep_etherpad-lite/templates/timeslider.html", {
|
||||
req: req,
|
||||
toolbar: toolbar
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', {
|
||||
req,
|
||||
toolbar,
|
||||
}));
|
||||
});
|
||||
|
||||
//serve favicon.ico from all path levels except as a pad name
|
||||
args.app.get( /\/favicon.ico$/, function(req, res) {
|
||||
var filePath = path.join(settings.root, "src", "static", "skins", settings.skinName, "favicon.ico");
|
||||
// serve favicon.ico from all path levels except as a pad name
|
||||
args.app.get(/\/favicon.ico$/, (req, res) => {
|
||||
let filePath = path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico');
|
||||
|
||||
res.sendFile(filePath, function(err) {
|
||||
//there is no custom favicon, send the default favicon
|
||||
if(err)
|
||||
{
|
||||
filePath = path.join(settings.root, "src", "static", "favicon.ico");
|
||||
res.sendFile(filePath, (err) => {
|
||||
// there is no custom favicon, send the default favicon
|
||||
if (err) {
|
||||
filePath = path.join(settings.root, 'src', 'static', 'favicon.ico');
|
||||
res.sendFile(filePath);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
var minify = require('../../utils/Minify');
|
||||
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugin_defs");
|
||||
var CachingMiddleware = require('../../utils/caching_middleware');
|
||||
var settings = require("../../utils/Settings");
|
||||
var Yajsml = require('etherpad-yajsml');
|
||||
var _ = require("underscore");
|
||||
const minify = require('../../utils/Minify');
|
||||
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
|
||||
const CachingMiddleware = require('../../utils/caching_middleware');
|
||||
const settings = require('../../utils/Settings');
|
||||
const Yajsml = require('etherpad-yajsml');
|
||||
const _ = require('underscore');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
|
||||
// Cache both minified and static.
|
||||
var assetCache = new CachingMiddleware;
|
||||
const assetCache = new CachingMiddleware();
|
||||
args.app.all(/\/javascripts\/(.*)/, assetCache.handle);
|
||||
|
||||
// Minify will serve static files compressed (minify enabled). It also has
|
||||
|
@ -18,43 +17,42 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
// Setup middleware that will package JavaScript files served by minify for
|
||||
// CommonJS loader on the client-side.
|
||||
// Hostname "invalid.invalid" is a dummy value to allow parsing as a URI.
|
||||
var jsServer = new (Yajsml.Server)({
|
||||
rootPath: 'javascripts/src/'
|
||||
, rootURI: 'http://invalid.invalid/static/js/'
|
||||
, libraryPath: 'javascripts/lib/'
|
||||
, libraryURI: 'http://invalid.invalid/static/plugins/'
|
||||
, requestURIs: minify.requestURIs // Loop-back is causing problems, this is a workaround.
|
||||
const jsServer = new (Yajsml.Server)({
|
||||
rootPath: 'javascripts/src/',
|
||||
rootURI: 'http://invalid.invalid/static/js/',
|
||||
libraryPath: 'javascripts/lib/',
|
||||
libraryURI: 'http://invalid.invalid/static/plugins/',
|
||||
requestURIs: minify.requestURIs, // Loop-back is causing problems, this is a workaround.
|
||||
});
|
||||
|
||||
var StaticAssociator = Yajsml.associators.StaticAssociator;
|
||||
var associations =
|
||||
const StaticAssociator = Yajsml.associators.StaticAssociator;
|
||||
const associations =
|
||||
Yajsml.associators.associationsForSimpleMapping(minify.tar);
|
||||
var associator = new StaticAssociator(associations);
|
||||
const associator = new StaticAssociator(associations);
|
||||
jsServer.setAssociator(associator);
|
||||
|
||||
args.app.use(jsServer.handle.bind(jsServer));
|
||||
|
||||
// serve plugin definitions
|
||||
// not very static, but served here so that client can do require("pluginfw/static/js/plugin-definitions.js");
|
||||
args.app.get('/pluginfw/plugin-definitions.json', function (req, res, next) {
|
||||
args.app.get('/pluginfw/plugin-definitions.json', (req, res, next) => {
|
||||
const clientParts = _(plugins.parts)
|
||||
.filter((part) => _(part).has('client_hooks'));
|
||||
|
||||
var clientParts = _(plugins.parts)
|
||||
.filter(function(part){ return _(part).has('client_hooks') });
|
||||
|
||||
var clientPlugins = {};
|
||||
const clientPlugins = {};
|
||||
|
||||
_(clientParts).chain()
|
||||
.map(function(part){ return part.plugin })
|
||||
.uniq()
|
||||
.each(function(name){
|
||||
clientPlugins[name] = _(plugins.plugins[name]).clone();
|
||||
delete clientPlugins[name]['package'];
|
||||
});
|
||||
.map((part) => part.plugin)
|
||||
.uniq()
|
||||
.each((name) => {
|
||||
clientPlugins[name] = _(plugins.plugins[name]).clone();
|
||||
delete clientPlugins[name].package;
|
||||
});
|
||||
|
||||
res.header("Content-Type","application/json; charset=utf-8");
|
||||
res.write(JSON.stringify({"plugins": clientPlugins, "parts": clientParts}));
|
||||
res.header('Content-Type', 'application/json; charset=utf-8');
|
||||
res.write(JSON.stringify({plugins: clientPlugins, parts: clientParts}));
|
||||
res.end();
|
||||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
var path = require("path")
|
||||
, npm = require("npm")
|
||||
, fs = require("fs")
|
||||
, util = require("util");
|
||||
const path = require('path');
|
||||
const npm = require('npm');
|
||||
const fs = require('fs');
|
||||
const util = require('util');
|
||||
|
||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||
args.app.get('/tests/frontend/specs_list.js', async function(req, res) {
|
||||
let [coreTests, pluginTests] = await Promise.all([
|
||||
args.app.get('/tests/frontend/specs_list.js', async (req, res) => {
|
||||
const [coreTests, pluginTests] = await Promise.all([
|
||||
exports.getCoreTests(),
|
||||
exports.getPluginTests()
|
||||
exports.getPluginTests(),
|
||||
]);
|
||||
|
||||
// merge the two sets of results
|
||||
|
@ -16,79 +16,77 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
// Keep only *.js files
|
||||
files = files.filter((f) => f.endsWith('.js'));
|
||||
|
||||
console.debug("Sent browser the following test specs:", files);
|
||||
console.debug('Sent browser the following test specs:', files);
|
||||
res.setHeader('content-type', 'text/javascript');
|
||||
res.end("var specs_list = " + JSON.stringify(files) + ";\n");
|
||||
res.end(`var specs_list = ${JSON.stringify(files)};\n`);
|
||||
});
|
||||
|
||||
// path.join seems to normalize by default, but we'll just be explicit
|
||||
var rootTestFolder = path.normalize(path.join(npm.root, "../tests/frontend/"));
|
||||
const rootTestFolder = path.normalize(path.join(npm.root, '../tests/frontend/'));
|
||||
|
||||
var url2FilePath = function(url) {
|
||||
var subPath = url.substr("/tests/frontend".length);
|
||||
if (subPath == "") {
|
||||
subPath = "index.html"
|
||||
const url2FilePath = function (url) {
|
||||
let subPath = url.substr('/tests/frontend'.length);
|
||||
if (subPath == '') {
|
||||
subPath = 'index.html';
|
||||
}
|
||||
subPath = subPath.split("?")[0];
|
||||
subPath = subPath.split('?')[0];
|
||||
|
||||
var filePath = path.normalize(path.join(rootTestFolder, subPath));
|
||||
let filePath = path.normalize(path.join(rootTestFolder, subPath));
|
||||
|
||||
// make sure we jail the paths to the test folder, otherwise serve index
|
||||
if (filePath.indexOf(rootTestFolder) !== 0) {
|
||||
filePath = path.join(rootTestFolder, "index.html");
|
||||
filePath = path.join(rootTestFolder, 'index.html');
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
};
|
||||
|
||||
args.app.get('/tests/frontend/specs/*', function (req, res) {
|
||||
var specFilePath = url2FilePath(req.url);
|
||||
var specFileName = path.basename(specFilePath);
|
||||
args.app.get('/tests/frontend/specs/*', (req, res) => {
|
||||
const specFilePath = url2FilePath(req.url);
|
||||
const specFileName = path.basename(specFilePath);
|
||||
|
||||
fs.readFile(specFilePath, function(err, content) {
|
||||
fs.readFile(specFilePath, (err, content) => {
|
||||
if (err) { return res.send(500); }
|
||||
|
||||
content = "describe(" + JSON.stringify(specFileName) + ", function(){ " + content + " });";
|
||||
content = `describe(${JSON.stringify(specFileName)}, function(){ ${content} });`;
|
||||
|
||||
res.send(content);
|
||||
});
|
||||
});
|
||||
|
||||
args.app.get('/tests/frontend/*', function (req, res) {
|
||||
var filePath = url2FilePath(req.url);
|
||||
args.app.get('/tests/frontend/*', (req, res) => {
|
||||
const filePath = url2FilePath(req.url);
|
||||
res.sendFile(filePath);
|
||||
});
|
||||
|
||||
args.app.get('/tests/frontend', function (req, res) {
|
||||
args.app.get('/tests/frontend', (req, res) => {
|
||||
res.redirect('/tests/frontend/index.html');
|
||||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
||||
const readdir = util.promisify(fs.readdir);
|
||||
|
||||
exports.getPluginTests = async function(callback) {
|
||||
const moduleDir = "node_modules/";
|
||||
const specPath = "/static/tests/frontend/specs/";
|
||||
const staticDir = "/static/plugins/";
|
||||
exports.getPluginTests = async function (callback) {
|
||||
const moduleDir = 'node_modules/';
|
||||
const specPath = '/static/tests/frontend/specs/';
|
||||
const staticDir = '/static/plugins/';
|
||||
|
||||
let pluginSpecs = [];
|
||||
const pluginSpecs = [];
|
||||
|
||||
let plugins = await readdir(moduleDir);
|
||||
let promises = plugins
|
||||
.map(plugin => [ plugin, moduleDir + plugin + specPath] )
|
||||
.filter(([plugin, specDir]) => fs.existsSync(specDir)) // check plugin exists
|
||||
.map(([plugin, specDir]) => {
|
||||
return readdir(specDir)
|
||||
.then(specFiles => specFiles.map(spec => {
|
||||
pluginSpecs.push(staticDir + plugin + specPath + spec);
|
||||
}));
|
||||
});
|
||||
const plugins = await readdir(moduleDir);
|
||||
const promises = plugins
|
||||
.map((plugin) => [plugin, moduleDir + plugin + specPath])
|
||||
.filter(([plugin, specDir]) => fs.existsSync(specDir)) // check plugin exists
|
||||
.map(([plugin, specDir]) => readdir(specDir)
|
||||
.then((specFiles) => specFiles.map((spec) => {
|
||||
pluginSpecs.push(staticDir + plugin + specPath + spec);
|
||||
})));
|
||||
|
||||
return Promise.all(promises).then(() => pluginSpecs);
|
||||
}
|
||||
};
|
||||
|
||||
exports.getCoreTests = function() {
|
||||
exports.getCoreTests = function () {
|
||||
// get the core test specs
|
||||
return readdir('tests/frontend/specs');
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,58 +1,58 @@
|
|||
var languages = require('languages4translatewiki')
|
||||
, fs = require('fs')
|
||||
, path = require('path')
|
||||
, _ = require('underscore')
|
||||
, npm = require('npm')
|
||||
, plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs.js').plugins
|
||||
, semver = require('semver')
|
||||
, existsSync = require('../utils/path_exists')
|
||||
, settings = require('../utils/Settings')
|
||||
const languages = require('languages4translatewiki');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const _ = require('underscore');
|
||||
const npm = require('npm');
|
||||
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs.js').plugins;
|
||||
const semver = require('semver');
|
||||
const existsSync = require('../utils/path_exists');
|
||||
const settings = require('../utils/Settings')
|
||||
;
|
||||
|
||||
|
||||
// returns all existing messages merged together and grouped by langcode
|
||||
// {es: {"foo": "string"}, en:...}
|
||||
function getAllLocales() {
|
||||
var locales2paths = {};
|
||||
const locales2paths = {};
|
||||
|
||||
// Puts the paths of all locale files contained in a given directory
|
||||
// into `locales2paths` (files from various dirs are grouped by lang code)
|
||||
// (only json files with valid language code as name)
|
||||
function extractLangs(dir) {
|
||||
if(!existsSync(dir)) return;
|
||||
var stat = fs.lstatSync(dir);
|
||||
if (!existsSync(dir)) return;
|
||||
let stat = fs.lstatSync(dir);
|
||||
if (!stat.isDirectory() || stat.isSymbolicLink()) return;
|
||||
|
||||
fs.readdirSync(dir).forEach(function(file) {
|
||||
fs.readdirSync(dir).forEach((file) => {
|
||||
file = path.resolve(dir, file);
|
||||
stat = fs.lstatSync(file);
|
||||
if (stat.isDirectory() || stat.isSymbolicLink()) return;
|
||||
|
||||
var ext = path.extname(file)
|
||||
, locale = path.basename(file, ext).toLowerCase();
|
||||
const ext = path.extname(file);
|
||||
const locale = path.basename(file, ext).toLowerCase();
|
||||
|
||||
if ((ext == '.json') && languages.isValid(locale)) {
|
||||
if(!locales2paths[locale]) locales2paths[locale] = [];
|
||||
if (!locales2paths[locale]) locales2paths[locale] = [];
|
||||
locales2paths[locale].push(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//add core supported languages first
|
||||
extractLangs(npm.root+"/ep_etherpad-lite/locales");
|
||||
// add core supported languages first
|
||||
extractLangs(`${npm.root}/ep_etherpad-lite/locales`);
|
||||
|
||||
//add plugins languages (if any)
|
||||
for(var pluginName in plugins) extractLangs(path.join(npm.root, pluginName, 'locales'));
|
||||
// add plugins languages (if any)
|
||||
for (const pluginName in plugins) extractLangs(path.join(npm.root, pluginName, 'locales'));
|
||||
|
||||
// Build a locale index (merge all locale data other than user-supplied overrides)
|
||||
var locales = {}
|
||||
_.each (locales2paths, function(files, langcode) {
|
||||
locales[langcode]={};
|
||||
const locales = {};
|
||||
_.each(locales2paths, (files, langcode) => {
|
||||
locales[langcode] = {};
|
||||
|
||||
files.forEach(function(file) {
|
||||
files.forEach((file) => {
|
||||
let fileContents;
|
||||
try {
|
||||
fileContents = JSON.parse(fs.readFileSync(file,'utf8'));
|
||||
fileContents = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
} catch (err) {
|
||||
console.error(`failed to read JSON file ${file}: ${err}`);
|
||||
throw err;
|
||||
|
@ -64,17 +64,17 @@ function getAllLocales() {
|
|||
// Add custom strings from settings.json
|
||||
// Since this is user-supplied, we'll do some extra sanity checks
|
||||
const wrongFormatErr = Error(
|
||||
"customLocaleStrings in wrong format. See documentation " +
|
||||
"for Customization for Administrators, under Localization.")
|
||||
'customLocaleStrings in wrong format. See documentation ' +
|
||||
'for Customization for Administrators, under Localization.');
|
||||
if (settings.customLocaleStrings) {
|
||||
if (typeof settings.customLocaleStrings !== "object") throw wrongFormatErr
|
||||
_.each(settings.customLocaleStrings, function(overrides, langcode) {
|
||||
if (typeof overrides !== "object") throw wrongFormatErr
|
||||
_.each(overrides, function(localeString, key) {
|
||||
if (typeof localeString !== "string") throw wrongFormatErr
|
||||
locales[langcode][key] = localeString
|
||||
})
|
||||
})
|
||||
if (typeof settings.customLocaleStrings !== 'object') throw wrongFormatErr;
|
||||
_.each(settings.customLocaleStrings, (overrides, langcode) => {
|
||||
if (typeof overrides !== 'object') throw wrongFormatErr;
|
||||
_.each(overrides, (localeString, key) => {
|
||||
if (typeof localeString !== 'string') throw wrongFormatErr;
|
||||
locales[langcode][key] = localeString;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return locales;
|
||||
|
@ -83,45 +83,44 @@ function getAllLocales() {
|
|||
// returns a hash of all available languages availables with nativeName and direction
|
||||
// e.g. { es: {nativeName: "español", direction: "ltr"}, ... }
|
||||
function getAvailableLangs(locales) {
|
||||
var result = {};
|
||||
_.each(_.keys(locales), function(langcode) {
|
||||
const result = {};
|
||||
_.each(_.keys(locales), (langcode) => {
|
||||
result[langcode] = languages.getLanguageInfo(langcode);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// returns locale index that will be served in /locales.json
|
||||
var generateLocaleIndex = function (locales) {
|
||||
var result = _.clone(locales) // keep English strings
|
||||
_.each(_.keys(locales), function(langcode) {
|
||||
if (langcode != 'en') result[langcode]='locales/'+langcode+'.json';
|
||||
const generateLocaleIndex = function (locales) {
|
||||
const result = _.clone(locales); // keep English strings
|
||||
_.each(_.keys(locales), (langcode) => {
|
||||
if (langcode != 'en') result[langcode] = `locales/${langcode}.json`;
|
||||
});
|
||||
return JSON.stringify(result);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
exports.expressCreateServer = function(n, args, cb) {
|
||||
|
||||
//regenerate locales on server restart
|
||||
var locales = getAllLocales();
|
||||
var localeIndex = generateLocaleIndex(locales);
|
||||
exports.expressCreateServer = function (n, args, cb) {
|
||||
// regenerate locales on server restart
|
||||
const locales = getAllLocales();
|
||||
const localeIndex = generateLocaleIndex(locales);
|
||||
exports.availableLangs = getAvailableLangs(locales);
|
||||
|
||||
args.app.get ('/locales/:locale', function(req, res) {
|
||||
//works with /locale/en and /locale/en.json requests
|
||||
var locale = req.params.locale.split('.')[0];
|
||||
args.app.get('/locales/:locale', (req, res) => {
|
||||
// works with /locale/en and /locale/en.json requests
|
||||
const locale = req.params.locale.split('.')[0];
|
||||
if (exports.availableLangs.hasOwnProperty(locale)) {
|
||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
res.send('{"'+locale+'":'+JSON.stringify(locales[locale])+'}');
|
||||
res.send(`{"${locale}":${JSON.stringify(locales[locale])}}`);
|
||||
} else {
|
||||
res.status(404).send('Language not available');
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
args.app.get('/locales.json', function(req, res) {
|
||||
args.app.get('/locales.json', (req, res) => {
|
||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
res.send(localeIndex);
|
||||
})
|
||||
});
|
||||
|
||||
return cb();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var securityManager = require('./db/SecurityManager');
|
||||
const securityManager = require('./db/SecurityManager');
|
||||
|
||||
// checks for padAccess
|
||||
module.exports = async function (req, res) {
|
||||
|
@ -7,7 +7,7 @@ module.exports = async function (req, res) {
|
|||
const accessObj = await securityManager.checkAccess(
|
||||
req.params.pad, req.cookies.sessionID, req.cookies.token, user);
|
||||
|
||||
if (accessObj.accessStatus === "grant") {
|
||||
if (accessObj.accessStatus === 'grant') {
|
||||
// there is access, continue
|
||||
return true;
|
||||
} else {
|
||||
|
@ -19,4 +19,4 @@ module.exports = async function (req, res) {
|
|||
// @TODO - send internal server error here?
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -61,13 +61,13 @@ exports.start = async () => {
|
|||
try {
|
||||
await db.init();
|
||||
await plugins.update();
|
||||
console.info('Installed plugins: ' + plugins.formatPluginsWithVersion());
|
||||
console.debug('Installed parts:\n' + plugins.formatParts());
|
||||
console.debug('Installed hooks:\n' + plugins.formatHooks());
|
||||
console.info(`Installed plugins: ${plugins.formatPluginsWithVersion()}`);
|
||||
console.debug(`Installed parts:\n${plugins.formatParts()}`);
|
||||
console.debug(`Installed hooks:\n${plugins.formatHooks()}`);
|
||||
await hooks.aCallAll('loadSettings', {settings});
|
||||
await hooks.aCallAll('createServer');
|
||||
} catch (e) {
|
||||
console.error('exception thrown: ' + e.message);
|
||||
console.error(`exception thrown: ${e.message}`);
|
||||
if (e.stack) console.log(e.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var measured = require('measured-core')
|
||||
const measured = require('measured-core');
|
||||
|
||||
module.exports = measured.createCollection();
|
||||
|
||||
|
|
|
@ -18,41 +18,39 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var spawn = require('child_process').spawn;
|
||||
var async = require("async");
|
||||
var settings = require("./Settings");
|
||||
var os = require('os');
|
||||
const spawn = require('child_process').spawn;
|
||||
const async = require('async');
|
||||
const settings = require('./Settings');
|
||||
const os = require('os');
|
||||
|
||||
var doConvertTask;
|
||||
let doConvertTask;
|
||||
|
||||
//on windows we have to spawn a process for each convertion, cause the plugin abicommand doesn't exist on this platform
|
||||
if(os.type().indexOf("Windows") > -1)
|
||||
{
|
||||
var stdoutBuffer = "";
|
||||
// on windows we have to spawn a process for each convertion, cause the plugin abicommand doesn't exist on this platform
|
||||
if (os.type().indexOf('Windows') > -1) {
|
||||
let stdoutBuffer = '';
|
||||
|
||||
doConvertTask = function(task, callback) {
|
||||
//span an abiword process to perform the conversion
|
||||
var abiword = spawn(settings.abiword, ["--to=" + task.destFile, task.srcFile]);
|
||||
doConvertTask = function (task, callback) {
|
||||
// span an abiword process to perform the conversion
|
||||
const abiword = spawn(settings.abiword, [`--to=${task.destFile}`, task.srcFile]);
|
||||
|
||||
//delegate the processing of stdout to another function
|
||||
abiword.stdout.on('data', function (data) {
|
||||
//add data to buffer
|
||||
stdoutBuffer+=data.toString();
|
||||
});
|
||||
|
||||
//append error messages to the buffer
|
||||
abiword.stderr.on('data', function (data) {
|
||||
// delegate the processing of stdout to another function
|
||||
abiword.stdout.on('data', (data) => {
|
||||
// add data to buffer
|
||||
stdoutBuffer += data.toString();
|
||||
});
|
||||
|
||||
//throw exceptions if abiword is dieing
|
||||
abiword.on('exit', function (code) {
|
||||
if(code != 0) {
|
||||
// append error messages to the buffer
|
||||
abiword.stderr.on('data', (data) => {
|
||||
stdoutBuffer += data.toString();
|
||||
});
|
||||
|
||||
// throw exceptions if abiword is dieing
|
||||
abiword.on('exit', (code) => {
|
||||
if (code != 0) {
|
||||
return callback(`Abiword died with exit code ${code}`);
|
||||
}
|
||||
|
||||
if(stdoutBuffer != "")
|
||||
{
|
||||
if (stdoutBuffer != '') {
|
||||
console.log(stdoutBuffer);
|
||||
}
|
||||
|
||||
|
@ -60,51 +58,48 @@ if(os.type().indexOf("Windows") > -1)
|
|||
});
|
||||
};
|
||||
|
||||
exports.convertFile = function(srcFile, destFile, type, callback) {
|
||||
doConvertTask({"srcFile": srcFile, "destFile": destFile, "type": type}, callback);
|
||||
exports.convertFile = function (srcFile, destFile, type, callback) {
|
||||
doConvertTask({srcFile, destFile, type}, callback);
|
||||
};
|
||||
}
|
||||
//on unix operating systems, we can start abiword with abicommand and communicate with it via stdin/stdout
|
||||
//thats much faster, about factor 10
|
||||
else
|
||||
{
|
||||
//spawn the abiword process
|
||||
var abiword;
|
||||
var stdoutCallback = null;
|
||||
var spawnAbiword = function (){
|
||||
abiword = spawn(settings.abiword, ["--plugin", "AbiCommand"]);
|
||||
var stdoutBuffer = "";
|
||||
var firstPrompt = true;
|
||||
// on unix operating systems, we can start abiword with abicommand and communicate with it via stdin/stdout
|
||||
// thats much faster, about factor 10
|
||||
else {
|
||||
// spawn the abiword process
|
||||
let abiword;
|
||||
let stdoutCallback = null;
|
||||
var spawnAbiword = function () {
|
||||
abiword = spawn(settings.abiword, ['--plugin', 'AbiCommand']);
|
||||
let stdoutBuffer = '';
|
||||
let firstPrompt = true;
|
||||
|
||||
//append error messages to the buffer
|
||||
abiword.stderr.on('data', function (data) {
|
||||
// append error messages to the buffer
|
||||
abiword.stderr.on('data', (data) => {
|
||||
stdoutBuffer += data.toString();
|
||||
});
|
||||
|
||||
//abiword died, let's restart abiword and return an error with the callback
|
||||
abiword.on('exit', function (code) {
|
||||
// abiword died, let's restart abiword and return an error with the callback
|
||||
abiword.on('exit', (code) => {
|
||||
spawnAbiword();
|
||||
stdoutCallback(`Abiword died with exit code ${code}`);
|
||||
});
|
||||
|
||||
//delegate the processing of stdout to a other function
|
||||
abiword.stdout.on('data',function (data) {
|
||||
//add data to buffer
|
||||
stdoutBuffer+=data.toString();
|
||||
// delegate the processing of stdout to a other function
|
||||
abiword.stdout.on('data', (data) => {
|
||||
// add data to buffer
|
||||
stdoutBuffer += data.toString();
|
||||
|
||||
//we're searching for the prompt, cause this means everything we need is in the buffer
|
||||
if(stdoutBuffer.search("AbiWord:>") != -1)
|
||||
{
|
||||
//filter the feedback message
|
||||
var err = stdoutBuffer.search("OK") != -1 ? null : stdoutBuffer;
|
||||
// we're searching for the prompt, cause this means everything we need is in the buffer
|
||||
if (stdoutBuffer.search('AbiWord:>') != -1) {
|
||||
// filter the feedback message
|
||||
const err = stdoutBuffer.search('OK') != -1 ? null : stdoutBuffer;
|
||||
|
||||
//reset the buffer
|
||||
stdoutBuffer = "";
|
||||
// reset the buffer
|
||||
stdoutBuffer = '';
|
||||
|
||||
//call the callback with the error message
|
||||
//skip the first prompt
|
||||
if(stdoutCallback != null && !firstPrompt)
|
||||
{
|
||||
// call the callback with the error message
|
||||
// skip the first prompt
|
||||
if (stdoutCallback != null && !firstPrompt) {
|
||||
stdoutCallback(err);
|
||||
stdoutCallback = null;
|
||||
}
|
||||
|
@ -115,23 +110,23 @@ else
|
|||
};
|
||||
spawnAbiword();
|
||||
|
||||
doConvertTask = function(task, callback) {
|
||||
abiword.stdin.write("convert " + task.srcFile + " " + task.destFile + " " + task.type + "\n");
|
||||
//create a callback that calls the task callback and the caller callback
|
||||
doConvertTask = function (task, callback) {
|
||||
abiword.stdin.write(`convert ${task.srcFile} ${task.destFile} ${task.type}\n`);
|
||||
// create a callback that calls the task callback and the caller callback
|
||||
stdoutCallback = function (err) {
|
||||
callback();
|
||||
console.log("queue continue");
|
||||
try{
|
||||
console.log('queue continue');
|
||||
try {
|
||||
task.callback(err);
|
||||
}catch(e){
|
||||
console.error("Abiword File failed to convert", e);
|
||||
} catch (e) {
|
||||
console.error('Abiword File failed to convert', e);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
//Queue with the converts we have to do
|
||||
var queue = async.queue(doConvertTask, 1);
|
||||
exports.convertFile = function(srcFile, destFile, type, callback) {
|
||||
queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback});
|
||||
// Queue with the converts we have to do
|
||||
const queue = async.queue(doConvertTask, 1);
|
||||
exports.convertFile = function (srcFile, destFile, type, callback) {
|
||||
queue.push({srcFile, destFile, type, callback});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,17 +18,17 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var log4js = require('log4js');
|
||||
var path = require('path');
|
||||
var _ = require('underscore');
|
||||
const log4js = require('log4js');
|
||||
const path = require('path');
|
||||
const _ = require('underscore');
|
||||
|
||||
var absPathLogger = log4js.getLogger('AbsolutePaths');
|
||||
const absPathLogger = log4js.getLogger('AbsolutePaths');
|
||||
|
||||
/*
|
||||
* findEtherpadRoot() computes its value only on first invocation.
|
||||
* Subsequent invocations are served from this variable.
|
||||
*/
|
||||
var etherpadRoot = null;
|
||||
let etherpadRoot = null;
|
||||
|
||||
/**
|
||||
* If stringArray's last elements are exactly equal to lastDesiredElements,
|
||||
|
@ -40,9 +40,9 @@ var etherpadRoot = null;
|
|||
* @return {string[]|boolean} The shortened array, or false if there was no
|
||||
* overlap.
|
||||
*/
|
||||
var popIfEndsWith = function(stringArray, lastDesiredElements) {
|
||||
const popIfEndsWith = function (stringArray, lastDesiredElements) {
|
||||
if (stringArray.length <= lastDesiredElements.length) {
|
||||
absPathLogger.debug(`In order to pop "${lastDesiredElements.join(path.sep)}" from "${stringArray.join(path.sep)}", it should contain at least ${lastDesiredElements.length + 1 } elements`);
|
||||
absPathLogger.debug(`In order to pop "${lastDesiredElements.join(path.sep)}" from "${stringArray.join(path.sep)}", it should contain at least ${lastDesiredElements.length + 1} elements`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ var popIfEndsWith = function(stringArray, lastDesiredElements) {
|
|||
* @return {string} The identified absolute base path. If such path cannot be
|
||||
* identified, prints a log and exits the application.
|
||||
*/
|
||||
exports.findEtherpadRoot = function() {
|
||||
exports.findEtherpadRoot = function () {
|
||||
if (etherpadRoot !== null) {
|
||||
return etherpadRoot;
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ exports.findEtherpadRoot = function() {
|
|||
*
|
||||
* <BASE_DIR>\src
|
||||
*/
|
||||
var maybeEtherpadRoot = popIfEndsWith(splitFoundRoot, ['src']);
|
||||
let maybeEtherpadRoot = popIfEndsWith(splitFoundRoot, ['src']);
|
||||
|
||||
if ((maybeEtherpadRoot === false) && (process.platform === 'win32')) {
|
||||
/*
|
||||
|
@ -126,7 +126,7 @@ exports.findEtherpadRoot = function() {
|
|||
* it is returned unchanged. Otherwise it is interpreted
|
||||
* relative to exports.root.
|
||||
*/
|
||||
exports.makeAbsolute = function(somePath) {
|
||||
exports.makeAbsolute = function (somePath) {
|
||||
if (path.isAbsolute(somePath)) {
|
||||
return somePath;
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ exports.makeAbsolute = function(somePath) {
|
|||
* a subdirectory of the base one
|
||||
* @return {boolean}
|
||||
*/
|
||||
exports.isSubdir = function(parent, arbitraryDir) {
|
||||
exports.isSubdir = function (parent, arbitraryDir) {
|
||||
// modified from: https://stackoverflow.com/questions/37521893/determine-if-a-path-is-subdirectory-of-another-in-node-js#45242825
|
||||
const relative = path.relative(parent, arbitraryDir);
|
||||
const isSubdir = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);
|
||||
|
|
|
@ -22,30 +22,30 @@
|
|||
// An object containing the parsed command-line options
|
||||
exports.argv = {};
|
||||
|
||||
var argv = process.argv.slice(2);
|
||||
var arg, prevArg;
|
||||
const argv = process.argv.slice(2);
|
||||
let arg, prevArg;
|
||||
|
||||
// Loop through args
|
||||
for ( var i = 0; i < argv.length; i++ ) {
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
arg = argv[i];
|
||||
|
||||
// Override location of settings.json file
|
||||
if ( prevArg == '--settings' || prevArg == '-s' ) {
|
||||
if (prevArg == '--settings' || prevArg == '-s') {
|
||||
exports.argv.settings = arg;
|
||||
}
|
||||
|
||||
// Override location of credentials.json file
|
||||
if ( prevArg == '--credentials' ) {
|
||||
if (prevArg == '--credentials') {
|
||||
exports.argv.credentials = arg;
|
||||
}
|
||||
|
||||
// Override location of settings.json file
|
||||
if ( prevArg == '--sessionkey' ) {
|
||||
if (prevArg == '--sessionkey') {
|
||||
exports.argv.sessionkey = arg;
|
||||
}
|
||||
|
||||
// Override location of settings.json file
|
||||
if ( prevArg == '--apikey' ) {
|
||||
if (prevArg == '--apikey') {
|
||||
exports.argv.apikey = arg;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,41 +15,39 @@
|
|||
*/
|
||||
|
||||
|
||||
let db = require("../db/DB");
|
||||
let hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
const db = require('../db/DB');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
|
||||
exports.getPadRaw = async function(padId) {
|
||||
exports.getPadRaw = async function (padId) {
|
||||
const padKey = `pad:${padId}`;
|
||||
const padcontent = await db.get(padKey);
|
||||
|
||||
let padKey = "pad:" + padId;
|
||||
let padcontent = await db.get(padKey);
|
||||
|
||||
let records = [ padKey ];
|
||||
const records = [padKey];
|
||||
for (let i = 0; i <= padcontent.head; i++) {
|
||||
records.push(padKey + ":revs:" + i);
|
||||
records.push(`${padKey}:revs:${i}`);
|
||||
}
|
||||
|
||||
for (let i = 0; i <= padcontent.chatHead; i++) {
|
||||
records.push(padKey + ":chat:" + i);
|
||||
records.push(`${padKey}:chat:${i}`);
|
||||
}
|
||||
|
||||
let data = {};
|
||||
for (let key of records) {
|
||||
|
||||
const data = {};
|
||||
for (const key of records) {
|
||||
// For each piece of info about a pad.
|
||||
let entry = data[key] = await db.get(key);
|
||||
const entry = data[key] = await db.get(key);
|
||||
|
||||
// Get the Pad Authors
|
||||
if (entry.pool && entry.pool.numToAttrib) {
|
||||
let authors = entry.pool.numToAttrib;
|
||||
const authors = entry.pool.numToAttrib;
|
||||
|
||||
for (let k of Object.keys(authors)) {
|
||||
if (authors[k][0] === "author") {
|
||||
let authorId = authors[k][1];
|
||||
for (const k of Object.keys(authors)) {
|
||||
if (authors[k][0] === 'author') {
|
||||
const authorId = authors[k][1];
|
||||
|
||||
// Get the author info
|
||||
let authorEntry = await db.get("globalAuthor:" + authorId);
|
||||
const authorEntry = await db.get(`globalAuthor:${authorId}`);
|
||||
if (authorEntry) {
|
||||
data["globalAuthor:" + authorId] = authorEntry;
|
||||
data[`globalAuthor:${authorId}`] = authorEntry;
|
||||
if (authorEntry.padIDs) {
|
||||
authorEntry.padIDs = padId;
|
||||
}
|
||||
|
@ -68,4 +66,4 @@ exports.getPadRaw = async function(padId) {
|
|||
}));
|
||||
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -18,24 +18,23 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
|
||||
exports.getPadPlainText = function(pad, revNum){
|
||||
var _analyzeLine = exports._analyzeLine;
|
||||
var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext);
|
||||
var textLines = atext.text.slice(0, -1).split('\n');
|
||||
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||
var apool = pad.pool;
|
||||
exports.getPadPlainText = function (pad, revNum) {
|
||||
const _analyzeLine = exports._analyzeLine;
|
||||
const atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext);
|
||||
const textLines = atext.text.slice(0, -1).split('\n');
|
||||
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||
const apool = pad.pool;
|
||||
|
||||
var pieces = [];
|
||||
for (var i = 0; i < textLines.length; i++){
|
||||
var line = _analyzeLine(textLines[i], attribLines[i], apool);
|
||||
if (line.listLevel){
|
||||
var numSpaces = line.listLevel * 2 - 1;
|
||||
var bullet = '*';
|
||||
const pieces = [];
|
||||
for (let i = 0; i < textLines.length; i++) {
|
||||
const line = _analyzeLine(textLines[i], attribLines[i], apool);
|
||||
if (line.listLevel) {
|
||||
const numSpaces = line.listLevel * 2 - 1;
|
||||
const bullet = '*';
|
||||
pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n');
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
pieces.push(line.text, '\n');
|
||||
}
|
||||
}
|
||||
|
@ -44,38 +43,37 @@ exports.getPadPlainText = function(pad, revNum){
|
|||
};
|
||||
|
||||
|
||||
exports._analyzeLine = function(text, aline, apool){
|
||||
var line = {};
|
||||
exports._analyzeLine = function (text, aline, apool) {
|
||||
const line = {};
|
||||
|
||||
// identify list
|
||||
var lineMarker = 0;
|
||||
let lineMarker = 0;
|
||||
line.listLevel = 0;
|
||||
if (aline){
|
||||
var opIter = Changeset.opIterator(aline);
|
||||
if (opIter.hasNext()){
|
||||
var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
|
||||
if (listType){
|
||||
if (aline) {
|
||||
const opIter = Changeset.opIterator(aline);
|
||||
if (opIter.hasNext()) {
|
||||
let listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
|
||||
if (listType) {
|
||||
lineMarker = 1;
|
||||
listType = /([a-z]+)([0-9]+)/.exec(listType);
|
||||
if (listType){
|
||||
if (listType) {
|
||||
line.listTypeName = listType[1];
|
||||
line.listLevel = Number(listType[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
var opIter2 = Changeset.opIterator(aline);
|
||||
if (opIter2.hasNext()){
|
||||
var start = Changeset.opAttributeValue(opIter2.next(), 'start', apool);
|
||||
if (start){
|
||||
line.start = start;
|
||||
const opIter2 = Changeset.opIterator(aline);
|
||||
if (opIter2.hasNext()) {
|
||||
const start = Changeset.opAttributeValue(opIter2.next(), 'start', apool);
|
||||
if (start) {
|
||||
line.start = start;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lineMarker){
|
||||
if (lineMarker) {
|
||||
line.text = text.substring(1);
|
||||
line.aline = Changeset.subattribution(aline, 1);
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
line.text = text;
|
||||
line.aline = aline;
|
||||
}
|
||||
|
@ -83,8 +81,6 @@ exports._analyzeLine = function(text, aline, apool){
|
|||
};
|
||||
|
||||
|
||||
exports._encodeWhitespace = function(s){
|
||||
return s.replace(/[^\x21-\x7E\s\t\n\r]/gu, function(c){
|
||||
return "&#" +c.codePointAt(0) + ";";
|
||||
});
|
||||
exports._encodeWhitespace = function (s) {
|
||||
return s.replace(/[^\x21-\x7E\s\t\n\r]/gu, (c) => `&#${c.codePointAt(0)};`);
|
||||
};
|
||||
|
|
|
@ -14,14 +14,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
var padManager = require("../db/PadManager");
|
||||
var _ = require('underscore');
|
||||
var Security = require('ep_etherpad-lite/static/js/security');
|
||||
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
var eejs = require('ep_etherpad-lite/node/eejs');
|
||||
var _analyzeLine = require('./ExportHelper')._analyzeLine;
|
||||
var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
const padManager = require('../db/PadManager');
|
||||
const _ = require('underscore');
|
||||
const Security = require('ep_etherpad-lite/static/js/security');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
const eejs = require('ep_etherpad-lite/node/eejs');
|
||||
const _analyzeLine = require('./ExportHelper')._analyzeLine;
|
||||
const _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
|
||||
|
||||
async function getPadHTML(pad, revNum) {
|
||||
let atext = pad.atext;
|
||||
|
@ -39,12 +39,12 @@ exports.getPadHTML = getPadHTML;
|
|||
exports.getHTMLFromAtext = getHTMLFromAtext;
|
||||
|
||||
async function getHTMLFromAtext(pad, atext, authorColors) {
|
||||
var apool = pad.apool();
|
||||
var textLines = atext.text.slice(0, -1).split('\n');
|
||||
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||
const apool = pad.apool();
|
||||
const textLines = atext.text.slice(0, -1).split('\n');
|
||||
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||
|
||||
var tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
|
||||
var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
|
||||
const tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
|
||||
const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
|
||||
|
||||
await Promise.all([
|
||||
// prepare tags stored as ['tag', true] to be exported
|
||||
|
@ -68,56 +68,55 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
|
|||
// and maps them to an index in props
|
||||
// *3:2 -> the attribute *3 means strong
|
||||
// *2:5 -> the attribute *2 means s(trikethrough)
|
||||
var anumMap = {};
|
||||
var css = "";
|
||||
const anumMap = {};
|
||||
let css = '';
|
||||
|
||||
var stripDotFromAuthorID = function(id){
|
||||
return id.replace(/\./g,'_');
|
||||
const stripDotFromAuthorID = function (id) {
|
||||
return id.replace(/\./g, '_');
|
||||
};
|
||||
|
||||
if(authorColors){
|
||||
css+="<style>\n";
|
||||
if (authorColors) {
|
||||
css += '<style>\n';
|
||||
|
||||
for (var a in apool.numToAttrib) {
|
||||
var attr = apool.numToAttrib[a];
|
||||
for (const a in apool.numToAttrib) {
|
||||
const attr = apool.numToAttrib[a];
|
||||
|
||||
//skip non author attributes
|
||||
if(attr[0] === "author" && attr[1] !== ""){
|
||||
//add to props array
|
||||
var propName = "author" + stripDotFromAuthorID(attr[1]);
|
||||
// skip non author attributes
|
||||
if (attr[0] === 'author' && attr[1] !== '') {
|
||||
// add to props array
|
||||
var propName = `author${stripDotFromAuthorID(attr[1])}`;
|
||||
var newLength = props.push(propName);
|
||||
anumMap[a] = newLength -1;
|
||||
anumMap[a] = newLength - 1;
|
||||
|
||||
css+="." + propName + " {background-color: " + authorColors[attr[1]]+ "}\n";
|
||||
} else if(attr[0] === "removed") {
|
||||
var propName = "removed";
|
||||
css += `.${propName} {background-color: ${authorColors[attr[1]]}}\n`;
|
||||
} else if (attr[0] === 'removed') {
|
||||
var propName = 'removed';
|
||||
|
||||
var newLength = props.push(propName);
|
||||
anumMap[a] = newLength -1;
|
||||
anumMap[a] = newLength - 1;
|
||||
|
||||
css+=".removed {text-decoration: line-through; " +
|
||||
"-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; "+
|
||||
"filter: alpha(opacity=80); "+
|
||||
"opacity: 0.8; "+
|
||||
"}\n";
|
||||
css += '.removed {text-decoration: line-through; ' +
|
||||
"-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; " +
|
||||
'filter: alpha(opacity=80); ' +
|
||||
'opacity: 0.8; ' +
|
||||
'}\n';
|
||||
}
|
||||
}
|
||||
|
||||
css+="</style>";
|
||||
css += '</style>';
|
||||
}
|
||||
|
||||
// iterates over all props(h1,h2,strong,...), checks if it is used in
|
||||
// this pad, and if yes puts its attrib id->props value into anumMap
|
||||
props.forEach(function (propName, i) {
|
||||
var attrib = [propName, true];
|
||||
props.forEach((propName, i) => {
|
||||
let attrib = [propName, true];
|
||||
if (_.isArray(propName)) {
|
||||
// propName can be in the form of ['color', 'red'],
|
||||
// see hook exportHtmlAdditionalTagsWithData
|
||||
attrib = propName;
|
||||
}
|
||||
var propTrueNum = apool.putAttrib(attrib, true);
|
||||
if (propTrueNum >= 0)
|
||||
{
|
||||
const propTrueNum = apool.putAttrib(attrib, true);
|
||||
if (propTrueNum >= 0) {
|
||||
anumMap[propTrueNum] = i;
|
||||
}
|
||||
});
|
||||
|
@ -128,15 +127,15 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
|
|||
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
|
||||
// becomes
|
||||
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
|
||||
var taker = Changeset.stringIterator(text);
|
||||
var assem = Changeset.stringAssembler();
|
||||
var openTags = [];
|
||||
const taker = Changeset.stringIterator(text);
|
||||
const assem = Changeset.stringAssembler();
|
||||
const openTags = [];
|
||||
|
||||
function getSpanClassFor(i){
|
||||
//return if author colors are disabled
|
||||
function getSpanClassFor(i) {
|
||||
// return if author colors are disabled
|
||||
if (!authorColors) return false;
|
||||
|
||||
var property = props[i];
|
||||
const property = props[i];
|
||||
|
||||
// we are not insterested on properties in the form of ['color', 'red'],
|
||||
// see hook exportHtmlAdditionalTagsWithData
|
||||
|
@ -144,12 +143,12 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if(property.substr(0,6) === "author"){
|
||||
if (property.substr(0, 6) === 'author') {
|
||||
return stripDotFromAuthorID(property);
|
||||
}
|
||||
|
||||
if(property === "removed"){
|
||||
return "removed";
|
||||
if (property === 'removed') {
|
||||
return 'removed';
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -157,16 +156,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
|
|||
|
||||
// tags added by exportHtmlAdditionalTagsWithData will be exported as <span> with
|
||||
// data attributes
|
||||
function isSpanWithData(i){
|
||||
var property = props[i];
|
||||
function isSpanWithData(i) {
|
||||
const property = props[i];
|
||||
return _.isArray(property);
|
||||
}
|
||||
|
||||
function emitOpenTag(i) {
|
||||
openTags.unshift(i);
|
||||
var spanClass = getSpanClassFor(i);
|
||||
const spanClass = getSpanClassFor(i);
|
||||
|
||||
if(spanClass){
|
||||
if (spanClass) {
|
||||
assem.append('<span class="');
|
||||
assem.append(spanClass);
|
||||
assem.append('">');
|
||||
|
@ -180,10 +179,10 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
|
|||
// this closes an open tag and removes its reference from openTags
|
||||
function emitCloseTag(i) {
|
||||
openTags.shift();
|
||||
var spanClass = getSpanClassFor(i);
|
||||
var spanWithData = isSpanWithData(i);
|
||||
const spanClass = getSpanClassFor(i);
|
||||
const spanWithData = isSpanWithData(i);
|
||||
|
||||
if(spanClass || spanWithData){
|
||||
if (spanClass || spanWithData) {
|
||||
assem.append('</span>');
|
||||
} else {
|
||||
assem.append('</');
|
||||
|
@ -192,90 +191,78 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
|
|||
}
|
||||
}
|
||||
|
||||
var urls = _findURLs(text);
|
||||
const urls = _findURLs(text);
|
||||
|
||||
var idx = 0;
|
||||
let idx = 0;
|
||||
|
||||
function processNextChars(numChars) {
|
||||
if (numChars <= 0)
|
||||
{
|
||||
if (numChars <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
|
||||
const iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
|
||||
idx += numChars;
|
||||
|
||||
// this iterates over every op string and decides which tags to open or to close
|
||||
// based on the attribs used
|
||||
while (iter.hasNext())
|
||||
{
|
||||
var o = iter.next();
|
||||
while (iter.hasNext()) {
|
||||
const o = iter.next();
|
||||
var usedAttribs = [];
|
||||
|
||||
// mark all attribs as used
|
||||
Changeset.eachAttribNumber(o.attribs, function (a) {
|
||||
if (a in anumMap)
|
||||
{
|
||||
Changeset.eachAttribNumber(o.attribs, (a) => {
|
||||
if (a in anumMap) {
|
||||
usedAttribs.push(anumMap[a]); // i = 0 => bold, etc.
|
||||
}
|
||||
});
|
||||
var outermostTag = -1;
|
||||
let outermostTag = -1;
|
||||
// find the outer most open tag that is no longer used
|
||||
for (var i = openTags.length - 1; i >= 0; i--)
|
||||
{
|
||||
if (usedAttribs.indexOf(openTags[i]) === -1)
|
||||
{
|
||||
for (var i = openTags.length - 1; i >= 0; i--) {
|
||||
if (usedAttribs.indexOf(openTags[i]) === -1) {
|
||||
outermostTag = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// close all tags upto the outer most
|
||||
if (outermostTag !== -1)
|
||||
{
|
||||
while ( outermostTag >= 0 )
|
||||
{
|
||||
if (outermostTag !== -1) {
|
||||
while (outermostTag >= 0) {
|
||||
emitCloseTag(openTags[0]);
|
||||
outermostTag--;
|
||||
}
|
||||
}
|
||||
|
||||
// open all tags that are used but not open
|
||||
for (i=0; i < usedAttribs.length; i++)
|
||||
{
|
||||
if (openTags.indexOf(usedAttribs[i]) === -1)
|
||||
{
|
||||
for (i = 0; i < usedAttribs.length; i++) {
|
||||
if (openTags.indexOf(usedAttribs[i]) === -1) {
|
||||
emitOpenTag(usedAttribs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var chars = o.chars;
|
||||
if (o.lines)
|
||||
{
|
||||
let chars = o.chars;
|
||||
if (o.lines) {
|
||||
chars--; // exclude newline at end of line, if present
|
||||
}
|
||||
|
||||
var s = taker.take(chars);
|
||||
let s = taker.take(chars);
|
||||
|
||||
//removes the characters with the code 12. Don't know where they come
|
||||
//from but they break the abiword parser and are completly useless
|
||||
s = s.replace(String.fromCharCode(12), "");
|
||||
// removes the characters with the code 12. Don't know where they come
|
||||
// from but they break the abiword parser and are completly useless
|
||||
s = s.replace(String.fromCharCode(12), '');
|
||||
|
||||
assem.append(_encodeWhitespace(Security.escapeHTML(s)));
|
||||
} // end iteration over spans in line
|
||||
|
||||
// close all the tags that are open after the last op
|
||||
while (openTags.length > 0)
|
||||
{
|
||||
while (openTags.length > 0) {
|
||||
emitCloseTag(openTags[0]);
|
||||
}
|
||||
} // end processNextChars
|
||||
if (urls)
|
||||
{
|
||||
urls.forEach(function (urlData) {
|
||||
var startIndex = urlData[0];
|
||||
var url = urlData[1];
|
||||
var urlLength = url.length;
|
||||
if (urls) {
|
||||
urls.forEach((urlData) => {
|
||||
const startIndex = urlData[0];
|
||||
const url = urlData[1];
|
||||
const urlLength = url.length;
|
||||
processNextChars(startIndex - idx);
|
||||
// Using rel="noreferrer" stops leaking the URL/location of the exported HTML when clicking links in the document.
|
||||
// Not all browsers understand this attribute, but it's part of the HTML5 standard.
|
||||
|
@ -284,16 +271,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
|
|||
// https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
|
||||
// https://mathiasbynens.github.io/rel-noopener/
|
||||
// https://github.com/ether/etherpad-lite/pull/3636
|
||||
assem.append('<a href="' + Security.escapeHTMLAttribute(url) + '" rel="noreferrer noopener">');
|
||||
assem.append(`<a href="${Security.escapeHTMLAttribute(url)}" rel="noreferrer noopener">`);
|
||||
processNextChars(urlLength);
|
||||
assem.append('</a>');
|
||||
});
|
||||
}
|
||||
processNextChars(text.length - idx);
|
||||
|
||||
|
||||
return _processSpaces(assem.toString());
|
||||
} // end getLineHTML
|
||||
var pieces = [css];
|
||||
const pieces = [css];
|
||||
|
||||
// Need to deal with constraints imposed on HTML lists; can
|
||||
// only gain one level of nesting at once, can't change type
|
||||
|
@ -302,56 +289,48 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
|
|||
// so we want to do something reasonable there. We also
|
||||
// want to deal gracefully with blank lines.
|
||||
// => keeps track of the parents level of indentation
|
||||
var openLists = [];
|
||||
for (var i = 0; i < textLines.length; i++)
|
||||
{
|
||||
let openLists = [];
|
||||
for (let i = 0; i < textLines.length; i++) {
|
||||
var context;
|
||||
var line = _analyzeLine(textLines[i], attribLines[i], apool);
|
||||
var lineContent = getLineHTML(line.text, line.aline);
|
||||
if (line.listLevel)//If we are inside a list
|
||||
const lineContent = getLineHTML(line.text, line.aline);
|
||||
if (line.listLevel)// If we are inside a list
|
||||
{
|
||||
context = {
|
||||
line: line,
|
||||
lineContent: lineContent,
|
||||
apool: apool,
|
||||
line,
|
||||
lineContent,
|
||||
apool,
|
||||
attribLine: attribLines[i],
|
||||
text: textLines[i],
|
||||
padId: pad.id
|
||||
padId: pad.id,
|
||||
};
|
||||
var prevLine = null;
|
||||
var nextLine = null;
|
||||
if (i > 0)
|
||||
{
|
||||
prevLine = _analyzeLine(textLines[i -1], attribLines[i -1], apool);
|
||||
let prevLine = null;
|
||||
let nextLine = null;
|
||||
if (i > 0) {
|
||||
prevLine = _analyzeLine(textLines[i - 1], attribLines[i - 1], apool);
|
||||
}
|
||||
if (i < textLines.length)
|
||||
{
|
||||
if (i < textLines.length) {
|
||||
nextLine = _analyzeLine(textLines[i + 1], attribLines[i + 1], apool);
|
||||
}
|
||||
await hooks.aCallAll('getLineHTMLForExport', context);
|
||||
//To create list parent elements
|
||||
if ((!prevLine || prevLine.listLevel !== line.listLevel) || (prevLine && line.listTypeName !== prevLine.listTypeName))
|
||||
{
|
||||
var exists = _.find(openLists, function (item) {
|
||||
return (item.level === line.listLevel && item.type === line.listTypeName);
|
||||
});
|
||||
// To create list parent elements
|
||||
if ((!prevLine || prevLine.listLevel !== line.listLevel) || (prevLine && line.listTypeName !== prevLine.listTypeName)) {
|
||||
const exists = _.find(openLists, (item) => (item.level === line.listLevel && item.type === line.listTypeName));
|
||||
if (!exists) {
|
||||
var prevLevel = 0;
|
||||
let prevLevel = 0;
|
||||
if (prevLine && prevLine.listLevel) {
|
||||
prevLevel = prevLine.listLevel;
|
||||
}
|
||||
if (prevLine && line.listTypeName !== prevLine.listTypeName)
|
||||
{
|
||||
if (prevLine && line.listTypeName !== prevLine.listTypeName) {
|
||||
prevLevel = 0;
|
||||
}
|
||||
|
||||
for (var diff = prevLevel; diff < line.listLevel; diff++) {
|
||||
openLists.push({level: diff, type: line.listTypeName});
|
||||
var prevPiece = pieces[pieces.length - 1];
|
||||
const prevPiece = pieces[pieces.length - 1];
|
||||
|
||||
if (prevPiece.indexOf("<ul") === 0 || prevPiece.indexOf("<ol") === 0 || prevPiece.indexOf("</li>") === 0)
|
||||
{
|
||||
/*
|
||||
if (prevPiece.indexOf('<ul') === 0 || prevPiece.indexOf('<ol') === 0 || prevPiece.indexOf('</li>') === 0) {
|
||||
/*
|
||||
uncommenting this breaks nested ols..
|
||||
if the previous item is NOT a ul, NOT an ol OR closing li then close the list
|
||||
so we consider this HTML, I inserted ** where it throws a problem in Example Wrong..
|
||||
|
@ -367,19 +346,16 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
|
|||
// pieces.push("</li>");
|
||||
|
||||
*/
|
||||
if( (nextLine.listTypeName === 'number') && (nextLine.text === '') ){
|
||||
if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) {
|
||||
// is the listTypeName check needed here? null text might be completely fine!
|
||||
// TODO Check against Uls
|
||||
// don't do anything because the next item is a nested ol openener so we need to keep the li open
|
||||
}else{
|
||||
pieces.push("<li>");
|
||||
} else {
|
||||
pieces.push('<li>');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (line.listTypeName === "number")
|
||||
{
|
||||
if (line.listTypeName === 'number') {
|
||||
// We introduce line.start here, this is useful for continuing Ordered list line numbers
|
||||
// in case you have a bullet in a list IE you Want
|
||||
// 1. hello
|
||||
|
@ -390,80 +366,66 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
|
|||
|
||||
// TODO: This logic could also be used to continue OL with indented content
|
||||
// but that's a job for another day....
|
||||
if(line.start){
|
||||
pieces.push("<ol start=\""+Number(line.start)+"\" class=\"" + line.listTypeName + "\">");
|
||||
}else{
|
||||
pieces.push("<ol class=\"" + line.listTypeName + "\">");
|
||||
if (line.start) {
|
||||
pieces.push(`<ol start="${Number(line.start)}" class="${line.listTypeName}">`);
|
||||
} else {
|
||||
pieces.push(`<ol class="${line.listTypeName}">`);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pieces.push("<ul class=\"" + line.listTypeName + "\">");
|
||||
} else {
|
||||
pieces.push(`<ul class="${line.listTypeName}">`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we're going up a level we shouldn't be adding..
|
||||
if(context.lineContent){
|
||||
pieces.push("<li>", context.lineContent);
|
||||
if (context.lineContent) {
|
||||
pieces.push('<li>', context.lineContent);
|
||||
}
|
||||
|
||||
// To close list elements
|
||||
if (nextLine && nextLine.listLevel === line.listLevel && line.listTypeName === nextLine.listTypeName)
|
||||
{
|
||||
if(context.lineContent){
|
||||
if( (nextLine.listTypeName === 'number') && (nextLine.text === '') ){
|
||||
if (nextLine && nextLine.listLevel === line.listLevel && line.listTypeName === nextLine.listTypeName) {
|
||||
if (context.lineContent) {
|
||||
if ((nextLine.listTypeName === 'number') && (nextLine.text === '')) {
|
||||
// is the listTypeName check needed here? null text might be completely fine!
|
||||
// TODO Check against Uls
|
||||
// don't do anything because the next item is a nested ol openener so we need to keep the li open
|
||||
}else{
|
||||
pieces.push("</li>");
|
||||
} else {
|
||||
pieces.push('</li>');
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if ((!nextLine || !nextLine.listLevel || nextLine.listLevel < line.listLevel) || (nextLine && line.listTypeName !== nextLine.listTypeName))
|
||||
{
|
||||
var nextLevel = 0;
|
||||
if ((!nextLine || !nextLine.listLevel || nextLine.listLevel < line.listLevel) || (nextLine && line.listTypeName !== nextLine.listTypeName)) {
|
||||
let nextLevel = 0;
|
||||
if (nextLine && nextLine.listLevel) {
|
||||
nextLevel = nextLine.listLevel;
|
||||
}
|
||||
if (nextLine && line.listTypeName !== nextLine.listTypeName)
|
||||
{
|
||||
if (nextLine && line.listTypeName !== nextLine.listTypeName) {
|
||||
nextLevel = 0;
|
||||
}
|
||||
|
||||
for (var diff = nextLevel; diff < line.listLevel; diff++)
|
||||
{
|
||||
openLists = openLists.filter(function(el) {
|
||||
return el.level !== diff && el.type !== line.listTypeName;
|
||||
});
|
||||
for (var diff = nextLevel; diff < line.listLevel; diff++) {
|
||||
openLists = openLists.filter((el) => el.level !== diff && el.type !== line.listTypeName);
|
||||
|
||||
if (pieces[pieces.length - 1].indexOf("</ul") === 0 || pieces[pieces.length - 1].indexOf("</ol") === 0)
|
||||
{
|
||||
pieces.push("</li>");
|
||||
if (pieces[pieces.length - 1].indexOf('</ul') === 0 || pieces[pieces.length - 1].indexOf('</ol') === 0) {
|
||||
pieces.push('</li>');
|
||||
}
|
||||
|
||||
if (line.listTypeName === "number")
|
||||
{
|
||||
pieces.push("</ol>");
|
||||
}
|
||||
else
|
||||
{
|
||||
pieces.push("</ul>");
|
||||
if (line.listTypeName === 'number') {
|
||||
pieces.push('</ol>');
|
||||
} else {
|
||||
pieces.push('</ul>');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else//outside any list, need to close line.listLevel of lists
|
||||
} else// outside any list, need to close line.listLevel of lists
|
||||
{
|
||||
context = {
|
||||
line: line,
|
||||
lineContent: lineContent,
|
||||
apool: apool,
|
||||
line,
|
||||
lineContent,
|
||||
apool,
|
||||
attribLine: attribLines[i],
|
||||
text: textLines[i],
|
||||
padId: pad.id
|
||||
padId: pad.id,
|
||||
};
|
||||
|
||||
await hooks.aCallAll('getLineHTMLForExport', context);
|
||||
|
@ -475,46 +437,45 @@ async function getHTMLFromAtext(pad, atext, authorColors) {
|
|||
}
|
||||
|
||||
exports.getPadHTMLDocument = async function (padId, revNum) {
|
||||
let pad = await padManager.getPad(padId);
|
||||
const pad = await padManager.getPad(padId);
|
||||
|
||||
// Include some Styles into the Head for Export
|
||||
let stylesForExportCSS = "";
|
||||
let stylesForExport = await hooks.aCallAll("stylesForExport", padId);
|
||||
stylesForExport.forEach(function(css){
|
||||
let stylesForExportCSS = '';
|
||||
const stylesForExport = await hooks.aCallAll('stylesForExport', padId);
|
||||
stylesForExport.forEach((css) => {
|
||||
stylesForExportCSS += css;
|
||||
});
|
||||
|
||||
let html = await getPadHTML(pad, revNum);
|
||||
|
||||
for (const hookHtml of await hooks.aCallAll("exportHTMLAdditionalContent", {padId})) {
|
||||
for (const hookHtml of await hooks.aCallAll('exportHTMLAdditionalContent', {padId})) {
|
||||
html += hookHtml;
|
||||
}
|
||||
|
||||
return eejs.require("ep_etherpad-lite/templates/export_html.html", {
|
||||
return eejs.require('ep_etherpad-lite/templates/export_html.html', {
|
||||
body: html,
|
||||
padId: Security.escapeHTML(padId),
|
||||
extraCSS: stylesForExportCSS
|
||||
extraCSS: stylesForExportCSS,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// copied from ACE
|
||||
var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
|
||||
var _REGEX_SPACE = /\s/;
|
||||
var _REGEX_URLCHAR = new RegExp('(' + /[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source + '|' + _REGEX_WORDCHAR.source + ')');
|
||||
var _REGEX_URL = new RegExp(/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source + '*(?![:.,;])' + _REGEX_URLCHAR.source, 'g');
|
||||
const _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
|
||||
const _REGEX_SPACE = /\s/;
|
||||
const _REGEX_URLCHAR = new RegExp(`(${/[-:@a-zA-Z0-9_.,~%+\/\\?=&#;()$]/.source}|${_REGEX_WORDCHAR.source})`);
|
||||
const _REGEX_URL = new RegExp(`${/(?:(?:https?|s?ftp|ftps|file|smb|afp|nfs|(x-)?man|gopher|txmt):\/\/|mailto:)/.source + _REGEX_URLCHAR.source}*(?![:.,;])${_REGEX_URLCHAR.source}`, 'g');
|
||||
|
||||
// returns null if no URLs, or [[startIndex1, url1], [startIndex2, url2], ...]
|
||||
|
||||
|
||||
function _findURLs(text) {
|
||||
_REGEX_URL.lastIndex = 0;
|
||||
var urls = null;
|
||||
var execResult;
|
||||
while ((execResult = _REGEX_URL.exec(text)))
|
||||
{
|
||||
let urls = null;
|
||||
let execResult;
|
||||
while ((execResult = _REGEX_URL.exec(text))) {
|
||||
urls = (urls || []);
|
||||
var startIndex = execResult.index;
|
||||
var url = execResult[0];
|
||||
const startIndex = execResult.index;
|
||||
const url = execResult[0];
|
||||
urls.push([startIndex, url]);
|
||||
}
|
||||
|
||||
|
@ -523,50 +484,46 @@ function _findURLs(text) {
|
|||
|
||||
|
||||
// copied from ACE
|
||||
function _processSpaces(s){
|
||||
var doesWrap = true;
|
||||
if (s.indexOf("<") < 0 && !doesWrap){
|
||||
function _processSpaces(s) {
|
||||
const doesWrap = true;
|
||||
if (s.indexOf('<') < 0 && !doesWrap) {
|
||||
// short-cut
|
||||
return s.replace(/ /g, ' ');
|
||||
}
|
||||
var parts = [];
|
||||
s.replace(/<[^>]*>?| |[^ <]+/g, function (m){
|
||||
const parts = [];
|
||||
s.replace(/<[^>]*>?| |[^ <]+/g, (m) => {
|
||||
parts.push(m);
|
||||
});
|
||||
if (doesWrap){
|
||||
var endOfLine = true;
|
||||
var beforeSpace = false;
|
||||
if (doesWrap) {
|
||||
let endOfLine = true;
|
||||
let beforeSpace = false;
|
||||
// last space in a run is normal, others are nbsp,
|
||||
// end of line is nbsp
|
||||
for (var i = parts.length - 1; i >= 0; i--){
|
||||
for (var i = parts.length - 1; i >= 0; i--) {
|
||||
var p = parts[i];
|
||||
if (p == " "){
|
||||
if (p == ' ') {
|
||||
if (endOfLine || beforeSpace) parts[i] = ' ';
|
||||
endOfLine = false;
|
||||
beforeSpace = true;
|
||||
}
|
||||
else if (p.charAt(0) != "<"){
|
||||
} else if (p.charAt(0) != '<') {
|
||||
endOfLine = false;
|
||||
beforeSpace = false;
|
||||
}
|
||||
}
|
||||
// beginning of line is nbsp
|
||||
for (i = 0; i < parts.length; i++){
|
||||
for (i = 0; i < parts.length; i++) {
|
||||
p = parts[i];
|
||||
if (p == " "){
|
||||
if (p == ' ') {
|
||||
parts[i] = ' ';
|
||||
break;
|
||||
}
|
||||
else if (p.charAt(0) != "<"){
|
||||
} else if (p.charAt(0) != '<') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < parts.length; i++){
|
||||
} else {
|
||||
for (i = 0; i < parts.length; i++) {
|
||||
p = parts[i];
|
||||
if (p == " "){
|
||||
if (p == ' ') {
|
||||
parts[i] = ' ';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,12 +18,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
var padManager = require("../db/PadManager");
|
||||
var _analyzeLine = require('./ExportHelper')._analyzeLine;
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
const padManager = require('../db/PadManager');
|
||||
const _analyzeLine = require('./ExportHelper')._analyzeLine;
|
||||
|
||||
// This is slightly different than the HTML method as it passes the output to getTXTFromAText
|
||||
var getPadTXT = async function(pad, revNum) {
|
||||
const getPadTXT = async function (pad, revNum) {
|
||||
let atext = pad.atext;
|
||||
|
||||
if (revNum != undefined) {
|
||||
|
@ -33,57 +33,57 @@ var getPadTXT = async function(pad, revNum) {
|
|||
|
||||
// convert atext to html
|
||||
return getTXTFromAtext(pad, atext);
|
||||
}
|
||||
};
|
||||
|
||||
// This is different than the functionality provided in ExportHtml as it provides formatting
|
||||
// functionality that is designed specifically for TXT exports
|
||||
function getTXTFromAtext(pad, atext, authorColors) {
|
||||
var apool = pad.apool();
|
||||
var textLines = atext.text.slice(0, -1).split('\n');
|
||||
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||
const apool = pad.apool();
|
||||
const textLines = atext.text.slice(0, -1).split('\n');
|
||||
const attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
|
||||
|
||||
var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
|
||||
var anumMap = {};
|
||||
var css = "";
|
||||
const props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
|
||||
const anumMap = {};
|
||||
const css = '';
|
||||
|
||||
props.forEach(function(propName, i) {
|
||||
var propTrueNum = apool.putAttrib([propName, true], true);
|
||||
props.forEach((propName, i) => {
|
||||
const propTrueNum = apool.putAttrib([propName, true], true);
|
||||
if (propTrueNum >= 0) {
|
||||
anumMap[propTrueNum] = i;
|
||||
}
|
||||
});
|
||||
|
||||
function getLineTXT(text, attribs) {
|
||||
var propVals = [false, false, false];
|
||||
var ENTER = 1;
|
||||
var STAY = 2;
|
||||
var LEAVE = 0;
|
||||
const propVals = [false, false, false];
|
||||
const ENTER = 1;
|
||||
const STAY = 2;
|
||||
const LEAVE = 0;
|
||||
|
||||
// Use order of tags (b/i/u) as order of nesting, for simplicity
|
||||
// and decent nesting. For example,
|
||||
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
|
||||
// becomes
|
||||
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
|
||||
var taker = Changeset.stringIterator(text);
|
||||
var assem = Changeset.stringAssembler();
|
||||
const taker = Changeset.stringIterator(text);
|
||||
const assem = Changeset.stringAssembler();
|
||||
|
||||
var idx = 0;
|
||||
let idx = 0;
|
||||
|
||||
function processNextChars(numChars) {
|
||||
if (numChars <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
|
||||
const iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
|
||||
idx += numChars;
|
||||
|
||||
while (iter.hasNext()) {
|
||||
var o = iter.next();
|
||||
const o = iter.next();
|
||||
var propChanged = false;
|
||||
|
||||
Changeset.eachAttribNumber(o.attribs, function(a) {
|
||||
Changeset.eachAttribNumber(o.attribs, (a) => {
|
||||
if (a in anumMap) {
|
||||
var i = anumMap[a]; // i = 0 => bold, etc.
|
||||
const i = anumMap[a]; // i = 0 => bold, etc.
|
||||
|
||||
if (!propVals[i]) {
|
||||
propVals[i] = ENTER;
|
||||
|
@ -108,20 +108,18 @@ function getTXTFromAtext(pad, atext, authorColors) {
|
|||
// according to what happens at start of span
|
||||
if (propChanged) {
|
||||
// leaving bold (e.g.) also leaves italics, etc.
|
||||
var left = false;
|
||||
let left = false;
|
||||
|
||||
for (var i = 0; i < propVals.length; i++) {
|
||||
var v = propVals[i];
|
||||
const v = propVals[i];
|
||||
|
||||
if (!left) {
|
||||
if (v === LEAVE) {
|
||||
left = true;
|
||||
}
|
||||
} else {
|
||||
if (v === true) {
|
||||
// tag will be closed and re-opened
|
||||
propVals[i] = STAY;
|
||||
}
|
||||
} else if (v === true) {
|
||||
// tag will be closed and re-opened
|
||||
propVals[i] = STAY;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,11 +127,11 @@ function getTXTFromAtext(pad, atext, authorColors) {
|
|||
|
||||
for (var i = propVals.length - 1; i >= 0; i--) {
|
||||
if (propVals[i] === LEAVE) {
|
||||
//emitCloseTag(i);
|
||||
// emitCloseTag(i);
|
||||
tags2close.push(i);
|
||||
propVals[i] = false;
|
||||
} else if (propVals[i] === STAY) {
|
||||
//emitCloseTag(i);
|
||||
// emitCloseTag(i);
|
||||
tags2close.push(i);
|
||||
}
|
||||
}
|
||||
|
@ -146,13 +144,13 @@ function getTXTFromAtext(pad, atext, authorColors) {
|
|||
// propVals is now all {true,false} again
|
||||
} // end if (propChanged)
|
||||
|
||||
var chars = o.chars;
|
||||
let chars = o.chars;
|
||||
if (o.lines) {
|
||||
// exclude newline at end of line, if present
|
||||
chars--;
|
||||
}
|
||||
|
||||
var s = taker.take(chars);
|
||||
const s = taker.take(chars);
|
||||
|
||||
// removes the characters with the code 12. Don't know where they come
|
||||
// from but they break the abiword parser and are completly useless
|
||||
|
@ -172,14 +170,13 @@ function getTXTFromAtext(pad, atext, authorColors) {
|
|||
propVals[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // end processNextChars
|
||||
|
||||
processNextChars(text.length - idx);
|
||||
return(assem.toString());
|
||||
return (assem.toString());
|
||||
} // end getLineHTML
|
||||
|
||||
var pieces = [css];
|
||||
const pieces = [css];
|
||||
|
||||
// Need to deal with constraints imposed on HTML lists; can
|
||||
// only gain one level of nesting at once, can't change type
|
||||
|
@ -189,34 +186,33 @@ function getTXTFromAtext(pad, atext, authorColors) {
|
|||
// want to deal gracefully with blank lines.
|
||||
// => keeps track of the parents level of indentation
|
||||
|
||||
var listNumbers = {};
|
||||
var prevListLevel;
|
||||
const listNumbers = {};
|
||||
let prevListLevel;
|
||||
|
||||
for (var i = 0; i < textLines.length; i++) {
|
||||
for (let i = 0; i < textLines.length; i++) {
|
||||
const line = _analyzeLine(textLines[i], attribLines[i], apool);
|
||||
let lineContent = getLineTXT(line.text, line.aline);
|
||||
|
||||
var line = _analyzeLine(textLines[i], attribLines[i], apool);
|
||||
var lineContent = getLineTXT(line.text, line.aline);
|
||||
|
||||
if (line.listTypeName == "bullet") {
|
||||
lineContent = "* " + lineContent; // add a bullet
|
||||
if (line.listTypeName == 'bullet') {
|
||||
lineContent = `* ${lineContent}`; // add a bullet
|
||||
}
|
||||
|
||||
if (line.listTypeName !== "number") {
|
||||
if (line.listTypeName !== 'number') {
|
||||
// We're no longer in an OL so we can reset counting
|
||||
for (var key in listNumbers) {
|
||||
for (const key in listNumbers) {
|
||||
delete listNumbers[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (line.listLevel > 0) {
|
||||
for (var j = line.listLevel - 1; j >= 0; j--) {
|
||||
for (let j = line.listLevel - 1; j >= 0; j--) {
|
||||
pieces.push('\t'); // tab indent list numbers..
|
||||
if(!listNumbers[line.listLevel]){
|
||||
if (!listNumbers[line.listLevel]) {
|
||||
listNumbers[line.listLevel] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (line.listTypeName == "number") {
|
||||
if (line.listTypeName == 'number') {
|
||||
/*
|
||||
* listLevel == amount of indentation
|
||||
* listNumber(s) == item number
|
||||
|
@ -230,19 +226,19 @@ function getTXTFromAtext(pad, atext, authorColors) {
|
|||
* To handle going back to 2.1 when prevListLevel is lower number
|
||||
* than current line.listLevel then reset the object value
|
||||
*/
|
||||
if(line.listLevel < prevListLevel){
|
||||
if (line.listLevel < prevListLevel) {
|
||||
delete listNumbers[prevListLevel];
|
||||
}
|
||||
|
||||
listNumbers[line.listLevel]++;
|
||||
if(line.listLevel > 1){
|
||||
var x = 1;
|
||||
while(x <= line.listLevel-1){
|
||||
pieces.push(listNumbers[x]+".")
|
||||
if (line.listLevel > 1) {
|
||||
let x = 1;
|
||||
while (x <= line.listLevel - 1) {
|
||||
pieces.push(`${listNumbers[x]}.`);
|
||||
x++;
|
||||
}
|
||||
}
|
||||
pieces.push(listNumbers[line.listLevel]+". ")
|
||||
pieces.push(`${listNumbers[line.listLevel]}. `);
|
||||
prevListLevel = line.listLevel;
|
||||
}
|
||||
|
||||
|
@ -257,7 +253,7 @@ function getTXTFromAtext(pad, atext, authorColors) {
|
|||
|
||||
exports.getTXTFromAtext = getTXTFromAtext;
|
||||
|
||||
exports.getPadTXTDocument = async function(padId, revNum) {
|
||||
let pad = await padManager.getPad(padId);
|
||||
exports.getPadTXTDocument = async function (padId, revNum) {
|
||||
const pad = await padManager.getPad(padId);
|
||||
return getPadTXT(pad, revNum);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,14 +14,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var log4js = require('log4js');
|
||||
const db = require("../db/DB");
|
||||
const log4js = require('log4js');
|
||||
const db = require('../db/DB');
|
||||
const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
|
||||
exports.setPadRaw = function(padId, records) {
|
||||
exports.setPadRaw = function (padId, records) {
|
||||
records = JSON.parse(records);
|
||||
|
||||
Object.keys(records).forEach(async function(key) {
|
||||
Object.keys(records).forEach(async (key) => {
|
||||
let value = records[key];
|
||||
|
||||
if (!value) {
|
||||
|
@ -36,7 +36,7 @@ exports.setPadRaw = function(padId, records) {
|
|||
newKey = key;
|
||||
|
||||
// Does this author already exist?
|
||||
let author = await db.get(key);
|
||||
const author = await db.get(key);
|
||||
|
||||
if (author) {
|
||||
// Yes, add the padID to the author
|
||||
|
@ -47,20 +47,20 @@ exports.setPadRaw = function(padId, records) {
|
|||
value = author;
|
||||
} else {
|
||||
// No, create a new array with the author info in
|
||||
value.padIDs = [ padId ];
|
||||
value.padIDs = [padId];
|
||||
}
|
||||
} else {
|
||||
// Not author data, probably pad data
|
||||
// we can split it to look to see if it's pad data
|
||||
let oldPadId = key.split(":");
|
||||
const oldPadId = key.split(':');
|
||||
|
||||
// we know it's pad data
|
||||
if (oldPadId[0] === "pad") {
|
||||
if (oldPadId[0] === 'pad') {
|
||||
// so set the new pad id for the author
|
||||
oldPadId[1] = padId;
|
||||
|
||||
// and create the value
|
||||
newKey = oldPadId.join(":"); // create the new key
|
||||
newKey = oldPadId.join(':'); // create the new key
|
||||
}
|
||||
|
||||
// is this a key that is supported through a plugin?
|
||||
|
@ -74,4 +74,4 @@ exports.setPadRaw = function(padId, records) {
|
|||
// Write the value to the server
|
||||
await db.set(newKey, value);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,75 +14,75 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const log4js = require('log4js');
|
||||
const Changeset = require("ep_etherpad-lite/static/js/Changeset");
|
||||
const contentcollector = require("ep_etherpad-lite/static/js/contentcollector");
|
||||
const cheerio = require("cheerio");
|
||||
const rehype = require("rehype")
|
||||
const format = require("rehype-format")
|
||||
const log4js = require('log4js');
|
||||
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
|
||||
const contentcollector = require('ep_etherpad-lite/static/js/contentcollector');
|
||||
const cheerio = require('cheerio');
|
||||
const rehype = require('rehype');
|
||||
const format = require('rehype-format');
|
||||
|
||||
|
||||
exports.setPadHTML = async (pad, html) => {
|
||||
var apiLogger = log4js.getLogger("ImportHtml");
|
||||
const apiLogger = log4js.getLogger('ImportHtml');
|
||||
|
||||
var opts = {
|
||||
const opts = {
|
||||
indentInitial: false,
|
||||
indent: -1
|
||||
}
|
||||
indent: -1,
|
||||
};
|
||||
|
||||
rehype()
|
||||
.use(format, opts)
|
||||
.process(html, function(err, output){
|
||||
html = String(output).replace(/(\r\n|\n|\r)/gm,"");
|
||||
})
|
||||
.use(format, opts)
|
||||
.process(html, (err, output) => {
|
||||
html = String(output).replace(/(\r\n|\n|\r)/gm, '');
|
||||
});
|
||||
|
||||
var $ = cheerio.load(html);
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
// Appends a line break, used by Etherpad to ensure a caret is available
|
||||
// below the last line of an import
|
||||
$('body').append("<p></p>");
|
||||
$('body').append('<p></p>');
|
||||
|
||||
var doc = $('html')[0];
|
||||
const doc = $('html')[0];
|
||||
apiLogger.debug('html:');
|
||||
apiLogger.debug(html);
|
||||
|
||||
// Convert a dom tree into a list of lines and attribute liens
|
||||
// using the content collector object
|
||||
var cc = contentcollector.makeContentCollector(true, null, pad.pool);
|
||||
const cc = contentcollector.makeContentCollector(true, null, pad.pool);
|
||||
try {
|
||||
// we use a try here because if the HTML is bad it will blow up
|
||||
cc.collectContent(doc);
|
||||
} catch(e) {
|
||||
apiLogger.warn("HTML was not properly formed", e);
|
||||
} catch (e) {
|
||||
apiLogger.warn('HTML was not properly formed', e);
|
||||
|
||||
// don't process the HTML because it was bad
|
||||
throw e;
|
||||
}
|
||||
|
||||
var result = cc.finish();
|
||||
const result = cc.finish();
|
||||
|
||||
apiLogger.debug('Lines:');
|
||||
|
||||
var i;
|
||||
let i;
|
||||
for (i = 0; i < result.lines.length; i++) {
|
||||
apiLogger.debug('Line ' + (i + 1) + ' text: ' + result.lines[i]);
|
||||
apiLogger.debug('Line ' + (i + 1) + ' attributes: ' + result.lineAttribs[i]);
|
||||
apiLogger.debug(`Line ${i + 1} text: ${result.lines[i]}`);
|
||||
apiLogger.debug(`Line ${i + 1} attributes: ${result.lineAttribs[i]}`);
|
||||
}
|
||||
|
||||
// Get the new plain text and its attributes
|
||||
var newText = result.lines.join('\n');
|
||||
const newText = result.lines.join('\n');
|
||||
apiLogger.debug('newText:');
|
||||
apiLogger.debug(newText);
|
||||
var newAttribs = result.lineAttribs.join('|1+1') + '|1+1';
|
||||
const newAttribs = `${result.lineAttribs.join('|1+1')}|1+1`;
|
||||
|
||||
function eachAttribRun(attribs, func /*(startInNewText, endInNewText, attribs)*/ ) {
|
||||
var attribsIter = Changeset.opIterator(attribs);
|
||||
var textIndex = 0;
|
||||
var newTextStart = 0;
|
||||
var newTextEnd = newText.length;
|
||||
function eachAttribRun(attribs, func /* (startInNewText, endInNewText, attribs)*/) {
|
||||
const attribsIter = Changeset.opIterator(attribs);
|
||||
let textIndex = 0;
|
||||
const newTextStart = 0;
|
||||
const newTextEnd = newText.length;
|
||||
while (attribsIter.hasNext()) {
|
||||
var op = attribsIter.next();
|
||||
var nextIndex = textIndex + op.chars;
|
||||
const op = attribsIter.next();
|
||||
const nextIndex = textIndex + op.chars;
|
||||
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
|
||||
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
|
||||
}
|
||||
|
@ -91,19 +91,19 @@ exports.setPadHTML = async (pad, html) => {
|
|||
}
|
||||
|
||||
// create a new changeset with a helper builder object
|
||||
var builder = Changeset.builder(1);
|
||||
const builder = Changeset.builder(1);
|
||||
|
||||
// assemble each line into the builder
|
||||
eachAttribRun(newAttribs, function(start, end, attribs) {
|
||||
eachAttribRun(newAttribs, (start, end, attribs) => {
|
||||
builder.insert(newText.substring(start, end), attribs);
|
||||
});
|
||||
|
||||
// the changeset is ready!
|
||||
var theChangeset = builder.toString();
|
||||
const theChangeset = builder.toString();
|
||||
|
||||
apiLogger.debug('The changeset: ' + theChangeset);
|
||||
apiLogger.debug(`The changeset: ${theChangeset}`);
|
||||
await Promise.all([
|
||||
pad.setText('\n'),
|
||||
pad.appendRevision(theChangeset),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -16,18 +16,18 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var async = require("async");
|
||||
var fs = require("fs");
|
||||
var log4js = require('log4js');
|
||||
var os = require("os");
|
||||
var path = require("path");
|
||||
var settings = require("./Settings");
|
||||
var spawn = require("child_process").spawn;
|
||||
const async = require('async');
|
||||
const fs = require('fs');
|
||||
const log4js = require('log4js');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const settings = require('./Settings');
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
// Conversion tasks will be queued up, so we don't overload the system
|
||||
var queue = async.queue(doConvertTask, 1);
|
||||
const queue = async.queue(doConvertTask, 1);
|
||||
|
||||
var libreOfficeLogger = log4js.getLogger('LibreOffice');
|
||||
const libreOfficeLogger = log4js.getLogger('LibreOffice');
|
||||
|
||||
/**
|
||||
* Convert a file from one type to another
|
||||
|
@ -37,18 +37,18 @@ var libreOfficeLogger = log4js.getLogger('LibreOffice');
|
|||
* @param {String} type The type to convert into
|
||||
* @param {Function} callback Standard callback function
|
||||
*/
|
||||
exports.convertFile = function(srcFile, destFile, type, callback) {
|
||||
exports.convertFile = function (srcFile, destFile, type, callback) {
|
||||
// Used for the moving of the file, not the conversion
|
||||
var fileExtension = type;
|
||||
const fileExtension = type;
|
||||
|
||||
if (type === "html") {
|
||||
if (type === 'html') {
|
||||
// "html:XHTML Writer File:UTF8" does a better job than normal html exports
|
||||
if (path.extname(srcFile).toLowerCase() === ".doc") {
|
||||
type = "html";
|
||||
if (path.extname(srcFile).toLowerCase() === '.doc') {
|
||||
type = 'html';
|
||||
}
|
||||
// PDF files need to be converted with LO Draw ref https://github.com/ether/etherpad-lite/issues/4151
|
||||
if (path.extname(srcFile).toLowerCase() === ".pdf") {
|
||||
type = "html:XHTML Draw File"
|
||||
if (path.extname(srcFile).toLowerCase() === '.pdf') {
|
||||
type = 'html:XHTML Draw File';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,58 +57,60 @@ exports.convertFile = function(srcFile, destFile, type, callback) {
|
|||
// to avoid `Error: no export filter for /tmp/xxxx.doc` error
|
||||
if (type === 'doc') {
|
||||
queue.push({
|
||||
"srcFile": srcFile,
|
||||
"destFile": destFile.replace(/\.doc$/, '.odt'),
|
||||
"type": 'odt',
|
||||
"callback": function () {
|
||||
queue.push({"srcFile": srcFile.replace(/\.html$/, '.odt'), "destFile": destFile, "type": type, "callback": callback, "fileExtension": fileExtension });
|
||||
}
|
||||
srcFile,
|
||||
destFile: destFile.replace(/\.doc$/, '.odt'),
|
||||
type: 'odt',
|
||||
callback() {
|
||||
queue.push({srcFile: srcFile.replace(/\.html$/, '.odt'), destFile, type, callback, fileExtension});
|
||||
},
|
||||
});
|
||||
} else {
|
||||
queue.push({"srcFile": srcFile, "destFile": destFile, "type": type, "callback": callback, "fileExtension": fileExtension});
|
||||
queue.push({srcFile, destFile, type, callback, fileExtension});
|
||||
}
|
||||
};
|
||||
|
||||
function doConvertTask(task, callback) {
|
||||
var tmpDir = os.tmpdir();
|
||||
const tmpDir = os.tmpdir();
|
||||
|
||||
async.series([
|
||||
/*
|
||||
* use LibreOffice to convert task.srcFile to another format, given in
|
||||
* task.type
|
||||
*/
|
||||
function(callback) {
|
||||
function (callback) {
|
||||
libreOfficeLogger.debug(`Converting ${task.srcFile} to format ${task.type}. The result will be put in ${tmpDir}`);
|
||||
var soffice = spawn(settings.soffice, [
|
||||
const soffice = spawn(settings.soffice, [
|
||||
'--headless',
|
||||
'--invisible',
|
||||
'--nologo',
|
||||
'--nolockcheck',
|
||||
'--writer',
|
||||
'--convert-to', task.type,
|
||||
'--convert-to',
|
||||
task.type,
|
||||
task.srcFile,
|
||||
'--outdir', tmpDir
|
||||
'--outdir',
|
||||
tmpDir,
|
||||
]);
|
||||
// Soffice/libreoffice is buggy and often hangs.
|
||||
// To remedy this we kill the spawned process after a while.
|
||||
const hangTimeout = setTimeout(() => {
|
||||
soffice.stdin.pause(); // required to kill hanging threads
|
||||
soffice.kill();
|
||||
}, 120000);
|
||||
|
||||
var stdoutBuffer = '';
|
||||
}, 120000);
|
||||
|
||||
let stdoutBuffer = '';
|
||||
|
||||
// Delegate the processing of stdout to another function
|
||||
soffice.stdout.on('data', function(data) {
|
||||
soffice.stdout.on('data', (data) => {
|
||||
stdoutBuffer += data.toString();
|
||||
});
|
||||
|
||||
// Append error messages to the buffer
|
||||
soffice.stderr.on('data', function(data) {
|
||||
soffice.stderr.on('data', (data) => {
|
||||
stdoutBuffer += data.toString();
|
||||
});
|
||||
|
||||
soffice.on('exit', function(code) {
|
||||
soffice.on('exit', (code) => {
|
||||
clearTimeout(hangTimeout);
|
||||
if (code != 0) {
|
||||
// Throw an exception if libreoffice failed
|
||||
|
@ -117,18 +119,18 @@ function doConvertTask(task, callback) {
|
|||
|
||||
// if LibreOffice exited succesfully, go on with processing
|
||||
callback();
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
// Move the converted file to the correct place
|
||||
function(callback) {
|
||||
var filename = path.basename(task.srcFile);
|
||||
var sourceFilename = filename.substr(0, filename.lastIndexOf('.')) + '.' + task.fileExtension;
|
||||
var sourcePath = path.join(tmpDir, sourceFilename);
|
||||
function (callback) {
|
||||
const filename = path.basename(task.srcFile);
|
||||
const sourceFilename = `${filename.substr(0, filename.lastIndexOf('.'))}.${task.fileExtension}`;
|
||||
const sourcePath = path.join(tmpDir, sourceFilename);
|
||||
libreOfficeLogger.debug(`Renaming ${sourcePath} to ${task.destFile}`);
|
||||
fs.rename(sourcePath, task.destFile, callback);
|
||||
}
|
||||
], function(err) {
|
||||
},
|
||||
], (err) => {
|
||||
// Invoke the callback for the local queue
|
||||
callback();
|
||||
|
||||
|
|
|
@ -19,31 +19,29 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var ERR = require("async-stacktrace");
|
||||
var settings = require('./Settings');
|
||||
var async = require('async');
|
||||
var fs = require('fs');
|
||||
var StringDecoder = require('string_decoder').StringDecoder;
|
||||
var CleanCSS = require('clean-css');
|
||||
var path = require('path');
|
||||
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugin_defs");
|
||||
var RequireKernel = require('etherpad-require-kernel');
|
||||
var urlutil = require('url');
|
||||
var mime = require('mime-types')
|
||||
var Threads = require('threads')
|
||||
var log4js = require('log4js');
|
||||
const ERR = require('async-stacktrace');
|
||||
const settings = require('./Settings');
|
||||
const async = require('async');
|
||||
const fs = require('fs');
|
||||
const StringDecoder = require('string_decoder').StringDecoder;
|
||||
const CleanCSS = require('clean-css');
|
||||
const path = require('path');
|
||||
const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs');
|
||||
const RequireKernel = require('etherpad-require-kernel');
|
||||
const urlutil = require('url');
|
||||
const mime = require('mime-types');
|
||||
const Threads = require('threads');
|
||||
const log4js = require('log4js');
|
||||
|
||||
var logger = log4js.getLogger("Minify");
|
||||
const logger = log4js.getLogger('Minify');
|
||||
|
||||
var ROOT_DIR = path.normalize(__dirname + "/../../static/");
|
||||
var TAR_PATH = path.join(__dirname, 'tar.json');
|
||||
var tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8'));
|
||||
const ROOT_DIR = path.normalize(`${__dirname}/../../static/`);
|
||||
const TAR_PATH = path.join(__dirname, 'tar.json');
|
||||
const tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8'));
|
||||
|
||||
var threadsPool = Threads.Pool(function () {
|
||||
return Threads.spawn(new Threads.Worker("./MinifyWorker"))
|
||||
}, 2)
|
||||
const threadsPool = Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2);
|
||||
|
||||
var LIBRARY_WHITELIST = [
|
||||
const LIBRARY_WHITELIST = [
|
||||
'async',
|
||||
'js-cookie',
|
||||
'security',
|
||||
|
@ -53,71 +51,68 @@ var LIBRARY_WHITELIST = [
|
|||
];
|
||||
|
||||
// Rewrite tar to include modules with no extensions and proper rooted paths.
|
||||
var LIBRARY_PREFIX = 'ep_etherpad-lite/static/js';
|
||||
const LIBRARY_PREFIX = 'ep_etherpad-lite/static/js';
|
||||
exports.tar = {};
|
||||
function prefixLocalLibraryPath(path) {
|
||||
if (path.charAt(0) == '$') {
|
||||
return path.slice(1);
|
||||
} else {
|
||||
return LIBRARY_PREFIX + '/' + path;
|
||||
return `${LIBRARY_PREFIX}/${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
for (var key in tar) {
|
||||
for (const key in tar) {
|
||||
exports.tar[prefixLocalLibraryPath(key)] =
|
||||
tar[key].map(prefixLocalLibraryPath).concat(
|
||||
tar[key].map(prefixLocalLibraryPath).map(function (p) {
|
||||
return p.replace(/\.js$/, '');
|
||||
})
|
||||
tar[key].map(prefixLocalLibraryPath).map((p) => p.replace(/\.js$/, '')),
|
||||
).concat(
|
||||
tar[key].map(prefixLocalLibraryPath).map(function (p) {
|
||||
return p.replace(/\.js$/, '') + '/index.js';
|
||||
})
|
||||
tar[key].map(prefixLocalLibraryPath).map((p) => `${p.replace(/\.js$/, '')}/index.js`),
|
||||
);
|
||||
}
|
||||
|
||||
// What follows is a terrible hack to avoid loop-back within the server.
|
||||
// TODO: Serve files from another service, or directly from the file system.
|
||||
function requestURI(url, method, headers, callback) {
|
||||
var parsedURL = urlutil.parse(url);
|
||||
const parsedURL = urlutil.parse(url);
|
||||
|
||||
var status = 500, headers = {}, content = [];
|
||||
let status = 500; var headers = {}; const
|
||||
content = [];
|
||||
|
||||
var mockRequest = {
|
||||
url: url
|
||||
, method: method
|
||||
, params: {filename: parsedURL.path.replace(/^\/static\//, '')}
|
||||
, headers: headers
|
||||
const mockRequest = {
|
||||
url,
|
||||
method,
|
||||
params: {filename: parsedURL.path.replace(/^\/static\//, '')},
|
||||
headers,
|
||||
};
|
||||
var mockResponse = {
|
||||
writeHead: function (_status, _headers) {
|
||||
const mockResponse = {
|
||||
writeHead(_status, _headers) {
|
||||
status = _status;
|
||||
for (var header in _headers) {
|
||||
for (const header in _headers) {
|
||||
if (Object.prototype.hasOwnProperty.call(_headers, header)) {
|
||||
headers[header] = _headers[header];
|
||||
}
|
||||
}
|
||||
}
|
||||
, setHeader: function (header, value) {
|
||||
},
|
||||
setHeader(header, value) {
|
||||
headers[header.toLowerCase()] = value.toString();
|
||||
}
|
||||
, header: function (header, value) {
|
||||
},
|
||||
header(header, value) {
|
||||
headers[header.toLowerCase()] = value.toString();
|
||||
}
|
||||
, write: function (_content) {
|
||||
_content && content.push(_content);
|
||||
}
|
||||
, end: function (_content) {
|
||||
},
|
||||
write(_content) {
|
||||
_content && content.push(_content);
|
||||
},
|
||||
end(_content) {
|
||||
_content && content.push(_content);
|
||||
callback(status, headers, content.join(''));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
minify(mockRequest, mockResponse);
|
||||
}
|
||||
function requestURIs(locations, method, headers, callback) {
|
||||
var pendingRequests = locations.length;
|
||||
var responses = [];
|
||||
let pendingRequests = locations.length;
|
||||
const responses = [];
|
||||
|
||||
function respondFor(i) {
|
||||
return function (status, headers, content) {
|
||||
|
@ -128,14 +123,14 @@ function requestURIs(locations, method, headers, callback) {
|
|||
};
|
||||
}
|
||||
|
||||
for (var i = 0, ii = locations.length; i < ii; i++) {
|
||||
for (let i = 0, ii = locations.length; i < ii; i++) {
|
||||
requestURI(locations[i], method, headers, respondFor(i));
|
||||
}
|
||||
|
||||
function completed() {
|
||||
var statuss = responses.map(function (x) {return x[0];});
|
||||
var headerss = responses.map(function (x) {return x[1];});
|
||||
var contentss = responses.map(function (x) {return x[2];});
|
||||
const statuss = responses.map((x) => x[0]);
|
||||
const headerss = responses.map((x) => x[1]);
|
||||
const contentss = responses.map((x) => x[2]);
|
||||
callback(statuss, headerss, contentss);
|
||||
}
|
||||
}
|
||||
|
@ -146,15 +141,15 @@ function requestURIs(locations, method, headers, callback) {
|
|||
* @param res the Express response
|
||||
*/
|
||||
function minify(req, res) {
|
||||
var filename = req.params['filename'];
|
||||
let filename = req.params.filename;
|
||||
|
||||
// No relative paths, especially if they may go up the file hierarchy.
|
||||
filename = path.normalize(path.join(ROOT_DIR, filename));
|
||||
filename = filename.replace(/\.\./g, '')
|
||||
filename = filename.replace(/\.\./g, '');
|
||||
|
||||
if (filename.indexOf(ROOT_DIR) == 0) {
|
||||
filename = filename.slice(ROOT_DIR.length);
|
||||
filename = filename.replace(/\\/g, '/')
|
||||
filename = filename.replace(/\\/g, '/');
|
||||
} else {
|
||||
res.writeHead(404, {});
|
||||
res.end();
|
||||
|
@ -166,36 +161,36 @@ function minify(req, res) {
|
|||
are rewritten into ROOT_PATH_OF_MYPLUGIN/static/js/test.js,
|
||||
commonly ETHERPAD_ROOT/node_modules/ep_myplugin/static/js/test.js
|
||||
*/
|
||||
var match = filename.match(/^plugins\/([^\/]+)(\/(?:(static\/.*)|.*))?$/);
|
||||
const match = filename.match(/^plugins\/([^\/]+)(\/(?:(static\/.*)|.*))?$/);
|
||||
if (match) {
|
||||
var library = match[1];
|
||||
var libraryPath = match[2] || '';
|
||||
const library = match[1];
|
||||
const libraryPath = match[2] || '';
|
||||
|
||||
if (plugins.plugins[library] && match[3]) {
|
||||
var plugin = plugins.plugins[library];
|
||||
var pluginPath = plugin.package.realPath;
|
||||
const plugin = plugins.plugins[library];
|
||||
const pluginPath = plugin.package.realPath;
|
||||
filename = path.relative(ROOT_DIR, pluginPath + libraryPath);
|
||||
filename = filename.replace(/\\/g, '/'); // windows path fix
|
||||
} else if (LIBRARY_WHITELIST.indexOf(library) != -1) {
|
||||
// Go straight into node_modules
|
||||
// Avoid `require.resolve()`, since 'mustache' and 'mustache/index.js'
|
||||
// would end up resolving to logically distinct resources.
|
||||
filename = '../node_modules/' + library + libraryPath;
|
||||
filename = `../node_modules/${library}${libraryPath}`;
|
||||
}
|
||||
}
|
||||
|
||||
var contentType = mime.lookup(filename);
|
||||
const contentType = mime.lookup(filename);
|
||||
|
||||
statFile(filename, function (error, date, exists) {
|
||||
statFile(filename, (error, date, exists) => {
|
||||
if (date) {
|
||||
date = new Date(date);
|
||||
date.setMilliseconds(0);
|
||||
res.setHeader('last-modified', date.toUTCString());
|
||||
res.setHeader('date', (new Date()).toUTCString());
|
||||
if (settings.maxAge !== undefined) {
|
||||
var expiresDate = new Date(Date.now()+settings.maxAge*1000);
|
||||
const expiresDate = new Date(Date.now() + settings.maxAge * 1000);
|
||||
res.setHeader('expires', expiresDate.toUTCString());
|
||||
res.setHeader('cache-control', 'max-age=' + settings.maxAge);
|
||||
res.setHeader('cache-control', `max-age=${settings.maxAge}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,37 +203,35 @@ function minify(req, res) {
|
|||
} else if (new Date(req.headers['if-modified-since']) >= date) {
|
||||
res.writeHead(304, {});
|
||||
res.end();
|
||||
} else {
|
||||
if (req.method == 'HEAD') {
|
||||
res.header("Content-Type", contentType);
|
||||
res.writeHead(200, {});
|
||||
res.end();
|
||||
} else if (req.method == 'GET') {
|
||||
getFileCompressed(filename, contentType, function (error, content) {
|
||||
if(ERR(error, function(){
|
||||
res.writeHead(500, {});
|
||||
res.end();
|
||||
})) return;
|
||||
res.header("Content-Type", contentType);
|
||||
res.writeHead(200, {});
|
||||
res.write(content);
|
||||
} else if (req.method == 'HEAD') {
|
||||
res.header('Content-Type', contentType);
|
||||
res.writeHead(200, {});
|
||||
res.end();
|
||||
} else if (req.method == 'GET') {
|
||||
getFileCompressed(filename, contentType, (error, content) => {
|
||||
if (ERR(error, () => {
|
||||
res.writeHead(500, {});
|
||||
res.end();
|
||||
});
|
||||
} else {
|
||||
res.writeHead(405, {'allow': 'HEAD, GET'});
|
||||
})) return;
|
||||
res.header('Content-Type', contentType);
|
||||
res.writeHead(200, {});
|
||||
res.write(content);
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.writeHead(405, {allow: 'HEAD, GET'});
|
||||
res.end();
|
||||
}
|
||||
}, 3);
|
||||
}
|
||||
|
||||
// find all includes in ace.js and embed them.
|
||||
function getAceFile(callback) {
|
||||
fs.readFile(ROOT_DIR + 'js/ace.js', "utf8", function(err, data) {
|
||||
if(ERR(err, callback)) return;
|
||||
fs.readFile(`${ROOT_DIR}js/ace.js`, 'utf8', (err, data) => {
|
||||
if (ERR(err, callback)) return;
|
||||
|
||||
// Find all includes in ace.js and embed them
|
||||
var founds = data.match(/\$\$INCLUDE_[a-zA-Z_]+\("[^"]*"\)/gi);
|
||||
let founds = data.match(/\$\$INCLUDE_[a-zA-Z_]+\("[^"]*"\)/gi);
|
||||
if (!settings.minify) {
|
||||
founds = [];
|
||||
}
|
||||
|
@ -250,25 +243,25 @@ function getAceFile(callback) {
|
|||
|
||||
// Request the contents of the included file on the server-side and write
|
||||
// them into the file.
|
||||
async.forEach(founds, function (item, callback) {
|
||||
var filename = item.match(/"([^"]*)"/)[1];
|
||||
async.forEach(founds, (item, callback) => {
|
||||
const filename = item.match(/"([^"]*)"/)[1];
|
||||
|
||||
// Hostname "invalid.invalid" is a dummy value to allow parsing as a URI.
|
||||
var baseURI = 'http://invalid.invalid';
|
||||
var resourceURI = baseURI + path.normalize(path.join('/static/', filename));
|
||||
const baseURI = 'http://invalid.invalid';
|
||||
let resourceURI = baseURI + path.normalize(path.join('/static/', filename));
|
||||
resourceURI = resourceURI.replace(/\\/g, '/'); // Windows (safe generally?)
|
||||
|
||||
requestURI(resourceURI, 'GET', {}, function (status, headers, body) {
|
||||
var error = !(status == 200 || status == 404);
|
||||
requestURI(resourceURI, 'GET', {}, (status, headers, body) => {
|
||||
const error = !(status == 200 || status == 404);
|
||||
if (!error) {
|
||||
data += 'Ace2Editor.EMBEDED[' + JSON.stringify(filename) + '] = '
|
||||
+ JSON.stringify(status == 200 ? body || '' : null) + ';\n';
|
||||
data += `Ace2Editor.EMBEDED[${JSON.stringify(filename)}] = ${
|
||||
JSON.stringify(status == 200 ? body || '' : null)};\n`;
|
||||
} else {
|
||||
console.error(`getAceFile(): error getting ${resourceURI}. Status code: ${status}`);
|
||||
}
|
||||
callback();
|
||||
});
|
||||
}, function(error) {
|
||||
}, (error) => {
|
||||
callback(error, data);
|
||||
});
|
||||
});
|
||||
|
@ -289,19 +282,19 @@ function statFile(filename, callback, dirStatLimit) {
|
|||
} else if (filename == 'js/ace.js') {
|
||||
// Sometimes static assets are inlined into this file, so we have to stat
|
||||
// everything.
|
||||
lastModifiedDateOfEverything(function (error, date) {
|
||||
lastModifiedDateOfEverything((error, date) => {
|
||||
callback(error, date, !error);
|
||||
});
|
||||
} else if (filename == 'js/require-kernel.js') {
|
||||
callback(null, requireLastModified(), true);
|
||||
} else {
|
||||
fs.stat(ROOT_DIR + filename, function (error, stats) {
|
||||
fs.stat(ROOT_DIR + filename, (error, stats) => {
|
||||
if (error) {
|
||||
if (error.code == "ENOENT") {
|
||||
if (error.code == 'ENOENT') {
|
||||
// Stat the directory instead.
|
||||
statFile(path.dirname(filename), function (error, date, exists) {
|
||||
statFile(path.dirname(filename), (error, date, exists) => {
|
||||
callback(error, date, false);
|
||||
}, dirStatLimit-1);
|
||||
}, dirStatLimit - 1);
|
||||
} else {
|
||||
callback(error);
|
||||
}
|
||||
|
@ -314,29 +307,28 @@ function statFile(filename, callback, dirStatLimit) {
|
|||
}
|
||||
}
|
||||
function lastModifiedDateOfEverything(callback) {
|
||||
var folders2check = [ROOT_DIR + 'js/', ROOT_DIR + 'css/'];
|
||||
var latestModification = 0;
|
||||
//go trough this two folders
|
||||
async.forEach(folders2check, function(path, callback) {
|
||||
//read the files in the folder
|
||||
fs.readdir(path, function(err, files) {
|
||||
if(ERR(err, callback)) return;
|
||||
const folders2check = [`${ROOT_DIR}js/`, `${ROOT_DIR}css/`];
|
||||
let latestModification = 0;
|
||||
// go trough this two folders
|
||||
async.forEach(folders2check, (path, callback) => {
|
||||
// read the files in the folder
|
||||
fs.readdir(path, (err, files) => {
|
||||
if (ERR(err, callback)) return;
|
||||
|
||||
//we wanna check the directory itself for changes too
|
||||
files.push(".");
|
||||
// we wanna check the directory itself for changes too
|
||||
files.push('.');
|
||||
|
||||
//go trough all files in this folder
|
||||
async.forEach(files, function(filename, callback) {
|
||||
//get the stat data of this file
|
||||
fs.stat(path + "/" + filename, function(err, stats) {
|
||||
if(ERR(err, callback)) return;
|
||||
// go trough all files in this folder
|
||||
async.forEach(files, (filename, callback) => {
|
||||
// get the stat data of this file
|
||||
fs.stat(`${path}/${filename}`, (err, stats) => {
|
||||
if (ERR(err, callback)) return;
|
||||
|
||||
//get the modification time
|
||||
var modificationTime = stats.mtime.getTime();
|
||||
// get the modification time
|
||||
const modificationTime = stats.mtime.getTime();
|
||||
|
||||
//compare the modification time to the highest found
|
||||
if(modificationTime > latestModification)
|
||||
{
|
||||
// compare the modification time to the highest found
|
||||
if (modificationTime > latestModification) {
|
||||
latestModification = modificationTime;
|
||||
}
|
||||
|
||||
|
@ -344,29 +336,29 @@ function lastModifiedDateOfEverything(callback) {
|
|||
});
|
||||
}, callback);
|
||||
});
|
||||
}, function () {
|
||||
}, () => {
|
||||
callback(null, latestModification);
|
||||
});
|
||||
}
|
||||
|
||||
// This should be provided by the module, but until then, just use startup
|
||||
// time.
|
||||
var _requireLastModified = new Date();
|
||||
const _requireLastModified = new Date();
|
||||
function requireLastModified() {
|
||||
return _requireLastModified.toUTCString();
|
||||
}
|
||||
function requireDefinition() {
|
||||
return 'var require = ' + RequireKernel.kernelSource + ';\n';
|
||||
return `var require = ${RequireKernel.kernelSource};\n`;
|
||||
}
|
||||
|
||||
function getFileCompressed(filename, contentType, callback) {
|
||||
getFile(filename, function (error, content) {
|
||||
getFile(filename, (error, content) => {
|
||||
if (error || !content || !settings.minify) {
|
||||
callback(error, content);
|
||||
} else if (contentType == 'application/javascript') {
|
||||
threadsPool.queue(async ({ compressJS }) => {
|
||||
threadsPool.queue(async ({compressJS}) => {
|
||||
try {
|
||||
logger.info('Compress JS file %s.', filename)
|
||||
logger.info('Compress JS file %s.', filename);
|
||||
|
||||
content = content.toString();
|
||||
const compressResult = await compressJS(content);
|
||||
|
@ -381,11 +373,11 @@ function getFileCompressed(filename, contentType, callback) {
|
|||
}
|
||||
|
||||
callback(null, content);
|
||||
})
|
||||
});
|
||||
} else if (contentType == 'text/css') {
|
||||
threadsPool.queue(async ({ compressCSS }) => {
|
||||
threadsPool.queue(async ({compressCSS}) => {
|
||||
try {
|
||||
logger.info('Compress CSS file %s.', filename)
|
||||
logger.info('Compress CSS file %s.', filename);
|
||||
|
||||
content = await compressCSS(filename, ROOT_DIR);
|
||||
} catch (error) {
|
||||
|
@ -393,7 +385,7 @@ function getFileCompressed(filename, contentType, callback) {
|
|||
}
|
||||
|
||||
callback(null, content);
|
||||
})
|
||||
});
|
||||
} else {
|
||||
callback(null, content);
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
* Worker thread to minify JS & CSS files out of the main NodeJS thread
|
||||
*/
|
||||
|
||||
var CleanCSS = require('clean-css');
|
||||
var Terser = require("terser");
|
||||
var path = require('path');
|
||||
var Threads = require('threads')
|
||||
const CleanCSS = require('clean-css');
|
||||
const Terser = require('terser');
|
||||
const path = require('path');
|
||||
const Threads = require('threads');
|
||||
|
||||
function compressJS(content) {
|
||||
return Terser.minify(content);
|
||||
|
@ -46,20 +46,20 @@ function compressCSS(filename, ROOT_DIR) {
|
|||
new CleanCSS({
|
||||
rebase: true,
|
||||
rebaseTo: basePath,
|
||||
}).minify([absPath], function (errors, minified) {
|
||||
if (errors) return rej(errors)
|
||||
}).minify([absPath], (errors, minified) => {
|
||||
if (errors) return rej(errors);
|
||||
|
||||
return res(minified.styles)
|
||||
return res(minified.styles);
|
||||
});
|
||||
} catch (error) {
|
||||
// on error, just yield the un-minified original, but write a log message
|
||||
console.error(`Unexpected error minifying ${filename} (${absPath}): ${error}`);
|
||||
callback(null, content);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
Threads.expose({
|
||||
compressJS,
|
||||
compressCSS
|
||||
})
|
||||
compressCSS,
|
||||
});
|
||||
|
|
|
@ -25,17 +25,17 @@ const semver = require('semver');
|
|||
*
|
||||
* @param {String} minNodeVersion Minimum required Node version
|
||||
*/
|
||||
exports.enforceMinNodeVersion = function(minNodeVersion) {
|
||||
exports.enforceMinNodeVersion = function (minNodeVersion) {
|
||||
const currentNodeVersion = process.version;
|
||||
|
||||
// we cannot use template literals, since we still do not know if we are
|
||||
// running under Node >= 4.0
|
||||
if (semver.lt(currentNodeVersion, minNodeVersion)) {
|
||||
console.error('Running Etherpad on Node ' + currentNodeVersion + ' is not supported. Please upgrade at least to Node ' + minNodeVersion);
|
||||
console.error(`Running Etherpad on Node ${currentNodeVersion} is not supported. Please upgrade at least to Node ${minNodeVersion}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.debug('Running on Node ' + currentNodeVersion + ' (minimum required Node version: ' + minNodeVersion + ')');
|
||||
console.debug(`Running on Node ${currentNodeVersion} (minimum required Node version: ${minNodeVersion})`);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -44,7 +44,7 @@ exports.enforceMinNodeVersion = function(minNodeVersion) {
|
|||
* @param {String} lowestNonDeprecatedNodeVersion all Node version less than this one are deprecated
|
||||
* @param {Function} epRemovalVersion Etherpad version that will remove support for deprecated Node releases
|
||||
*/
|
||||
exports.checkDeprecationStatus = function(lowestNonDeprecatedNodeVersion, epRemovalVersion) {
|
||||
exports.checkDeprecationStatus = function (lowestNonDeprecatedNodeVersion, epRemovalVersion) {
|
||||
const currentNodeVersion = process.version;
|
||||
|
||||
if (semver.lt(currentNodeVersion, lowestNonDeprecatedNodeVersion)) {
|
||||
|
|
|
@ -26,17 +26,17 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var absolutePaths = require('./AbsolutePaths');
|
||||
var fs = require("fs");
|
||||
var os = require("os");
|
||||
var path = require('path');
|
||||
var argv = require('./Cli').argv;
|
||||
var npm = require("npm/lib/npm.js");
|
||||
var jsonminify = require("jsonminify");
|
||||
var log4js = require("log4js");
|
||||
var randomString = require("./randomstring");
|
||||
var suppressDisableMsg = " -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n";
|
||||
var _ = require("underscore");
|
||||
const absolutePaths = require('./AbsolutePaths');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const argv = require('./Cli').argv;
|
||||
const npm = require('npm/lib/npm.js');
|
||||
const jsonminify = require('jsonminify');
|
||||
const log4js = require('log4js');
|
||||
const randomString = require('./randomstring');
|
||||
const suppressDisableMsg = ' -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n';
|
||||
const _ = require('underscore');
|
||||
|
||||
/* Root path of the installation */
|
||||
exports.root = absolutePaths.findEtherpadRoot();
|
||||
|
@ -59,14 +59,14 @@ console.log(`Random string used for versioning assets: ${exports.randomVersionSt
|
|||
/**
|
||||
* The app title, visible e.g. in the browser window
|
||||
*/
|
||||
exports.title = "Etherpad";
|
||||
exports.title = 'Etherpad';
|
||||
|
||||
/**
|
||||
* The app favicon fully specified url, visible e.g. in the browser window
|
||||
*/
|
||||
exports.favicon = "favicon.ico";
|
||||
exports.faviconPad = "../" + exports.favicon;
|
||||
exports.faviconTimeslider = "../../" + exports.favicon;
|
||||
exports.favicon = 'favicon.ico';
|
||||
exports.faviconPad = `../${exports.favicon}`;
|
||||
exports.faviconTimeslider = `../../${exports.favicon}`;
|
||||
|
||||
/*
|
||||
* Skin name.
|
||||
|
@ -76,12 +76,12 @@ exports.faviconTimeslider = "../../" + exports.favicon;
|
|||
*/
|
||||
exports.skinName = null;
|
||||
|
||||
exports.skinVariants = "super-light-toolbar super-light-editor light-background";
|
||||
exports.skinVariants = 'super-light-toolbar super-light-editor light-background';
|
||||
|
||||
/**
|
||||
* The IP ep-lite should listen to
|
||||
*/
|
||||
exports.ip = "0.0.0.0";
|
||||
exports.ip = '0.0.0.0';
|
||||
|
||||
/**
|
||||
* The Port ep-lite should listen to
|
||||
|
@ -107,60 +107,60 @@ exports.socketTransportProtocols = ['xhr-polling', 'jsonp-polling', 'htmlfile'];
|
|||
/*
|
||||
* The Type of the database
|
||||
*/
|
||||
exports.dbType = "dirty";
|
||||
exports.dbType = 'dirty';
|
||||
/**
|
||||
* This setting is passed with dbType to ueberDB to set up the database
|
||||
*/
|
||||
exports.dbSettings = { "filename" : path.join(exports.root, "var/dirty.db") };
|
||||
exports.dbSettings = {filename: path.join(exports.root, 'var/dirty.db')};
|
||||
|
||||
/**
|
||||
* The default Text of a new pad
|
||||
*/
|
||||
exports.defaultPadText = "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad on Github: https:\/\/github.com\/ether\/etherpad-lite\n";
|
||||
exports.defaultPadText = 'Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad on Github: https:\/\/github.com\/ether\/etherpad-lite\n';
|
||||
|
||||
/**
|
||||
* The default Pad Settings for a user (Can be overridden by changing the setting
|
||||
*/
|
||||
exports.padOptions = {
|
||||
"noColors": false,
|
||||
"showControls": true,
|
||||
"showChat": true,
|
||||
"showLineNumbers": true,
|
||||
"useMonospaceFont": false,
|
||||
"userName": false,
|
||||
"userColor": false,
|
||||
"rtl": false,
|
||||
"alwaysShowChat": false,
|
||||
"chatAndUsers": false,
|
||||
"lang": "en-gb"
|
||||
noColors: false,
|
||||
showControls: true,
|
||||
showChat: true,
|
||||
showLineNumbers: true,
|
||||
useMonospaceFont: false,
|
||||
userName: false,
|
||||
userColor: false,
|
||||
rtl: false,
|
||||
alwaysShowChat: false,
|
||||
chatAndUsers: false,
|
||||
lang: 'en-gb',
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether certain shortcut keys are enabled for a user in the pad
|
||||
*/
|
||||
exports.padShortcutEnabled = {
|
||||
"altF9" : true,
|
||||
"altC" : true,
|
||||
"delete" : true,
|
||||
"cmdShift2" : true,
|
||||
"return" : true,
|
||||
"esc" : true,
|
||||
"cmdS" : true,
|
||||
"tab" : true,
|
||||
"cmdZ" : true,
|
||||
"cmdY" : true,
|
||||
"cmdB" : true,
|
||||
"cmdI" : true,
|
||||
"cmdU" : true,
|
||||
"cmd5" : true,
|
||||
"cmdShiftL" : true,
|
||||
"cmdShiftN" : true,
|
||||
"cmdShift1" : true,
|
||||
"cmdShiftC" : true,
|
||||
"cmdH" : true,
|
||||
"ctrlHome" : true,
|
||||
"pageUp" : true,
|
||||
"pageDown" : true,
|
||||
altF9: true,
|
||||
altC: true,
|
||||
delete: true,
|
||||
cmdShift2: true,
|
||||
return: true,
|
||||
esc: true,
|
||||
cmdS: true,
|
||||
tab: true,
|
||||
cmdZ: true,
|
||||
cmdY: true,
|
||||
cmdB: true,
|
||||
cmdI: true,
|
||||
cmdU: true,
|
||||
cmd5: true,
|
||||
cmdShiftL: true,
|
||||
cmdShiftN: true,
|
||||
cmdShift1: true,
|
||||
cmdShiftC: true,
|
||||
cmdH: true,
|
||||
ctrlHome: true,
|
||||
pageUp: true,
|
||||
pageDown: true,
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -168,20 +168,20 @@ exports.padShortcutEnabled = {
|
|||
*/
|
||||
exports.toolbar = {
|
||||
left: [
|
||||
["bold", "italic", "underline", "strikethrough"],
|
||||
["orderedlist", "unorderedlist", "indent", "outdent"],
|
||||
["undo", "redo"],
|
||||
["clearauthorship"]
|
||||
['bold', 'italic', 'underline', 'strikethrough'],
|
||||
['orderedlist', 'unorderedlist', 'indent', 'outdent'],
|
||||
['undo', 'redo'],
|
||||
['clearauthorship'],
|
||||
],
|
||||
right: [
|
||||
["importexport", "timeslider", "savedrevision"],
|
||||
["settings", "embed"],
|
||||
["showusers"]
|
||||
['importexport', 'timeslider', 'savedrevision'],
|
||||
['settings', 'embed'],
|
||||
['showusers'],
|
||||
],
|
||||
timeslider: [
|
||||
["timeslider_export", "timeslider_settings", "timeslider_returnToPad"]
|
||||
]
|
||||
}
|
||||
['timeslider_export', 'timeslider_settings', 'timeslider_returnToPad'],
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* A flag that requires any user to have a valid session (via the api) before accessing a pad
|
||||
|
@ -196,7 +196,7 @@ exports.editOnly = false;
|
|||
/**
|
||||
* Max age that responses will have (affects caching layer).
|
||||
*/
|
||||
exports.maxAge = 1000*60*60*6; // 6 hours
|
||||
exports.maxAge = 1000 * 60 * 60 * 6; // 6 hours
|
||||
|
||||
/**
|
||||
* A flag that shows if minification is enabled or not
|
||||
|
@ -226,7 +226,7 @@ exports.allowUnknownFileEnds = true;
|
|||
/**
|
||||
* The log level of log4js
|
||||
*/
|
||||
exports.loglevel = "INFO";
|
||||
exports.loglevel = 'INFO';
|
||||
|
||||
/**
|
||||
* Disable IP logging
|
||||
|
@ -251,7 +251,7 @@ exports.indentationOnNewLine = true;
|
|||
/*
|
||||
* log4js appender configuration
|
||||
*/
|
||||
exports.logconfig = { appenders: [{ type: "console" }]};
|
||||
exports.logconfig = {appenders: [{type: 'console'}]};
|
||||
|
||||
/*
|
||||
* Session Key, do not sure this.
|
||||
|
@ -303,28 +303,28 @@ exports.scrollWhenFocusLineIsOutOfViewport = {
|
|||
/*
|
||||
* Percentage of viewport height to be additionally scrolled.
|
||||
*/
|
||||
"percentage": {
|
||||
"editionAboveViewport": 0,
|
||||
"editionBelowViewport": 0
|
||||
percentage: {
|
||||
editionAboveViewport: 0,
|
||||
editionBelowViewport: 0,
|
||||
},
|
||||
|
||||
/*
|
||||
* Time (in milliseconds) used to animate the scroll transition. Set to 0 to
|
||||
* disable animation
|
||||
*/
|
||||
"duration": 0,
|
||||
duration: 0,
|
||||
|
||||
/*
|
||||
* Percentage of viewport height to be additionally scrolled when user presses arrow up
|
||||
* in the line of the top of the viewport.
|
||||
*/
|
||||
"percentageToScrollWhenUserPressesArrowUp": 0,
|
||||
percentageToScrollWhenUserPressesArrowUp: 0,
|
||||
|
||||
/*
|
||||
* Flag to control if it should scroll when user places the caret in the last
|
||||
* line of the viewport
|
||||
*/
|
||||
"scrollWhenCaretIsInTheLastLineOfViewport": false
|
||||
scrollWhenCaretIsInTheLastLineOfViewport: false,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -350,10 +350,10 @@ exports.customLocaleStrings = {};
|
|||
*/
|
||||
exports.importExportRateLimiting = {
|
||||
// duration of the rate limit window (milliseconds)
|
||||
"windowMs": 90000,
|
||||
windowMs: 90000,
|
||||
|
||||
// maximum number of requests per IP to allow during the rate limit window
|
||||
"max": 10
|
||||
max: 10,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -366,10 +366,10 @@ exports.importExportRateLimiting = {
|
|||
*/
|
||||
exports.commitRateLimiting = {
|
||||
// duration of the rate limit window (seconds)
|
||||
"duration": 1,
|
||||
duration: 1,
|
||||
|
||||
// maximum number of chanes per IP to allow during the rate limit window
|
||||
"points": 10
|
||||
points: 10,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -381,64 +381,64 @@ exports.commitRateLimiting = {
|
|||
exports.importMaxFileSize = 50 * 1024 * 1024;
|
||||
|
||||
// checks if abiword is avaiable
|
||||
exports.abiwordAvailable = function() {
|
||||
exports.abiwordAvailable = function () {
|
||||
if (exports.abiword != null) {
|
||||
return os.type().indexOf("Windows") != -1 ? "withoutPDF" : "yes";
|
||||
return os.type().indexOf('Windows') != -1 ? 'withoutPDF' : 'yes';
|
||||
} else {
|
||||
return "no";
|
||||
return 'no';
|
||||
}
|
||||
};
|
||||
|
||||
exports.sofficeAvailable = function() {
|
||||
exports.sofficeAvailable = function () {
|
||||
if (exports.soffice != null) {
|
||||
return os.type().indexOf("Windows") != -1 ? "withoutPDF": "yes";
|
||||
return os.type().indexOf('Windows') != -1 ? 'withoutPDF' : 'yes';
|
||||
} else {
|
||||
return "no";
|
||||
return 'no';
|
||||
}
|
||||
};
|
||||
|
||||
exports.exportAvailable = function() {
|
||||
var abiword = exports.abiwordAvailable();
|
||||
var soffice = exports.sofficeAvailable();
|
||||
exports.exportAvailable = function () {
|
||||
const abiword = exports.abiwordAvailable();
|
||||
const soffice = exports.sofficeAvailable();
|
||||
|
||||
if (abiword == "no" && soffice == "no") {
|
||||
return "no";
|
||||
} else if ((abiword == "withoutPDF" && soffice == "no") || (abiword == "no" && soffice == "withoutPDF")) {
|
||||
return "withoutPDF";
|
||||
if (abiword == 'no' && soffice == 'no') {
|
||||
return 'no';
|
||||
} else if ((abiword == 'withoutPDF' && soffice == 'no') || (abiword == 'no' && soffice == 'withoutPDF')) {
|
||||
return 'withoutPDF';
|
||||
} else {
|
||||
return "yes";
|
||||
return 'yes';
|
||||
}
|
||||
};
|
||||
|
||||
// Provide git version if available
|
||||
exports.getGitCommit = function() {
|
||||
var version = "";
|
||||
exports.getGitCommit = function () {
|
||||
let version = '';
|
||||
try {
|
||||
var rootPath = exports.root;
|
||||
if (fs.lstatSync(rootPath + '/.git').isFile()) {
|
||||
rootPath = fs.readFileSync(rootPath + '/.git', "utf8");
|
||||
let rootPath = exports.root;
|
||||
if (fs.lstatSync(`${rootPath}/.git`).isFile()) {
|
||||
rootPath = fs.readFileSync(`${rootPath}/.git`, 'utf8');
|
||||
rootPath = rootPath.split(' ').pop().trim();
|
||||
} else {
|
||||
rootPath += '/.git';
|
||||
}
|
||||
var ref = fs.readFileSync(rootPath + "/HEAD", "utf-8");
|
||||
if (ref.startsWith("ref: ")) {
|
||||
var refPath = rootPath + "/" + ref.substring(5, ref.indexOf("\n"));
|
||||
version = fs.readFileSync(refPath, "utf-8");
|
||||
const ref = fs.readFileSync(`${rootPath}/HEAD`, 'utf-8');
|
||||
if (ref.startsWith('ref: ')) {
|
||||
const refPath = `${rootPath}/${ref.substring(5, ref.indexOf('\n'))}`;
|
||||
version = fs.readFileSync(refPath, 'utf-8');
|
||||
} else {
|
||||
version = ref;
|
||||
}
|
||||
version = version.substring(0, 7);
|
||||
} catch(e) {
|
||||
console.warn("Can't get git version for server header\n" + e.message)
|
||||
} catch (e) {
|
||||
console.warn(`Can't get git version for server header\n${e.message}`);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
};
|
||||
|
||||
// Return etherpad version from package.json
|
||||
exports.getEpVersion = function() {
|
||||
exports.getEpVersion = function () {
|
||||
return require('ep_etherpad-lite/package.json').version;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Receives a settingsObj and, if the property name is a valid configuration
|
||||
|
@ -448,9 +448,9 @@ exports.getEpVersion = function() {
|
|||
* both "settings.json" and "credentials.json".
|
||||
*/
|
||||
function storeSettings(settingsObj) {
|
||||
for (var i in settingsObj) {
|
||||
for (const i in settingsObj) {
|
||||
// test if the setting starts with a lowercase character
|
||||
if (i.charAt(0).search("[a-z]") !== 0) {
|
||||
if (i.charAt(0).search('[a-z]') !== 0) {
|
||||
console.warn(`Settings should start with a lowercase character: '${i}'`);
|
||||
}
|
||||
|
||||
|
@ -482,26 +482,26 @@ function storeSettings(settingsObj) {
|
|||
* in the literal string "null", instead.
|
||||
*/
|
||||
function coerceValue(stringValue) {
|
||||
// cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number
|
||||
const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue));
|
||||
// cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number
|
||||
const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue));
|
||||
|
||||
if (isNumeric) {
|
||||
// detected numeric string. Coerce to a number
|
||||
if (isNumeric) {
|
||||
// detected numeric string. Coerce to a number
|
||||
|
||||
return +stringValue;
|
||||
}
|
||||
return +stringValue;
|
||||
}
|
||||
|
||||
// the boolean literal case is easy.
|
||||
if (stringValue === "true" ) {
|
||||
return true;
|
||||
}
|
||||
// the boolean literal case is easy.
|
||||
if (stringValue === 'true') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stringValue === "false") {
|
||||
return false;
|
||||
}
|
||||
if (stringValue === 'false') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise, return this value as-is
|
||||
return stringValue;
|
||||
// otherwise, return this value as-is
|
||||
return stringValue;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -624,24 +624,24 @@ function lookupEnvironmentVariables(obj) {
|
|||
* The isSettings variable only controls the error logging.
|
||||
*/
|
||||
function parseSettings(settingsFilename, isSettings) {
|
||||
let settingsStr = "";
|
||||
let settingsStr = '';
|
||||
|
||||
let settingsType, notFoundMessage, notFoundFunction;
|
||||
|
||||
if (isSettings) {
|
||||
settingsType = "settings";
|
||||
notFoundMessage = "Continuing using defaults!";
|
||||
settingsType = 'settings';
|
||||
notFoundMessage = 'Continuing using defaults!';
|
||||
notFoundFunction = console.warn;
|
||||
} else {
|
||||
settingsType = "credentials";
|
||||
notFoundMessage = "Ignoring.";
|
||||
settingsType = 'credentials';
|
||||
notFoundMessage = 'Ignoring.';
|
||||
notFoundFunction = console.info;
|
||||
}
|
||||
|
||||
try {
|
||||
//read the settings file
|
||||
// read the settings file
|
||||
settingsStr = fs.readFileSync(settingsFilename).toString();
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
notFoundFunction(`No ${settingsType} file found in ${settingsFilename}. ${notFoundMessage}`);
|
||||
|
||||
// or maybe undefined!
|
||||
|
@ -649,7 +649,7 @@ function parseSettings(settingsFilename, isSettings) {
|
|||
}
|
||||
|
||||
try {
|
||||
settingsStr = jsonminify(settingsStr).replace(",]","]").replace(",}","}");
|
||||
settingsStr = jsonminify(settingsStr).replace(',]', ']').replace(',}', '}');
|
||||
|
||||
const settings = JSON.parse(settingsStr);
|
||||
|
||||
|
@ -658,7 +658,7 @@ function parseSettings(settingsFilename, isSettings) {
|
|||
const replacedSettings = lookupEnvironmentVariables(settings);
|
||||
|
||||
return replacedSettings;
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
console.error(`There was an error processing your ${settingsType} file from ${settingsFilename}: ${e.message}`);
|
||||
|
||||
process.exit(1);
|
||||
|
@ -667,55 +667,55 @@ function parseSettings(settingsFilename, isSettings) {
|
|||
|
||||
exports.reloadSettings = function reloadSettings() {
|
||||
// Discover where the settings file lives
|
||||
var settingsFilename = absolutePaths.makeAbsolute(argv.settings || "settings.json");
|
||||
const settingsFilename = absolutePaths.makeAbsolute(argv.settings || 'settings.json');
|
||||
|
||||
// Discover if a credential file exists
|
||||
var credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || "credentials.json");
|
||||
const credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || 'credentials.json');
|
||||
|
||||
// try to parse the settings
|
||||
var settings = parseSettings(settingsFilename, true);
|
||||
const settings = parseSettings(settingsFilename, true);
|
||||
|
||||
// try to parse the credentials
|
||||
var credentials = parseSettings(credentialsFilename, false);
|
||||
const credentials = parseSettings(credentialsFilename, false);
|
||||
|
||||
storeSettings(settings);
|
||||
storeSettings(credentials);
|
||||
|
||||
log4js.configure(exports.logconfig);//Configure the logging appenders
|
||||
log4js.setGlobalLogLevel(exports.loglevel);//set loglevel
|
||||
process.env['DEBUG'] = 'socket.io:' + exports.loglevel; // Used by SocketIO for Debug
|
||||
log4js.configure(exports.logconfig);// Configure the logging appenders
|
||||
log4js.setGlobalLogLevel(exports.loglevel);// set loglevel
|
||||
process.env.DEBUG = `socket.io:${exports.loglevel}`; // Used by SocketIO for Debug
|
||||
log4js.replaceConsole();
|
||||
|
||||
if (!exports.skinName) {
|
||||
console.warn(`No "skinName" parameter found. Please check out settings.json.template and update your settings.json. Falling back to the default "colibris".`);
|
||||
exports.skinName = "colibris";
|
||||
console.warn('No "skinName" parameter found. Please check out settings.json.template and update your settings.json. Falling back to the default "colibris".');
|
||||
exports.skinName = 'colibris';
|
||||
}
|
||||
|
||||
// checks if skinName has an acceptable value, otherwise falls back to "colibris"
|
||||
if (exports.skinName) {
|
||||
const skinBasePath = path.join(exports.root, "src", "static", "skins");
|
||||
const skinBasePath = path.join(exports.root, 'src', 'static', 'skins');
|
||||
const countPieces = exports.skinName.split(path.sep).length;
|
||||
|
||||
if (countPieces != 1) {
|
||||
console.error(`skinName must be the name of a directory under "${skinBasePath}". This is not valid: "${exports.skinName}". Falling back to the default "colibris".`);
|
||||
|
||||
exports.skinName = "colibris";
|
||||
exports.skinName = 'colibris';
|
||||
}
|
||||
|
||||
// informative variable, just for the log messages
|
||||
var skinPath = path.normalize(path.join(skinBasePath, exports.skinName));
|
||||
let skinPath = path.normalize(path.join(skinBasePath, exports.skinName));
|
||||
|
||||
// what if someone sets skinName == ".." or "."? We catch him!
|
||||
if (absolutePaths.isSubdir(skinBasePath, skinPath) === false) {
|
||||
console.error(`Skin path ${skinPath} must be a subdirectory of ${skinBasePath}. Falling back to the default "colibris".`);
|
||||
|
||||
exports.skinName = "colibris";
|
||||
exports.skinName = 'colibris';
|
||||
skinPath = path.join(skinBasePath, exports.skinName);
|
||||
}
|
||||
|
||||
if (fs.existsSync(skinPath) === false) {
|
||||
console.error(`Skin path ${skinPath} does not exist. Falling back to the default "colibris".`);
|
||||
exports.skinName = "colibris";
|
||||
exports.skinName = 'colibris';
|
||||
skinPath = path.join(skinBasePath, exports.skinName);
|
||||
}
|
||||
|
||||
|
@ -725,13 +725,13 @@ exports.reloadSettings = function reloadSettings() {
|
|||
if (exports.abiword) {
|
||||
// Check abiword actually exists
|
||||
if (exports.abiword != null) {
|
||||
fs.exists(exports.abiword, function(exists) {
|
||||
fs.exists(exports.abiword, (exists) => {
|
||||
if (!exists) {
|
||||
var abiwordError = "Abiword does not exist at this path, check your settings file.";
|
||||
const abiwordError = 'Abiword does not exist at this path, check your settings file.';
|
||||
if (!exports.suppressErrorsInPadText) {
|
||||
exports.defaultPadText = exports.defaultPadText + "\nError: " + abiwordError + suppressDisableMsg;
|
||||
exports.defaultPadText = `${exports.defaultPadText}\nError: ${abiwordError}${suppressDisableMsg}`;
|
||||
}
|
||||
console.error(abiwordError + ` File location: ${exports.abiword}`);
|
||||
console.error(`${abiwordError} File location: ${exports.abiword}`);
|
||||
exports.abiword = null;
|
||||
}
|
||||
});
|
||||
|
@ -739,46 +739,46 @@ exports.reloadSettings = function reloadSettings() {
|
|||
}
|
||||
|
||||
if (exports.soffice) {
|
||||
fs.exists(exports.soffice, function(exists) {
|
||||
fs.exists(exports.soffice, (exists) => {
|
||||
if (!exists) {
|
||||
var sofficeError = "soffice (libreoffice) does not exist at this path, check your settings file.";
|
||||
const sofficeError = 'soffice (libreoffice) does not exist at this path, check your settings file.';
|
||||
|
||||
if (!exports.suppressErrorsInPadText) {
|
||||
exports.defaultPadText = exports.defaultPadText + "\nError: " + sofficeError + suppressDisableMsg;
|
||||
exports.defaultPadText = `${exports.defaultPadText}\nError: ${sofficeError}${suppressDisableMsg}`;
|
||||
}
|
||||
console.error(sofficeError + ` File location: ${exports.soffice}`);
|
||||
console.error(`${sofficeError} File location: ${exports.soffice}`);
|
||||
exports.soffice = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!exports.sessionKey) {
|
||||
var sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || "./SESSIONKEY.txt");
|
||||
const sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || './SESSIONKEY.txt');
|
||||
try {
|
||||
exports.sessionKey = fs.readFileSync(sessionkeyFilename,"utf8");
|
||||
exports.sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8');
|
||||
console.info(`Session key loaded from: ${sessionkeyFilename}`);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
console.info(`Session key file "${sessionkeyFilename}" not found. Creating with random contents.`);
|
||||
exports.sessionKey = randomString(32);
|
||||
fs.writeFileSync(sessionkeyFilename,exports.sessionKey,"utf8");
|
||||
fs.writeFileSync(sessionkeyFilename, exports.sessionKey, 'utf8');
|
||||
}
|
||||
} else {
|
||||
console.warn("Declaring the sessionKey in the settings.json is deprecated. This value is auto-generated now. Please remove the setting from the file. -- If you are seeing this error after restarting using the Admin User Interface then you can ignore this message.");
|
||||
console.warn('Declaring the sessionKey in the settings.json is deprecated. This value is auto-generated now. Please remove the setting from the file. -- If you are seeing this error after restarting using the Admin User Interface then you can ignore this message.');
|
||||
}
|
||||
|
||||
if (exports.dbType === "dirty") {
|
||||
var dirtyWarning = "DirtyDB is used. This is fine for testing but not recommended for production.";
|
||||
if (exports.dbType === 'dirty') {
|
||||
const dirtyWarning = 'DirtyDB is used. This is fine for testing but not recommended for production.';
|
||||
if (!exports.suppressErrorsInPadText) {
|
||||
exports.defaultPadText = exports.defaultPadText + "\nWarning: " + dirtyWarning + suppressDisableMsg;
|
||||
exports.defaultPadText = `${exports.defaultPadText}\nWarning: ${dirtyWarning}${suppressDisableMsg}`;
|
||||
}
|
||||
|
||||
exports.dbSettings.filename = absolutePaths.makeAbsolute(exports.dbSettings.filename);
|
||||
console.warn(dirtyWarning + ` File location: ${exports.dbSettings.filename}`);
|
||||
console.warn(`${dirtyWarning} File location: ${exports.dbSettings.filename}`);
|
||||
}
|
||||
|
||||
if (exports.ip === "") {
|
||||
if (exports.ip === '') {
|
||||
// using Unix socket for connectivity
|
||||
console.warn(`The settings file contains an empty string ("") for the "ip" parameter. The "port" parameter will be interpreted as the path to a Unix socket to bind at.`);
|
||||
console.warn('The settings file contains an empty string ("") for the "ip" parameter. The "port" parameter will be interpreted as the path to a Unix socket to bind at.');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -2,42 +2,41 @@
|
|||
* Tidy up the HTML in a given file
|
||||
*/
|
||||
|
||||
var log4js = require('log4js');
|
||||
var settings = require('./Settings');
|
||||
var spawn = require('child_process').spawn;
|
||||
const log4js = require('log4js');
|
||||
const settings = require('./Settings');
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
exports.tidy = function(srcFile) {
|
||||
var logger = log4js.getLogger('TidyHtml');
|
||||
exports.tidy = function (srcFile) {
|
||||
const logger = log4js.getLogger('TidyHtml');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// Don't do anything if Tidy hasn't been enabled
|
||||
if (!settings.tidyHtml) {
|
||||
logger.debug('tidyHtml has not been configured yet, ignoring tidy request');
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
var errMessage = '';
|
||||
let errMessage = '';
|
||||
|
||||
// Spawn a new tidy instance that cleans up the file inline
|
||||
logger.debug('Tidying ' + srcFile);
|
||||
var tidy = spawn(settings.tidyHtml, ['-modify', srcFile]);
|
||||
logger.debug(`Tidying ${srcFile}`);
|
||||
const tidy = spawn(settings.tidyHtml, ['-modify', srcFile]);
|
||||
|
||||
// Keep track of any error messages
|
||||
tidy.stderr.on('data', function (data) {
|
||||
tidy.stderr.on('data', (data) => {
|
||||
errMessage += data.toString();
|
||||
});
|
||||
|
||||
tidy.on('close', function(code) {
|
||||
tidy.on('close', (code) => {
|
||||
// Tidy returns a 0 when no errors occur and a 1 exit code when
|
||||
// the file could be tidied but a few warnings were generated
|
||||
if (code === 0 || code === 1) {
|
||||
logger.debug('Tidied ' + srcFile + ' successfully');
|
||||
logger.debug(`Tidied ${srcFile} successfully`);
|
||||
resolve(null);
|
||||
} else {
|
||||
logger.error('Failed to tidy ' + srcFile + '\n' + errMessage);
|
||||
reject('Tidy died with exit code ' + code);
|
||||
logger.error(`Failed to tidy ${srcFile}\n${errMessage}`);
|
||||
reject(`Tidy died with exit code ${code}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -5,8 +5,8 @@ const request = require('request');
|
|||
let infos;
|
||||
|
||||
function loadEtherpadInformations() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
request('https://static.etherpad.org/info.json', function (er, response, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request('https://static.etherpad.org/info.json', (er, response, body) => {
|
||||
if (er) return reject(er);
|
||||
|
||||
try {
|
||||
|
@ -16,29 +16,29 @@ function loadEtherpadInformations() {
|
|||
return reject(err);
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
exports.getLatestVersion = function() {
|
||||
exports.getLatestVersion = function () {
|
||||
exports.needsUpdate();
|
||||
return infos.latestVersion;
|
||||
}
|
||||
};
|
||||
|
||||
exports.needsUpdate = function(cb) {
|
||||
loadEtherpadInformations().then(function(info) {
|
||||
exports.needsUpdate = function (cb) {
|
||||
loadEtherpadInformations().then((info) => {
|
||||
if (semver.gt(info.latestVersion, settings.getEpVersion())) {
|
||||
if (cb) return cb(true);
|
||||
}
|
||||
}).catch(function (err) {
|
||||
console.error('Can not perform Etherpad update check: ' + err)
|
||||
}).catch((err) => {
|
||||
console.error(`Can not perform Etherpad update check: ${err}`);
|
||||
if (cb) return cb(false);
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.check = function() {
|
||||
exports.needsUpdate(function (needsUpdate) {
|
||||
exports.check = function () {
|
||||
exports.needsUpdate((needsUpdate) => {
|
||||
if (needsUpdate) {
|
||||
console.warn('Update available: Download the actual version ' + infos.latestVersion)
|
||||
console.warn(`Update available: Download the actual version ${infos.latestVersion}`);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -14,13 +14,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var async = require('async');
|
||||
var Buffer = require('buffer').Buffer;
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var zlib = require('zlib');
|
||||
var settings = require('./Settings');
|
||||
var existsSync = require('./path_exists');
|
||||
const async = require('async');
|
||||
const Buffer = require('buffer').Buffer;
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const zlib = require('zlib');
|
||||
const settings = require('./Settings');
|
||||
const existsSync = require('./path_exists');
|
||||
|
||||
/*
|
||||
* The crypto module can be absent on reduced node installations.
|
||||
|
@ -42,13 +42,13 @@ try {
|
|||
_crypto = undefined;
|
||||
}
|
||||
|
||||
var CACHE_DIR = path.normalize(path.join(settings.root, 'var/'));
|
||||
let CACHE_DIR = path.normalize(path.join(settings.root, 'var/'));
|
||||
CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
|
||||
|
||||
var responseCache = {};
|
||||
const responseCache = {};
|
||||
|
||||
function djb2Hash(data) {
|
||||
const chars = data.split("").map(str => str.charCodeAt(0));
|
||||
const chars = data.split('').map((str) => str.charCodeAt(0));
|
||||
return `${chars.reduce((prev, curr) => ((prev << 5) + prev) + curr, 5381)}`;
|
||||
}
|
||||
|
||||
|
@ -81,23 +81,23 @@ function CachingMiddleware() {
|
|||
}
|
||||
CachingMiddleware.prototype = new function () {
|
||||
function handle(req, res, next) {
|
||||
if (!(req.method == "GET" || req.method == "HEAD") || !CACHE_DIR) {
|
||||
if (!(req.method == 'GET' || req.method == 'HEAD') || !CACHE_DIR) {
|
||||
return next(undefined, req, res);
|
||||
}
|
||||
|
||||
var old_req = {};
|
||||
var old_res = {};
|
||||
const old_req = {};
|
||||
const old_res = {};
|
||||
|
||||
var supportsGzip =
|
||||
const supportsGzip =
|
||||
(req.get('Accept-Encoding') || '').indexOf('gzip') != -1;
|
||||
|
||||
var path = require('url').parse(req.url).path;
|
||||
var cacheKey = generateCacheKey(path);
|
||||
const path = require('url').parse(req.url).path;
|
||||
const cacheKey = generateCacheKey(path);
|
||||
|
||||
fs.stat(CACHE_DIR + 'minified_' + cacheKey, function (error, stats) {
|
||||
var modifiedSince = (req.headers['if-modified-since']
|
||||
&& new Date(req.headers['if-modified-since']));
|
||||
var lastModifiedCache = !error && stats.mtime;
|
||||
fs.stat(`${CACHE_DIR}minified_${cacheKey}`, (error, stats) => {
|
||||
const modifiedSince = (req.headers['if-modified-since'] &&
|
||||
new Date(req.headers['if-modified-since']));
|
||||
const lastModifiedCache = !error && stats.mtime;
|
||||
if (lastModifiedCache && responseCache[cacheKey]) {
|
||||
req.headers['if-modified-since'] = lastModifiedCache.toUTCString();
|
||||
} else {
|
||||
|
@ -108,13 +108,13 @@ CachingMiddleware.prototype = new function () {
|
|||
old_req.method = req.method;
|
||||
req.method = 'GET';
|
||||
|
||||
var expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {})['expires']);
|
||||
const expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {}).expires);
|
||||
if (expirationDate > new Date()) {
|
||||
// Our cached version is still valid.
|
||||
return respond();
|
||||
}
|
||||
|
||||
var _headers = {};
|
||||
const _headers = {};
|
||||
old_res.setHeader = res.setHeader;
|
||||
res.setHeader = function (key, value) {
|
||||
// Don't set cookies, see issue #707
|
||||
|
@ -126,46 +126,46 @@ CachingMiddleware.prototype = new function () {
|
|||
|
||||
old_res.writeHead = res.writeHead;
|
||||
res.writeHead = function (status, headers) {
|
||||
var lastModified = (res.getHeader('last-modified')
|
||||
&& new Date(res.getHeader('last-modified')));
|
||||
const lastModified = (res.getHeader('last-modified') &&
|
||||
new Date(res.getHeader('last-modified')));
|
||||
|
||||
res.writeHead = old_res.writeHead;
|
||||
if (status == 200) {
|
||||
// Update cache
|
||||
var buffer = '';
|
||||
let buffer = '';
|
||||
|
||||
Object.keys(headers || {}).forEach(function (key) {
|
||||
Object.keys(headers || {}).forEach((key) => {
|
||||
res.setHeader(key, headers[key]);
|
||||
});
|
||||
headers = _headers;
|
||||
|
||||
old_res.write = res.write;
|
||||
old_res.end = res.end;
|
||||
res.write = function(data, encoding) {
|
||||
res.write = function (data, encoding) {
|
||||
buffer += data.toString(encoding);
|
||||
};
|
||||
res.end = function(data, encoding) {
|
||||
res.end = function (data, encoding) {
|
||||
async.parallel([
|
||||
function (callback) {
|
||||
var path = CACHE_DIR + 'minified_' + cacheKey;
|
||||
fs.writeFile(path, buffer, function (error, stats) {
|
||||
const path = `${CACHE_DIR}minified_${cacheKey}`;
|
||||
fs.writeFile(path, buffer, (error, stats) => {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
, function (callback) {
|
||||
var path = CACHE_DIR + 'minified_' + cacheKey + '.gz';
|
||||
zlib.gzip(buffer, function(error, content) {
|
||||
},
|
||||
function (callback) {
|
||||
const path = `${CACHE_DIR}minified_${cacheKey}.gz`;
|
||||
zlib.gzip(buffer, (error, content) => {
|
||||
if (error) {
|
||||
callback();
|
||||
} else {
|
||||
fs.writeFile(path, content, function (error, stats) {
|
||||
fs.writeFile(path, content, (error, stats) => {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
], function () {
|
||||
responseCache[cacheKey] = {statusCode: status, headers: headers};
|
||||
},
|
||||
], () => {
|
||||
responseCache[cacheKey] = {statusCode: status, headers};
|
||||
respond();
|
||||
});
|
||||
};
|
||||
|
@ -173,8 +173,8 @@ CachingMiddleware.prototype = new function () {
|
|||
// Nothing new changed from the cached version.
|
||||
old_res.write = res.write;
|
||||
old_res.end = res.end;
|
||||
res.write = function(data, encoding) {};
|
||||
res.end = function(data, encoding) { respond(); };
|
||||
res.write = function (data, encoding) {};
|
||||
res.end = function (data, encoding) { respond(); };
|
||||
} else {
|
||||
res.writeHead(status, headers);
|
||||
}
|
||||
|
@ -191,24 +191,24 @@ CachingMiddleware.prototype = new function () {
|
|||
res.write = old_res.write || res.write;
|
||||
res.end = old_res.end || res.end;
|
||||
|
||||
let headers = {};
|
||||
const headers = {};
|
||||
Object.assign(headers, (responseCache[cacheKey].headers || {}));
|
||||
var statusCode = responseCache[cacheKey].statusCode;
|
||||
const statusCode = responseCache[cacheKey].statusCode;
|
||||
|
||||
var pathStr = CACHE_DIR + 'minified_' + cacheKey;
|
||||
let pathStr = `${CACHE_DIR}minified_${cacheKey}`;
|
||||
if (supportsGzip && /application\/javascript/.test(headers['content-type'])) {
|
||||
pathStr = pathStr + '.gz';
|
||||
pathStr += '.gz';
|
||||
headers['content-encoding'] = 'gzip';
|
||||
}
|
||||
|
||||
var lastModified = (headers['last-modified']
|
||||
&& new Date(headers['last-modified']));
|
||||
const lastModified = (headers['last-modified'] &&
|
||||
new Date(headers['last-modified']));
|
||||
|
||||
if (statusCode == 200 && lastModified <= modifiedSince) {
|
||||
res.writeHead(304, headers);
|
||||
res.end();
|
||||
} else if (req.method == 'GET') {
|
||||
var readStream = fs.createReadStream(pathStr);
|
||||
const readStream = fs.createReadStream(pathStr);
|
||||
res.writeHead(statusCode, headers);
|
||||
readStream.pipe(res);
|
||||
} else {
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
var Changeset = require("../../static/js/Changeset");
|
||||
var exportHtml = require('./ExportHtml');
|
||||
const Changeset = require('../../static/js/Changeset');
|
||||
const exportHtml = require('./ExportHtml');
|
||||
|
||||
function PadDiff (pad, fromRev, toRev) {
|
||||
function PadDiff(pad, fromRev, toRev) {
|
||||
// check parameters
|
||||
if (!pad || !pad.id || !pad.atext || !pad.pool) {
|
||||
throw new Error('Invalid pad');
|
||||
}
|
||||
|
||||
var range = pad.getValidRevisionRange(fromRev, toRev);
|
||||
const range = pad.getValidRevisionRange(fromRev, toRev);
|
||||
if (!range) {
|
||||
throw new Error('Invalid revision range.' +
|
||||
' startRev: ' + fromRev +
|
||||
' endRev: ' + toRev);
|
||||
throw new Error(`${'Invalid revision range.' +
|
||||
' startRev: '}${fromRev
|
||||
} endRev: ${toRev}`);
|
||||
}
|
||||
|
||||
this._pad = pad;
|
||||
|
@ -21,12 +21,12 @@ function PadDiff (pad, fromRev, toRev) {
|
|||
this._authors = [];
|
||||
}
|
||||
|
||||
PadDiff.prototype._isClearAuthorship = function(changeset) {
|
||||
PadDiff.prototype._isClearAuthorship = function (changeset) {
|
||||
// unpack
|
||||
var unpacked = Changeset.unpack(changeset);
|
||||
const unpacked = Changeset.unpack(changeset);
|
||||
|
||||
// check if there is nothing in the charBank
|
||||
if (unpacked.charBank !== "") {
|
||||
if (unpacked.charBank !== '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -36,10 +36,10 @@ PadDiff.prototype._isClearAuthorship = function(changeset) {
|
|||
}
|
||||
|
||||
// lets iterator over the operators
|
||||
var iterator = Changeset.opIterator(unpacked.ops);
|
||||
const iterator = Changeset.opIterator(unpacked.ops);
|
||||
|
||||
// get the first operator, this should be a clear operator
|
||||
var clearOperator = iterator.next();
|
||||
const clearOperator = iterator.next();
|
||||
|
||||
// check if there is only one operator
|
||||
if (iterator.hasNext() === true) {
|
||||
|
@ -47,18 +47,18 @@ PadDiff.prototype._isClearAuthorship = function(changeset) {
|
|||
}
|
||||
|
||||
// check if this operator doesn't change text
|
||||
if (clearOperator.opcode !== "=") {
|
||||
if (clearOperator.opcode !== '=') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check that this operator applys to the complete text
|
||||
// if the text ends with a new line, its exactly one character less, else it has the same length
|
||||
if (clearOperator.chars !== unpacked.oldLen-1 && clearOperator.chars !== unpacked.oldLen) {
|
||||
if (clearOperator.chars !== unpacked.oldLen - 1 && clearOperator.chars !== unpacked.oldLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var attributes = [];
|
||||
Changeset.eachAttribNumber(changeset, function(attrNum) {
|
||||
const attributes = [];
|
||||
Changeset.eachAttribNumber(changeset, (attrNum) => {
|
||||
attributes.push(attrNum);
|
||||
});
|
||||
|
||||
|
@ -67,90 +67,84 @@ PadDiff.prototype._isClearAuthorship = function(changeset) {
|
|||
return false;
|
||||
}
|
||||
|
||||
var appliedAttribute = this._pad.pool.getAttrib(attributes[0]);
|
||||
const appliedAttribute = this._pad.pool.getAttrib(attributes[0]);
|
||||
|
||||
// check if the applied attribute is an anonymous author attribute
|
||||
if (appliedAttribute[0] !== "author" || appliedAttribute[1] !== "") {
|
||||
if (appliedAttribute[0] !== 'author' || appliedAttribute[1] !== '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
PadDiff.prototype._createClearAuthorship = async function(rev) {
|
||||
|
||||
let atext = await this._pad.getInternalRevisionAText(rev);
|
||||
PadDiff.prototype._createClearAuthorship = async function (rev) {
|
||||
const atext = await this._pad.getInternalRevisionAText(rev);
|
||||
|
||||
// build clearAuthorship changeset
|
||||
var builder = Changeset.builder(atext.text.length);
|
||||
builder.keepText(atext.text, [['author','']], this._pad.pool);
|
||||
var changeset = builder.toString();
|
||||
const builder = Changeset.builder(atext.text.length);
|
||||
builder.keepText(atext.text, [['author', '']], this._pad.pool);
|
||||
const changeset = builder.toString();
|
||||
|
||||
return changeset;
|
||||
}
|
||||
|
||||
PadDiff.prototype._createClearStartAtext = async function(rev) {
|
||||
};
|
||||
|
||||
PadDiff.prototype._createClearStartAtext = async function (rev) {
|
||||
// get the atext of this revision
|
||||
let atext = this._pad.getInternalRevisionAText(rev);
|
||||
const atext = this._pad.getInternalRevisionAText(rev);
|
||||
|
||||
// create the clearAuthorship changeset
|
||||
let changeset = await this._createClearAuthorship(rev);
|
||||
const changeset = await this._createClearAuthorship(rev);
|
||||
|
||||
// apply the clearAuthorship changeset
|
||||
let newAText = Changeset.applyToAText(changeset, atext, this._pad.pool);
|
||||
const newAText = Changeset.applyToAText(changeset, atext, this._pad.pool);
|
||||
|
||||
return newAText;
|
||||
}
|
||||
|
||||
PadDiff.prototype._getChangesetsInBulk = async function(startRev, count) {
|
||||
};
|
||||
|
||||
PadDiff.prototype._getChangesetsInBulk = async function (startRev, count) {
|
||||
// find out which revisions we need
|
||||
let revisions = [];
|
||||
const revisions = [];
|
||||
for (let i = startRev; i < (startRev + count) && i <= this._pad.head; i++) {
|
||||
revisions.push(i);
|
||||
}
|
||||
|
||||
// get all needed revisions (in parallel)
|
||||
let changesets = [], authors = [];
|
||||
await Promise.all(revisions.map(rev => {
|
||||
return this._pad.getRevision(rev).then(revision => {
|
||||
let arrayNum = rev - startRev;
|
||||
changesets[arrayNum] = revision.changeset;
|
||||
authors[arrayNum] = revision.meta.author;
|
||||
});
|
||||
}));
|
||||
const changesets = []; const
|
||||
authors = [];
|
||||
await Promise.all(revisions.map((rev) => this._pad.getRevision(rev).then((revision) => {
|
||||
const arrayNum = rev - startRev;
|
||||
changesets[arrayNum] = revision.changeset;
|
||||
authors[arrayNum] = revision.meta.author;
|
||||
})));
|
||||
|
||||
return { changesets, authors };
|
||||
}
|
||||
return {changesets, authors};
|
||||
};
|
||||
|
||||
PadDiff.prototype._addAuthors = function(authors) {
|
||||
var self = this;
|
||||
PadDiff.prototype._addAuthors = function (authors) {
|
||||
const self = this;
|
||||
|
||||
// add to array if not in the array
|
||||
authors.forEach(function(author) {
|
||||
authors.forEach((author) => {
|
||||
if (self._authors.indexOf(author) == -1) {
|
||||
self._authors.push(author);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
PadDiff.prototype._createDiffAtext = async function() {
|
||||
|
||||
let bulkSize = 100;
|
||||
PadDiff.prototype._createDiffAtext = async function () {
|
||||
const bulkSize = 100;
|
||||
|
||||
// get the cleaned startAText
|
||||
let atext = await this._createClearStartAtext(this._fromRev);
|
||||
|
||||
let superChangeset = null;
|
||||
let rev = this._fromRev + 1;
|
||||
const rev = this._fromRev + 1;
|
||||
|
||||
for (let rev = this._fromRev + 1; rev <= this._toRev; rev += bulkSize) {
|
||||
|
||||
// get the bulk
|
||||
let { changesets, authors } = await this._getChangesetsInBulk(rev, bulkSize);
|
||||
const {changesets, authors} = await this._getChangesetsInBulk(rev, bulkSize);
|
||||
|
||||
let addedAuthors = [];
|
||||
const addedAuthors = [];
|
||||
|
||||
// run through all changesets
|
||||
for (let i = 0; i < changesets.length && (rev + i) <= this._toRev; ++i) {
|
||||
|
@ -180,7 +174,7 @@ PadDiff.prototype._createDiffAtext = async function() {
|
|||
|
||||
// if there are only clearAuthorship changesets, we don't get a superChangeset, so we can skip this step
|
||||
if (superChangeset) {
|
||||
let deletionChangeset = this._createDeletionChangeset(superChangeset, atext, this._pad.pool);
|
||||
const deletionChangeset = this._createDeletionChangeset(superChangeset, atext, this._pad.pool);
|
||||
|
||||
// apply the superChangeset, which includes all addings
|
||||
atext = Changeset.applyToAText(superChangeset, atext, this._pad.pool);
|
||||
|
@ -190,59 +184,57 @@ PadDiff.prototype._createDiffAtext = async function() {
|
|||
}
|
||||
|
||||
return atext;
|
||||
}
|
||||
|
||||
PadDiff.prototype.getHtml = async function() {
|
||||
};
|
||||
|
||||
PadDiff.prototype.getHtml = async function () {
|
||||
// cache the html
|
||||
if (this._html != null) {
|
||||
return this._html;
|
||||
}
|
||||
|
||||
// get the diff atext
|
||||
let atext = await this._createDiffAtext();
|
||||
const atext = await this._createDiffAtext();
|
||||
|
||||
// get the authorColor table
|
||||
let authorColors = await this._pad.getAllAuthorColors();
|
||||
const authorColors = await this._pad.getAllAuthorColors();
|
||||
|
||||
// convert the atext to html
|
||||
this._html = await exportHtml.getHTMLFromAtext(this._pad, atext, authorColors);
|
||||
|
||||
return this._html;
|
||||
}
|
||||
|
||||
PadDiff.prototype.getAuthors = async function() {
|
||||
};
|
||||
|
||||
PadDiff.prototype.getAuthors = async function () {
|
||||
// check if html was already produced, if not produce it, this generates the author array at the same time
|
||||
if (this._html == null) {
|
||||
await this.getHtml();
|
||||
}
|
||||
|
||||
return self._authors;
|
||||
}
|
||||
};
|
||||
|
||||
PadDiff.prototype._extendChangesetWithAuthor = function(changeset, author, apool) {
|
||||
PadDiff.prototype._extendChangesetWithAuthor = function (changeset, author, apool) {
|
||||
// unpack
|
||||
var unpacked = Changeset.unpack(changeset);
|
||||
const unpacked = Changeset.unpack(changeset);
|
||||
|
||||
var iterator = Changeset.opIterator(unpacked.ops);
|
||||
var assem = Changeset.opAssembler();
|
||||
const iterator = Changeset.opIterator(unpacked.ops);
|
||||
const assem = Changeset.opAssembler();
|
||||
|
||||
// create deleted attribs
|
||||
var authorAttrib = apool.putAttrib(["author", author || ""]);
|
||||
var deletedAttrib = apool.putAttrib(["removed", true]);
|
||||
var attribs = "*" + Changeset.numToString(authorAttrib) + "*" + Changeset.numToString(deletedAttrib);
|
||||
const authorAttrib = apool.putAttrib(['author', author || '']);
|
||||
const deletedAttrib = apool.putAttrib(['removed', true]);
|
||||
const attribs = `*${Changeset.numToString(authorAttrib)}*${Changeset.numToString(deletedAttrib)}`;
|
||||
|
||||
// iteratore over the operators of the changeset
|
||||
while(iterator.hasNext()) {
|
||||
var operator = iterator.next();
|
||||
while (iterator.hasNext()) {
|
||||
const operator = iterator.next();
|
||||
|
||||
if (operator.opcode === "-") {
|
||||
if (operator.opcode === '-') {
|
||||
// this is a delete operator, extend it with the author
|
||||
operator.attribs = attribs;
|
||||
} else if (operator.opcode === "=" && operator.attribs) {
|
||||
} else if (operator.opcode === '=' && operator.attribs) {
|
||||
// this is operator changes only attributes, let's mark which author did that
|
||||
operator.attribs+="*"+Changeset.numToString(authorAttrib);
|
||||
operator.attribs += `*${Changeset.numToString(authorAttrib)}`;
|
||||
}
|
||||
|
||||
// append the new operator to our assembler
|
||||
|
@ -254,9 +246,9 @@ PadDiff.prototype._extendChangesetWithAuthor = function(changeset, author, apool
|
|||
};
|
||||
|
||||
// this method is 80% like Changeset.inverse. I just changed so instead of reverting, it adds deletions and attribute changes to to the atext.
|
||||
PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
||||
var lines = Changeset.splitTextLines(startAText.text);
|
||||
var alines = Changeset.splitAttributionLines(startAText.attribs, startAText.text);
|
||||
PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
|
||||
const lines = Changeset.splitTextLines(startAText.text);
|
||||
const alines = Changeset.splitAttributionLines(startAText.attribs, startAText.text);
|
||||
|
||||
// lines and alines are what the exports is meant to apply to.
|
||||
// They may be arrays or objects with .get(i) and .length methods.
|
||||
|
@ -278,24 +270,23 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
|||
}
|
||||
}
|
||||
|
||||
var curLine = 0;
|
||||
var curChar = 0;
|
||||
var curLineOpIter = null;
|
||||
var curLineOpIterLine;
|
||||
var curLineNextOp = Changeset.newOp('+');
|
||||
let curLine = 0;
|
||||
let curChar = 0;
|
||||
let curLineOpIter = null;
|
||||
let curLineOpIterLine;
|
||||
const curLineNextOp = Changeset.newOp('+');
|
||||
|
||||
var unpacked = Changeset.unpack(cs);
|
||||
var csIter = Changeset.opIterator(unpacked.ops);
|
||||
var builder = Changeset.builder(unpacked.newLen);
|
||||
|
||||
function consumeAttribRuns(numChars, func /*(len, attribs, endsLine)*/ ) {
|
||||
const unpacked = Changeset.unpack(cs);
|
||||
const csIter = Changeset.opIterator(unpacked.ops);
|
||||
const builder = Changeset.builder(unpacked.newLen);
|
||||
|
||||
function consumeAttribRuns(numChars, func /* (len, attribs, endsLine)*/) {
|
||||
if ((!curLineOpIter) || (curLineOpIterLine != curLine)) {
|
||||
// create curLineOpIter and advance it to curChar
|
||||
curLineOpIter = Changeset.opIterator(alines_get(curLine));
|
||||
curLineOpIterLine = curLine;
|
||||
var indexIntoLine = 0;
|
||||
var done = false;
|
||||
let indexIntoLine = 0;
|
||||
let done = false;
|
||||
while (!done) {
|
||||
curLineOpIter.next(curLineNextOp);
|
||||
if (indexIntoLine + curLineNextOp.chars >= curChar) {
|
||||
|
@ -320,7 +311,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
|||
curLineOpIter.next(curLineNextOp);
|
||||
}
|
||||
|
||||
var charsToUse = Math.min(numChars, curLineNextOp.chars);
|
||||
const charsToUse = Math.min(numChars, curLineNextOp.chars);
|
||||
|
||||
func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0);
|
||||
numChars -= charsToUse;
|
||||
|
@ -338,26 +329,24 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
|||
if (L) {
|
||||
curLine += L;
|
||||
curChar = 0;
|
||||
} else if (curLineOpIter && curLineOpIterLine == curLine) {
|
||||
consumeAttribRuns(N, () => {});
|
||||
} else {
|
||||
if (curLineOpIter && curLineOpIterLine == curLine) {
|
||||
consumeAttribRuns(N, function () {});
|
||||
} else {
|
||||
curChar += N;
|
||||
}
|
||||
curChar += N;
|
||||
}
|
||||
}
|
||||
|
||||
function nextText(numChars) {
|
||||
var len = 0;
|
||||
var assem = Changeset.stringAssembler();
|
||||
var firstString = lines_get(curLine).substring(curChar);
|
||||
let len = 0;
|
||||
const assem = Changeset.stringAssembler();
|
||||
const firstString = lines_get(curLine).substring(curChar);
|
||||
len += firstString.length;
|
||||
assem.append(firstString);
|
||||
|
||||
var lineNum = curLine + 1;
|
||||
let lineNum = curLine + 1;
|
||||
|
||||
while (len < numChars) {
|
||||
var nextString = lines_get(lineNum);
|
||||
const nextString = lines_get(lineNum);
|
||||
len += nextString.length;
|
||||
assem.append(nextString);
|
||||
lineNum++;
|
||||
|
@ -367,7 +356,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
|||
}
|
||||
|
||||
function cachedStrFunc(func) {
|
||||
var cache = {};
|
||||
const cache = {};
|
||||
|
||||
return function (s) {
|
||||
if (!cache[s]) {
|
||||
|
@ -377,8 +366,8 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
|||
};
|
||||
}
|
||||
|
||||
var attribKeys = [];
|
||||
var attribValues = [];
|
||||
const attribKeys = [];
|
||||
const attribValues = [];
|
||||
|
||||
// iterate over all operators of this changeset
|
||||
while (csIter.hasNext()) {
|
||||
|
@ -389,27 +378,27 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
|||
|
||||
// decide if this equal operator is an attribution change or not. We can see this by checkinf if attribs is set.
|
||||
// If the text this operator applies to is only a star, than this is a false positive and should be ignored
|
||||
if (csOp.attribs && textBank != "*") {
|
||||
var deletedAttrib = apool.putAttrib(["removed", true]);
|
||||
var authorAttrib = apool.putAttrib(["author", ""]);
|
||||
if (csOp.attribs && textBank != '*') {
|
||||
const deletedAttrib = apool.putAttrib(['removed', true]);
|
||||
var authorAttrib = apool.putAttrib(['author', '']);
|
||||
|
||||
attribKeys.length = 0;
|
||||
attribValues.length = 0;
|
||||
Changeset.eachAttribNumber(csOp.attribs, function (n) {
|
||||
Changeset.eachAttribNumber(csOp.attribs, (n) => {
|
||||
attribKeys.push(apool.getAttribKey(n));
|
||||
attribValues.push(apool.getAttribValue(n));
|
||||
|
||||
if (apool.getAttribKey(n) === "author") {
|
||||
if (apool.getAttribKey(n) === 'author') {
|
||||
authorAttrib = n;
|
||||
}
|
||||
});
|
||||
|
||||
var undoBackToAttribs = cachedStrFunc(function (attribs) {
|
||||
var backAttribs = [];
|
||||
for (var i = 0; i < attribKeys.length; i++) {
|
||||
var appliedKey = attribKeys[i];
|
||||
var appliedValue = attribValues[i];
|
||||
var oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, apool);
|
||||
var undoBackToAttribs = cachedStrFunc((attribs) => {
|
||||
const backAttribs = [];
|
||||
for (let i = 0; i < attribKeys.length; i++) {
|
||||
const appliedKey = attribKeys[i];
|
||||
const appliedValue = attribValues[i];
|
||||
const oldValue = Changeset.attribsAttributeValue(attribs, appliedKey, apool);
|
||||
|
||||
if (appliedValue != oldValue) {
|
||||
backAttribs.push([appliedKey, oldValue]);
|
||||
|
@ -419,21 +408,21 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
|||
return Changeset.makeAttribsString('=', backAttribs, apool);
|
||||
});
|
||||
|
||||
var oldAttribsAddition = "*" + Changeset.numToString(deletedAttrib) + "*" + Changeset.numToString(authorAttrib);
|
||||
var oldAttribsAddition = `*${Changeset.numToString(deletedAttrib)}*${Changeset.numToString(authorAttrib)}`;
|
||||
|
||||
var textLeftToProcess = textBank;
|
||||
let textLeftToProcess = textBank;
|
||||
|
||||
while(textLeftToProcess.length > 0) {
|
||||
while (textLeftToProcess.length > 0) {
|
||||
// process till the next line break or process only one line break
|
||||
var lengthToProcess = textLeftToProcess.indexOf("\n");
|
||||
var lineBreak = false;
|
||||
switch(lengthToProcess) {
|
||||
let lengthToProcess = textLeftToProcess.indexOf('\n');
|
||||
let lineBreak = false;
|
||||
switch (lengthToProcess) {
|
||||
case -1:
|
||||
lengthToProcess=textLeftToProcess.length;
|
||||
lengthToProcess = textLeftToProcess.length;
|
||||
break;
|
||||
case 0:
|
||||
lineBreak = true;
|
||||
lengthToProcess=1;
|
||||
lengthToProcess = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -446,13 +435,13 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
|||
builder.keep(1, 1); // just skip linebreaks, don't do a insert + keep for a linebreak
|
||||
|
||||
// consume the attributes of this linebreak
|
||||
consumeAttribRuns(1, function() {});
|
||||
consumeAttribRuns(1, () => {});
|
||||
} else {
|
||||
// add the old text via an insert, but add a deletion attribute + the author attribute of the author who deleted it
|
||||
var textBankIndex = 0;
|
||||
consumeAttribRuns(lengthToProcess, function (len, attribs, endsLine) {
|
||||
consumeAttribRuns(lengthToProcess, (len, attribs, endsLine) => {
|
||||
// get the old attributes back
|
||||
var attribs = (undoBackToAttribs(attribs) || "") + oldAttribsAddition;
|
||||
var attribs = (undoBackToAttribs(attribs) || '') + oldAttribsAddition;
|
||||
|
||||
builder.insert(processText.substr(textBankIndex, len), attribs);
|
||||
textBankIndex += len;
|
||||
|
@ -471,7 +460,7 @@ PadDiff.prototype._createDeletionChangeset = function(cs, startAText, apool) {
|
|||
var textBank = nextText(csOp.chars);
|
||||
var textBankIndex = 0;
|
||||
|
||||
consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) {
|
||||
consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => {
|
||||
builder.insert(textBank.substr(textBankIndex, len), attribs + csOp.attribs);
|
||||
textBankIndex += len;
|
||||
});
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
var fs = require('fs');
|
||||
const fs = require('fs');
|
||||
|
||||
var check = function(path) {
|
||||
var existsSync = fs.statSync || fs.existsSync || path.existsSync;
|
||||
const check = function (path) {
|
||||
const existsSync = fs.statSync || fs.existsSync || path.existsSync;
|
||||
|
||||
var result;
|
||||
let result;
|
||||
try {
|
||||
result = existsSync(path);
|
||||
} catch (e) {
|
||||
result = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = check;
|
||||
|
|
|
@ -13,7 +13,7 @@ exports.firstSatisfies = (promises, predicate) => {
|
|||
// value does not satisfy `predicate`. These transformed Promises will be passed to Promise.race,
|
||||
// yielding the first resolved value that satisfies `predicate`.
|
||||
const newPromises = promises.map(
|
||||
(p) => new Promise((resolve, reject) => p.then((v) => predicate(v) && resolve(v), reject)));
|
||||
(p) => new Promise((resolve, reject) => p.then((v) => predicate(v) && resolve(v), reject)));
|
||||
|
||||
// If `promises` is an empty array or if none of them resolve to a value that satisfies
|
||||
// `predicate`, then `Promise.race(newPromises)` will never resolve. To handle that, add another
|
||||
|
@ -48,8 +48,8 @@ exports.timesLimit = async (total, concurrency, promiseCreator) => {
|
|||
if (next < total) return addAnother();
|
||||
});
|
||||
const promises = [];
|
||||
for (var i = 0; i < concurrency && i < total; i++) {
|
||||
for (let i = 0; i < concurrency && i < total; i++) {
|
||||
promises.push(addAnother());
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/**
|
||||
* Generates a random String with the given length. Is needed to generate the Author, Group, readonly, session Ids
|
||||
*/
|
||||
var crypto = require('crypto');
|
||||
const crypto = require('crypto');
|
||||
|
||||
var randomString = function(len) {
|
||||
return crypto.randomBytes(len).toString('hex')
|
||||
const randomString = function (len) {
|
||||
return crypto.randomBytes(len).toString('hex');
|
||||
};
|
||||
|
||||
module.exports = randomString;
|
||||
|
|
|
@ -1,53 +1,50 @@
|
|||
/**
|
||||
* The Toolbar Module creates and renders the toolbars and buttons
|
||||
*/
|
||||
var _ = require("underscore")
|
||||
, tagAttributes
|
||||
, tag
|
||||
, Button
|
||||
, ButtonsGroup
|
||||
, Separator
|
||||
, defaultButtonAttributes
|
||||
, removeItem;
|
||||
const _ = require('underscore');
|
||||
let tagAttributes;
|
||||
let tag;
|
||||
let Button;
|
||||
let ButtonsGroup;
|
||||
let Separator;
|
||||
let defaultButtonAttributes;
|
||||
let removeItem;
|
||||
|
||||
removeItem = function(array,what) {
|
||||
var ax;
|
||||
removeItem = function (array, what) {
|
||||
let ax;
|
||||
while ((ax = array.indexOf(what)) !== -1) {
|
||||
array.splice(ax, 1);
|
||||
}
|
||||
return array;
|
||||
return array;
|
||||
};
|
||||
|
||||
defaultButtonAttributes = function (name, overrides) {
|
||||
return {
|
||||
command: name,
|
||||
localizationId: "pad.toolbar." + name + ".title",
|
||||
class: "buttonicon buttonicon-" + name
|
||||
localizationId: `pad.toolbar.${name}.title`,
|
||||
class: `buttonicon buttonicon-${name}`,
|
||||
};
|
||||
};
|
||||
|
||||
tag = function (name, attributes, contents) {
|
||||
var aStr = tagAttributes(attributes);
|
||||
const aStr = tagAttributes(attributes);
|
||||
|
||||
if (_.isString(contents) && contents.length > 0) {
|
||||
return '<' + name + aStr + '>' + contents + '</' + name + '>';
|
||||
}
|
||||
else {
|
||||
return '<' + name + aStr + '></' + name + '>';
|
||||
return `<${name}${aStr}>${contents}</${name}>`;
|
||||
} else {
|
||||
return `<${name}${aStr}></${name}>`;
|
||||
}
|
||||
};
|
||||
|
||||
tagAttributes = function (attributes) {
|
||||
attributes = _.reduce(attributes || {}, function (o, val, name) {
|
||||
attributes = _.reduce(attributes || {}, (o, val, name) => {
|
||||
if (!_.isUndefined(val)) {
|
||||
o[name] = val;
|
||||
}
|
||||
return o;
|
||||
}, {});
|
||||
|
||||
return " " + _.map(attributes, function (val, name) {
|
||||
return "" + name + '="' + _.escape(val) + '"';
|
||||
}).join(" ");
|
||||
return ` ${_.map(attributes, (val, name) => `${name}="${_.escape(val)}"`).join(' ')}`;
|
||||
};
|
||||
|
||||
ButtonsGroup = function () {
|
||||
|
@ -55,8 +52,8 @@ ButtonsGroup = function () {
|
|||
};
|
||||
|
||||
ButtonsGroup.fromArray = function (array) {
|
||||
var btnGroup = new this;
|
||||
_.each(array, function (btnName) {
|
||||
const btnGroup = new this();
|
||||
_.each(array, (btnName) => {
|
||||
btnGroup.addButton(Button.load(btnName));
|
||||
});
|
||||
return btnGroup;
|
||||
|
@ -69,19 +66,18 @@ ButtonsGroup.prototype.addButton = function (button) {
|
|||
|
||||
ButtonsGroup.prototype.render = function () {
|
||||
if (this.buttons && this.buttons.length == 1) {
|
||||
this.buttons[0].grouping = "";
|
||||
}
|
||||
else {
|
||||
_.first(this.buttons).grouping = "grouped-left";
|
||||
_.last(this.buttons).grouping = "grouped-right";
|
||||
_.each(this.buttons.slice(1, -1), function (btn) {
|
||||
btn.grouping = "grouped-middle"
|
||||
this.buttons[0].grouping = '';
|
||||
} else {
|
||||
_.first(this.buttons).grouping = 'grouped-left';
|
||||
_.last(this.buttons).grouping = 'grouped-right';
|
||||
_.each(this.buttons.slice(1, -1), (btn) => {
|
||||
btn.grouping = 'grouped-middle';
|
||||
});
|
||||
}
|
||||
|
||||
return _.map(this.buttons, function (btn) {
|
||||
if(btn) return btn.render();
|
||||
}).join("\n");
|
||||
return _.map(this.buttons, (btn) => {
|
||||
if (btn) return btn.render();
|
||||
}).join('\n');
|
||||
};
|
||||
|
||||
Button = function (attributes) {
|
||||
|
@ -89,165 +85,163 @@ Button = function (attributes) {
|
|||
};
|
||||
|
||||
Button.load = function (btnName) {
|
||||
var button = module.exports.availableButtons[btnName];
|
||||
try{
|
||||
const button = module.exports.availableButtons[btnName];
|
||||
try {
|
||||
if (button.constructor === Button || button.constructor === SelectButton) {
|
||||
return button;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return new Button(button);
|
||||
}
|
||||
}catch(e){
|
||||
console.warn("Error loading button", btnName);
|
||||
} catch (e) {
|
||||
console.warn('Error loading button', btnName);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
_.extend(Button.prototype, {
|
||||
grouping: "",
|
||||
grouping: '',
|
||||
|
||||
render: function () {
|
||||
var liAttributes = {
|
||||
"data-type": "button",
|
||||
"data-key": this.attributes.command,
|
||||
render() {
|
||||
const liAttributes = {
|
||||
'data-type': 'button',
|
||||
'data-key': this.attributes.command,
|
||||
};
|
||||
return tag("li", liAttributes,
|
||||
tag("a", { "class": this.grouping, "data-l10n-id": this.attributes.localizationId },
|
||||
tag("button", { "class": " "+ this.attributes.class, "data-l10n-id": this.attributes.localizationId })
|
||||
)
|
||||
return tag('li', liAttributes,
|
||||
tag('a', {'class': this.grouping, 'data-l10n-id': this.attributes.localizationId},
|
||||
tag('button', {'class': ` ${this.attributes.class}`, 'data-l10n-id': this.attributes.localizationId}),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
var SelectButton = function (attributes) {
|
||||
this.attributes = attributes;
|
||||
this.options = [];
|
||||
};
|
||||
|
||||
_.extend(SelectButton.prototype, Button.prototype, {
|
||||
addOption: function (value, text, attributes) {
|
||||
addOption(value, text, attributes) {
|
||||
this.options.push({
|
||||
value: value,
|
||||
text: text,
|
||||
attributes: attributes
|
||||
value,
|
||||
text,
|
||||
attributes,
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
select: function (attributes) {
|
||||
var options = [];
|
||||
select(attributes) {
|
||||
const options = [];
|
||||
|
||||
_.each(this.options, function (opt) {
|
||||
var a = _.extend({
|
||||
value: opt.value
|
||||
_.each(this.options, (opt) => {
|
||||
const a = _.extend({
|
||||
value: opt.value,
|
||||
}, opt.attributes);
|
||||
|
||||
options.push( tag("option", a, opt.text) );
|
||||
options.push(tag('option', a, opt.text));
|
||||
});
|
||||
return tag("select", attributes, options.join(""));
|
||||
return tag('select', attributes, options.join(''));
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var attributes = {
|
||||
id: this.attributes.id,
|
||||
"data-key": this.attributes.command,
|
||||
"data-type": "select"
|
||||
render() {
|
||||
const attributes = {
|
||||
'id': this.attributes.id,
|
||||
'data-key': this.attributes.command,
|
||||
'data-type': 'select',
|
||||
};
|
||||
return tag("li", attributes,
|
||||
this.select({ id: this.attributes.selectId })
|
||||
return tag('li', attributes,
|
||||
this.select({id: this.attributes.selectId}),
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Separator = function () {};
|
||||
Separator.prototype.render = function () {
|
||||
return tag("li", { "class": "separator" });
|
||||
return tag('li', {class: 'separator'});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
availableButtons: {
|
||||
bold: defaultButtonAttributes("bold"),
|
||||
italic: defaultButtonAttributes("italic"),
|
||||
underline: defaultButtonAttributes("underline"),
|
||||
strikethrough: defaultButtonAttributes("strikethrough"),
|
||||
bold: defaultButtonAttributes('bold'),
|
||||
italic: defaultButtonAttributes('italic'),
|
||||
underline: defaultButtonAttributes('underline'),
|
||||
strikethrough: defaultButtonAttributes('strikethrough'),
|
||||
|
||||
orderedlist: {
|
||||
command: "insertorderedlist",
|
||||
localizationId: "pad.toolbar.ol.title",
|
||||
class: "buttonicon buttonicon-insertorderedlist"
|
||||
command: 'insertorderedlist',
|
||||
localizationId: 'pad.toolbar.ol.title',
|
||||
class: 'buttonicon buttonicon-insertorderedlist',
|
||||
},
|
||||
|
||||
unorderedlist: {
|
||||
command: "insertunorderedlist",
|
||||
localizationId: "pad.toolbar.ul.title",
|
||||
class: "buttonicon buttonicon-insertunorderedlist"
|
||||
command: 'insertunorderedlist',
|
||||
localizationId: 'pad.toolbar.ul.title',
|
||||
class: 'buttonicon buttonicon-insertunorderedlist',
|
||||
},
|
||||
|
||||
indent: defaultButtonAttributes("indent"),
|
||||
indent: defaultButtonAttributes('indent'),
|
||||
outdent: {
|
||||
command: "outdent",
|
||||
localizationId: "pad.toolbar.unindent.title",
|
||||
class: "buttonicon buttonicon-outdent"
|
||||
command: 'outdent',
|
||||
localizationId: 'pad.toolbar.unindent.title',
|
||||
class: 'buttonicon buttonicon-outdent',
|
||||
},
|
||||
|
||||
undo: defaultButtonAttributes("undo"),
|
||||
redo: defaultButtonAttributes("redo"),
|
||||
undo: defaultButtonAttributes('undo'),
|
||||
redo: defaultButtonAttributes('redo'),
|
||||
|
||||
clearauthorship: {
|
||||
command: "clearauthorship",
|
||||
localizationId: "pad.toolbar.clearAuthorship.title",
|
||||
class: "buttonicon buttonicon-clearauthorship"
|
||||
command: 'clearauthorship',
|
||||
localizationId: 'pad.toolbar.clearAuthorship.title',
|
||||
class: 'buttonicon buttonicon-clearauthorship',
|
||||
},
|
||||
|
||||
importexport: {
|
||||
command: "import_export",
|
||||
localizationId: "pad.toolbar.import_export.title",
|
||||
class: "buttonicon buttonicon-import_export"
|
||||
command: 'import_export',
|
||||
localizationId: 'pad.toolbar.import_export.title',
|
||||
class: 'buttonicon buttonicon-import_export',
|
||||
},
|
||||
|
||||
timeslider: {
|
||||
command: "showTimeSlider",
|
||||
localizationId: "pad.toolbar.timeslider.title",
|
||||
class: "buttonicon buttonicon-history"
|
||||
command: 'showTimeSlider',
|
||||
localizationId: 'pad.toolbar.timeslider.title',
|
||||
class: 'buttonicon buttonicon-history',
|
||||
},
|
||||
|
||||
savedrevision: defaultButtonAttributes("savedRevision"),
|
||||
settings: defaultButtonAttributes("settings"),
|
||||
embed: defaultButtonAttributes("embed"),
|
||||
showusers: defaultButtonAttributes("showusers"),
|
||||
savedrevision: defaultButtonAttributes('savedRevision'),
|
||||
settings: defaultButtonAttributes('settings'),
|
||||
embed: defaultButtonAttributes('embed'),
|
||||
showusers: defaultButtonAttributes('showusers'),
|
||||
|
||||
timeslider_export: {
|
||||
command: "import_export",
|
||||
localizationId: "timeslider.toolbar.exportlink.title",
|
||||
class: "buttonicon buttonicon-import_export"
|
||||
command: 'import_export',
|
||||
localizationId: 'timeslider.toolbar.exportlink.title',
|
||||
class: 'buttonicon buttonicon-import_export',
|
||||
},
|
||||
|
||||
timeslider_settings: {
|
||||
command: "settings",
|
||||
localizationId: "pad.toolbar.settings.title",
|
||||
class: "buttonicon buttonicon-settings"
|
||||
command: 'settings',
|
||||
localizationId: 'pad.toolbar.settings.title',
|
||||
class: 'buttonicon buttonicon-settings',
|
||||
},
|
||||
|
||||
timeslider_returnToPad: {
|
||||
command: "timeslider_returnToPad",
|
||||
localizationId: "timeslider.toolbar.returnbutton",
|
||||
class: "buttontext"
|
||||
}
|
||||
command: 'timeslider_returnToPad',
|
||||
localizationId: 'timeslider.toolbar.returnbutton',
|
||||
class: 'buttontext',
|
||||
},
|
||||
},
|
||||
|
||||
registerButton: function (buttonName, buttonInfo) {
|
||||
registerButton(buttonName, buttonInfo) {
|
||||
this.availableButtons[buttonName] = buttonInfo;
|
||||
},
|
||||
|
||||
button: function (attributes) {
|
||||
button(attributes) {
|
||||
return new Button(attributes);
|
||||
},
|
||||
separator: function () {
|
||||
return (new Separator).render();
|
||||
separator() {
|
||||
return (new Separator()).render();
|
||||
},
|
||||
selectButton: function (attributes) {
|
||||
selectButton(attributes) {
|
||||
return new SelectButton(attributes);
|
||||
},
|
||||
|
||||
|
@ -255,15 +249,15 @@ module.exports = {
|
|||
* Valid values for whichMenu: 'left' | 'right' | 'timeslider-right'
|
||||
* Valid values for page: 'pad' | 'timeslider'
|
||||
*/
|
||||
menu: function (buttons, isReadOnly, whichMenu, page) {
|
||||
menu(buttons, isReadOnly, whichMenu, page) {
|
||||
if (isReadOnly) {
|
||||
// The best way to detect if it's the left editbar is to check for a bold button
|
||||
if (buttons[0].indexOf("bold") !== -1) {
|
||||
if (buttons[0].indexOf('bold') !== -1) {
|
||||
// Clear all formatting buttons
|
||||
buttons = [];
|
||||
} else {
|
||||
// Remove Save Revision from the right menu
|
||||
removeItem(buttons[0],"savedrevision");
|
||||
removeItem(buttons[0], 'savedrevision');
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
|
@ -277,14 +271,12 @@ module.exports = {
|
|||
* sufficient to visit a single read only pad to cause the disappearence
|
||||
* of the star button from all the pads.
|
||||
*/
|
||||
if ((buttons[0].indexOf("savedrevision") === -1) && (whichMenu === "right") && (page === "pad")) {
|
||||
buttons[0].push("savedrevision");
|
||||
if ((buttons[0].indexOf('savedrevision') === -1) && (whichMenu === 'right') && (page === 'pad')) {
|
||||
buttons[0].push('savedrevision');
|
||||
}
|
||||
}
|
||||
|
||||
var groups = _.map(buttons, function (group) {
|
||||
return ButtonsGroup.fromArray(group).render();
|
||||
});
|
||||
const groups = _.map(buttons, (group) => ButtonsGroup.fromArray(group).render());
|
||||
return groups.join(this.separator());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
@ -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]);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -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"> </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(''));
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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> </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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
@ -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}`;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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 += ' ';
|
||||
}
|
||||
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, ' ');
|
||||
}
|
||||
var parts = [];
|
||||
s.replace(/<[^>]*>?| |[^ <]+/g, function(m) {
|
||||
const parts = [];
|
||||
s.replace(/<[^>]*>?| |[^ <]+/g, (m) => {
|
||||
parts.push(m);
|
||||
});
|
||||
if (doesWrap)
|
||||
{
|
||||
var endOfLine = true;
|
||||
var beforeSpace = false;
|
||||
if (doesWrap) {
|
||||
let endOfLine = true;
|
||||
let beforeSpace = false;
|
||||
// last space in a run is normal, others are nbsp,
|
||||
// end of line is nbsp
|
||||
for (var i = parts.length - 1; i >= 0; i--)
|
||||
{
|
||||
for (var i = parts.length - 1; i >= 0; i--) {
|
||||
var p = parts[i];
|
||||
if (p == " ")
|
||||
{
|
||||
if (p == ' ') {
|
||||
if (endOfLine || beforeSpace) parts[i] = ' ';
|
||||
endOfLine = false;
|
||||
beforeSpace = true;
|
||||
}
|
||||
else if (p.charAt(0) != "<")
|
||||
{
|
||||
} else if (p.charAt(0) != '<') {
|
||||
endOfLine = false;
|
||||
beforeSpace = false;
|
||||
}
|
||||
}
|
||||
// beginning of line is nbsp
|
||||
for (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] = ' ';
|
||||
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] = ' ';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}());
|
||||
|
|
|
@ -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('/'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}());
|
||||
|
|
|
@ -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;
|
||||
}());
|
||||
|
|
|
@ -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;
|
||||
}());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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) + '"> </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)}"> </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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue