plugins: own custom.html

Massimo Melina committed May 7, 2023 at 15:33 UTC a0613048bba46933f841d008ba0f445383a7dcad
3 files changed +46 -23
dev-plugins.md
+10 -2
@@ -138,8 +138,6 @@ The `api` object you get as parameter of the `init` contains the following:
138 If you need something for your plugin that's not covered by `api`, you can test it with this method,
139 but you should then discuss it on the forum because an addition to `api` is your best option for making a future-proof plugin.
140
141 -Each plug-in can have a `public` folder, and its files will be accessible at `/~/plugins/PLUGIN_NAME/FILENAME`.
142 -
141 ## Front-end specific
142
143 The following information applies to the default front-end, and may not apply to a custom one.
@@ -218,6 +216,15 @@ This is a list of available frontend-events, with respective object parameter an
216 }
217 type FileMenuProp = [ReactNode,ReactNode] | ReactElement
218 ```
219 +
220 +## Other files
221 +
222 +Together with the main file (plugin.js), you can have
223 +
224 +- `public` folder, and its files will be accessible at `/~/plugins/PLUGIN_NAME/FILENAME`
225 +- `custom.html` file, that works exactly like the main `custom.html`. Even when same section is specified
226 + by 2 (or more) files, both contents are appended.
227 +
228 ## Publish your plug-in
229
230 Suggested method for publishing is to have a dedicated repository on GitHub, with topic `hfs-plugin`.
@@ -241,6 +248,7 @@ HFS will scan through them in inverted alphabetical order searching for a compat
248
249 - 8.1 (v0.45.0) should have been 0.44.0 but forgot to update number
250 - full URL support for frontend_js and frontend_css
251 + - custom.html
252 - entry.cantOpen
253 - HFS.apiCall, reloadList, logout, h, React, state, t, _, dialogLib
254 - second parameter of onEvent is now deprecated
src/customHtml.ts
+29 -19
@@ -6,16 +6,20 @@ import { watchLoad } from './watchLoad'
6 import { proxy } from 'valtio/vanilla' // without /vanilla we trigger react dependency
7 import Dict = NodeJS.Dict
8 import { writeFile } from 'fs/promises'
9 +import { mapPlugins } from './plugins'
10
11 export const customHtmlSections: ReadonlyArray<string> = ['top', 'bottom', 'beforeHeader', 'afterHeader',
12 'afterMenuBar', 'afterEntryName', 'afterList', 'beforeLogin']
13
13 -export const customHtmlState = proxy<{
14 - sections: Map<string,string>
15 -}>({
16 - sections: new Map()
14 +export const customHtmlState = proxy({
15 + sections: newCustomHtmlState()
16 })
17
18 +type CustomHtml = ReturnType<typeof newCustomHtmlState>
19 +export function newCustomHtmlState() {
20 + return new Map<string, string>()
21 +}
22 +
23 const FILE = 'custom.html'
24
25 if (!existsSync(FILE))
@@ -24,23 +28,29 @@ if (!existsSync(FILE))
28 writeFileSync(FILE, legacy)
29 customHeader.set('') // get rid of it
30 })
27 -watchLoad(FILE, data => {
28 - const re = /^\[(\w+)] *$/gm
29 - customHtmlState.sections.clear()
30 - if (!data) return
31 - let name: string | undefined = 'top'
32 - do {
33 - let last = re.lastIndex
34 - const match = re.exec(data)
35 - const content = data.slice(last, !match ? undefined : re.lastIndex - (match?.[0]?.length || 0)).trim()
36 - if (content)
37 - customHtmlState.sections.set(name, content)
38 - name = match?.[1]
39 - } while (name)
40 -})
31 +
32 +watchLoadCustomHtml(customHtmlState.sections)
33 +
34 +export function watchLoadCustomHtml(state: CustomHtml, folder='') {
35 + return watchLoad(prefix('', folder, '/') + FILE, data => {
36 + const re = /^\[(\w+)] *$/gm
37 + state.clear()
38 + if (!data) return
39 + let name: string | undefined = 'top'
40 + do {
41 + let last = re.lastIndex
42 + const match = re.exec(data)
43 + const content = data.slice(last, !match ? undefined : re.lastIndex - (match?.[0]?.length || 0)).trim()
44 + if (content)
45 + state.set(name, content)
46 + name = match?.[1]
47 + } while (name)
48 + })
49 +}
50
51 export function getSection(name: string) {
43 - return customHtmlState.sections.get(name) || ''
52 + return (customHtmlState.sections.get(name) || '')
53 + + mapPlugins(pl => pl.getData().customHtml.get(name)).join('\n')
54 }
55
56 export async function saveCustomHtml(sections: Dict<string>) {
src/plugins.ts
+7 -2
@@ -27,6 +27,7 @@ import { readFile } from 'fs/promises'
27 import { existsSync, mkdirSync } from 'fs'
28 import { getConnections } from './connections'
29 import { dirname, resolve } from 'path'
30 +import { newCustomHtmlState, watchLoadCustomHtml } from './customHtml'
31
32 export const PATH = 'plugins'
33 export const DISABLING_POSTFIX = '-disabled'
@@ -290,8 +291,12 @@ function loadPlugin(id: string, path: string) {
291 },
292 getHfsConfig: getConfig,
293 })
293 - Object.assign(data, res)
294 - const plugin = new Plugin(id, dirname(module), data, unwatch)
294 + const folder = dirname(module)
295 + Object.assign(data, res, {
296 + customHtml: newCustomHtmlState()
297 + })
298 + const customHtmlWatcher = watchLoadCustomHtml(data.customHtml, folder)
299 + const plugin = new Plugin(id, folder, data, _.flow(unwatch, customHtmlWatcher.unwatch))
300 if (alreadyRunning)
301 events.emit('pluginUpdated', Object.assign(_.pick(plugin, 'started'), getPluginInfo(id)))
302 else {