Added live reloading.

This commit is contained in:
SamTv12345 2024-07-17 10:47:59 +02:00
parent d522de17e6
commit bbf4adb075
6 changed files with 144 additions and 91 deletions

View file

@ -42,7 +42,8 @@
"name": "specialpages", "name": "specialpages",
"hooks": { "hooks": {
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/specialpages", "expressCreateServer": "ep_etherpad-lite/node/hooks/express/specialpages",
"expressPreSession": "ep_etherpad-lite/node/hooks/express/specialpages" "expressPreSession": "ep_etherpad-lite/node/hooks/express/specialpages",
"socketio": "ep_etherpad-lite/node/hooks/express/specialpages"
} }
}, },
{ {

View file

@ -996,7 +996,8 @@ const handleClientReady = async (socket:any, message: typeof ChatMessage) => {
percentageToScrollWhenUserPressesArrowUp: percentageToScrollWhenUserPressesArrowUp:
settings.scrollWhenFocusLineIsOutOfViewport.percentageToScrollWhenUserPressesArrowUp, settings.scrollWhenFocusLineIsOutOfViewport.percentageToScrollWhenUserPressesArrowUp,
}, },
initialChangesets: [], // FIXME: REMOVE THIS SHIT initialChangesets: [], // FIXME: REMOVE THIS SHIT,
mode: process.env.NODE_ENV
}; };
// Add a username to the clientVars if one avaiable // Add a username to the clientVars if one avaiable

View file

@ -12,6 +12,13 @@ const webaccess = require('./webaccess');
const plugins = require('../../../static/js/pluginfw/plugin_defs'); const plugins = require('../../../static/js/pluginfw/plugin_defs');
import {build, buildSync} from 'esbuild' import {build, buildSync} from 'esbuild'
let ioI: { sockets: { sockets: any[]; }; } | null = null
exports.socketio = (hookName: string, {io}: any) => {
ioI = io
}
exports.expressPreSession = async (hookName:string, {app}:any) => { exports.expressPreSession = async (hookName:string, {app}:any) => {
// This endpoint is intended to conform to: // This endpoint is intended to conform to:
// https://www.ietf.org/archive/id/draft-inadarei-api-health-check-06.html // https://www.ietf.org/archive/id/draft-inadarei-api-health-check-06.html
@ -100,6 +107,99 @@ const convertTypescript = (content: string) => {
} }
} }
const handleLiveReload = async (args: any, padString: string, timeSliderString: string ) => {
const chokidar = await import('chokidar')
const watcher = chokidar.watch(path.join(settings.root, 'src', 'static', 'js'));
let routeHandlers: { [key: string]: Function } = {};
const setRouteHandler = (path: string, newHandler: Function) => {
routeHandlers[path] = newHandler;
};
args.app.use((req: any, res: any, next: Function) => {
if (req.path.startsWith('/p/') && req.path.split('/').length == 3) {
req.params = {
pad: req.path.split('/')[2]
}
routeHandlers['/p/:pad'](req, res);
} else if (req.path.startsWith('/p/') && req.path.split('/').length == 4) {
req.params = {
pad: req.path.split('/')[2]
}
routeHandlers['/p/:pad/timeslider'](req, res);
} else if (routeHandlers[req.path]) {
routeHandlers[req.path](req, res);
} else {
next();
}
});
function handleUpdate() {
convertTypescriptWatched(padString, (output, hash) => {
console.log("New pad hash is", hash)
setRouteHandler('/watch/pad', (req: any, res: any) => {
res.header('Content-Type', 'application/javascript');
res.send(output)
})
setRouteHandler("/p/:pad", (req: any, res: any, next: Function) => {
// The below might break for pads being rewritten
const isReadOnly = !webaccess.userCanModify(req.params.pad, req);
hooks.callAll('padInitToolbar', {
toolbar,
isReadOnly
});
// can be removed when require-kernel is dropped
res.header('Feature-Policy', 'sync-xhr \'self\'');
const content = eejs.require('ep_etherpad-lite/templates/pad.html', {
req,
toolbar,
isReadOnly,
entrypoint: '/watch/pad?hash=' + hash
})
res.send(content);
})
ioI!.sockets.sockets.forEach(socket => socket.emit('liveupdate'))
})
convertTypescriptWatched(timeSliderString, (output, hash) => {
// serve timeslider.html under /p/$padname/timeslider
console.log("New timeslider hash is", hash)
setRouteHandler('/watch/timeslider', (req: any, res: any) => {
res.header('Content-Type', 'application/javascript');
res.send(output)
})
setRouteHandler("/p/:pad/timeslider", (req: any, res: any, next: Function) => {
console.log("Reloading pad")
// The below might break for pads being rewritten
const isReadOnly = !webaccess.userCanModify(req.params.pad, req);
hooks.callAll('padInitToolbar', {
toolbar,
isReadOnly
});
// can be removed when require-kernel is dropped
res.header('Feature-Policy', 'sync-xhr \'self\'');
const content = eejs.require('ep_etherpad-lite/templates/pad.html', {
req,
toolbar,
isReadOnly,
entrypoint: '/watch/timeslider?hash=' + hash
})
res.send(content);
})
})
}
watcher.on('change', path => {
console.log(`File ${path} has been changed`);
handleUpdate();
});
handleUpdate()
}
const convertTypescriptWatched = (content: string, cb: (output:string, hash: string)=>void) => { const convertTypescriptWatched = (content: string, cb: (output:string, hash: string)=>void) => {
build({ build({
@ -224,67 +324,9 @@ exports.expressCreateServer = async (hookName: string, args: any, cb: Function)
})); }));
}); });
} else { } else {
const chokidar = await import('chokidar') await handleLiveReload(args, padString, timeSliderString)
const map = new Map<string, string>()
const watcher = chokidar.watch(path.join(settings.root, 'src','static','js'));
watcher.on('change', path => {
console.log(`File ${path} has been changed`);
convertTypescriptWatched(padString, (output, hash)=>{
console.log("New hash is", hash)
map.set('output', output)
map.set('fileNamePad', `padbootstrap-${hash}.js`)
});
convertTypescriptWatched(timeSliderString, (output, hash)=>{
// serve timeslider.html under /p/$padname/timeslider
console.log("New hash is", hash)
args.app.get('/watch/timeslider', (req: any, res: any) => {
res.header('Content-Type', 'application/javascript');
res.send(output)
})
});
});
args.app.get('/watch/pad', (req: any, res: any) => {
res.header('Content-Type', 'application/javascript');
res.send(map.get('output'))
})
// serve pad.html under /p
args.app.get('/p/:pad', (req: any, res: any, next: Function) => {
console.log("Reloading pad")
// The below might break for pads being rewritten
const isReadOnly = !webaccess.userCanModify(req.params.pad, req);
hooks.callAll('padInitToolbar', {
toolbar,
isReadOnly
});
// can be removed when require-kernel is dropped
res.header('Feature-Policy', 'sync-xhr \'self\'');
const content = eejs.require('ep_etherpad-lite/templates/pad.html', {
req,
toolbar,
isReadOnly,
entrypoint: map.get('fileNamePad')
})
res.send(content);
});
args.app.get('/p/:pad/timeslider', (req: any, res: any, next: Function) => {
hooks.callAll('padInitToolbar', {
toolbar,
});
let timeSliderFileName = "/watch/timeslider?hash="+map.get('fileNamePad')
res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', {
req,
toolbar,
entrypoint: timeSliderFileName
}));
});
} }
// The client occasionally polls this endpoint to get an updated expiration for the express_sid // 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. // 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) => {

View file

@ -24,6 +24,7 @@
let socket; let socket;
// These jQuery things should create local references, but for now `require()` // These jQuery things should create local references, but for now `require()`
// assigns to the global `$` and augments it with plugins. // assigns to the global `$` and augments it with plugins.
require('./vendors/jquery'); require('./vendors/jquery');
@ -283,6 +284,7 @@ const handshake = async () => {
} }
}); });
socket.on('error', (error) => { socket.on('error', (error) => {
// pad.collabClient might be null if the error occurred before the hanshake completed. // pad.collabClient might be null if the error occurred before the hanshake completed.
if (pad.collabClient != null) { if (pad.collabClient != null) {
@ -315,6 +317,15 @@ const handshake = async () => {
() => $.ajax('../_extendExpressSessionLifetime', {method: 'PUT'}).catch(() => {}); () => $.ajax('../_extendExpressSessionLifetime', {method: 'PUT'}).catch(() => {});
setInterval(ping, window.clientVars.sessionRefreshInterval); setInterval(ping, window.clientVars.sessionRefreshInterval);
} }
if(window.clientVars.mode === "development") {
console.warn('Enabling development mode with live update')
socket.on('liveupdate', ()=>{
console.log('Live reload update received')
location.reload()
})
}
} else if (obj.disconnect) { } else if (obj.disconnect) {
padconnectionstatus.disconnected(obj.disconnect); padconnectionstatus.disconnected(obj.disconnect);
socket.disconnect(); socket.disconnect();

View file

@ -117,6 +117,14 @@ const handleClientVars = (message) => {
setInterval(ping, window.clientVars.sessionRefreshInterval); setInterval(ping, window.clientVars.sessionRefreshInterval);
} }
if(window.clientVars.mode === "development") {
console.warn('Enabling development mode with live update')
socket.on('liveupdate', ()=>{
console.log('Doing live reload')
location.reload()
})
}
// load all script that doesn't work without the clientVars // load all script that doesn't work without the clientVars
BroadcastSlider = require('./broadcast_slider') BroadcastSlider = require('./broadcast_slider')
.loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded); .loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded);

View file

@ -8,15 +8,14 @@ export class Html10n {
private rtl: string[] private rtl: string[]
private _pluralRules?: PluralFunc private _pluralRules?: PluralFunc
public mt: MicroEvent public mt: MicroEvent
private loader: Loader private loader: Loader | undefined
private translations: Map<string, any> public translations: Map<string, any>
private macros: Map<string, Function> private macros: Map<string, Function>
constructor() { constructor() {
this.language = undefined this.language = undefined
this.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"] this.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"]
this.mt = new MicroEvent() this.mt = new MicroEvent()
this.loader = new Loader([])
this.translations = new Map() this.translations = new Map()
this.macros = new Map() this.macros = new Map()
@ -469,16 +468,6 @@ export class Html10n {
} }
getTranslatableChildren(element: HTMLElement) { getTranslatableChildren(element: HTMLElement) {
if(!document.querySelectorAll) {
if (!element) return []
const nodes = element.getElementsByTagName('*')
, l10nElements = []
for (let i=0, n=nodes.length; i < n; i++) {
if (nodes[i].getAttribute('data-l10n-id'))
l10nElements.push(nodes[i]);
}
return l10nElements
}
return element.querySelectorAll('*[data-l10n-id]') return element.querySelectorAll('*[data-l10n-id]')
} }
@ -494,6 +483,7 @@ export class Html10n {
}) })
this.build(langs, (er: null, translations: Map<string, any>) =>{ this.build(langs, (er: null, translations: Map<string, any>) =>{
console.log("Translations are", translations)
this.translations = translations this.translations = translations
this.translateElement(translations) this.translateElement(translations)
this.mt.trigger('localized') this.mt.trigger('localized')
@ -535,13 +525,11 @@ export class Html10n {
* @param cb Function - a callback that will be called once all langs have been loaded * @param cb Function - a callback that will be called once all langs have been loaded
*/ */
build(langs: (string|undefined)[], cb: Function) { build(langs: (string|undefined)[], cb: Function) {
const that = this;
const build = new Map<string, any>() const build = new Map<string, any>()
this.asyncForEach(langs, function (lang: string, _i: number, next:LoaderFunc) { this.asyncForEach(langs, (lang: string, _i: number, next:LoaderFunc)=> {
if(!lang) return next(); if(!lang) return next();
that.loader.load(lang, next) this.loader!.load(lang, next)
}, () =>{ }, () =>{
let lang; let lang;
langs.reverse() langs.reverse()
@ -549,30 +537,31 @@ export class Html10n {
// loop through the priority array... // loop through the priority array...
for (let i=0, n=langs.length; i < n; i++) { for (let i=0, n=langs.length; i < n; i++) {
lang = langs[i] lang = langs[i]
if(!lang) continue; if(!lang) continue;
if(!(lang in that.loader.langs)) {// uh, we don't have this lang availbable.. if(!(lang in langs)) {// uh, we don't have this lang availbable..
// then check for related langs // then check for related langs
if(~lang.indexOf('-')) lang = lang.split('-')[0]; if(~lang.indexOf('-')) lang = lang.split('-')[0];
let l let l: string|undefined = ''
for(l in that.loader.langs) { for(l of langs) {
if(lang != l && l.indexOf(lang) === 0) { if(l && lang != l && l.indexOf(lang) === 0) {
lang = l lang = l
break; break;
} }
} }
// @ts-ignore
if(lang != l) continue; if(lang != l) continue;
} }
// ... and apply all strings of the current lang in the list // ... and apply all strings of the current lang in the list
// to our build object // to our build object
for (let string in that.loader.langs.get(lang)) { //lang = "de"
build.set(string,that.loader.langs.get(lang).get(string)) for (let string in this.loader!.langs.get(lang)) {
build.set(string,this.loader!.langs.get(lang)[string])
} }
// the last applied lang will be exposed as the // the last applied lang will be exposed as the
// lang the page was translated to // lang the page was translated to
that.language = lang this.language = lang
} }
cb(null, build) cb(null, build)
}) })
@ -848,7 +837,6 @@ class Loader {
} }
fetch(href: string, lang: string, callback: ErrorFunc) { fetch(href: string, lang: string, callback: ErrorFunc) {
const that = this;
if (this.cache.get(href)) { if (this.cache.get(href)) {
this.parse(lang, href, this.cache.get(href), callback) this.parse(lang, href, this.cache.get(href), callback)
@ -860,13 +848,14 @@ class Loader {
if (xhr.overrideMimeType) { if (xhr.overrideMimeType) {
xhr.overrideMimeType('application/json; charset=utf-8'); xhr.overrideMimeType('application/json; charset=utf-8');
} }
xhr.onreadystatechange = function() { xhr.onreadystatechange = ()=> {
if (xhr.readyState == 4) { if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status === 0) { if (xhr.status == 200 || xhr.status === 0) {
const data = JSON.parse(xhr.responseText); const data = JSON.parse(xhr.responseText);
that.cache.set(href, data) console.log("Data is", data)
this.cache.set(href, data)
// Pass on the contents for parsing // Pass on the contents for parsing
that.parse(lang, href, data, callback) this.parse(lang, href, data, callback)
} else { } else {
callback(new Error('Failed to load '+href)) callback(new Error('Failed to load '+href))
} }
@ -875,6 +864,7 @@ class Loader {
xhr.send(null); xhr.send(null);
} }
parse(lang: string, href: string, data: { parse(lang: string, href: string, data: {
[key: string]: string [key: string]: string
}, callback: ErrorFunc) { }, callback: ErrorFunc) {
@ -957,7 +947,6 @@ class Loader {
return callback(new Error(msg)); return callback(new Error(msg));
} }
} }
} }
@ -983,6 +972,7 @@ class Loader {
return return
} }
console.log("Setting lang", lang)
this.langs.set(lang,data[lang]) this.langs.set(lang,data[lang])
// TODO: Also store accompanying langs // TODO: Also store accompanying langs
callback() callback()