diff --git a/packages/hydrooj/src/handler/manage.ts b/packages/hydrooj/src/handler/manage.ts index b5774998b..269d06277 100644 --- a/packages/hydrooj/src/handler/manage.ts +++ b/packages/hydrooj/src/handler/manage.ts @@ -177,6 +177,7 @@ class SystemConfigHandler extends SystemHandler { let value = this.ctx.setting.configSource; const processNode = (node: any, schema: Schema, parent?: any, accessKey?: string) => { + if (!node) return; if (['union', 'intersect'].includes(schema.type)) { for (const item of schema.list) processNode(node, item, parent, accessKey); } @@ -193,7 +194,9 @@ class SystemConfigHandler extends SystemHandler { const temp = yaml.load(this.ctx.setting.configSource); for (const schema of this.ctx.setting.settings) processNode(temp, schema); value = yaml.dump(temp); - } catch (e) { } + } catch (e) { + logger.error('Failed to process config', e.message); + } this.response.body = { schema: Schema.intersect(this.ctx.setting.settings).toJSON(), value, diff --git a/packages/hydrooj/src/settings.ts b/packages/hydrooj/src/settings.ts index 16ea82e63..31e02f96b 100644 --- a/packages/hydrooj/src/settings.ts +++ b/packages/hydrooj/src/settings.ts @@ -172,12 +172,10 @@ export class SettingService extends Service { }); const that = this; const getAccess = (path: (string | symbol)[]) => { - if (path.some((p) => SettingService.blacklist.includes(p.toString()))) throw new Error('Invalid path'); + if (path.some((p) => SettingService.blacklist.includes(p.toString()))) throw new Error(`Invalid path: ${path.join('.')}`); let currentValue = curValue; - for (const p of path) { - currentValue = currentValue[p]; - } - if (typeof currentValue !== 'object' || !currentValue) return currentValue; + for (const p of path) currentValue = currentValue[p]; + if (typeof currentValue !== 'object' || !currentValue || Array.isArray(currentValue)) return currentValue; return new Proxy(currentValue, { get(self, key: string) { return getAccess(path.concat(key)); diff --git a/packages/ui-default/backendlib/builder.ts b/packages/ui-default/backendlib/builder.ts index 56e4bedfd..d00c4cf27 100644 --- a/packages/ui-default/backendlib/builder.ts +++ b/packages/ui-default/backendlib/builder.ts @@ -30,7 +30,8 @@ const federationPlugin: esbuild.Plugin = { setup(b) { const packages = { react: 'React', - 'react-dom': 'ReactDOM', + 'react/jsx-runtime': 'jsxRuntime', + 'react-dom/client': 'ReactDOM', jquery: '$', }; b.onResolve({ filter: /^@hydrooj\/ui-default/ }, () => ({ @@ -38,7 +39,7 @@ const federationPlugin: esbuild.Plugin = { namespace: 'ui-default', })); for (const key in packages) { - b.onResolve({ filter: new RegExp(`^${key}($|\\/)`) }, () => ({ + b.onResolve({ filter: new RegExp(`^${key}$`) }, () => ({ path: packages[key], namespace: 'ui-default', })); diff --git a/packages/ui-default/build/config/webpack.ts b/packages/ui-default/build/config/webpack.ts index 40152690a..088178460 100644 --- a/packages/ui-default/build/config/webpack.ts +++ b/packages/ui-default/build/config/webpack.ts @@ -1,5 +1,5 @@ import fs from 'fs'; -import { dirname } from 'path'; +import { dirname, join } from 'path'; import { sentryWebpackPlugin } from '@sentry/webpack-plugin'; import { CleanWebpackPlugin } from 'clean-webpack-plugin'; import CopyWebpackPlugin from 'copy-webpack-plugin'; @@ -136,6 +136,9 @@ export default async function (env: { watch?: boolean, production?: boolean, mea vj: root(), 'react/jsx-runtime': require.resolve('react/jsx-runtime'), react: require.resolve('react'), + 'react-dom/client': require.resolve('react-dom/client'), + 'react-dom/server': join(dirname(require.resolve('react-dom/package.json')), 'server.browser.js'), + 'react-dom': require.resolve('react-dom'), }, }, module: { @@ -282,7 +285,8 @@ export default async function (env: { watch?: boolean, production?: boolean, mea monaco: 'monaco-editor/esm/vs/editor/editor.api', }), new ExtractCssPlugin({ - filename: '[name].css?[fullhash:6]', + filename: `[name]-${version}.css?[fullhash:6]`, + chunkFilename: '[name]-[chunkhash:6].chunk.css', }), new WebpackManifestPlugin({}), new webpack.IgnorePlugin({ resourceRegExp: /(^\.\/locale$)/ }), diff --git a/packages/ui-default/build/main.ts b/packages/ui-default/build/main.ts index 4bf54e7c8..cdffc40cd 100644 --- a/packages/ui-default/build/main.ts +++ b/packages/ui-default/build/main.ts @@ -117,9 +117,6 @@ async function main() { }); } else { await runWebpack(argv.options as any); - if (fs.existsSync('public/theme.css')) { - fs.copyFileSync('public/theme.css', `public/theme-${pkg.version}.css`); - } await Promise.all(globbySync('public/**/*.map').map((i) => fs.remove(i))); } process.chdir(dir); diff --git a/packages/ui-default/index.ts b/packages/ui-default/index.ts index b3e903ffb..11f8c7676 100644 --- a/packages/ui-default/index.ts +++ b/packages/ui-default/index.ts @@ -162,7 +162,16 @@ const codeFontRange = { const defaultAbout = (yaml.load(readFileSync(join(__dirname, 'setting.yaml'), 'utf-8')) as any).about.value; -export function apply(ctx: Context) { +export const name = 'ui-default'; +export const Config = Schema.object({ + serviceWorker: Schema.object({ + preload: Schema.string().default(''), + assets: Schema.array(Schema.string()).default([]), + domains: Schema.array(Schema.string()).default([]), + }).description('Service worker optimization settings').experimental(), +}); + +export function apply(ctx: Context, config: ReturnType) { ctx.inject(['setting'], (c) => { c.setting.PreferenceSetting( SettingModel.Setting('setting_display', 'rounded', false, 'boolean', 'Rounded Corners'), @@ -179,7 +188,6 @@ export function apply(ctx: Context) { 'ui-default': Schema.object({ footer_extra_html: Schema.string().role('textarea').default(''), nav_logo_dark: Schema.string().default('/components/navigation/nav-logo-small_dark.png'), - preload: Schema.string().default(''), domainNavigation: Schema.boolean().default(true).description('Show Domain Navigation'), about: Schema.string().role('markdown').default(defaultAbout), enableScratchpad: Schema.boolean().default(true).description('Enable Scratchpad Mode'), @@ -202,15 +210,15 @@ export function apply(ctx: Context) { }); ctx.on('handler/after', async (that) => { that.UiContext.SWConfig = { - preload: SystemModel.get('ui-default.preload'), + preload: config.serviceWorker.preload, hosts: [ `http://${that.request.host}`, `https://${that.request.host}`, SystemModel.get('server.url'), SystemModel.get('server.cdn'), ], - assets: ((SystemModel.get('ui-default.assets') || '').split(',')).filter((i) => i) || [], - domains: SystemModel.get('ui-default.domains') || [], + assets: config.serviceWorker.assets, + domains: config.serviceWorker.domains, }; }); ctx.plugin(TemplateService); diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 609e4cb01..c2e18a676 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -21,8 +21,8 @@ "@fontsource/source-code-pro": "^5.2.7", "@fontsource/ubuntu-mono": "^5.2.8", "@hydrooj/utils": "workspace:^", - "@sentry/browser": "^10.20.0", - "@sentry/webpack-plugin": "^4.4.0", + "@sentry/browser": "^10.22.0", + "@sentry/webpack-plugin": "^4.5.0", "@simplewebauthn/browser": "13.2.2", "@svgr/webpack": "^8.1.0", "@types/html-to-text": "^9.0.4", @@ -33,7 +33,7 @@ "@types/markdown-it": "^14.1.2", "@types/nunjucks": "^3.2.6", "@types/pickadate": "^3.5.35", - "@types/qrcode": "^1.5.5", + "@types/qrcode": "^1.5.6", "@types/react": "^18.3.26", "@types/react-dom": "^18.3.7", "@types/redux-logger": "^3.0.13", @@ -99,7 +99,7 @@ "redux-thunk": "^3.1.0", "rupture": "^0.7.1", "sass": "^1.93.2", - "sass-loader": "^16.0.5", + "sass-loader": "^16.0.6", "schemastery": "^3.17.1", "shorty.js": "^1.0.1", "slideout": "^1.0.1", diff --git a/packages/ui-default/service-worker.ts b/packages/ui-default/service-worker.ts index 059cf0cdc..f3090bc4e 100644 --- a/packages/ui-default/service-worker.ts +++ b/packages/ui-default/service-worker.ts @@ -87,7 +87,7 @@ self.addEventListener('notificationclick', (event) => { })); }); -const PRECACHE = `precache-${process.env.VERSION}`; +const PRECACHE = 'ui-resources-cache'; const DO_NOT_PRECACHE = ['.worker.js', 'fonts']; function shouldCachePath(path: string) {