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);