patches from zhengzhou site

This commit is contained in:
undefined committed 2025-11-24 15:45:11 +08:00
1 parent ded00efa60
commit e6c22d6f81
19 files changed
+80 -40

No files matched your search

+2 -2
View File
@@ -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": "*",
+2 -2
View File
@@ -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",
+4 -4
View File
@@ -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",
+1 -1
View File
@@ -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 }),
+3 -3
View File
@@ -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;
+1
View File
@@ -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 {
+12 -1
View File
@@ -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<string, string>;
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({
+1 -1
View File
@@ -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);
}
+7 -4
View File
@@ -139,7 +139,7 @@ export function apply(ctx: Context, config: ReturnType<typeof Config>) {
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<typeof Config>) {
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<typeof Config>) {
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 });
+1 -1
View File
@@ -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),
+3
View File
@@ -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<typeof Config>) {
@@ -200,6 +201,7 @@ export async function apply(ctx: Context, config: ReturnType<typeof Config>) {
options: {
submission_timestamp_unit: 'millisecond',
},
...(typeof config.override === 'object' ? config.override || {} : {}),
},
...state,
};
@@ -216,6 +218,7 @@ export async function apply(ctx: Context, config: ReturnType<typeof Config>) {
js: indexJs,
css: indexCss,
realtime,
tdoc: this.tdoc,
};
}
},
@@ -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 `\
<object classid="clsid:${uuid().toUpperCase()}">
<param name="SRC" value="${src}">
<embed type="application/pdf" width="100%" style="min-height:100vh;border:none;" fullscreen="yes" src="${src}#toolbar=0&navpanes=0">
<embed type="application/pdf" width="100%" style="min-height:100vh;border:none;" fullscreen="yes" src="${src}#toolbar=${pdfToolbar ? '0' : '1'}&navpanes=0&view=FitH">
<noembed></noembed>
</embed>
</object>`;
+14 -14
View File
@@ -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"
@@ -103,9 +103,16 @@
<span class="icon icon-comment--text"></span> {{ _('Discussion') }}
</a></li>
{% endif %}
{% if handler.user.own(tdoc) or handler.user.hasPriv(PRIV.PRIV_READ_RECORD_CODE) or handler.user.hasPerm(perm.PERM_READ_RECORD_CODE) %}
<li class="menu__item"><a class="menu__link" href="{{ url('record_main', query={tid:tdoc.docId}) }}">
<span class="icon icon-flag"></span> {{ _('All Submissions') }}
</a></li>
{% endif %}
{% if tsdoc.attend and model.contest.canShowSelfRecord.call(handler, tdoc) %}
<li class="menu__item"><a class="menu__link" href="{{ url('record_main', query={tid:tdoc.docId, uidOrName:handler.user._id}) }}">
<span class="icon icon-flag"></span> {{ _('My Submissions') }}
</a></li>
{% endif %}
<li class="menu__item"><a class="menu__link" href="{{ url('wiki_help', anchor='contest') }}">
<span class="icon icon-help"></span> {{ _('Help') }}
</a></li>
+1 -1
View File
@@ -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"
}
}