diff --git a/admin/package.json b/admin/package.json index 2c3241b5b..63227ba40 100644 --- a/admin/package.json +++ b/admin/package.json @@ -26,7 +26,7 @@ "eslint-plugin-react-refresh": "^0.4.8", "i18next": "^23.11.5", "i18next-browser-languagedetector": "^8.0.0", - "lucide-react": "^0.407.0", + "lucide-react": "^0.408.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.52.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f4dcf216..e6ad36935 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,8 +65,8 @@ importers: specifier: ^8.0.0 version: 8.0.0 lucide-react: - specifier: ^0.407.0 - version: 0.407.0(react@18.3.1) + specifier: ^0.408.0 + version: 0.408.0(react@18.3.1) react: specifier: ^18.2.0 version: 18.3.1 @@ -152,6 +152,9 @@ importers: cookie-parser: specifier: ^1.4.6 version: 1.4.6 + cross-env: + specifier: ^7.0.3 + version: 7.0.3 cross-spawn: specifier: ^7.0.3 version: 7.0.3 @@ -2085,6 +2088,11 @@ packages: typescript: optional: true + cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -3222,8 +3230,8 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - lucide-react@0.407.0: - resolution: {integrity: sha512-+dRIu9Sry+E8wPF9+sY5eKld2omrU4X5IKXxrgqBt+o11IIHVU0QOfNoVWFuj0ZRDrxr4Wci26o2mKZqLGE0lA==} + lucide-react@0.408.0: + resolution: {integrity: sha512-8kETAAeWmOvtGIr7HPHm51DXoxlfkNncQ5FZWXR+abX8saQwMYXANWIkUstaYtcKSo/imOe/q+tVFA8ANzdSVA==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -6144,6 +6152,10 @@ snapshots: optionalDependencies: typescript: 5.5.3 + cross-env@7.0.3: + dependencies: + cross-spawn: 7.0.3 + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 @@ -7572,7 +7584,7 @@ snapshots: lru-cache@7.18.3: {} - lucide-react@0.407.0(react@18.3.1): + lucide-react@0.408.0(react@18.3.1): dependencies: react: 18.3.1 diff --git a/src/node/hooks/express/specialpages.ts b/src/node/hooks/express/specialpages.ts index 0905abf03..8ea64962a 100644 --- a/src/node/hooks/express/specialpages.ts +++ b/src/node/hooks/express/specialpages.ts @@ -1,13 +1,13 @@ 'use strict'; -const path = require('path'); +import path from 'node:path'; const eejs = require('../../eejs') -const fs = require('fs'); +import fs from 'node:fs'; const fsp = fs.promises; const toolbar = require('../../utils/toolbar'); const hooks = require('../../../static/js/pluginfw/hooks'); const settings = require('../../utils/Settings'); -const util = require('util'); +import util from 'node:util'; const webaccess = require('./webaccess'); const plugins = require('../../../static/js/pluginfw/plugin_defs'); import {hash, createHash} from 'node:crypto' @@ -83,44 +83,96 @@ exports.expressCreateServer = async (hookName: string, args: any, cb: Function) res.send(eejs.require('ep_etherpad-lite/templates/index.html', {req})); }); - await fsp.writeFile( - path.join(settings.root, 'var/js/padbootstrap.js'), - eejs.require('ep_etherpad-lite/templates/padBootstrap.js', { + + const padString = eejs.require('ep_etherpad-lite/templates/padBootstrap.js', { pluginModules: (() => { const pluginModules = new Set(); for (const part of plugins.parts) { for (const [, hookFnName] of Object.entries(part.client_hooks || {})) { - console.log(hookFnName.split(':')[0]) + // @ts-ignore pluginModules.add(hookFnName.split(':')[0]); } } return [...pluginModules]; })(), settings, - })); - const hash = createHash('sha256').update(fs.readFileSync(path.join(settings.root, 'var/js/padbootstrap.js'))).digest('hex'); + }) - const fileName = `padbootstrap-${hash.substring(0,16)}.min.js` - const result = buildSync({ - entryPoints: [settings.root + "/var/js/padbootstrap.js"], // Entry file(s) + + const timeSliderString = eejs.require('ep_etherpad-lite/templates/timeSliderBootstrap.js', { + pluginModules: (() => { + const pluginModules = new Set(); + for (const part of plugins.parts) { + for (const [, hookFnName] of Object.entries(part.client_hooks || {})) { + // @ts-ignore + pluginModules.add(hookFnName.split(':')[0]); + } + } + return [...pluginModules]; + })(), + settings, + }) + + + + const outdir = path.join(settings.root, 'var','js') + + const padWriteResult = buildSync({ + stdin: { + contents: padString, + resolveDir: path.join(settings.root, 'var','js'), + loader: 'js' + }, // Entry file(s) bundle: true, // Bundle the files together - minify: false, // Minify the output - sourcemap: true, // Generate source maps + minify: process.env.NODE_ENV === "production", // Minify the output + sourcemap: !(process.env.NODE_ENV === "production"), // Generate source maps sourceRoot: settings.root+"/src/static/js/", target: ['es2020'], // Target ECMAScript version metafile: true, - - write: true, // Do not write to file system, - outfile: settings.root + `/var/js/${fileName}`, // Output file + write: false, // Do not write to file system, }) + const outputPadJS = padWriteResult.outputFiles[0].text - args.app.get(`/${fileName}`, (req: any, res: any) => { - res.sendFile(settings.root+`/var/js/${fileName}`) + const timeSliderWrite = buildSync({ + stdin: { + contents: timeSliderString, + resolveDir: path.join(settings.root, 'var','js'), + loader: 'js' + }, + bundle: true, // Bundle the files together + minify: process.env.NODE_ENV === "production", // Minify the output + sourcemap: !(process.env.NODE_ENV === "production"), // Generate source maps + sourceRoot: settings.root+"/src/static/js/", + target: ['es2020'], // Target ECMAScript version + metafile: true, + write: false, // Do not write to file system, }) - args.app.get(`/${fileName}.map`, (req: any, res: any) => { - res.sendFile(settings.root+`/var/js/${fileName}.map`) + const outputTimeslider = timeSliderWrite.outputFiles[0].text + + const hash = padWriteResult.outputFiles[0].hash + const hashTimeSlider = timeSliderWrite.outputFiles[0].hash + + const fileNamePad = `padbootstrap-${hash}.min.js` + const fileNameTimeSlider = `timeSliderBootstrap-${hashTimeSlider}.min.js` + const pathNamePad = path.join(outdir, fileNamePad) + const pathNameTimeSlider = path.join(outdir, fileNameTimeSlider) + + if (!fs.existsSync(pathNamePad)) { + fs.writeFileSync(pathNamePad, outputPadJS); + } + + if (!fs.existsSync(pathNameTimeSlider)) { + fs.writeFileSync(pathNameTimeSlider,outputTimeslider) + } + + args.app.get("/"+fileNamePad, (req: any, res: any) => { + res.sendFile(pathNamePad) + }) + + args.app.get("/"+fileNameTimeSlider, (req: any, res: any) => { + res.sendFile(pathNameTimeSlider) }) @@ -140,7 +192,7 @@ exports.expressCreateServer = async (hookName: string, args: any, cb: Function) req, toolbar, isReadOnly, - entrypoint: "/"+fileName + entrypoint: "/"+fileNamePad })); }); @@ -153,6 +205,7 @@ exports.expressCreateServer = async (hookName: string, args: any, cb: Function) res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', { req, toolbar, + entrypoint: "/"+fileNameTimeSlider })); }); diff --git a/src/node/utils/toolbar.ts b/src/node/utils/toolbar.ts index aac3fb3d3..f0ef45479 100644 --- a/src/node/utils/toolbar.ts +++ b/src/node/utils/toolbar.ts @@ -2,7 +2,7 @@ /** * The Toolbar Module creates and renders the toolbars and buttons */ -const _ = require('underscore'); +import {isString, reduce, each, isUndefined, map, first, last, extend, escape} from 'underscore'; const removeItem = (array: string[], what: string) => { let ax; @@ -21,7 +21,7 @@ const defaultButtonAttributes = (name: string, overrides?: boolean) => ({ const tag = (name: string, attributes: AttributeObj, contents?: string) => { const aStr = tagAttributes(attributes); - if (_.isString(contents) && contents!.length > 0) { + if (isString(contents) && contents!.length > 0) { return `<${name}${aStr}>${contents}${name}>`; } else { return `<${name}${aStr}>${name}>`; @@ -34,14 +34,14 @@ type AttributeObj = { } const tagAttributes = (attributes: AttributeObj) => { - attributes = _.reduce(attributes || {}, (o: AttributeObj, val: string, name: string) => { - if (!_.isUndefined(val)) { + attributes = reduce(attributes || {}, (o: AttributeObj, val: string, name: string) => { + if (!isUndefined(val)) { o[name] = val; } return o; }, {}); - return ` ${_.map(attributes, (val: string, name: string) => `${name}="${_.escape(val)}"`).join(' ')}`; + return ` ${map(attributes, (val: string, name: string) => `${name}="${escape(val)}"`).join(' ')}`; }; type ButtonGroupType = { @@ -58,7 +58,7 @@ class ButtonGroup { public static fromArray = function (array: string[]) { const btnGroup = new ButtonGroup(); - _.each(array, (btnName: string) => { + each(array, (btnName: string) => { const button = Button.load(btnName) as Button btnGroup.addButton(button); }); @@ -70,18 +70,19 @@ class ButtonGroup { return this; } - render() { + render(): string { if (this.buttons && this.buttons.length === 1) { this.buttons[0].grouping = ''; } else if (this.buttons && this.buttons.length > 1) { - _.first(this.buttons).grouping = 'grouped-left'; - _.last(this.buttons).grouping = 'grouped-right'; - _.each(this.buttons.slice(1, -1), (btn: Button) => { + first(this.buttons)!.grouping = 'grouped-left'; + last(this.buttons)!.grouping = 'grouped-right'; + each(this.buttons.slice(1, -1), (btn: Button) => { btn.grouping = 'grouped-middle'; }); } - return _.map(this.buttons, (btn: ButtonGroup) => { + // @ts-ignore + return map(this.buttons, (btn: ButtonGroup) => { if (btn) return btn.render(); }).join('\n'); } @@ -151,8 +152,8 @@ class SelectButton extends Button { select(attributes: AttributeObj) { const options: string[] = []; - _.each(this.options, (opt: AttributeSelect) => { - const a = _.extend({ + each(this.options, (opt: AttributeSelect) => { + const a = extend({ value: opt.value, }, opt.attributes); @@ -299,7 +300,7 @@ module.exports = { buttons[0].push('savedrevision'); } - const groups = _.map(buttons, (group: string[]) => ButtonGroup.fromArray(group).render()); + const groups = map(buttons, (group: string[]) => ButtonGroup.fromArray(group).render()); return groups.join(this.separator()); }, }; diff --git a/src/package.json b/src/package.json index ab49c1ae7..ad5c95a69 100644 --- a/src/package.json +++ b/src/package.json @@ -75,7 +75,8 @@ "ueberdb2": "^4.2.82", "underscore": "1.13.6", "unorm": "1.6.0", - "wtfnode": "^0.9.3" + "wtfnode": "^0.9.3", + "cross-env": "^7.0.3" }, "bin": { "etherpad-healthcheck": "../bin/etherpad-healthcheck", @@ -124,8 +125,8 @@ "test": "mocha --import=tsx --timeout 120000 --recursive tests/backend/specs/**.ts ../node_modules/ep_*/static/tests/backend/specs/**", "test-utils": "mocha --import=tsx --timeout 5000 --recursive tests/backend/specs/*utils.ts", "test-container": "mocha --import=tsx --timeout 5000 tests/container/specs/api", - "dev": "node --require tsx/cjs node/server.ts", - "prod": "node --require tsx/cjs node/server.ts", + "dev": "cross-env NODE_ENV=development node --require tsx/cjs node/server.ts", + "prod": "cross-env NODE_ENV=production node --require tsx/cjs node/server.ts", "ts-check": "tsc --noEmit", "ts-check:watch": "tsc --noEmit --watch", "test-ui": "npx playwright test tests/frontend-new/specs", diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index dd83f36d3..90abcb7b8 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -3188,10 +3188,10 @@ function Ace2Inner(editorInfo, cssManagers) { editorInfo.ace_getInInternationalComposition = () => inInternationalComposition; const bindTheEventHandlers = () => { - $(document).on('keydown', handleKeyEvent); - $(document).on('keypress', handleKeyEvent); - $(document).on('keyup', handleKeyEvent); - $(document).on('click', handleClick); + $(targetDoc).on('keydown', handleKeyEvent); + $(targetDoc).on('keypress', handleKeyEvent); + $(targetDoc).on('keyup', handleKeyEvent); + $(targetDoc).on('click', handleClick); // dropdowns on edit bar need to be closed on clicks on both pad inner and pad outer $(outerDoc).on('click', hideEditBarDropdowns); diff --git a/src/static/js/pad_savedrevs.js b/src/static/js/pad_savedrevs.js index b5868f699..4082e0380 100644 --- a/src/static/js/pad_savedrevs.js +++ b/src/static/js/pad_savedrevs.js @@ -20,7 +20,7 @@ let pad; exports.saveNow = () => { pad.collabClient.sendMessage({type: 'SAVE_REVISION'}); - $.gritter.add({ + window.$.gritter.add({ // (string | mandatory) the heading of the notification title: html10n.get('pad.savedrevs.marked'), // (string | mandatory) the text inside the notification diff --git a/src/static/js/pad_utils.js b/src/static/js/pad_utils.js index 6601cb2c3..467a8adc9 100644 --- a/src/static/js/pad_utils.js +++ b/src/static/js/pad_utils.js @@ -356,7 +356,6 @@ const padutils = { let globalExceptionHandler = null; padutils.setupGlobalExceptionHandler = () => { if (globalExceptionHandler == null) { - require('./vendors/gritter'); globalExceptionHandler = (e) => { let type; let err; diff --git a/src/static/js/vendors/gritter.js b/src/static/js/vendors/gritter.js index a20cb4de9..1b8b9a759 100644 --- a/src/static/js/vendors/gritter.js +++ b/src/static/js/vendors/gritter.js @@ -42,8 +42,8 @@ return Gritter.add(params || {}); } catch(e) { - var err = 'Gritter Error: ' + e; - (typeof(console) != 'undefined' && console.error) ? + const err = 'Gritter Error: ' + e; + (typeof(console) != 'undefined' && console.error) ? console.error(err, params) : alert(err); @@ -289,7 +289,7 @@ */ _runSetup: function(){ - for(opt in $.gritter.options){ + for(let opt in $.gritter.options){ this[opt] = $.gritter.options[opt]; } this._is_setup = 1; diff --git a/src/static/js/vendors/html10n.js b/src/static/js/vendors/html10n.js index 1f6a11728..6fe5e307f 100644 --- a/src/static/js/vendors/html10n.js +++ b/src/static/js/vendors/html10n.js @@ -725,7 +725,7 @@ export let html10n = (function(window, document, undefined) { return; // initialize _pluralRules - if (!this._pluralRules) + if (!("_pluralRules" in this)) this._pluralRules = getPluralRules(html10n.language); var index = this._pluralRules(n); diff --git a/src/templates/timeSliderBootstrap.js b/src/templates/timeSliderBootstrap.js new file mode 100644 index 000000000..e3138cfbd --- /dev/null +++ b/src/templates/timeSliderBootstrap.js @@ -0,0 +1,37 @@ +// @license magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt +window.clientVars = { + // This is needed to fetch /pluginfw/plugin-definitions.json, which happens before the + // server sends the CLIENT_VARS message. + randomVersionString: <%-JSON.stringify(settings.randomVersionString)%>, +}; +let BroadcastSlider; + + +(function () { + const timeSlider = require('ep_etherpad-lite/static/js/timeslider') + const pathComponents = location.pathname.split('/'); + + // Strip 'p', the padname and 'timeslider' from the pathname and set as baseURL + const baseURL = pathComponents.slice(0,pathComponents.length-3).join('/') + '/'; + require('ep_etherpad-lite/static/js/l10n') + window.$ = window.jQuery = require('ep_etherpad-lite/static/js/rjquery').jQuery; // Expose jQuery #HACK + require('ep_etherpad-lite/static/js/vendors/gritter') + + window.browser = require('ep_etherpad-lite/static/js/vendors/browser'); + + window.plugins = require('ep_etherpad-lite/static/js/pluginfw/client_plugins'); + const socket = timeSlider.socket; + BroadcastSlider = timeSlider.BroadcastSlider; + plugins.baseURL = baseURL; + plugins.update(function () { + + + /* TODO: These globals shouldn't exist. */ + + }); + const padeditbar = require('ep_etherpad-lite/static/js/pad_editbar').padeditbar; + const padimpexp = require('ep_etherpad-lite/static/js/pad_impexp').padimpexp; + timeSlider.baseURL = baseURL; + timeSlider.init(); + padeditbar.init() +})(); diff --git a/src/templates/timeslider.html b/src/templates/timeslider.html index 71346f21e..e2178e54e 100644 --- a/src/templates/timeslider.html +++ b/src/templates/timeslider.html @@ -47,8 +47,6 @@ <% e.begin_block("timesliderScripts"); %> - - <% e.end_block(); %> @@ -250,58 +248,14 @@ - - - - + <% e.end_block(); %>