From e6c22d6f815e8cd331f0dabea4e8809cc5e53818 Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 24 Nov 2025 15:45:11 +0800 Subject: [PATCH] patches from zhengzhou site --- README.md | 17 ++++++++++- framework/eslint-config/package.json | 2 +- framework/utils/lib/utils.ts | 2 +- package.json | 2 +- packages/components/package.json | 4 +-- packages/hydrojudge/package.json | 4 +-- packages/hydrooj/package.json | 8 +++--- packages/hydrooj/src/handler/contest.ts | 2 +- packages/hydrooj/src/handler/record.ts | 6 ++-- packages/hydrooj/src/interface.ts | 1 + packages/hydrooj/src/model/record.ts | 13 ++++++++- packages/hydrooj/src/model/user.ts | 2 +- packages/onsite-toolkit/index.ts | 11 +++++--- packages/onsite-toolkit/submit.ts | 2 +- packages/scoreboard-xcpcio/index.ts | 3 ++ .../backendlib/markdown-it-media.ts | 4 +-- packages/ui-default/package.json | 28 +++++++++---------- .../templates/partials/contest_sidebar.html | 7 +++++ packages/ui-next/package.json | 2 +- 19 files changed, 80 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 28823e47d..7a56ba600 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,22 @@ Hydro 用户群:1085853538

更新日志(点击展开)

+## Hydro 5.0.0-beta.13 / UI 4.58.0-beta.14 + +- ui: 比赛页面添加打印快捷入口 +- ui: 比赛题目列表显示时空限制和提交按钮 +- core: 检查提解投票权限 +- ui: 修复题目配置页文件操作 +- ui: 移除旧的 window.Hydro.util 和 window.Hydro.component 对象 +- onsite-toolkit: 添加导入工具 +- onsite-toolkit: 添加 submit 和 printfile 支持 +- onsite-toolkit: 支持限制通信功能 +- core: backup: 速度优化 +- ui: 优化超长公式显示 +- core&ui: 添加允许所有人邀请的选项 +- ui: 优化题目配置页暗色模式 +- scoreboard-xcpcio: 支持注册为默认榜单 + ## Hydro 5.0.0-beta.12 / UI 4.58.0-beta.12 - framework: 修复 SSE 模式参数识别 @@ -121,7 +137,6 @@ Hydro 用户群:1085853538 - core: 添加 hydrooj diag 快速诊断工具 - ui: 禁用链接自动折叠,复制链接自动转义括号 - ui: 优化资源缓存 -- ui: 优化超长公式显示 - core&ui: 支持题目排序 *Breaking API Changes* diff --git a/framework/eslint-config/package.json b/framework/eslint-config/package.json index c01b73efa..0f2e520f3 100644 --- a/framework/eslint-config/package.json +++ b/framework/eslint-config/package.json @@ -6,7 +6,7 @@ "repository": "https://github.com/hydro-dev/Hydro", "dependencies": { "@antfu/eslint-config": "^6.2.0", - "@eslint-react/eslint-plugin": "^2.3.5", + "@eslint-react/eslint-plugin": "^2.3.7", "@typescript-eslint/eslint-plugin": "^8.47.0", "@typescript-eslint/parser": "^8.47.0", "eslint-plugin-de-morgan": "^2.0.0", diff --git a/framework/utils/lib/utils.ts b/framework/utils/lib/utils.ts index e3bda4868..d73bf55a2 100644 --- a/framework/utils/lib/utils.ts +++ b/framework/utils/lib/utils.ts @@ -154,7 +154,7 @@ export namespace Time { throw new Error('No bson module found'); } try { - isMoment ||= require('moment'); + isMoment ||= require('moment').isMoment; } catch (e) { throw new Error('No moment module found'); } diff --git a/package.json b/package.json index 3a77057c6..0d31410bf 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@types/autocannon": "^7.12.7", "@types/chai": "^5.2.3", "@types/cross-spawn": "^6.0.6", - "@types/node": "24.10.0", + "@types/node": "24.10.1", "@types/semver": "^7.7.1", "autocannon": "^8.0.0", "cac": "^6.7.14", diff --git a/packages/components/package.json b/packages/components/package.json index 700b176f3..b6d7965a7 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -20,8 +20,8 @@ "devDependencies": { "@types/diff": "^8.0.0", "@types/js-yaml": "^4.0.9", - "@types/lodash": "^4.17.20", - "sass": "^1.94.1" + "@types/lodash": "^4.17.21", + "sass": "^1.94.2" }, "peerDependencies": { "react": "*", diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index cd688006e..2cec0184e 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -8,9 +8,9 @@ "dependencies": { "@hydrooj/common": "workspace:^", "@hydrooj/utils": "workspace:^", - "@zip.js/zip.js": "^2.8.10", + "@zip.js/zip.js": "^2.8.11", "cac": "^6.7.14", - "p-queue": "^9.0.0", + "p-queue": "^9.0.1", "schemastery": "^3.17.2", "semver": "^7.7.3", "shell-quote": "^1.8.3", diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 588043376..530d14c6e 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -24,7 +24,7 @@ "@mongodb-js/saslprep": "^1.3.2", "@simplewebauthn/server": "13.2.2", "@undefined-moe/require-resolve-hook": "^1.1.0", - "@zip.js/zip.js": "^2.8.10", + "@zip.js/zip.js": "^2.8.11", "adm-zip": "0.5.16", "cac": "^6.7.14", "cordis": "4.0.0-beta.5", @@ -37,14 +37,14 @@ "koa-static-cache": "^5.1.4", "lodash": "^4.17.21", "lru-cache": "^11.2.2", - "mime-types": "^3.0.1", + "mime-types": "^3.0.2", "moment-timezone": "^0.6.0", "mongodb": "^7.0.0", "mongodb-uri": "^0.9.7", "nanoid": "^5.1.6", "nodemailer": "^7.0.10", "notp": "^2.0.3", - "p-queue": "^9.0.0", + "p-queue": "^9.0.1", "sanitize-filename": "^1.6.3", "schemastery": "^3.17.2", "semver": "^7.7.3", @@ -60,7 +60,7 @@ "@types/fs-extra": "^11.0.4", "@types/js-yaml": "^4.0.9", "@types/koa-static-cache": "^5.1.4", - "@types/lodash": "^4.17.20", + "@types/lodash": "^4.17.21", "@types/mime-types": "^3.0.1", "@types/mongodb-uri": "^0.9.4", "@types/nodemailer": "^7.0.4", diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index d9e3a9fcd..3a20b5b10 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -333,7 +333,7 @@ export class ContestProblemListHandler extends ContestDetailBaseHandler { await this.limitRate('add_discussion', 3600, 60); await contest.addClarification(domainId, tid, this.user._id, content, this.request.ip, subject); if (!this.user.own(this.tdoc)) { - await message.send(1, this.tdoc.maintainer.concat(this.tdoc.owner), JSON.stringify({ + await message.send(1, (this.tdoc.maintainer || []).concat(this.tdoc.owner), JSON.stringify({ message: 'Contest {0} has a new clarification about {1}, please go to contest clarifications page to reply.', params: [this.tdoc.title, subject > 0 ? `#${this.tdoc.pids.indexOf(subject) + 1}` : 'the contest'], url: this.url('contest_clarification', { tid }), diff --git a/packages/hydrooj/src/handler/record.ts b/packages/hydrooj/src/handler/record.ts index 2d22ba8fb..471b0f569 100644 --- a/packages/hydrooj/src/handler/record.ts +++ b/packages/hydrooj/src/handler/record.ts @@ -333,8 +333,8 @@ class RecordMainConnectionHandler extends ConnectionHandler { if (!this.all) { if (rdoc.contest && ![this.tid, '000000000000000000000000'].includes(rdoc.contest.toString())) return; if (this.tid && rdoc.contest?.toString() !== '0'.repeat(24)) { - if (contest.isLocked(this.tdoc) && !this.pretest) return; - if (!contest.canShowSelfRecord.call(this, this.tdoc, true)) return; + if (rdoc.uid !== this.user._id && !contest.canShowRecord.call(this, this.tdoc, true)) return; + if (rdoc.uid === this.user._id && !contest.canShowSelfRecord.call(this, this.tdoc, true)) return; } } } @@ -345,7 +345,7 @@ class RecordMainConnectionHandler extends ConnectionHandler { user.getById(this.args.domainId, rdoc.uid), problem.get(rdoc.domainId, rdoc.pid), ]); - const tdoc = this.tid ? this.tdoc || await contest.get(rdoc.domainId, new ObjectId(this.tid)) : null; + const tdoc = this.tid ? this.tdoc : null; if (pdoc && !rdoc.contest) { if (!problem.canViewBy(pdoc, this.user)) pdoc = null; if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM)) pdoc = null; diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index b4eef3de6..206412b18 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -213,6 +213,7 @@ export type RecordDoc = { [K in keyof RecordPayload]: K extends 'hackTarget' | 'contest' ? ObjectId : RecordPayload[K]; } & { _id: ObjectId; + notify?: boolean; }; export interface RecordHistoryDoc extends RecordJudgeInfo { diff --git a/packages/hydrooj/src/model/record.ts b/packages/hydrooj/src/model/record.ts index 5c2a33b55..ed9bdbcd1 100644 --- a/packages/hydrooj/src/model/record.ts +++ b/packages/hydrooj/src/model/record.ts @@ -4,7 +4,7 @@ import { Filter, FindOptions, MatchKeysAndValues, ObjectId, OnlyFieldsOfType, PushOperator, UpdateFilter, } from 'mongodb'; -import { ProblemConfigFile } from '@hydrooj/common'; +import { ProblemConfigFile, STATUS_TEXTS } from '@hydrooj/common'; import { Context } from '../context'; import { ProblemNotFoundError } from '../error'; import { JudgeMeta, RecordDoc } from '../interface'; @@ -13,6 +13,7 @@ import { MaybeArray, NumberKeys } from '../typeutils'; import { ArgMethod, buildProjection, Time } from '../utils'; import { STATUS } from './builtin'; import DomainModel from './domain'; +import MessageModel from './message'; import problem from './problem'; import SystemModel from './system'; import task from './task'; @@ -137,6 +138,7 @@ export default class RecordModel { files?: Record; hackTarget?: ObjectId; type: 'judge' | 'rejudge' | 'pretest' | 'hack' | 'generate'; + notify?: boolean; } = { type: 'judge' }, ) { const data: RecordDoc = { @@ -161,6 +163,7 @@ export default class RecordModel { if (args.contest) data.contest = args.contest; if (args.files) data.files = args.files; if (args.hackTarget) data.hackTarget = args.hackTarget; + if (args.notify) data.notify = true; if (args.type === 'rejudge') { args.type = 'judge'; data.rejudged = true; @@ -290,6 +293,14 @@ export async function apply(ctx: Context) { ])); ctx.on('domain/delete', (domainId) => RecordModel.coll.deleteMany({ domainId })); ctx.on('record/judge', async (rdoc, updated) => { + if (rdoc.contest?.toHexString().startsWith('0'.repeat(23))) return; + if (rdoc.notify) { + const pdoc = await Hydro.model.problem.get(rdoc.domainId, rdoc.pid); + await MessageModel.send(1, rdoc.uid, JSON.stringify({ + message: 'Judge Result\n{0}: {1}', + params: [pdoc.title, STATUS_TEXTS[rdoc.status]], + }), MessageModel.FLAG_I18N); + } if (rdoc.status === STATUS.STATUS_ACCEPTED && updated) { if (SystemModel.get('record.statMode') === 'unique') { await RecordModel.collStat.deleteMany({ diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index 6fac72d5d..af9ee556c 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -163,7 +163,7 @@ export class User { } getFields(type: 'public' | 'private' = 'public') { - const fields = ['_id', 'uname', 'mail', 'perm', 'role', 'priv', 'regat', 'loginat', 'tfa', 'authn'].concat(this._publicFields); + const fields = ['_id', 'uname', 'mail', 'perm', 'role', 'priv', 'regat', 'loginat', 'tfa', 'authn', 'avatar'].concat(this._publicFields); return type === 'public' ? fields : fields.concat(this._privateFields); } diff --git a/packages/onsite-toolkit/index.ts b/packages/onsite-toolkit/index.ts index 99ec9a0a1..efee050a8 100644 --- a/packages/onsite-toolkit/index.ts +++ b/packages/onsite-toolkit/index.ts @@ -139,7 +139,7 @@ export function apply(ctx: Context, config: ReturnType) { type, id: data.id, data, token: `t${token++}`, }); const [pdict, tsdocs] = await Promise.all([ - ProblemModel.getList(tdoc.domainId, tdoc.pids, true, false, ProblemModel.PROJECTION_LIST, true), + ProblemModel.getList(tdoc.domainId, tdoc.pids, true, false, ProblemModel.PROJECTION_LIST.concat('config'), true), ContestModel.getMultiStatus(tdoc.domainId, { docId: tdoc._id }).toArray(), ]); const udict = await UserModel.getList(tdoc.domainId, tsdocs.map((i) => i.uid)); @@ -326,15 +326,18 @@ export function apply(ctx: Context, config: ReturnType) { const tids = tidsInput.split(',').map((i) => i.trim()).filter((i) => i).map((i) => new ObjectId(i)); const tdocs = await Promise.all(tids.map((i) => ContestModel.get('system', i))); const convertUname = Types.Username[0]; + let cnt = 0; for (const line of data) { + cnt++; + const id = line.id || cnt; const uname = convertUname(line.name); - const email = `${line.id}@onsite.local`; + const email = `${id}@onsite.local`; let team = await UserModel.getByEmail('system', email); if (!team) { const uid = await UserModel.create(email, uname, line.password || randomstring()); team = await UserModel.getById('system', uid); } else { - await UserModel.setById(team._id, { uname }); + await UserModel.setUname(team._id, uname); if (line.password) await UserModel.setPassword(team._id, line.password); } if (line.member1) line.members.push(line.member1); @@ -346,7 +349,7 @@ export function apply(ctx: Context, config: ReturnType) { if (Object.keys(set).length) await UserModel.setById(team._id, set); for (const tdoc of tdocs) { const tsdoc = await ContestModel.getStatus('system', tdoc.docId, team._id); - if (!tsdoc?.attend) await ContestModel.attend('system', tdoc.docId, team._id, 'rank' in line ? { unrank: !line.rank } : {}); + if (!tsdoc?.attend) await ContestModel.attend('system', tdoc.docId, team._id, 'rank' in line ? { unrank: !line.rank, subscribe: 1 } : { subscribe: 1 }); else if ('rank' in line && tsdoc.unrank === line.rank) await ContestModel.setStatus('system', tdoc.docId, team._id, { unrank: !line.rank }); } if (line.ip) await coll.updateOne({ _id: normalizeIp(line.ip) }, { $set: { uid: team._id } }, { upsert: true }); diff --git a/packages/onsite-toolkit/submit.ts b/packages/onsite-toolkit/submit.ts index 85082c654..691164fbd 100644 --- a/packages/onsite-toolkit/submit.ts +++ b/packages/onsite-toolkit/submit.ts @@ -68,7 +68,7 @@ Language ${SettingModel.langs[lang].display} (${lang}) const rid = await RecordModel.add( domainId, this.pdoc.docId, this.user._id, lang, code, true, - { contest: this.tdoc.docId, files, type: 'judge' }, + { contest: this.tdoc.docId, files, type: 'judge', notify: true }, ); await Promise.all([ ProblemModel.inc(domainId, this.pdoc.docId, 'nSubmit', 1), diff --git a/packages/scoreboard-xcpcio/index.ts b/packages/scoreboard-xcpcio/index.ts index 055fc2495..c00c456c0 100644 --- a/packages/scoreboard-xcpcio/index.ts +++ b/packages/scoreboard-xcpcio/index.ts @@ -92,6 +92,7 @@ export const Config = Schema.object({ cacheTTL: Schema.number().default(0).description('Cache TTL in milliseconds'), cacheSize: Schema.number().default(100).description('Cache size'), asDefault: Schema.boolean().default(false).description('As default scoreboard'), + override: Schema.any().default({}).description('Scoreboard contest override'), }); export async function apply(ctx: Context, config: ReturnType) { @@ -200,6 +201,7 @@ export async function apply(ctx: Context, config: ReturnType) { options: { submission_timestamp_unit: 'millisecond', }, + ...(typeof config.override === 'object' ? config.override || {} : {}), }, ...state, }; @@ -216,6 +218,7 @@ export async function apply(ctx: Context, config: ReturnType) { js: indexJs, css: indexCss, realtime, + tdoc: this.tdoc, }; } }, diff --git a/packages/ui-default/backendlib/markdown-it-media.ts b/packages/ui-default/backendlib/markdown-it-media.ts index f92bed9cf..4a748fdd2 100644 --- a/packages/ui-default/backendlib/markdown-it-media.ts +++ b/packages/ui-default/backendlib/markdown-it-media.ts @@ -92,7 +92,7 @@ declare module 'hydrooj' { } } -export function Media(md: MarkdownIt) { +export function Media(md: MarkdownIt, { pdfToolbar = false }: { pdfToolbar?: boolean } = {}) { const supported = ['youtube', 'vimeo', 'vine', 'prezi', 'bilibili', 'youku', 'msoffice']; md.renderer.rules.video = function tokenizeReturn(tokens, idx) { let src = md.utils.escapeHtml(tokens[idx].attrGet('src')); @@ -109,7 +109,7 @@ export function Media(md: MarkdownIt) { return `\ - + `; diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 36c8f99e0..8acac8655 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -20,11 +20,11 @@ "@fontsource/source-code-pro": "^5.2.7", "@fontsource/ubuntu-mono": "^5.2.8", "@hydrooj/utils": "workspace:^", - "@mantine/core": "^8.3.8", - "@mantine/hooks": "^8.3.8", - "@mantine/notifications": "^8.3.8", - "@sentry/browser": "^10.25.0", - "@sentry/webpack-plugin": "^4.6.0", + "@mantine/core": "^8.3.9", + "@mantine/hooks": "^8.3.9", + "@mantine/notifications": "^8.3.9", + "@sentry/browser": "^10.26.0", + "@sentry/webpack-plugin": "^4.6.1", "@simplewebauthn/browser": "13.2.2", "@svgr/webpack": "^8.1.0", "@types/html-to-text": "^9.0.4", @@ -41,7 +41,7 @@ "@types/redux-logger": "^3.0.13", "@types/uuid": "^11.0.0", "@types/webpack-env": "^1.18.8", - "@vscode/codicons": "^0.0.42", + "@vscode/codicons": "^0.0.43", "ajv": "^8.17.1", "allotment": "^1.20.4", "autoprefixer": "^10.4.22", @@ -52,8 +52,8 @@ "clipboard": "^2.0.11", "copy-webpack-plugin": "^13.0.1", "cordis": "4.0.0-beta.5", - "core-js": "^3.46.0", - "core-js-compat": "^3.46.0", + "core-js": "^3.47.0", + "core-js-compat": "^3.47.0", "css-loader": "^7.1.2", "diff": "^8.0.2", "diff-dom": "^5.2.1", @@ -67,9 +67,9 @@ "jquery-scroll-lock": "^3.1.3", "jquery.easing": "^1.4.1", "jquery.transit": "^0.9.12", - "mantine-contextmenu": "^8.3.8", + "mantine-contextmenu": "^8.3.9", "matchmedia-polyfill": "^0.3.2", - "md-editor-rt": "^6.1.1", + "md-editor-rt": "^6.2.0", "mini-css-extract-plugin": "^2.9.4", "moment": "^2.30.1", "monaco-editor": "0.52.2", @@ -80,7 +80,7 @@ "nanoid": "^5.1.6", "normalize.css": "^8.0.1", "nprogress": "^0.2.0", - "p-queue": "^9.0.0", + "p-queue": "^9.0.1", "pickadate": "^3.6.4", "postcss-loader": "7.3.4", "postcss-preset-mantine": "^1.18.0", @@ -101,7 +101,7 @@ "redux-promise-middleware": "^6.2.0", "redux-thunk": "^3.1.0", "rupture": "^0.7.1", - "sass": "^1.94.1", + "sass": "^1.94.2", "sass-loader": "^16.0.6", "schemastery": "^3.17.2", "shorty.js": "^1.0.1", @@ -117,8 +117,8 @@ "timeago.js": "^4.0.2", "ts-loader": "^9.5.4", "web-streams-polyfill": "^4.2.0", - "webpack": "^5.102.1", - "webpack-bundle-analyzer": "^4.10.2", + "webpack": "^5.103.0", + "webpack-bundle-analyzer": "^5.0.1", "webpack-dev-server": "^5.2.2", "webpack-manifest-plugin": "^6.0.1", "webpackbar": "^7.0.0" diff --git a/packages/ui-default/templates/partials/contest_sidebar.html b/packages/ui-default/templates/partials/contest_sidebar.html index 427bd8dff..b2bf78858 100644 --- a/packages/ui-default/templates/partials/contest_sidebar.html +++ b/packages/ui-default/templates/partials/contest_sidebar.html @@ -103,9 +103,16 @@ {{ _('Discussion') }} {% endif %} + {% if handler.user.own(tdoc) or handler.user.hasPriv(PRIV.PRIV_READ_RECORD_CODE) or handler.user.hasPerm(perm.PERM_READ_RECORD_CODE) %} + {% endif %} + {% if tsdoc.attend and model.contest.canShowSelfRecord.call(handler, tdoc) %} + + {% endif %} diff --git a/packages/ui-next/package.json b/packages/ui-next/package.json index 465ac8a8d..7560b6bf1 100644 --- a/packages/ui-next/package.json +++ b/packages/ui-next/package.json @@ -16,6 +16,6 @@ "@types/react": "^19.2.6", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react-swc": "^4.2.2", - "vite": "^7.2.2" + "vite": "^7.2.4" } }