From 792267bc766380585e24902ca58e8221e6a7f9bb Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 17 Apr 2026 21:05:14 +0800 Subject: [PATCH] core&ui: add keep scoreboard hidden option --- package.json | 6 +++--- packages/hydrooj/src/handler/contest.ts | 6 ++++-- packages/hydrooj/src/interface.ts | 1 + packages/hydrooj/src/model/contest.ts | 12 ++++++------ packages/onsite-toolkit/index.ts | 18 +++--------------- packages/ui-default/locales/zh.yaml | 1 + packages/ui-default/locales/zh_TW.yaml | 1 + .../ui-default/templates/contest_edit.html | 11 +++++++++++ 8 files changed, 30 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index f6f53e8c3..ea136d79f 100644 --- a/package.json +++ b/package.json @@ -47,16 +47,16 @@ "eslint-import-resolver-typescript": "^4.4.4", "eslint-import-resolver-webpack": "^0.13.11", "fs-extra": "^11.3.4", - "globals": "^17.4.0", + "globals": "^17.5.0", "globby": "~14.0.2", "inspectpack": "^4.7.1", "mongodb-memory-server": "11.0.1", "nyc": "^18.0.0", "ora": "^9.3.0", - "oxlint": "^1.58.0", + "oxlint": "^1.60.0", "package-json": "^10.0.1", "semver": "^7.7.4", - "simple-git": "^3.33.0", + "simple-git": "^3.36.0", "supertest": "^7.2.2", "supports-hyperlinks": "^4.4.0", "typescript": "5.8.3", diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index 141e25725..a948b10cd 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -403,12 +403,14 @@ export class ContestEditHandler extends Handler { @param('maintainer', Types.NumericArray, true) @param('allowViewCode', Types.Boolean) @param('allowPrint', Types.Boolean) + @param('keepScoreboardHidden', Types.Boolean) @param('langs', Types.CommaSeperatedArray, true) async postUpdate( domainId: string, tid: ObjectId, beginAtDate: string, beginAtTime: string, duration: number, title: string, content: string, rule: string, _pids: string, rated = false, _code = '', autoHide = false, assign: string[] = [], lock: number = null, - contestDuration: number = null, maintainer: number[] = [], allowViewCode = false, allowPrint = false, langs: string[] = [], + contestDuration: number = null, maintainer: number[] = [], allowViewCode = false, allowPrint = false, + keepScoreboardHidden = false, langs: string[] = [], ) { if (!Object.keys(contest.RULES).includes(rule) || contest.RULES[rule].hidden) throw new ValidationError('rule'); if (autoHide) this.checkPerm(PERM.PERM_EDIT_PROBLEM); @@ -450,7 +452,7 @@ export class ContestEditHandler extends Handler { }); } await contest.edit(domainId, tid, { - assign, _code, autoHide, lockAt, maintainer, allowViewCode, allowPrint, langs, + assign, _code, autoHide, lockAt, maintainer, allowViewCode, allowPrint, keepScoreboardHidden, langs, }); this.response.body = { tid }; this.response.redirect = this.url('contest_detail', { tid }); diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index f54a787f3..f2bf5bbbf 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -268,6 +268,7 @@ export interface Tdoc extends Document { privateFiles?: FileInfo[]; allowViewCode?: boolean; allowPrint?: boolean; + keepScoreboardHidden?: boolean; // For contest lockAt?: Date; diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index 404261746..b701a732d 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -312,9 +312,9 @@ const oi = buildContestRule({ } return { score, detail, display }; }, - showScoreboard: (tdoc, now) => now > tdoc.endAt, - showSelfRecord: (tdoc, now) => now > tdoc.endAt, - showRecord: (tdoc, now) => now > tdoc.endAt, + showScoreboard: (tdoc, now) => now > tdoc.endAt && !tdoc.keepScoreboardHidden, + showSelfRecord: (tdoc, now) => now > tdoc.endAt && !tdoc.keepScoreboardHidden, + showRecord: (tdoc, now) => now > tdoc.endAt && !tdoc.keepScoreboardHidden, async scoreboardHeader(config, _, tdoc, pdict) { const columns: ScoreboardNode[] = [ { type: 'rank', value: '#' }, @@ -477,9 +477,9 @@ const ioi = buildContestRule({ const strictioi = buildContestRule({ TEXT: 'IOI(Strict)', submitAfterAccept: false, - showRecord: (tdoc, now) => now > tdoc.endAt, - showSelfRecord: () => true, - showScoreboard: (tdoc, now) => now > tdoc.endAt, + showRecord: (tdoc, now) => now > tdoc.endAt && !tdoc.keepScoreboardHidden, + showSelfRecord: (tdoc) => !tdoc.keepScoreboardHidden || !isDone(tdoc), + showScoreboard: (tdoc, now) => now > tdoc.endAt && !tdoc.keepScoreboardHidden, stat(tdoc, journal) { const detail = {}; let score = 0; diff --git a/packages/onsite-toolkit/index.ts b/packages/onsite-toolkit/index.ts index 1b5f25ac8..e640ed08c 100644 --- a/packages/onsite-toolkit/index.ts +++ b/packages/onsite-toolkit/index.ts @@ -2,7 +2,7 @@ import { LRUCache } from 'lru-cache'; import moment from 'moment'; import { - _, avatar, BadRequestError, ContestModel, ContestNotEndedError, Context, db, findFileSync, + _, avatar, ContestModel, ContestNotEndedError, Context, db, findFileSync, ForbiddenError, fs, Handler, InvalidTokenError, ObjectId, param, parseTimeMS, PERM, ProblemConfig, ProblemModel, randomstring, Schema, SettingModel, STATUS, STATUS_SHORT_TEXTS, STATUS_TEXTS, SystemModel, TokenModel, Types, UserModel, Zip, @@ -89,12 +89,6 @@ export function apply(ctx: Context, config: ReturnType) { }); async function generateCdpZip(tdoc) { - const serverUrl = SystemModel.get('server.url'); - try { - new URL(serverUrl); - } catch (e) { - throw new BadRequestError('Server URL not set'); - } let token = 0; const getFeed = (type: string, data: any) => ({ type, id: data.id, data, token: `t${token++}`, @@ -168,7 +162,7 @@ export function apply(ctx: Context, config: ReturnType) { group_ids: i.group.map((j) => groupId[j]), organization_id: orgId[i.organization], photo: [{ href: `contest/${tdoc._id}/teams/${i.team_id}/photo`, filename: 'photo.png', mime: 'image/png', width: 1920, height: 1080 }], - logo: [{ href: new URL(i.avatar, serverUrl).toString(), filename: 'logo.webp', mime: 'image/webp', width: 128, height: 128 }], + logo: [{ href: new URL(i.avatar, SystemModel.get('server.url')).toString(), filename: 'logo.webp', mime: 'image/webp', width: 128, height: 128 }], })), ...tdoc.pids.map((i, idx) => getFeed('problems', { id: `${i}`, label: String.fromCharCode(65 + idx), name: pdict[i].title, ordinal: idx, @@ -268,17 +262,11 @@ export function apply(ctx: Context, config: ReturnType) { async display({ tdoc }) { if (!this.user.own(tdoc)) this.checkPerm(PERM.PERM_EDIT_CONTEST); if (!ContestModel.isDone(tdoc)) throw new ContestNotEndedError(); - const serverUrl = SystemModel.get('server.url'); - try { - new URL(serverUrl); - } catch (e) { - throw new BadRequestError('Server URL not set'); - } const [tokenId] = await TokenModel.add( TokenModel.TYPE_EXPORT, 600, { domainId: tdoc.domainId, contestId: tdoc._id }, ); - const source = new URL(`/d/${tdoc.domainId}/contest/${tdoc._id}/resolver-cdp/${tokenId}`, serverUrl).toString(); + const source = new URL(`/d/${tdoc.domainId}/contest/${tdoc._id}/resolver-cdp/${tokenId}`, SystemModel.get('server.url')).toString(); const target = new URL('https://resolver.hydrooj.com'); target.searchParams.set('source', source); target.searchParams.set('mode', tdoc.rule === 'oi' ? 'oi' : 'acm'); diff --git a/packages/ui-default/locales/zh.yaml b/packages/ui-default/locales/zh.yaml index 4039dcdb1..6aea34e8a 100644 --- a/packages/ui-default/locales/zh.yaml +++ b/packages/ui-default/locales/zh.yaml @@ -419,6 +419,7 @@ Forgot Password and/or Username: 忘记密码和/或用户名 Forgot password or username?: 忘记密码或者用户名? Formula blocks: 公式区块 Freeze scoreboard with N minutes remaining: 在赛事剩余N分钟时封榜 +Keep scoreboard hidden even after contest ends: 即使比赛结束后也保持成绩表隐藏 fs_upload: 上传文件 Gender Visibility: 性别可见性 Gender: 性别 diff --git a/packages/ui-default/locales/zh_TW.yaml b/packages/ui-default/locales/zh_TW.yaml index 9039247d0..b2df1f511 100644 --- a/packages/ui-default/locales/zh_TW.yaml +++ b/packages/ui-default/locales/zh_TW.yaml @@ -418,6 +418,7 @@ Forgot Password and/or Username: 忘記密碼和/或使用者名稱 Forgot password or username?: 忘記密碼或者使用者名? Formula blocks: 公式區塊 Freeze scoreboard with N minutes remaining: 在賽事剩餘N分鐘時封榜 +Keep scoreboard hidden even after contest ends: 即使比賽結束後也保持成績表隱藏 fs_upload: 上傳檔案 Gender Visibility: 性別可見性 Gender: 性別 diff --git a/packages/ui-default/templates/contest_edit.html b/packages/ui-default/templates/contest_edit.html index ad7e1c36d..833f8b2d5 100644 --- a/packages/ui-default/templates/contest_edit.html +++ b/packages/ui-default/templates/contest_edit.html @@ -174,6 +174,17 @@ value:tdoc.allowPrint|default(false), row:false }) }} +