mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-22 00:16:15 -04:00
Moved to ts for other dependencies.
This commit is contained in:
parent
3c2129b1cc
commit
7b99edc471
47 changed files with 1056 additions and 1363 deletions
|
@ -50,41 +50,18 @@ import {getPadHTMLDocument} from "../utils/ExportHtml";
|
|||
* GROUP FUNCTIONS ****
|
||||
******************** */
|
||||
|
||||
/*
|
||||
exports.listAllGroups = listAllGroups;
|
||||
exports.createGroup = createGroup;
|
||||
exports.createGroupIfNotExistsFor = createGroupIfNotExistsFor;
|
||||
exports.deleteGroup = deleteGroup;
|
||||
exports.listPads = listPads;
|
||||
exports.createGroupPad = createGroupPad;
|
||||
*/
|
||||
/* ********************
|
||||
* PADLIST FUNCTION ***
|
||||
******************** */
|
||||
/*
|
||||
exports.listAllPads = padManager.listAllPads;
|
||||
*/
|
||||
|
||||
/* ********************
|
||||
* AUTHOR FUNCTIONS ***
|
||||
******************** */
|
||||
/*
|
||||
exports.createAuthor = createAuthor;
|
||||
exports.createAuthorIfNotExistsFor = createAuthorIfNotExistsFor;
|
||||
exports.getAuthorName = getAuthorName;
|
||||
exports.listPadsOfAuthor = listPadsOfAuthor;
|
||||
exports.padUsers = padMessageHandler.padUsers;
|
||||
exports.padUsersCount = padMessageHandler.padUsersCount;
|
||||
*/
|
||||
|
||||
/* ********************
|
||||
* SESSION FUNCTIONS **
|
||||
******************** */
|
||||
/*
|
||||
exports.createSession = sessionManager.createSession;
|
||||
exports.deleteSession = sessionManager.deleteSession;
|
||||
exports.getSessionInfo = sessionManager.getSessionInfo;
|
||||
exports.listSessionsOfGroup = sessionManager.listSessionsOfGroup;
|
||||
exports.listSessionsOfAuthor = sessionManager.listSessionsOfAuthor;
|
||||
*/
|
||||
|
||||
/* ***********************
|
||||
* PAD CONTENT FUNCTIONS *
|
||||
*********************** */
|
||||
|
@ -742,7 +719,7 @@ Example returns:
|
|||
{"code":0,"message":"ok","data":null}
|
||||
{"code":4,"message":"no or wrong API Key","data":null}
|
||||
*/
|
||||
exports.checkToken = async () => {
|
||||
export const checkToken = async () => {
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
import {db} from './DB';
|
||||
import {CustomError} from '../utils/customError';
|
||||
import hooks from '../../static/js/pluginfw/hooks.js';
|
||||
import {aCallFirst} from '../../static/js/pluginfw/hooks.js';
|
||||
|
||||
const {randomString, padutils: {warnDeprecated}} = require('../../static/js/pad_utils');
|
||||
|
||||
|
@ -110,7 +110,7 @@ const getAuthor4Token2 = async (token: string) => {
|
|||
|
||||
export const getAuthorId = async (token, user) => {
|
||||
const context = {dbKey: token, token, user};
|
||||
let [authorId] = await hooks.aCallFirst('getAuthorId', context);
|
||||
let [authorId] = await aCallFirst('getAuthorId', context);
|
||||
if (!authorId) authorId = await getAuthor4Token2(context.dbKey);
|
||||
return authorId;
|
||||
};
|
||||
|
@ -137,7 +137,7 @@ export const createAuthorIfNotExistsFor = async (authorMapper, name: string) =>
|
|||
|
||||
if (name) {
|
||||
// set the name of this author
|
||||
await exports.setAuthorName(author.authorID, name);
|
||||
await setAuthorName(author.authorID, name);
|
||||
}
|
||||
|
||||
return author;
|
||||
|
@ -155,7 +155,7 @@ export const mapAuthorWithDBKey = async (mapperkey: string, mapper) => {
|
|||
|
||||
if (author == null) {
|
||||
// there is no author with this mapper, so create one
|
||||
const author = await exports.createAuthor(null);
|
||||
const author = await createAuthor(null);
|
||||
|
||||
// create the token2author relation
|
||||
await db.set(`${mapperkey}:${mapper}`, author.authorID);
|
||||
|
@ -182,7 +182,7 @@ export const createAuthor = async (name) => {
|
|||
|
||||
// create the globalAuthors db entry
|
||||
const authorObj = {
|
||||
colorId: Math.floor(Math.random() * (exports.getColorPalette().length)),
|
||||
colorId: Math.floor(Math.random() * (getColorPalette().length)),
|
||||
name,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
|
|
@ -31,32 +31,32 @@ const logger = log4js.getLogger('ueberDB');
|
|||
/**
|
||||
* The UeberDB Object that provides the database functions
|
||||
*/
|
||||
const db = null;
|
||||
let db = null;
|
||||
|
||||
/**
|
||||
* Initializes the database with the settings provided by the settings module
|
||||
*/
|
||||
const init = async () => {
|
||||
exports.db = new ueberDB.Database(dbType, dbSettings, null, logger);
|
||||
await exports.db.init();
|
||||
if (exports.db.metrics != null) {
|
||||
for (const [metric, value] of Object.entries(exports.db.metrics)) {
|
||||
db = new ueberDB.Database(dbType, dbSettings, null, logger);
|
||||
await db.init();
|
||||
if (db.metrics != null) {
|
||||
for (const [metric, value] of Object.entries(db.metrics)) {
|
||||
if (typeof value !== 'number') continue;
|
||||
// FIXME find a better replacement for measure-core
|
||||
createCollection.gauge(`ueberdb_${metric}`, () => exports.db.metrics[metric]);
|
||||
createCollection.gauge(`ueberdb_${metric}`, () => db.metrics[metric]);
|
||||
}
|
||||
}
|
||||
for (const fn of ['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove']) {
|
||||
const f = exports.db[fn];
|
||||
exports[fn] = async (...args) => await f.call(exports.db, ...args);
|
||||
const f = db[fn];
|
||||
exports[fn] = async (...args) => await f.call(db, ...args);
|
||||
Object.setPrototypeOf(exports[fn], Object.getPrototypeOf(f));
|
||||
Object.defineProperties(exports[fn], Object.getOwnPropertyDescriptors(f));
|
||||
}
|
||||
};
|
||||
|
||||
const shutdown = async (hookName, context) => {
|
||||
if (exports.db != null) await exports.db.close();
|
||||
exports.db = null;
|
||||
if (db != null) await db.close();
|
||||
db = null;
|
||||
logger.log('Database closed');
|
||||
};
|
||||
|
||||
|
|
|
@ -90,8 +90,8 @@ export const createGroupIfNotExistsFor = async (groupMapper) => {
|
|||
throw new CustomError('groupMapper is not a string', 'apierror');
|
||||
}
|
||||
const groupID = await db.get(`mapper2group:${groupMapper}`);
|
||||
if (groupID && await exports.doesGroupExist(groupID)) return {groupID};
|
||||
const result = await exports.createGroup();
|
||||
if (groupID && await doesGroupExist(groupID)) return {groupID};
|
||||
const result = await createGroup();
|
||||
await Promise.all([
|
||||
db.set(`mapper2group:${groupMapper}`, result.groupID),
|
||||
// Remember the mapping in the group record so that it can be cleaned up when the group is
|
||||
|
@ -132,7 +132,7 @@ export const createGroupPad = async (groupID, padName, text, authorId = '') => {
|
|||
};
|
||||
|
||||
export const listPads = async (groupID) => {
|
||||
const exists = await exports.doesGroupExist(groupID);
|
||||
const exists = await doesGroupExist(groupID);
|
||||
|
||||
// ensure the group exists
|
||||
if (!exists) {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import AttributeMap from '../../static/js/AttributeMap';
|
||||
import Changeset from '../../static/js/Changeset';
|
||||
import {applyToAText, makeAText} from '../../static/js/Changeset';
|
||||
import ChatMessage from '../../static/js/ChatMessage';
|
||||
import {AttributePool} from '../../static/js/AttributePool';
|
||||
import {Stream} from '../utils/Stream';
|
||||
|
@ -19,7 +19,7 @@ import {doesGroupExist} from './GroupManager';
|
|||
import {CustomError} from '../utils/customError';
|
||||
import {getReadOnlyId} from './ReadOnlyManager';
|
||||
import {randomString} from '../utils/randomstring';
|
||||
import hooks from '../../static/js/pluginfw/hooks';
|
||||
import {aCallAll} from '../../static/js/pluginfw/hooks';
|
||||
import {timesLimit} from '../utils/promises';
|
||||
import {padutils} from '../../static/js/pad_utils';
|
||||
/**
|
||||
|
@ -27,7 +27,7 @@ import {padutils} from '../../static/js/pad_utils';
|
|||
* line breaks and convert Tabs to spaces
|
||||
* @param txt
|
||||
*/
|
||||
exports.cleanText = (txt) => txt.replace(/\r\n/g, '\n')
|
||||
export const cleanText = (txt) => txt.replace(/\r\n/g, '\n')
|
||||
.replace(/\r/g, '\n')
|
||||
.replace(/\t/g, ' ')
|
||||
.replace(/\xa0/g, ' ');
|
||||
|
@ -51,7 +51,7 @@ export class Pad {
|
|||
*/
|
||||
constructor(id: string, database = db) {
|
||||
this.db = database;
|
||||
this.atext = Changeset.makeAText('\n');
|
||||
this.atext = makeAText('\n');
|
||||
this.pool = new AttributePool();
|
||||
this.head = -1;
|
||||
this.chatHead = -1;
|
||||
|
@ -83,11 +83,11 @@ export class Pad {
|
|||
}
|
||||
|
||||
async appendRevision(aChangeset, authorId = '') {
|
||||
const newAText = Changeset.applyToAText(aChangeset, this.atext, this.pool);
|
||||
const newAText = applyToAText(aChangeset, this.atext, this.pool);
|
||||
if (newAText.text === this.atext.text && newAText.attribs === this.atext.attribs) {
|
||||
return this.head;
|
||||
}
|
||||
Changeset.copyAText(newAText, this.atext);
|
||||
copyAText(newAText, this.atext);
|
||||
|
||||
const newRev = ++this.head;
|
||||
|
||||
|
@ -109,7 +109,7 @@ export class Pad {
|
|||
}),
|
||||
this.saveToDatabase(),
|
||||
authorId && addPad(authorId, this.id),
|
||||
hooks.aCallAll(hook, {
|
||||
aCallAll(hook, {
|
||||
pad: this,
|
||||
authorId,
|
||||
get author() {
|
||||
|
@ -188,7 +188,7 @@ export class Pad {
|
|||
]);
|
||||
const apool = this.apool();
|
||||
let atext = keyAText;
|
||||
for (const cs of changesets) atext = Changeset.applyToAText(cs, atext, apool);
|
||||
for (const cs of changesets) atext = applyToAText(cs, atext, apool);
|
||||
return atext;
|
||||
}
|
||||
|
||||
|
@ -259,7 +259,7 @@ export class Pad {
|
|||
const orig = this.text();
|
||||
assert(orig.endsWith('\n'));
|
||||
if (start + ndel > orig.length) throw new RangeError('start/delete past the end of the text');
|
||||
ins = exports.cleanText(ins);
|
||||
ins = cleanText(ins);
|
||||
const willEndWithNewline =
|
||||
start + ndel < orig.length || // Keeping last char (which is guaranteed to be a newline).
|
||||
ins.endsWith('\n') ||
|
||||
|
@ -362,14 +362,14 @@ export class Pad {
|
|||
} else {
|
||||
if (text == null) {
|
||||
const context = {pad: this, authorId, type: 'text', content: defaultPadText};
|
||||
await hooks.aCallAll('padDefaultContent', context);
|
||||
await aCallAll('padDefaultContent', context);
|
||||
if (context.type !== 'text') throw new Error(`unsupported content type: ${context.type}`);
|
||||
text = exports.cleanText(context.content);
|
||||
text = cleanText(context.content);
|
||||
}
|
||||
const firstChangeset = Changeset.makeSplice('\n', 0, 0, text);
|
||||
await this.appendRevision(firstChangeset, authorId);
|
||||
}
|
||||
await hooks.aCallAll('padLoad', {pad: this});
|
||||
await aCallAll('padLoad', {pad: this});
|
||||
}
|
||||
|
||||
async copy(destinationID, force) {
|
||||
|
@ -405,7 +405,7 @@ export class Pad {
|
|||
const dstPad = await getPad(destinationID, null);
|
||||
|
||||
// let the plugins know the pad was copied
|
||||
await hooks.aCallAll('padCopy', {
|
||||
await aCallAll('padCopy', {
|
||||
get originalPad() {
|
||||
padutils.warnDeprecated('padCopy originalPad context property is deprecated; use srcPad instead');
|
||||
return this.srcPad;
|
||||
|
@ -506,7 +506,7 @@ export class Pad {
|
|||
const changeset = Changeset.pack(oldLength, newLength, assem.toString(), newText);
|
||||
dstPad.appendRevision(changeset, authorId);
|
||||
|
||||
await hooks.aCallAll('padCopy', {
|
||||
await aCallAll('padCopy', {
|
||||
get originalPad() {
|
||||
padutils.warnDeprecated('padCopy originalPad context property is deprecated; use srcPad instead');
|
||||
return this.srcPad;
|
||||
|
@ -571,7 +571,7 @@ export class Pad {
|
|||
|
||||
// delete the pad entry and delete pad from padManager
|
||||
p.push(removePad(padID));
|
||||
p.push(hooks.aCallAll('padRemove', {
|
||||
p.push(aCallAll('padRemove', {
|
||||
get padID() {
|
||||
padutils.warnDeprecated('padRemove padID context property is deprecated; use pad.id instead');
|
||||
return this.pad.id;
|
||||
|
@ -730,7 +730,6 @@ export class Pad {
|
|||
.batch(100).buffer(99);
|
||||
for (const p of chats) await p;
|
||||
|
||||
await hooks.aCallAll('padCheck', {pad: this});
|
||||
await aCallAll('padCheck', {pad: this});
|
||||
}
|
||||
}
|
||||
exports.Pad = Pad;
|
||||
|
|
|
@ -61,11 +61,11 @@ export const getPadId = async (readOnlyId) => await db.get(`readonly2pad:${readO
|
|||
* @param {String} id padIdOrReadonlyPadId read only id or real pad id
|
||||
*/
|
||||
export const getIds = async (id: string) => {
|
||||
const readonly = exports.isReadOnlyId(id);
|
||||
const readonly = isReadOnlyId(id);
|
||||
|
||||
// Might be null, if this is an unknown read-only id
|
||||
const readOnlyPadId = readonly ? id : await exports.getReadOnlyId(id);
|
||||
const padId = readonly ? await exports.getPadId(id) : id;
|
||||
const readOnlyPadId = readonly ? id : await getReadOnlyId(id);
|
||||
const padId = readonly ? await getPadId(id) : id;
|
||||
|
||||
return {readOnlyPadId, padId, readonly};
|
||||
};
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
import {getAuthorId} from "./AuthorManager";
|
||||
|
||||
import hooks from "../../static/js/pluginfw/hooks.js";
|
||||
import {callAll} from "../../static/js/pluginfw/hooks.js";
|
||||
|
||||
import {doesPadExist, getPad} from "./PadManager";
|
||||
|
||||
|
@ -102,7 +102,7 @@ export const checkAccess = async (padID, sessionCookie, token, userSettings) =>
|
|||
|
||||
// allow plugins to deny access
|
||||
const isFalse = (x) => x === false;
|
||||
if (hooks.callAll('onAccessCheck', {padID, token, sessionCookie}).some(isFalse)) {
|
||||
if (callAll('onAccessCheck', {padID, token, sessionCookie}).some(isFalse)) {
|
||||
authLogger.debug('access denied: an onAccessCheck hook function returned false');
|
||||
return DENY;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
import ejs from 'ejs';
|
||||
import fs from "fs";
|
||||
|
||||
import hooks from "../../static/js/pluginfw/hooks.js";
|
||||
import {callAll} from "../../static/js/pluginfw/hooks.js";
|
||||
|
||||
import path from "path";
|
||||
|
||||
|
@ -64,7 +64,7 @@ export const end_block = () => {
|
|||
const content = info.__output.get();
|
||||
info.__output.set(info.__output_stack.pop());
|
||||
const args = {content, renderContext};
|
||||
hooks.callAll(`eejsBlock_${name}`, args);
|
||||
callAll(`eejsBlock_${name}`, args);
|
||||
info.__output.set(info.__output.get().concat(args.content));
|
||||
};
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ import os from 'os';
|
|||
import {setPadHTML} from '../utils/ImportHtml';
|
||||
import {setPadRaw} from '../utils/ImportEtherpad';
|
||||
import log4js from 'log4js';
|
||||
import hooks from '../../static/js/pluginfw/hooks.js';
|
||||
import {aCallAll} from '../../static/js/pluginfw/hooks.js';
|
||||
|
||||
const logger = log4js.getLogger('ImportHandler');
|
||||
|
||||
|
@ -134,7 +134,7 @@ const doImport = async (req, res, padId, authorId) => {
|
|||
|
||||
const destFile = path.join(tmpDirectory, `etherpad_import_${randNum}.${exportExtension}`);
|
||||
const context = {srcFile, destFile, fileEnding, padId, ImportError};
|
||||
const importHandledByPlugin = (await hooks.aCallAll('import', context)).some((x) => x);
|
||||
const importHandledByPlugin = (await aCallAll('import', context)).some((x) => x);
|
||||
const fileIsEtherpad = (fileEnding === '.etherpad');
|
||||
const fileIsHTML = (fileEnding === '.html' || fileEnding === '.htm');
|
||||
const fileIsTXT = (fileEnding === '.txt');
|
||||
|
|
|
@ -45,15 +45,14 @@ import {
|
|||
indentationOnNewLine,
|
||||
padOptions,
|
||||
padShortcutEnabled,
|
||||
randomVersionString,
|
||||
scrollWhenFocusLineIsOutOfViewport,
|
||||
skinName,
|
||||
skinVariants,
|
||||
sofficeAvailable
|
||||
} from '../utils/Settings';
|
||||
import plugins from '../../static/js/pluginfw/plugin_defs.js';
|
||||
import {parts, plugins} from '../../static/js/pluginfw/plugin_defs.js';
|
||||
import log4js from "log4js";
|
||||
import hooks from '../../static/js/pluginfw/hooks.js';
|
||||
import {aCallAll, deprecationNotices} from '../../static/js/pluginfw/hooks.js';
|
||||
import {createCollection} from '../stats';
|
||||
import {strict as assert} from "assert";
|
||||
|
||||
|
@ -62,6 +61,7 @@ import {userCanModify} from '../hooks/express/webaccess';
|
|||
import {ErrorCaused} from "../models/ErrorCaused";
|
||||
import {Pad} from "../db/Pad";
|
||||
import {SessionInfo} from "../models/SessionInfo";
|
||||
import {randomString} from "../utils/randomstring";
|
||||
|
||||
const securityManager = require('../db/SecurityManager');
|
||||
|
||||
|
@ -71,7 +71,7 @@ const accessLogger = log4js.getLogger('access');
|
|||
let rateLimiter;
|
||||
let socketio = null;
|
||||
|
||||
hooks.deprecationNotices.clientReady = 'use the userJoin hook instead';
|
||||
deprecationNotices.clientReady = 'use the userJoin hook instead';
|
||||
|
||||
const addContextToError = (err: Error, pfx) => {
|
||||
const newErr = new ErrorCaused(`${pfx}${err.message}`, err);
|
||||
|
@ -225,7 +225,7 @@ export const handleDisconnect = async (socket) => {
|
|||
},
|
||||
},
|
||||
});
|
||||
await hooks.aCallAll('userLeave', {
|
||||
await aCallAll('userLeave', {
|
||||
...session, // For backwards compatibility.
|
||||
authorId: session.author,
|
||||
readOnly: session.readonly,
|
||||
|
@ -328,7 +328,7 @@ export const handleMessage = async (socket, message) => {
|
|||
return this.socket;
|
||||
},
|
||||
};
|
||||
for (const res of await hooks.aCallAll('handleMessageSecurity', context)) {
|
||||
for (const res of await aCallAll('handleMessageSecurity', context)) {
|
||||
switch (res) {
|
||||
case true:
|
||||
padutils.warnDeprecated(
|
||||
|
@ -346,7 +346,7 @@ export const handleMessage = async (socket, message) => {
|
|||
}
|
||||
|
||||
// Call handleMessage hook. If a plugin returns null, the message will be dropped.
|
||||
if ((await hooks.aCallAll('handleMessage', context)).some((m) => m == null)) {
|
||||
if ((await aCallAll('handleMessage', context)).some((m) => m == null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -475,7 +475,7 @@ export const sendChatMessageToPadClients = async (mt, puId, text = null, padId =
|
|||
const message = mt instanceof ChatMessage ? mt : new ChatMessage(text, puId, mt);
|
||||
padId = mt instanceof ChatMessage ? puId : padId;
|
||||
const pad = await getPad(padId, null, message.authorId);
|
||||
await hooks.aCallAll('chatNewMessage', {message, pad, padId});
|
||||
await aCallAll('chatNewMessage', {message, pad, padId});
|
||||
// pad.appendChatMessage() ignores the displayName property so we don't need to wait for
|
||||
// authorManager.getAuthorName() to resolve before saving the message to the database.
|
||||
const promise = pad.appendChatMessage(message);
|
||||
|
@ -809,7 +809,7 @@ const handleClientReady = async (socket, message) => {
|
|||
if (sessionInfo == null) throw new Error('client disconnected');
|
||||
assert(sessionInfo.author);
|
||||
|
||||
await hooks.aCallAll('clientReady', message); // Deprecated due to awkward context.
|
||||
await aCallAll('clientReady', message); // Deprecated due to awkward context.
|
||||
|
||||
let {colorId: authorColorId, name: authorName} = message.userInfo || {};
|
||||
if (authorColorId && !/^#(?:[0-9A-F]{3}){1,2}$/i.test(authorColorId)) {
|
||||
|
@ -957,7 +957,7 @@ const handleClientReady = async (socket, message) => {
|
|||
const clientVars = {
|
||||
skinName: skinName,
|
||||
skinVariants: skinVariants,
|
||||
randomVersionString: randomVersionString,
|
||||
randomVersionString: randomString(4),
|
||||
accountPrivs: {
|
||||
maxRevisions: 100,
|
||||
},
|
||||
|
@ -995,8 +995,8 @@ const handleClientReady = async (socket, message) => {
|
|||
sofficeAvailable: sofficeAvailable(),
|
||||
exportAvailable: exportAvailable(),
|
||||
plugins: {
|
||||
plugins: plugins.plugins,
|
||||
parts: plugins.parts,
|
||||
plugins: plugins,
|
||||
parts: parts,
|
||||
},
|
||||
indentationOnNewLine: indentationOnNewLine,
|
||||
scrollWhenFocusLineIsOutOfViewport: {
|
||||
|
@ -1022,7 +1022,7 @@ const handleClientReady = async (socket, message) => {
|
|||
}
|
||||
|
||||
// call the clientVars-hook so plugins can modify them before they get sent to the client
|
||||
const messages = await hooks.aCallAll('clientVars', {clientVars, pad, socket});
|
||||
const messages = await aCallAll('clientVars', {clientVars, pad, socket});
|
||||
|
||||
// combine our old object with the new attributes from the hook
|
||||
for (const msg of messages) {
|
||||
|
@ -1093,7 +1093,7 @@ const handleClientReady = async (socket, message) => {
|
|||
socket.json.send(msg);
|
||||
}));
|
||||
|
||||
await hooks.aCallAll('userJoin', {
|
||||
await aCallAll('userJoin', {
|
||||
authorId: sessionInfo.author,
|
||||
displayName: authorName,
|
||||
padId: sessionInfo.padId,
|
||||
|
|
|
@ -10,7 +10,7 @@ import fs from "fs";
|
|||
|
||||
import expressSession from "express-session";
|
||||
|
||||
import hooks from "../../static/js/pluginfw/hooks";
|
||||
import {aCallAll} from "../../static/js/pluginfw/hooks";
|
||||
|
||||
import log4js from "log4js";
|
||||
|
||||
|
@ -52,7 +52,7 @@ const closeServer = async () => {
|
|||
// Call exports.server.close() to reject new connections but don't await just yet because the
|
||||
// Promise won't resolve until all preexisting connections are closed.
|
||||
const p = util.promisify(server.close.bind(server))();
|
||||
await hooks.aCallAll('expressCloseServer');
|
||||
await aCallAll('expressCloseServer');
|
||||
// Give existing connections some time to close on their own before forcibly terminating. The
|
||||
// time should be long enough to avoid interrupting most preexisting transmissions but short
|
||||
// enough to avoid a noticeable outage.
|
||||
|
@ -240,14 +240,14 @@ sessionMiddleware = expressSession({
|
|||
// Give plugins an opportunity to install handlers/middleware before the express-session
|
||||
// middleware. This allows plugins to avoid creating an express-session record in the database
|
||||
// when it is not needed (e.g., public static content).
|
||||
await hooks.aCallAll('expressPreSession', {app});
|
||||
await aCallAll('expressPreSession', {app});
|
||||
app.use(sessionMiddleware);
|
||||
|
||||
app.use(checkAccess2);
|
||||
|
||||
await Promise.all([
|
||||
hooks.aCallAll('expressConfigure', {app}),
|
||||
hooks.aCallAll('expressCreateServer', {app, server: server}),
|
||||
aCallAll('expressConfigure', {app}),
|
||||
aCallAll('expressCreateServer', {app, server: server}),
|
||||
]);
|
||||
server.on('connection', (socket) => {
|
||||
sockets.add(socket);
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
import {required} from '../../eejs';
|
||||
import {getEpVersion, getGitCommit} from "../../utils/Settings";
|
||||
|
||||
import installer from "../../../static/js/pluginfw/installer";
|
||||
import {getAvailablePlugins, install, search, uninstall} from "../../../static/js/pluginfw/installer";
|
||||
|
||||
import pluginDefs from "../../../static/js/pluginfw/plugin_defs";
|
||||
import {plugins} from "../../../static/js/pluginfw/plugin_defs";
|
||||
|
||||
import plugins from "../../../static/js/pluginfw/plugins";
|
||||
import {formatHooks, formatParts, formatPlugins} from "../../../static/js/pluginfw/plugins";
|
||||
|
||||
import semver from "semver";
|
||||
|
||||
|
@ -16,7 +16,7 @@ import UpdateCheck from "../../utils/UpdateCheck";
|
|||
export const expressCreateServer = (hookName, args, cb) => {
|
||||
args.app.get('/admin/plugins', (req, res) => {
|
||||
res.send(required('ep_etherpad-lite/templates/admin/plugins.html', {
|
||||
plugins: pluginDefs.plugins,
|
||||
plugins: plugins,
|
||||
req,
|
||||
errors: [],
|
||||
}));
|
||||
|
@ -29,10 +29,10 @@ export const expressCreateServer = (hookName, args, cb) => {
|
|||
res.send(required('ep_etherpad-lite/templates/admin/plugins-info.html', {
|
||||
gitCommit,
|
||||
epVersion,
|
||||
installedPlugins: `<pre>${plugins.formatPlugins().replace(/, /g, '\n')}</pre>`,
|
||||
installedParts: `<pre>${plugins.formatParts()}</pre>`,
|
||||
installedServerHooks: `<div>${plugins.formatHooks('hooks', true)}</div>`,
|
||||
installedClientHooks: `<div>${plugins.formatHooks('client_hooks', true)}</div>`,
|
||||
installedPlugins: `<pre>${formatPlugins().replace(/, /g, '\n')}</pre>`,
|
||||
installedParts: `<pre>${formatParts()}</pre>`,
|
||||
installedServerHooks: `<div>${formatHooks('hooks', true)}</div>`,
|
||||
installedClientHooks: `<div>${formatHooks('client_hooks', true)}</div>`,
|
||||
latestVersion: UpdateCheck.getLatestVersion(),
|
||||
req,
|
||||
}));
|
||||
|
@ -50,7 +50,7 @@ export const socketio = (hookName, args, cb) => {
|
|||
socket.on('getInstalled', (query) => {
|
||||
// send currently installed plugins
|
||||
const installed =
|
||||
Object.keys(pluginDefs.plugins).map((plugin) => pluginDefs.plugins[plugin].package);
|
||||
Object.keys(plugins).map((plugin) => plugins[plugin].package);
|
||||
|
||||
socket.emit('results:installed', {installed});
|
||||
});
|
||||
|
@ -58,13 +58,13 @@ export const socketio = (hookName, args, cb) => {
|
|||
socket.on('checkUpdates', async () => {
|
||||
// Check plugins for updates
|
||||
try {
|
||||
const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ 60 * 10);
|
||||
const results = await getAvailablePlugins(/* maxCacheAge:*/ 60 * 10);
|
||||
|
||||
const updatable = Object.keys(pluginDefs.plugins).filter((plugin) => {
|
||||
const updatable = Object.keys(plugins).filter((plugin) => {
|
||||
if (!results[plugin]) return false;
|
||||
|
||||
const latestVersion = results[plugin].version;
|
||||
const currentVersion = pluginDefs.plugins[plugin].package.version;
|
||||
const currentVersion = plugins[plugin].package.version;
|
||||
|
||||
return semver.gt(latestVersion, currentVersion);
|
||||
});
|
||||
|
@ -79,7 +79,7 @@ export const socketio = (hookName, args, cb) => {
|
|||
|
||||
socket.on('getAvailable', async (query) => {
|
||||
try {
|
||||
const results = await installer.getAvailablePlugins(/* maxCacheAge:*/ false);
|
||||
const results = await getAvailablePlugins(/* maxCacheAge:*/ false);
|
||||
socket.emit('results:available', results);
|
||||
} catch (er) {
|
||||
console.error(er);
|
||||
|
@ -89,10 +89,10 @@ export const socketio = (hookName, args, cb) => {
|
|||
|
||||
socket.on('search', async (query) => {
|
||||
try {
|
||||
const results = await installer.search(query.searchTerm, /* maxCacheAge:*/ 60 * 10);
|
||||
const results = await search(query.searchTerm, /* maxCacheAge:*/ 60 * 10);
|
||||
let res = Object.keys(results)
|
||||
.map((pluginName) => results[pluginName])
|
||||
.filter((plugin) => !pluginDefs.plugins[plugin.name]);
|
||||
.filter((plugin) => !plugins[plugin.name]);
|
||||
res = sortPluginList(res, query.sortBy, query.sortDir)
|
||||
.slice(query.offset, query.offset + query.limit);
|
||||
socket.emit('results:search', {results: res, query});
|
||||
|
@ -104,7 +104,7 @@ export const socketio = (hookName, args, cb) => {
|
|||
});
|
||||
|
||||
socket.on('install', (pluginName) => {
|
||||
installer.install(pluginName, (err) => {
|
||||
install(pluginName, (err) => {
|
||||
if (err) console.warn(err.stack || err.toString());
|
||||
|
||||
socket.emit('finished:install', {
|
||||
|
@ -116,7 +116,7 @@ export const socketio = (hookName, args, cb) => {
|
|||
});
|
||||
|
||||
socket.on('uninstall', (pluginName) => {
|
||||
installer.uninstall(pluginName, (err) => {
|
||||
uninstall(pluginName, (err) => {
|
||||
if (err) console.warn(err.stack || err.toString());
|
||||
|
||||
socket.emit('finished:uninstall', {plugin: pluginName, error: err ? err.message : null});
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
import {required} from '../../eejs';
|
||||
import {promises as fsp} from "fs";
|
||||
|
||||
import hooks from "../../../static/js/pluginfw/hooks";
|
||||
import {aCallAll} from "../../../static/js/pluginfw/hooks";
|
||||
|
||||
import plugins from "../../../static/js/pluginfw/plugins";
|
||||
import {update} from "../../../static/js/pluginfw/plugins";
|
||||
|
||||
import {reloadSettings, settingsFilename, showSettingsInAdminPage} from "../../utils/Settings";
|
||||
import * as settings from "../../utils/Settings";
|
||||
|
||||
exports.expressCreateServer = (hookName, {app}) => {
|
||||
export const expressCreateServer = (hookName, {app}) => {
|
||||
app.get('/admin/settings', (req, res) => {
|
||||
res.send(required('ep_etherpad-lite/templates/admin/settings.html', {
|
||||
req,
|
||||
|
@ -20,7 +20,7 @@ exports.expressCreateServer = (hookName, {app}) => {
|
|||
});
|
||||
};
|
||||
|
||||
exports.socketio = (hookName, {io}) => {
|
||||
export const socketio = (hookName, {io}) => {
|
||||
io.of('/settings').on('connection', (socket) => {
|
||||
const {session: {user: {is_admin: isAdmin} = {}} = {}}:SessionSocketModel = socket.conn.request;
|
||||
if (!isAdmin) return;
|
||||
|
@ -50,9 +50,9 @@ exports.socketio = (hookName, {io}) => {
|
|||
socket.on('restartServer', async () => {
|
||||
console.log('Admin request to restart server through a socket on /admin/settings');
|
||||
reloadSettings();
|
||||
await plugins.update();
|
||||
await hooks.aCallAll('loadSettings', {});
|
||||
await hooks.aCallAll('restartServer');
|
||||
await update();
|
||||
await aCallAll('loadSettings', {});
|
||||
await aCallAll('restartServer');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
import {createCollection} from '../../stats';
|
||||
|
||||
exports.expressCreateServer = (hook_name, args, cb) => {
|
||||
exports.app = args.app;
|
||||
export let app:any;
|
||||
export const expressCreateServer = (hook_name, args, cb) => {
|
||||
app = args.app;
|
||||
|
||||
// Handle errors
|
||||
args.app.use((err, req, res, next) => {
|
||||
|
|
|
@ -10,7 +10,7 @@ import rateLimit from 'express-rate-limit';
|
|||
import {checkAccess} from '../../db/SecurityManager';
|
||||
import {userCanModify} from './webaccess';
|
||||
|
||||
exports.expressCreateServer = (hookName, args, cb) => {
|
||||
export const expressCreateServer = (hookName, args, cb) => {
|
||||
importExportRateLimiting.onLimitReached = (req, res, options) => {
|
||||
// when the rate limiter triggers, write a warning in the logs
|
||||
console.warn('Import/Export rate limiter triggered on ' +
|
||||
|
|
|
@ -548,7 +548,7 @@ const generateDefinitionForVersion = (version, style = APIPathStyle.FLAT) => {
|
|||
return definition;
|
||||
};
|
||||
|
||||
exports.expressPreSession = async (hookName, {app}) => {
|
||||
export const expressPreSession = async (hookName, {app}) => {
|
||||
// create openapi-backend handlers for each api version under /api/{version}/*
|
||||
for (const version of Object.keys(apiHandler.version)) {
|
||||
// we support two different styles of api: flat + rest
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import {isValidPadId, sanitizePadId} from '../../db/PadManager';
|
||||
|
||||
exports.expressCreateServer = (hookName, args, cb) => {
|
||||
export const expressCreateServer = (hookName, args, cb) => {
|
||||
// redirects browser to the pad's sanitized url if needed. otherwise, renders the html
|
||||
args.app.param('pad', (req, res, next, padId) => {
|
||||
(async () => {
|
||||
|
|
|
@ -7,7 +7,7 @@ import proxyaddr from 'proxy-addr';
|
|||
import {socketIo, socketTransportProtocols, trustProxy} from '../../utils/Settings';
|
||||
import socketio from 'socket.io';
|
||||
import {addComponent, setSocketIO} from '../../handler/SocketIORouter';
|
||||
import hooks from '../../../static/js/pluginfw/hooks';
|
||||
import {callAll} from '../../../static/js/pluginfw/hooks';
|
||||
import * as padMessageHandler from '../../handler/PadMessageHandler';
|
||||
|
||||
let io;
|
||||
|
@ -15,7 +15,7 @@ const logger = log4js.getLogger('socket.io');
|
|||
const sockets = new Set();
|
||||
const socketsEvents = new events.EventEmitter();
|
||||
|
||||
exports.expressCloseServer = async () => {
|
||||
export const expressCloseServer = async () => {
|
||||
if (io == null) return;
|
||||
logger.info('Closing socket.io engine...');
|
||||
// Close the socket.io engine to disconnect existing clients and reject new clients. Don't call
|
||||
|
@ -46,7 +46,7 @@ exports.expressCloseServer = async () => {
|
|||
logger.info('All socket.io clients have disconnected');
|
||||
};
|
||||
|
||||
exports.expressCreateServer = (hookName, args, cb) => {
|
||||
export const expressCreateServer = (hookName, args, cb) => {
|
||||
// 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
|
||||
|
@ -133,7 +133,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
setSocketIO(io);
|
||||
addComponent('pad', padMessageHandler);
|
||||
|
||||
hooks.callAll('socketio', {app: args.app, io, server: args.server});
|
||||
callAll('socketio', {app: args.app, io, server: args.server});
|
||||
|
||||
return cb();
|
||||
};
|
||||
|
|
|
@ -5,12 +5,12 @@ import {required} from '../../eejs';
|
|||
import fs from 'fs';
|
||||
const fsp = fs.promises;
|
||||
import {} from '../../utils/toolbar';
|
||||
import hooks from '../../../static/js/pluginfw/hooks';
|
||||
import {callAll} from '../../../static/js/pluginfw/hooks';
|
||||
import {favicon, getEpVersion, maxAge, root, skinName} from '../../utils/Settings';
|
||||
import util from 'util';
|
||||
import {userCanModify} from './webaccess';
|
||||
|
||||
exports.expressPreSession = async (hookName, {app}) => {
|
||||
export const expressPreSession = async (hookName, {app}) => {
|
||||
// This endpoint is intended to conform to:
|
||||
// https://www.ietf.org/archive/id/draft-inadarei-api-health-check-06.html
|
||||
app.get('/health', (req, res) => {
|
||||
|
@ -63,7 +63,7 @@ exports.expressPreSession = async (hookName, {app}) => {
|
|||
});
|
||||
};
|
||||
|
||||
exports.expressCreateServer = (hookName, args, cb) => {
|
||||
export const expressCreateServer = (hookName, args, cb) => {
|
||||
// serve index.html under /
|
||||
args.app.get('/', (req, res) => {
|
||||
res.send(required('ep_etherpad-lite/templates/index.html', {req}));
|
||||
|
@ -74,7 +74,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
// The below might break for pads being rewritten
|
||||
const isReadOnly = !userCanModify(req.params.pad, req);
|
||||
|
||||
hooks.callAll('padInitToolbar', {
|
||||
callAll('padInitToolbar', {
|
||||
toolbar,
|
||||
isReadOnly,
|
||||
});
|
||||
|
@ -90,7 +90,7 @@ exports.expressCreateServer = (hookName, args, cb) => {
|
|||
|
||||
// serve timeslider.html under /p/$padname/timeslider
|
||||
args.app.get('/p/:pad/timeslider', (req, res, next) => {
|
||||
hooks.callAll('padInitToolbar', {
|
||||
callAll('padInitToolbar', {
|
||||
toolbar,
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import path from 'path';
|
||||
import {promises as fsp} from "fs";
|
||||
|
||||
import plugins from "../../../static/js/pluginfw/plugin_defs";
|
||||
import {plugins} from "../../../static/js/pluginfw/plugin_defs";
|
||||
|
||||
import sanitizePathname from "../../utils/sanitizePathname";
|
||||
|
||||
|
@ -36,7 +36,7 @@ export const expressPreSession = async (hookName, {app}) => {
|
|||
app.get('/tests/frontend/frontendTestSpecs.json', (req, res, next) => {
|
||||
(async () => {
|
||||
const modules = [];
|
||||
await Promise.all(Object.entries(plugins.plugins).map(async ([plugin, def]) => {
|
||||
await Promise.all(Object.entries(plugins).map(async ([plugin, def]) => {
|
||||
const mappedDef = def as Presession;
|
||||
let {package: {path: pluginPath}} = mappedDef;
|
||||
if (!pluginPath.endsWith(path.sep)) pluginPath += path.sep;
|
||||
|
|
|
@ -6,17 +6,18 @@ import log4js from "log4js";
|
|||
|
||||
import {requireAuthentication, requireAuthorization, setUsers, users} from "../../utils/Settings";
|
||||
|
||||
import hooks from "../../../static/js/pluginfw/hooks";
|
||||
import {deprecationNotices} from "../../../static/js/pluginfw/hooks";
|
||||
|
||||
import {getPadId, isReadOnlyId} from "../../db/ReadOnlyManager";
|
||||
import {UserIndexedModel} from "../../models/UserIndexedModel";
|
||||
|
||||
const httpLogger = log4js.getLogger('http');
|
||||
hooks.deprecationNotices.authFailure = 'use the authnFailure and authzFailure hooks instead';
|
||||
deprecationNotices.authFailure = 'use the authnFailure and authzFailure hooks instead';
|
||||
|
||||
// Promisified wrapper around hooks.aCallFirst.
|
||||
const aCallFirst = (hookName, context, pred = null) => new Promise((resolve, reject) => {
|
||||
hooks.aCallFirst(hookName, context, (err, r) => err != null ? reject(err) : resolve(r), pred);
|
||||
// FIXME Why are there 4 arguments but only 3 parameters?
|
||||
aCallFirst(hookName, context, (err, r) => err != null ? reject(err) : resolve(r));
|
||||
});
|
||||
|
||||
const aCallFirst0 =
|
||||
|
|
|
@ -4,7 +4,7 @@ import languages from 'languages4translatewiki';
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import _ from 'underscore';
|
||||
import pluginDefs from '../../static/js/pluginfw/plugin_defs.js';
|
||||
import {plugins} from '../../static/js/pluginfw/plugin_defs.js';
|
||||
import {check} from '../utils/path_exists';
|
||||
import {customLocaleStrings, maxAge, root} from '../utils/Settings';
|
||||
import {Presession} from "../models/Presession";
|
||||
|
@ -41,7 +41,7 @@ const getAllLocales = () => {
|
|||
extractLangs(path.join(root, 'src/locales'));
|
||||
|
||||
// add plugins languages (if any)
|
||||
for (const val of Object.values(pluginDefs.plugins)) {
|
||||
for (const val of Object.values(plugins)) {
|
||||
const pluginPath:Presession = val as Presession
|
||||
// plugin locales should overwrite etherpad's core locales
|
||||
if (pluginPath.package.path.endsWith('/ep_etherpad-lite') === true) continue;
|
||||
|
@ -86,7 +86,7 @@ const getAllLocales = () => {
|
|||
|
||||
// returns a hash of all available languages availables with nativeName and direction
|
||||
// e.g. { es: {nativeName: "español", direction: "ltr"}, ... }
|
||||
const getAvailableLangs = (locales) => {
|
||||
export const getAvailableLangs = (locales) => {
|
||||
const result = {};
|
||||
for (const langcode of Object.keys(locales)) {
|
||||
result[langcode] = languages.getLanguageInfo(langcode);
|
||||
|
@ -104,16 +104,16 @@ const generateLocaleIndex = (locales) => {
|
|||
};
|
||||
|
||||
|
||||
exports.expressPreSession = async (hookName, {app}) => {
|
||||
export const expressPreSession = async (hookName, {app}) => {
|
||||
// regenerate locales on server restart
|
||||
const locales = getAllLocales();
|
||||
const localeIndex = generateLocaleIndex(locales);
|
||||
exports.availableLangs = getAvailableLangs(locales);
|
||||
let availableLangs = getAvailableLangs(locales);
|
||||
|
||||
app.get('/locales/:locale', (req, res) => {
|
||||
// works with /locale/en and /locale/en.json requests
|
||||
const locale = req.params.locale.split('.')[0];
|
||||
if (Object.prototype.hasOwnProperty.call(exports.availableLangs, locale)) {
|
||||
if (Object.prototype.hasOwnProperty.call(availableLangs, locale)) {
|
||||
res.setHeader('Cache-Control', `public, max-age=${maxAge}`);
|
||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
res.send(`{"${locale}":${JSON.stringify(locales[locale])}}`);
|
||||
|
|
3
src/node/models/CMDArgv.ts
Normal file
3
src/node/models/CMDArgv.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export type CMDArgv = {
|
||||
[key: string]: any
|
||||
}
|
|
@ -25,10 +25,12 @@ import async from 'async';
|
|||
import {abiword} from './Settings';
|
||||
import os from 'os';
|
||||
|
||||
export let convertFile;
|
||||
|
||||
// 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) {
|
||||
exports.convertFile = async (srcFile, destFile, type) => {
|
||||
convertFile = async (srcFile, destFile, type) => {
|
||||
const abiword2 = spawn(abiword, [`--to=${destFile}`, srcFile]);
|
||||
let stdoutBuffer = '';
|
||||
abiword2.stdout.on('data', (data) => { stdoutBuffer += data.toString(); });
|
||||
|
@ -85,7 +87,7 @@ if (os.type().indexOf('Windows') > -1) {
|
|||
};
|
||||
}, 1);
|
||||
|
||||
exports.convertFile = async (srcFile, destFile, type) => {
|
||||
convertFile = async (srcFile, destFile, type) => {
|
||||
await queue.pushAsync({srcFile, destFile, type});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -21,32 +21,35 @@
|
|||
*/
|
||||
|
||||
// An object containing the parsed command-line options
|
||||
export const argv = process.argv.slice(2);
|
||||
import {CMDArgv} from "../models/CMDArgv";
|
||||
|
||||
let arg, prevArg;
|
||||
const argvcmd = process.argv.slice(2);
|
||||
|
||||
let arg, prevArg
|
||||
|
||||
export const argv:CMDArgv|undefined = {};
|
||||
// Loop through args
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
for (let i = 0; i < argvcmd.length; i++) {
|
||||
arg = argv[i];
|
||||
|
||||
// Override location of settings.json file
|
||||
if (prevArg === '--settings' || prevArg === '-s') {
|
||||
exports.argv.settings = arg;
|
||||
argv.settings = arg;
|
||||
}
|
||||
|
||||
// Override location of credentials.json file
|
||||
if (prevArg === '--credentials') {
|
||||
exports.argv.credentials = arg;
|
||||
argv.credentials = arg;
|
||||
}
|
||||
|
||||
// Override location of settings.json file
|
||||
if (prevArg === '--sessionkey') {
|
||||
exports.argv.sessionkey = arg;
|
||||
argv.sessionkey = arg;
|
||||
}
|
||||
|
||||
// Override location of settings.json file
|
||||
if (prevArg === '--apikey') {
|
||||
exports.argv.apikey = arg;
|
||||
argv.apikey = arg;
|
||||
}
|
||||
|
||||
prevArg = arg;
|
||||
|
|
|
@ -20,7 +20,7 @@ import {strict as assert} from "assert";
|
|||
|
||||
import {getAuthor} from "../db/AuthorManager";
|
||||
|
||||
import hooks from "../../static/js/pluginfw/hooks";
|
||||
import {aCallAll} from "../../static/js/pluginfw/hooks";
|
||||
|
||||
import {getPad} from "../db/PadManager";
|
||||
|
||||
|
@ -28,7 +28,7 @@ export const getPadRaw = async (padId, readOnlyId) => {
|
|||
const dstPfx = `pad:${readOnlyId || padId}`;
|
||||
const [pad, customPrefixes] = await Promise.all([
|
||||
getPad(padId),
|
||||
hooks.aCallAll('exportEtherpadAdditionalContent'),
|
||||
aCallAll('exportEtherpadAdditionalContent'),
|
||||
]);
|
||||
const pluginRecords = await Promise.all(customPrefixes.map(async (customPrefix) => {
|
||||
const srcPfx = `${customPrefix}:${padId}`;
|
||||
|
@ -58,7 +58,7 @@ export const getPadRaw = async (padId, readOnlyId) => {
|
|||
})();
|
||||
const data = {[dstPfx]: pad};
|
||||
for (const [dstKey, p] of new Stream(records).batch(100).buffer(99)) data[dstKey] = await p;
|
||||
await hooks.aCallAll('exportEtherpad', {
|
||||
await aCallAll('exportEtherpad', {
|
||||
pad,
|
||||
data,
|
||||
dstPadId: readOnlyId || padId,
|
||||
|
|
|
@ -23,7 +23,7 @@ import {getPad} from "../db/PadManager";
|
|||
import _ from "underscore";
|
||||
|
||||
import Security from '../../static/js/security';
|
||||
import hooks from '../../static/js/pluginfw/hooks';
|
||||
import {aCallAll} from '../../static/js/pluginfw/hooks';
|
||||
import {required} from '../eejs';
|
||||
import {_analyzeLine, _encodeWhitespace} from "./ExportHelper";
|
||||
|
||||
|
@ -51,7 +51,7 @@ export const getHTMLFromAtext = async (pad, atext, authorColors?) => {
|
|||
|
||||
await Promise.all([
|
||||
// prepare tags stored as ['tag', true] to be exported
|
||||
hooks.aCallAll('exportHtmlAdditionalTags', pad).then((newProps) => {
|
||||
aCallAll('exportHtmlAdditionalTags', pad).then((newProps) => {
|
||||
newProps.forEach((prop) => {
|
||||
tags.push(prop);
|
||||
props.push(prop);
|
||||
|
@ -59,7 +59,7 @@ export const getHTMLFromAtext = async (pad, atext, authorColors?) => {
|
|||
}),
|
||||
// prepare tags stored as ['tag', 'value'] to be exported. This will generate HTML with tags
|
||||
// like <span data-tag="value">
|
||||
hooks.aCallAll('exportHtmlAdditionalTagsWithData', pad).then((newProps) => {
|
||||
aCallAll('exportHtmlAdditionalTagsWithData', pad).then((newProps) => {
|
||||
newProps.forEach((prop) => {
|
||||
tags.push(`span data-${prop[0]}="${prop[1]}"`);
|
||||
props.push(prop);
|
||||
|
@ -314,7 +314,7 @@ export const getHTMLFromAtext = async (pad, atext, authorColors?) => {
|
|||
if (i < textLines.length) {
|
||||
nextLine = _analyzeLine(textLines[i + 1], attribLines[i + 1], apool);
|
||||
}
|
||||
await hooks.aCallAll('getLineHTMLForExport', context);
|
||||
await aCallAll('getLineHTMLForExport', context);
|
||||
// To create list parent elements
|
||||
if ((!prevLine || prevLine.listLevel !== line.listLevel) ||
|
||||
(line.listTypeName !== prevLine.listTypeName)) {
|
||||
|
@ -451,7 +451,7 @@ export const getHTMLFromAtext = async (pad, atext, authorColors?) => {
|
|||
padId: pad.id,
|
||||
};
|
||||
|
||||
await hooks.aCallAll('getLineHTMLForExport', context);
|
||||
await aCallAll('getLineHTMLForExport', context);
|
||||
pieces.push(context.lineContent, '<br>');
|
||||
}
|
||||
}
|
||||
|
@ -464,14 +464,14 @@ export const getPadHTMLDocument = async (padId, revNum, readOnlyId?) => {
|
|||
|
||||
// Include some Styles into the Head for Export
|
||||
let stylesForExportCSS = '';
|
||||
const stylesForExport = await hooks.aCallAll('stylesForExport', padId);
|
||||
const stylesForExport = await 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 aCallAll('exportHTMLAdditionalContent', {padId})) {
|
||||
html += hookHtml;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import {Pad} from '../db/Pad';
|
|||
import {Stream} from './Stream';
|
||||
import {addPad, doesAuthorExist} from '../db/AuthorManager';
|
||||
import {db} from '../db/DB';
|
||||
import hooks from '../../static/js/pluginfw/hooks';
|
||||
import {aCallAll, callAll} from '../../static/js/pluginfw/hooks';
|
||||
import log4js from "log4js";
|
||||
|
||||
import {supportedElems} from "../../static/js/contentcollector";
|
||||
|
@ -34,14 +34,14 @@ export const setPadRaw = async (padId, r, authorId = '') => {
|
|||
const records = JSON.parse(r);
|
||||
|
||||
// get supported block Elements from plugins, we will use this later.
|
||||
hooks.callAll('ccRegisterBlockElements').forEach((element) => {
|
||||
callAll('ccRegisterBlockElements').forEach((element) => {
|
||||
supportedElems.add(element);
|
||||
});
|
||||
|
||||
// DB key prefixes for pad records. Each key is expected to have the form `${prefix}:${padId}` or
|
||||
// `${prefix}:${padId}:${otherstuff}`.
|
||||
const padKeyPrefixes = [
|
||||
...await hooks.aCallAll('exportEtherpadAdditionalContent'),
|
||||
...await aCallAll('exportEtherpadAdditionalContent'),
|
||||
'pad',
|
||||
];
|
||||
|
||||
|
@ -103,7 +103,7 @@ export const setPadRaw = async (padId, r, authorId = '') => {
|
|||
|
||||
const pad = new Pad(padId, padDb);
|
||||
await pad.init(null, authorId);
|
||||
await hooks.aCallAll('importEtherpad', {
|
||||
await aCallAll('importEtherpad', {
|
||||
pad,
|
||||
// Shallow freeze meant to prevent accidental bugs. It would be better to deep freeze, but
|
||||
// it's not worth the added complexity.
|
||||
|
|
|
@ -26,7 +26,7 @@ import {promises as fs} from "fs";
|
|||
|
||||
import path from "path";
|
||||
|
||||
import plugins from "../../static/js/pluginfw/plugin_defs";
|
||||
import {plugins} from "../../static/js/pluginfw/plugin_defs";
|
||||
|
||||
import RequireKernel from "etherpad-require-kernel";
|
||||
|
||||
|
@ -155,8 +155,8 @@ const minify = async (req, res) => {
|
|||
const library = match[1];
|
||||
const libraryPath = match[2] || '';
|
||||
|
||||
if (plugins.plugins[library] && match[3]) {
|
||||
const plugin = plugins.plugins[library];
|
||||
if (plugins[library] && match[3]) {
|
||||
const plugin = plugins[library];
|
||||
const pluginPath = plugin.package.realPath;
|
||||
filename = path.join(pluginPath, libraryPath);
|
||||
// On Windows, path.relative converts forward slashes to backslashes. Convert them back
|
||||
|
|
|
@ -29,19 +29,20 @@
|
|||
|
||||
import exp from "constants";
|
||||
|
||||
const absolutePaths = require('./AbsolutePaths');
|
||||
const deepEqual = require('fast-deep-equal/es6');
|
||||
import {findEtherpadRoot, makeAbsolute, isSubdir} from './AbsolutePaths';
|
||||
import deepEqual from 'fast-deep-equal/es6';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
const argv = require('./Cli').argv;
|
||||
import jsonminify from 'jsonminify';
|
||||
import log4js from 'log4js';
|
||||
import {LogLevel} from "../models/LogLevel";
|
||||
const randomString = require('./randomstring');
|
||||
import {argv} from "./Cli";
|
||||
|
||||
import {randomString} from './randomstring';
|
||||
const suppressDisableMsg = ' -- To suppress these warning messages change ' +
|
||||
'suppressErrorsInPadText to true in your settings.json\n';
|
||||
const _ = require('underscore');
|
||||
import _ from 'underscore';
|
||||
|
||||
const logger = log4js.getLogger('settings');
|
||||
|
||||
|
@ -53,7 +54,10 @@ const nonSettings = [
|
|||
|
||||
// This is a function to make it easy to create a new instance. It is important to not reuse a
|
||||
// config object after passing it to log4js.configure() because that method mutates the object. :(
|
||||
const defaultLogConfig = () => ({appenders: [{type: 'console'}]});
|
||||
const defaultLogConfig = () => ({appenders: { console: { type: 'console' } },
|
||||
categories:{
|
||||
default: { appenders: ['console'], level: 'info'}
|
||||
}});
|
||||
const defaultLogLevel = 'INFO';
|
||||
|
||||
const initLogging = (logLevel, config) => {
|
||||
|
@ -71,11 +75,11 @@ const initLogging = (logLevel, config) => {
|
|||
initLogging(defaultLogLevel, defaultLogConfig());
|
||||
|
||||
/* Root path of the installation */
|
||||
export const root = absolutePaths.findEtherpadRoot();
|
||||
export const root = findEtherpadRoot();
|
||||
logger.info('All relative paths will be interpreted relative to the identified ' +
|
||||
`Etherpad base dir: ${root}`);
|
||||
export const settingsFilename = absolutePaths.makeAbsolute(argv.settings || 'settings.json');
|
||||
export const credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || 'credentials.json');
|
||||
export const settingsFilename = makeAbsolute(argv.settings || 'settings.json');
|
||||
export const credentialsFilename = makeAbsolute(argv.credentials || 'credentials.json');
|
||||
|
||||
/**
|
||||
* The app title, visible e.g. in the browser window
|
||||
|
@ -737,9 +741,7 @@ const parseSettings = (settingsFilename: string, isSettings:boolean) => {
|
|||
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
export const randomVersionString = randomString(4);
|
||||
}
|
||||
|
||||
|
||||
export const reloadSettings = () => {
|
||||
|
@ -772,7 +774,7 @@ export const reloadSettings = () => {
|
|||
let skinPath = path.join(skinBasePath, skinName);
|
||||
|
||||
// what if someone sets skinName == ".." or "."? We catch him!
|
||||
if (absolutePaths.isSubdir(skinBasePath, skinPath) === false) {
|
||||
if (isSubdir(skinBasePath, skinPath) === false) {
|
||||
logger.error(`Skin path ${skinPath} must be a subdirectory of ${skinBasePath}. ` +
|
||||
'Falling back to the default "colibris".');
|
||||
|
||||
|
@ -821,7 +823,7 @@ export const reloadSettings = () => {
|
|||
}
|
||||
|
||||
if (!sessionKey) {
|
||||
const sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || './SESSIONKEY.txt');
|
||||
const sessionkeyFilename = makeAbsolute(argv.sessionkey || './SESSIONKEY.txt');
|
||||
try {
|
||||
sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8');
|
||||
logger.info(`Session key loaded from: ${sessionkeyFilename}`);
|
||||
|
@ -846,7 +848,7 @@ export const reloadSettings = () => {
|
|||
defaultPadText += `\nWarning: ${dirtyWarning}${suppressDisableMsg}`;
|
||||
}
|
||||
|
||||
dbSettings.filename = absolutePaths.makeAbsolute(dbSettings.filename);
|
||||
dbSettings.filename = makeAbsolute(dbSettings.filename);
|
||||
logger.warn(`${dirtyWarning} File location: ${dbSettings.filename}`);
|
||||
}
|
||||
|
||||
|
@ -867,10 +869,12 @@ export const reloadSettings = () => {
|
|||
* ACHTUNG: this may prevent caching HTTP proxies to work
|
||||
* TODO: remove the "?v=randomstring" parameter, and replace with hashed filenames instead
|
||||
*/
|
||||
logger.info(`Random string used for versioning assets: ${randomVersionString}`);
|
||||
const randomVersionStringWith4Chars = randomString(4);
|
||||
logger.info(`Random string used for versioning assets: ${randomVersionStringWith4Chars}`);
|
||||
return settings
|
||||
};
|
||||
|
||||
exports.exportedForTestingOnly = {
|
||||
export const exportedForTestingOnly = {
|
||||
parseSettings,
|
||||
};
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import {tidyHtml} from "./Settings";
|
|||
|
||||
import {spawn} from "child_process";
|
||||
|
||||
exports.tidy = (srcFile) => {
|
||||
export const tidy = (srcFile) => {
|
||||
const logger = log4js.getLogger('TidyHtml');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
|
@ -28,7 +28,7 @@ const loadEtherpadInformations = () => new Promise<InfoModel>((resolve, reject)
|
|||
});
|
||||
|
||||
const getLatestVersion = () => {
|
||||
exports.needsUpdate();
|
||||
needsUpdate();
|
||||
if(infos === undefined){
|
||||
throw new Error("Could not retrieve latest version")
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ const getLatestVersion = () => {
|
|||
return infos.latestVersion;
|
||||
}
|
||||
|
||||
exports.needsUpdate = (cb?:(arg0: boolean)=>void) => {
|
||||
export const needsUpdate = (cb?:(arg0: boolean)=>void) => {
|
||||
loadEtherpadInformations().then((info) => {
|
||||
if (semver.gt(info.latestVersion, getEpVersion())) {
|
||||
if (cb) return cb(true);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
export class CustomError extends Error {
|
||||
code: any;
|
||||
signal: any;
|
||||
easysync: boolean
|
||||
/**
|
||||
* Creates an instance of CustomError.
|
||||
* @param {*} message
|
||||
|
|
|
@ -254,7 +254,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
|
|||
// They include final newlines on lines.
|
||||
|
||||
const linesGet = (idx) => {
|
||||
if (lines.get) {
|
||||
if ("get" in lines && lines.get instanceof Function) {
|
||||
return lines.get(idx);
|
||||
} else {
|
||||
return lines[idx];
|
||||
|
@ -262,7 +262,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
|
|||
};
|
||||
|
||||
const aLinesGet = (idx) => {
|
||||
if (alines.get) {
|
||||
if ("get" in alines && alines.get instanceof Function) {
|
||||
return alines.get(idx);
|
||||
} else {
|
||||
return alines[idx];
|
||||
|
|
|
@ -69,7 +69,7 @@ const logLines = (readable, logLineFn) => {
|
|||
* - `stderr`: Similar to `stdout` but for stderr.
|
||||
* - `child`: The ChildProcess object.
|
||||
*/
|
||||
export const exportCMD: (args: string[], opts:CMDOptions)=>void = async (args, opts = {
|
||||
export const exportCMD: (args: string[], opts?:CMDOptions)=>void = async (args, opts = {
|
||||
cwd: undefined,
|
||||
stdio: undefined,
|
||||
env: undefined
|
||||
|
|
1653
src/package-lock.json
generated
1653
src/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -31,6 +31,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"async": "^3.2.2",
|
||||
"cjstoesm": "^2.1.2",
|
||||
"clean-css": "^5.3.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cross-spawn": "^7.0.3",
|
||||
|
@ -113,6 +114,5 @@
|
|||
"dev": "concurrently \"npx tsc --watch\" \"nodemon -q dist/server.js\""
|
||||
},
|
||||
"version": "1.9.0",
|
||||
"license": "Apache-2.0",
|
||||
"type": "module"
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
* https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js
|
||||
*/
|
||||
|
||||
import {CustomError} from "../../node/utils/customError";
|
||||
|
||||
const AttributeMap = require('./AttributeMap');
|
||||
const AttributePool = require('./AttributePool');
|
||||
const attributes = require('./attributes');
|
||||
|
@ -47,8 +49,8 @@ const {padutils} = require('./pad_utils');
|
|||
*
|
||||
* @param {string} msg - Just some message
|
||||
*/
|
||||
const error = (msg) => {
|
||||
const e = new Error(msg);
|
||||
export const error = (msg) => {
|
||||
const e = new CustomError(msg);
|
||||
e.easysync = true;
|
||||
throw e;
|
||||
};
|
||||
|
@ -71,7 +73,7 @@ const assert = (b, msg) => {
|
|||
* @param {string} str - string of the number in base 36
|
||||
* @returns {number} number
|
||||
*/
|
||||
exports.parseNum = (str) => parseInt(str, 36);
|
||||
export const parseNum = (str) => parseInt(str, 36);
|
||||
|
||||
/**
|
||||
* Writes a number in base 36 and puts it in a string.
|
||||
|
@ -79,12 +81,16 @@ exports.parseNum = (str) => parseInt(str, 36);
|
|||
* @param {number} num - number
|
||||
* @returns {string} string
|
||||
*/
|
||||
exports.numToString = (num) => num.toString(36).toLowerCase();
|
||||
export const numToString = (num) => num.toString(36).toLowerCase();
|
||||
|
||||
/**
|
||||
* An operation to apply to a shared document.
|
||||
*/
|
||||
class Op {
|
||||
opcode: string;
|
||||
chars: number;
|
||||
lines: number;
|
||||
attribs: string;
|
||||
/**
|
||||
* @param {(''|'='|'+'|'-')} [opcode=''] - Initial value of the `opcode` property.
|
||||
*/
|
||||
|
@ -146,11 +152,11 @@ class Op {
|
|||
toString() {
|
||||
if (!this.opcode) throw new TypeError('null op');
|
||||
if (typeof this.attribs !== 'string') throw new TypeError('attribs must be a string');
|
||||
const l = this.lines ? `|${exports.numToString(this.lines)}` : '';
|
||||
return this.attribs + l + this.opcode + exports.numToString(this.chars);
|
||||
const l = this.lines ? `|${numToString(this.lines)}` : '';
|
||||
return this.attribs + l + this.opcode + numToString(this.chars);
|
||||
}
|
||||
}
|
||||
exports.Op = Op;
|
||||
export const Op = Op;
|
||||
|
||||
/**
|
||||
* Describes changes to apply to a document. Does not include the attribute pool or the original
|
||||
|
@ -170,7 +176,7 @@ exports.Op = Op;
|
|||
* @param {string} cs - String representation of the Changeset
|
||||
* @returns {number} oldLen property
|
||||
*/
|
||||
exports.oldLen = (cs) => exports.unpack(cs).oldLen;
|
||||
export const oldLen = (cs) => exports.unpack(cs).oldLen;
|
||||
|
||||
/**
|
||||
* Returns the length of the text after changeset is applied.
|
||||
|
@ -178,7 +184,7 @@ exports.oldLen = (cs) => exports.unpack(cs).oldLen;
|
|||
* @param {string} cs - String representation of the Changeset
|
||||
* @returns {number} newLen property
|
||||
*/
|
||||
exports.newLen = (cs) => exports.unpack(cs).newLen;
|
||||
export const newLen = (cs) => exports.unpack(cs).newLen;
|
||||
|
||||
/**
|
||||
* Parses a string of serialized changeset operations.
|
||||
|
@ -187,7 +193,7 @@ exports.newLen = (cs) => exports.unpack(cs).newLen;
|
|||
* @yields {Op}
|
||||
* @returns {Generator<Op>}
|
||||
*/
|
||||
exports.deserializeOps = function* (ops) {
|
||||
export const deserializeOps = function* (ops) {
|
||||
// TODO: Migrate to String.prototype.matchAll() once there is enough browser support.
|
||||
const regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|(.)/g;
|
||||
let match;
|
||||
|
@ -195,8 +201,8 @@ exports.deserializeOps = function* (ops) {
|
|||
if (match[5] === '$') return; // Start of the insert operation character bank.
|
||||
if (match[5] != null) error(`invalid operation: ${ops.slice(regex.lastIndex - 1)}`);
|
||||
const op = new Op(match[3]);
|
||||
op.lines = exports.parseNum(match[2] || '0');
|
||||
op.chars = exports.parseNum(match[4]);
|
||||
op.lines = parseNum(match[2] || '0');
|
||||
op.chars = parseNum(match[4]);
|
||||
op.attribs = match[1];
|
||||
yield op;
|
||||
}
|
||||
|
@ -210,11 +216,18 @@ exports.deserializeOps = function* (ops) {
|
|||
* @deprecated Use `deserializeOps` instead.
|
||||
*/
|
||||
class OpIter {
|
||||
private _next: {
|
||||
value: any;
|
||||
done: boolean,
|
||||
|
||||
|
||||
};
|
||||
private _gen: any;
|
||||
/**
|
||||
* @param {string} ops - String encoding the change operations to iterate over.
|
||||
*/
|
||||
constructor(ops) {
|
||||
this._gen = exports.deserializeOps(ops);
|
||||
this._gen = deserializeOps(ops);
|
||||
this._next = this._gen.next();
|
||||
}
|
||||
|
||||
|
@ -252,7 +265,7 @@ class OpIter {
|
|||
* @param {string} opsStr - String encoding of the change operations to perform.
|
||||
* @returns {OpIter} Operator iterator object.
|
||||
*/
|
||||
exports.opIterator = (opsStr) => {
|
||||
export const opIterator = (opsStr) => {
|
||||
padutils.warnDeprecated(
|
||||
'Changeset.opIterator() is deprecated; use Changeset.deserializeOps() instead');
|
||||
return new OpIter(opsStr);
|
||||
|
@ -277,7 +290,7 @@ const clearOp = (op) => {
|
|||
* @param {('+'|'-'|'='|'')} [optOpcode=''] - The operation's operator.
|
||||
* @returns {Op}
|
||||
*/
|
||||
exports.newOp = (optOpcode) => {
|
||||
export const newOp = (optOpcode) => {
|
||||
padutils.warnDeprecated('Changeset.newOp() is deprecated; use the Changeset.Op class instead');
|
||||
return new Op(optOpcode);
|
||||
};
|
||||
|
@ -370,17 +383,17 @@ const opsFromText = function* (opcode, text, attribs = '', pool = null) {
|
|||
* @param {string} cs - Changeset to check
|
||||
* @returns {string} the checked Changeset
|
||||
*/
|
||||
exports.checkRep = (cs) => {
|
||||
const unpacked = exports.unpack(cs);
|
||||
export const checkRep = (cs) => {
|
||||
const unpacked = unpack(cs);
|
||||
const oldLen = unpacked.oldLen;
|
||||
const newLen = unpacked.newLen;
|
||||
const ops = unpacked.ops;
|
||||
let charBank = unpacked.charBank;
|
||||
|
||||
const assem = exports.smartOpAssembler();
|
||||
const assem = smartOpAssembler();
|
||||
let oldPos = 0;
|
||||
let calcNewLen = 0;
|
||||
for (const o of exports.deserializeOps(ops)) {
|
||||
for (const o of deserializeOps(ops)) {
|
||||
switch (o.opcode) {
|
||||
case '=':
|
||||
oldPos += o.chars;
|
||||
|
@ -413,7 +426,7 @@ exports.checkRep = (cs) => {
|
|||
assert(calcNewLen === newLen, 'Invalid changeset: claimed length does not match actual length');
|
||||
assert(charBank === '', 'Invalid changeset: excess characters in the charBank');
|
||||
assem.endDocument();
|
||||
const normalized = exports.pack(oldLen, calcNewLen, assem.toString(), unpacked.charBank);
|
||||
const normalized = pack(oldLen, calcNewLen, assem.toString(), unpacked.charBank);
|
||||
assert(normalized === cs, 'Invalid changeset: not in canonical form');
|
||||
return cs;
|
||||
};
|
||||
|
@ -421,11 +434,11 @@ exports.checkRep = (cs) => {
|
|||
/**
|
||||
* @returns {SmartOpAssembler}
|
||||
*/
|
||||
exports.smartOpAssembler = () => {
|
||||
const minusAssem = exports.mergingOpAssembler();
|
||||
const plusAssem = exports.mergingOpAssembler();
|
||||
const keepAssem = exports.mergingOpAssembler();
|
||||
const assem = exports.stringAssembler();
|
||||
export const smartOpAssembler = () => {
|
||||
const minusAssem = mergingOpAssembler();
|
||||
const plusAssem = mergingOpAssembler();
|
||||
const keepAssem = mergingOpAssembler();
|
||||
const assem = stringAssembler();
|
||||
let lastOpcode = '';
|
||||
let lengthChange = 0;
|
||||
|
||||
|
@ -515,8 +528,8 @@ exports.smartOpAssembler = () => {
|
|||
/**
|
||||
* @returns {MergingOpAssembler}
|
||||
*/
|
||||
exports.mergingOpAssembler = () => {
|
||||
const assem = exports.opAssembler();
|
||||
export const mergingOpAssembler = () => {
|
||||
const assem = opAssembler();
|
||||
const bufOp = new Op();
|
||||
|
||||
// If we get, for example, insertions [xxx\n,yyy], those don't merge,
|
||||
|
@ -528,7 +541,7 @@ exports.mergingOpAssembler = () => {
|
|||
/**
|
||||
* @param {boolean} [isEndDocument]
|
||||
*/
|
||||
const flush = (isEndDocument) => {
|
||||
const flush = (isEndDocument?: boolean) => {
|
||||
if (!bufOp.opcode) return;
|
||||
if (isEndDocument && bufOp.opcode === '=' && !bufOp.attribs) {
|
||||
// final merged keep, leave it implicit
|
||||
|
@ -589,7 +602,7 @@ exports.mergingOpAssembler = () => {
|
|||
/**
|
||||
* @returns {OpAssembler}
|
||||
*/
|
||||
exports.opAssembler = () => {
|
||||
export const opAssembler = () => {
|
||||
let serialized = '';
|
||||
|
||||
/**
|
||||
|
@ -627,7 +640,7 @@ exports.opAssembler = () => {
|
|||
* @param {string} str - String to iterate over
|
||||
* @returns {StringIterator}
|
||||
*/
|
||||
exports.stringIterator = (str) => {
|
||||
export const stringIterator = (str) => {
|
||||
let curIndex = 0;
|
||||
// newLines is the number of \n between curIndex and str.length
|
||||
let newLines = str.split('\n').length - 1;
|
||||
|
@ -677,7 +690,7 @@ exports.stringIterator = (str) => {
|
|||
/**
|
||||
* @returns {StringAssembler}
|
||||
*/
|
||||
exports.stringAssembler = () => ({
|
||||
export const stringAssembler = () => ({
|
||||
_str: '',
|
||||
clear() { this._str = ''; },
|
||||
/**
|
||||
|
@ -711,6 +724,11 @@ exports.stringAssembler = () => ({
|
|||
* with no newlines.
|
||||
*/
|
||||
class TextLinesMutator {
|
||||
private _lines: any;
|
||||
private _curSplice: number[]|{[key: string]:any};
|
||||
private _inSplice: boolean;
|
||||
private _curLine: number;
|
||||
private _curCol: number;
|
||||
/**
|
||||
* @param {(string[]|StringArrayLike)} lines - Lines to mutate (in place).
|
||||
*/
|
||||
|
@ -798,10 +816,12 @@ class TextLinesMutator {
|
|||
* close or TODO(doc).
|
||||
*/
|
||||
_leaveSplice() {
|
||||
this._lines.splice(...this._curSplice);
|
||||
this._curSplice.length = 2;
|
||||
this._curSplice[0] = this._curSplice[1] = 0;
|
||||
this._inSplice = false;
|
||||
if(this._curSplice instanceof Array) {
|
||||
this._lines.splice(...this._curSplice);
|
||||
this._curSplice.length = 2;
|
||||
this._curSplice[0] = this._curSplice[1] = 0;
|
||||
this._inSplice = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -837,7 +857,7 @@ class TextLinesMutator {
|
|||
* @param {number} L -
|
||||
* @param {boolean} includeInSplice - Indicates that attributes are present.
|
||||
*/
|
||||
skipLines(L, includeInSplice) {
|
||||
skipLines(L, includeInSplice?:boolean) {
|
||||
if (!L) return;
|
||||
if (includeInSplice) {
|
||||
if (!this._inSplice) this._enterSplice();
|
||||
|
@ -905,7 +925,7 @@ class TextLinesMutator {
|
|||
return this._linesSlice(m, m + k).join('');
|
||||
};
|
||||
|
||||
let removed = '';
|
||||
let removed:string|number = '';
|
||||
if (this._isCurLineInSplice()) {
|
||||
if (this._curCol === 0) {
|
||||
removed = this._curSplice[this._curSplice.length - 1];
|
||||
|
@ -958,7 +978,7 @@ class TextLinesMutator {
|
|||
if (!text) return;
|
||||
if (!this._inSplice) this._enterSplice();
|
||||
if (L) {
|
||||
const newLines = exports.splitTextLines(text);
|
||||
const newLines = splitTextLines(text);
|
||||
if (this._isCurLineInSplice()) {
|
||||
const sline = this._curSplice.length - 1;
|
||||
/** @type {string} */
|
||||
|
@ -1039,11 +1059,11 @@ class TextLinesMutator {
|
|||
* @returns {string} the integrated changeset
|
||||
*/
|
||||
const applyZip = (in1, in2, func) => {
|
||||
const ops1 = exports.deserializeOps(in1);
|
||||
const ops2 = exports.deserializeOps(in2);
|
||||
const ops1 = deserializeOps(in1);
|
||||
const ops2 = deserializeOps(in2);
|
||||
let next1 = ops1.next();
|
||||
let next2 = ops2.next();
|
||||
const assem = exports.smartOpAssembler();
|
||||
const assem = smartOpAssembler();
|
||||
while (!next1.done || !next2.done) {
|
||||
if (!next1.done && !next1.value.opcode) next1 = ops1.next();
|
||||
if (!next2.done && !next2.value.opcode) next2 = ops2.next();
|
||||
|
@ -1063,13 +1083,13 @@ const applyZip = (in1, in2, func) => {
|
|||
* @param {string} cs - The encoded changeset.
|
||||
* @returns {Changeset}
|
||||
*/
|
||||
exports.unpack = (cs) => {
|
||||
export const unpack = (cs) => {
|
||||
const headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/;
|
||||
const headerMatch = headerRegex.exec(cs);
|
||||
if ((!headerMatch) || (!headerMatch[0])) error(`Not a changeset: ${cs}`);
|
||||
const oldLen = exports.parseNum(headerMatch[1]);
|
||||
const oldLen = parseNum(headerMatch[1]);
|
||||
const changeSign = (headerMatch[2] === '>') ? 1 : -1;
|
||||
const changeMag = exports.parseNum(headerMatch[3]);
|
||||
const changeMag = parseNum(headerMatch[3]);
|
||||
const newLen = oldLen + changeSign * changeMag;
|
||||
const opsStart = headerMatch[0].length;
|
||||
let opsEnd = cs.indexOf('$');
|
||||
|
@ -1091,12 +1111,12 @@ exports.unpack = (cs) => {
|
|||
* @param {string} bank - Characters for insert operations.
|
||||
* @returns {string} The encoded changeset.
|
||||
*/
|
||||
exports.pack = (oldLen, newLen, opsStr, bank) => {
|
||||
export const pack = (oldLen, newLen, opsStr, bank) => {
|
||||
const lenDiff = newLen - oldLen;
|
||||
const lenDiffStr = (lenDiff >= 0 ? `>${exports.numToString(lenDiff)}`
|
||||
: `<${exports.numToString(-lenDiff)}`);
|
||||
const lenDiffStr = (lenDiff >= 0 ? `>${numToString(lenDiff)}`
|
||||
: `<${numToString(-lenDiff)}`);
|
||||
const a = [];
|
||||
a.push('Z:', exports.numToString(oldLen), lenDiffStr, opsStr, '$', bank);
|
||||
a.push('Z:', numToString(oldLen), lenDiffStr, opsStr, '$', bank);
|
||||
return a.join('');
|
||||
};
|
||||
|
||||
|
@ -1107,13 +1127,13 @@ exports.pack = (oldLen, newLen, opsStr, bank) => {
|
|||
* @param {string} str - String to which a Changeset should be applied
|
||||
* @returns {string}
|
||||
*/
|
||||
exports.applyToText = (cs, str) => {
|
||||
const unpacked = exports.unpack(cs);
|
||||
export const applyToText = (cs, str) => {
|
||||
const unpacked = unpack(cs);
|
||||
assert(str.length === unpacked.oldLen, `mismatched apply: ${str.length} / ${unpacked.oldLen}`);
|
||||
const bankIter = exports.stringIterator(unpacked.charBank);
|
||||
const strIter = exports.stringIterator(str);
|
||||
const assem = exports.stringAssembler();
|
||||
for (const op of exports.deserializeOps(unpacked.ops)) {
|
||||
const bankIter = stringIterator(unpacked.charBank);
|
||||
const strIter = stringIterator(str);
|
||||
const assem = stringAssembler();
|
||||
for (const op of deserializeOps(unpacked.ops)) {
|
||||
switch (op.opcode) {
|
||||
case '+':
|
||||
// op is + and op.lines 0: no newlines must be in op.chars
|
||||
|
@ -1151,11 +1171,11 @@ exports.applyToText = (cs, str) => {
|
|||
* @param {string} cs - the changeset to apply
|
||||
* @param {string[]} lines - The lines to which the changeset needs to be applied
|
||||
*/
|
||||
exports.mutateTextLines = (cs, lines) => {
|
||||
const unpacked = exports.unpack(cs);
|
||||
const bankIter = exports.stringIterator(unpacked.charBank);
|
||||
export const mutateTextLines = (cs, lines) => {
|
||||
const unpacked = unpack(cs);
|
||||
const bankIter = stringIterator(unpacked.charBank);
|
||||
const mut = new TextLinesMutator(lines);
|
||||
for (const op of exports.deserializeOps(unpacked.ops)) {
|
||||
for (const op of deserializeOps(unpacked.ops)) {
|
||||
switch (op.opcode) {
|
||||
case '+':
|
||||
mut.insert(bankIter.take(op.chars), op.lines);
|
||||
|
@ -1180,7 +1200,7 @@ exports.mutateTextLines = (cs, lines) => {
|
|||
* @param {AttributePool} pool - attribute pool
|
||||
* @returns {string}
|
||||
*/
|
||||
exports.composeAttributes = (att1, att2, resultIsMutation, pool) => {
|
||||
export const composeAttributes = (att1, att2, resultIsMutation, pool) => {
|
||||
// att1 and att2 are strings like "*3*f*1c", asMutation is a boolean.
|
||||
// Sometimes attribute (key,value) pairs are treated as attribute presence
|
||||
// information, while other times they are treated as operations that
|
||||
|
@ -1258,7 +1278,7 @@ const slicerZipperFunc = (attOp, csOp, pool) => {
|
|||
// normally be the empty string. However, padDiff.js adds attributes to remove ops and needs
|
||||
// them preserved so they are copied here.
|
||||
? csOp.attribs
|
||||
: exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode === '=', pool);
|
||||
: composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode === '=', pool);
|
||||
partiallyConsumedOp.chars -= fullyConsumedOp.chars;
|
||||
partiallyConsumedOp.lines -= fullyConsumedOp.lines;
|
||||
if (!partiallyConsumedOp.chars) partiallyConsumedOp.opcode = '';
|
||||
|
@ -1275,8 +1295,8 @@ const slicerZipperFunc = (attOp, csOp, pool) => {
|
|||
* @param {AttributePool} pool - the attibutes pool
|
||||
* @returns {string}
|
||||
*/
|
||||
exports.applyToAttribution = (cs, astr, pool) => {
|
||||
const unpacked = exports.unpack(cs);
|
||||
export const applyToAttribution = (cs, astr, pool) => {
|
||||
const unpacked = unpack(cs);
|
||||
return applyZip(astr, unpacked.ops, (op1, op2) => slicerZipperFunc(op1, op2, pool));
|
||||
};
|
||||
|
||||
|
@ -1749,7 +1769,7 @@ exports.mapAttribNumbers = (cs, func) => {
|
|||
* attributes
|
||||
* @returns {AText}
|
||||
*/
|
||||
exports.makeAText = (text, attribs) => ({
|
||||
export const makeAText = (text, attribs) => ({
|
||||
text,
|
||||
attribs: (attribs || exports.makeAttribution(text)),
|
||||
});
|
||||
|
@ -1762,7 +1782,7 @@ exports.makeAText = (text, attribs) => ({
|
|||
* @param {AttributePool} pool - Attribute Pool to add to
|
||||
* @returns {AText}
|
||||
*/
|
||||
exports.applyToAText = (cs, atext, pool) => ({
|
||||
export const applyToAText = (cs, atext, pool) => ({
|
||||
text: exports.applyToText(cs, atext.text),
|
||||
attribs: exports.applyToAttribution(cs, atext.attribs, pool),
|
||||
});
|
||||
|
@ -1787,7 +1807,7 @@ exports.cloneAText = (atext) => {
|
|||
* @param {AText} atext1 -
|
||||
* @param {AText} atext2 -
|
||||
*/
|
||||
exports.copyAText = (atext1, atext2) => {
|
||||
export const copyAText = (atext1, atext2) => {
|
||||
atext2.text = atext1.text;
|
||||
atext2.attribs = atext1.attribs;
|
||||
};
|
||||
|
@ -1799,10 +1819,10 @@ exports.copyAText = (atext1, atext2) => {
|
|||
* @yields {Op}
|
||||
* @returns {Generator<Op>}
|
||||
*/
|
||||
exports.opsFromAText = function* (atext) {
|
||||
export const opsFromAText = function* (atext) {
|
||||
// intentionally skips last newline char of atext
|
||||
let lastOp = null;
|
||||
for (const op of exports.deserializeOps(atext.attribs)) {
|
||||
for (const op of deserializeOps(atext.attribs)) {
|
||||
if (lastOp != null) yield lastOp;
|
||||
lastOp = op;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const pluginDefs = require('./plugin_defs');
|
||||
import {hooks} from './plugin_defs';
|
||||
|
||||
// Maps the name of a server-side hook to a string explaining the deprecation
|
||||
// (e.g., 'use the foo hook instead').
|
||||
|
@ -10,12 +10,15 @@ const pluginDefs = require('./plugin_defs');
|
|||
// const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
|
||||
// hooks.deprecationNotices.fooBar = 'use the newSpiffy hook instead';
|
||||
//
|
||||
exports.deprecationNotices = {};
|
||||
export const deprecationNotices:{
|
||||
authFailure?: string;
|
||||
clientReady?: string
|
||||
} = {};
|
||||
|
||||
const deprecationWarned = {};
|
||||
|
||||
const checkDeprecation = (hook) => {
|
||||
const notice = exports.deprecationNotices[hook.hook_name];
|
||||
const notice = deprecationNotices[hook.hook_name];
|
||||
if (notice == null) return;
|
||||
if (deprecationWarned[hook.hook_fn_name]) return;
|
||||
console.warn(`${hook.hook_name} hook used by the ${hook.part.plugin} plugin ` +
|
||||
|
@ -189,10 +192,10 @@ const callHookFnSync = (hook, context) => {
|
|||
// 1. Collect all values returned by the hook functions into an array.
|
||||
// 2. Convert each `undefined` entry into `[]`.
|
||||
// 3. Flatten one level.
|
||||
exports.callAll = (hookName, context) => {
|
||||
export const callAll = (hookName, context?) => {
|
||||
if (context == null) context = {};
|
||||
const hooks = pluginDefs.hooks[hookName] || [];
|
||||
return flatten1(hooks.map((hook) => normalizeValue(callHookFnSync(hook, context))));
|
||||
const hooksResult = hooks[hookName] || [];
|
||||
return flatten1(hooksResult.map((hook) => normalizeValue(callHookFnSync(hook, context))));
|
||||
};
|
||||
|
||||
// Calls the hook function asynchronously and returns a Promise that either resolves to the hook
|
||||
|
@ -342,23 +345,23 @@ const callHookFnAsync = async (hook, context) => {
|
|||
// 2. Convert each `undefined` entry into `[]`.
|
||||
// 3. Flatten one level.
|
||||
// If cb is non-null, this function resolves to the value returned by cb.
|
||||
exports.aCallAll = async (hookName, context, cb = null) => {
|
||||
if (cb != null) return await attachCallback(exports.aCallAll(hookName, context), cb);
|
||||
export const aCallAll = async (hookName, context?, cb = null) => {
|
||||
if (cb != null) return await attachCallback(aCallAll(hookName, context), cb);
|
||||
if (context == null) context = {};
|
||||
const hooks = pluginDefs.hooks[hookName] || [];
|
||||
const hooksResult = hooks[hookName] || [];
|
||||
const results = await Promise.all(
|
||||
hooks.map(async (hook) => normalizeValue(await callHookFnAsync(hook, context))));
|
||||
hooksResult.map(async (hook) => normalizeValue(await callHookFnAsync(hook, context))));
|
||||
return flatten1(results);
|
||||
};
|
||||
|
||||
// Like `aCallAll()` except the hook functions are called one at a time instead of concurrently.
|
||||
// Only use this function if the hook functions must be called one at a time, otherwise use
|
||||
// `aCallAll()`.
|
||||
exports.callAllSerial = async (hookName, context) => {
|
||||
export const callAllSerial = async (hookName, context) => {
|
||||
if (context == null) context = {};
|
||||
const hooks = pluginDefs.hooks[hookName] || [];
|
||||
const hooksResult = hooks[hookName] || [];
|
||||
const results = [];
|
||||
for (const hook of hooks) {
|
||||
for (const hook of hooksResult) {
|
||||
results.push(normalizeValue(await callHookFnAsync(hook, context)));
|
||||
}
|
||||
return flatten1(results);
|
||||
|
@ -367,11 +370,11 @@ exports.callAllSerial = async (hookName, context) => {
|
|||
// DEPRECATED: Use `aCallFirst()` instead.
|
||||
//
|
||||
// Like `aCallFirst()`, but synchronous. Hook functions must provide their values synchronously.
|
||||
exports.callFirst = (hookName, context) => {
|
||||
export const callFirst = (hookName, context) => {
|
||||
if (context == null) context = {};
|
||||
const predicate = (val) => val.length;
|
||||
const hooks = pluginDefs.hooks[hookName] || [];
|
||||
for (const hook of hooks) {
|
||||
const hooksResult = hooks[hookName] || [];
|
||||
for (const hook of hooksResult) {
|
||||
const val = normalizeValue(callHookFnSync(hook, context));
|
||||
if (predicate(val)) return val;
|
||||
}
|
||||
|
@ -399,21 +402,21 @@ exports.callFirst = (hookName, context) => {
|
|||
// If cb is nullish, resolves to an array that is either the normalized value that satisfied the
|
||||
// predicate or empty if the predicate was never satisfied. If cb is non-nullish, resolves to the
|
||||
// value returned from cb().
|
||||
exports.aCallFirst = async (hookName, context, cb = null, predicate = null) => {
|
||||
export const aCallFirst = async (hookName, context, cb = null, predicate = null) => {
|
||||
if (cb != null) {
|
||||
return await attachCallback(exports.aCallFirst(hookName, context, null, predicate), cb);
|
||||
return await attachCallback(aCallFirst(hookName, context, null, predicate), cb);
|
||||
}
|
||||
if (context == null) context = {};
|
||||
if (predicate == null) predicate = (val) => val.length;
|
||||
const hooks = pluginDefs.hooks[hookName] || [];
|
||||
for (const hook of hooks) {
|
||||
const hooksResult = hooks[hookName] || [];
|
||||
for (const hook of hooksResult) {
|
||||
const val = normalizeValue(await callHookFnAsync(hook, context));
|
||||
if (predicate(val)) return val;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
exports.exportedForTestingOnly = {
|
||||
export const exportedForTestingOnly = {
|
||||
callHookFnAsync,
|
||||
callHookFnSync,
|
||||
deprecationWarned,
|
|
@ -1,18 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
const log4js = require('log4js');
|
||||
const plugins = require('./plugins');
|
||||
const hooks = require('./hooks');
|
||||
const request = require('request');
|
||||
const runCmd = require('../../../node/utils/run_cmd');
|
||||
const settings = require('../../../node/utils/Settings');
|
||||
import log4js from 'log4js';
|
||||
import {prefix, update} from "./plugins";
|
||||
|
||||
import {aCallAll} from "./hooks";
|
||||
|
||||
import request from "request";
|
||||
|
||||
import {exportCMD} from "../../../node/utils/run_cmd";
|
||||
|
||||
import {reloadSettings} from "../../../node/utils/Settings";
|
||||
import {InstallerModel} from "../../module/InstallerModel";
|
||||
|
||||
const logger = log4js.getLogger('plugins');
|
||||
|
||||
const onAllTasksFinished = async () => {
|
||||
settings.reloadSettings();
|
||||
await hooks.aCallAll('loadSettings', {settings});
|
||||
await hooks.aCallAll('restartServer');
|
||||
const settings = reloadSettings();
|
||||
await aCallAll('loadSettings', {settings});
|
||||
await aCallAll('restartServer');
|
||||
};
|
||||
|
||||
let tasks = 0;
|
||||
|
@ -27,54 +32,54 @@ const wrapTaskCb = (cb) => {
|
|||
};
|
||||
};
|
||||
|
||||
exports.uninstall = async (pluginName, cb = null) => {
|
||||
export const uninstall = async (pluginName, cb = null) => {
|
||||
cb = wrapTaskCb(cb);
|
||||
logger.info(`Uninstalling plugin ${pluginName}...`);
|
||||
try {
|
||||
// The --no-save flag prevents npm from creating package.json or package-lock.json.
|
||||
// The --legacy-peer-deps flag is required to work around a bug in npm v7:
|
||||
// https://github.com/npm/cli/issues/2199
|
||||
await runCmd(['npm', 'uninstall', '--no-save', '--legacy-peer-deps', pluginName]);
|
||||
await exportCMD(['npm', 'uninstall', '--no-save', '--legacy-peer-deps', pluginName]);
|
||||
} catch (err) {
|
||||
logger.error(`Failed to uninstall plugin ${pluginName}`);
|
||||
cb(err || new Error(err));
|
||||
throw err;
|
||||
}
|
||||
logger.info(`Successfully uninstalled plugin ${pluginName}`);
|
||||
await hooks.aCallAll('pluginUninstall', {pluginName});
|
||||
await plugins.update();
|
||||
await aCallAll('pluginUninstall', {pluginName});
|
||||
await update();
|
||||
cb(null);
|
||||
};
|
||||
|
||||
exports.install = async (pluginName, cb = null) => {
|
||||
export const install = async (pluginName, cb = null) => {
|
||||
cb = wrapTaskCb(cb);
|
||||
logger.info(`Installing plugin ${pluginName}...`);
|
||||
try {
|
||||
// The --no-save flag prevents npm from creating package.json or package-lock.json.
|
||||
// The --legacy-peer-deps flag is required to work around a bug in npm v7:
|
||||
// https://github.com/npm/cli/issues/2199
|
||||
await runCmd(['npm', 'install', '--no-save', '--legacy-peer-deps', pluginName]);
|
||||
await exportCMD(['npm', 'install', '--no-save', '--legacy-peer-deps', pluginName]);
|
||||
} catch (err) {
|
||||
logger.error(`Failed to install plugin ${pluginName}`);
|
||||
cb(err || new Error(err));
|
||||
throw err;
|
||||
}
|
||||
logger.info(`Successfully installed plugin ${pluginName}`);
|
||||
await hooks.aCallAll('pluginInstall', {pluginName});
|
||||
await plugins.update();
|
||||
await aCallAll('pluginInstall', {pluginName});
|
||||
await update();
|
||||
cb(null);
|
||||
};
|
||||
|
||||
exports.availablePlugins = null;
|
||||
export let availablePlugins = null;
|
||||
let cacheTimestamp = 0;
|
||||
|
||||
exports.getAvailablePlugins = (maxCacheAge) => {
|
||||
export const getAvailablePlugins = (maxCacheAge) => {
|
||||
const nowTimestamp = Math.round(Date.now() / 1000);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// check cache age before making any request
|
||||
if (exports.availablePlugins && maxCacheAge && (nowTimestamp - cacheTimestamp) <= maxCacheAge) {
|
||||
return resolve(exports.availablePlugins);
|
||||
if (availablePlugins && maxCacheAge && (nowTimestamp - cacheTimestamp) <= maxCacheAge) {
|
||||
return resolve(availablePlugins);
|
||||
}
|
||||
|
||||
request('https://static.etherpad.org/plugins.json', (er, response, plugins) => {
|
||||
|
@ -87,7 +92,7 @@ exports.getAvailablePlugins = (maxCacheAge) => {
|
|||
plugins = [];
|
||||
}
|
||||
|
||||
exports.availablePlugins = plugins;
|
||||
availablePlugins = plugins;
|
||||
cacheTimestamp = nowTimestamp;
|
||||
resolve(plugins);
|
||||
});
|
||||
|
@ -95,18 +100,19 @@ exports.getAvailablePlugins = (maxCacheAge) => {
|
|||
};
|
||||
|
||||
|
||||
exports.search = (searchTerm, maxCacheAge) => exports.getAvailablePlugins(maxCacheAge).then(
|
||||
(results) => {
|
||||
export const search = (searchTerm, maxCacheAge) => getAvailablePlugins(maxCacheAge).then(
|
||||
(results: InstallerModel[]) => {
|
||||
const res = {};
|
||||
|
||||
if (searchTerm) {
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
for (const pluginName in results) {
|
||||
// for every available plugin
|
||||
// TODO: Also search in keywords here!
|
||||
if (pluginName.indexOf(plugins.prefix) !== 0) continue;
|
||||
if (pluginName.indexOf(prefix) !== 0) continue;
|
||||
|
||||
if (searchTerm && !~results[pluginName].name.toLowerCase().indexOf(searchTerm) &&
|
||||
(typeof results[pluginName].description !== 'undefined' &&
|
|
@ -8,13 +8,13 @@
|
|||
// * hook_fn: Plugin-supplied hook function.
|
||||
// * hook_fn_name: Name of the hook function, with the form <filename>:<functionName>.
|
||||
// * part: The ep.json part object that declared the hook. See exports.plugins.
|
||||
exports.hooks = {};
|
||||
export const hooks = {};
|
||||
|
||||
// Whether the plugins have been loaded.
|
||||
exports.loaded = false;
|
||||
export let loaded = false;
|
||||
|
||||
// Topologically sorted list of parts from exports.plugins.
|
||||
exports.parts = [];
|
||||
export const parts = [];
|
||||
|
||||
// Maps the name of a plugin to the plugin's definition provided in ep.json. The ep.json object is
|
||||
// augmented with additional metadata:
|
||||
|
@ -25,4 +25,20 @@ exports.parts = [];
|
|||
// - version
|
||||
// - path
|
||||
// - realPath
|
||||
exports.plugins = {};
|
||||
export const plugins = {};
|
||||
|
||||
export const setPlugins = (newPlugins) => {
|
||||
Object.assign(plugins, newPlugins);
|
||||
}
|
||||
|
||||
export const setParts = (newParts) => {
|
||||
Object.assign(parts, newParts);
|
||||
}
|
||||
|
||||
export const setHooks = (newHooks) => {
|
||||
Object.assign(hooks, newHooks);
|
||||
}
|
||||
|
||||
export const setLoaded = (newLoaded) => {
|
||||
loaded = newLoaded;
|
||||
}
|
|
@ -1,20 +1,28 @@
|
|||
'use strict';
|
||||
|
||||
const fs = require('fs').promises;
|
||||
const hooks = require('./hooks');
|
||||
const log4js = require('log4js');
|
||||
const path = require('path');
|
||||
const runCmd = require('../../../node/utils/run_cmd');
|
||||
const tsort = require('./tsort');
|
||||
const pluginUtils = require('./shared');
|
||||
const defs = require('./plugin_defs');
|
||||
import {promises as fs} from "fs";
|
||||
|
||||
import {aCallAll} from "./hooks";
|
||||
|
||||
import log4js from "log4js";
|
||||
|
||||
import path from "path";
|
||||
|
||||
import {exportCMD} from "../../../node/utils/run_cmd";
|
||||
|
||||
import {tsort} from "./tsort";
|
||||
|
||||
import {extractHooks} from "./shared";
|
||||
|
||||
import {loaded, parts, plugins, setHooks, setLoaded, setParts, setPlugins} from "./plugin_defs";
|
||||
import {PluginInfo} from "../../module/PluginInfo";
|
||||
|
||||
const logger = log4js.getLogger('plugins');
|
||||
|
||||
// Log the version of npm at startup.
|
||||
(async () => {
|
||||
try {
|
||||
const version = await runCmd(['npm', '--version'], {stdio: [null, 'string']});
|
||||
const version = await exportCMD(['npm', '--version'], {stdio: [null, 'string']});
|
||||
logger.info(`npm --version: ${version}`);
|
||||
} catch (err) {
|
||||
logger.error(`Failed to get npm version: ${err.stack || err}`);
|
||||
|
@ -22,16 +30,19 @@ const logger = log4js.getLogger('plugins');
|
|||
}
|
||||
})();
|
||||
|
||||
exports.prefix = 'ep_';
|
||||
type PartType = {
|
||||
[keys: string]:any
|
||||
}
|
||||
export const prefix = 'ep_';
|
||||
|
||||
exports.formatPlugins = () => Object.keys(defs.plugins).join(', ');
|
||||
export const formatPlugins = () => Object.keys(plugins).join(', ');
|
||||
|
||||
exports.formatParts = () => defs.parts.map((part) => part.full_name).join('\n');
|
||||
export const formatParts = () => parts.map((part) => part.full_name).join('\n');
|
||||
|
||||
exports.formatHooks = (hookSetName, html) => {
|
||||
export const formatHooks = (hookSetName, html) => {
|
||||
let hooks = new Map();
|
||||
for (const [pluginName, def] of Object.entries(defs.plugins)) {
|
||||
for (const part of def.parts) {
|
||||
for (const [pluginName, def] of Object.entries(plugins)) {
|
||||
for (const part of parts) {
|
||||
for (const [hookName, hookFnName] of Object.entries(part[hookSetName] || {})) {
|
||||
let hookEntry = hooks.get(hookName);
|
||||
if (!hookEntry) {
|
||||
|
@ -53,7 +64,7 @@ exports.formatHooks = (hookSetName, html) => {
|
|||
hooks = new Map([...hooks].sort(sortStringKeys));
|
||||
for (const [hookName, hookEntry] of hooks) {
|
||||
lines.push(html ? ` <dt>${hookName}:</dt><dd><dl>` : ` ${hookName}:`);
|
||||
const sortedHookEntry = new Map([...hookEntry].sort(sortStringKeys));
|
||||
const sortedHookEntry = new Map<any,any>([...hookEntry].sort(sortStringKeys));
|
||||
hooks.set(hookName, sortedHookEntry);
|
||||
for (const [pluginName, pluginEntry] of sortedHookEntry) {
|
||||
lines.push(html ? ` <dt>${pluginName}:</dt><dd><dl>` : ` ${pluginName}:`);
|
||||
|
@ -72,20 +83,20 @@ exports.formatHooks = (hookSetName, html) => {
|
|||
return lines.join('\n');
|
||||
};
|
||||
|
||||
exports.pathNormalization = (part, hookFnName, hookName) => {
|
||||
export const pathNormalization = (part, hookFnName, hookName) => {
|
||||
const tmp = hookFnName.split(':'); // hookFnName might be something like 'C:\\foo.js:myFunc'.
|
||||
// If there is a single colon assume it's 'filename:funcname' not 'C:\\filename'.
|
||||
const functionName = (tmp.length > 1 ? tmp.pop() : null) || hookName;
|
||||
const moduleName = tmp.join(':') || part.plugin;
|
||||
const packageDir = path.dirname(defs.plugins[part.plugin].package.path);
|
||||
const packageDir = path.dirname(plugins[part.plugin].package.path);
|
||||
const fileName = path.join(packageDir, moduleName);
|
||||
return `${fileName}:${functionName}`;
|
||||
};
|
||||
|
||||
exports.update = async () => {
|
||||
export const update = async () => {
|
||||
const packages = await exports.getPackages();
|
||||
const parts = {}; // Key is full name. sortParts converts this into a topologically sorted array.
|
||||
const plugins = {};
|
||||
let parts:{[keys: string]:any} = {}; // Key is full name. sortParts converts this into a topologically sorted array.
|
||||
let plugins = {};
|
||||
|
||||
// Load plugin metadata ep.json
|
||||
await Promise.all(Object.keys(packages).map(async (pluginName) => {
|
||||
|
@ -94,13 +105,13 @@ exports.update = async () => {
|
|||
}));
|
||||
logger.info(`Loaded ${Object.keys(packages).length} plugins`);
|
||||
|
||||
defs.plugins = plugins;
|
||||
defs.parts = sortParts(parts);
|
||||
defs.hooks = pluginUtils.extractHooks(defs.parts, 'hooks', exports.pathNormalization);
|
||||
defs.loaded = true;
|
||||
await Promise.all(Object.keys(defs.plugins).map(async (p) => {
|
||||
setPlugins(plugins);
|
||||
setParts(sortParts(parts))
|
||||
setHooks(extractHooks(parts, 'hooks', exports.pathNormalization));
|
||||
setLoaded(true)
|
||||
await Promise.all(Object.keys(plugins).map(async (p) => {
|
||||
const logger = log4js.getLogger(`plugin:${p}`);
|
||||
await hooks.aCallAll(`init_${p}`, {logger});
|
||||
await aCallAll(`init_${p}`, {logger});
|
||||
}));
|
||||
};
|
||||
|
||||
|
@ -112,13 +123,15 @@ exports.getPackages = async () => {
|
|||
// unset or set to `development`) because otherwise `npm ls` will not mention any packages
|
||||
// that are not included in `package.json` (which is expected to not exist).
|
||||
const cmd = ['npm', 'ls', '--long', '--json', '--depth=0', '--no-production'];
|
||||
const {dependencies = {}} = JSON.parse(await runCmd(cmd, {stdio: [null, 'string']}));
|
||||
const {dependencies = {}} = JSON.parse(await exportCMD(cmd, {stdio: [null, 'string']}) as unknown as string);
|
||||
await Promise.all(Object.entries(dependencies).map(async ([pkg, info]) => {
|
||||
if (!pkg.startsWith(exports.prefix)) {
|
||||
delete dependencies[pkg];
|
||||
return;
|
||||
}
|
||||
info.realPath = await fs.realpath(info.path);
|
||||
const mappedInfo = info as PluginInfo
|
||||
|
||||
mappedInfo.realPath = await fs.realpath(mappedInfo.path);
|
||||
}));
|
||||
return dependencies;
|
||||
};
|
||||
|
@ -126,7 +139,7 @@ exports.getPackages = async () => {
|
|||
const loadPlugin = async (packages, pluginName, plugins, parts) => {
|
||||
const pluginPath = path.resolve(packages[pluginName].path, 'ep.json');
|
||||
try {
|
||||
const data = await fs.readFile(pluginPath);
|
||||
const data = await fs.readFile(pluginPath, "utf8");
|
||||
try {
|
||||
const plugin = JSON.parse(data);
|
||||
plugin.package = packages[pluginName];
|
||||
|
@ -145,7 +158,7 @@ const loadPlugin = async (packages, pluginName, plugins, parts) => {
|
|||
};
|
||||
|
||||
const partsToParentChildList = (parts) => {
|
||||
const res = [];
|
||||
const res:(string|number)[][] = [];
|
||||
for (const name of Object.keys(parts)) {
|
||||
for (const childName of parts[name].post || []) {
|
||||
res.push([name, childName]);
|
||||
|
@ -161,6 +174,7 @@ const partsToParentChildList = (parts) => {
|
|||
};
|
||||
|
||||
// Used only in Node, so no need for _
|
||||
const sortParts = (parts) => tsort(partsToParentChildList(parts))
|
||||
.filter((name) => parts[name] !== undefined)
|
||||
.map((name) => parts[name]);
|
||||
//FIXME Is this better
|
||||
const sortParts = (parts:PartType) => tsort(partsToParentChildList(parts))
|
||||
.filter((name) => name !== undefined)
|
||||
.map((name) => name);
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const defs = require('./plugin_defs');
|
||||
import {parts} from './plugin_defs';
|
||||
|
||||
const disabledHookReasons = {
|
||||
hooks: {
|
||||
|
@ -9,7 +9,7 @@ const disabledHookReasons = {
|
|||
},
|
||||
};
|
||||
|
||||
const loadFn = (path, hookName) => {
|
||||
export const loadFn = (path, hookName) => {
|
||||
let functionName;
|
||||
const parts = path.split(':');
|
||||
|
||||
|
@ -33,7 +33,7 @@ const loadFn = (path, hookName) => {
|
|||
return fn;
|
||||
};
|
||||
|
||||
const extractHooks = (parts, hookSetName, normalizer) => {
|
||||
export const extractHooks = (parts, hookSetName, normalizer) => {
|
||||
const hooks = {};
|
||||
for (const part of parts) {
|
||||
for (const [hookName, regHookFnName] of Object.entries(part[hookSetName] || {})) {
|
||||
|
@ -72,9 +72,7 @@ const extractHooks = (parts, hookSetName, normalizer) => {
|
|||
}
|
||||
}
|
||||
return hooks;
|
||||
};
|
||||
|
||||
exports.extractHooks = extractHooks;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns an array containing the names of the installed client-side plugins
|
||||
|
@ -88,8 +86,8 @@ exports.extractHooks = extractHooks;
|
|||
* No plugins: []
|
||||
* Some plugins: [ 'ep_adminpads', 'ep_add_buttons', 'ep_activepads' ]
|
||||
*/
|
||||
exports.clientPluginNames = () => {
|
||||
const clientPluginNames = defs.parts
|
||||
export const clientPluginNames = () => {
|
||||
const clientPluginNames = parts
|
||||
.filter((part) => Object.prototype.hasOwnProperty.call(part, 'client_hooks'))
|
||||
.map((part) => `plugin-${part.plugin}`);
|
||||
return [...new Set(clientPluginNames)];
|
|
@ -4,14 +4,14 @@
|
|||
* general topological sort
|
||||
* from https://gist.github.com/1232505
|
||||
* @author SHIN Suzuki (shinout310@gmail.com)
|
||||
* @param Array<Array> edges : list of edges. each edge forms Array<ID,ID> e.g. [12 , 3]
|
||||
* @param edges Array<Array> edges : list of edges. each edge forms Array<ID,ID> e.g. [12 , 3]
|
||||
*
|
||||
* @returns Array : topological sorted list of IDs
|
||||
**/
|
||||
|
||||
const tsort = (edges) => {
|
||||
export const tsort = (edges: (string|number)[][]) => {
|
||||
const nodes = {}; // hash: stringified id of the node => { id: id, afters: lisf of ids }
|
||||
const sorted = []; // sorted list of IDs ( returned value )
|
||||
const sorted: (string|number)[][]= []; // sorted list of IDs ( returned value )
|
||||
const visited = {}; // hash: id of already visited node => true
|
||||
|
||||
const Node = function (id) {
|
||||
|
@ -62,7 +62,7 @@ const tsort = (edges) => {
|
|||
**/
|
||||
const tsortTest = () => {
|
||||
// example 1: success
|
||||
let edges = [
|
||||
let edges:(string|number)[][] = [
|
||||
[1, 2],
|
||||
[1, 3],
|
||||
[2, 4],
|
5
src/static/module/InstallerModel.ts
Normal file
5
src/static/module/InstallerModel.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export type InstallerModel = {
|
||||
name: string,
|
||||
description: string,
|
||||
|
||||
}
|
4
src/static/module/PluginInfo.ts
Normal file
4
src/static/module/PluginInfo.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export type PluginInfo = {
|
||||
realPath: string,
|
||||
path: string,
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"typeRoots": ["node_modules/@types"],
|
||||
"types": ["node", "jquery"],
|
||||
"module": "commonjs",
|
||||
|
@ -7,7 +8,7 @@
|
|||
"target": "es6",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"outDir": "dist"
|
||||
"outDir": "../dist"
|
||||
},
|
||||
"lib": ["es2015"]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue