mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-05-04 22:27:10 -04:00
Added live reloading.
This commit is contained in:
parent
d522de17e6
commit
bbf4adb075
6 changed files with 144 additions and 91 deletions
|
@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
50
src/static/js/vendors/html10n.ts
vendored
50
src/static/js/vendors/html10n.ts
vendored
|
@ -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()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue