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 {