From 2f2e006eff4bbab53ee878fce09142572a090b8d Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 23 Oct 2025 17:57:29 +0800 Subject: [PATCH] bug fixes --- framework/register/package.json | 2 +- packages/hydrojudge/src/hosts/builtin.ts | 14 +++--- packages/scoreboard-xcpcio/index.ts | 7 +-- packages/scoreboard-xcpcio/package.json | 5 +- .../backendlib/markdown-it-media.ts | 7 ++- .../ui-default/components/dialog/index.tsx | 11 ++++- .../components/signin/signInDialog.page.js | 12 ++--- packages/ui-default/pages/contest.page.styl | 3 ++ .../ui-default/pages/home_domain.page.tsx | 48 ++++++------------- .../ui-default/pages/problem_main.page.tsx | 12 ++--- 10 files changed, 56 insertions(+), 65 deletions(-) diff --git a/framework/register/package.json b/framework/register/package.json index ade5a4894..c447fe269 100644 --- a/framework/register/package.json +++ b/framework/register/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/register", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "main": "index.js", "engines": { diff --git a/packages/hydrojudge/src/hosts/builtin.ts b/packages/hydrojudge/src/hosts/builtin.ts index 393743ca4..00b6859eb 100644 --- a/packages/hydrojudge/src/hosts/builtin.ts +++ b/packages/hydrojudge/src/hosts/builtin.ts @@ -69,21 +69,19 @@ export async function apply(ctx: HydroContext) { const parallelism = getConfig('parallelism'); async function collectInfo() { const coll = db.collection('status'); - const compilers = await compilerVersions(langs); + const [compilers, size] = await Promise.all([ + compilerVersions(langs), + stackSize(), + ]); await coll.updateOne( { mid: info.mid, type: 'server' }, - { $set: { compilers } }, - { upsert: true }, - ); - const size = await stackSize(); - await coll.updateOne( - { mid: info.mid, type: 'server' }, - { $set: { stackSize: size } }, + { $set: { compilers, stackSize: size } }, { upsert: true }, ); } ctx.effect(() => { const taskConsumer = TaskModel.consume({ type: 'judge' }, handle, true, parallelism); + collectInfo(); const dispose = ctx.on('system/setting', () => { taskConsumer.setConcurrency(getConfig('parallelism')); collectInfo(); diff --git a/packages/scoreboard-xcpcio/index.ts b/packages/scoreboard-xcpcio/index.ts index 7c96efeec..79e1b1fef 100644 --- a/packages/scoreboard-xcpcio/index.ts +++ b/packages/scoreboard-xcpcio/index.ts @@ -1,6 +1,6 @@ import path from 'path'; import { - ContestModel, Context, fs, getAlphabeticId, ObjectId, PERM, Schema, STATUS, Types, UserModel, + avatar, ContestModel, Context, fs, getAlphabeticId, ObjectId, PERM, Schema, STATUS, Types, UserModel, } from 'hydrooj'; const file = fs.readFileSync(path.join(__dirname, 'public/assets/board.html'), 'utf8'); @@ -47,8 +47,9 @@ export async function apply(ctx: Context) { team_id: `${udoc._id}`, name: udoc.uname, organization: udoc.school, - members: [], - coach: '', + members: udoc.members?.split(',').filter((t) => t) || [], + coach: udoc.coach, + badge: { url: avatar(udoc.avatar) }, group: [ ...(udoc.group || []), i.unrank ? 'unofficial' : 'official', diff --git a/packages/scoreboard-xcpcio/package.json b/packages/scoreboard-xcpcio/package.json index 60d594e38..88d3d16bb 100644 --- a/packages/scoreboard-xcpcio/package.json +++ b/packages/scoreboard-xcpcio/package.json @@ -8,6 +8,9 @@ "postinstall": "node -r @hydrooj/register install.ts" }, "dependencies": { - "@xcpcio/board-app": "^0.64.5" + "@xcpcio/board-app": "^0.67.0" + }, + "devDependencies": { + "@xcpcio/types": "^0.67.0" } } diff --git a/packages/ui-default/backendlib/markdown-it-media.ts b/packages/ui-default/backendlib/markdown-it-media.ts index 5410b9b2f..8deffecfa 100644 --- a/packages/ui-default/backendlib/markdown-it-media.ts +++ b/packages/ui-default/backendlib/markdown-it-media.ts @@ -102,10 +102,13 @@ export function Media(md: MarkdownIt) { } if (service === 'pdf') { if (src.startsWith('file://') || src.startsWith('./')) src += src.includes('?') ? '&noDisposition=1' : '?noDisposition=1'; + // A response with has content-disposition header causes the browser to download the file automatically. + // As we cannot control response header from external sites, we block embedding external PDFs. + else return '

Embedding an external PDF is no longer supported.

'; return `\ - - + + `; diff --git a/packages/ui-default/components/dialog/index.tsx b/packages/ui-default/components/dialog/index.tsx index 1cd700533..a39e202ab 100644 --- a/packages/ui-default/components/dialog/index.tsx +++ b/packages/ui-default/components/dialog/index.tsx @@ -125,14 +125,19 @@ export interface Field { } type Result> = { - [K in keyof R]: ('text' | 'password' | 'username' | 'domain') extends R[K]['type'] ? string + [K in keyof R]: R[K]['type'] extends ('text' | 'password' | 'username' | 'domain') ? string : R[K]['type'] extends 'checkbox' ? boolean : R[K]['type'] extends 'userId' ? number : R[K]['type'] extends 'user' ? any : never; }; -export async function prompt>(title: string, fields: R): Promise> { +interface PromptOptions { + cancelByClickingBack: boolean; + cancelByEsc: boolean; +} + +export async function prompt>(title: string, fields: R, options?: PromptOptions): Promise> { let valueCache: Result = {} as any; const defaultValues = Object.fromEntries(Object.entries(fields) .map(([name, field]: [T, Field]) => { @@ -225,6 +230,8 @@ export async function prompt>(title const res = await new Dialog({ $body: $(div), $action: [buttonCancel, buttonOk].join('\n'), + cancelByClickingBack: options?.cancelByClickingBack ?? false, + cancelByEsc: options?.cancelByEsc ?? false, onDispatch(action) { if (action === 'ok') { for (const [name, field] of Object.entries(fields) as [string, Field][]) { diff --git a/packages/ui-default/components/signin/signInDialog.page.js b/packages/ui-default/components/signin/signInDialog.page.js index 4a0cd6b5b..414406adc 100644 --- a/packages/ui-default/components/signin/signInDialog.page.js +++ b/packages/ui-default/components/signin/signInDialog.page.js @@ -2,12 +2,10 @@ import { browserSupportsWebAuthnAutofill, startAuthentication } from '@simpleweb import $ from 'jquery'; import responsiveCutoff from 'vj/breakpoints.json'; import DomDialog from 'vj/components/dialog/DomDialog'; -import { InfoDialog } from 'vj/components/dialog/index'; +import { alert } from 'vj/components/dialog/index'; import Notification from 'vj/components/notification'; import { AutoloadPage } from 'vj/misc/Page'; -import { - i18n, mediaQuery, request, tpl, -} from 'vj/utils'; +import { i18n, mediaQuery, request } from 'vj/utils'; const signinDialogPage = new AutoloadPage('signinDialogPage', null, () => { const signInDialog = DomDialog.getOrConstruct($('.dialog--signin'), { @@ -18,7 +16,7 @@ const signinDialogPage = new AutoloadPage('signinDialogPage', null, () => { let authnInitialized = false; async function initPasskey() { - if (authnInitialized) return; + if (authnInitialized || !window.isSecureContext) return; authnInitialized = true; const support = await browserSupportsWebAuthnAutofill(); if (!support) return; @@ -79,9 +77,7 @@ const signinDialogPage = new AutoloadPage('signinDialogPage', null, () => { $('[data-lostpass]').on('click', (e) => { e.preventDefault(); - new InfoDialog({ - $body: tpl.typoMsg(i18n('Relax and try to remember your password.')), - }).open(); + alert(i18n('Relax and try to remember your password.')); }); }); diff --git a/packages/ui-default/pages/contest.page.styl b/packages/ui-default/pages/contest.page.styl index cd9aa1ef5..c882d9156 100644 --- a/packages/ui-default/pages/contest.page.styl +++ b/packages/ui-default/pages/contest.page.styl @@ -164,6 +164,9 @@ $highlight-button-color = #F6DF45 .row max-width: 100vw !important; + .data-table td + line-height: 1.5 + .col--rank width: rem(60px) text-align: center diff --git a/packages/ui-default/pages/home_domain.page.tsx b/packages/ui-default/pages/home_domain.page.tsx index d737e690b..218d2bf27 100644 --- a/packages/ui-default/pages/home_domain.page.tsx +++ b/packages/ui-default/pages/home_domain.page.tsx @@ -1,42 +1,22 @@ import $ from 'jquery'; -import React from 'react'; -import DomainSelectAutoComplete from 'vj/components/autocomplete/DomainSelectAutoComplete'; -import { ActionDialog } from 'vj/components/dialog'; +import { prompt } from 'vj/components/dialog'; import { NamedPage } from 'vj/misc/Page'; -import { i18n, tpl } from 'vj/utils'; +import { i18n } from 'vj/utils'; export default new NamedPage('home_domain', () => { - $(tpl( -
-
-

{i18n('Join Domain')}

-
-
-
- -
-
-
, - )).appendTo(document.body); - const selector: any = DomainSelectAutoComplete.getOrConstruct($('#join-domain-dialog [name="domainId"]')); - $(document).on('click', '[id="join-domain-button"]', async () => { - const action = await new ActionDialog({ - $body: $('.dialog__body--join-domain > div'), - onDispatch(a) { - if (a === 'ok' && selector.value() === null) { - selector.focus(); - return false; - } - return true; + const res = await prompt(i18n('Join Domain'), { + domain: { + type: 'domain', + label: i18n('Domain ID'), + required: true, + autofocus: true, }, - }).open(); - if (action !== 'ok') return; - window.location.href = `/domain/join?target=${encodeURIComponent(selector.value()._id.toString())}`; + }, { + cancelByClickingBack: true, + cancelByEsc: true, + }); + if (!res?.domain) return; + window.location.href = `/domain/join?target=${encodeURIComponent(res.domain.toString())}`; }); }); diff --git a/packages/ui-default/pages/problem_main.page.tsx b/packages/ui-default/pages/problem_main.page.tsx index 1c17cd45e..5a0316d27 100644 --- a/packages/ui-default/pages/problem_main.page.tsx +++ b/packages/ui-default/pages/problem_main.page.tsx @@ -15,11 +15,12 @@ import { addSpeculationRules, delay, i18n, pjax, request, } from 'vj/utils'; +const keywords = ['category', 'difficulty', 'namespace']; const list = []; const filterTags = {}; -const pinned: Record = { category: [], difficulty: [], namespace: [] }; -const selections = { category: {}, difficulty: {}, namespace: {} }; -const selectedTags: Record = { category: [], difficulty: [], namespace: [] }; +const pinned: Record = Object.fromEntries(keywords.map((keyword) => [keyword, []])); +const selections = Object.fromEntries(keywords.map((keyword) => [keyword, {}])); +const selectedTags: Record = Object.fromEntries(keywords.map((keyword) => [keyword, []])); let selectedPids: string[] = []; let clearSelectionHandler: (() => void) | null = null; @@ -35,7 +36,7 @@ function setDomSelected($dom, selected, icon?) { } const parserOptions = { - keywords: ['category', 'difficulty', 'namespace'], + keywords, offsets: true, alwaysArray: true, tokenize: true, @@ -46,8 +47,7 @@ function writeSelectionToInput() { const parsedCurrentValue = parser.parse(currentValue, parserOptions) as SearchParserResult; const q = parser.stringify({ ...parsedCurrentValue, - category: selectedTags.category, - difficulty: selectedTags.difficulty, + ...keywords.reduce((acc, keyword) => ({ ...acc, [keyword]: selectedTags[keyword] }), {}), text: parsedCurrentValue.text, }, parserOptions); $('[name="q"]').val(q);