simplified frontend-side plugins' injection
Massimo Melina committed
Jan 15, 2022 at 14:45 UTC
b0d828aa98bb2df9e1adb6a8431db7cf6a42ccc7
5 files changed
+21
-45
frontend/public/index.html
+1
-4
@@ -5,15 +5,12 @@
5
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6
<meta name="viewport" content="width=device-width, initial-scale=1" />
7
<meta name="theme-color" content="#000000" />
8
- <meta
9
- name="description"
10
- content="Web site created using create-react-app"
11
- />
8
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
9
<link href="icons.css" rel="stylesheet" />
10
<title>File Server</title>
11
</head>
12
<body>
13
+ _HFS_PLUGINS_
14
<noscript>You need to enable JavaScript to run this app.</noscript>
15
<div id="root"></div>
16
</body>
frontend/src/App.tsx
+1
-26
@@ -1,44 +1,19 @@
1
import { BrowserRouter, Route, Routes } from "react-router-dom"
2
-import { createElement as h, Fragment, useEffect, useState } from 'react'
2
+import { createElement as h, Fragment } from 'react'
3
import { BrowseFiles } from "./BrowseFiles";
4
import { Dialogs } from './dialog'
5
-import { useApi } from "./api";
5
import useTheme from "./useTheme";
6
7
function App() {
9
- const extras = useApi('extras_to_load')
8
useTheme()
11
- if (!useImportJs(extras))
12
- return null
9
return h(Fragment, {},
10
h(BrowserRouter, {},
11
h(Routes, {},
12
h(Route, { path:'*', element:h(BrowseFiles) })
13
)
14
),
19
- extras?.css?.map((href:string) =>
20
- h('link', { key:href, rel:"stylesheet", type:"text/css", href })),
15
h(Dialogs)
16
)
17
}
18
19
export default App;
26
-
27
-// return true when all is loaded
28
-function useImportJs(extras:{ js?: string[] }) {
29
- const [ready, setReady] = useState(false)
30
- useEffect(() => {
31
- if (!extras) return
32
- const toImport = extras.js
33
- let missing = toImport?.length || 0
34
- if (!missing)
35
- return setReady(true)
36
- for (const url of toImport!) {
37
- const el = document.createElement('script')
38
- el.src = url
39
- el.onload = ()=> setReady(!--missing)
40
- document.body.appendChild(el)
41
- }
42
- }, [extras])
43
- return ready
44
-}
src/frontEndApis.ts
-10
@@ -1,6 +1,4 @@
1
import { ApiHandlers } from './apis'
2
-import { mapPlugins } from './plugins'
3
-import { PLUGINS_PUB_URI } from './const'
2
import * as api_file_list from './api.file_list'
3
import * as api_auth from './api.auth'
4
import { setConfig } from './config'
@@ -11,14 +9,6 @@ export const frontEndApis: ApiHandlers = {
9
10
...api_auth,
11
14
- async extras_to_load() {
15
- const css = mapPlugins((plug,k) =>
16
- plug.frontend_css?.map(f => PLUGINS_PUB_URI + k + '/' + f)).flat().filter(Boolean)
17
- const js = mapPlugins((plug,k) =>
18
- plug.frontend_js?.map(f => PLUGINS_PUB_URI + k + '/' + f)).flat().filter(Boolean)
19
- return { css, js }
20
- },
21
-
12
async config({ values }, ctx) {
13
if (isLocalhost(ctx.ip))
14
setConfig(values)
src/serveFrontend.ts
+19
-3
@@ -1,8 +1,9 @@
1
import proxy from 'koa-better-http-proxy'
2
import Koa from 'koa'
3
import fs from 'fs/promises'
4
-import { DEV, FRONTEND_URI, METHOD_NOT_ALLOWED, NO_CONTENT } from './const'
4
+import { DEV, FRONTEND_URI, METHOD_NOT_ALLOWED, NO_CONTENT, PLUGINS_PUB_URI } from './const'
5
import { serveFile } from './serveFile'
6
+import { mapPlugins } from './plugins'
7
8
export const serveFrontend = DEV ? serveProxyFrontend() : serveStaticFrontend()
9
@@ -12,7 +13,7 @@ function serveProxyFrontend() {
13
filter: ctx => ctx.method === 'GET' || (ctx.status = METHOD_NOT_ALLOWED) && false,
14
proxyReqPathResolver: (ctx) => ctx.path.endsWith('/') ? '/' : ctx.path,
15
userResDecorator: (res, data, req) => {
15
- return req.url.endsWith('/') ? replaceFrontEndRes(data.toString('utf8'))
16
+ return req.url.endsWith('/') ? treatIndex(data.toString('utf8'))
17
: data
18
}
19
})
@@ -30,7 +31,7 @@ function serveStaticFrontend() : Koa.Middleware {
31
if (method !== 'GET')
32
return ctx.status = METHOD_NOT_ALLOWED
33
if (path.endsWith('/')) {
33
- ctx.body = replaceFrontEndRes(String(await fs.readFile(BASE + 'index.html')))
34
+ ctx.body = treatIndex(String(await fs.readFile(BASE + 'index.html')))
35
ctx.type = 'html'
36
} else {
37
const fullPath = BASE + path.slice(1)
@@ -49,3 +50,18 @@ function serveStaticFrontend() : Koa.Middleware {
50
function replaceFrontEndRes(body: string) {
51
return body.replace(/((?:src|href) *= *['"])\/?(?![a-z]+:\/\/)/g, '$1'+FRONTEND_URI)
52
}
53
+
54
+function treatIndex(body: string) {
55
+ return replaceFrontEndRes(body)
56
+ // replacing this text allow us to avoid injecting in frontends that don't support plugins. Don't use a <--comment--> or it will be removed by webpack
57
+ .replace('_HFS_PLUGINS_', pluginsInjection)
58
+}
59
+
60
+function pluginsInjection() {
61
+ const css = mapPlugins((plug,k) =>
62
+ plug.frontend_css?.map(f => PLUGINS_PUB_URI + k + '/' + f)).flat().filter(Boolean)
63
+ const js = mapPlugins((plug,k) =>
64
+ plug.frontend_js?.map(f => PLUGINS_PUB_URI + k + '/' + f)).flat().filter(Boolean)
65
+ return css.map(uri => `\n<link rel='stylesheet' type='text/css' href='${uri}'/>`).join('')
66
+ + js.map(uri => `\n<script defer src='${uri}'></script>`).join('')
67
+}
todo.md
-2
@@ -1,7 +1,5 @@
1
# To do
2
-- inject extras directly into <head> ? easier for makers and faster to load
2
- confirm archive
4
-- automatic theme based on system preferences
3
- archive button as a link that can be copied
4
- emoji as an alternative to icons?
5
- smaller icons file?