diff --git a/src/node/hooks/express/specialpages.ts b/src/node/hooks/express/specialpages.ts index 85a23479f..16708f488 100644 --- a/src/node/hooks/express/specialpages.ts +++ b/src/node/hooks/express/specialpages.ts @@ -1,7 +1,7 @@ 'use strict'; const path = require('path'); -const eejs = require('../../eejs'); +const eejs = require('../../eejs') const fs = require('fs'); const fsp = fs.promises; const toolbar = require('../../utils/toolbar'); @@ -9,7 +9,8 @@ const hooks = require('../../../static/js/pluginfw/hooks'); const settings = require('../../utils/Settings'); const util = require('util'); const webaccess = require('./webaccess'); - +const plugins = require('../../../static/js/pluginfw/plugin_defs'); +import {buildSync} from 'esbuild' exports.expressPreSession = async (hookName:string, {app}:any) => { // This endpoint is intended to conform to: // https://www.ietf.org/archive/id/draft-inadarei-api-health-check-06.html @@ -73,14 +74,42 @@ exports.expressPreSession = async (hookName:string, {app}:any) => { }); }; -exports.expressCreateServer = (hookName:string, args:any, cb:Function) => { +exports.expressCreateServer = async (hookName: string, args: any, cb: Function) => { // serve index.html under / - args.app.get('/', (req:any, res:any) => { + args.app.get('/', (req: any, res: any) => { 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', { + pluginModules: (() => { + const pluginModules = new Set(); + for (const part of plugins.parts) { + for (const [, hookFnName] of Object.entries(part.client_hooks || {})) { + pluginModules.add(hookFnName.split(':')[0]); + } + } + return [...pluginModules]; + })(), + settings, + })); + + const result = buildSync({ + entryPoints: [settings.root + "/src/templates/padBootstrap.js"], // Entry file(s) + bundle: true, // Bundle the files together + minify: true, // Minify the output + sourcemap: true, // Generate source maps + sourceRoot: settings.root+"/src/static/js/", + target: ['es2020'], // Target ECMAScript version + write: false, // Do not write to file system, + }) + + const textResult = result.outputFiles[0].text + + // serve pad.html under /p - args.app.get('/p/:pad', (req:any, res:any, next:Function) => { + args.app.get('/p/:pad', (req: any, res: any, next: Function) => { // The below might break for pads being rewritten const isReadOnly = !webaccess.userCanModify(req.params.pad, req); @@ -99,7 +128,7 @@ exports.expressCreateServer = (hookName:string, args:any, cb:Function) => { }); // serve timeslider.html under /p/$padname/timeslider - args.app.get('/p/:pad/timeslider', (req:any, res:any, next:Function) => { + args.app.get('/p/:pad/timeslider', (req: any, res: any, next: Function) => { hooks.callAll('padInitToolbar', { toolbar, }); @@ -112,7 +141,7 @@ exports.expressCreateServer = (hookName:string, args:any, cb:Function) => { // The client occasionally polls this endpoint to get an updated expiration for the express_sid // cookie. This handler must be installed after the express-session middleware. - args.app.put('/_extendExpressSessionLifetime', (req:any, res:any) => { + args.app.put('/_extendExpressSessionLifetime', (req: any, res: any) => { // express-session automatically calls req.session.touch() so we don't need to do it here. res.json({status: 'ok'}); }); diff --git a/src/templates/pad.html b/src/templates/pad.html index c0c56bf24..0d7072f82 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -441,68 +441,13 @@ <% e.begin_block("scripts"); %> - - - - - + <% e.begin_block("customScripts"); %> <% e.end_block(); %> - - -
JavaScript license information
<% e.end_block(); %> diff --git a/src/templates/padBootstrap.js b/src/templates/padBootstrap.js new file mode 100644 index 000000000..5758234ae --- /dev/null +++ b/src/templates/padBootstrap.js @@ -0,0 +1,40 @@ +(async () => { + 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)%>, + }; + + // Allow other frames to access this frame's modules. + window.require.resolveTmp = require.resolve('ep_etherpad-lite/static/js/pad_cookie'); + + const basePath = new URL('..', window.location.href).pathname; + window.$ = window.jQuery = require('../../src/static/js/rjquery').jQuery; + window.browser = require('../../src/static/js/vendors/browser'); + const pad = require('../../src/static/js/pad'); + pad.baseURL = basePath; + window.plugins = require('../../src/static/js/pluginfw/client_plugins'); + const hooks = require('../../src/static/js/pluginfw/hooks'); + + // TODO: These globals shouldn't exist. + window.pad = pad.pad; + window.chat = require('../../src/static/js/chat').chat; + window.padeditbar = require('../../src/static/js/pad_editbar').padeditbar; + window.padimpexp = require('../../src/static/js/pad_impexp').padimpexp; + require('../../src/static/js/skin_variants'); + + window.plugins.baseURL = basePath; + await window.plugins.update(new Map([ + <% for (const module of pluginModules) { %> + [<%- JSON.stringify(module) %>, require(<%- JSON.stringify(module) %>)], + <% } %> +])); + // Mechanism for tests to register hook functions (install fake plugins). + window._postPluginUpdateForTestingDone = false; + if (window._postPluginUpdateForTesting != null) window._postPluginUpdateForTesting(); + window._postPluginUpdateForTestingDone = true; + window.pluginDefs = require('../../src/static/js/pluginfw/plugin_defs'); + pad.init(); + await new Promise((resolve) => $(resolve)); + await hooks.aCallAll('documentReady'); +})(); diff --git a/var/js/.gitignore b/var/js/.gitignore new file mode 100644 index 000000000..e69de29bb