diff --git a/framework/eslint-config/package.json b/framework/eslint-config/package.json index e416ce386..24b3800c6 100644 --- a/framework/eslint-config/package.json +++ b/framework/eslint-config/package.json @@ -4,7 +4,7 @@ "license": "MIT", "main": "base.mjs", "dependencies": { - "@eslint-react/eslint-plugin": "^1.38.3", + "@eslint-react/eslint-plugin": "^1.38.4", "@eslint/compat": "^1.2.7", "@eslint/eslintrc": "^3.3.1", "@stylistic/eslint-plugin": "^4.2.0", diff --git a/package.json b/package.json index bb2ca18b0..327a05ca3 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@types/autocannon": "^7.12.6", "@types/chai": "^5.2.1", "@types/cross-spawn": "^6.0.6", - "@types/node": "22.13.8", + "@types/node": "22.13.14", "@types/semver": "^7.7.0", "autocannon": "^8.0.0", "cac": "^6.7.14", @@ -44,7 +44,7 @@ "cross-spawn": "^7.0.6", "esbuild": "0.25.0", "eslint": "^9.23.0", - "eslint-import-resolver-typescript": "^4.2.5", + "eslint-import-resolver-typescript": "^4.3.1", "eslint-import-resolver-webpack": "^0.13.10", "fs-extra": "^11.3.0", "globals": "^16.0.0", diff --git a/packages/common/lang.ts b/packages/common/lang.ts index 6a189a9d6..9b3ad1a11 100644 --- a/packages/common/lang.ts +++ b/packages/common/lang.ts @@ -17,8 +17,7 @@ export interface LangConfig { hidden: boolean; isBinary?: boolean; analysis?: string; - /** @deprecated */ - remote?: string; + remote: boolean; validAs?: Record; /** @deprecated */ pretest?: string | false; @@ -51,6 +50,7 @@ export function parseLang(config: string): Record { entry.code_file ||= `foo.${key}`; entry.execute ||= '/w/foo'; entry.key = key; + entry.remote = !!entry.remote; entry.hidden ||= false; entry.disabled ||= false; entry.isBinary ||= false; diff --git a/packages/components/frontend/ConfigEditor.tsx b/packages/components/frontend/ConfigEditor.tsx index d0fa52e1d..213725df5 100644 --- a/packages/components/frontend/ConfigEditor.tsx +++ b/packages/components/frontend/ConfigEditor.tsx @@ -15,10 +15,9 @@ const locales = { function MonacoContainer({ config, setValue, setError, monaco, schema, editorCallback, size, }) { - const editorRef = React.useRef(null); - const [loading, setLoading] = React.useState(false); const [editor, setEditor] = React.useState(null); const [model, setModel] = React.useState(null); + const [initialized, setInitialized] = React.useState(false); const { codeFontFamily } = React.useContext(ComponentsContext); React.useEffect(() => { @@ -62,14 +61,14 @@ function MonacoContainer({ } }); return () => disposable.dispose(); // eslint-disable-line - }, [editor, model, setValue, setError]); - React.useEffect(() => { - if (!editorRef.current || loading) return; - setLoading(true); + }, [editor, model, setValue, setError, schema]); + const initializeEditor = React.useCallback((element) => { + if (!element || initialized) return; + setInitialized(true); // eslint-disable-next-line const model = monaco.editor.createModel(config, 'yaml', monaco.Uri.parse('hydro://system/setting.yaml')); setModel(model); - const e = monaco.editor.create(editorRef.current, { + const e = monaco.editor.create(element, { theme: 'vs-light', lineNumbers: 'off', glyphMargin: true, @@ -85,9 +84,9 @@ function MonacoContainer({ ambiguousCharacters: true, }, }); - editorCallback(e, model, editorRef.current); + editorCallback(e, model, element); setEditor(e); - }, [editorRef.current]); + }, [config, monaco, editorCallback, codeFontFamily, initialized]); React.useEffect(() => { if (!editor) return; const current = editor.getValue({ lineEnding: '\n', preserveBOM: false }); @@ -111,7 +110,7 @@ function MonacoContainer({ model.pushEditOperations([], ops, undefined); }, [editor, config]); return ( -
); @@ -120,10 +119,17 @@ function MonacoContainer({ export default function ConfigEditor({ schema, config, monaco, Markdown, onSave, registerAction, sidebar, }) { - const [value, setValue] = React.useState(yaml.load(config)); + const getValue = () => { + try { + return yaml.load(config); + } catch (e) { + return {}; + } + }; + const [value, setValue] = React.useState(getValue); const [info, setInfo] = React.useState(''); const [stringConfig, setStringConfig] = React.useState(config); - const initial = React.useMemo(() => yaml.load(config), []); + const initial = React.useMemo(getValue, []); const { i18n } = React.useContext(ComponentsContext); const Form = React.useMemo(() => createSchemasteryReact({ diff --git a/packages/hydrooj/src/handler/domain.ts b/packages/hydrooj/src/handler/domain.ts index a45e01d87..0e37b2405 100644 --- a/packages/hydrooj/src/handler/domain.ts +++ b/packages/hydrooj/src/handler/domain.ts @@ -4,7 +4,7 @@ import moment from 'moment-timezone'; import type { Context } from '../context'; import { CannotDeleteSystemDomainError, DomainJoinAlreadyMemberError, DomainJoinForbiddenError, ForbiddenError, - InvalidJoinInvitationCodeError, OnlyOwnerCanDeleteDomainError, PermissionError, RoleAlreadyExistError, ValidationError, + InvalidJoinInvitationCodeError, NotFoundError, OnlyOwnerCanDeleteDomainError, PermissionError, RoleAlreadyExistError, ValidationError, } from '../error'; import type { DomainDoc } from '../interface'; import avatar from '../lib/avatar'; @@ -337,6 +337,7 @@ class DomainJoinHandler extends Handler { domain.get(target), domain.collUser.findOne({ domainId: target, uid: this.user._id }), ]); + if (!dudoc) throw new NotFoundError(target); const assignedRole = this.user.hasPriv(PRIV.PRIV_MANAGE_ALL_DOMAIN) ? 'root' : dudoc?.role || 'default'; diff --git a/packages/hydrooj/src/handler/manage.ts b/packages/hydrooj/src/handler/manage.ts index 0a70310bc..f87b70bb2 100644 --- a/packages/hydrooj/src/handler/manage.ts +++ b/packages/hydrooj/src/handler/manage.ts @@ -198,7 +198,11 @@ class SystemConfigHandler extends SystemHandler { }; try { - value = Schema.intersect(this.ctx.config.settings)(yaml.load(this.ctx.config.configSource)); + try { + value = Schema.intersect(this.ctx.config.settings)(yaml.load(this.ctx.config.configSource)); + } catch (e) { + value = yaml.load(this.ctx.config.configSource); + } for (const schema of this.ctx.config.settings) processNode(value, schema); } catch (e) { } this.response.body = { diff --git a/packages/hydrooj/src/settings.ts b/packages/hydrooj/src/settings.ts index 173990947..638e588fc 100644 --- a/packages/hydrooj/src/settings.ts +++ b/packages/hydrooj/src/settings.ts @@ -139,16 +139,15 @@ export class ConfigService extends Service { }); } let curValue = s(this.systemConfig); - if (dynamic) { - this.ctx.on('system/setting', () => { - try { - curValue = s(this.systemConfig); - } catch (e) { - logger.warn('Cannot read config: ', e.message); - curValue = null; - } - }); - } + if (!dynamic) return curValue; + this.ctx.on('system/setting', () => { + try { + curValue = s(this.systemConfig); + } catch (e) { + logger.warn('Cannot read config: ', e.message); + curValue = null; + } + }); const that = this; const getAccess = (path: (string | symbol)[]) => { if (path.some((p) => ConfigService.blacklist.includes(p.toString()))) throw new Error('Invalid path'); diff --git a/packages/scoreboard-xcpcio/package.json b/packages/scoreboard-xcpcio/package.json index 2a54e69a1..7d39c2070 100644 --- a/packages/scoreboard-xcpcio/package.json +++ b/packages/scoreboard-xcpcio/package.json @@ -7,6 +7,6 @@ "postinstall": "node -r @hydrooj/register install.ts" }, "dependencies": { - "@xcpcio/board-app": "^0.54.0" + "@xcpcio/board-app": "^0.55.1" } } diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index b666bfb5e..d0e0015bc 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -22,7 +22,7 @@ "@fontsource/source-code-pro": "^5.2.5", "@fontsource/ubuntu-mono": "^5.2.5", "@hydrooj/utils": "workspace:^", - "@sentry/browser": "^9.10.0", + "@sentry/browser": "^9.10.1", "@sentry/webpack-plugin": "^3.2.4", "@simplewebauthn/browser": "13.1.0", "@svgr/webpack": "^8.1.0", diff --git a/packages/ui-default/pages/setting.page.tsx b/packages/ui-default/pages/setting.page.tsx index 30c70d0dd..f5443cba3 100644 --- a/packages/ui-default/pages/setting.page.tsx +++ b/packages/ui-default/pages/setting.page.tsx @@ -46,7 +46,7 @@ const page = new NamedPage('manage_config', async () => { overflow: hidden; } .footer { - display: hidden; + visibility: hidden; } .omnibar-toggle { display: none; diff --git a/packages/ui-default/templates/partials/contest_sidebar.html b/packages/ui-default/templates/partials/contest_sidebar.html index 40d0af245..7d85aaa21 100644 --- a/packages/ui-default/templates/partials/contest_sidebar.html +++ b/packages/ui-default/templates/partials/contest_sidebar.html @@ -55,8 +55,9 @@ {{ _('Login to Attend Contest') }} {% elif not handler.user._dudoc.join %} - + + {{ _('Join Domain to Attend Contest') }} + {% else %} {{ _('No Permission to Attend Contest') }} diff --git a/packages/ui-next/package.json b/packages/ui-next/package.json index e6085b1d1..6ecdc0382 100644 --- a/packages/ui-next/package.json +++ b/packages/ui-next/package.json @@ -16,6 +16,6 @@ "@types/react": "^19.0.12", "@types/react-dom": "^19.0.4", "@vitejs/plugin-react-swc": "^3.8.1", - "vite": "^6.2.3" + "vite": "^6.2.4" } }