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

+16 -1
View File
@@ -111,6 +111,22 @@ Hydro 用户群:1085853538
<details> <details>
<summary><h2>更新日志(点击展开)</h2></summary> <summary><h2>更新日志(点击展开)</h2></summary>
## 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 ## Hydro 5.0.0-beta.12 / UI 4.58.0-beta.12
- framework: 修复 SSE 模式参数识别 - framework: 修复 SSE 模式参数识别
@@ -121,7 +137,6 @@ Hydro 用户群:1085853538
- core: 添加 hydrooj diag 快速诊断工具 - core: 添加 hydrooj diag 快速诊断工具
- ui: 禁用链接自动折叠,复制链接自动转义括号 - ui: 禁用链接自动折叠,复制链接自动转义括号
- ui: 优化资源缓存 - ui: 优化资源缓存
- ui: 优化超长公式显示
- core&ui: 支持题目排序 - core&ui: 支持题目排序
*Breaking API Changes* *Breaking API Changes*
+1 -1
View File
@@ -6,7 +6,7 @@
"repository": "https://github.com/hydro-dev/Hydro", "repository": "https://github.com/hydro-dev/Hydro",
"dependencies": { "dependencies": {
"@antfu/eslint-config": "^6.2.0", "@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/eslint-plugin": "^8.47.0",
"@typescript-eslint/parser": "^8.47.0", "@typescript-eslint/parser": "^8.47.0",
"eslint-plugin-de-morgan": "^2.0.0", "eslint-plugin-de-morgan": "^2.0.0",
+1 -1
View File
@@ -154,7 +154,7 @@ export namespace Time {
throw new Error('No bson module found'); throw new Error('No bson module found');
} }
try { try {
isMoment ||= require('moment'); isMoment ||= require('moment').isMoment;
} catch (e) { } catch (e) {
throw new Error('No moment module found'); throw new Error('No moment module found');
} }
+1 -1
View File
@@ -35,7 +35,7 @@
"@types/autocannon": "^7.12.7", "@types/autocannon": "^7.12.7",
"@types/chai": "^5.2.3", "@types/chai": "^5.2.3",
"@types/cross-spawn": "^6.0.6", "@types/cross-spawn": "^6.0.6",
"@types/node": "24.10.0", "@types/node": "24.10.1",
"@types/semver": "^7.7.1", "@types/semver": "^7.7.1",
"autocannon": "^8.0.0", "autocannon": "^8.0.0",
"cac": "^6.7.14", "cac": "^6.7.14",
+2 -2
View File
@@ -20,8 +20,8 @@
"devDependencies": { "devDependencies": {
"@types/diff": "^8.0.0", "@types/diff": "^8.0.0",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/lodash": "^4.17.20", "@types/lodash": "^4.17.21",
"sass": "^1.94.1" "sass": "^1.94.2"
}, },
"peerDependencies": { "peerDependencies": {
"react": "*", "react": "*",
+2 -2
View File
@@ -8,9 +8,9 @@
"dependencies": { "dependencies": {
"@hydrooj/common": "workspace:^", "@hydrooj/common": "workspace:^",
"@hydrooj/utils": "workspace:^", "@hydrooj/utils": "workspace:^",
"@zip.js/zip.js": "^2.8.10", "@zip.js/zip.js": "^2.8.11",
"cac": "^6.7.14", "cac": "^6.7.14",
"p-queue": "^9.0.0", "p-queue": "^9.0.1",
"schemastery": "^3.17.2", "schemastery": "^3.17.2",
"semver": "^7.7.3", "semver": "^7.7.3",
"shell-quote": "^1.8.3", "shell-quote": "^1.8.3",
+4 -4
View File
@@ -24,7 +24,7 @@
"@mongodb-js/saslprep": "^1.3.2", "@mongodb-js/saslprep": "^1.3.2",
"@simplewebauthn/server": "13.2.2", "@simplewebauthn/server": "13.2.2",
"@undefined-moe/require-resolve-hook": "^1.1.0", "@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", "adm-zip": "0.5.16",
"cac": "^6.7.14", "cac": "^6.7.14",
"cordis": "4.0.0-beta.5", "cordis": "4.0.0-beta.5",
@@ -37,14 +37,14 @@
"koa-static-cache": "^5.1.4", "koa-static-cache": "^5.1.4",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lru-cache": "^11.2.2", "lru-cache": "^11.2.2",
"mime-types": "^3.0.1", "mime-types": "^3.0.2",
"moment-timezone": "^0.6.0", "moment-timezone": "^0.6.0",
"mongodb": "^7.0.0", "mongodb": "^7.0.0",
"mongodb-uri": "^0.9.7", "mongodb-uri": "^0.9.7",
"nanoid": "^5.1.6", "nanoid": "^5.1.6",
"nodemailer": "^7.0.10", "nodemailer": "^7.0.10",
"notp": "^2.0.3", "notp": "^2.0.3",
"p-queue": "^9.0.0", "p-queue": "^9.0.1",
"sanitize-filename": "^1.6.3", "sanitize-filename": "^1.6.3",
"schemastery": "^3.17.2", "schemastery": "^3.17.2",
"semver": "^7.7.3", "semver": "^7.7.3",
@@ -60,7 +60,7 @@
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/koa-static-cache": "^5.1.4", "@types/koa-static-cache": "^5.1.4",
"@types/lodash": "^4.17.20", "@types/lodash": "^4.17.21",
"@types/mime-types": "^3.0.1", "@types/mime-types": "^3.0.1",
"@types/mongodb-uri": "^0.9.4", "@types/mongodb-uri": "^0.9.4",
"@types/nodemailer": "^7.0.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 this.limitRate('add_discussion', 3600, 60);
await contest.addClarification(domainId, tid, this.user._id, content, this.request.ip, subject); await contest.addClarification(domainId, tid, this.user._id, content, this.request.ip, subject);
if (!this.user.own(this.tdoc)) { 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.', 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'], params: [this.tdoc.title, subject > 0 ? `#${this.tdoc.pids.indexOf(subject) + 1}` : 'the contest'],
url: this.url('contest_clarification', { tid }), url: this.url('contest_clarification', { tid }),
+3 -3
View File
@@ -333,8 +333,8 @@ class RecordMainConnectionHandler extends ConnectionHandler {
if (!this.all) { if (!this.all) {
if (rdoc.contest && ![this.tid, '000000000000000000000000'].includes(rdoc.contest.toString())) return; if (rdoc.contest && ![this.tid, '000000000000000000000000'].includes(rdoc.contest.toString())) return;
if (this.tid && rdoc.contest?.toString() !== '0'.repeat(24)) { if (this.tid && rdoc.contest?.toString() !== '0'.repeat(24)) {
if (contest.isLocked(this.tdoc) && !this.pretest) return; if (rdoc.uid !== this.user._id && !contest.canShowRecord.call(this, this.tdoc, true)) return;
if (!contest.canShowSelfRecord.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), user.getById(this.args.domainId, rdoc.uid),
problem.get(rdoc.domainId, rdoc.pid), 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 (pdoc && !rdoc.contest) {
if (!problem.canViewBy(pdoc, this.user)) pdoc = null; if (!problem.canViewBy(pdoc, this.user)) pdoc = null;
if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM)) 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]; [K in keyof RecordPayload]: K extends 'hackTarget' | 'contest' ? ObjectId : RecordPayload[K];
} & { } & {
_id: ObjectId; _id: ObjectId;
notify?: boolean;
}; };
export interface RecordHistoryDoc extends RecordJudgeInfo { export interface RecordHistoryDoc extends RecordJudgeInfo {
+12 -1
View File
@@ -4,7 +4,7 @@ import {
Filter, FindOptions, MatchKeysAndValues, Filter, FindOptions, MatchKeysAndValues,
ObjectId, OnlyFieldsOfType, PushOperator, UpdateFilter, ObjectId, OnlyFieldsOfType, PushOperator, UpdateFilter,
} from 'mongodb'; } from 'mongodb';
import { ProblemConfigFile } from '@hydrooj/common'; import { ProblemConfigFile, STATUS_TEXTS } from '@hydrooj/common';
import { Context } from '../context'; import { Context } from '../context';
import { ProblemNotFoundError } from '../error'; import { ProblemNotFoundError } from '../error';
import { JudgeMeta, RecordDoc } from '../interface'; import { JudgeMeta, RecordDoc } from '../interface';
@@ -13,6 +13,7 @@ import { MaybeArray, NumberKeys } from '../typeutils';
import { ArgMethod, buildProjection, Time } from '../utils'; import { ArgMethod, buildProjection, Time } from '../utils';
import { STATUS } from './builtin'; import { STATUS } from './builtin';
import DomainModel from './domain'; import DomainModel from './domain';
import MessageModel from './message';
import problem from './problem'; import problem from './problem';
import SystemModel from './system'; import SystemModel from './system';
import task from './task'; import task from './task';
@@ -137,6 +138,7 @@ export default class RecordModel {
files?: Record<string, string>; files?: Record<string, string>;
hackTarget?: ObjectId; hackTarget?: ObjectId;
type: 'judge' | 'rejudge' | 'pretest' | 'hack' | 'generate'; type: 'judge' | 'rejudge' | 'pretest' | 'hack' | 'generate';
notify?: boolean;
} = { type: 'judge' }, } = { type: 'judge' },
) { ) {
const data: RecordDoc = { const data: RecordDoc = {
@@ -161,6 +163,7 @@ export default class RecordModel {
if (args.contest) data.contest = args.contest; if (args.contest) data.contest = args.contest;
if (args.files) data.files = args.files; if (args.files) data.files = args.files;
if (args.hackTarget) data.hackTarget = args.hackTarget; if (args.hackTarget) data.hackTarget = args.hackTarget;
if (args.notify) data.notify = true;
if (args.type === 'rejudge') { if (args.type === 'rejudge') {
args.type = 'judge'; args.type = 'judge';
data.rejudged = true; data.rejudged = true;
@@ -290,6 +293,14 @@ export async function apply(ctx: Context) {
])); ]));
ctx.on('domain/delete', (domainId) => RecordModel.coll.deleteMany({ domainId })); ctx.on('domain/delete', (domainId) => RecordModel.coll.deleteMany({ domainId }));
ctx.on('record/judge', async (rdoc, updated) => { 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 (rdoc.status === STATUS.STATUS_ACCEPTED && updated) {
if (SystemModel.get('record.statMode') === 'unique') { if (SystemModel.get('record.statMode') === 'unique') {
await RecordModel.collStat.deleteMany({ await RecordModel.collStat.deleteMany({
+1 -1
View File
@@ -163,7 +163,7 @@ export class User {
} }
getFields(type: 'public' | 'private' = 'public') { 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); 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++}`, type, id: data.id, data, token: `t${token++}`,
}); });
const [pdict, tsdocs] = await Promise.all([ 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(), ContestModel.getMultiStatus(tdoc.domainId, { docId: tdoc._id }).toArray(),
]); ]);
const udict = await UserModel.getList(tdoc.domainId, tsdocs.map((i) => i.uid)); 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 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 tdocs = await Promise.all(tids.map((i) => ContestModel.get('system', i)));
const convertUname = Types.Username[0]; const convertUname = Types.Username[0];
let cnt = 0;
for (const line of data) { for (const line of data) {
cnt++;
const id = line.id || cnt;
const uname = convertUname(line.name); const uname = convertUname(line.name);
const email = `${line.id}@onsite.local`; const email = `${id}@onsite.local`;
let team = await UserModel.getByEmail('system', email); let team = await UserModel.getByEmail('system', email);
if (!team) { if (!team) {
const uid = await UserModel.create(email, uname, line.password || randomstring()); const uid = await UserModel.create(email, uname, line.password || randomstring());
team = await UserModel.getById('system', uid); team = await UserModel.getById('system', uid);
} else { } 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.password) await UserModel.setPassword(team._id, line.password);
} }
if (line.member1) line.members.push(line.member1); 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); if (Object.keys(set).length) await UserModel.setById(team._id, set);
for (const tdoc of tdocs) { for (const tdoc of tdocs) {
const tsdoc = await ContestModel.getStatus('system', tdoc.docId, team._id); 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 }); 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 }); 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( const rid = await RecordModel.add(
domainId, this.pdoc.docId, this.user._id, lang, code, true, 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([ await Promise.all([
ProblemModel.inc(domainId, this.pdoc.docId, 'nSubmit', 1), 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'), cacheTTL: Schema.number().default(0).description('Cache TTL in milliseconds'),
cacheSize: Schema.number().default(100).description('Cache size'), cacheSize: Schema.number().default(100).description('Cache size'),
asDefault: Schema.boolean().default(false).description('As default scoreboard'), 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>) { 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: { options: {
submission_timestamp_unit: 'millisecond', submission_timestamp_unit: 'millisecond',
}, },
...(typeof config.override === 'object' ? config.override || {} : {}),
}, },
...state, ...state,
}; };
@@ -216,6 +218,7 @@ export async function apply(ctx: Context, config: ReturnType<typeof Config>) {
js: indexJs, js: indexJs,
css: indexCss, css: indexCss,
realtime, 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']; const supported = ['youtube', 'vimeo', 'vine', 'prezi', 'bilibili', 'youku', 'msoffice'];
md.renderer.rules.video = function tokenizeReturn(tokens, idx) { md.renderer.rules.video = function tokenizeReturn(tokens, idx) {
let src = md.utils.escapeHtml(tokens[idx].attrGet('src')); let src = md.utils.escapeHtml(tokens[idx].attrGet('src'));
@@ -109,7 +109,7 @@ export function Media(md: MarkdownIt) {
return `\ return `\
<object classid="clsid:${uuid().toUpperCase()}"> <object classid="clsid:${uuid().toUpperCase()}">
<param name="SRC" value="${src}"> <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> <noembed></noembed>
</embed> </embed>
</object>`; </object>`;
+14 -14
View File
@@ -20,11 +20,11 @@
"@fontsource/source-code-pro": "^5.2.7", "@fontsource/source-code-pro": "^5.2.7",
"@fontsource/ubuntu-mono": "^5.2.8", "@fontsource/ubuntu-mono": "^5.2.8",
"@hydrooj/utils": "workspace:^", "@hydrooj/utils": "workspace:^",
"@mantine/core": "^8.3.8", "@mantine/core": "^8.3.9",
"@mantine/hooks": "^8.3.8", "@mantine/hooks": "^8.3.9",
"@mantine/notifications": "^8.3.8", "@mantine/notifications": "^8.3.9",
"@sentry/browser": "^10.25.0", "@sentry/browser": "^10.26.0",
"@sentry/webpack-plugin": "^4.6.0", "@sentry/webpack-plugin": "^4.6.1",
"@simplewebauthn/browser": "13.2.2", "@simplewebauthn/browser": "13.2.2",
"@svgr/webpack": "^8.1.0", "@svgr/webpack": "^8.1.0",
"@types/html-to-text": "^9.0.4", "@types/html-to-text": "^9.0.4",
@@ -41,7 +41,7 @@
"@types/redux-logger": "^3.0.13", "@types/redux-logger": "^3.0.13",
"@types/uuid": "^11.0.0", "@types/uuid": "^11.0.0",
"@types/webpack-env": "^1.18.8", "@types/webpack-env": "^1.18.8",
"@vscode/codicons": "^0.0.42", "@vscode/codicons": "^0.0.43",
"ajv": "^8.17.1", "ajv": "^8.17.1",
"allotment": "^1.20.4", "allotment": "^1.20.4",
"autoprefixer": "^10.4.22", "autoprefixer": "^10.4.22",
@@ -52,8 +52,8 @@
"clipboard": "^2.0.11", "clipboard": "^2.0.11",
"copy-webpack-plugin": "^13.0.1", "copy-webpack-plugin": "^13.0.1",
"cordis": "4.0.0-beta.5", "cordis": "4.0.0-beta.5",
"core-js": "^3.46.0", "core-js": "^3.47.0",
"core-js-compat": "^3.46.0", "core-js-compat": "^3.47.0",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"diff": "^8.0.2", "diff": "^8.0.2",
"diff-dom": "^5.2.1", "diff-dom": "^5.2.1",
@@ -67,9 +67,9 @@
"jquery-scroll-lock": "^3.1.3", "jquery-scroll-lock": "^3.1.3",
"jquery.easing": "^1.4.1", "jquery.easing": "^1.4.1",
"jquery.transit": "^0.9.12", "jquery.transit": "^0.9.12",
"mantine-contextmenu": "^8.3.8", "mantine-contextmenu": "^8.3.9",
"matchmedia-polyfill": "^0.3.2", "matchmedia-polyfill": "^0.3.2",
"md-editor-rt": "^6.1.1", "md-editor-rt": "^6.2.0",
"mini-css-extract-plugin": "^2.9.4", "mini-css-extract-plugin": "^2.9.4",
"moment": "^2.30.1", "moment": "^2.30.1",
"monaco-editor": "0.52.2", "monaco-editor": "0.52.2",
@@ -80,7 +80,7 @@
"nanoid": "^5.1.6", "nanoid": "^5.1.6",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"p-queue": "^9.0.0", "p-queue": "^9.0.1",
"pickadate": "^3.6.4", "pickadate": "^3.6.4",
"postcss-loader": "7.3.4", "postcss-loader": "7.3.4",
"postcss-preset-mantine": "^1.18.0", "postcss-preset-mantine": "^1.18.0",
@@ -101,7 +101,7 @@
"redux-promise-middleware": "^6.2.0", "redux-promise-middleware": "^6.2.0",
"redux-thunk": "^3.1.0", "redux-thunk": "^3.1.0",
"rupture": "^0.7.1", "rupture": "^0.7.1",
"sass": "^1.94.1", "sass": "^1.94.2",
"sass-loader": "^16.0.6", "sass-loader": "^16.0.6",
"schemastery": "^3.17.2", "schemastery": "^3.17.2",
"shorty.js": "^1.0.1", "shorty.js": "^1.0.1",
@@ -117,8 +117,8 @@
"timeago.js": "^4.0.2", "timeago.js": "^4.0.2",
"ts-loader": "^9.5.4", "ts-loader": "^9.5.4",
"web-streams-polyfill": "^4.2.0", "web-streams-polyfill": "^4.2.0",
"webpack": "^5.102.1", "webpack": "^5.103.0",
"webpack-bundle-analyzer": "^4.10.2", "webpack-bundle-analyzer": "^5.0.1",
"webpack-dev-server": "^5.2.2", "webpack-dev-server": "^5.2.2",
"webpack-manifest-plugin": "^6.0.1", "webpack-manifest-plugin": "^6.0.1",
"webpackbar": "^7.0.0" "webpackbar": "^7.0.0"
@@ -103,9 +103,16 @@
<span class="icon icon-comment--text"></span> {{ _('Discussion') }} <span class="icon icon-comment--text"></span> {{ _('Discussion') }}
</a></li> </a></li>
{% endif %} {% 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}) }}"> <li class="menu__item"><a class="menu__link" href="{{ url('record_main', query={tid:tdoc.docId}) }}">
<span class="icon icon-flag"></span> {{ _('All Submissions') }} <span class="icon icon-flag"></span> {{ _('All Submissions') }}
</a></li> </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') }}"> <li class="menu__item"><a class="menu__link" href="{{ url('wiki_help', anchor='contest') }}">
<span class="icon icon-help"></span> {{ _('Help') }} <span class="icon icon-help"></span> {{ _('Help') }}
</a></li> </a></li>
+1 -1
View File
@@ -16,6 +16,6 @@
"@types/react": "^19.2.6", "@types/react": "^19.2.6",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react-swc": "^4.2.2", "@vitejs/plugin-react-swc": "^4.2.2",
"vite": "^7.2.2" "vite": "^7.2.4"
} }
} }