From 9fb2c640b9cabf4e6bd7870f059ee232d7efe2d7 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 18 Feb 2021 18:49:43 +0000 Subject: [PATCH 001/115] tests: Microsoft Windows Server CI (#4791) Due to a recent release that wasn't functioning properly this CI will help us catch the majority of Microsoft Node Quirks before they make it into a release. --- .gitattributes | 1 + .github/workflows/backend-tests.yml | 92 ++++++++++++++++++- .../backend/specs/api/importexportGetPost.js | 12 +-- 3 files changed, 91 insertions(+), 14 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index a9c8c602..d87973f1 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -4,13 +4,13 @@ name: "Backend tests" on: [push, pull_request] jobs: - withoutplugins: + withoutpluginsLinux: # run on pushes to any branch # run on PRs from external forks if: | (github.event_name != 'pull_request') || (github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id) - name: without plugins + name: Linux without plugins runs-on: ubuntu-latest steps: @@ -33,13 +33,13 @@ jobs: - name: Run the backend tests run: cd src && npm test - withplugins: + withpluginsLinux: # run on pushes to any branch # run on PRs from external forks if: | (github.event_name != 'pull_request') || (github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id) - name: with Plugins + name: Linux with Plugins runs-on: ubuntu-latest steps: @@ -85,3 +85,87 @@ jobs: - name: Run the backend tests run: cd src && npm test + + withoutpluginsWindows: + # run on pushes to any branch + # run on PRs from external forks + if: | + (github.event_name != 'pull_request') + || (github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id) + name: Windows without plugins + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - uses: actions/setup-node@v2 + with: + node-version: 12 + + - name: Install all dependencies and symlink for ep_etherpad-lite + run: | + cd src + npm ci --no-optional + + - name: Fix up the settings.json + run: | + powershell -Command "(gc settings.json.template) -replace '\"max\": 10', '\"max\": 10000' | Out-File -encoding ASCII settings.json.holder" + powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json" + + - name: Run the backend tests + run: cd src && npm test + + withpluginsWindows: + # run on pushes to any branch + # run on PRs from external forks + if: | + (github.event_name != 'pull_request') + || (github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id) + name: Windows with Plugins + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - uses: actions/setup-node@v2 + with: + node-version: 12 + + - name: Install Etherpad plugins + run: > + npm install + ep_align + ep_author_hover + ep_cursortrace + ep_font_size + ep_hash_auth + ep_headings2 + ep_image_upload + ep_markdown + ep_readonly_guest + ep_set_title_on_pad + ep_spellcheck + ep_subscript_and_superscript + ep_table_of_contents + + # This must be run after installing the plugins, otherwise npm will try to + # hoist common dependencies by removing them from src/node_modules and + # installing them in the top-level node_modules. As of v6.14.10, npm's hoist + # logic appears to be buggy, because it sometimes removes dependencies from + # src/node_modules but fails to add them to the top-level node_modules. Even + # if npm correctly hoists the dependencies, the hoisting seems to confuse + # tools such as `npm outdated`, `npm update`, and some ESLint rules. + - name: Install all dependencies and symlink for ep_etherpad-lite + run: | + cd src + npm ci --no-optional + + - name: Fix up the settings.json + run: | + powershell -Command "(gc settings.json.template) -replace '\"max\": 10', '\"max\": 10000' | Out-File -encoding ASCII settings.json.holder" + powershell -Command "(gc settings.json.holder) -replace '\"points\": 10', '\"points\": 1000' | Out-File -encoding ASCII settings.json" + + - name: Run the backend tests + run: cd src && npm test diff --git a/src/tests/backend/specs/api/importexportGetPost.js b/src/tests/backend/specs/api/importexportGetPost.js index fbd5b944..9261aafa 100644 --- a/src/tests/backend/specs/api/importexportGetPost.js +++ b/src/tests/backend/specs/api/importexportGetPost.js @@ -280,6 +280,8 @@ describe(__filename, function () { return pad; }; + this.timeout(1000); + beforeEach(async function () { await deleteTestPad(); settings.requireAuthorization = true; @@ -292,7 +294,6 @@ describe(__filename, function () { }); it('!authn !exist -> create', async function () { - this.timeout(100); await agent.post(`/p/${testPadIdEnc}/import`) .attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'}) .expect(200); @@ -302,7 +303,6 @@ describe(__filename, function () { }); it('!authn exist -> replace', async function () { - this.timeout(100); const pad = await createTestPad('before import'); await agent.post(`/p/${testPadIdEnc}/import`) .attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'}) @@ -312,7 +312,6 @@ describe(__filename, function () { }); it('authn anonymous !exist -> fail', async function () { - this.timeout(100); settings.requireAuthentication = true; await agent.post(`/p/${testPadIdEnc}/import`) .attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'}) @@ -321,7 +320,6 @@ describe(__filename, function () { }); it('authn anonymous exist -> fail', async function () { - this.timeout(100); settings.requireAuthentication = true; const pad = await createTestPad('before import\n'); await agent.post(`/p/${testPadIdEnc}/import`) @@ -331,7 +329,6 @@ describe(__filename, function () { }); it('authn user create !exist -> create', async function () { - this.timeout(100); settings.requireAuthentication = true; await agent.post(`/p/${testPadIdEnc}/import`) .auth('user', 'user-password') @@ -343,7 +340,6 @@ describe(__filename, function () { }); it('authn user modify !exist -> fail', async function () { - this.timeout(100); settings.requireAuthentication = true; authorize = () => 'modify'; await agent.post(`/p/${testPadIdEnc}/import`) @@ -354,7 +350,6 @@ describe(__filename, function () { }); it('authn user readonly !exist -> fail', async function () { - this.timeout(100); settings.requireAuthentication = true; authorize = () => 'readOnly'; await agent.post(`/p/${testPadIdEnc}/import`) @@ -365,7 +360,6 @@ describe(__filename, function () { }); it('authn user create exist -> replace', async function () { - this.timeout(100); settings.requireAuthentication = true; const pad = await createTestPad('before import\n'); await agent.post(`/p/${testPadIdEnc}/import`) @@ -376,7 +370,6 @@ describe(__filename, function () { }); it('authn user modify exist -> replace', async function () { - this.timeout(100); settings.requireAuthentication = true; authorize = () => 'modify'; const pad = await createTestPad('before import\n'); @@ -388,7 +381,6 @@ describe(__filename, function () { }); it('authn user readonly exist -> fail', async function () { - this.timeout(100); const pad = await createTestPad('before import\n'); settings.requireAuthentication = true; authorize = () => 'readOnly'; From 449b03d7e8b4509d65896cdc666229816dc86578 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Wed, 17 Feb 2021 21:54:13 +0000 Subject: [PATCH 002/115] fix: upgrade unorm from 1.4.1 to 1.6.0 Snyk has created this PR to upgrade unorm from 1.4.1 to 1.6.0. See this package in npm: https://www.npmjs.com/package/unorm See this project in Snyk: https://app.snyk.io/org/johnmclear/project/d9a12bfb-7ccd-443f-9e22-f30d339cc8c5?utm_source=github&utm_medium=upgrade-pr --- src/package-lock.json | 6 +++--- src/package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/package-lock.json b/src/package-lock.json index a54e57b4..f5872831 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -8449,9 +8449,9 @@ } }, "unorm": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.4.1.tgz", - "integrity": "sha1-NkIA1fE2RsqLzURJAnEzVhR5IwA=" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", + "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==" }, "unpipe": { "version": "1.0.0", diff --git a/src/package.json b/src/package.json index 757525c2..adef338f 100644 --- a/src/package.json +++ b/src/package.json @@ -71,7 +71,7 @@ "tinycon": "0.6.8", "ueberdb2": "^1.2.5", "underscore": "1.12.0", - "unorm": "1.4.1", + "unorm": "1.6.0", "wtfnode": "^0.8.4" }, "bin": { From 4c6cb53d18863b67f70a299be39b2f7d84f3b79d Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 18 Feb 2021 02:05:55 -0500 Subject: [PATCH 003/115] server: Improve log messages when exiting --- src/node/server.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/node/server.js b/src/node/server.js index 0f0b0147..6986e527 100755 --- a/src/node/server.js +++ b/src/node/server.js @@ -111,10 +111,16 @@ exports.start = async () => { stats.gauge('memoryUsage', () => process.memoryUsage().rss); stats.gauge('memoryUsageHeap', () => process.memoryUsage().heapUsed); - process.on('uncaughtException', (err) => exports.exit(err)); + process.on('uncaughtException', (err) => { + logger.debug(`uncaught exception: ${err.stack || err}`); + exports.exit(err); + }); // As of v14, Node.js does not exit when there is an unhandled Promise rejection. Convert an // unhandled rejection into an uncaught exception, which does cause Node.js to exit. - process.on('unhandledRejection', (err) => { throw err; }); + process.on('unhandledRejection', (err) => { + logger.debug(`unhandled rejection: ${err.stack || err}`); + throw err; + }); for (const signal of ['SIGINT', 'SIGTERM']) { // Forcibly remove other signal listeners to prevent them from terminating node before we are @@ -219,6 +225,7 @@ exports.exit = async (err = null) => { process.exit(1); } } + if (!exitCalled) logger.info('Exiting...'); exitCalled = true; switch (state) { case State.STARTING: @@ -241,7 +248,6 @@ exports.exit = async (err = null) => { default: throw new Error(`unknown State: ${state.toString()}`); } - logger.info('Exiting...'); exitGate = new Gate(); state = State.EXITING; exitGate.resolve(); From 84c1d74f8b07e7380efddbcb72280f10c1fdb6e5 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 18 Feb 2021 02:08:25 -0500 Subject: [PATCH 004/115] server: Fix Gate constructor The ECMAScript spec for `.then()` requires Promise subclass constructors to take an executor. --- src/node/server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node/server.js b/src/node/server.js index 6986e527..e890a020 100755 --- a/src/node/server.js +++ b/src/node/server.js @@ -65,9 +65,9 @@ const State = { let state = State.INITIAL; class Gate extends Promise { - constructor() { + constructor(executor = null) { let res; - super((resolve) => { res = resolve; }); + super((resolve, reject) => { res = resolve; if (executor != null) executor(resolve, reject); }); this.resolve = res; } } From f8687884178fd5ac511b934421ace0fc20eb9a58 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 10 Feb 2021 01:12:43 -0500 Subject: [PATCH 005/115] Remove unnecessary `path.normalize()` calls `path.join()` already normalizes. --- src/node/hooks/express/tests.js | 5 ++--- src/node/utils/AbsolutePaths.js | 2 +- src/node/utils/Minify.js | 4 ++-- src/node/utils/Settings.js | 2 +- src/node/utils/caching_middleware.js | 2 +- src/static/js/pluginfw/plugins.js | 4 ++-- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/node/hooks/express/tests.js b/src/node/hooks/express/tests.js index 825c495c..8c2e8edb 100644 --- a/src/node/hooks/express/tests.js +++ b/src/node/hooks/express/tests.js @@ -29,8 +29,7 @@ exports.expressCreateServer = (hookName, args, cb) => { res.end(`var specs_list = ${JSON.stringify(files)};\n`); }); - // path.join seems to normalize by default, but we'll just be explicit - const rootTestFolder = path.normalize(path.join(npm.root, '../tests/frontend/')); + const rootTestFolder = path.join(npm.root, '../tests/frontend/'); const url2FilePath = (url) => { let subPath = url.substr('/tests/frontend'.length); @@ -39,7 +38,7 @@ exports.expressCreateServer = (hookName, args, cb) => { } subPath = subPath.split('?')[0]; - let filePath = path.normalize(path.join(rootTestFolder, subPath)); + let filePath = path.join(rootTestFolder, subPath); // make sure we jail the paths to the test folder, otherwise serve index if (filePath.indexOf(rootTestFolder) !== 0) { diff --git a/src/node/utils/AbsolutePaths.js b/src/node/utils/AbsolutePaths.js index 5b364ed8..8ca17ba1 100644 --- a/src/node/utils/AbsolutePaths.js +++ b/src/node/utils/AbsolutePaths.js @@ -132,7 +132,7 @@ exports.makeAbsolute = (somePath) => { return somePath; } - const rewrittenPath = path.normalize(path.join(exports.findEtherpadRoot(), somePath)); + const rewrittenPath = path.join(exports.findEtherpadRoot(), somePath); absPathLogger.debug(`Relative path "${somePath}" can be rewritten to "${rewrittenPath}"`); return rewrittenPath; diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.js index 2a7ee2bf..c9d26340 100644 --- a/src/node/utils/Minify.js +++ b/src/node/utils/Minify.js @@ -101,7 +101,7 @@ const minify = async (req, res) => { let filename = req.params.filename; // No relative paths, especially if they may go up the file hierarchy. - filename = path.normalize(path.join(ROOT_DIR, filename)); + filename = path.join(ROOT_DIR, filename); filename = filename.replace(/\.\./g, ''); if (filename.indexOf(ROOT_DIR) === 0) { @@ -198,7 +198,7 @@ const getAceFile = async () => { await Promise.all(filenames.map(async (filename) => { // Hostname "invalid.invalid" is a dummy value to allow parsing as a URI. const baseURI = 'http://invalid.invalid'; - let resourceURI = baseURI + path.normalize(path.join('/static/', filename)); + let resourceURI = baseURI + path.join('/static/', filename); resourceURI = resourceURI.replace(/\\/g, '/'); // Windows (safe generally?) const [status, , body] = await requestURI(resourceURI, 'GET', {}); diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index 0da117a5..202e80c6 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.js @@ -719,7 +719,7 @@ exports.reloadSettings = () => { } // informative variable, just for the log messages - let skinPath = path.normalize(path.join(skinBasePath, exports.skinName)); + let skinPath = path.join(skinBasePath, exports.skinName); // what if someone sets skinName == ".." or "."? We catch him! if (absolutePaths.isSubdir(skinBasePath, skinPath) === false) { diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.js index ff25513c..23122342 100644 --- a/src/node/utils/caching_middleware.js +++ b/src/node/utils/caching_middleware.js @@ -44,7 +44,7 @@ try { _crypto = undefined; } -let CACHE_DIR = path.normalize(path.join(settings.root, 'var/')); +let CACHE_DIR = path.join(settings.root, 'var/'); CACHE_DIR = existsSync(CACHE_DIR) ? CACHE_DIR : undefined; const responseCache = {}; diff --git a/src/static/js/pluginfw/plugins.js b/src/static/js/pluginfw/plugins.js index 8323cfc7..48ae4c92 100644 --- a/src/static/js/pluginfw/plugins.js +++ b/src/static/js/pluginfw/plugins.js @@ -32,7 +32,7 @@ exports.formatHooks = (hookSetName) => { const callInit = async () => { await Promise.all(Object.keys(defs.plugins).map(async (pluginName) => { const plugin = defs.plugins[pluginName]; - const epInit = path.normalize(path.join(plugin.package.path, '.ep_initialized')); + const epInit = path.join(plugin.package.path, '.ep_initialized'); try { await fs.stat(epInit); } catch (err) { @@ -48,7 +48,7 @@ exports.pathNormalization = (part, hookFnName, hookName) => { 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 fileName = path.normalize(path.join(packageDir, moduleName)); + const fileName = path.join(packageDir, moduleName); return `${fileName}:${functionName}`; }; From a45e85a730885fb74b4e8d18b32562cc904da04d Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 10 Feb 2021 01:21:29 -0500 Subject: [PATCH 006/115] Use `settings.root` to anchor pathnames --- src/node/hooks/express/tests.js | 3 +-- src/node/hooks/i18n.js | 9 +++++---- src/node/utils/Minify.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/node/hooks/express/tests.js b/src/node/hooks/express/tests.js index 8c2e8edb..c533ae8c 100644 --- a/src/node/hooks/express/tests.js +++ b/src/node/hooks/express/tests.js @@ -1,7 +1,6 @@ 'use strict'; const path = require('path'); -const npm = require('npm'); const fs = require('fs'); const util = require('util'); const settings = require('../../utils/Settings'); @@ -29,7 +28,7 @@ exports.expressCreateServer = (hookName, args, cb) => { res.end(`var specs_list = ${JSON.stringify(files)};\n`); }); - const rootTestFolder = path.join(npm.root, '../tests/frontend/'); + const rootTestFolder = path.join(settings.root, 'src/tests/frontend/'); const url2FilePath = (url) => { let subPath = url.substr('/tests/frontend'.length); diff --git a/src/node/hooks/i18n.js b/src/node/hooks/i18n.js index 31848b48..81944a07 100644 --- a/src/node/hooks/i18n.js +++ b/src/node/hooks/i18n.js @@ -4,8 +4,7 @@ const languages = require('languages4translatewiki'); const fs = require('fs'); const path = require('path'); const _ = require('underscore'); -const npm = require('npm'); -const plugins = require('../../static/js/pluginfw/plugin_defs.js').plugins; +const pluginDefs = require('../../static/js/pluginfw/plugin_defs.js'); const existsSync = require('../utils/path_exists'); const settings = require('../utils/Settings'); @@ -38,10 +37,12 @@ const getAllLocales = () => { }; // add core supported languages first - extractLangs(`${npm.root}/ep_etherpad-lite/locales`); + extractLangs(path.join(settings.root, 'src/locales')); // add plugins languages (if any) - for (const pluginName in plugins) extractLangs(path.join(npm.root, pluginName, 'locales')); + for (const {package: {path: pluginPath}} of Object.values(pluginDefs.plugins)) { + extractLangs(path.join(pluginPath, 'locales')); + } // Build a locale index (merge all locale data other than user-supplied overrides) const locales = {}; diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.js index c9d26340..bf25787a 100644 --- a/src/node/utils/Minify.js +++ b/src/node/utils/Minify.js @@ -32,7 +32,7 @@ const log4js = require('log4js'); const logger = log4js.getLogger('Minify'); -const ROOT_DIR = path.normalize(`${__dirname}/../../static/`); +const ROOT_DIR = path.join(settings.root, 'src/static/'); const threadsPool = new Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2); From a8479e4a0ee86a33e93d9a969bc366476026e9cf Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 10 Feb 2021 01:28:10 -0500 Subject: [PATCH 007/115] lint: Fix some ESLint errors in pluginfw --- src/static/js/pluginfw/installer.js | 8 ++++---- src/static/js/pluginfw/plugins.js | 3 --- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index ae5c06e1..a0f60d82 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -21,15 +21,15 @@ const onAllTasksFinished = () => { let tasks = 0; -function wrapTaskCb(cb) { +const wrapTaskCb = (cb) => { tasks++; - return function (...args) { - cb && cb.apply(this, args); + return (...args) => { + cb && cb(...args); tasks--; if (tasks === 0) onAllTasksFinished(); }; -} +}; exports.uninstall = async (pluginName, cb = null) => { cb = wrapTaskCb(cb); diff --git a/src/static/js/pluginfw/plugins.js b/src/static/js/pluginfw/plugins.js index 48ae4c92..7a01d424 100644 --- a/src/static/js/pluginfw/plugins.js +++ b/src/static/js/pluginfw/plugins.js @@ -5,9 +5,6 @@ const hooks = require('./hooks'); const path = require('path'); const runNpm = require('../../../node/utils/run_npm'); const tsort = require('./tsort'); -const util = require('util'); -const settings = require('../../../node/utils/Settings'); - const pluginUtils = require('./shared'); const defs = require('./plugin_defs'); From 4253a2ea8f595090d8b9cae72c53cedeb4885934 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 18 Feb 2021 02:36:50 -0500 Subject: [PATCH 008/115] plugins: Move hook call and plugin update out of try block Exceptions thrown by these function calls are serious and should crash Etherpad. --- src/static/js/pluginfw/installer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index a0f60d82..4d2e223d 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -36,12 +36,12 @@ exports.uninstall = async (pluginName, cb = null) => { try { await loadNpm(); await util.promisify(npm.commands.uninstall)([pluginName]); - await hooks.aCallAll('pluginUninstall', {pluginName}); - await plugins.update(); } catch (err) { cb(err || new Error(err)); throw err; } + await hooks.aCallAll('pluginUninstall', {pluginName}); + await plugins.update(); cb(null); }; @@ -50,12 +50,12 @@ exports.install = async (pluginName, cb = null) => { try { await loadNpm(); await util.promisify(npm.commands.install)([`${pluginName}@latest`]); - await hooks.aCallAll('pluginInstall', {pluginName}); - await plugins.update(); } catch (err) { cb(err || new Error(err)); throw err; } + await hooks.aCallAll('pluginInstall', {pluginName}); + await plugins.update(); cb(null); }; From dcf78913163c110e13942a0c8590ddb479100925 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 10 Feb 2021 01:34:38 -0500 Subject: [PATCH 009/115] plugins: Improve logging of plugin events This will make it easier to troubleshoot plugin and npm issues. --- src/node/utils/run_npm.js | 7 +------ src/static/js/pluginfw/installer.js | 12 ++++++++++-- src/static/js/pluginfw/plugins.js | 19 +++++++++++++------ 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/node/utils/run_npm.js b/src/node/utils/run_npm.js index ff2b7b0d..90d8ade9 100644 --- a/src/node/utils/run_npm.js +++ b/src/node/utils/run_npm.js @@ -21,13 +21,8 @@ const stderrLogger = (line) => npmLogger.error(line); */ module.exports = exports = (args, opts = {}) => { const cmd = ['npm', ...args]; - logger.info(`Executing command: ${cmd.join(' ')}`); - const p = runCmd(cmd, {stdoutLogger, stderrLogger, ...opts}); - p.then( - () => logger.info(`Successfully ran command: ${cmd.join(' ')}`), - () => logger.error(`npm command failed: ${cmd.join(' ')}`)); // MUST return the original Promise returned from runCmd so that the caller can access stdout. - return p; + return runCmd(cmd, {stdoutLogger, stderrLogger, ...opts}); }; // Log the version of npm at startup. diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index 4d2e223d..5f2e6ea1 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -7,6 +7,8 @@ const npm = require('npm'); const request = require('request'); const util = require('util'); +const logger = log4js.getLogger('plugins'); + let npmIsLoaded = false; const loadNpm = async () => { if (npmIsLoaded) return; @@ -33,13 +35,16 @@ const wrapTaskCb = (cb) => { exports.uninstall = async (pluginName, cb = null) => { cb = wrapTaskCb(cb); + logger.info(`Uninstalling plugin ${pluginName}...`); try { await loadNpm(); await util.promisify(npm.commands.uninstall)([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(); cb(null); @@ -47,13 +52,16 @@ exports.uninstall = async (pluginName, cb = null) => { exports.install = async (pluginName, cb = null) => { cb = wrapTaskCb(cb); + logger.info(`Installing plugin ${pluginName}...`); try { await loadNpm(); await util.promisify(npm.commands.install)([`${pluginName}@latest`]); } 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(); cb(null); @@ -77,7 +85,7 @@ exports.getAvailablePlugins = (maxCacheAge) => { try { plugins = JSON.parse(plugins); } catch (err) { - console.error('error parsing plugins.json:', err); + logger.error(`error parsing plugins.json: ${err.stack || err}`); plugins = []; } @@ -107,7 +115,7 @@ exports.search = (searchTerm, maxCacheAge) => exports.getAvailablePlugins(maxCac !~results[pluginName].description.toLowerCase().indexOf(searchTerm)) ) { if (typeof results[pluginName].description === 'undefined') { - console.debug('plugin without Description: %s', results[pluginName].name); + logger.debug(`plugin without Description: ${results[pluginName].name}`); } continue; diff --git a/src/static/js/pluginfw/plugins.js b/src/static/js/pluginfw/plugins.js index 7a01d424..4cf01fa2 100644 --- a/src/static/js/pluginfw/plugins.js +++ b/src/static/js/pluginfw/plugins.js @@ -2,12 +2,15 @@ const fs = require('fs').promises; const hooks = require('./hooks'); +const log4js = require('log4js'); const path = require('path'); const runNpm = require('../../../node/utils/run_npm'); const tsort = require('./tsort'); const pluginUtils = require('./shared'); const defs = require('./plugin_defs'); +const logger = log4js.getLogger('plugins'); + exports.prefix = 'ep_'; exports.formatPlugins = () => Object.keys(defs.plugins).join(', '); @@ -55,8 +58,11 @@ exports.update = async () => { const plugins = {}; // Load plugin metadata ep.json - await Promise.all(Object.keys(packages).map( - async (pluginName) => await loadPlugin(packages, pluginName, plugins, parts))); + await Promise.all(Object.keys(packages).map(async (pluginName) => { + logger.info(`Loading plugin ${pluginName}...`); + await loadPlugin(packages, pluginName, plugins, parts); + })); + logger.info(`Loaded ${Object.keys(packages).length} plugins`); defs.plugins = plugins; defs.parts = sortParts(parts); @@ -66,6 +72,7 @@ exports.update = async () => { }; exports.getPackages = async () => { + logger.info('Running npm to get a list of installed plugins...'); // Note: Do not pass `--prod` because it does not work if there is no package.json. const np = runNpm(['ls', '--long', '--json', '--depth=0'], { stdoutLogger: null, // We want to capture stdout, so don't attempt to log it. @@ -104,11 +111,11 @@ const loadPlugin = async (packages, pluginName, plugins, parts) => { part.full_name = `${pluginName}/${part.name}`; parts[part.full_name] = part; } - } catch (ex) { - console.error(`Unable to parse plugin definition file ${pluginPath}: ${ex.toString()}`); + } catch (err) { + logger.error(`Unable to parse plugin definition file ${pluginPath}: ${err.stack || err}`); } - } catch (er) { - console.error(`Unable to load plugin definition file ${pluginPath}`); + } catch (err) { + logger.error(`Unable to load plugin definition file ${pluginPath}: ${err.stack || err}`); } }; From 689a75b381e6fe4fc0e9b969bd5ae38f0adc1276 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 10 Feb 2021 03:04:28 -0500 Subject: [PATCH 010/115] plugins: Pass `--no-production` instead of setting `NODE_ENV=development` --- src/static/js/pluginfw/plugins.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/static/js/pluginfw/plugins.js b/src/static/js/pluginfw/plugins.js index 4cf01fa2..a65b4f08 100644 --- a/src/static/js/pluginfw/plugins.js +++ b/src/static/js/pluginfw/plugins.js @@ -73,14 +73,13 @@ exports.update = async () => { exports.getPackages = async () => { logger.info('Running npm to get a list of installed plugins...'); - // Note: Do not pass `--prod` because it does not work if there is no package.json. - const np = runNpm(['ls', '--long', '--json', '--depth=0'], { + // Notes: + // * Do not pass `--prod` otherwise `npm ls` will fail if there is no `package.json`. + // * The `--no-production` flag is required (or the `NODE_ENV` environment variable must be + // 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 np = runNpm(['ls', '--long', '--json', '--depth=0', '--no-production'], { stdoutLogger: null, // We want to capture stdout, so don't attempt to log it. - env: { - ...process.env, - // NODE_ENV must be set to development for `npm ls` to show files without a package.json. - NODE_ENV: 'development', - }, }); const chunks = []; await Promise.all([ From 426c02512723a42300964f0dee2e252018fd98f3 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 17 Feb 2021 18:22:01 -0500 Subject: [PATCH 011/115] run_cmd: Log to Etherpad logs by default --- src/node/utils/run_cmd.js | 11 ++++++++--- src/node/utils/run_npm.js | 15 +++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/node/utils/run_cmd.js b/src/node/utils/run_cmd.js index 4b30af1f..2af5381a 100644 --- a/src/node/utils/run_cmd.js +++ b/src/node/utils/run_cmd.js @@ -38,8 +38,9 @@ const logLines = (readable, logLineFn) => { * @param opts Optional options that will be passed to `child_process.spawn()` with two extensions: * - `stdoutLogger`: Callback that is called each time a line of text is written to stdout (utf8 * is assumed). The line (without trailing newline) is passed as the only argument. If null, - * stdout is not logged. If unset, defaults to no-op. Ignored if stdout is not a pipe. - * - `stderrLogger`: Like `stdoutLogger` but for stderr. + * stdout is not logged. If unset, defaults to logging to the Etherpad logs at log level INFO. + * Ignored if stdout is not a pipe. + * - `stderrLogger`: Like `stdoutLogger` but for stderr and log level ERROR. * * @returns A Promise with `stdout`, `stderr`, and `child` properties containing the stdout stream, * stderr stream, and ChildProcess objects, respectively. @@ -47,7 +48,11 @@ const logLines = (readable, logLineFn) => { module.exports = exports = (args, opts = {}) => { logger.debug(`Executing command: ${args.join(' ')}`); - const {stdoutLogger = () => {}, stderrLogger = () => {}} = opts; + const cmdLogger = log4js.getLogger(`runCmd|${args[0]}`); + const { + stdoutLogger = (line) => cmdLogger.info(line), + stderrLogger = (line) => cmdLogger.error(line), + } = opts; // Avoid confusing child_process.spawn() with our extensions. opts = {...opts}; // Make a copy to avoid mutating the caller's copy. delete opts.stdoutLogger; diff --git a/src/node/utils/run_npm.js b/src/node/utils/run_npm.js index 90d8ade9..869e1087 100644 --- a/src/node/utils/run_npm.js +++ b/src/node/utils/run_npm.js @@ -4,17 +4,12 @@ const log4js = require('log4js'); const runCmd = require('./run_cmd'); const logger = log4js.getLogger('runNpm'); -const npmLogger = log4js.getLogger('npm'); - -const stdoutLogger = (line) => npmLogger.info(line); -const stderrLogger = (line) => npmLogger.error(line); /** - * Wrapper around `runCmd()` that logs output to an npm logger by default. + * Wrapper around `runCmd()` to make it easier to run npm. * * @param args Command-line arguments to pass to npm. - * @param opts See the documentation for `runCmd()`. The `stdoutLogger` and `stderrLogger` options - * default to a log4js logger. + * @param opts See the documentation for `runCmd()`. * * @returns A Promise with additional `stdout`, `stderr`, and `child` properties. See the * documentation for `runCmd()`. @@ -22,7 +17,7 @@ const stderrLogger = (line) => npmLogger.error(line); module.exports = exports = (args, opts = {}) => { const cmd = ['npm', ...args]; // MUST return the original Promise returned from runCmd so that the caller can access stdout. - return runCmd(cmd, {stdoutLogger, stderrLogger, ...opts}); + return runCmd(cmd, opts); }; // Log the version of npm at startup. @@ -30,11 +25,11 @@ let loggedVersion = false; (async () => { if (loggedVersion) return; loggedVersion = true; - const p = runCmd(['npm', '--version'], {stdoutLogger: null, stderrLogger}); + const p = runCmd(['npm', '--version'], {stdoutLogger: null}); const chunks = []; await Promise.all([ (async () => { for await (const chunk of p.stdout) chunks.push(chunk); })(), - p, // Await in parallel to avoid unhandled rejection if np rejects during chunk read. + p, // Await in parallel to avoid unhandled rejection if p rejects during chunk read. ]); const version = Buffer.concat(chunks).toString().replace(/\n+$/g, ''); logger.info(`npm --version: ${version}`); From d8bb5aa0091f107804904fc8bebab64c0078ba3f Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 17 Feb 2021 19:54:49 -0500 Subject: [PATCH 012/115] plugins: Eliminate unnecessary `run_npm.js` I had anticipated more shared logic than we actually need (the abstraction in `run_npm.js` is YAGNI). --- src/node/utils/run_npm.js | 39 ------------------------------- src/static/js/pluginfw/plugins.js | 26 +++++++++++++++++---- 2 files changed, 22 insertions(+), 43 deletions(-) delete mode 100644 src/node/utils/run_npm.js diff --git a/src/node/utils/run_npm.js b/src/node/utils/run_npm.js deleted file mode 100644 index 869e1087..00000000 --- a/src/node/utils/run_npm.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -const log4js = require('log4js'); -const runCmd = require('./run_cmd'); - -const logger = log4js.getLogger('runNpm'); - -/** - * Wrapper around `runCmd()` to make it easier to run npm. - * - * @param args Command-line arguments to pass to npm. - * @param opts See the documentation for `runCmd()`. - * - * @returns A Promise with additional `stdout`, `stderr`, and `child` properties. See the - * documentation for `runCmd()`. - */ -module.exports = exports = (args, opts = {}) => { - const cmd = ['npm', ...args]; - // MUST return the original Promise returned from runCmd so that the caller can access stdout. - return runCmd(cmd, opts); -}; - -// Log the version of npm at startup. -let loggedVersion = false; -(async () => { - if (loggedVersion) return; - loggedVersion = true; - const p = runCmd(['npm', '--version'], {stdoutLogger: null}); - const chunks = []; - await Promise.all([ - (async () => { for await (const chunk of p.stdout) chunks.push(chunk); })(), - p, // Await in parallel to avoid unhandled rejection if p rejects during chunk read. - ]); - const version = Buffer.concat(chunks).toString().replace(/\n+$/g, ''); - logger.info(`npm --version: ${version}`); -})().catch((err) => { - logger.error(`Failed to get npm version: ${err.stack}`); - // This isn't a fatal error so don't re-throw. -}); diff --git a/src/static/js/pluginfw/plugins.js b/src/static/js/pluginfw/plugins.js index a65b4f08..0860a65b 100644 --- a/src/static/js/pluginfw/plugins.js +++ b/src/static/js/pluginfw/plugins.js @@ -4,13 +4,31 @@ const fs = require('fs').promises; const hooks = require('./hooks'); const log4js = require('log4js'); const path = require('path'); -const runNpm = require('../../../node/utils/run_npm'); +const runCmd = require('../../../node/utils/run_cmd'); const tsort = require('./tsort'); const pluginUtils = require('./shared'); const defs = require('./plugin_defs'); const logger = log4js.getLogger('plugins'); +// Log the version of npm at startup. +let loggedVersion = false; +(async () => { + if (loggedVersion) return; + loggedVersion = true; + const p = runCmd(['npm', '--version'], {stdoutLogger: null}); + const chunks = []; + await Promise.all([ + (async () => { for await (const chunk of p.stdout) chunks.push(chunk); })(), + p, // Await in parallel to avoid unhandled rejection if p rejects during chunk read. + ]); + const version = Buffer.concat(chunks).toString().replace(/\n+$/g, ''); + logger.info(`npm --version: ${version}`); +})().catch((err) => { + logger.error(`Failed to get npm version: ${err.stack || err}`); + // This isn't a fatal error so don't re-throw. +}); + exports.prefix = 'ep_'; exports.formatPlugins = () => Object.keys(defs.plugins).join(', '); @@ -78,13 +96,13 @@ exports.getPackages = async () => { // * The `--no-production` flag is required (or the `NODE_ENV` environment variable must be // 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 np = runNpm(['ls', '--long', '--json', '--depth=0', '--no-production'], { + const p = runCmd(['npm', 'ls', '--long', '--json', '--depth=0', '--no-production'], { stdoutLogger: null, // We want to capture stdout, so don't attempt to log it. }); const chunks = []; await Promise.all([ - (async () => { for await (const chunk of np.stdout) chunks.push(chunk); })(), - np, // Await in parallel to avoid unhandled rejection if np rejects during chunk read. + (async () => { for await (const chunk of p.stdout) chunks.push(chunk); })(), + p, // Await in parallel to avoid unhandled rejection if p rejects during chunk read. ]); const {dependencies = {}} = JSON.parse(Buffer.concat(chunks).toString()); await Promise.all(Object.entries(dependencies).map(async ([pkg, info]) => { From 1cfbf88f7c62d66a6c686757ae5e405126d6768e Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 18 Feb 2021 01:26:16 -0500 Subject: [PATCH 013/115] run_cmd: Enhance with ability to return stdout as string --- src/node/utils/run_cmd.js | 158 +++++++++++++++++++++--------- src/static/js/pluginfw/plugins.js | 34 ++----- 2 files changed, 119 insertions(+), 73 deletions(-) diff --git a/src/node/utils/run_cmd.js b/src/node/utils/run_cmd.js index 2af5381a..f4bc59bd 100644 --- a/src/node/utils/run_cmd.js +++ b/src/node/utils/run_cmd.js @@ -28,70 +28,130 @@ const logLines = (readable, logLineFn) => { }; /** - * Similar to `util.promisify(child_rocess.exec)`, except: - * - `cwd` defaults to the Etherpad root directory. - * - PATH is prefixed with src/node_modules/.bin so that utilities from installed dependencies - * (e.g., npm) are preferred over system utilities. - * - Output is passed to logger callback functions by default. See below for details. + * Runs a command, logging its output to Etherpad's logs by default. + * + * Examples: + * + * Just run a command, logging stdout and stder to Etherpad's logs: + * await runCmd(['ls', '-l']); + * + * Capture just stdout as a string: + * const stdout = await runCmd(['ls', '-l'], {stdio: [null, 'string']}); + * + * Capture both stdout and stderr as strings: + * const p = runCmd(['ls', '-l'], {stdio: 'string'}); + * const stdout = await p; // Or: await p.stdout; + * const stderr = await p.stderr; + * + * Call a callback with each line of stdout: + * await runCmd(['ls', '-l'], {stdio: [null, (line) => console.log(line)]}); * * @param args Array of command-line arguments, where `args[0]` is the command to run. - * @param opts Optional options that will be passed to `child_process.spawn()` with two extensions: - * - `stdoutLogger`: Callback that is called each time a line of text is written to stdout (utf8 - * is assumed). The line (without trailing newline) is passed as the only argument. If null, - * stdout is not logged. If unset, defaults to logging to the Etherpad logs at log level INFO. - * Ignored if stdout is not a pipe. - * - `stderrLogger`: Like `stdoutLogger` but for stderr and log level ERROR. + * @param opts As with `child_process.spawn()`, except: + * - `cwd` defaults to the Etherpad root directory. + * - `env.PATH` is prefixed with `src/node_modules/.bin:node_modules/.bin` so that utilities from + * installed dependencies (e.g., npm) are preferred over system utilities. + * - By default stdout and stderr are logged to the Etherpad log at log levels INFO and ERROR. + * To pipe without logging you must explicitly use 'pipe' for opts.stdio. + * - opts.stdio[1] and opts.stdio[2] can be functions that will be called each time a line (utf8) + * is written to stdout or stderr. The line (without its trailing newline, if present) will be + * passed as the only argument, and the return value is ignored. opts.stdio = fn is equivalent + * to opts.stdio = [null, fn, fn]. + * - opts.stdio[1] and opts.stdio[2] can be 'string', which will cause output to be collected, + * decoded as utf8, and returned (see below). opts.stdio = 'string' is equivalent to + * opts.stdio = [null, 'string', 'string']. * - * @returns A Promise with `stdout`, `stderr`, and `child` properties containing the stdout stream, - * stderr stream, and ChildProcess objects, respectively. + * @returns A Promise that resolves when the command exits. The Promise resolves to the complete + * stdout if opts.stdio[1] is 'string', otherwise it resolves to undefined. The returned Promise is + * augmented with these additional properties: + * - `stdout`: If opts.stdio[1] is 'pipe', the stdout stream object. If opts.stdio[1] is 'string', + * a Promise that will resolve to the complete stdout (utf8 decoded) when the command exits. + * - `stderr`: Similar to `stdout` but for stderr. + * - `child`: The ChildProcess object. */ module.exports = exports = (args, opts = {}) => { logger.debug(`Executing command: ${args.join(' ')}`); - const cmdLogger = log4js.getLogger(`runCmd|${args[0]}`); - const { - stdoutLogger = (line) => cmdLogger.info(line), - stderrLogger = (line) => cmdLogger.error(line), - } = opts; - // Avoid confusing child_process.spawn() with our extensions. - opts = {...opts}; // Make a copy to avoid mutating the caller's copy. - delete opts.stdoutLogger; - delete opts.stderrLogger; + opts = {cwd: settings.root, ...opts}; + logger.debug(`cwd: ${opts.cwd}`); + // Log stdout and stderr by default. + const stdio = + Array.isArray(opts.stdio) ? opts.stdio.slice() // Copy to avoid mutating the caller's array. + : typeof opts.stdio === 'function' ? [null, opts.stdio, opts.stdio] + : opts.stdio === 'string' ? [null, 'string', 'string'] + : Array(3).fill(opts.stdio); + const cmdLogger = log4js.getLogger(`runCmd|${args[0]}`); + if (stdio[1] == null) stdio[1] = (line) => cmdLogger.info(line); + if (stdio[2] == null) stdio[2] = (line) => cmdLogger.error(line); + const stdioLoggers = []; + const stdioSaveString = []; + for (const fd of [1, 2]) { + if (typeof stdio[fd] === 'function') { + stdioLoggers[fd] = stdio[fd]; + stdio[fd] = 'pipe'; + } else if (stdio[fd] === 'string') { + stdioSaveString[fd] = true; + stdio[fd] = 'pipe'; + } + } + opts.stdio = stdio; + + // On Windows the PATH environment var might be spelled "Path". + const pathVarName = + Object.keys(process.env).filter((k) => k.toUpperCase() === 'PATH')[0] || 'PATH'; // Set PATH so that utilities from installed dependencies (e.g., npm) are preferred over system // (global) utilities. - let {env = process.env} = opts; - env = {...env}; // Copy to avoid modifying process.env. - // On Windows the PATH environment var might be spelled "Path". - const pathVarName = Object.keys(env).filter((k) => k.toUpperCase() === 'PATH')[0] || 'PATH'; - env[pathVarName] = [ - path.join(settings.root, 'src', 'node_modules', '.bin'), - path.join(settings.root, 'node_modules', '.bin'), - ...(env[pathVarName] ? env[pathVarName].split(path.delimiter) : []), - ].join(path.delimiter); + const {env = process.env} = opts; + const {[pathVarName]: PATH} = env; + opts.env = { + ...env, // Copy env to avoid modifying process.env or the caller's supplied env. + [pathVarName]: [ + path.join(settings.root, 'src', 'node_modules', '.bin'), + path.join(settings.root, 'node_modules', '.bin'), + ...(PATH ? PATH.split(path.delimiter) : []), + ].join(path.delimiter), + }; logger.debug(`${pathVarName}=${env[pathVarName]}`); // Create an error object to use in case the process fails. This is done here rather than in the // process's `exit` handler so that we get a useful stack trace. - const procFailedErr = new Error(`Command exited non-zero: ${args.join(' ')}`); + const procFailedErr = new Error(); - const proc = spawn(args[0], args.slice(1), {cwd: settings.root, ...opts, env}); - if (proc.stdout != null && stdoutLogger != null) logLines(proc.stdout, stdoutLogger); - if (proc.stderr != null && stderrLogger != null) logLines(proc.stderr, stderrLogger); - const p = new Promise((resolve, reject) => { - proc.on('exit', (code, signal) => { - if (code !== 0) { - logger.debug(procFailedErr.stack); - procFailedErr.code = code; - procFailedErr.signal = signal; - return reject(procFailedErr); - } - logger.debug(`Command returned successfully: ${args.join(' ')}`); - resolve(); - }); - }); - p.stdout = proc.stdout; - p.stderr = proc.stderr; + const proc = spawn(args[0], args.slice(1), opts); + const streams = [undefined, proc.stdout, proc.stderr]; + + let px; + const p = new Promise((resolve, reject) => { px = {resolve, reject}; }); + [, p.stdout, p.stderr] = streams; p.child = proc; + + const stdioStringPromises = [undefined, Promise.resolve(), Promise.resolve()]; + for (const fd of [1, 2]) { + if (streams[fd] == null) continue; + if (stdioLoggers[fd] != null) { + logLines(streams[fd], stdioLoggers[fd]); + } else if (stdioSaveString[fd]) { + p[[null, 'stdout', 'stderr'][fd]] = stdioStringPromises[fd] = (async () => { + const chunks = []; + for await (const chunk of streams[fd]) chunks.push(chunk); + return Buffer.concat(chunks).toString().replace(/\n+$/g, ''); + })(); + } + } + + proc.on('exit', async (code, signal) => { + const [, stdout] = await Promise.all(stdioStringPromises); + if (code !== 0) { + procFailedErr.message = + `Command exited ${code ? `with code ${code}` : `on signal ${signal}`}: ${args.join(' ')}`; + procFailedErr.code = code; + procFailedErr.signal = signal; + logger.debug(procFailedErr.stack); + return px.reject(procFailedErr); + } + logger.debug(`Command returned successfully: ${args.join(' ')}`); + px.resolve(stdout); + }); return p; }; diff --git a/src/static/js/pluginfw/plugins.js b/src/static/js/pluginfw/plugins.js index 0860a65b..b705fb73 100644 --- a/src/static/js/pluginfw/plugins.js +++ b/src/static/js/pluginfw/plugins.js @@ -12,22 +12,15 @@ const defs = require('./plugin_defs'); const logger = log4js.getLogger('plugins'); // Log the version of npm at startup. -let loggedVersion = false; (async () => { - if (loggedVersion) return; - loggedVersion = true; - const p = runCmd(['npm', '--version'], {stdoutLogger: null}); - const chunks = []; - await Promise.all([ - (async () => { for await (const chunk of p.stdout) chunks.push(chunk); })(), - p, // Await in parallel to avoid unhandled rejection if p rejects during chunk read. - ]); - const version = Buffer.concat(chunks).toString().replace(/\n+$/g, ''); - logger.info(`npm --version: ${version}`); -})().catch((err) => { - logger.error(`Failed to get npm version: ${err.stack || err}`); - // This isn't a fatal error so don't re-throw. -}); + try { + const version = await runCmd(['npm', '--version'], {stdio: [null, 'string']}); + logger.info(`npm --version: ${version}`); + } catch (err) { + logger.error(`Failed to get npm version: ${err.stack || err}`); + // This isn't a fatal error so don't re-throw. + } +})(); exports.prefix = 'ep_'; @@ -96,15 +89,8 @@ exports.getPackages = async () => { // * The `--no-production` flag is required (or the `NODE_ENV` environment variable must be // 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 p = runCmd(['npm', 'ls', '--long', '--json', '--depth=0', '--no-production'], { - stdoutLogger: null, // We want to capture stdout, so don't attempt to log it. - }); - const chunks = []; - await Promise.all([ - (async () => { for await (const chunk of p.stdout) chunks.push(chunk); })(), - p, // Await in parallel to avoid unhandled rejection if p rejects during chunk read. - ]); - const {dependencies = {}} = JSON.parse(Buffer.concat(chunks).toString()); + const cmd = ['npm', 'ls', '--long', '--json', '--depth=0', '--no-production']; + const {dependencies = {}} = JSON.parse(await runCmd(cmd, {stdio: [null, 'string']})); await Promise.all(Object.entries(dependencies).map(async ([pkg, info]) => { if (!pkg.startsWith(exports.prefix)) { delete dependencies[pkg]; From 9633b98f92a6270dbeb7d4865a480a555f4fbe51 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 17 Feb 2021 22:53:24 -0500 Subject: [PATCH 014/115] tests: Delete unnecessary use of `npm` package --- src/tests/backend/specs/api/tidy.js | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/tests/backend/specs/api/tidy.js b/src/tests/backend/specs/api/tidy.js index aa07aa0b..6a8bd6a2 100644 --- a/src/tests/backend/specs/api/tidy.js +++ b/src/tests/backend/specs/api/tidy.js @@ -1,25 +1,15 @@ 'use strict'; +const Settings = require('../../../../node/utils/Settings'); +const TidyHtml = require('../../../../node/utils/TidyHtml'); const assert = require('assert'); const os = require('os'); const fs = require('fs'); const path = require('path'); -let TidyHtml; -let Settings; -const npm = require('npm/lib/npm.js'); const nodeify = require('nodeify'); describe(__filename, function () { describe('tidyHtml', function () { - before(function (done) { - npm.load({}, (err) => { - assert.ok(!err); - TidyHtml = require('../../../../node/utils/TidyHtml'); - Settings = require('../../../../node/utils/Settings'); - return done(); - }); - }); - const tidy = (file, callback) => nodeify(TidyHtml.tidy(file), callback); it('Tidies HTML', function (done) { From b3b5af3c3c69380686c3374a9a190f19ca5e05bc Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Wed, 17 Feb 2021 22:53:50 -0500 Subject: [PATCH 015/115] plugins: Use `npm` CLI to install/uninstall plugins Using npm as a module has long been discouraged and will stop working with npm v7. --- src/node/server.js | 3 --- src/static/js/pluginfw/installer.js | 19 +++++-------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/node/server.js b/src/node/server.js index e890a020..aec0d442 100755 --- a/src/node/server.js +++ b/src/node/server.js @@ -43,11 +43,9 @@ const UpdateCheck = require('./utils/UpdateCheck'); const db = require('./db/DB'); const express = require('./hooks/express'); const hooks = require('../static/js/pluginfw/hooks'); -const npm = require('npm/lib/npm.js'); const pluginDefs = require('../static/js/pluginfw/plugin_defs'); const plugins = require('../static/js/pluginfw/plugins'); const settings = require('./utils/Settings'); -const util = require('util'); const logger = log4js.getLogger('server'); @@ -138,7 +136,6 @@ exports.start = async () => { }); } - await util.promisify(npm.load)(); await db.init(); await plugins.update(); const installedPlugins = Object.values(pluginDefs.plugins) diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index 5f2e6ea1..8908dbaa 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -3,20 +3,11 @@ const log4js = require('log4js'); const plugins = require('./plugins'); const hooks = require('./hooks'); -const npm = require('npm'); const request = require('request'); -const util = require('util'); +const runCmd = require('../../../node/utils/run_cmd'); const logger = log4js.getLogger('plugins'); -let npmIsLoaded = false; -const loadNpm = async () => { - if (npmIsLoaded) return; - await util.promisify(npm.load)({}); - npmIsLoaded = true; - npm.on('log', log4js.getLogger('npm').log); -}; - const onAllTasksFinished = () => { hooks.aCallAll('restartServer', {}, () => {}); }; @@ -37,8 +28,8 @@ exports.uninstall = async (pluginName, cb = null) => { cb = wrapTaskCb(cb); logger.info(`Uninstalling plugin ${pluginName}...`); try { - await loadNpm(); - await util.promisify(npm.commands.uninstall)([pluginName]); + // The --no-save flag prevents npm from creating package.json or package-lock.json. + await runCmd(['npm', 'uninstall', '--no-save', pluginName]); } catch (err) { logger.error(`Failed to uninstall plugin ${pluginName}`); cb(err || new Error(err)); @@ -54,8 +45,8 @@ exports.install = async (pluginName, cb = null) => { cb = wrapTaskCb(cb); logger.info(`Installing plugin ${pluginName}...`); try { - await loadNpm(); - await util.promisify(npm.commands.install)([`${pluginName}@latest`]); + // The --no-save flag prevents npm from creating package.json or package-lock.json. + await runCmd(['npm', 'install', '--no-save', pluginName]); } catch (err) { logger.error(`Failed to install plugin ${pluginName}`); cb(err || new Error(err)); From 6163339c0dfab013ca30dff476cc753fc3e3c820 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Thu, 18 Feb 2021 03:13:09 -0500 Subject: [PATCH 016/115] plugins: Always install plugins with `--no-save` The npm CLI can get confused if `package.json` or `package-lock.json` exist. --- .github/workflows/backend-tests.yml | 2 +- .github/workflows/frontend-admin-tests.yml | 2 +- .github/workflows/frontend-tests.yml | 2 +- .github/workflows/load-test.yml | 2 +- .travis.yml | 2 +- README.md | 2 +- doc/plugins.md | 4 ++-- src/bin/plugins/lib/README.md | 5 ++++- 8 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index d87973f1..fc488256 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -58,7 +58,7 @@ jobs: - name: Install Etherpad plugins run: > - npm install + npm install --no-save ep_align ep_author_hover ep_cursortrace diff --git a/.github/workflows/frontend-admin-tests.yml b/.github/workflows/frontend-admin-tests.yml index e42aa3bb..0592d03f 100644 --- a/.github/workflows/frontend-admin-tests.yml +++ b/.github/workflows/frontend-admin-tests.yml @@ -28,7 +28,7 @@ jobs: # We intentionally install a much old ep_align version to test update minor versions - name: Install etherpad plugins - run: npm install ep_align@0.2.27 + run: npm install --no-save ep_align@0.2.27 # Nuke plugin tests - name: Install etherpad plugins diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml index 00c8dd6b..60429ab2 100644 --- a/.github/workflows/frontend-tests.yml +++ b/.github/workflows/frontend-tests.yml @@ -65,7 +65,7 @@ jobs: - name: Install Etherpad plugins run: > - npm install + npm install --no-save ep_align ep_author_hover ep_cursortrace diff --git a/.github/workflows/load-test.yml b/.github/workflows/load-test.yml index 98379dfe..2dbe9815 100644 --- a/.github/workflows/load-test.yml +++ b/.github/workflows/load-test.yml @@ -52,7 +52,7 @@ jobs: - name: Install etherpad plugins run: > - npm install + npm install --no-save ep_align ep_author_hover ep_cursortrace diff --git a/.travis.yml b/.travis.yml index 99aece0b..5048d7f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ _install_libreoffice: &install_libreoffice >- sudo apt-get -y install libreoffice libreoffice-pdfimport _install_plugins: &install_plugins >- - npm install + npm install --no-save ep_align ep_author_hover ep_cursortrace diff --git a/README.md b/README.md index d2dcfbe2..fff848a7 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ Etherpad is very customizable through plugins. Instructions for installing theme Run the following command in your Etherpad folder to get all of the features visible in the demo gif: ``` -npm install ep_headings2 ep_markdown ep_comments_page ep_align ep_font_color ep_webrtc ep_embedded_hyperlinks2 +npm install --no-save ep_headings2 ep_markdown ep_comments_page ep_align ep_font_color ep_webrtc ep_embedded_hyperlinks2 ``` ## Customize the style with skin variants diff --git a/doc/plugins.md b/doc/plugins.md index d8239c68..1df620f9 100644 --- a/doc/plugins.md +++ b/doc/plugins.md @@ -7,8 +7,8 @@ execute its own functionality based on these events. Publicly available plugins can be found in the npm registry (see ). Etherpad's naming convention for plugins is to prefix your plugins with `ep_`. So, e.g. it's `ep_flubberworms`. Thus you can install -plugins from npm, using `npm install ep_flubberworm` in Etherpad's root -directory. +plugins from npm, using `npm install --no-save ep_flubberworm` in Etherpad's +root directory. You can also browse to `http://yourEtherpadInstan.ce/admin/plugins`, which will list all installed plugins and those available on npm. It even provides diff --git a/src/bin/plugins/lib/README.md b/src/bin/plugins/lib/README.md index 3a1e2619..d2457b5f 100755 --- a/src/bin/plugins/lib/README.md +++ b/src/bin/plugins/lib/README.md @@ -7,7 +7,10 @@ Explain what your plugin does and who it's useful for. ![screenshot](https://user-images.githubusercontent.com/220864/99979953-97841d80-2d9f-11eb-9782-5f65817c58f4.PNG) ## Installing -npm install [plugin_name] + +``` +npm install --no-save [plugin_name] +``` or Use the Etherpad ``/admin`` interface. From d5997ddf0540bf91f1c3108b8b14aecfc213bd3d Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Thu, 11 Feb 2021 21:58:44 +0000 Subject: [PATCH 017/115] fix: upgrade log4js from 0.6.35 to 0.6.38 Snyk has created this PR to upgrade log4js from 0.6.35 to 0.6.38. See this package in npm: https://www.npmjs.com/package/log4js See this project in Snyk: https://app.snyk.io/org/johnmclear/project/d9a12bfb-7ccd-443f-9e22-f30d339cc8c5?utm_source=github&utm_medium=upgrade-pr --- src/package-lock.json | 6 +++--- src/package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/package-lock.json b/src/package-lock.json index f5872831..5c4b4b14 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -3181,9 +3181,9 @@ } }, "log4js": { - "version": "0.6.35", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.35.tgz", - "integrity": "sha1-OrHafLFII7dO04ZcSFk6zfEfG1k=", + "version": "0.6.38", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", + "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=", "requires": { "readable-stream": "~1.0.2", "semver": "~4.3.3" diff --git a/src/package.json b/src/package.json index adef338f..324c4621 100644 --- a/src/package.json +++ b/src/package.json @@ -50,7 +50,7 @@ "jsonminify": "0.4.1", "languages4translatewiki": "0.1.3", "lodash.clonedeep": "4.5.0", - "log4js": "0.6.35", + "log4js": "0.6.38", "measured-core": "1.51.1", "mime-types": "^2.1.27", "nodeify": "1.0.1", From 4d50813c948906a1ffa55d79c40d26bc8d6aaf21 Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Fri, 19 Feb 2021 02:51:15 -0500 Subject: [PATCH 018/115] issue templates: Ask for server info when opening a bug --- .github/ISSUE_TEMPLATE/bug_report.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea78..d003a478 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -23,6 +23,12 @@ A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. +**Server (please complete the following information):** + - Etherpad version: + - OS: [e.g., Ubuntu 20.04] + - Node.js version (`node --version`): + - npm version (`npm --version`): + **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] From 7e86acad8ffee163a56b15e7e2638b0db6f96baf Mon Sep 17 00:00:00 2001 From: John McLear Date: Sat, 20 Feb 2021 09:13:44 +0000 Subject: [PATCH 019/115] CI: Build and test Windows .zip (#4828) --- .github/workflows/windows-zip.yml | 66 +++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/windows-zip.yml diff --git a/.github/workflows/windows-zip.yml b/.github/workflows/windows-zip.yml new file mode 100644 index 00000000..8e52dc06 --- /dev/null +++ b/.github/workflows/windows-zip.yml @@ -0,0 +1,66 @@ +name: "Windows Zip" + +# any branch is useful for testing before a PR is submitted +on: [push, pull_request] + +jobs: + build: + # run on pushes to any branch + # run on PRs from external forks + if: | + (github.event_name != 'pull_request') + || (github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id) + name: Building Windows Zip on Linux without plugins + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - uses: actions/setup-node@v2 + with: + node-version: 12 + + - name: Install all dependencies and symlink for ep_etherpad-lite + run: src/bin/installDeps.sh + + - name: Run the backend tests + run: cd src && npm test + + - name: Build the .zip + run: src/bin/buildForWindows.sh + + - name: Archive production artifacts + uses: actions/upload-artifact@v2 + with: + name: etherpad-lite-win.zip + path: etherpad-lite-win.zip + + + deploy: + # run on pushes to any branch + # run on PRs from external forks + if: | + (github.event_name != 'pull_request') + || (github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id) + name: Deploy Windows Zip on Windows + needs: build + runs-on: windows-latest + + steps: + - name: Download zip + uses: actions/download-artifact@v2 + with: + name: etherpad-lite-win.zip + + - name: Extract Etherpad + run: 7z x etherpad-lite-win.zip -oetherpad + + - name: list + run: dir etherpad + + - name: Run Etherpad + run: | + cd etherpad + node node_modules\ep_etherpad-lite\node\server.js & + curl --connect-timeout 10 --max-time 20 --retry 5 --retry-delay 10 --retry-max-time 60 --retry-connrefused http://127.0.0.1:9001/p/test From 08d2024caf1183c476804e0f014d50ca36edc90a Mon Sep 17 00:00:00 2001 From: John McLear Date: Sat, 20 Feb 2021 13:22:23 +0000 Subject: [PATCH 020/115] docs: badges Badges into the README.md to show code quality, engagement etc. --- README.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fff848a7..c06a9931 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,4 @@ # A real-time collaborative editor for the web -Docker Pulls - -[![Travis (.com)](https://api.travis-ci.com/ether/etherpad-lite.svg?branch=develop)](https://travis-ci.com/github/ether/etherpad-lite) ![Demo Etherpad Animated Jif](doc/images/etherpad_demo.gif "Etherpad in action") @@ -10,6 +7,19 @@ Etherpad is a real-time collaborative editor [scalable to thousands of simultane **[Try it out](https://video.etherpad.com)** +# Project Status + +### Code Quality +[![Code Quality](https://github.com/ether/etherpad-lite/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/ether/etherpad-lite/actions/workflows/codeql-analysis.yml) [![Total alerts](https://img.shields.io/lgtm/alerts/g/ether/etherpad-lite.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ether/etherpad-lite/alerts/) [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/ether/etherpad-lite.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ether/etherpad-lite/context:javascript) [![package.lock](https://github.com/ether/etherpad-lite/actions/workflows/lint-package-lock.yml/badge.svg)](https://github.com/ether/etherpad-lite/actions/workflows/lint-package-lock.yml) + +### Testing +[![Backend tests](https://github.com/ether/etherpad-lite/actions/workflows/backend-tests.yml/badge.svg)](https://github.com/ether/etherpad-lite/actions/workflows/backend-tests.yml) [![Simulated Load](https://github.com/ether/etherpad-lite/actions/workflows/load-test.yml/badge.svg)](https://github.com/ether/etherpad-lite/actions/workflows/load-test.yml) [![Rate Limit](https://github.com/ether/etherpad-lite/actions/workflows/rate-limit.yml/badge.svg)](https://github.com/ether/etherpad-lite/actions/workflows/rate-limit.yml) [![Windows Zip](https://github.com/ether/etherpad-lite/actions/workflows/windows-zip.yml/badge.svg)](https://github.com/ether/etherpad-lite/actions/workflows/windows-zip.yml) [![Docker file](https://github.com/ether/etherpad-lite/actions/workflows/dockerfile.yml/badge.svg)](https://github.com/ether/etherpad-lite/actions/workflows/dockerfile.yml) +[![Frontend admin tests](https://github.com/ether/etherpad-lite/actions/workflows/frontend-admin-tests.yml/badge.svg)](https://github.com/ether/etherpad-lite/actions/workflows/frontend-admin-tests.yml) [![Frontend tests](https://github.com/ether/etherpad-lite/actions/workflows/frontend-tests.yml/badge.svg)](https://github.com/ether/etherpad-lite/actions/workflows/frontend-tests.yml) + +### Engagement +Docker Pulls +![GitHub all releases](https://img.shields.io/github/downloads/ether/etherpad-lite/total) ![Discord](https://img.shields.io/discord/741309013593030667) ![Etherpad plugins](https://img.shields.io/endpoint?url=https%3A%2F%2Fstatic.etherpad.org%2Fshields.json "Etherpad plugins") + # Installation ## Requirements From 4ca2d7ea3a80aa54efb66671770ba3c73fab311e Mon Sep 17 00:00:00 2001 From: Sebastian Castro Date: Sat, 20 Feb 2021 14:24:17 +0100 Subject: [PATCH 021/115] include lineHeight property in sidebar elements (#4831) This also makes the full line number element clickable to ensure a positive UX for the ``?lineNumber`` URL endpoint. It also makes it more obvious that a click action can happen based on the hover. Make line numbers stick to baseline of first line of wrapped content and editor lines with increased line hieght. Make it compatible with ep_author_neat --- src/static/js/ace2_inner.js | 43 ++++++++++++++++--- .../skins/colibris/src/components/sidediv.css | 16 ++++++- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index b41f4f0f..8e41ae13 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3786,7 +3786,24 @@ function Ace2Inner() { // Refs #4228, to avoid layout trashing, we need to first calculate all the heights, // and then apply at once all new height to div elements + const lineOffsets = []; + + // To place the line number on the same Z point as the first character of the first line + // we need to know the line height including the margins of the firstChild within the line + // This is somewhat computationally expensive as it looks at the first element within + // the line. Alternative, cheaper approaches are welcome. + // Original Issue: https://github.com/ether/etherpad-lite/issues/4527 const lineHeights = []; + + // 24 is the default line height within Etherpad - There may be quirks here such as + // none text elements (images/embeds etc) where the line height will be greater + // but as it's non-text type the line-height/margins might not be present and it + // could be that this breaks a theme that has a different default line height.. + // So instead of using an integer here we get the value from the Editor CSS. + const innerdocbody = document.querySelector('#innerdocbody'); + const innerdocbodyStyles = getComputedStyle(innerdocbody); + const defaultLineHeight = parseInt(innerdocbodyStyles['line-height']); + let docLine = doc.body.firstChild; let currentLine = 0; let h = null; @@ -3811,7 +3828,21 @@ function Ace2Inner() { // last line h = (docLine.clientHeight || docLine.offsetHeight); } - lineHeights.push(h); + lineOffsets.push(h); + + if (docLine.clientHeight !== defaultLineHeight) { + // line is wrapped OR has a larger line height within so we will do additional + // computation to figure out the line-height of the first element and + // use that for displaying the side div line number inline with the first line + // of content -- This is used in ep_headings, ep_font_size etc. where the line + // height is increased. + const elementStyle = window.getComputedStyle(docLine.firstChild); + const lineHeight = parseInt(elementStyle.getPropertyValue('line-height')); + const marginBottom = parseInt(elementStyle.getPropertyValue('margin-bottom')); + lineHeights.push(lineHeight + marginBottom); + } else { + lineHeights.push(defaultLineHeight); + } docLine = docLine.nextSibling; currentLine++; } @@ -3823,8 +3854,9 @@ function Ace2Inner() { // Apply height to existing sidediv lines currentLine = 0; while (sidebarLine && currentLine <= lineNumbersShown) { - if (lineHeights[currentLine] != null) { - sidebarLine.style.height = `${lineHeights[currentLine]}px`; + if (lineOffsets[currentLine] != null) { + sidebarLine.style.height = `${lineOffsets[currentLine]}px`; + sidebarLine.style.lineHeight = `${lineHeights[currentLine]}px`; } sidebarLine = sidebarLine.nextSibling; currentLine++; @@ -3839,8 +3871,9 @@ function Ace2Inner() { while (lineNumbersShown < newNumLines) { lineNumbersShown++; const div = odoc.createElement('DIV'); - if (lineHeights[currentLine]) { - div.style.height = `${lineHeights[currentLine]}px`; + if (lineOffsets[currentLine]) { + div.style.height = `${lineOffsets[currentLine]}px`; + div.style.lineHeight = `${lineHeights[currentLine]}px`; } $(div).append($(`${String(lineNumbersShown)}`)); fragment.appendChild(div); diff --git a/src/static/skins/colibris/src/components/sidediv.css b/src/static/skins/colibris/src/components/sidediv.css index 6f5556df..c48d38f6 100644 --- a/src/static/skins/colibris/src/components/sidediv.css +++ b/src/static/skins/colibris/src/components/sidediv.css @@ -12,8 +12,20 @@ } #sidedivinner>div .line-number { - line-height: 24px; + line-height: inherit; font-family: RobotoMono; + display: inline-block; color: #576273; color: var(--text-soft-color); -} \ No newline at end of file + height:100%; +} + +#sidedivinner>div .line-number:hover { + background-color: var(--bg-soft-color); + border-radius: 5px 0 0 5px; + font-weight: bold; + color: var(--text-color); +} +.plugin-ep_author_neat #sidedivinner>div .line-number:hover { + background-color: transparent; +} From c64b1b8eade4358bee78b3bb54e47c37e2b7e7a8 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 14:49:34 +0000 Subject: [PATCH 022/115] lint: skins --- src/static/skins/colibris/index.js | 6 ++++-- src/static/skins/colibris/pad.js | 6 ++++-- src/static/skins/colibris/timeslider.js | 6 ++++-- src/static/skins/no-skin/index.js | 6 ++++-- src/static/skins/no-skin/pad.js | 6 ++++-- src/static/skins/no-skin/timeslider.js | 6 ++++-- 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/static/skins/colibris/index.js b/src/static/skins/colibris/index.js index c56ba5bb..633db726 100644 --- a/src/static/skins/colibris/index.js +++ b/src/static/skins/colibris/index.js @@ -1,5 +1,7 @@ -function customStart() { +'use strict'; + +window.customStart = () => { // define your javascript here // jquery is available - except index.js // you can load extra scripts with $.getScript http://api.jquery.com/jQuery.getScript/ -} +}; diff --git a/src/static/skins/colibris/pad.js b/src/static/skins/colibris/pad.js index 0f5e4731..1987f320 100644 --- a/src/static/skins/colibris/pad.js +++ b/src/static/skins/colibris/pad.js @@ -1,5 +1,7 @@ -function customStart() { +'use strict'; + +window.customStart = () => { $('#pad_title').show(); $('.buttonicon').mousedown(function () { $(this).parent().addClass('pressed'); }); $('.buttonicon').mouseup(function () { $(this).parent().removeClass('pressed'); }); -} +}; diff --git a/src/static/skins/colibris/timeslider.js b/src/static/skins/colibris/timeslider.js index 74101127..5fa8ae3b 100644 --- a/src/static/skins/colibris/timeslider.js +++ b/src/static/skins/colibris/timeslider.js @@ -1,2 +1,4 @@ -function customStart() { -} +'use strict'; + +window.customStart = () => { +}; diff --git a/src/static/skins/no-skin/index.js b/src/static/skins/no-skin/index.js index c56ba5bb..633db726 100644 --- a/src/static/skins/no-skin/index.js +++ b/src/static/skins/no-skin/index.js @@ -1,5 +1,7 @@ -function customStart() { +'use strict'; + +window.customStart = () => { // define your javascript here // jquery is available - except index.js // you can load extra scripts with $.getScript http://api.jquery.com/jQuery.getScript/ -} +}; diff --git a/src/static/skins/no-skin/pad.js b/src/static/skins/no-skin/pad.js index c56ba5bb..633db726 100644 --- a/src/static/skins/no-skin/pad.js +++ b/src/static/skins/no-skin/pad.js @@ -1,5 +1,7 @@ -function customStart() { +'use strict'; + +window.customStart = () => { // define your javascript here // jquery is available - except index.js // you can load extra scripts with $.getScript http://api.jquery.com/jQuery.getScript/ -} +}; diff --git a/src/static/skins/no-skin/timeslider.js b/src/static/skins/no-skin/timeslider.js index c56ba5bb..633db726 100644 --- a/src/static/skins/no-skin/timeslider.js +++ b/src/static/skins/no-skin/timeslider.js @@ -1,5 +1,7 @@ -function customStart() { +'use strict'; + +window.customStart = () => { // define your javascript here // jquery is available - except index.js // you can load extra scripts with $.getScript http://api.jquery.com/jQuery.getScript/ -} +}; From 7c514460401f382bc0dc299ba92abd1b5380ff12 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 14:55:54 +0000 Subject: [PATCH 023/115] lint: use strict --- src/static/js/ChangesetUtils.js | 2 ++ src/static/js/index.js | 4 ++-- src/static/js/l10n.js | 4 +++- src/static/js/security.js | 2 ++ src/static/js/underscore.js | 2 ++ 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/static/js/ChangesetUtils.js b/src/static/js/ChangesetUtils.js index c7333afc..15ce9c56 100644 --- a/src/static/js/ChangesetUtils.js +++ b/src/static/js/ChangesetUtils.js @@ -1,3 +1,5 @@ +'use strict'; + /** * This module contains several helper Functions to build Changesets * based on a SkipList diff --git a/src/static/js/index.js b/src/static/js/index.js index 72941cd8..566305e7 100644 --- a/src/static/js/index.js +++ b/src/static/js/index.js @@ -1,3 +1,5 @@ +'use strict'; + // @license magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt Apache-2.0 /** * Copyright 2011 Peter Martischka, Primary Technology. @@ -16,8 +18,6 @@ * limitations under the License. */ -/* global $, customStart */ - function randomPadName() { // the number of distinct chars (64) is chosen to ensure that the selection will be uniform when // using the PRNG below diff --git a/src/static/js/l10n.js b/src/static/js/l10n.js index 3c980898..7206f913 100644 --- a/src/static/js/l10n.js +++ b/src/static/js/l10n.js @@ -1,4 +1,6 @@ -(function (document) { +'use strict'; + +((document) => { // Set language for l10n let language = document.cookie.match(/language=((\w{2,3})(-\w+)?)/); if (language) language = language[1]; diff --git a/src/static/js/security.js b/src/static/js/security.js index 9c9fff37..d92425cb 100644 --- a/src/static/js/security.js +++ b/src/static/js/security.js @@ -1,3 +1,5 @@ +'use strict'; + /** * Copyright 2009 Google Inc. * diff --git a/src/static/js/underscore.js b/src/static/js/underscore.js index a6d42b85..d30543ca 100644 --- a/src/static/js/underscore.js +++ b/src/static/js/underscore.js @@ -1 +1,3 @@ +'use strict'; + module.exports = require('underscore'); From 01dd0040542ff84a8989881431c7fd56a6c64e44 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 14:56:25 +0000 Subject: [PATCH 024/115] lint: ChangesetUtils --- src/static/js/ChangesetUtils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/static/js/ChangesetUtils.js b/src/static/js/ChangesetUtils.js index 15ce9c56..ef2be2eb 100644 --- a/src/static/js/ChangesetUtils.js +++ b/src/static/js/ChangesetUtils.js @@ -20,7 +20,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -exports.buildRemoveRange = function (rep, builder, start, end) { +exports.buildRemoveRange = (rep, builder, start, end) => { const startLineOffset = rep.lines.offsetOfIndex(start[0]); const endLineOffset = rep.lines.offsetOfIndex(end[0]); @@ -32,7 +32,7 @@ exports.buildRemoveRange = function (rep, builder, start, end) { } }; -exports.buildKeepRange = function (rep, builder, start, end, attribs, pool) { +exports.buildKeepRange = (rep, builder, start, end, attribs, pool) => { const startLineOffset = rep.lines.offsetOfIndex(start[0]); const endLineOffset = rep.lines.offsetOfIndex(end[0]); @@ -44,7 +44,7 @@ exports.buildKeepRange = function (rep, builder, start, end, attribs, pool) { } }; -exports.buildKeepToStartOfRange = function (rep, builder, start) { +exports.buildKeepToStartOfRange = (rep, builder, start) => { const startLineOffset = rep.lines.offsetOfIndex(start[0]); builder.keep(startLineOffset, start[0]); From b029edb9319a221ee57ff77306dedfe844cdca9a Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 14:58:05 +0000 Subject: [PATCH 025/115] lint: index.js --- src/static/js/index.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/static/js/index.js b/src/static/js/index.js index 566305e7..b0b3a1eb 100644 --- a/src/static/js/index.js +++ b/src/static/js/index.js @@ -1,5 +1,6 @@ 'use strict'; +/* eslint-disable-next-line max-len */ // @license magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt Apache-2.0 /** * Copyright 2011 Peter Martischka, Primary Technology. @@ -18,26 +19,26 @@ * limitations under the License. */ -function randomPadName() { +const randomPadName = () => { // the number of distinct chars (64) is chosen to ensure that the selection will be uniform when // using the PRNG below const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'; // the length of the pad name is chosen to get 120-bit security: log2(64^20) = 120 - const string_length = 20; + const stringLength = 20; // make room for 8-bit integer values that span from 0 to 255. - const randomarray = new Uint8Array(string_length); + const randomarray = new Uint8Array(stringLength); // use browser's PRNG to generate a "unique" sequence const cryptoObj = window.crypto || window.msCrypto; // for IE 11 cryptoObj.getRandomValues(randomarray); let randomstring = ''; - for (let i = 0; i < string_length; i++) { + for (let i = 0; i < stringLength; i++) { // instead of writing "Math.floor(randomarray[i]/256*64)" // we can save some cycles. const rnum = Math.floor(randomarray[i] / 4); randomstring += chars.substring(rnum, rnum + 1); } return randomstring; -} +}; $(() => { $('#go2Name').submit(() => { @@ -55,7 +56,7 @@ $(() => { }); // start the custom js - if (typeof customStart === 'function') customStart(); + if (typeof window.customStart === 'function') window.customStart(); }); // @license-end From cf37f52093e766fb47702db6586919f3ef00b963 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 15:03:30 +0000 Subject: [PATCH 026/115] lint: collab_client.js --- src/static/js/collab_client.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/static/js/collab_client.js b/src/static/js/collab_client.js index abe3911d..d2b9c402 100644 --- a/src/static/js/collab_client.js +++ b/src/static/js/collab_client.js @@ -34,7 +34,7 @@ const getSocket = () => pad && pad.socket; /** Call this when the document is ready, and a new Ace2Editor() has been created and inited. ACE's ready callback does not need to have fired yet. "serverVars" are from calling doc.getCollabClientVars() on the server. */ -function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) { +const getCollabClient = (ace2editor, serverVars, initialUserInfo, options, _pad) => { const editor = ace2editor; pad = _pad; // Inject pad to avoid a circular dependency. @@ -583,6 +583,6 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad) setUpSocket(); return self; -} +}; exports.getCollabClient = getCollabClient; From acccf5672419969e369fa137f3a153326dacb087 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 15:23:11 +0000 Subject: [PATCH 027/115] lint: Changeset.js arrow-functions --- src/static/js/Changeset.js | 399 +++++++++++++++++-------------------- 1 file changed, 179 insertions(+), 220 deletions(-) diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js index 422c7ede..18018454 100644 --- a/src/static/js/Changeset.js +++ b/src/static/js/Changeset.js @@ -1,5 +1,8 @@ +'use strict'; + /* - * This is the Changeset library copied from the old Etherpad with some modifications to use it in node.js + * This is the Changeset library copied from the old Etherpad with some modifications + * to use it in node.js * Can be found in https://github.com/ether/pad/blob/master/infrastructure/ace/www/easysync2.js */ @@ -35,7 +38,7 @@ const AttributePool = require('./AttributePool'); * This method is called whenever there is an error in the sync process * @param msg {string} Just some message */ -exports.error = function error(msg) { +exports.error = (msg) => { const e = new Error(msg); e.easysync = true; throw e; @@ -47,7 +50,7 @@ exports.error = function error(msg) { * @param b {boolean} assertion condition * @param msgParts {string} error to be passed if it fails */ -exports.assert = function assert(b, msgParts) { +exports.assert = (b, msgParts) => { if (!b) { const msg = Array.prototype.slice.call(arguments, 1).join(''); exports.error(`Failed assertion: ${msg}`); @@ -59,18 +62,14 @@ exports.assert = function assert(b, msgParts) { * @param str {string} string of the number in base 36 * @returns {int} number */ -exports.parseNum = function (str) { - return parseInt(str, 36); -}; +exports.parseNum = (str) => parseInt(str, 36); /** * Writes a number in base 36 and puts it in a string * @param num {int} number * @returns {string} string */ -exports.numToString = function (num) { - return num.toString(36).toLowerCase(); -}; +exports.numToString = (num) => num.toString(36).toLowerCase(); /** * Converts stuff before $ to base 10 @@ -78,7 +77,7 @@ exports.numToString = function (num) { * @param cs {string} the string * @return integer */ -exports.toBaseTen = function (cs) { +exports.toBaseTen = (cs) => { const dollarIndex = cs.indexOf('$'); const beforeDollar = cs.substring(0, dollarIndex); const fromDollar = cs.substring(dollarIndex); @@ -95,17 +94,13 @@ exports.toBaseTen = function (cs) { * can be applied * @param cs {string} String representation of the Changeset */ -exports.oldLen = function (cs) { - return exports.unpack(cs).oldLen; -}; +exports.oldLen = (cs) => exports.unpack(cs).oldLen; /** * returns the length of the text after changeset is applied * @param cs {string} String representation of the Changeset */ -exports.newLen = function (cs) { - return exports.unpack(cs).newLen; -}; +exports.newLen = (cs) => exports.unpack(cs).newLen; /** * this function creates an iterator which decodes string changeset operations @@ -113,29 +108,28 @@ exports.newLen = function (cs) { * @param optStartIndex {int} from where in the string should the iterator start * @return {Op} type object iterator */ -exports.opIterator = function (opsStr, optStartIndex) { +exports.opIterator = (opsStr, optStartIndex) => { // print(opsStr); const regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|\?|/g; const startIndex = (optStartIndex || 0); let curIndex = startIndex; let prevIndex = curIndex; - function nextRegexMatch() { + const nextRegexMatch = () => { prevIndex = curIndex; - let result; regex.lastIndex = curIndex; - result = regex.exec(opsStr); + const result = regex.exec(opsStr); curIndex = regex.lastIndex; - if (result[0] == '?') { + if (result[0] === '?') { exports.error('Hit error opcode in op stream'); } return result; - } + }; let regexResult = nextRegexMatch(); const obj = exports.newOp(); - function next(optObj) { + const next = (optObj) => { const op = (optObj || obj); if (regexResult[0]) { op.attribs = regexResult[1]; @@ -147,15 +141,12 @@ exports.opIterator = function (opsStr, optStartIndex) { exports.clearOp(op); } return op; - } + }; - function hasNext() { - return !!(regexResult[0]); - } + const hasNext = () => !!(regexResult[0]); + + const lastIndex = () => prevIndex; - function lastIndex() { - return prevIndex; - } return { next, hasNext, @@ -167,7 +158,7 @@ exports.opIterator = function (opsStr, optStartIndex) { * Cleans an Op object * @param {Op} object to be cleared */ -exports.clearOp = function (op) { +exports.clearOp = (op) => { op.opcode = ''; op.chars = 0; op.lines = 0; @@ -178,34 +169,30 @@ exports.clearOp = function (op) { * Creates a new Op object * @param optOpcode the type operation of the Op object */ -exports.newOp = function (optOpcode) { - return { - opcode: (optOpcode || ''), - chars: 0, - lines: 0, - attribs: '', - }; -}; +exports.newOp = (optOpcode) => ({ + opcode: (optOpcode || ''), + chars: 0, + lines: 0, + attribs: '', +}); /** * Clones an Op * @param op Op to be cloned */ -exports.cloneOp = function (op) { - return { - opcode: op.opcode, - chars: op.chars, - lines: op.lines, - attribs: op.attribs, - }; -}; +exports.cloneOp = (op) => ({ + opcode: op.opcode, + chars: op.chars, + lines: op.lines, + attribs: op.attribs, +}); /** * Copies op1 to op2 * @param op1 src Op * @param op2 dest Op */ -exports.copyOp = function (op1, op2) { +exports.copyOp = (op1, op2) => { op2.opcode = op1.opcode; op2.chars = op1.chars; op2.lines = op1.lines; @@ -215,7 +202,7 @@ exports.copyOp = function (op1, op2) { /** * Writes the Op in a string the way that changesets need it */ -exports.opString = function (op) { +exports.opString = (op) => { // just for debugging if (!op.opcode) return 'null'; const assem = exports.opAssembler(); @@ -226,16 +213,13 @@ exports.opString = function (op) { /** * Used just for debugging */ -exports.stringOp = function (str) { - // just for debugging - return exports.opIterator(str).next(); -}; +exports.stringOp = (str) => exports.opIterator(str).next(); /** * Used to check if a Changeset if valid * @param cs {Changeset} Changeset to be checked */ -exports.checkRep = function (cs) { +exports.checkRep = (cs) => { // doesn't check things that require access to attrib pool (e.g. attribute order) // or original string (e.g. newline positions) const unpacked = exports.unpack(cs); @@ -279,7 +263,7 @@ exports.checkRep = function (cs) { assem.endDocument(); const normalized = exports.pack(oldLen, calcNewLen, assem.toString(), charBank); - exports.assert(normalized == cs, 'Invalid changeset (checkRep failed)'); + exports.assert(normalized === cs, 'Invalid changeset (checkRep failed)'); return cs; }; @@ -293,7 +277,7 @@ exports.checkRep = function (cs) { * creates an object that allows you to append operations (type Op) and also * compresses them if possible */ -exports.smartOpAssembler = function () { +exports.smartOpAssembler = () => { // Like opAssembler but able to produce conforming exportss // from slightly looser input, at the cost of speed. // Specifically: @@ -308,19 +292,19 @@ exports.smartOpAssembler = function () { let lastOpcode = ''; let lengthChange = 0; - function flushKeeps() { + const flushKeeps = () => { assem.append(keepAssem.toString()); keepAssem.clear(); - } + }; - function flushPlusMinus() { + const flushPlusMinus = () => { assem.append(minusAssem.toString()); minusAssem.clear(); assem.append(plusAssem.toString()); plusAssem.clear(); - } + }; - function append(op) { + const append = (op) => { if (!op.opcode) return; if (!op.chars) return; @@ -343,9 +327,9 @@ exports.smartOpAssembler = function () { keepAssem.append(op); } lastOpcode = op.opcode; - } + }; - function appendOpWithText(opcode, text, attribs, pool) { + const appendOpWithText = (opcode, text, attribs, pool) => { const op = exports.newOp(opcode); op.attribs = exports.makeAttribsString(opcode, attribs, pool); const lastNewlinePos = text.lastIndexOf('\n'); @@ -361,29 +345,27 @@ exports.smartOpAssembler = function () { op.lines = 0; append(op); } - } + }; - function toString() { + const toString = () => { flushPlusMinus(); flushKeeps(); return assem.toString(); - } + }; - function clear() { + const clear = () => { minusAssem.clear(); plusAssem.clear(); keepAssem.clear(); assem.clear(); lengthChange = 0; - } + }; - function endDocument() { + const endDocument = () => { keepAssem.endDocument(); - } + }; - function getLengthChange() { - return lengthChange; - } + const getLengthChange = () => lengthChange; return { append, @@ -396,7 +378,7 @@ exports.smartOpAssembler = function () { }; -exports.mergingOpAssembler = function () { +exports.mergingOpAssembler = () => { // This assembler can be used in production; it efficiently // merges consecutive operations that are mergeable, ignores // no-ops, and drops final pure "keeps". It does not re-order @@ -410,7 +392,7 @@ exports.mergingOpAssembler = function () { // ops immediately after it. let bufOpAdditionalCharsAfterNewline = 0; - function flush(isEndDocument) { + const flush = (isEndDocument) => { if (bufOp.opcode) { if (isEndDocument && bufOp.opcode == '=' && !bufOp.attribs) { // final merged keep, leave it implicit @@ -425,9 +407,9 @@ exports.mergingOpAssembler = function () { } bufOp.opcode = ''; } - } + }; - function append(op) { + const append = (op) => { if (op.chars > 0) { if (bufOp.opcode == op.opcode && bufOp.attribs == op.attribs) { if (op.lines > 0) { @@ -447,21 +429,21 @@ exports.mergingOpAssembler = function () { exports.copyOp(op, bufOp); } } - } + }; - function endDocument() { + const endDocument = () => { flush(true); - } + }; - function toString() { + const toString = () => { flush(); return assem.toString(); - } + }; - function clear() { + const clear = () => { assem.clear(); exports.clearOp(bufOp); - } + }; return { append, toString, @@ -471,26 +453,24 @@ exports.mergingOpAssembler = function () { }; -exports.opAssembler = function () { +exports.opAssembler = () => { const pieces = []; // this function allows op to be mutated later (doesn't keep a ref) - function append(op) { + const append = (op) => { pieces.push(op.attribs); if (op.lines) { pieces.push('|', exports.numToString(op.lines)); } pieces.push(op.opcode); pieces.push(exports.numToString(op.chars)); - } + }; - function toString() { - return pieces.join(''); - } + const toString = () => pieces.join(''); - function clear() { + const clear = () => { pieces.length = 0; - } + }; return { append, toString, @@ -502,40 +482,36 @@ exports.opAssembler = function () { * A custom made String Iterator * @param str {string} String to be iterated over */ -exports.stringIterator = function (str) { +exports.stringIterator = (str) => { let curIndex = 0; // newLines is the number of \n between curIndex and str.length let newLines = str.split('\n').length - 1; - function getnewLines() { - return newLines; - } + const getnewLines = () => newLines; - function assertRemaining(n) { + const assertRemaining = (n) => { exports.assert(n <= remaining(), '!(', n, ' <= ', remaining(), ')'); - } + }; - function take(n) { + const take = (n) => { assertRemaining(n); const s = str.substr(curIndex, n); newLines -= s.split('\n').length - 1; curIndex += n; return s; - } + }; - function peek(n) { + const peek = (n) => { assertRemaining(n); const s = str.substr(curIndex, n); return s; - } + }; - function skip(n) { + const skip = (n) => { assertRemaining(n); curIndex += n; - } + }; - function remaining() { - return str.length - curIndex; - } + const remaining = () => str.length - curIndex; return { take, skip, @@ -548,16 +524,14 @@ exports.stringIterator = function (str) { /** * A custom made StringBuffer */ -exports.stringAssembler = function () { +exports.stringAssembler = () => { const pieces = []; - function append(x) { + const append = (x) => { pieces.push(String(x)); - } + }; - function toString() { - return pieces.join(''); - } + const toString = () => pieces.join(''); return { append, toString, @@ -569,7 +543,7 @@ exports.stringAssembler = function () { * It is used for applying Changesets on arrays of lines * Note from prev docs: "lines" need not be an array as long as it supports certain calls (lines_foo inside). */ -exports.textLinesMutator = function (lines) { +exports.textLinesMutator = (lines) => { // Mutates lines, an array of strings, in place. // Mutation operations have the same constraints as exports operations // with respect to newlines, but not the other additional constraints @@ -588,72 +562,68 @@ exports.textLinesMutator = function (lines) { // invariant: if (inSplice && (curLine >= curSplice[0] + curSplice.length - 2)) then // curCol == 0 - function lines_applySplice(s) { + const lines_applySplice = (s) => { lines.splice.apply(lines, s); - } + }; - function lines_toSource() { - return lines.toSource(); - } + const lines_toSource = () => lines.toSource(); - function lines_get(idx) { + const lines_get = (idx) => { if (lines.get) { return lines.get(idx); } else { return lines[idx]; } - } + }; // can be unimplemented if removeLines's return value not needed - function lines_slice(start, end) { + const lines_slice = (start, end) => { if (lines.slice) { return lines.slice(start, end); } else { return []; } - } + }; - function lines_length() { + const lines_length = () => { if ((typeof lines.length) === 'number') { return lines.length; } else { return lines.length(); } - } + }; - function enterSplice() { + const enterSplice = () => { curSplice[0] = curLine; curSplice[1] = 0; if (curCol > 0) { putCurLineInSplice(); } inSplice = true; - } + }; - function leaveSplice() { + const leaveSplice = () => { lines_applySplice(curSplice); curSplice.length = 2; curSplice[0] = curSplice[1] = 0; inSplice = false; - } + }; - function isCurLineInSplice() { - return (curLine - curSplice[0] < (curSplice.length - 2)); - } + const isCurLineInSplice = () => (curLine - curSplice[0] < (curSplice.length - 2)); - function debugPrint(typ) { + const debugPrint = (typ) => { print(`${typ}: ${curSplice.toSource()} / ${curLine},${curCol} / ${lines_toSource()}`); - } + }; - function putCurLineInSplice() { + const putCurLineInSplice = () => { if (!isCurLineInSplice()) { curSplice.push(lines_get(curSplice[0] + curSplice[1])); curSplice[1]++; } return 2 + curLine - curSplice[0]; - } + }; - function skipLines(L, includeInSplice) { + const skipLines = (L, includeInSplice) => { if (L) { if (includeInSplice) { if (!inSplice) { @@ -683,9 +653,9 @@ exports.textLinesMutator = function (lines) { // tests case foo in remove(), which isn't otherwise covered in current impl } // debugPrint("skip"); - } + }; - function skip(N, L, includeInSplice) { + const skip = (N, L, includeInSplice) => { if (N) { if (L) { skipLines(L, includeInSplice); @@ -700,19 +670,19 @@ exports.textLinesMutator = function (lines) { // debugPrint("skip"); } } - } + }; - function removeLines(L) { + const removeLines = (L) => { let removed = ''; if (L) { if (!inSplice) { enterSplice(); } - function nextKLinesText(k) { + const nextKLinesText = (k) => { const m = curSplice[0] + curSplice[1]; return lines_slice(m, m + k).join(''); - } + }; if (isCurLineInSplice()) { // print(curCol); if (curCol == 0) { @@ -736,9 +706,9 @@ exports.textLinesMutator = function (lines) { // debugPrint("remove"); } return removed; - } + }; - function remove(N, L) { + const remove = (N, L) => { let removed = ''; if (N) { if (L) { @@ -754,9 +724,9 @@ exports.textLinesMutator = function (lines) { } } return removed; - } + }; - function insert(text, L) { + const insert = (text, L) => { if (text) { if (!inSplice) { enterSplice(); @@ -796,23 +766,23 @@ exports.textLinesMutator = function (lines) { } // debugPrint("insert"); } - } + }; - function hasMore() { + const hasMore = () => { // print(lines.length+" / "+inSplice+" / "+(curSplice.length - 2)+" / "+curSplice[1]); let docLines = lines_length(); if (inSplice) { docLines += curSplice.length - 2 - curSplice[1]; } return curLine < docLines; - } + }; - function close() { + const close = () => { if (inSplice) { leaveSplice(); } // debugPrint("close"); - } + }; const self = { skip, @@ -841,7 +811,7 @@ exports.textLinesMutator = function (lines) { * opOut - result operator to be put into Changeset * @return {string} the integrated changeset */ -exports.applyZip = function (in1, idx1, in2, idx2, func) { +exports.applyZip = (in1, idx1, in2, idx2, func) => { const iter1 = exports.opIterator(in1, idx1); const iter2 = exports.opIterator(in2, idx2); const assem = exports.smartOpAssembler(); @@ -867,7 +837,7 @@ exports.applyZip = function (in1, idx1, in2, idx2, func) { * @params cs {string} String encoded Changeset * @returns {Changeset} a Changeset class */ -exports.unpack = function (cs) { +exports.unpack = (cs) => { const headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/; const headerMatch = headerRegex.exec(cs); if ((!headerMatch) || (!headerMatch[0])) { @@ -896,7 +866,7 @@ exports.unpack = function (cs) { * @params bank {string} Charbank of the Changeset * @returns {Changeset} a Changeset class */ -exports.pack = function (oldLen, newLen, opsStr, bank) { +exports.pack = (oldLen, newLen, opsStr, bank) => { const lenDiff = newLen - oldLen; const lenDiffStr = (lenDiff >= 0 ? `>${exports.numToString(lenDiff)}` : `<${exports.numToString(-lenDiff)}`); const a = []; @@ -909,7 +879,7 @@ exports.pack = function (oldLen, newLen, opsStr, bank) { * @params cs {string} String encoded Changeset * @params str {string} String to which a Changeset should be applied */ -exports.applyToText = function (cs, str) { +exports.applyToText = (cs, str) => { const unpacked = exports.unpack(cs); exports.assert(str.length == unpacked.oldLen, 'mismatched apply: ', str.length, ' / ', unpacked.oldLen); const csIter = exports.opIterator(unpacked.ops); @@ -954,7 +924,7 @@ exports.applyToText = function (cs, str) { * @param CS {Changeset} the changeset to be applied * @param lines The lines to which the changeset needs to be applied */ -exports.mutateTextLines = function (cs, lines) { +exports.mutateTextLines = (cs, lines) => { const unpacked = exports.unpack(cs); const csIter = exports.opIterator(unpacked.ops); const bankIter = exports.stringIterator(unpacked.charBank); @@ -983,7 +953,7 @@ exports.mutateTextLines = function (cs, lines) { * @param resultIsMutaton {boolean} * @param pool {AttribPool} attribute pool */ -exports.composeAttributes = function (att1, att2, resultIsMutation, pool) { +exports.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 @@ -1044,7 +1014,7 @@ exports.composeAttributes = function (att1, att2, resultIsMutation, pool) { * Function used as parameter for applyZip to apply a Changeset to an * attribute */ -exports._slicerZipperFunc = function (attOp, csOp, opOut, pool) { +exports._slicerZipperFunc = (attOp, csOp, opOut, pool) => { // attOp is the op from the sequence that is being operated on, either an // attribution string or the earlier of two exportss being composed. // pool can be null if definitely not needed. @@ -1136,7 +1106,7 @@ exports._slicerZipperFunc = function (attOp, csOp, opOut, pool) { * @param astr {string} the attribs string of a AText * @param pool {AttribsPool} the attibutes pool */ -exports.applyToAttribution = function (cs, astr, pool) { +exports.applyToAttribution = (cs, astr, pool) => { const unpacked = exports.unpack(cs); return exports.applyZip(astr, 0, unpacked.ops, 0, (op1, op2, opOut) => exports._slicerZipperFunc(op1, op2, opOut, pool)); @@ -1148,7 +1118,7 @@ exports.applyToAttribution = function (cs, astr, pool) { };*/ -exports.mutateAttributionLines = function (cs, lines, pool) { +exports.mutateAttributionLines = (cs, lines, pool) => { // dmesg(cs); // dmesg(lines.toSource()+" ->"); const unpacked = exports.unpack(cs); @@ -1160,11 +1130,9 @@ exports.mutateAttributionLines = function (cs, lines, pool) { let lineIter = null; - function isNextMutOp() { - return (lineIter && lineIter.hasNext()) || mut.hasMore(); - } + const isNextMutOp = () => (lineIter && lineIter.hasNext()) || mut.hasMore(); - function nextMutOp(destOp) { + const nextMutOp = (destOp) => { if ((!(lineIter && lineIter.hasNext())) && mut.hasMore()) { const line = mut.removeLines(1); lineIter = exports.opIterator(line); @@ -1174,10 +1142,10 @@ exports.mutateAttributionLines = function (cs, lines, pool) { } else { destOp.opcode = ''; } - } + }; let lineAssem = null; - function outputMutOp(op) { + const outputMutOp = (op) => { // print("outputMutOp: "+op.toSource()); if (!lineAssem) { lineAssem = exports.mergingOpAssembler(); @@ -1189,7 +1157,7 @@ exports.mutateAttributionLines = function (cs, lines, pool) { mut.insert(lineAssem.toString(), 1); lineAssem = null; } - } + }; const csOp = exports.newOp(); const attOp = exports.newOp(); @@ -1247,7 +1215,7 @@ exports.mutateAttributionLines = function (cs, lines, pool) { * @param theAlines collection of Attribution lines * @returns {string} joined Attribution lines */ -exports.joinAttributionLines = function (theAlines) { +exports.joinAttributionLines = (theAlines) => { const assem = exports.mergingOpAssembler(); for (let i = 0; i < theAlines.length; i++) { const aline = theAlines[i]; @@ -1259,20 +1227,20 @@ exports.joinAttributionLines = function (theAlines) { return assem.toString(); }; -exports.splitAttributionLines = function (attrOps, text) { +exports.splitAttributionLines = (attrOps, text) => { const iter = exports.opIterator(attrOps); const assem = exports.mergingOpAssembler(); const lines = []; let pos = 0; - function appendOp(op) { + const appendOp = (op) => { assem.append(op); if (op.lines > 0) { lines.push(assem.toString()); assem.clear(); } pos += op.chars; - } + }; while (iter.hasNext()) { const op = iter.next(); @@ -1301,9 +1269,7 @@ exports.splitAttributionLines = function (attrOps, text) { * splits text into lines * @param {string} text to be splitted */ -exports.splitTextLines = function (text) { - return text.match(/[^\n]*(?:\n|[^\n]$)/g); -}; +exports.splitTextLines = (text) => text.match(/[^\n]*(?:\n|[^\n]$)/g); /** * compose two Changesets @@ -1311,7 +1277,7 @@ exports.splitTextLines = function (text) { * @param cs2 {Changeset} second Changeset * @param pool {AtribsPool} Attribs pool */ -exports.compose = function (cs1, cs2, pool) { +exports.compose = (cs1, cs2, pool) => { const unpacked1 = exports.unpack(cs1); const unpacked2 = exports.unpack(cs2); const len1 = unpacked1.oldLen; @@ -1360,7 +1326,8 @@ exports.compose = function (cs1, cs2, pool) { * @param attribPair array [key,value] of the attribute * @param pool {AttribPool} Attribute pool */ -exports.attributeTester = function (attribPair, pool) { +exports.attributeTester = (attribPair, pool) => { + const never = (attribs) => false; if (!pool) { return never; } @@ -1373,19 +1340,13 @@ exports.attributeTester = function (attribPair, pool) { return re.test(attribs); }; } - - function never(attribs) { - return false; - } }; /** * creates the identity Changeset of length N * @param N {int} length of the identity changeset */ -exports.identity = function (N) { - return exports.pack(N, N, '', ''); -}; +exports.identity = (N) => exports.pack(N, N, '', ''); /** @@ -1400,7 +1361,7 @@ exports.identity = function (N) { * @param optNewTextAPairs {string} new pairs to be inserted * @param pool {AttribPool} Attribution Pool */ -exports.makeSplice = function (oldFullText, spliceStart, numRemoved, newText, optNewTextAPairs, pool) { +exports.makeSplice = (oldFullText, spliceStart, numRemoved, newText, optNewTextAPairs, pool) => { const oldLen = oldFullText.length; if (spliceStart >= oldLen) { @@ -1426,7 +1387,7 @@ exports.makeSplice = function (oldFullText, spliceStart, numRemoved, newText, op * startChar to endChar with newText * @param cs Changeset */ -exports.toSplices = function (cs) { +exports.toSplices = (cs) => { // const unpacked = exports.unpack(cs); const splices = []; @@ -1460,7 +1421,7 @@ exports.toSplices = function (cs) { /** * */ -exports.characterRangeFollow = function (cs, startChar, endChar, insertionsAfter) { +exports.characterRangeFollow = (cs, startChar, endChar, insertionsAfter) => { let newStartChar = startChar; let newEndChar = endChar; const splices = exports.toSplices(cs); @@ -1512,7 +1473,7 @@ exports.characterRangeFollow = function (cs, startChar, endChar, insertionsAfter * @param newPool {AttribPool} new attributes pool * @return {string} the new Changeset */ -exports.moveOpsToNewPool = function (cs, oldPool, newPool) { +exports.moveOpsToNewPool = (cs, oldPool, newPool) => { // works on exports or attribution string let dollarPos = cs.indexOf('$'); if (dollarPos < 0) { @@ -1544,7 +1505,7 @@ exports.moveOpsToNewPool = function (cs, oldPool, newPool) { * create an attribution inserting a text * @param text {string} text to be inserted */ -exports.makeAttribution = function (text) { +exports.makeAttribution = (text) => { const assem = exports.smartOpAssembler(); assem.appendOpWithText('+', text); return assem.toString(); @@ -1556,7 +1517,7 @@ exports.makeAttribution = function (text) { * @param cs {Changeset} changeset * @param func {function} function to be called */ -exports.eachAttribNumber = function (cs, func) { +exports.eachAttribNumber = (cs, func) => { let dollarPos = cs.indexOf('$'); if (dollarPos < 0) { dollarPos = cs.length; @@ -1577,14 +1538,14 @@ exports.eachAttribNumber = function (cs, func) { * @param filter {function} fnc which returns true if an * attribute X (int) should be kept in the Changeset */ -exports.filterAttribNumbers = function (cs, filter) { +exports.filterAttribNumbers = (cs, filter) => { return exports.mapAttribNumbers(cs, filter); }; /** * does exactly the same as exports.filterAttribNumbers */ -exports.mapAttribNumbers = function (cs, func) { +exports.mapAttribNumbers = (cs, func) => { let dollarPos = cs.indexOf('$'); if (dollarPos < 0) { dollarPos = cs.length; @@ -1611,7 +1572,7 @@ exports.mapAttribNumbers = function (cs, func) { * @attribs attribs {string} optional, operations which insert * the text and also puts the right attributes */ -exports.makeAText = function (text, attribs) { +exports.makeAText = (text, attribs) => { return { text, attribs: (attribs || exports.makeAttribution(text)), @@ -1624,7 +1585,7 @@ exports.makeAText = function (text, attribs) { * @param atext {AText} * @param pool {AttribPool} Attribute Pool to add to */ -exports.applyToAText = function (cs, atext, pool) { +exports.applyToAText = (cs, atext, pool) => { return { text: exports.applyToText(cs, atext.text), attribs: exports.applyToAttribution(cs, atext.attribs, pool), @@ -1635,7 +1596,7 @@ exports.applyToAText = function (cs, atext, pool) { * Clones a AText structure * @param atext {AText} */ -exports.cloneAText = function (atext) { +exports.cloneAText = (atext) => { if (atext) { return { text: atext.text, @@ -1648,7 +1609,7 @@ exports.cloneAText = function (atext) { * Copies a AText structure from atext1 to atext2 * @param atext {AText} */ -exports.copyAText = function (atext1, atext2) { +exports.copyAText = (atext1, atext2) => { atext2.text = atext1.text; atext2.attribs = atext1.attribs; }; @@ -1658,7 +1619,7 @@ exports.copyAText = function (atext1, atext2) { * @param atext {AText} * @param assem Assembler like smartOpAssembler */ -exports.appendATextToAssembler = function (atext, assem) { +exports.appendATextToAssembler = (atext, assem) => { // intentionally skips last newline char of atext const iter = exports.opIterator(atext.attribs); const op = exports.newOp(); @@ -1696,7 +1657,7 @@ exports.appendATextToAssembler = function (atext, assem) { * @param cs {Changeset} * @param pool {AtributePool} */ -exports.prepareForWire = function (cs, pool) { +exports.prepareForWire = (cs, pool) => { const newPool = new AttributePool(); const newCs = exports.moveOpsToNewPool(cs, pool, newPool); return { @@ -1708,7 +1669,7 @@ exports.prepareForWire = function (cs, pool) { /** * Checks if a changeset s the identity changeset */ -exports.isIdentity = function (cs) { +exports.isIdentity = (cs) => { const unpacked = exports.unpack(cs); return unpacked.ops == '' && unpacked.oldLen == unpacked.newLen; }; @@ -1720,9 +1681,7 @@ exports.isIdentity = function (cs) { * @param key {string} string to be seached for * @param pool {AttribPool} attribute pool */ -exports.opAttributeValue = function (op, key, pool) { - return exports.attribsAttributeValue(op.attribs, key, pool); -}; +exports.opAttributeValue = (op, key, pool) => exports.attribsAttributeValue(op.attribs, key, pool); /** * returns all the values of attributes with a certain key @@ -1731,7 +1690,7 @@ exports.opAttributeValue = function (op, key, pool) { * @param key {string} string to be seached for * @param pool {AttribPool} attribute pool */ -exports.attribsAttributeValue = function (attribs, key, pool) { +exports.attribsAttributeValue = (attribs, key, pool) => { let value = ''; if (attribs) { exports.eachAttribNumber(attribs, (n) => { @@ -1748,7 +1707,7 @@ exports.attribsAttributeValue = function (attribs, key, pool) { * length oldLen. Allows to add/remove parts of it * @param oldLen {int} Old length */ -exports.builder = function (oldLen) { +exports.builder = (oldLen) => { const assem = exports.smartOpAssembler(); const o = exports.newOp(); const charBank = exports.stringAssembler(); @@ -1763,7 +1722,7 @@ exports.builder = function (oldLen) { assem.append(o); return self; }, - keepText(text, attribs, pool) { + keepText: (text, attribs, pool) => { assem.appendOpWithText('=', text, attribs, pool); return self; }, @@ -1790,7 +1749,7 @@ exports.builder = function (oldLen) { return self; }; -exports.makeAttribsString = function (opcode, attribs, pool) { +exports.makeAttribsString = (opcode, attribs, pool) => { // makeAttribsString(opcode, '*3') or makeAttribsString(opcode, [['foo','bar']], myPool) work if (!attribs) { return ''; @@ -1813,14 +1772,14 @@ exports.makeAttribsString = function (opcode, attribs, pool) { }; // like "substring" but on a single-line attribution string -exports.subattribution = function (astr, start, optEnd) { +exports.subattribution = (astr, start, optEnd) => { const iter = exports.opIterator(astr, 0); const assem = exports.smartOpAssembler(); const attOp = exports.newOp(); const csOp = exports.newOp(); const opOut = exports.newOp(); - function doCsOp() { + const doCsOp = () => { if (csOp.chars) { while (csOp.opcode && (attOp.opcode || iter.hasNext())) { if (!attOp.opcode) iter.next(attOp); @@ -1860,12 +1819,12 @@ exports.subattribution = function (astr, start, optEnd) { return assem.toString(); }; -exports.inverse = function (cs, lines, alines, pool) { +exports.inverse = (cs, lines, alines, pool) => { // lines and alines are what the exports is meant to apply to. // They may be arrays or objects with .get(i) and .length methods. // They include final newlines on lines. - function lines_get(idx) { + const lines_get = (idx) => { if (lines.get) { return lines.get(idx); } else { @@ -1873,7 +1832,7 @@ exports.inverse = function (cs, lines, alines, pool) { } } - function alines_get(idx) { + const alines_get = (idx) => { if (alines.get) { return alines.get(idx); } else { @@ -1891,7 +1850,7 @@ exports.inverse = function (cs, lines, alines, pool) { const csIter = exports.opIterator(unpacked.ops); const builder = exports.builder(unpacked.newLen); - function consumeAttribRuns(numChars, func /* (len, attribs, endsLine)*/) { + const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => { if ((!curLineOpIter) || (curLineOpIterLine != curLine)) { // create curLineOpIter and advance it to curChar curLineOpIter = exports.opIterator(alines_get(curLine)); @@ -1933,7 +1892,7 @@ exports.inverse = function (cs, lines, alines, pool) { } } - function skip(N, L) { + const skip = (N, L) => { if (L) { curLine += L; curChar = 0; @@ -1942,9 +1901,9 @@ exports.inverse = function (cs, lines, alines, pool) { } else { curChar += N; } - } + }; - function nextText(numChars) { + const nextText = (numChars) => { let len = 0; const assem = exports.stringAssembler(); const firstString = lines_get(curLine).substring(curChar); @@ -1962,7 +1921,7 @@ exports.inverse = function (cs, lines, alines, pool) { return assem.toString().substring(0, numChars); } - function cachedStrFunc(func) { + const cachedStrFunc = (func) => { const cache = {}; return function (s) { if (!cache[s]) { @@ -2019,7 +1978,7 @@ exports.inverse = function (cs, lines, alines, pool) { }; // %CLIENT FILE ENDS HERE% -exports.follow = function (cs1, cs2, reverseInsertOrder, pool) { +exports.follow = (cs1, cs2, reverseInsertOrder, pool) => { const unpacked1 = exports.unpack(cs1); const unpacked2 = exports.unpack(cs2); const len1 = unpacked1.oldLen; @@ -2160,7 +2119,7 @@ exports.follow = function (cs1, cs2, reverseInsertOrder, pool) { return exports.pack(oldLen, newLen, newOps, unpacked2.charBank); }; -exports.followAttributes = function (att1, att2, pool) { +exports.followAttributes = (att1, att2, pool) => { // The merge of two sets of attribute changes to the same text // takes the lexically-earlier value if there are two values // for the same key. Otherwise, all key/value changes from @@ -2197,7 +2156,7 @@ exports.followAttributes = function (att1, att2, pool) { return buf.toString(); }; -exports.composeWithDeletions = function (cs1, cs2, pool) { +exports.composeWithDeletions = (cs1, cs2, pool) => { const unpacked1 = exports.unpack(cs1); const unpacked2 = exports.unpack(cs2); const len1 = unpacked1.oldLen; @@ -2229,7 +2188,7 @@ exports.composeWithDeletions = function (cs1, cs2, pool) { // This function is 95% like _slicerZipperFunc, we just changed two lines to ensure it merges the attribs of deletions properly. // This is necassary for correct paddiff. But to ensure these changes doesn't affect anything else, we've created a seperate function only used for paddiffs -exports._slicerZipperFuncWithDeletions = function (attOp, csOp, opOut, pool) { +exports._slicerZipperFuncWithDeletions = (attOp, csOp, opOut, pool) => { // attOp is the op from the sequence that is being operated on, either an // attribution string or the earlier of two exportss being composed. // pool can be null if definitely not needed. From d91f2b5b077717dcb1d7a3558d46644469248616 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 15:35:53 +0000 Subject: [PATCH 028/115] lint: Changeset.js additional arrow functions --- src/static/js/Changeset.js | 50 ++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js index 18018454..6c7905a5 100644 --- a/src/static/js/Changeset.js +++ b/src/static/js/Changeset.js @@ -1336,9 +1336,7 @@ exports.attributeTester = (attribPair, pool) => { return never; } else { const re = new RegExp(`\\*${exports.numToString(attribNum)}(?!\\w)`); - return function (attribs) { - return re.test(attribs); - }; + return (attribs) => re.test(attribs); } }; @@ -1538,9 +1536,7 @@ exports.eachAttribNumber = (cs, func) => { * @param filter {function} fnc which returns true if an * attribute X (int) should be kept in the Changeset */ -exports.filterAttribNumbers = (cs, filter) => { - return exports.mapAttribNumbers(cs, filter); -}; +exports.filterAttribNumbers = (cs, filter) => exports.mapAttribNumbers(cs, filter); /** * does exactly the same as exports.filterAttribNumbers @@ -1572,12 +1568,10 @@ exports.mapAttribNumbers = (cs, func) => { * @attribs attribs {string} optional, operations which insert * the text and also puts the right attributes */ -exports.makeAText = (text, attribs) => { - return { - text, - attribs: (attribs || exports.makeAttribution(text)), - }; -}; +exports.makeAText = (text, attribs) => ({ + text, + attribs: (attribs || exports.makeAttribution(text)), +}); /** * Apply a Changeset to a AText @@ -1585,12 +1579,10 @@ exports.makeAText = (text, attribs) => { * @param atext {AText} * @param pool {AttribPool} Attribute Pool to add to */ -exports.applyToAText = (cs, atext, pool) => { - return { - text: exports.applyToText(cs, atext.text), - attribs: exports.applyToAttribution(cs, atext.attribs, pool), - }; -}; +exports.applyToAText = (cs, atext, pool) => ({ + text: exports.applyToText(cs, atext.text), + attribs: exports.applyToAttribution(cs, atext.attribs, pool), +}); /** * Clones a AText structure @@ -1714,7 +1706,7 @@ exports.builder = (oldLen) => { var self = { // attribs are [[key1,value1],[key2,value2],...] or '*0*1...' (no pool needed in latter case) - keep(N, L, attribs, pool) { + keep: (N, L, attribs, pool) => { o.opcode = '='; o.attribs = (attribs && exports.makeAttribsString('=', attribs, pool)) || ''; o.chars = N; @@ -1726,12 +1718,12 @@ exports.builder = (oldLen) => { assem.appendOpWithText('=', text, attribs, pool); return self; }, - insert(text, attribs, pool) { + insert: (text, attribs, pool) => { assem.appendOpWithText('+', text, attribs, pool); charBank.append(text); return self; }, - remove(N, L) { + remove: (N, L) => { o.opcode = '-'; o.attribs = ''; o.chars = N; @@ -1739,7 +1731,7 @@ exports.builder = (oldLen) => { assem.append(o); return self; }, - toString() { + toString: () => { assem.endDocument(); const newLen = oldLen + assem.getLengthChange(); return exports.pack(oldLen, newLen, assem.toString(), charBank.toString()); @@ -1795,7 +1787,7 @@ exports.subattribution = (astr, start, optEnd) => { } } } - } + }; csOp.opcode = '-'; csOp.chars = start; @@ -1830,7 +1822,7 @@ exports.inverse = (cs, lines, alines, pool) => { } else { return lines[idx]; } - } + }; const alines_get = (idx) => { if (alines.get) { @@ -1838,7 +1830,7 @@ exports.inverse = (cs, lines, alines, pool) => { } else { return alines[idx]; } - } + }; let curLine = 0; let curChar = 0; @@ -1890,7 +1882,7 @@ exports.inverse = (cs, lines, alines, pool) => { curLine++; curChar = 0; } - } + }; const skip = (N, L) => { if (L) { @@ -1919,17 +1911,17 @@ exports.inverse = (cs, lines, alines, pool) => { } return assem.toString().substring(0, numChars); - } + }; const cachedStrFunc = (func) => { const cache = {}; - return function (s) { + return (s) => { if (!cache[s]) { cache[s] = func(s); } return cache[s]; }; - } + }; const attribKeys = []; const attribValues = []; From 98a0e76a20e4fb5d652779caa7d37d96b93a3618 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 15:53:42 +0000 Subject: [PATCH 029/115] lint: Changeset.js opcode eqeqeq checks --- src/static/js/Changeset.js | 76 +++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js index 6c7905a5..28863ef4 100644 --- a/src/static/js/Changeset.js +++ b/src/static/js/Changeset.js @@ -308,20 +308,20 @@ exports.smartOpAssembler = () => { if (!op.opcode) return; if (!op.chars) return; - if (op.opcode == '-') { - if (lastOpcode == '=') { + if (op.opcode === '-') { + if (lastOpcode === '=') { flushKeeps(); } minusAssem.append(op); lengthChange -= op.chars; - } else if (op.opcode == '+') { - if (lastOpcode == '=') { + } else if (op.opcode === '+') { + if (lastOpcode === '=') { flushKeeps(); } plusAssem.append(op); lengthChange += op.chars; - } else if (op.opcode == '=') { - if (lastOpcode != '=') { + } else if (op.opcode === '=') { + if (lastOpcode !== '=') { flushPlusMinus(); } keepAssem.append(op); @@ -394,7 +394,7 @@ exports.mergingOpAssembler = () => { const flush = (isEndDocument) => { if (bufOp.opcode) { - if (isEndDocument && bufOp.opcode == '=' && !bufOp.attribs) { + if (isEndDocument && bufOp.opcode === '=' && !bufOp.attribs) { // final merged keep, leave it implicit } else { assem.append(bufOp); @@ -411,7 +411,7 @@ exports.mergingOpAssembler = () => { const append = (op) => { if (op.chars > 0) { - if (bufOp.opcode == op.opcode && bufOp.attribs == op.attribs) { + if (bufOp.opcode === op.opcode && bufOp.attribs === op.attribs) { if (op.lines > 0) { // bufOp and additional chars are all mergeable into a multi-line op bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars; @@ -844,7 +844,7 @@ exports.unpack = (cs) => { exports.error(`Not a exports: ${cs}`); } const oldLen = exports.parseNum(headerMatch[1]); - const changeSign = (headerMatch[2] == '>') ? 1 : -1; + const changeSign = (headerMatch[2] === '>') ? 1 : -1; const changeMag = exports.parseNum(headerMatch[3]); const newLen = oldLen + changeSign * changeMag; const opsStart = headerMatch[0].length; @@ -1019,7 +1019,7 @@ exports._slicerZipperFunc = (attOp, csOp, opOut, pool) => { // attribution string or the earlier of two exportss being composed. // pool can be null if definitely not needed. // print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource()); - if (attOp.opcode == '-') { + if (attOp.opcode === '-') { exports.copyOp(attOp, opOut); attOp.opcode = ''; } else if (!attOp.opcode) { @@ -1045,7 +1045,7 @@ exports._slicerZipperFunc = (attOp, csOp, opOut, pool) => { } } else { // delete and keep going - if (attOp.opcode == '=') { + if (attOp.opcode === '=') { opOut.opcode = '-'; opOut.chars = attOp.chars; opOut.lines = attOp.lines; @@ -1071,7 +1071,7 @@ exports._slicerZipperFunc = (attOp, csOp, opOut, pool) => { opOut.opcode = attOp.opcode; opOut.chars = csOp.chars; opOut.lines = csOp.lines; - opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); + opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode === '=', pool); csOp.opcode = ''; attOp.chars -= csOp.chars; attOp.lines -= csOp.lines; @@ -1171,12 +1171,12 @@ exports.mutateAttributionLines = (cs, lines, pool) => { // print("csOp: "+csOp.toSource()); if ((!csOp.opcode) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) { break; // done - } else if (csOp.opcode == '=' && csOp.lines > 0 && (!csOp.attribs) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) { + } else if (csOp.opcode === '=' && csOp.lines > 0 && (!csOp.attribs) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) { // skip multiple lines; this is what makes small changes not order of the document size mut.skipLines(csOp.lines); // print("skipped: "+csOp.lines); csOp.opcode = ''; - } else if (csOp.opcode == '+') { + } else if (csOp.opcode === '+') { if (csOp.lines > 1) { const firstLineLen = csBank.indexOf('\n', csBankIndex) + 1 - csBankIndex; exports.copyOp(csOp, opOut); @@ -1296,12 +1296,12 @@ exports.compose = (cs1, cs2, pool) => { // debugBuilder.append(' / '); const op1code = op1.opcode; const op2code = op2.opcode; - if (op1code == '+' && op2code == '-') { + if (op1code === '+' && op2code === '-') { bankIter1.skip(Math.min(op1.chars, op2.chars)); } exports._slicerZipperFunc(op1, op2, opOut, pool); - if (opOut.opcode == '+') { - if (op2code == '+') { + if (opOut.opcode === '+') { + if (op2code === '+') { bankAssem.append(bankIter2.take(opOut.chars)); } else { bankAssem.append(bankIter1.take(opOut.chars)); @@ -1396,7 +1396,7 @@ exports.toSplices = (cs) => { let inSplice = false; while (iter.hasNext()) { const op = iter.next(); - if (op.opcode == '=') { + if (op.opcode === '=') { oldPos += op.chars; inSplice = false; } else { @@ -1404,10 +1404,10 @@ exports.toSplices = (cs) => { splices.push([oldPos, oldPos, '']); inSplice = true; } - if (op.opcode == '-') { + if (op.opcode === '-') { oldPos += op.chars; splices[splices.length - 1][1] += op.chars; - } else if (op.opcode == '+') { + } else if (op.opcode === '+') { splices[splices.length - 1][2] += charIter.take(op.chars); } } @@ -1663,7 +1663,7 @@ exports.prepareForWire = (cs, pool) => { */ exports.isIdentity = (cs) => { const unpacked = exports.unpack(cs); - return unpacked.ops == '' && unpacked.oldLen == unpacked.newLen; + return unpacked.ops === '' && unpacked.oldLen == unpacked.newLen; }; /** @@ -1755,7 +1755,7 @@ exports.makeAttribsString = (opcode, attribs, pool) => { const result = []; for (let i = 0; i < attribs.length; i++) { const pair = attribs[i]; - if (opcode == '=' || (opcode == '+' && pair[1])) { + if (opcode === '=' || (opcode === '+' && pair[1])) { result.push(`*${exports.numToString(pool.putAttrib(pair))}`); } } @@ -1927,7 +1927,7 @@ exports.inverse = (cs, lines, alines, pool) => { const attribValues = []; while (csIter.hasNext()) { const csOp = csIter.next(); - if (csOp.opcode == '=') { + if (csOp.opcode === '=') { if (csOp.attribs) { attribKeys.length = 0; attribValues.length = 0; @@ -1954,9 +1954,9 @@ exports.inverse = (cs, lines, alines, pool) => { skip(csOp.chars, csOp.lines); builder.keep(csOp.chars, csOp.lines); } - } else if (csOp.opcode == '+') { + } else if (csOp.opcode === '+') { builder.remove(csOp.chars, csOp.lines); - } else if (csOp.opcode == '-') { + } else if (csOp.opcode === '-') { var textBank = nextText(csOp.chars); var textBankIndex = 0; consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => { @@ -1986,11 +1986,11 @@ exports.follow = (cs1, cs2, reverseInsertOrder, pool) => { const hasInsertFirst = exports.attributeTester(['insertorder', 'first'], pool); const newOps = exports.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, (op1, op2, opOut) => { - if (op1.opcode == '+' || op2.opcode == '+') { + if (op1.opcode === '+' || op2.opcode === '+') { let whichToDo; - if (op2.opcode != '+') { + if (op2.opcode !== '+') { whichToDo = 1; - } else if (op1.opcode != '+') { + } else if (op1.opcode !== '+') { whichToDo = 2; } else { // both + @@ -2029,7 +2029,7 @@ exports.follow = (cs1, cs2, reverseInsertOrder, pool) => { exports.copyOp(op2, opOut); op2.opcode = ''; } - } else if (op1.opcode == '-') { + } else if (op1.opcode === '-') { if (!op2.opcode) { op1.opcode = ''; } else if (op1.chars <= op2.chars) { @@ -2044,7 +2044,7 @@ exports.follow = (cs1, cs2, reverseInsertOrder, pool) => { op1.lines -= op2.lines; op2.opcode = ''; } - } else if (op2.opcode == '-') { + } else if (op2.opcode === '-') { exports.copyOp(op2, opOut); if (!op1.opcode) { op2.opcode = ''; @@ -2162,12 +2162,12 @@ exports.composeWithDeletions = (cs1, cs2, pool) => { const newOps = exports.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, (op1, op2, opOut) => { const op1code = op1.opcode; const op2code = op2.opcode; - if (op1code == '+' && op2code == '-') { + if (op1code === '+' && op2code === '-') { bankIter1.skip(Math.min(op1.chars, op2.chars)); } exports._slicerZipperFuncWithDeletions(op1, op2, opOut, pool); - if (opOut.opcode == '+') { - if (op2code == '+') { + if (opOut.opcode === '+') { + if (op2code === '+') { bankAssem.append(bankIter2.take(opOut.chars)); } else { bankAssem.append(bankIter1.take(opOut.chars)); @@ -2185,7 +2185,7 @@ exports._slicerZipperFuncWithDeletions = (attOp, csOp, opOut, pool) => { // attribution string or the earlier of two exportss being composed. // pool can be null if definitely not needed. // print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource()); - if (attOp.opcode == '-') { + if (attOp.opcode === '-') { exports.copyOp(attOp, opOut); attOp.opcode = ''; } else if (!attOp.opcode) { @@ -2197,7 +2197,7 @@ exports._slicerZipperFuncWithDeletions = (attOp, csOp, opOut, pool) => { { if (csOp.chars <= attOp.chars) { // delete or delete part - if (attOp.opcode == '=') { + if (attOp.opcode === '=') { opOut.opcode = '-'; opOut.chars = csOp.chars; opOut.lines = csOp.lines; @@ -2211,7 +2211,7 @@ exports._slicerZipperFuncWithDeletions = (attOp, csOp, opOut, pool) => { } } else { // delete and keep going - if (attOp.opcode == '=') { + if (attOp.opcode === '=') { opOut.opcode = '-'; opOut.chars = attOp.chars; opOut.lines = attOp.lines; @@ -2237,7 +2237,7 @@ exports._slicerZipperFuncWithDeletions = (attOp, csOp, opOut, pool) => { opOut.opcode = attOp.opcode; opOut.chars = csOp.chars; opOut.lines = csOp.lines; - opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); + opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode === '=', pool); csOp.opcode = ''; attOp.chars -= csOp.chars; attOp.lines -= csOp.lines; @@ -2249,7 +2249,7 @@ exports._slicerZipperFuncWithDeletions = (attOp, csOp, opOut, pool) => { opOut.opcode = attOp.opcode; opOut.chars = attOp.chars; opOut.lines = attOp.lines; - opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); + opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode === '=', pool); attOp.opcode = ''; csOp.chars -= attOp.chars; csOp.lines -= attOp.lines; From 23d7544763e522ef8a20f90fdc2f3ae5c0932cb7 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 15:59:52 +0000 Subject: [PATCH 030/115] lint: Changeset.js max-len --- src/static/js/Changeset.js | 54 +++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js index 28863ef4..de23fa98 100644 --- a/src/static/js/Changeset.js +++ b/src/static/js/Changeset.js @@ -541,7 +541,8 @@ exports.stringAssembler = () => { /** * This class allows to iterate and modify texts which have several lines * It is used for applying Changesets on arrays of lines - * Note from prev docs: "lines" need not be an array as long as it supports certain calls (lines_foo inside). + * Note from prev docs: "lines" need not be an array as long as it supports + * certain calls (lines_foo inside). */ exports.textLinesMutator = (lines) => { // Mutates lines, an array of strings, in place. @@ -645,7 +646,8 @@ exports.textLinesMutator = (lines) => { curLine += L; curCol = 0; } - // print(inSplice+" / "+isCurLineInSplice()+" / "+curSplice[0]+" / "+curSplice[1]+" / "+lines.length); + // print(inSplice+" / "+isCurLineInSplice()+" / "+curSplice[0]+" / + // "+curSplice[1]+" / "+lines.length); /* if (inSplice && (! isCurLineInSplice()) && (curSplice[0] + curSplice[1] < lines.length)) { print("BLAH"); putCurLineInSplice(); @@ -696,7 +698,8 @@ exports.textLinesMutator = (lines) => { curSplice[1] += L - 1; const sline = curSplice.length - 1; removed = curSplice[sline].substring(curCol) + removed; - curSplice[sline] = curSplice[sline].substring(0, curCol) + lines_get(curSplice[0] + curSplice[1]); + curSplice[sline] = curSplice[sline].substring(0, curCol) + + lines_get(curSplice[0] + curSplice[1]); curSplice[1] += 1; } } else { @@ -719,7 +722,8 @@ exports.textLinesMutator = (lines) => { } const sline = putCurLineInSplice(); removed = curSplice[sline].substring(curCol, curCol + N); - curSplice[sline] = curSplice[sline].substring(0, curCol) + curSplice[sline].substring(curCol + N); + curSplice[sline] = curSplice[sline].substring(0, curCol) + + curSplice[sline].substring(curCol + N); // debugPrint("remove"); } } @@ -761,7 +765,8 @@ exports.textLinesMutator = (lines) => { if (!curSplice[sline]) { console.error('curSplice[sline] not populated, actual curSplice contents is ', curSplice, '. Possibly related to https://github.com/ether/etherpad-lite/issues/2802'); } - curSplice[sline] = curSplice[sline].substring(0, curCol) + text + curSplice[sline].substring(curCol); + curSplice[sline] = curSplice[sline].substring(0, curCol) + text + + curSplice[sline].substring(curCol); curCol += text.length; } // debugPrint("insert"); @@ -868,7 +873,8 @@ exports.unpack = (cs) => { */ exports.pack = (oldLen, newLen, opsStr, bank) => { const lenDiff = newLen - oldLen; - const lenDiffStr = (lenDiff >= 0 ? `>${exports.numToString(lenDiff)}` : `<${exports.numToString(-lenDiff)}`); + const lenDiffStr = (lenDiff >= 0 ? `>${exports.numToString(lenDiff)}` + : `<${exports.numToString(-lenDiff)}`); const a = []; a.push('Z:', exports.numToString(oldLen), lenDiffStr, opsStr, '$', bank); return a.join(''); @@ -881,7 +887,8 @@ exports.pack = (oldLen, newLen, opsStr, bank) => { */ exports.applyToText = (cs, str) => { const unpacked = exports.unpack(cs); - exports.assert(str.length == unpacked.oldLen, 'mismatched apply: ', str.length, ' / ', unpacked.oldLen); + exports.assert(str.length == unpacked.oldLen, 'mismatched apply: ', str.length, + ' / ', unpacked.oldLen); const csIter = exports.opIterator(unpacked.ops); const bankIter = exports.stringIterator(unpacked.charBank); const strIter = exports.stringIterator(str); @@ -1071,7 +1078,8 @@ exports._slicerZipperFunc = (attOp, csOp, opOut, pool) => { opOut.opcode = attOp.opcode; opOut.chars = csOp.chars; opOut.lines = csOp.lines; - opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode === '=', pool); + opOut.attribs = exports.composeAttributes( + attOp.attribs, csOp.attribs, attOp.opcode === '=', pool); csOp.opcode = ''; attOp.chars -= csOp.chars; attOp.lines -= csOp.lines; @@ -1083,7 +1091,8 @@ exports._slicerZipperFunc = (attOp, csOp, opOut, pool) => { opOut.opcode = attOp.opcode; opOut.chars = attOp.chars; opOut.lines = attOp.lines; - opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); + opOut.attribs = exports.composeAttributes( + attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); attOp.opcode = ''; csOp.chars -= attOp.chars; csOp.lines -= attOp.lines; @@ -1109,7 +1118,8 @@ exports._slicerZipperFunc = (attOp, csOp, opOut, pool) => { exports.applyToAttribution = (cs, astr, pool) => { const unpacked = exports.unpack(cs); - return exports.applyZip(astr, 0, unpacked.ops, 0, (op1, op2, opOut) => exports._slicerZipperFunc(op1, op2, opOut, pool)); + return exports.applyZip(astr, 0, unpacked.ops, 0, + (op1, op2, opOut) => exports._slicerZipperFunc(op1, op2, opOut, pool)); }; /* exports.oneInsertedLineAtATimeOpIterator = function(opsStr, optStartIndex, charBank) { @@ -1167,11 +1177,13 @@ exports.mutateAttributionLines = (cs, lines, pool) => { csIter.next(csOp); } // print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource()); - // print(csOp.opcode+"/"+csOp.lines+"/"+csOp.attribs+"/"+lineAssem+"/"+lineIter+"/"+(lineIter?lineIter.hasNext():null)); + // print(csOp.opcode+"/"+csOp.lines+"/"+csOp.attribs+"/"+lineAssem+ + // "/"+lineIter+"/"+(lineIter?lineIter.hasNext():null)); // print("csOp: "+csOp.toSource()); if ((!csOp.opcode) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) { break; // done - } else if (csOp.opcode === '=' && csOp.lines > 0 && (!csOp.attribs) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) { + } else if (csOp.opcode === '=' && csOp.lines > 0 && (!csOp.attribs) && + (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) { // skip multiple lines; this is what makes small changes not order of the document size mut.skipLines(csOp.lines); // print("skipped: "+csOp.lines); @@ -1776,7 +1788,8 @@ exports.subattribution = (astr, start, optEnd) => { while (csOp.opcode && (attOp.opcode || iter.hasNext())) { if (!attOp.opcode) iter.next(attOp); - if (csOp.opcode && attOp.opcode && csOp.chars >= attOp.chars && attOp.lines > 0 && csOp.lines <= 0) { + if (csOp.opcode && attOp.opcode && csOp.chars >= attOp.chars && + attOp.lines > 0 && csOp.lines <= 0) { csOp.lines++; } @@ -1872,7 +1885,8 @@ exports.inverse = (cs, lines, alines, pool) => { curLineOpIter.next(curLineNextOp); } const charsToUse = Math.min(numChars, curLineNextOp.chars); - func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0); + func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && + curLineNextOp.lines > 0); numChars -= charsToUse; curLineNextOp.chars -= charsToUse; curChar += charsToUse; @@ -2178,8 +2192,10 @@ exports.composeWithDeletions = (cs1, cs2, pool) => { return exports.pack(len1, len3, newOps, bankAssem.toString()); }; -// This function is 95% like _slicerZipperFunc, we just changed two lines to ensure it merges the attribs of deletions properly. -// This is necassary for correct paddiff. But to ensure these changes doesn't affect anything else, we've created a seperate function only used for paddiffs +// This function is 95% like _slicerZipperFunc, we just changed two lines to +// ensure it merges the attribs of deletions properly. +// This is necassary for correct paddiff. But to ensure these changes doesn't +// affect anything else, we've created a seperate function only used for paddiffs exports._slicerZipperFuncWithDeletions = (attOp, csOp, opOut, pool) => { // attOp is the op from the sequence that is being operated on, either an // attribution string or the earlier of two exportss being composed. @@ -2237,7 +2253,8 @@ exports._slicerZipperFuncWithDeletions = (attOp, csOp, opOut, pool) => { opOut.opcode = attOp.opcode; opOut.chars = csOp.chars; opOut.lines = csOp.lines; - opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode === '=', pool); + opOut.attribs = exports.composeAttributes( + attOp.attribs, csOp.attribs, attOp.opcode === '=', pool); csOp.opcode = ''; attOp.chars -= csOp.chars; attOp.lines -= csOp.lines; @@ -2249,7 +2266,8 @@ exports._slicerZipperFuncWithDeletions = (attOp, csOp, opOut, pool) => { opOut.opcode = attOp.opcode; opOut.chars = attOp.chars; opOut.lines = attOp.lines; - opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode === '=', pool); + opOut.attribs = exports.composeAttributes( + attOp.attribs, csOp.attribs, attOp.opcode === '=', pool); attOp.opcode = ''; csOp.chars -= attOp.chars; csOp.lines -= attOp.lines; From 0b78ad2f90bfb9fadc795d9e9f72d34e7d2fe21a Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 16:02:10 +0000 Subject: [PATCH 031/115] lint: Changeset.js curly braces in correct position --- src/static/js/Changeset.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js index de23fa98..71e1eb8e 100644 --- a/src/static/js/Changeset.js +++ b/src/static/js/Changeset.js @@ -2016,21 +2016,19 @@ exports.follow = (cs1, cs2, reverseInsertOrder, pool) => { whichToDo = 1; } else if (insertFirst2 && !insertFirst1) { whichToDo = 2; - } - // insert string that doesn't start with a newline first so as not to break up lines - else if (firstChar1 == '\n' && firstChar2 != '\n') { + } else if (firstChar1 === '\n' && firstChar2 !== '\n') { + // insert string that doesn't start with a newline first so as not to break up lines whichToDo = 2; - } else if (firstChar1 != '\n' && firstChar2 == '\n') { + } else if (firstChar1 !== '\n' && firstChar2 === '\n') { whichToDo = 1; - } - // break symmetry: - else if (reverseInsertOrder) { + } else if (reverseInsertOrder) { + // break symmetry: whichToDo = 2; } else { whichToDo = 1; } } - if (whichToDo == 1) { + if (whichToDo === 1) { chars1.skip(op1.chars); opOut.opcode = '='; opOut.lines = op1.lines; From 3b5b996d846ede758cb33b818f975a95184a4bbb Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 16:15:36 +0000 Subject: [PATCH 032/115] lint: Changeset no var --- src/static/js/Changeset.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js index 71e1eb8e..da0d92d3 100644 --- a/src/static/js/Changeset.js +++ b/src/static/js/Changeset.js @@ -612,7 +612,7 @@ exports.textLinesMutator = (lines) => { const isCurLineInSplice = () => (curLine - curSplice[0] < (curSplice.length - 2)); - const debugPrint = (typ) => { + const debugPrint = (typ) => { /* eslint-disable-line no-unused-vars */ print(`${typ}: ${curSplice.toSource()} / ${curLine},${curCol} / ${lines_toSource()}`); }; @@ -745,7 +745,7 @@ exports.textLinesMutator = (lines) => { // curLine += newLines.length; // } // else { - var sline = curSplice.length - 1; + const sline = curSplice.length - 1; const theLine = curSplice[sline]; const lineCol = curCol; curSplice[sline] = theLine.substring(0, lineCol) + newLines[0]; @@ -761,7 +761,7 @@ exports.textLinesMutator = (lines) => { curLine += newLines.length; } } else { - var sline = putCurLineInSplice(); + const sline = putCurLineInSplice(); if (!curSplice[sline]) { console.error('curSplice[sline] not populated, actual curSplice contents is ', curSplice, '. Possibly related to https://github.com/ether/etherpad-lite/issues/2802'); } @@ -1716,7 +1716,7 @@ exports.builder = (oldLen) => { const o = exports.newOp(); const charBank = exports.stringAssembler(); - var self = { + const self = { // attribs are [[key1,value1],[key2,value2],...] or '*0*1...' (no pool needed in latter case) keep: (N, L, attribs, pool) => { o.opcode = '='; @@ -1949,7 +1949,7 @@ exports.inverse = (cs, lines, alines, pool) => { attribKeys.push(pool.getAttribKey(n)); attribValues.push(pool.getAttribValue(n)); }); - var undoBackToAttribs = cachedStrFunc((attribs) => { + const undoBackToAttribs = cachedStrFunc((attribs) => { const backAttribs = []; for (let i = 0; i < attribKeys.length; i++) { const appliedKey = attribKeys[i]; @@ -1971,8 +1971,8 @@ exports.inverse = (cs, lines, alines, pool) => { } else if (csOp.opcode === '+') { builder.remove(csOp.chars, csOp.lines); } else if (csOp.opcode === '-') { - var textBank = nextText(csOp.chars); - var textBankIndex = 0; + const textBank = nextText(csOp.chars); + let textBankIndex = 0; consumeAttribRuns(csOp.chars, (len, attribs, endsLine) => { builder.insert(textBank.substr(textBankIndex, len), attribs); textBankIndex += len; From d4b6cbc897af19cc4e9f150a7d9898bf4d46be8c Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 16:24:47 +0000 Subject: [PATCH 033/115] line: broadcast.js --- src/static/js/broadcast.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/static/js/broadcast.js b/src/static/js/broadcast.js index 1d35884b..26370f6b 100644 --- a/src/static/js/broadcast.js +++ b/src/static/js/broadcast.js @@ -33,7 +33,7 @@ const hooks = require('./pluginfw/hooks'); // These parameters were global, now they are injected. A reference to the // Timeslider controller would probably be more appropriate. -function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider) { +const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider) => { let goToRevisionIfEnabledCount = 0; let changesetLoader = undefined; @@ -488,6 +488,6 @@ function loadBroadcastJS(socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, Bro receiveAuthorData(clientVars.collab_client_vars.historicalAuthorData); return changesetLoader; -} +}; exports.loadBroadcastJS = loadBroadcastJS; From f5f4e3a6d1047fe0a51050201c8667bb7f0028cb Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 16:28:33 +0000 Subject: [PATCH 034/115] lint: changesettracker.js arrow funcs --- src/static/js/changesettracker.js | 32 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/static/js/changesettracker.js b/src/static/js/changesettracker.js index 8b0ac18c..d6492339 100644 --- a/src/static/js/changesettracker.js +++ b/src/static/js/changesettracker.js @@ -1,3 +1,5 @@ +'use strict'; + /** * This code is mostly from the old Etherpad. Please help us to comment this code. * This helps other people to understand this code better and helps them to improve it. @@ -23,7 +25,7 @@ const AttributePool = require('./AttributePool'); const Changeset = require('./Changeset'); -function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) { +const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => { // latest official text from server let baseAText = Changeset.makeAText('\n'); // changes applied to baseText that have been submitted @@ -42,7 +44,7 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) { let changeCallbackTimeout = null; - function setChangeCallbackTimeout() { + const setChangeCallbackTimeout = () => { // can call this multiple times per call-stack, because // we only schedule a call to changeCallback if it exists // and if there isn't a timeout already scheduled. @@ -55,17 +57,15 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) { } }, 0); } - } + }; let self; return self = { - isTracking() { - return tracking; - }, - setBaseText(text) { + isTracking: () => tracking, + setBaseText: (text) => { self.setBaseAttributedText(Changeset.makeAText(text), null); }, - setBaseAttributedText(atext, apoolJsonObj) { + setBaseAttributedText: (atext, apoolJsonObj) => { aceCallbacksProvider.withCallbacks('setBaseText', (callbacks) => { tracking = true; baseAText = Changeset.cloneAText(atext); @@ -83,7 +83,7 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) { } }); }, - composeUserChangeset(c) { + composeUserChangeset: (c) => { if (!tracking) return; if (applyingNonUserChanges) return; if (Changeset.isIdentity(c)) return; @@ -91,7 +91,7 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) { setChangeCallbackTimeout(); }, - applyChangesToBase(c, optAuthor, apoolJsonObj) { + applyChangesToBase: (c, optAuthor, apoolJsonObj) => { if (!tracking) return; aceCallbacksProvider.withCallbacks('applyChangesToBase', (callbacks) => { @@ -123,7 +123,7 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) { } }); }, - prepareUserChangeset() { + prepareUserChangeset: () => { // If there are user changes to submit, 'changeset' will be the // changeset, else it will be null. let toSubmit; @@ -201,7 +201,7 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) { }; return data; }, - applyPreparedChangesetToBase() { + applyPreparedChangesetToBase: () => { if (!submittedChangeset) { // violation of protocol; use prepareUserChangeset first throw new Error('applySubmittedChangesToBase: no submitted changes to apply'); @@ -210,13 +210,11 @@ function makeChangesetTracker(scheduler, apool, aceCallbacksProvider) { baseAText = Changeset.applyToAText(submittedChangeset, baseAText, apool); submittedChangeset = null; }, - setUserChangeNotificationCallback(callback) { + setUserChangeNotificationCallback: (callback) => { changeCallback = callback; }, - hasUncommittedChanges() { - return !!(submittedChangeset || (!Changeset.isIdentity(userChangeset))); - }, + hasUncommittedChanges: () => !!(submittedChangeset || (!Changeset.isIdentity(userChangeset))), }; -} +}; exports.makeChangesetTracker = makeChangesetTracker; From 3635cb6ca6b95ccc851aaaa21d6132f4ae744041 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 16:29:33 +0000 Subject: [PATCH 035/115] lint: changesettracker.js long-lines --- src/static/js/changesettracker.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/static/js/changesettracker.js b/src/static/js/changesettracker.js index d6492339..7ddc54a1 100644 --- a/src/static/js/changesettracker.js +++ b/src/static/js/changesettracker.js @@ -111,8 +111,10 @@ const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => { const preferInsertingAfterUserChanges = true; const oldUserChangeset = userChangeset; - userChangeset = Changeset.follow(c2, oldUserChangeset, preferInsertingAfterUserChanges, apool); - const postChange = Changeset.follow(oldUserChangeset, c2, !preferInsertingAfterUserChanges, apool); + userChangeset = Changeset.follow( + c2, oldUserChangeset, preferInsertingAfterUserChanges, apool); + const postChange = Changeset.follow( + oldUserChangeset, c2, !preferInsertingAfterUserChanges, apool); const preferInsertionAfterCaret = true; // (optAuthor && optAuthor > thisAuthor); applyingNonUserChanges = true; @@ -135,7 +137,9 @@ const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => { // add forEach function to Array.prototype for IE8 if (!('forEach' in Array.prototype)) { Array.prototype.forEach = function (action, that /* opt*/) { - for (let i = 0, n = this.length; i < n; i++) if (i in this) action.call(that, this[i], i, this); + for (let i = 0, n = this.length; i < n; i++) { + if (i in this) action.call(that, this[i], i, this); + } }; } @@ -143,10 +147,13 @@ const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => { const authorId = parent.parent.pad.myUserInfo.userId; // Sanitize authorship - // We need to replace all author attribs with thisSession.author, in case they copy/pasted or otherwise inserted other peoples changes + // We need to replace all author attribs with thisSession.author, + // in case they copy/pasted or otherwise inserted other peoples changes if (apool.numToAttrib) { for (const attr in apool.numToAttrib) { - if (apool.numToAttrib[attr][0] == 'author' && apool.numToAttrib[attr][1] == authorId) var authorAttr = Number(attr).toString(36); + if (apool.numToAttrib[attr][0] == 'author' && apool.numToAttrib[attr][1] == authorId) { + var authorAttr = Number(attr).toString(36); + } } // Replace all added 'author' attribs with the value of the current user From f86578ffc35986deb51838f8ee6e71991d7f29dc Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 16:31:56 +0000 Subject: [PATCH 036/115] lint: changesettracker.js var > const/let and other easy fixes --- src/static/js/changesettracker.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/static/js/changesettracker.js b/src/static/js/changesettracker.js index 7ddc54a1..6a132247 100644 --- a/src/static/js/changesettracker.js +++ b/src/static/js/changesettracker.js @@ -48,11 +48,13 @@ const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => { // can call this multiple times per call-stack, because // we only schedule a call to changeCallback if it exists // and if there isn't a timeout already scheduled. - if (changeCallback && changeCallbackTimeout === null) { + if (changeCallback && changeCallbackTimeout == null) { changeCallbackTimeout = scheduler.setTimeout(() => { try { changeCallback(); - } catch (pseudoError) {} finally { + } catch (pseudoError) { + // as empty as my soul + } finally { changeCallbackTimeout = null; } }, 0); @@ -150,28 +152,30 @@ const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => { // We need to replace all author attribs with thisSession.author, // in case they copy/pasted or otherwise inserted other peoples changes if (apool.numToAttrib) { + let authorAttr; for (const attr in apool.numToAttrib) { - if (apool.numToAttrib[attr][0] == 'author' && apool.numToAttrib[attr][1] == authorId) { - var authorAttr = Number(attr).toString(36); + if (apool.numToAttrib[attr][0] === 'author' && + apool.numToAttrib[attr][1] === authorId) { + authorAttr = Number(attr).toString(36); } } // Replace all added 'author' attribs with the value of the current user - var cs = Changeset.unpack(userChangeset); + const cs = Changeset.unpack(userChangeset); const iterator = Changeset.opIterator(cs.ops); let op; const assem = Changeset.mergingOpAssembler(); while (iterator.hasNext()) { op = iterator.next(); - if (op.opcode == '+') { - var newAttrs = ''; + if (op.opcode === '+') { + let newAttrs = ''; op.attribs.split('*').forEach((attrNum) => { if (!attrNum) return; const attr = apool.getAttrib(parseInt(attrNum, 36)); if (!attr) return; - if ('author' == attr[0]) { + if ('author' === attr[0]) { // replace that author with the current one newAttrs += `*${authorAttr}`; } else { newAttrs += `*${attrNum}`; } // overtake all other attribs as is @@ -188,7 +192,7 @@ const makeChangesetTracker = (scheduler, apool, aceCallbacksProvider) => { else toSubmit = userChangeset; } - var cs = null; + let cs = null; if (toSubmit) { submittedChangeset = toSubmit; userChangeset = Changeset.identity(Changeset.newLen(toSubmit)); From 73b3a2dc54bc5b85ec06069e2b1aebd02b3ef1ec Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 17:25:06 +0000 Subject: [PATCH 037/115] lint: AttributeManager.js use ES6 method for hasAttrib --- src/static/js/AttributeManager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/static/js/AttributeManager.js b/src/static/js/AttributeManager.js index 19424b0a..7a37c8ff 100644 --- a/src/static/js/AttributeManager.js +++ b/src/static/js/AttributeManager.js @@ -406,7 +406,8 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({ hasAttrib = this.getAttributeOnSelection(attributeName); } else { const attributesOnCaretPosition = this.getAttributesOnCaret(); - hasAttrib = _.contains(_.flatten(attributesOnCaretPosition), attributeName); + const allAttribs = [].concat(...attributesOnCaretPosition); // flatten + hasAttrib = allAttribs.includes(attributeName); } return hasAttrib; }, From 8cbd5222dd9009cd0b83fffd4f02216c87d70b90 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 17:49:59 +0000 Subject: [PATCH 038/115] lint: pad_userlist.js - remove require browser --- src/static/js/pad_userlist.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/static/js/pad_userlist.js b/src/static/js/pad_userlist.js index a43ef69a..074798fc 100644 --- a/src/static/js/pad_userlist.js +++ b/src/static/js/pad_userlist.js @@ -18,7 +18,6 @@ const padutils = require('./pad_utils').padutils; const hooks = require('./pluginfw/hooks'); -const browser = require('./browser'); let myUserInfo = {}; From 2511eed472c85dd479192fbbf8d1a4981373bcf2 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 18:15:01 +0000 Subject: [PATCH 039/115] lint: Changeset.js more literal conditionals --- src/static/js/Changeset.js | 42 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/static/js/Changeset.js b/src/static/js/Changeset.js index da0d92d3..75bb92b2 100644 --- a/src/static/js/Changeset.js +++ b/src/static/js/Changeset.js @@ -417,7 +417,7 @@ exports.mergingOpAssembler = () => { bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars; bufOp.lines += op.lines; bufOpAdditionalCharsAfterNewline = 0; - } else if (bufOp.lines == 0) { + } else if (bufOp.lines === 0) { // both bufOp and op are in-line bufOp.chars += op.chars; } else { @@ -687,7 +687,7 @@ exports.textLinesMutator = (lines) => { }; if (isCurLineInSplice()) { // print(curCol); - if (curCol == 0) { + if (curCol === 0) { removed = curSplice[curSplice.length - 1]; // print("FOO"); // case foo curSplice.length--; @@ -887,7 +887,7 @@ exports.pack = (oldLen, newLen, opsStr, bank) => { */ exports.applyToText = (cs, str) => { const unpacked = exports.unpack(cs); - exports.assert(str.length == unpacked.oldLen, 'mismatched apply: ', str.length, + exports.assert(str.length === unpacked.oldLen, 'mismatched apply: ', str.length, ' / ', unpacked.oldLen); const csIter = exports.opIterator(unpacked.ops); const bankIter = exports.stringIterator(unpacked.charBank); @@ -899,7 +899,7 @@ exports.applyToText = (cs, str) => { case '+': // op is + and op.lines 0: no newlines must be in op.chars // op is + and op.lines >0: op.chars must include op.lines newlines - if (op.lines != bankIter.peek(op.chars).split('\n').length - 1) { + if (op.lines !== bankIter.peek(op.chars).split('\n').length - 1) { throw new Error(`newline count is wrong in op +; cs:${cs} and text:${str}`); } assem.append(bankIter.take(op.chars)); @@ -907,7 +907,7 @@ exports.applyToText = (cs, str) => { case '-': // op is - and op.lines 0: no newlines must be in the deleted string // op is - and op.lines >0: op.lines newlines must be in the deleted string - if (op.lines != strIter.peek(op.chars).split('\n').length - 1) { + if (op.lines !== strIter.peek(op.chars).split('\n').length - 1) { throw new Error(`newline count is wrong in op -; cs:${cs} and text:${str}`); } strIter.skip(op.chars); @@ -915,7 +915,7 @@ exports.applyToText = (cs, str) => { case '=': // op is = and op.lines 0: no newlines must be in the copied string // op is = and op.lines >0: op.lines newlines must be in the copied string - if (op.lines != strIter.peek(op.chars).split('\n').length - 1) { + if (op.lines !== strIter.peek(op.chars).split('\n').length - 1) { throw new Error(`newline count is wrong in op =; cs:${cs} and text:${str}`); } assem.append(strIter.take(op.chars)); @@ -992,7 +992,7 @@ exports.composeAttributes = (att1, att2, resultIsMutation, pool) => { let found = false; for (let i = 0; i < atts.length; i++) { const oldPair = atts[i]; - if (oldPair[0] == pair[0]) { + if (oldPair[0] === pair[0]) { if (pair[1] || resultIsMutation) { oldPair[1] = pair[1]; } else { @@ -1038,7 +1038,7 @@ exports._slicerZipperFunc = (attOp, csOp, opOut, pool) => { { if (csOp.chars <= attOp.chars) { // delete or delete part - if (attOp.opcode == '=') { + if (attOp.opcode === '=') { opOut.opcode = '-'; opOut.chars = csOp.chars; opOut.lines = csOp.lines; @@ -1092,7 +1092,7 @@ exports._slicerZipperFunc = (attOp, csOp, opOut, pool) => { opOut.chars = attOp.chars; opOut.lines = attOp.lines; opOut.attribs = exports.composeAttributes( - attOp.attribs, csOp.attribs, attOp.opcode == '=', pool); + attOp.attribs, csOp.attribs, attOp.opcode === '=', pool); attOp.opcode = ''; csOp.chars -= attOp.chars; csOp.lines -= attOp.lines; @@ -1162,7 +1162,7 @@ exports.mutateAttributionLines = (cs, lines, pool) => { } lineAssem.append(op); if (op.lines > 0) { - exports.assert(op.lines == 1, "Can't have op.lines of ", op.lines, ' in attribution lines'); + exports.assert(op.lines === 1, "Can't have op.lines of ", op.lines, ' in attribution lines'); // ship it to the mut mut.insert(lineAssem.toString(), 1); lineAssem = null; @@ -1267,7 +1267,7 @@ exports.splitAttributionLines = (attrOps, text) => { numChars -= op.chars; numLines -= op.lines; } - if (numLines == 1) { + if (numLines === 1) { op.chars = numChars; op.lines = 1; } @@ -1294,7 +1294,7 @@ exports.compose = (cs1, cs2, pool) => { const unpacked2 = exports.unpack(cs2); const len1 = unpacked1.oldLen; const len2 = unpacked1.newLen; - exports.assert(len2 == unpacked2.oldLen, 'mismatched composition of two changesets'); + exports.assert(len2 === unpacked2.oldLen, 'mismatched composition of two changesets'); const len3 = unpacked2.newLen; const bankIter1 = exports.stringIterator(unpacked1.charBank); const bankIter2 = exports.stringIterator(unpacked2.charBank); @@ -1675,7 +1675,7 @@ exports.prepareForWire = (cs, pool) => { */ exports.isIdentity = (cs) => { const unpacked = exports.unpack(cs); - return unpacked.ops === '' && unpacked.oldLen == unpacked.newLen; + return unpacked.ops === '' && unpacked.oldLen === unpacked.newLen; }; /** @@ -1698,7 +1698,7 @@ exports.attribsAttributeValue = (attribs, key, pool) => { let value = ''; if (attribs) { exports.eachAttribNumber(attribs, (n) => { - if (pool.getAttribKey(n) == key) { + if (pool.getAttribKey(n) === key) { value = pool.getAttribValue(n); } }); @@ -1856,7 +1856,7 @@ exports.inverse = (cs, lines, alines, pool) => { const builder = exports.builder(unpacked.newLen); const consumeAttribRuns = (numChars, func /* (len, attribs, endsLine)*/) => { - if ((!curLineOpIter) || (curLineOpIterLine != curLine)) { + if ((!curLineOpIter) || (curLineOpIterLine !== curLine)) { // create curLineOpIter and advance it to curChar curLineOpIter = exports.opIterator(alines_get(curLine)); curLineOpIterLine = curLine; @@ -1885,7 +1885,7 @@ exports.inverse = (cs, lines, alines, pool) => { curLineOpIter.next(curLineNextOp); } const charsToUse = Math.min(numChars, curLineNextOp.chars); - func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && + func(charsToUse, curLineNextOp.attribs, charsToUse === curLineNextOp.chars && curLineNextOp.lines > 0); numChars -= charsToUse; curLineNextOp.chars -= charsToUse; @@ -1902,7 +1902,7 @@ exports.inverse = (cs, lines, alines, pool) => { if (L) { curLine += L; curChar = 0; - } else if (curLineOpIter && curLineOpIterLine == curLine) { + } else if (curLineOpIter && curLineOpIterLine === curLine) { consumeAttribRuns(N, () => {}); } else { curChar += N; @@ -1955,7 +1955,7 @@ exports.inverse = (cs, lines, alines, pool) => { const appliedKey = attribKeys[i]; const appliedValue = attribValues[i]; const oldValue = exports.attribsAttributeValue(attribs, appliedKey, pool); - if (appliedValue != oldValue) { + if (appliedValue !== oldValue) { backAttribs.push([appliedKey, oldValue]); } } @@ -1989,7 +1989,7 @@ exports.follow = (cs1, cs2, reverseInsertOrder, pool) => { const unpacked2 = exports.unpack(cs2); const len1 = unpacked1.oldLen; const len2 = unpacked2.oldLen; - exports.assert(len1 == len2, 'mismatched follow - cannot transform cs1 on top of cs2'); + exports.assert(len1 === len2, 'mismatched follow - cannot transform cs1 on top of cs2'); const chars1 = exports.stringIterator(unpacked1.charBank); const chars2 = exports.stringIterator(unpacked2.charBank); @@ -2141,7 +2141,7 @@ exports.followAttributes = (att1, att2, pool) => { const pair1 = pool.getAttrib(exports.parseNum(a)); for (let i = 0; i < atts.length; i++) { const pair2 = atts[i]; - if (pair1[0] == pair2[0]) { + if (pair1[0] === pair2[0]) { if (pair1[1] <= pair2[1]) { // winner of merge is pair1, delete this attribute atts.splice(i, 1); @@ -2165,7 +2165,7 @@ exports.composeWithDeletions = (cs1, cs2, pool) => { const unpacked2 = exports.unpack(cs2); const len1 = unpacked1.oldLen; const len2 = unpacked1.newLen; - exports.assert(len2 == unpacked2.oldLen, 'mismatched composition of two changesets'); + exports.assert(len2 === unpacked2.oldLen, 'mismatched composition of two changesets'); const len3 = unpacked2.newLen; const bankIter1 = exports.stringIterator(unpacked1.charBank); const bankIter2 = exports.stringIterator(unpacked2.charBank); From 77b2f372abb3f71044ae51a190190a7aeefe9e35 Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 17 Feb 2021 18:31:38 +0000 Subject: [PATCH 040/115] lint: pad_userlist.js arrow functions This probably needs a good rewrite/refactor to remove self. --- src/static/js/pad_userlist.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/static/js/pad_userlist.js b/src/static/js/pad_userlist.js index 074798fc..07c22334 100644 --- a/src/static/js/pad_userlist.js +++ b/src/static/js/pad_userlist.js @@ -24,8 +24,8 @@ let myUserInfo = {}; let colorPickerOpen = false; let colorPickerSetup = false; -const paduserlist = (function () { - const rowManager = (function () { +const paduserlist = (() => { + const rowManager = (() => { // The row manager handles rendering rows of the user list and animating // their insertion, removal, and reordering. It manipulates TD height // and TD opacity. @@ -290,7 +290,7 @@ const paduserlist = (function () { updateRow, }; return self; - }()); // //////// rowManager + })(); // //////// rowManager const otherUsersInfo = []; const otherUsersData = []; @@ -346,7 +346,7 @@ const paduserlist = (function () { let pad = undefined; const self = { - init(myInitialUserInfo, _pad) { + init: (myInitialUserInfo, _pad) => { pad = _pad; self.setMyUserInfo(myInitialUserInfo); @@ -543,7 +543,7 @@ const paduserlist = (function () { }, }; return self; -}()); +})(); const getColorPickerSwatchIndex = (jnode) => $('#colorpickerswatches li').index(jnode); From ee2b32281c538490583955b51614bbfc3bfebff0 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 21 Feb 2021 11:07:13 +0000 Subject: [PATCH 041/115] pluginfw: Warn plugins on missing plugin (#4826) * pluginfw: Warn plugins on missing plugin Add functionality to console.warn when a plugin is missing. This will help admins know when people are trying to use plugins that are missing. Resolves https://github.com/ether/etherpad-lite/issues/4730 * pluginfw: importing .etherpad can notify admins of missing plugins Extending .etherpad imports to notify admins if a missing plugin is present * Update ImportEtherpad.js --- src/node/utils/ImportEtherpad.js | 18 ++++++++++++++++++ src/static/js/contentcollector.js | 4 ++++ 2 files changed, 22 insertions(+) diff --git a/src/node/utils/ImportEtherpad.js b/src/node/utils/ImportEtherpad.js index bc46d10b..6494da56 100644 --- a/src/node/utils/ImportEtherpad.js +++ b/src/node/utils/ImportEtherpad.js @@ -22,6 +22,13 @@ const hooks = require('../../static/js/pluginfw/hooks'); exports.setPadRaw = (padId, r) => { const records = JSON.parse(r); + const blockElems = ['div', 'br', 'p', 'pre', 'li', 'author', 'lmkr', 'insertorder']; + + // get supported block Elements from plugins, we will use this later. + hooks.callAll('ccRegisterBlockElements').forEach((element) => { + blockElems.push(element); + }); + Object.keys(records).forEach(async (key) => { let value = records[key]; @@ -53,6 +60,17 @@ exports.setPadRaw = (padId, r) => { } else { // Not author data, probably pad data // we can split it to look to see if it's pad data + + // is this an attribute we support or not? If not, tell the admin + if (value.pool) { + for (const attrib of Object.keys(value.pool.numToAttrib)) { + const attribName = value.pool.numToAttrib[attrib][0]; + if(blockElems.indexOf(attribName) === -1) { + console.warn('Plugin missing: ' + + `You might want to install a plugin to support this node name: ${attribName}`); + } + } + } const oldPadId = key.split(':'); // we know it's pad data diff --git a/src/static/js/contentcollector.js b/src/static/js/contentcollector.js index 7ac27168..1c601cc5 100644 --- a/src/static/js/contentcollector.js +++ b/src/static/js/contentcollector.js @@ -315,6 +315,10 @@ const makeContentCollector = (collectStyles, abrowser, apool, className2Author) const localAttribs = state.localAttribs; state.localAttribs = null; const isBlock = isBlockElement(node); + if (!isBlock && node.name && (node.name !== 'body') && (node.name !== 'br')) { + console.warn('Plugin missing: ' + + `You might want to install a plugin to support this node name: ${node.name}`); + } const isEmpty = _isEmpty(node, state); if (isBlock) _ensureColumnZero(state); const startLine = lines.length() - 1; From 227370547d0968a444762d55f612843a0d2024bc Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 18 Feb 2021 19:58:20 +0000 Subject: [PATCH 042/115] update openapi-backend --- src/package-lock.json | 118 +++++++++++++++++------------------------- src/package.json | 2 +- 2 files changed, 48 insertions(+), 72 deletions(-) diff --git a/src/package-lock.json b/src/package-lock.json index 5c4b4b14..8a343b52 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -5,39 +5,15 @@ "requires": true, "dependencies": { "@apidevtools/json-schema-ref-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-8.0.0.tgz", - "integrity": "sha512-n4YBtwQhdpLto1BaUCyAeflizmIbaloGShsPyRtFf5qdFJxfssj+GgLavczgKJFa3Bq+3St2CKcpRJdjtB4EBw==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.7.tgz", + "integrity": "sha512-QdwOGF1+eeyFh+17v2Tz626WX0nucd1iKOm6JUTUvCZdbolblCOOQCxGrQPY0f7jEhn36PiAWqZnsC2r5vmUWg==", "requires": { - "@jsdevtools/ono": "^7.1.0", + "@jsdevtools/ono": "^7.1.3", "call-me-maybe": "^1.0.1", "js-yaml": "^3.13.1" } }, - "@apidevtools/openapi-schemas": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.0.4.tgz", - "integrity": "sha512-ob5c4UiaMYkb24pNhvfSABShAwpREvUGCkqjiz/BX9gKZ32y/S22M+ALIHftTAuv9KsFVSpVdIDzi9ZzFh5TCA==" - }, - "@apidevtools/swagger-methods": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", - "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" - }, - "@apidevtools/swagger-parser": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-9.0.1.tgz", - "integrity": "sha512-Irqybg4dQrcHhZcxJc/UM4vO7Ksoj1Id5e+K94XUOzllqX1n47HEA50EKiXTCQbykxuJ4cYGIivjx/MRSTC5OA==", - "requires": { - "@apidevtools/json-schema-ref-parser": "^8.0.0", - "@apidevtools/openapi-schemas": "^2.0.2", - "@apidevtools/swagger-methods": "^3.0.0", - "@jsdevtools/ono": "^7.1.0", - "call-me-maybe": "^1.0.1", - "openapi-types": "^1.3.5", - "z-schema": "^4.2.2" - } - }, "@azure/ms-rest-azure-env": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@azure/ms-rest-azure-env/-/ms-rest-azure-env-2.0.0.tgz", @@ -2921,12 +2897,14 @@ "jsonschema": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.4.tgz", - "integrity": "sha512-lz1nOH69GbsVHeVgEdvyavc/33oymY1AZwtePMiMj4HZPMbP5OIKK3zT9INMWjwua/V4Z4yq7wSlBbSG+g4AEw==" + "integrity": "sha512-lz1nOH69GbsVHeVgEdvyavc/33oymY1AZwtePMiMj4HZPMbP5OIKK3zT9INMWjwua/V4Z4yq7wSlBbSG+g4AEw==", + "dev": true }, "jsonschema-draft4": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/jsonschema-draft4/-/jsonschema-draft4-1.0.0.tgz", - "integrity": "sha1-8K8gBQVPDwrefqIRhhS2ncUS2GU=" + "integrity": "sha1-8K8gBQVPDwrefqIRhhS2ncUS2GU=", + "dev": true }, "jsprim": { "version": "1.4.1", @@ -3091,12 +3069,8 @@ "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" - }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true }, "lodash.isplainobject": { "version": "4.0.6", @@ -3423,6 +3397,13 @@ "requires": { "lodash": "^4.17.11", "openapi-types": "^1.3.2" + }, + "dependencies": { + "openapi-types": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-1.3.5.tgz", + "integrity": "sha512-11oi4zYorsgvg5yBarZplAqbpev5HkuVNPlZaPTknPDzAynq+lnJdXAmruGWP0s+dNYZS7bjM+xrTpJw7184Fg==" + } } }, "mongodb": { @@ -6952,35 +6933,54 @@ } }, "openapi-backend": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/openapi-backend/-/openapi-backend-2.4.1.tgz", - "integrity": "sha512-48j8QhDD9sfV6t7Zgn9JrfJtCpJ53bmoT2bzXYYig1HhG/Xn0Aa5fJhM0cQSZq9nq78/XbU7RDEa3e+IADNkmA==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/openapi-backend/-/openapi-backend-3.9.0.tgz", + "integrity": "sha512-RaEBFBFBGFcnOSqoKiX+Dg+CxS0zWBop1qw+JjPLzLs7ob637QEZcEGvKYKcwGv99mWwvcCHIuGH9l+LV5pHew==", "requires": { + "@apidevtools/json-schema-ref-parser": "^9.0.7", "ajv": "^6.10.0", "bath-es5": "^3.0.3", "cookie": "^0.4.0", "lodash": "^4.17.15", - "mock-json-schema": "^1.0.5", - "openapi-schema-validation": "^0.4.2", - "openapi-types": "^1.3.4", - "qs": "^6.6.0", - "swagger-parser": "^9.0.1" + "mock-json-schema": "^1.0.7", + "openapi-schema-validator": "^7.0.1", + "openapi-types": "^7.0.1", + "qs": "^6.9.3" + }, + "dependencies": { + "qs": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" + } } }, "openapi-schema-validation": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/openapi-schema-validation/-/openapi-schema-validation-0.4.2.tgz", "integrity": "sha512-K8LqLpkUf2S04p2Nphq9L+3bGFh/kJypxIG2NVGKX0ffzT4NQI9HirhiY6Iurfej9lCu7y4Ndm4tv+lm86Ck7w==", + "dev": true, "requires": { "jsonschema": "1.2.4", "jsonschema-draft4": "^1.0.0", "swagger-schema-official": "2.0.0-bab6bed" } }, + "openapi-schema-validator": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/openapi-schema-validator/-/openapi-schema-validator-7.2.3.tgz", + "integrity": "sha512-XT8NM5e/zBBa/cydTS1IeYkCPzJp9oixvt9Y1lEx+2gsCTOooNxw9x/KEivtWMSokne7X1aR+VtsYHQtNNOSyA==", + "requires": { + "ajv": "^6.5.2", + "lodash.merge": "^4.6.1", + "openapi-types": "^7.2.3", + "swagger-schema-official": "2.0.0-bab6bed" + } + }, "openapi-types": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-1.3.5.tgz", - "integrity": "sha512-11oi4zYorsgvg5yBarZplAqbpev5HkuVNPlZaPTknPDzAynq+lnJdXAmruGWP0s+dNYZS7bjM+xrTpJw7184Fg==" + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-7.2.3.tgz", + "integrity": "sha512-olbaNxz12R27+mTyJ/ZAFEfUruauHH27AkeQHDHRq5AF0LdNkK1SSV7EourXQDK+4aX7dv2HtyirAGK06WMAsA==" }, "optional-js": { "version": "2.3.0", @@ -8076,14 +8076,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" }, - "swagger-parser": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-9.0.1.tgz", - "integrity": "sha512-oxOHUaeNetO9ChhTJm2fD+48DbGbLD09ZEOwPOWEqcW8J6zmjWxutXtSuOiXsoRgDWvORYlImbwM21Pn+EiuvQ==", - "requires": { - "@apidevtools/swagger-parser": "9.0.1" - } - }, "swagger-schema-official": { "version": "2.0.0-bab6bed", "resolved": "https://registry.npmjs.org/swagger-schema-official/-/swagger-schema-official-2.0.0-bab6bed.tgz", @@ -8487,11 +8479,6 @@ "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", "dev": true }, - "validator": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-12.2.0.tgz", - "integrity": "sha512-jJfE/DW6tIK1Ek8nCfNFqt8Wb3nzMoAbocBF6/Icgg1ZFSBpObdnwVY2jQj6qUqzhx5jc71fpvBWyLGO7Xl+nQ==" - }, "vargs": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/vargs/-/vargs-0.1.0.tgz", @@ -8847,17 +8834,6 @@ "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" }, - "z-schema": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-4.2.3.tgz", - "integrity": "sha512-zkvK/9TC6p38IwcrbnT3ul9in1UX4cm1y/VZSs4GHKIiDCrlafc+YQBgQBUdDXLAoZHf2qvQ7gJJOo6yT1LH6A==", - "requires": { - "commander": "^2.7.1", - "lodash.get": "^4.4.2", - "lodash.isequal": "^4.5.0", - "validator": "^12.0.0" - } - }, "zip-stream": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz", diff --git a/src/package.json b/src/package.json index 324c4621..3c847b74 100644 --- a/src/package.json +++ b/src/package.json @@ -55,7 +55,7 @@ "mime-types": "^2.1.27", "nodeify": "1.0.1", "npm": "6.14.11", - "openapi-backend": "2.4.1", + "openapi-backend": "^3.9.0", "proxy-addr": "^2.0.6", "rate-limiter-flexible": "^2.1.4", "rehype": "^10.0.0", From bb14775820d5217273d01902ef08fd3f9d756ed5 Mon Sep 17 00:00:00 2001 From: John McLear Date: Sat, 20 Feb 2021 15:43:20 +0000 Subject: [PATCH 043/115] drop apiRoot object from build --- src/node/hooks/express/openapi.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/node/hooks/express/openapi.js b/src/node/hooks/express/openapi.js index bf7e5b14..056a98a2 100644 --- a/src/node/hooks/express/openapi.js +++ b/src/node/hooks/express/openapi.js @@ -573,7 +573,6 @@ exports.expressCreateServer = (hookName, args, cb) => { // build openapi-backend instance for this api version const api = new OpenAPIBackend({ - apiRoot, // each api version has its own root definition, validate: false, // for a small optimisation, we can run the quick startup for older From 6023117d2956733fcb35c7ca1baeabf00b7dd90d Mon Sep 17 00:00:00 2001 From: Richard Hansen Date: Sun, 21 Feb 2021 08:17:41 -0500 Subject: [PATCH 044/115] CI: Use `saucelabs/sauce-connect-action` to create the tunnel (#4833) --- .github/workflows/frontend-admin-tests.yml | 12 +++++------- .github/workflows/frontend-tests.yml | 12 +++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/.github/workflows/frontend-admin-tests.yml b/.github/workflows/frontend-admin-tests.yml index 0592d03f..bbe3d23f 100644 --- a/.github/workflows/frontend-admin-tests.yml +++ b/.github/workflows/frontend-admin-tests.yml @@ -15,13 +15,11 @@ jobs: with: node-version: 12 - - name: Run sauce-connect-action - shell: bash - env: - SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} - SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} - TRAVIS_JOB_NUMBER: ${{ github.run_id }}-${{ github.run_number }}-${{ github.job }} - run: src/tests/frontend/travis/sauce_tunnel.sh + - uses: saucelabs/sauce-connect-action@v1.1.2 + with: + username: ${{ secrets.SAUCE_USERNAME }} + accessKey: ${{ secrets.SAUCE_ACCESS_KEY }} + tunnelIdentifier: ${{ github.run_id }}-${{ github.run_number }}-${{ github.job }} - name: Install all dependencies and symlink for ep_etherpad-lite run: src/bin/installDeps.sh diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml index 60429ab2..6ddd9cf6 100644 --- a/.github/workflows/frontend-tests.yml +++ b/.github/workflows/frontend-tests.yml @@ -15,13 +15,11 @@ jobs: with: node-version: 12 - - name: Run sauce-connect-action - shell: bash - env: - SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} - SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} - TRAVIS_JOB_NUMBER: ${{ github.run_id }}-${{ github.run_number }}-${{ github.job }} - run: src/tests/frontend/travis/sauce_tunnel.sh + - uses: saucelabs/sauce-connect-action@v1.1.2 + with: + username: ${{ secrets.SAUCE_USERNAME }} + accessKey: ${{ secrets.SAUCE_ACCESS_KEY }} + tunnelIdentifier: ${{ github.run_id }}-${{ github.run_number }}-${{ github.job }} - name: Install all dependencies and symlink for ep_etherpad-lite run: src/bin/installDeps.sh From 086b59b30d4fbf1a313dc51fa54ae570b6fc9c9b Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 21 Feb 2021 13:24:51 +0000 Subject: [PATCH 045/115] editor: UI polish - Etherpad brand as reconnect & loading animation --- src/static/css/pad.css | 7 ++++ src/static/css/pad/icons.css | 3 +- src/static/img/brand.svg | 65 ++++++++++++++++++++++++++++++++++++ src/templates/pad.html | 5 ++- 4 files changed, 77 insertions(+), 3 deletions(-) create mode 100755 src/static/img/brand.svg diff --git a/src/static/css/pad.css b/src/static/css/pad.css index 6edd08d4..624da616 100644 --- a/src/static/css/pad.css +++ b/src/static/css/pad.css @@ -70,3 +70,10 @@ input { @media (max-width: 800px) { .hide-for-mobile { display: none; } } + +.etherpadBrand{ + width:20%; + max-width:100px; + margin-left:auto; + margin-right:auto; +} diff --git a/src/static/css/pad/icons.css b/src/static/css/pad/icons.css index 0a342770..c89d359e 100644 --- a/src/static/css/pad/icons.css +++ b/src/static/css/pad/icons.css @@ -130,7 +130,6 @@ .buttonicon-microphone-alt-slash:before { content: '\e83e'; } /* '' */ .buttonicon-compress:before { content: '\e83f'; } /* '' */ .buttonicon-expand:before { content: '\e840'; } /* '' */ -.buttonicon-spin5:before { content: '\e841'; } /* '' */ .buttonicon-eye-slash:before { content: '\e843'; } /* '' */ .buttonicon-list-ol:before { content: '\e844'; } /* '' */ .buttonicon-bold:before { content: '\e845'; } /* '' */ @@ -190,4 +189,4 @@ -webkit-transform: rotate(359deg); transform: rotate(359deg); } -} \ No newline at end of file +} diff --git a/src/static/img/brand.svg b/src/static/img/brand.svg new file mode 100755 index 00000000..df6fff34 --- /dev/null +++ b/src/static/img/brand.svg @@ -0,0 +1,65 @@ + + + Group 10 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/templates/pad.html b/src/templates/pad.html index ca410154..0f96734e 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -100,6 +100,7 @@ <% e.end_block(); %> <% e.begin_block("loading"); %>

+
Loading...

<% e.end_block(); %> @@ -237,7 +238,9 @@

- + +
+

From bdb78adb3fd6bfdf912c8805d56b450a22de1abe Mon Sep 17 00:00:00 2001 From: John McLear Date: Sun, 21 Feb 2021 13:50:25 +0000 Subject: [PATCH 046/115] Update CHANGELOG.md --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a17488f3..c94227fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ ### Notable fixes +### Notable enhancements + +* Improved line number alignment and user experience around line anchors +* Notification to admin console if a plugin is missing during user file import +* Beautiful loading and reconnecting animation +* Additional code quality improvements +* Dependency updates + +# 1.8.9 + +### Notable fixes + * Fixed HTTP 400 error when importing via the UI. * Fixed "Error: spawn npm ENOENT" crash on startup in Windows. From 0bb3e6502065b94647d9fe9662b4a744adabde79 Mon Sep 17 00:00:00 2001 From: webzwo0i Date: Mon, 15 Feb 2021 08:52:38 +0100 Subject: [PATCH 047/115] fix for caching plugin-definitions --- src/node/utils/Settings.js | 28 ++++++++++++------------ src/static/js/pluginfw/client_plugins.js | 5 +++-- src/static/js/pluginfw/installer.js | 7 ++++-- src/templates/pad.html | 6 ++++- src/templates/timeslider.html | 6 ++++- 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/node/utils/Settings.js b/src/node/utils/Settings.js index 202e80c6..70e9bb45 100644 --- a/src/node/utils/Settings.js +++ b/src/node/utils/Settings.js @@ -42,20 +42,6 @@ const _ = require('underscore'); exports.root = absolutePaths.findEtherpadRoot(); console.log(`All relative paths will be interpreted relative to the identified Etherpad base dir: ${exports.root}`); -/* - * At each start, Etherpad generates a random string and appends it as query - * parameter to the URLs of the static assets, in order to force their reload. - * Subsequent requests will be cached, as long as the server is not reloaded. - * - * For the rationale behind this choice, see - * https://github.com/ether/etherpad-lite/pull/3958 - * - * ACHTUNG: this may prevent caching HTTP proxies to work - * TODO: remove the "?v=randomstring" parameter, and replace with hashed filenames instead - */ -exports.randomVersionString = randomString(4); -console.log(`Random string used for versioning assets: ${exports.randomVersionString}`); - /** * The app title, visible e.g. in the browser window */ @@ -796,6 +782,20 @@ exports.reloadSettings = () => { // using Unix socket for connectivity console.warn('The settings file contains an empty string ("") for the "ip" parameter. The "port" parameter will be interpreted as the path to a Unix socket to bind at.'); } + + /* + * At each start, Etherpad generates a random string and appends it as query + * parameter to the URLs of the static assets, in order to force their reload. + * Subsequent requests will be cached, as long as the server is not reloaded. + * + * For the rationale behind this choice, see + * https://github.com/ether/etherpad-lite/pull/3958 + * + * ACHTUNG: this may prevent caching HTTP proxies to work + * TODO: remove the "?v=randomstring" parameter, and replace with hashed filenames instead + */ + exports.randomVersionString = randomString(4); + console.log(`Random string used for versioning assets: ${exports.randomVersionString}`); }; // initially load settings diff --git a/src/static/js/pluginfw/client_plugins.js b/src/static/js/pluginfw/client_plugins.js index 6163241a..221e786f 100644 --- a/src/static/js/pluginfw/client_plugins.js +++ b/src/static/js/pluginfw/client_plugins.js @@ -12,9 +12,10 @@ exports.update = (cb) => { // of execution on Firefox. This schedules the response in the run-loop, // which appears to fix the issue. const callback = () => setTimeout(cb, 0); - $.ajaxSetup({cache: false}); - jQuery.getJSON(`${exports.baseURL}pluginfw/plugin-definitions.json`).done((data) => { + jQuery.getJSON( + `${exports.baseURL}pluginfw/plugin-definitions.json?v=${clientVars.randomVersionString}` + ).done((data) => { defs.plugins = data.plugins; defs.parts = data.parts; defs.hooks = pluginUtils.extractHooks(defs.parts, 'client_hooks'); diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index 8908dbaa..104777d3 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -5,11 +5,14 @@ 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'); const logger = log4js.getLogger('plugins'); -const onAllTasksFinished = () => { - hooks.aCallAll('restartServer', {}, () => {}); +const onAllTasksFinished = async () => { + settings.reloadSettings(); + await hooks.aCallAll('loadSettings', {settings}); + await hooks.aCallAll('restartServer'); }; let tasks = 0; diff --git a/src/templates/pad.html b/src/templates/pad.html index 0f96734e..819abd86 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -478,7 +478,11 @@ + - + diff --git a/src/templates/admin/plugins-info.html b/src/templates/admin/plugins-info.html index a737ea05..57e41f85 100644 --- a/src/templates/admin/plugins-info.html +++ b/src/templates/admin/plugins-info.html @@ -5,7 +5,7 @@ - + diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index 76b01acb..278304fa 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -4,12 +4,12 @@ Plugin manager - Etherpad - + - + diff --git a/src/templates/admin/settings.html b/src/templates/admin/settings.html index a75a9168..ffa4172f 100644 --- a/src/templates/admin/settings.html +++ b/src/templates/admin/settings.html @@ -4,14 +4,14 @@ Settings - Etherpad - + - + diff --git a/src/templates/index.html b/src/templates/index.html index 213071fe..8f20c08d 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -11,9 +11,9 @@ - + - +