From 78e75ce8b72537959a0421a14dbf6c1806a2af4e Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 2 Dec 2022 21:39:32 +0800 Subject: [PATCH 01/16] core: add history on discussion create --- packages/hydrooj/src/model/discussion.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/hydrooj/src/model/discussion.ts b/packages/hydrooj/src/model/discussion.ts index 769bcd856..a554bba43 100644 --- a/packages/hydrooj/src/model/discussion.ts +++ b/packages/hydrooj/src/model/discussion.ts @@ -69,6 +69,9 @@ export async function add( payload.domainId!, payload.content!, payload.owner!, document.TYPE_DISCUSSION, null, payload.parentType, payload.parentId, omit(payload, ['domainId', 'content', 'owner', 'parentType', 'parentId']), ); + await coll.insertOne({ + domainId, docId: res, content, uid: owner, ip, time: new Date(), + }); payload.docId = res; await bus.parallel('discussion/add', payload); return payload.docId; From d92f3f3ed6336d739ec0b25e1d08bb5605d073db Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 2 Dec 2022 22:14:26 +0800 Subject: [PATCH 02/16] judge: add copyOutMax config --- packages/hydrojudge/src/sandbox.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/hydrojudge/src/sandbox.ts b/packages/hydrojudge/src/sandbox.ts index 386fb1a15..dae24667f 100644 --- a/packages/hydrojudge/src/sandbox.ts +++ b/packages/hydrojudge/src/sandbox.ts @@ -98,6 +98,7 @@ function proc(params: Parameter): Cmd { strictMemoryLimit: getConfig('strict_memory'), // stackLimit: memory * 1024 * 1024, procLimit: params.processLimit || getConfig('processLimit'), + copyOutMax: Math.floor(1024 * 1024 * size * 3), copyIn, copyOut, copyOutCached, From bba42d59363bd9d897cc42be1625a6d9b9e85b5b Mon Sep 17 00:00:00 2001 From: undefined Date: Sat, 3 Dec 2022 15:48:45 +0800 Subject: [PATCH 03/16] core: problem_prefix_list: support index by pid --- packages/hydrooj/src/handler/problem.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index ea342332e..620694ce5 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -2,7 +2,7 @@ import AdmZip from 'adm-zip'; import { readFile, statSync } from 'fs-extra'; import { isBinaryFile } from 'isbinaryfile'; import { - escapeRegExp, flattenDeep, intersection, isSafeInteger, pick, + escapeRegExp, flattenDeep, intersection, isSafeInteger, pick, uniqBy, } from 'lodash'; import { FilterQuery, ObjectID } from 'mongodb'; import { nanoid } from 'nanoid'; @@ -997,13 +997,15 @@ export class ProblemPrefixListHandler extends Handler { const pdoc = await problem.get(domainId, +prefix, ['domainId', 'docId', 'pid', 'title']); if (pdoc) pdocs.unshift(pdoc); } + const pdoc = await problem.get(domainId, prefix, ['domainId', 'docId', 'pid', 'title']); + if (pdoc) pdocs.unshift(pdoc); if (pdocs.length < 20) { const search = global.Hydro.lib.problemSearch || defaultSearch; const result = await search(domainId, prefix, { limit: 20 - pdocs.length }); const docs = await problem.getMulti(domainId, { docId: { $in: result.hits.map((i) => +i.split('/')[1]) } }).toArray(); pdocs.push(...docs); } - this.response.body = pdocs; + this.response.body = uniqBy(pdocs, 'docId'); } } From d29bd22249ed47ef577f3c771d386b2326c8929f Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 5 Dec 2022 13:04:15 +0800 Subject: [PATCH 04/16] judge: bug fix --- packages/hydrojudge/package.json | 2 +- packages/hydrojudge/src/hosts/hydro.ts | 2 +- packages/hydrooj/src/handler/problem.ts | 10 ++++------ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 81f0a5f94..5e09fa567 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -1,7 +1,7 @@ { "name": "@hydrooj/hydrojudge", "bin": "bin/hydrojudge.js", - "version": "3.1.9", + "version": "3.1.10", "main": "package.json", "author": "undefined ", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/hydrojudge/src/hosts/hydro.ts b/packages/hydrojudge/src/hosts/hydro.ts index 075e1c4f7..40c639d60 100644 --- a/packages/hydrojudge/src/hosts/hydro.ts +++ b/packages/hydrojudge/src/hosts/hydro.ts @@ -145,7 +145,7 @@ export default class Hydro { send(rid: string | ObjectID, key: 'next' | 'end', data: Partial) { data.rid = new ObjectID(rid); data.key = key; - if (data.case) data.case.message = removeNixPath(data.case.message || ''); + if (data.case && typeof data.case.message === 'string') data.case.message = removeNixPath(data.case.message); if (typeof data.message === 'string') data.message = removeNixPath(data.message); if (typeof data.compilerText === 'string') data.compilerText = removeNixPath(data.compilerText); this.ws.send(JSON.stringify(data)); diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 620694ce5..ec4389e73 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -992,12 +992,10 @@ export class ProblemCreateHandler extends Handler { export class ProblemPrefixListHandler extends Handler { @param('prefix', Types.Name) async get(domainId: string, prefix: string) { - const pdocs = await problem.getPrefixList(domainId, prefix); - if (!Number.isNaN(+prefix) && !pdocs.filter((i) => i.docId === +prefix)) { - const pdoc = await problem.get(domainId, +prefix, ['domainId', 'docId', 'pid', 'title']); - if (pdoc) pdocs.unshift(pdoc); - } - const pdoc = await problem.get(domainId, prefix, ['domainId', 'docId', 'pid', 'title']); + const [pdocs, pdoc] = await Promise.all([ + problem.getPrefixList(domainId, prefix), + problem.get(domainId, Number.isSafeInteger(+prefix) ? +prefix : prefix, ['domainId', 'docId', 'pid', 'title']), + ]); if (pdoc) pdocs.unshift(pdoc); if (pdocs.length < 20) { const search = global.Hydro.lib.problemSearch || defaultSearch; From af91a6307d03693cef5fa43330f3229327adf5d9 Mon Sep 17 00:00:00 2001 From: undefined Date: Tue, 6 Dec 2022 17:24:22 +0800 Subject: [PATCH 05/16] judge: fix testlib exitcode --- packages/hydrojudge/package.json | 2 +- packages/hydrojudge/src/checkers.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 5e09fa567..73a25df01 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -1,7 +1,7 @@ { "name": "@hydrooj/hydrojudge", "bin": "bin/hydrojudge.js", - "version": "3.1.10", + "version": "3.1.11", "main": "package.json", "author": "undefined ", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/hydrojudge/src/checkers.ts b/packages/hydrojudge/src/checkers.ts index 083812a57..fb93aec6f 100644 --- a/packages/hydrojudge/src/checkers.ts +++ b/packages/hydrojudge/src/checkers.ts @@ -210,7 +210,7 @@ const checkers: Record = new Proxy({ message: stderr, }; } - if (status === STATUS.STATUS_RUNTIME_ERROR) { + if (status === STATUS.STATUS_RUNTIME_ERROR && !stderr?.trim()) { return { status: STATUS.STATUS_WRONG_ANSWER, score: 0, From e479d5266e21bea0fc8ec9d5b7ff989014581ca5 Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 7 Dec 2022 17:49:04 +0800 Subject: [PATCH 06/16] core: problem.getPrefixList: handle pid --- packages/hydrooj/src/handler/problem.ts | 11 ++++++++--- packages/hydrooj/src/typeutils.ts | 2 +- packages/hydrooj/src/utils.ts | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index ec4389e73..82a2f0751 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -35,6 +35,7 @@ import * as bus from '../service/bus'; import { Handler, param, post, query, route, Types, } from '../service/server'; +import { buildProjection } from '../utils'; import { registerResolver, registerValue } from './api'; import { ContestDetailBaseHandler } from './contest'; @@ -992,15 +993,19 @@ export class ProblemCreateHandler extends Handler { export class ProblemPrefixListHandler extends Handler { @param('prefix', Types.Name) async get(domainId: string, prefix: string) { - const [pdocs, pdoc] = await Promise.all([ + const projection = ['domainId', 'docId', 'pid', 'title'] as const; + const [pdocs, pdoc, apdoc] = await Promise.all([ problem.getPrefixList(domainId, prefix), - problem.get(domainId, Number.isSafeInteger(+prefix) ? +prefix : prefix, ['domainId', 'docId', 'pid', 'title']), + problem.get(domainId, Number.isSafeInteger(+prefix) ? +prefix : prefix, projection), + /^P\d+$/.test(prefix) ? problem.get(domainId, +prefix.substring(1), projection) : Promise.resolve(null), ]); + if (apdoc) pdocs.unshift(apdoc); if (pdoc) pdocs.unshift(pdoc); if (pdocs.length < 20) { const search = global.Hydro.lib.problemSearch || defaultSearch; const result = await search(domainId, prefix, { limit: 20 - pdocs.length }); - const docs = await problem.getMulti(domainId, { docId: { $in: result.hits.map((i) => +i.split('/')[1]) } }).toArray(); + const docs = await problem.getMulti(domainId, { docId: { $in: result.hits.map((i) => +i.split('/')[1]) } }) + .project(buildProjection(projection)).toArray(); pdocs.push(...docs); } this.response.body = uniqBy(pdocs, 'docId'); diff --git a/packages/hydrooj/src/typeutils.ts b/packages/hydrooj/src/typeutils.ts index c19489055..9c44841a1 100644 --- a/packages/hydrooj/src/typeutils.ts +++ b/packages/hydrooj/src/typeutils.ts @@ -10,7 +10,7 @@ export type NestKeys = O extends object ? O extends S ? n export type Value = { [K in keyof O]: V }; -export type Projection = (string & keyof O)[]; +export type Projection = readonly (string & keyof O)[]; export type Omit = Pick>; export type Filter = T extends U ? T : never; export type MaybeArray = T | T[]; diff --git a/packages/hydrooj/src/utils.ts b/packages/hydrooj/src/utils.ts index c427317e8..063dfe1cd 100644 --- a/packages/hydrooj/src/utils.ts +++ b/packages/hydrooj/src/utils.ts @@ -1,4 +1,4 @@ -export function buildProjection(fields: Array): Record { +export function buildProjection(fields: readonly (string | number)[]): Record { const o = {}; for (const k of fields) o[k] = 1; return o; From 76f79adb4241b3987235ebcd66eed41f927c7f3f Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 7 Dec 2022 18:46:12 +0800 Subject: [PATCH 07/16] core: add bash to default langs --- packages/hydrooj/setting.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/hydrooj/setting.yaml b/packages/hydrooj/setting.yaml index 9b3b85493..3f606fbb3 100644 --- a/packages/hydrooj/setting.yaml +++ b/packages/hydrooj/setting.yaml @@ -41,6 +41,11 @@ langs: time_limit_rate: Time limit rate target: Compiler output file display: Display name + bash: + display: Bash + code_file: foo.sh + execute: /bin/bash foo.sh + highlight: bash c: compile: /usr/bin/gcc -Wall --std=c99 -o foo foo.c -lm highlight: c From 8b26e09fb25deab62fc08e6c2ee4e888c2f7ff81 Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 7 Dec 2022 23:53:16 +0800 Subject: [PATCH 08/16] vjudge: do not load in cli mode --- packages/vjudge/src/model.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vjudge/src/model.ts b/packages/vjudge/src/model.ts index fd07c3d3a..64b44184c 100644 --- a/packages/vjudge/src/model.ts +++ b/packages/vjudge/src/model.ts @@ -172,6 +172,7 @@ Context.service('vjudge', VJudgeService); export const name = 'vjudge'; export async function apply(ctx: Context) { if (process.env.NODE_APP_INSTANCE !== '0') return; + if (process.env.HYDRO_CLI) return; const vjudge = new VJudgeService(ctx); await vjudge.start(); for (const [k, v] of Object.entries(providers)) { From 4753621f78cec2e350ad114adcef1627539da8fe Mon Sep 17 00:00:00 2001 From: panda Date: Wed, 7 Dec 2022 23:55:52 +0800 Subject: [PATCH 09/16] core&ui: user_detail: add attended contest (#468) --- packages/hydrooj/src/handler/user.ts | 6 +++++- packages/ui-default/locales/zh.yaml | 1 + .../partials/user_detail/activity.html | 18 ++++++++++++++++++ .../partials/user_detail/solution.html | 4 ++-- 4 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 packages/ui-default/templates/partials/user_detail/activity.html diff --git a/packages/hydrooj/src/handler/user.ts b/packages/hydrooj/src/handler/user.ts index b71c2f5c2..9bc09a8dd 100644 --- a/packages/hydrooj/src/handler/user.ts +++ b/packages/hydrooj/src/handler/user.ts @@ -12,6 +12,7 @@ import { sendMail } from '../lib/mail'; import { isEmail, isPassword } from '../lib/validator'; import BlackListModel from '../model/blacklist'; import { PERM, PRIV, STATUS } from '../model/builtin'; +import * as ContestModel from '../model/contest'; import domain from '../model/domain'; import oauth from '../model/oauth'; import * as oplog from '../model/oplog'; @@ -356,9 +357,12 @@ class UserDetailHandler extends Handler { } } const tags = Object.entries(acInfo).sort((a, b) => b[1] - a[1]).slice(0, 20); + const tsdocs = await ContestModel.getMultiStatus(domainId, { uid: this.user._id, attend: { $exists: true } }).sort({ _id: 1 }).toArray(); + const tdocs = await ContestModel.getMulti(domainId, { docId: { $in: tsdocs.map((i) => i.docId) } }) + .project({ docId: 1, title: 1, rule: 1 }).toArray(); this.response.template = 'user_detail.html'; this.response.body = { - isSelfProfile, udoc, sdoc, pdocs, tags, + isSelfProfile, udoc, sdoc, pdocs, tags, tdocs, }; if (this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION)) { const psdocs = await SolutionModel.getByUser(domainId, uid).limit(10).toArray(); diff --git a/packages/ui-default/locales/zh.yaml b/packages/ui-default/locales/zh.yaml index be4555685..f9081d180 100644 --- a/packages/ui-default/locales/zh.yaml +++ b/packages/ui-default/locales/zh.yaml @@ -83,6 +83,7 @@ API: API Argument {0} is invalid.: 非法的参数 {0} 。 Arguments: 参数 Assign: 分配给 +Assignment: 作业 At least 4 characters: 至少 4 个字符 Attend Contest: 参加比赛 Attend contests: 参加比赛 diff --git a/packages/ui-default/templates/partials/user_detail/activity.html b/packages/ui-default/templates/partials/user_detail/activity.html new file mode 100644 index 000000000..c866a9cfd --- /dev/null +++ b/packages/ui-default/templates/partials/user_detail/activity.html @@ -0,0 +1,18 @@ +
  • +

    {{ _('Recent Activities') }}

    +
    + {% if not tdocs.length %} + {{ nothing.render("This person is lazy and didn't join any contest or homework") }} + {% else %} +
    + +
    + {% endif %} +
    +
  • \ No newline at end of file diff --git a/packages/ui-default/templates/partials/user_detail/solution.html b/packages/ui-default/templates/partials/user_detail/solution.html index 327369ef3..c8b755fee 100644 --- a/packages/ui-default/templates/partials/user_detail/solution.html +++ b/packages/ui-default/templates/partials/user_detail/solution.html @@ -1,9 +1,9 @@ {% if psdocs %}
  • -

    {{ _('Recent solutions') }}

    +

    {{ _('Recent Solutions') }}

    {% if not psdocs.length %} - {{ nothing.render("This person is lazy and wrote any solution") }} + {{ nothing.render("This person is lazy and didn't wrote any solution") }} {% else %}
    From 32e2ffc1dbb07cf86ccb176c2c0f6abba8d26d0f Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 9 Dec 2022 14:22:31 +0800 Subject: [PATCH 10/16] core: userDetail: fix attended contest filter --- packages/hydrooj/src/handler/user.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/hydrooj/src/handler/user.ts b/packages/hydrooj/src/handler/user.ts index 9bc09a8dd..c46542c82 100644 --- a/packages/hydrooj/src/handler/user.ts +++ b/packages/hydrooj/src/handler/user.ts @@ -357,9 +357,9 @@ class UserDetailHandler extends Handler { } } const tags = Object.entries(acInfo).sort((a, b) => b[1] - a[1]).slice(0, 20); - const tsdocs = await ContestModel.getMultiStatus(domainId, { uid: this.user._id, attend: { $exists: true } }).sort({ _id: 1 }).toArray(); + const tsdocs = await ContestModel.getMultiStatus(domainId, { uid, attend: { $exists: true } }).project({ docId: 1 }).toArray(); const tdocs = await ContestModel.getMulti(domainId, { docId: { $in: tsdocs.map((i) => i.docId) } }) - .project({ docId: 1, title: 1, rule: 1 }).toArray(); + .project({ docId: 1, title: 1, rule: 1 }).sort({ _id: -1 }).toArray(); this.response.template = 'user_detail.html'; this.response.body = { isSelfProfile, udoc, sdoc, pdocs, tags, tdocs, From 076e2097c08ddd63495a670a7970491f347fc51a Mon Sep 17 00:00:00 2001 From: undefined Date: Sat, 10 Dec 2022 04:05:38 +0800 Subject: [PATCH 11/16] core: rating&problemStat: performance enhancement --- packages/hydrooj/src/script/problemStat.ts | 19 ++++--------- packages/hydrooj/src/script/rating.ts | 32 ++++++++++------------ 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/packages/hydrooj/src/script/problemStat.ts b/packages/hydrooj/src/script/problemStat.ts index dcbe538db..a4bf109ef 100644 --- a/packages/hydrooj/src/script/problemStat.ts +++ b/packages/hydrooj/src/script/problemStat.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-constant-condition */ /* eslint-disable no-await-in-loop */ import { ObjectID } from 'mongodb'; import Schema from 'schemastery'; @@ -29,10 +28,8 @@ export async function udoc(report) { }, ]; let bulk = db.collection('domain.user').initializeUnorderedBulkOp(); - const cursor = db.collection('record').aggregate(pipeline, { allowDiskUse: true }); - while (true) { - const adoc = await cursor.next() as any; - if (!adoc) break; + const cursor = db.collection('record').aggregate(pipeline, { allowDiskUse: true }); + for await (const adoc of cursor) { bulk.find({ domainId: adoc._id.domainId, uid: adoc._id.uid, @@ -61,10 +58,8 @@ export async function psdoc(report) { }, }, ]; - const data = db.collection('record').aggregate(pipeline, { allowDiskUse: true }); - while (true) { - const adoc = await data.next() as any; - if (!adoc) break; + const data = db.collection('record').aggregate(pipeline, { allowDiskUse: true }); + for await (const adoc of data) { await document.setStatus(adoc._id.domainId, document.TYPE_PROBLEM, adoc._id.pid, adoc._id.uid, { nSubmit: adoc.nSubmit }); } } @@ -117,11 +112,9 @@ export async function pdoc(report) { pipeline[2].$group[`s${i}`] = { $sum: `$s${i}` }; } let bulk = db.collection('document').initializeUnorderedBulkOp(); - const data = db.collection('record').aggregate(pipeline, { allowDiskUse: true }); + const data = db.collection('record').aggregate(pipeline, { allowDiskUse: true }); let cnt = 0; - while (true) { - const adoc = await data.next() as any; - if (!adoc) break; + for await (const adoc of data) { const $set = { nSubmit: adoc.nSubmit, nAccept: adoc.nAccept, diff --git a/packages/hydrooj/src/script/rating.ts b/packages/hydrooj/src/script/rating.ts index a39b41ab5..35b7ec6d2 100644 --- a/packages/hydrooj/src/script/rating.ts +++ b/packages/hydrooj/src/script/rating.ts @@ -3,6 +3,7 @@ import { NumericDictionary, unionWith } from 'lodash'; import { FilterQuery } from 'mongodb'; import Schema from 'schemastery'; +import { Counter } from '@hydrooj/utils'; import { Tdoc, Udoc } from '../interface'; import difficultyAlgorithm from '../lib/difficulty'; import rating from '../lib/rating'; @@ -100,21 +101,15 @@ export const RpTypes: Record = { global.Hydro.model.rp = RpTypes; export async function calcLevel(domainId: string, report: Function) { - const filter = { rp: { $gt: 0 } }; - const ducnt = await domain.getMultiUserInDomain(domainId, filter).count(); await domain.setMultiUserInDomain(domainId, {}, { level: 0, rank: null }); - if (!ducnt) return; let last = { rp: null }; let rank = 0; let count = 0; const coll = db.collection('domain.user'); + const filter = { rp: { $gt: 0 }, uid: { $nin: [0, 1], $gt: -1000 } }; const ducur = domain.getMultiUserInDomain(domainId, filter).project({ rp: 1 }).sort({ rp: -1 }); let bulk = coll.initializeUnorderedBulkOp(); - // eslint-disable-next-line no-constant-condition - while (true) { - const dudoc = await ducur.next(); - if (!dudoc) break; - if ([0, 1].includes(dudoc.uid)) continue; + for await (const dudoc of ducur) { count++; if (!dudoc.rp) dudoc.rp = null; if (dudoc.rp !== last.rp) rank = count; @@ -122,6 +117,7 @@ export async function calcLevel(domainId: string, report: Function) { last = dudoc; if (count % 100 === 0) report({ message: `#${count}: Rank ${rank}` }); } + if (!count) return; await bulk.execute(); const levels = global.Hydro.model.builtin.LEVELS; bulk = coll.initializeUnorderedBulkOp(); @@ -136,28 +132,30 @@ export async function calcLevel(domainId: string, report: Function) { await bulk.execute(); } -async function runInDomain(id: string, report: Function) { - const info = await domain.get(id); - const domainIds = [id, ...(info.union || [])]; +async function runInDomain(domainId: string, report: Function) { + const info = await domain.get(domainId); + const domainIds = [domainId, ...(info.union || [])]; const results: Record = {}; - const udict = new Proxy({}, { get: (self, key) => self[key] || 0 }); + const udict = Counter(); for (const type in RpTypes) { results[type] = new Proxy({}, { get: (self, key) => self[key] || RpTypes[type].base }); await RpTypes[type].run(domainIds, results[type], report); + const bulk = db.collection('domain.user').initializeUnorderedBulkOp(); for (const uid in results[type]) { - const udoc = await UserModel.getById(id, +uid); + const udoc = await UserModel.getById(domainId, +uid); if (!udoc?.hasPriv(PRIV.PRIV_USER_PROFILE)) continue; - await domain.updateUserInDomain(id, +uid, { $set: { [`rpInfo.${type}`]: results[type][uid] } }); + bulk.find({ domainId, uid: +uid }).updateOne({ $set: { [`rpInfo.${type}`]: results[type][uid] } }); udict[+uid] += results[type][uid]; } + if (bulk.length) await bulk.execute(); } - await domain.setMultiUserInDomain(id, {}, { rp: 0 }); + await domain.setMultiUserInDomain(domainId, {}, { rp: 0 }); const bulk = db.collection('domain.user').initializeUnorderedBulkOp(); for (const uid in udict) { - bulk.find({ domainId: id, uid: +uid }).upsert().update({ $set: { rp: Math.max(0, udict[uid]) } }); + bulk.find({ domainId, uid: +uid }).upsert().update({ $set: { rp: Math.max(0, udict[uid]) } }); } if (bulk.length) await bulk.execute(); - await calcLevel(id, report); + await calcLevel(domainId, report); } export async function run({ domainId }, report: Function) { From c5ad6599a837fab86789e87579806b95cf0eb23d Mon Sep 17 00:00:00 2001 From: undefined Date: Sat, 10 Dec 2022 04:27:25 +0800 Subject: [PATCH 12/16] core: use case-sensitive query --- packages/hydrooj/src/model/problem.ts | 3 +-- packages/hydrooj/src/model/storage.ts | 2 +- packages/hydrooj/src/model/user.ts | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index 5ff38a5a9..6f1e48f62 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -330,8 +330,7 @@ export class ProblemModel { } static async getPrefixList(domainId: string, prefix: string) { - prefix = prefix.toLowerCase(); - const $regex = new RegExp(`\\A${escapeRegExp(prefix)}`, 'gmi'); + const $regex = new RegExp(`^${escapeRegExp(prefix.toLowerCase())}`, 'i'); const filter = { $or: [{ pid: { $regex } }, { title: { $regex } }] }; return await document.getMulti(domainId, document.TYPE_PROBLEM, filter, ['domainId', 'docId', 'pid', 'title']) .limit(20).toArray(); diff --git a/packages/hydrooj/src/model/storage.ts b/packages/hydrooj/src/model/storage.ts index 9e87b6487..49b32a50f 100644 --- a/packages/hydrooj/src/model/storage.ts +++ b/packages/hydrooj/src/model/storage.ts @@ -60,7 +60,7 @@ export class StorageModel { if (target.includes('..') || target.includes('//')) throw new Error('Invalid path'); if (target.length && !target.endsWith('/')) target += '/'; const results = await StorageModel.coll.find({ - path: { $regex: new RegExp(`^${escapeRegExp(target)}${recursive ? '' : '[^/]+$'}`, 'i') }, + path: { $regex: `^${escapeRegExp(target)}${recursive ? '' : '[^/]+$'}` }, autoDelete: null, }).toArray(); return results.map((i) => ({ diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index 05457bfba..5df7d011a 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -367,8 +367,7 @@ class UserModel { @ArgMethod static async getPrefixList(domainId: string, prefix: string, limit: number = 50) { - prefix = prefix.toLowerCase(); - const $regex = new RegExp(`\\A${escapeRegExp(prefix)}`, 'gmi'); + const $regex = `^${escapeRegExp(prefix.toLowerCase())}`; const udocs = await coll.find({ unameLower: { $regex } }) .limit(limit).project({ _id: 1 }).toArray(); const users = []; From 6d69e8873977e186224263679939ce500e31c7bb Mon Sep 17 00:00:00 2001 From: undefined Date: Sat, 10 Dec 2022 04:41:49 +0800 Subject: [PATCH 13/16] core: db: prefer connection uri --- install/install.ts | 6 +----- packages/hydrooj/src/service/db.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/install/install.ts b/install/install.ts index e94100ccb..f34783d4a 100644 --- a/install/install.ts +++ b/install/install.ts @@ -336,11 +336,7 @@ connect-timeout = 10`); })})`), [`mongo 127.0.0.1:27017/hydro ${tmpFile}`, { retry: true }], () => writeFileSync(`${process.env.HOME}/.hydro/config.json`, JSON.stringify({ - host: '127.0.0.1', - port: 27017, - name: 'hydro', - username: 'hydro', - password, + uri: `mongodb://hydro:${password}@127.0.0.1:27017/hydro`, })), 'pm2 stop mongod', 'pm2 del mongod', diff --git a/packages/hydrooj/src/service/db.ts b/packages/hydrooj/src/service/db.ts index 9bcb433c8..adb275574 100644 --- a/packages/hydrooj/src/service/db.ts +++ b/packages/hydrooj/src/service/db.ts @@ -17,6 +17,7 @@ interface MongoConfig { port?: string, name?: string, url?: string, + uri?: string, prefix?: string, } @@ -29,7 +30,7 @@ class MongoService { let mongourl = `${opts.protocol || 'mongodb'}://`; if (opts.username) mongourl += `${opts.username}:${encodeURIComponent(opts.password)}@`; mongourl += `${opts.host}:${opts.port}/${opts.name}`; - if (opts.url) mongourl = opts.url; + if (opts.url || opts.uri) mongourl = opts.url || opts.uri; return mongourl; } @@ -42,7 +43,12 @@ class MongoService { mongourl = mongod.getUri(); } this.opts = opts; - this.client = await MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true }); + this.client = await MongoClient.connect(mongourl, { + useNewUrlParser: true, + useUnifiedTopology: true, + readPreference: 'nearest', + writeConcern: 'majority', + }); this.db = this.client.db(opts.name || 'hydro'); await bus.parallel('database/connect', this.db); } From 96fa6cc7b9d25409173316a51248c14d3e84e21c Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 12 Dec 2022 00:53:44 +0800 Subject: [PATCH 14/16] ui: frontend api (#470) [skip-cache] --- .eslintignore | 2 +- .eslintrc.yaml | 24 ++- build/prepare.js | 7 + package.json | 4 +- packages/onlyoffice/public/office.page.js | 153 +++++++++--------- packages/ui-default/api.ts | 62 +++++++ .../build/utils/{root.js => root.ts} | 1 - packages/ui-default/bus.ts | 3 +- .../components/DOMAttachedObject.ts | 4 +- .../components/DomainSelectAutoComplete.tsx | 2 +- .../components/ProblemSelectAutoComplete.tsx | 3 +- .../components/UserSelectAutoComplete.tsx | 2 +- .../components/browser-update.page.ts | 3 +- .../ui-default/components/calendar/index.js | 2 +- .../ui-default/components/clipboard.page.ts | 4 +- .../components/contest/contest.page.ts | 4 +- .../ui-default/components/dialog/DomDialog.ts | 2 +- .../ui-default/components/dialog/index.ts | 3 +- .../components/discussion/CommentBox.js | 2 +- .../components/discussion/comments.page.tsx | 8 +- .../components/discussion/history.page.tsx | 4 +- .../components/discussion/reaction.page.tsx | 2 +- .../components/dropdown/Dropdown.js | 5 +- .../components/editor/cmeditor.page.ts | 2 +- .../ui-default/components/editor/index.ts | 3 +- .../ui-default/components/form/form.page.ts | 2 +- .../highlighter/highlighter.page.ts | 3 +- .../components/highlighter/prismjs.js | 2 +- packages/ui-default/components/hint.ts | 3 +- .../components/hitokoto/index.page.js | 4 +- .../ui-default/components/languageselect.tsx | 2 +- .../ui-default/components/marker/Marker.js | 3 +- .../components/marker/MarkerReactive.js | 2 +- .../ui-default/components/media/media.page.js | 2 +- .../components/menu/menu-heading.page.js | 2 +- .../ui-default/components/menu/menu.page.js | 3 +- .../components/message/index.page.ts | 3 +- .../MessagePadDialogueContentContainer.jsx | 9 +- .../MessagePadDialogueListContainer.jsx | 2 +- .../messagepad/MessagePadInputContainer.jsx | 2 +- .../components/messagepad/index.jsx | 3 +- .../ui-default/components/monaco/index.ts | 3 +- .../components/monaco/languages/markdown.ts | 2 +- .../ui-default/components/monaco/loader.ts | 2 +- .../components/navigation/navigation.page.js | 4 +- .../components/notification/index.js | 3 +- .../notification/notification.page.js | 2 +- .../components/preview/preview.page.ts | 7 +- .../components/problem/create.page.js | 3 +- .../components/problemconfig/BasicForm.tsx | 2 +- .../problemconfig/ProblemConfigForm.tsx | 2 +- .../components/problemconfig/ProblemType.tsx | 2 +- .../components/problemconfig/SubtaskTable.tsx | 2 +- .../problemconfig/TestCasesTable.tsx | 2 +- .../ui-default/components/rotator/index.js | 2 +- .../scratchpad/ScratchpadPretestContainer.jsx | 2 +- .../scratchpad/ScratchpadRecordsContainer.jsx | 2 +- .../ScratchpadRecordsRowContainer.jsx | 9 +- .../scratchpad/ScratchpadToolbarContainer.jsx | 4 +- .../components/scratchpad/reducers/ui.ts | 2 +- packages/ui-default/components/selectUser.ts | 3 +- .../components/signin/signInDialog.page.js | 8 +- .../ui-default/components/star/star.page.js | 2 +- .../ui-default/components/time/time.page.js | 2 +- packages/ui-default/components/upload.ts | 7 +- .../ui-default/components/vote/vote.page.js | 2 +- .../zipDownloader/{index.js => index.ts} | 12 +- packages/ui-default/entry.js | 7 +- packages/ui-default/hydro.ts | 5 +- packages/ui-default/index.ts | 17 +- packages/ui-default/modules.js | 37 ----- packages/ui-default/package.json | 2 + packages/ui-default/pages/api.page.tsx | 2 +- packages/ui-default/pages/contest.page.ts | 2 +- .../ui-default/pages/contest_edit.page.ts | 4 +- .../ui-default/pages/domain_group.page.ts | 7 +- packages/ui-default/pages/domain_role.page.js | 7 +- packages/ui-default/pages/domain_user.page.js | 7 +- packages/ui-default/pages/files.page.js | 7 +- .../ui-default/pages/home_messages.page.jsx | 3 +- .../ui-default/pages/home_preference.page.jsx | 7 +- .../ui-default/pages/home_security.page.ts | 7 +- .../ui-default/pages/homework_main.page.js | 5 +- .../ui-default/pages/manage_script.page.js | 2 +- .../pages/manage_user_import.page.js | 4 +- .../ui-default/pages/manage_user_priv.page.js | 4 +- .../ui-default/pages/problem_config.page.tsx | 13 +- .../ui-default/pages/problem_detail.page.jsx | 9 +- .../ui-default/pages/problem_edit.page.js | 7 +- .../ui-default/pages/problem_files.page.js | 7 +- .../ui-default/pages/problem_main.page.js | 8 +- .../ui-default/pages/problem_sidebar.page.ts | 4 +- .../ui-default/pages/problem_submit.page.tsx | 2 +- packages/ui-default/pages/record_main.page.ts | 3 +- packages/ui-default/pages/setting.page.tsx | 2 +- .../ui-default/pages/training_edit.page.ts | 4 +- packages/ui-default/pages/user_login.page.ts | 2 +- packages/ui-default/types.ts | 11 -- packages/ui-default/utils/api.ts | 26 --- packages/ui-default/utils/availableLangs.ts | 12 -- packages/ui-default/utils/base.ts | 153 ++++++++++++++++++ packages/ui-default/utils/delay.js | 5 - .../ui-default/utils/emulateAnchorClick.js | 20 --- packages/ui-default/utils/i18n.js | 8 - packages/ui-default/utils/index.ts | 118 ++++++++++++++ packages/ui-default/utils/loadModule.ts | 14 -- packages/ui-default/utils/loadReactRedux.ts | 2 +- packages/ui-default/utils/mongoId.js | 16 -- packages/ui-default/utils/pipeStream.js | 21 --- packages/ui-default/utils/pjax.js | 2 +- packages/ui-default/utils/request.js | 106 ------------ .../ui-default/utils/{slide.js => slide.ts} | 0 packages/ui-default/utils/substitute.js | 13 -- packages/ui-default/utils/tpl.ts | 31 ---- packages/ui-default/utils/zIndexManager.js | 13 -- packages/ui-default/utils/zip.js | 9 -- 116 files changed, 599 insertions(+), 629 deletions(-) create mode 100644 packages/ui-default/api.ts rename packages/ui-default/build/utils/{root.js => root.ts} (84%) rename packages/ui-default/components/zipDownloader/{index.js => index.ts} (93%) delete mode 100644 packages/ui-default/modules.js delete mode 100644 packages/ui-default/types.ts delete mode 100644 packages/ui-default/utils/api.ts delete mode 100644 packages/ui-default/utils/availableLangs.ts create mode 100644 packages/ui-default/utils/base.ts delete mode 100644 packages/ui-default/utils/delay.js delete mode 100644 packages/ui-default/utils/emulateAnchorClick.js delete mode 100644 packages/ui-default/utils/i18n.js create mode 100644 packages/ui-default/utils/index.ts delete mode 100644 packages/ui-default/utils/loadModule.ts delete mode 100644 packages/ui-default/utils/mongoId.js delete mode 100644 packages/ui-default/utils/pipeStream.js delete mode 100644 packages/ui-default/utils/request.js rename packages/ui-default/utils/{slide.js => slide.ts} (100%) delete mode 100644 packages/ui-default/utils/substitute.js delete mode 100644 packages/ui-default/utils/tpl.ts delete mode 100644 packages/ui-default/utils/zIndexManager.js delete mode 100644 packages/ui-default/utils/zip.js diff --git a/.eslintignore b/.eslintignore index 8b90310b9..401c38a5c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,4 @@ dist *.d.ts node_modules -public +public/**/*.js \ No newline at end of file diff --git a/.eslintrc.yaml b/.eslintrc.yaml index e3b8a2db9..d2d19209b 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -3,7 +3,7 @@ extends: - airbnb-base - airbnb-typescript/base env: - jquery: true + es6: true globals: Atomics: readonly SharedArrayBuffer: readonly @@ -14,8 +14,26 @@ plugins: - simple-import-sort - eslint-plugin-import ignorePatterns: - - public/ - - '*.spec.ts' + - public/**/*.js + - packages/ui-default +overrides: + - files: + - '**/public/**/*.ts' + - '**/public/**/*.page.js' + rules: + '@typescript-eslint/indent': + - warn + - 2 + env: + browser: true + es6: true + jquery: true + globals: + UiContext: true + UserContext: true + externalModules: true + LOCALES: true + LANGS: true rules: '@typescript-eslint/no-shadow': 1 diff --git a/build/prepare.js b/build/prepare.js index 8029fe800..1c0de4d69 100644 --- a/build/prepare.js +++ b/build/prepare.js @@ -1,5 +1,10 @@ const fs = require('fs'); const path = require('path'); +const child = require('child_process'); + +if (fs.existsSync('plugins/patch-package/package.json') && fs.existsSync('node_modules/patch-package/package.json')) { + child.execSync('npx patch-package --patch-dir=plugins/patch-package/patches', { stdio: 'inherit' }); +} const dir = path.dirname(path.dirname(require.resolve('@types/node/package.json'))); const types = fs.readdirSync(dir).filter((i) => !['sharedworker', 'serviceworker'].includes(i)); @@ -51,6 +56,8 @@ const configFlat = (name) => ({ if (!fs.existsSync(path.resolve(process.cwd(), 'plugins'))) { fs.mkdirSync(path.resolve(process.cwd(), 'plugins')); + // Write an empty file to make eslint happy + fs.writeFileSync(path.resolve(process.cwd(), 'plugins/eslint.ts'), ''); } const modules = [ diff --git a/package.json b/package.json index aff4dc8ea..29352ffed 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "build:ui:production:webpack": "cross-env NODE_OPTIONS=--max_old_space_size=8192 node packages/ui-default/build --production", "test": "mocha", "benchmark": "cross-env BENCHMARK=true mocha", - "lint": "eslint packages --ext ts --fix", - "lint:ci": "eslint packages --ext ts", + "lint": "eslint packages plugins --ext ts --fix", + "lint:ci": "eslint packages plugins --ext ts", "lint:ui": "yarn workspace @hydrooj/ui-default lint --ext .js,.ts,.jsx,.tsx . --fix", "lint:ui:ci": "yarn workspace @hydrooj/ui-default lint --ext .js,.ts,.jsx,.tsx .", "debug": "node --trace-warnings --async-stack-traces --trace-deprecation node_modules/hydrooj/bin/hydrooj --debug --template", diff --git a/packages/onlyoffice/public/office.page.js b/packages/onlyoffice/public/office.page.js index 203f9af5a..50a0bda2b 100644 --- a/packages/onlyoffice/public/office.page.js +++ b/packages/onlyoffice/public/office.page.js @@ -1,82 +1,81 @@ -(() => { - const { AutoloadPage } = window.Hydro; - const { $ } = window.node_modules; +import { $ } from '@hydrooj/ui-default'; - let loaded = false; - async function load() { - if (loaded) return Promise.resolve(); - return new Promise((resolve, reject) => { - const scriptElement = document.createElement('script'); - scriptElement.src = UiContext.onlyofficeApi; - scriptElement.async = true; - document.head.appendChild(scriptElement); - scriptElement.onload = resolve; - scriptElement.onerror = reject; - loaded = true; - }); - } +const { AutoloadPage } = window.Hydro; - const loader = mode => async (element) => { - const id = `${mode}-${Math.random().toString()}`; - $(element).attr('id', id); - const url = $(element).text(); - const t = new URL(url, window.location.href).pathname.split('.'); - const n = new URL(url, window.location.href).pathname.split('/'); - const lang = UserContext.viewLang.includes('_') ? UserContext.viewLang.split('_')[0] : UserContext.viewLang; - // eslint-disable-next-line no-undef - window.editor = new DocsAPI.DocEditor(id, { - document: { - fileType: t[t.length - 1], - key: Math.random().toString(16), - title: decodeURIComponent(n[n.length - 1]), - url: new URL(url, window.location.href), - permissions: { - comment: false, - copy: true, - download: true, - edit: false, - fillForms: false, - modifyContentControl: false, - modifyFilter: false, - print: true, - protect: false, - review: false, - }, - }, - editorConfig: { - lang, - mode: 'view', - user: { - group: "Hydro", - id: UserContext._id.toString(), - name: UserContext.uname, - }, - customization: { - chat: false, - comments: false, - help: false, - hideRulers: true, - plugins: false, - } - }, - documentType: mode, - height: mode === 'slide' ? '560px' : '900px', - }); - } +let loaded = false; +async function load() { + if (loaded) return Promise.resolve(); + return new Promise((resolve, reject) => { + const scriptElement = document.createElement('script'); + scriptElement.src = UiContext.onlyofficeApi; + scriptElement.async = true; + document.head.appendChild(scriptElement); + scriptElement.onload = resolve; + scriptElement.onerror = reject; + loaded = true; + }); +} - const getEles = types => { - const eles = []; - for (const type of types) eles.push(...$('div[data-' + type + ']').get()); - return eles; - } +const loader = (mode) => async (element) => { + const id = `${mode}-${Math.random().toString()}`; + $(element).attr('id', id); + const url = $(element).text(); + const t = new URL(url, window.location.href).pathname.split('.'); + const n = new URL(url, window.location.href).pathname.split('/'); + const lang = UserContext.viewLang.includes('_') ? UserContext.viewLang.split('_')[0] : UserContext.viewLang; + // eslint-disable-next-line no-undef + window.editor = new DocsAPI.DocEditor(id, { + document: { + fileType: t[t.length - 1], + key: Math.random().toString(16), + title: decodeURIComponent(n[n.length - 1]), + url: new URL(url, window.location.href), + permissions: { + comment: false, + copy: true, + download: true, + edit: false, + fillForms: false, + modifyContentControl: false, + modifyFilter: false, + print: true, + protect: false, + review: false, + }, + }, + editorConfig: { + lang, + mode: 'view', + user: { + group: 'Hydro', + id: UserContext._id.toString(), + name: UserContext.uname, + }, + customization: { + chat: false, + comments: false, + help: false, + hideRulers: true, + plugins: false, + }, + }, + documentType: mode, + height: mode === 'slide' ? '560px' : '900px', + }); +}; - const page = new AutoloadPage('onlyoffice', async () => { - let all = getEles(['doc', 'docx', 'cell', 'xls', 'xlsx', 'slide', 'ppt', 'pptx']); - if (all.length) await load(); - getEles(['doc', 'docx']).forEach(loader('word')); - getEles(['cell', 'xls', 'xlsx']).forEach(loader('cell')) - getEles(['slide', 'ppt', 'pptx']).forEach(loader('slide')); - }); +const getEles = (types) => { + const eles = []; + for (const type of types) eles.push(...$(`div[data-${type}]`).get()); + return eles; +}; - window.Hydro.extraPages.push(page); -})(); +const page = new AutoloadPage('onlyoffice', async () => { + const all = getEles(['doc', 'docx', 'cell', 'xls', 'xlsx', 'slide', 'ppt', 'pptx']); + if (all.length) await load(); + getEles(['doc', 'docx']).forEach(loader('word')); + getEles(['cell', 'xls', 'xlsx']).forEach(loader('cell')); + getEles(['slide', 'ppt', 'pptx']).forEach(loader('slide')); +}); + +window.Hydro.extraPages.push(page); diff --git a/packages/ui-default/api.ts b/packages/ui-default/api.ts new file mode 100644 index 000000000..6411f9e00 --- /dev/null +++ b/packages/ui-default/api.ts @@ -0,0 +1,62 @@ +export * from './utils'; +export { default as Notification } from './components/notification'; +export * from './components/dialog'; +export * as bus from './bus'; +export { default as loadMonaco } from './components/monaco/loader'; +export * as redux from 'react-redux'; +export * from './components/zipDownloader'; +export { default as $ } from 'jquery'; +export { default as _ } from 'lodash'; +export { default as React } from 'react'; +export { default as ReactDOM } from 'react-dom/client'; +export * from './misc/Page'; + +export default async function load(name: string) { + if (window.node_modules[name]) return window.node_modules[name]; + if (name === 'echarts') return import('echarts'); + if (name === 'moment') return import('moment'); + throw new Error(`Module ${name} not found`); +} + +import AutoComplete from './components/autocomplete'; +import CustomSelectAutoComplete from './components/autocomplete/CustomSelectAutoComplete'; +import DomainSelectAutoComplete from './components/autocomplete/DomainSelectAutoComplete'; +import ProblemSelectAutoComplete from './components/autocomplete/ProblemSelectAutoComplete'; +import UserSelectAutoComplete from './components/autocomplete/UserSelectAutoComplete'; + +export { + load, AutoComplete, UserSelectAutoComplete, ProblemSelectAutoComplete, DomainSelectAutoComplete, CustomSelectAutoComplete, +}; +export const { UserContext, UiContext } = window; +export function addPage(page: import('./misc/Page').Page | (() => Promise | void)) { + window.Hydro.extraPages.push(page); +} + +declare global { + interface Window { + LANGS: Record; + } + + let UserContext: Record; + let UiContext: Record; +} + +// Below are old version api compat +/* eslint-disable import/order */ +import $ from 'jquery'; +import _ from 'lodash'; +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import * as redux from 'react-redux'; + +const modules = { + _, $, React, redux, ReactDOM, load, +}; + +declare global { + interface Window { + node_modules: typeof modules; + } +} + +Object.assign(window, { node_modules: modules, $, jQuery: $ }); diff --git a/packages/ui-default/build/utils/root.js b/packages/ui-default/build/utils/root.ts similarity index 84% rename from packages/ui-default/build/utils/root.js rename to packages/ui-default/build/utils/root.ts index b402e6142..0468e917b 100644 --- a/packages/ui-default/build/utils/root.js +++ b/packages/ui-default/build/utils/root.ts @@ -1,4 +1,3 @@ -/* eslint-disable */ import path from 'path'; export default function root(fn = '.') { diff --git a/packages/ui-default/bus.ts b/packages/ui-default/bus.ts index 1a7348fe0..cd2b73e97 100644 --- a/packages/ui-default/bus.ts +++ b/packages/ui-default/bus.ts @@ -1,4 +1,5 @@ /* eslint-disable no-await-in-loop */ +import type * as monaco from 'monaco-editor'; // eslint-disable-next-line @typescript-eslint/naming-convention const _hooks: Record any>> = {}; @@ -11,7 +12,7 @@ export type Disposable = () => void; export type VoidReturn = Promise | any; export interface EventMap extends Record { - 'scratchpadEditorCreate': (editor, monaco) => any; + 'scratchpadEditorCreate': (editor: monaco.editor.IStandaloneCodeEditor, monaco: typeof import('monaco-editor')) => any; } function getHooks(name: K) { diff --git a/packages/ui-default/components/DOMAttachedObject.ts b/packages/ui-default/components/DOMAttachedObject.ts index 05707afa7..5c7a9d9ca 100644 --- a/packages/ui-default/components/DOMAttachedObject.ts +++ b/packages/ui-default/components/DOMAttachedObject.ts @@ -23,7 +23,7 @@ function monitorResource(resource) { } } -export default class DOMAttachedObject { +class DOMAttachedObject { static uniqueIdCounter = 0; static DOMAttachKey: string; static DOMAttachSelector: string; @@ -127,3 +127,5 @@ export default class DOMAttachedObject { if (monitorDetach) monitorResource(this); } } + +export default DOMAttachedObject as typeof DOMAttachedObject & DOMAttachedObject; diff --git a/packages/ui-default/components/autocomplete/components/DomainSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/components/DomainSelectAutoComplete.tsx index acd65bf7a..bcae93307 100644 --- a/packages/ui-default/components/autocomplete/components/DomainSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/components/DomainSelectAutoComplete.tsx @@ -1,7 +1,7 @@ import type { DomainDoc } from 'hydrooj/src/interface'; import PropTypes from 'prop-types'; import React, { forwardRef } from 'react'; -import request from 'vj/utils/request'; +import { request } from 'vj/utils'; import AutoComplete, { AutoCompleteHandle, AutoCompleteProps } from './AutoComplete'; const DomainSelectAutoComplete = forwardRef, AutoCompleteProps>((props, ref) => ( diff --git a/packages/ui-default/components/autocomplete/components/ProblemSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/components/ProblemSelectAutoComplete.tsx index b4aa4bde6..e0c1b26de 100644 --- a/packages/ui-default/components/autocomplete/components/ProblemSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/components/ProblemSelectAutoComplete.tsx @@ -1,8 +1,7 @@ import type { ProblemDoc } from 'hydrooj/src/interface'; import PropTypes from 'prop-types'; import React, { forwardRef } from 'react'; -import api, { gql } from 'vj/utils/api'; -import request from 'vj/utils/request'; +import { api, gql, request } from 'vj/utils'; import AutoComplete, { AutoCompleteHandle, AutoCompleteProps } from './AutoComplete'; const ProblemSelectAutoComplete = forwardRef, AutoCompleteProps>((props, ref) => ( diff --git a/packages/ui-default/components/autocomplete/components/UserSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/components/UserSelectAutoComplete.tsx index b887cfcaf..ba03aa466 100644 --- a/packages/ui-default/components/autocomplete/components/UserSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/components/UserSelectAutoComplete.tsx @@ -1,7 +1,7 @@ import type { Udoc } from 'hydrooj/src/interface'; import PropTypes from 'prop-types'; import React, { forwardRef } from 'react'; -import api, { gql } from 'vj/utils/api'; +import { api, gql } from 'vj/utils'; import AutoComplete, { AutoCompleteHandle, AutoCompleteProps } from './AutoComplete'; // eslint-disable-next-line prefer-arrow-callback diff --git a/packages/ui-default/components/browser-update.page.ts b/packages/ui-default/components/browser-update.page.ts index 099245766..6994ad831 100644 --- a/packages/ui-default/components/browser-update.page.ts +++ b/packages/ui-default/components/browser-update.page.ts @@ -1,6 +1,5 @@ import { AutoloadPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import tpl from 'vj/utils/tpl'; +import { i18n, tpl } from 'vj/utils'; import { InfoDialog } from './dialog'; function isSupported() { diff --git a/packages/ui-default/components/calendar/index.js b/packages/ui-default/components/calendar/index.js index ad39257b3..180b52024 100644 --- a/packages/ui-default/components/calendar/index.js +++ b/packages/ui-default/components/calendar/index.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import _ from 'lodash'; import moment from 'moment'; -import tpl from 'vj/utils/tpl'; +import { tpl } from 'vj/utils'; export default class Calendar { constructor(events) { diff --git a/packages/ui-default/components/clipboard.page.ts b/packages/ui-default/components/clipboard.page.ts index 9d19d5c72..18241c638 100644 --- a/packages/ui-default/components/clipboard.page.ts +++ b/packages/ui-default/components/clipboard.page.ts @@ -1,9 +1,7 @@ import Clipboard from 'clipboard'; import Notification from 'vj/components/notification'; import { AutoloadPage } from 'vj/misc/Page'; -import base64 from 'vj/utils/base64'; -import i18n from 'vj/utils/i18n'; -import substitute from 'vj/utils/substitute'; +import { base64, i18n, substitute } from 'vj/utils'; export default new AutoloadPage('clipboard', () => { $('[data-copy]').get().forEach((el) => { diff --git a/packages/ui-default/components/contest/contest.page.ts b/packages/ui-default/components/contest/contest.page.ts index 2b6ac5e78..fe98096a3 100644 --- a/packages/ui-default/components/contest/contest.page.ts +++ b/packages/ui-default/components/contest/contest.page.ts @@ -1,9 +1,7 @@ import $ from 'jquery'; import Notification from 'vj/components/notification'; import { AutoloadPage } from 'vj/misc/Page'; -import delay from 'vj/utils/delay'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; +import { delay, i18n, request } from 'vj/utils'; const contestPage = new AutoloadPage('contestPage', () => { $('[data-contest-code]').on('click', (ev) => { diff --git a/packages/ui-default/components/dialog/DomDialog.ts b/packages/ui-default/components/dialog/DomDialog.ts index 2ffaa2136..f03ef52b5 100644 --- a/packages/ui-default/components/dialog/DomDialog.ts +++ b/packages/ui-default/components/dialog/DomDialog.ts @@ -1,7 +1,7 @@ import $ from 'jquery'; import _ from 'lodash'; import DOMAttachedObject from 'vj/components/DOMAttachedObject'; -import zIndexManager from 'vj/utils/zIndexManager'; +import { zIndexManager } from 'vj/utils'; export interface DialogOptions { classes: string; diff --git a/packages/ui-default/components/dialog/index.ts b/packages/ui-default/components/dialog/index.ts index 5c061d2c4..b16f31d6a 100644 --- a/packages/ui-default/components/dialog/index.ts +++ b/packages/ui-default/components/dialog/index.ts @@ -1,6 +1,5 @@ import $ from 'jquery'; -import i18n from 'vj/utils/i18n'; -import tpl from 'vj/utils/tpl'; +import { i18n, tpl } from 'vj/utils'; import DomDialog, { DialogOptions } from './DomDialog'; export class Dialog { diff --git a/packages/ui-default/components/discussion/CommentBox.js b/packages/ui-default/components/discussion/CommentBox.js index a1e2f1655..3f4f27b26 100644 --- a/packages/ui-default/components/discussion/CommentBox.js +++ b/packages/ui-default/components/discussion/CommentBox.js @@ -3,7 +3,7 @@ import _ from 'lodash'; import DOMAttachedObject from 'vj/components/DOMAttachedObject'; import TextareaHandler from 'vj/components/editor/textareaHandler'; import Notification from 'vj/components/notification'; -import request from 'vj/utils/request'; +import { request } from 'vj/utils'; let initialized = false; const $template = $('.dczcomments__box').eq(0).clone(); diff --git a/packages/ui-default/components/discussion/comments.page.tsx b/packages/ui-default/components/discussion/comments.page.tsx index 41a72373f..35d7e85d8 100644 --- a/packages/ui-default/components/discussion/comments.page.tsx +++ b/packages/ui-default/components/discussion/comments.page.tsx @@ -4,11 +4,9 @@ import $ from 'jquery'; import { ConfirmDialog } from 'vj/components/dialog'; import CommentBox from 'vj/components/discussion/CommentBox'; import { AutoloadPage } from 'vj/misc/Page'; -import delay from 'vj/utils/delay'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; -import { slideDown, slideUp } from 'vj/utils/slide'; -import tpl from 'vj/utils/tpl'; +import { + delay, i18n, request, slideDown, slideUp, tpl, +} from 'vj/utils'; const $replyTemplate = $('.commentbox-container').eq(0).clone(); diff --git a/packages/ui-default/components/discussion/history.page.tsx b/packages/ui-default/components/discussion/history.page.tsx index 8cb56e55b..97dd12508 100644 --- a/packages/ui-default/components/discussion/history.page.tsx +++ b/packages/ui-default/components/discussion/history.page.tsx @@ -5,9 +5,7 @@ import { QueryClient, QueryClientProvider, useQuery } from 'react-query'; import TimeAgo from 'timeago-react'; import { InfoDialog } from 'vj/components/dialog'; import { AutoloadPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; -import tpl from 'vj/utils/tpl'; +import { i18n, request, tpl } from 'vj/utils'; async function historyDialog(payload, time, uid) { const ts = new Date(time).getTime(); diff --git a/packages/ui-default/components/discussion/reaction.page.tsx b/packages/ui-default/components/discussion/reaction.page.tsx index 9f0720709..98bb18278 100644 --- a/packages/ui-default/components/discussion/reaction.page.tsx +++ b/packages/ui-default/components/discussion/reaction.page.tsx @@ -6,7 +6,7 @@ import { chunk } from 'lodash'; import * as React from 'react'; import ReactDOM from 'react-dom/client'; import { AutoloadPage } from 'vj/misc/Page'; -import request from 'vj/utils/request'; +import { request } from 'vj/utils'; function renderReactions(reactions, self, rootEle) { let html = ''; diff --git a/packages/ui-default/components/dropdown/Dropdown.js b/packages/ui-default/components/dropdown/Dropdown.js index 758092076..a75c19110 100644 --- a/packages/ui-default/components/dropdown/Dropdown.js +++ b/packages/ui-default/components/dropdown/Dropdown.js @@ -2,8 +2,7 @@ import _ from 'lodash'; import Drop from 'tether-drop'; import responsiveCutoff from 'vj/breakpoints.json'; import DOMAttachedObject from 'vj/components/DOMAttachedObject'; -import { isBelow } from 'vj/utils/mediaQuery'; -import zIndexManager from 'vj/utils/zIndexManager'; +import { mediaQuery, zIndexManager } from 'vj/utils'; export default class Dropdown extends DOMAttachedObject { static DOMAttachKey = 'vjDropdownInstance'; @@ -12,7 +11,7 @@ export default class Dropdown extends DOMAttachedObject { constructor($dom, options = {}) { if ($dom.attr('data-dropdown-trigger-desktop-only') !== undefined) { - if (isBelow(responsiveCutoff.mobile)) { + if (mediaQuery.isBelow(responsiveCutoff.mobile)) { super(null); return; } diff --git a/packages/ui-default/components/editor/cmeditor.page.ts b/packages/ui-default/components/editor/cmeditor.page.ts index 37114ffc9..6300caaa1 100644 --- a/packages/ui-default/components/editor/cmeditor.page.ts +++ b/packages/ui-default/components/editor/cmeditor.page.ts @@ -1,5 +1,5 @@ import { AutoloadPage } from 'vj/misc/Page'; -import delay from 'vj/utils/delay'; +import { delay } from 'vj/utils'; import CmEditor from '.'; function runSubstitute($container: JQuery) { diff --git a/packages/ui-default/components/editor/index.ts b/packages/ui-default/components/editor/index.ts index a8c6f9ae4..408f6d933 100644 --- a/packages/ui-default/components/editor/index.ts +++ b/packages/ui-default/components/editor/index.ts @@ -3,8 +3,7 @@ import _ from 'lodash'; import { nanoid } from 'nanoid'; import DOMAttachedObject from 'vj/components/DOMAttachedObject'; import Notification from 'vj/components/notification'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; +import { i18n, request } from 'vj/utils'; export const config = { toolbar: [ diff --git a/packages/ui-default/components/form/form.page.ts b/packages/ui-default/components/form/form.page.ts index aa9f8eacc..4859fd1b5 100644 --- a/packages/ui-default/components/form/form.page.ts +++ b/packages/ui-default/components/form/form.page.ts @@ -1,6 +1,6 @@ import $ from 'jquery'; import { AutoloadPage } from 'vj/misc/Page'; -import delay from 'vj/utils/delay'; +import { delay } from 'vj/utils'; const formPage = new AutoloadPage('formPage', () => { $(document).on('vjFormDisableUpdate', 'input, select, textarea', (ev) => { diff --git a/packages/ui-default/components/highlighter/highlighter.page.ts b/packages/ui-default/components/highlighter/highlighter.page.ts index 5bde403ab..b6eee7d66 100644 --- a/packages/ui-default/components/highlighter/highlighter.page.ts +++ b/packages/ui-default/components/highlighter/highlighter.page.ts @@ -1,7 +1,6 @@ import $ from 'jquery'; import { AutoloadPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import tpl from 'vj/utils/tpl'; +import { i18n, tpl } from 'vj/utils'; const highlighterPage = new AutoloadPage('highlighterPage', () => { import('./prismjs').then(({ default: prismjs }) => { diff --git a/packages/ui-default/components/highlighter/prismjs.js b/packages/ui-default/components/highlighter/prismjs.js index d73f8893e..13c1a3d75 100644 --- a/packages/ui-default/components/highlighter/prismjs.js +++ b/packages/ui-default/components/highlighter/prismjs.js @@ -11,7 +11,7 @@ import $ from 'jquery'; import components from 'prismjs/components'; import getLoader from 'prismjs/dependencies'; import Notification from 'vj/components/notification/index'; -import i18n from 'vj/utils/i18n'; +import { i18n } from 'vj/utils'; import languageMeta from './meta'; const files = require.context('prismjs/components/', true, /prism-[a-z0-9-]+\.js/); diff --git a/packages/ui-default/components/hint.ts b/packages/ui-default/components/hint.ts index e8e7159c3..eb71b2c8d 100644 --- a/packages/ui-default/components/hint.ts +++ b/packages/ui-default/components/hint.ts @@ -1,7 +1,6 @@ import $ from 'jquery'; import { InfoDialog } from 'vj/components/dialog'; -import i18n from 'vj/utils/i18n'; -import tpl from 'vj/utils/tpl'; +import { i18n, tpl } from 'vj/utils'; export default function createHint(message: string, element?: any) { if (i18n(message) === message || !element) return; diff --git a/packages/ui-default/components/hitokoto/index.page.js b/packages/ui-default/components/hitokoto/index.page.js index 4ab68d783..8db34005b 100644 --- a/packages/ui-default/components/hitokoto/index.page.js +++ b/packages/ui-default/components/hitokoto/index.page.js @@ -1,8 +1,6 @@ import $ from 'jquery'; import { NamedPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; -import tpl from 'vj/utils/tpl'; +import { i18n, request, tpl } from 'vj/utils'; export default new NamedPage('homepage', () => { function getHitokoto($containers) { diff --git a/packages/ui-default/components/languageselect.tsx b/packages/ui-default/components/languageselect.tsx index 8282881bc..4e7d28648 100644 --- a/packages/ui-default/components/languageselect.tsx +++ b/packages/ui-default/components/languageselect.tsx @@ -1,7 +1,7 @@ import $ from 'jquery'; import React from 'react'; import ReactDOM from 'react-dom/client'; -import i18n from 'vj/utils/i18n'; +import { i18n } from 'vj/utils'; export default function LanguageSelect({ fieldSelector, firstLoadMain, firstLoadSub, availableLangs, mainLangs, diff --git a/packages/ui-default/components/marker/Marker.js b/packages/ui-default/components/marker/Marker.js index 4a4ce3236..00e93c807 100644 --- a/packages/ui-default/components/marker/Marker.js +++ b/packages/ui-default/components/marker/Marker.js @@ -1,8 +1,7 @@ import $ from 'jquery'; import _ from 'lodash'; import Tooltip from 'vj/components/tooltip/Tooltip'; -import delay from 'vj/utils/delay'; -import i18n from 'vj/utils/i18n'; +import { delay, i18n } from 'vj/utils'; const MARKER_ID = `marker_${Math.floor(Math.random() * 0xFFFFFF).toString(16)}`; const MARKER_OFFSET = 20; diff --git a/packages/ui-default/components/marker/MarkerReactive.js b/packages/ui-default/components/marker/MarkerReactive.js index 38022e5c6..92a2272db 100644 --- a/packages/ui-default/components/marker/MarkerReactive.js +++ b/packages/ui-default/components/marker/MarkerReactive.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import _ from 'lodash'; import DOMAttachedObject from 'vj/components/DOMAttachedObject'; -import delay from 'vj/utils/delay'; +import { delay } from 'vj/utils'; import Marker from './Marker'; export default class MarkerReactive extends DOMAttachedObject { diff --git a/packages/ui-default/components/media/media.page.js b/packages/ui-default/components/media/media.page.js index abb1cc2d0..3892db57e 100644 --- a/packages/ui-default/components/media/media.page.js +++ b/packages/ui-default/components/media/media.page.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import { AutoloadPage } from 'vj/misc/Page'; -import request from 'vj/utils/request'; +import { request } from 'vj/utils'; export default new AutoloadPage('media', async () => { async function parseMedia($dom = $(document.body)) { diff --git a/packages/ui-default/components/menu/menu-heading.page.js b/packages/ui-default/components/menu/menu-heading.page.js index 7ac908bfb..bacd1d230 100644 --- a/packages/ui-default/components/menu/menu-heading.page.js +++ b/packages/ui-default/components/menu/menu-heading.page.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import { AutoloadPage } from 'vj/misc/Page'; -import tpl from 'vj/utils/tpl'; +import { tpl } from 'vj/utils'; const menuHeadingPage = new AutoloadPage('menuHeadingPage', null, () => { $('[data-heading-extract-to]').get().forEach((container) => { diff --git a/packages/ui-default/components/menu/menu.page.js b/packages/ui-default/components/menu/menu.page.js index 74c746998..23aef115b 100644 --- a/packages/ui-default/components/menu/menu.page.js +++ b/packages/ui-default/components/menu/menu.page.js @@ -1,7 +1,6 @@ import $ from 'jquery'; import { AutoloadPage } from 'vj/misc/Page'; -import delay from 'vj/utils/delay'; -import { slideDown } from 'vj/utils/slide'; +import { delay, slideDown } from 'vj/utils'; function expandMenu($menu) { slideDown($menu, 500, { opacity: 0 }, { opacity: 1 }); diff --git a/packages/ui-default/components/message/index.page.ts b/packages/ui-default/components/message/index.page.ts index d23d0e106..6914a140e 100644 --- a/packages/ui-default/components/message/index.page.ts +++ b/packages/ui-default/components/message/index.page.ts @@ -4,8 +4,7 @@ import { InfoDialog } from 'vj/components/dialog'; import VjNotification from 'vj/components/notification/index'; import { FLAG_ALERT } from 'vj/constant/message'; import { AutoloadPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import tpl from 'vj/utils/tpl'; +import { i18n, tpl } from 'vj/utils'; const onmessage = (msg) => { console.log('Received message', msg); diff --git a/packages/ui-default/components/messagepad/MessagePadDialogueContentContainer.jsx b/packages/ui-default/components/messagepad/MessagePadDialogueContentContainer.jsx index 3f34d5ff2..4a6f43357 100644 --- a/packages/ui-default/components/messagepad/MessagePadDialogueContentContainer.jsx +++ b/packages/ui-default/components/messagepad/MessagePadDialogueContentContainer.jsx @@ -6,8 +6,7 @@ import moment from 'moment'; import React from 'react'; import { connect } from 'react-redux'; import TimeAgo from 'timeago-react'; -import i18n from 'vj/utils/i18n'; -import { parse as parseMongoId } from 'vj/utils/mongoId'; +import { i18n, mongoId } from 'vj/utils'; import Message from './MessageComponent'; const mapStateToProps = (state) => ({ @@ -70,7 +69,7 @@ export default connect(mapStateToProps)(class MessagePadDialogueContentContainer renderInner() { if (this.props.activeId === null) return []; const sorted = this.props.item.messages - .sort((msg1, msg2) => parseMongoId(msg1._id).timestamp - parseMongoId(msg2._id).timestamp); + .sort((msg1, msg2) => mongoId(msg1._id).timestamp - mongoId(msg2._id).timestamp); return sorted.map((msg) => (
    {this.renderContent(msg)}
    -
    )); diff --git a/packages/ui-default/components/messagepad/MessagePadDialogueListContainer.jsx b/packages/ui-default/components/messagepad/MessagePadDialogueListContainer.jsx index cdf9e9f62..e85732e40 100644 --- a/packages/ui-default/components/messagepad/MessagePadDialogueListContainer.jsx +++ b/packages/ui-default/components/messagepad/MessagePadDialogueListContainer.jsx @@ -4,7 +4,7 @@ import $ from 'jquery'; import _ from 'lodash'; import React from 'react'; import { connect } from 'react-redux'; -import i18n from 'vj/utils/i18n'; +import { i18n } from 'vj/utils'; import ListItem from './DialogueListItemComponent'; const mapStateToProps = (state) => ({ diff --git a/packages/ui-default/components/messagepad/MessagePadInputContainer.jsx b/packages/ui-default/components/messagepad/MessagePadInputContainer.jsx index ff7b6d3f7..eda747021 100644 --- a/packages/ui-default/components/messagepad/MessagePadInputContainer.jsx +++ b/packages/ui-default/components/messagepad/MessagePadInputContainer.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { connect } from 'react-redux'; import Icon from 'vj/components/react/IconComponent'; -import request from 'vj/utils/request'; +import { request } from 'vj/utils'; const mapStateToProps = (state) => ({ activeId: state.activeId, diff --git a/packages/ui-default/components/messagepad/index.jsx b/packages/ui-default/components/messagepad/index.jsx index d62f8975e..73b298b17 100644 --- a/packages/ui-default/components/messagepad/index.jsx +++ b/packages/ui-default/components/messagepad/index.jsx @@ -2,8 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { connect } from 'react-redux'; import Icon from 'vj/components/react/IconComponent'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; +import { i18n, request } from 'vj/utils'; import MessagePadDialogueContent from './MessagePadDialogueContentContainer'; import MessagePadDialogueList from './MessagePadDialogueListContainer'; import MessagePadInput from './MessagePadInputContainer'; diff --git a/packages/ui-default/components/monaco/index.ts b/packages/ui-default/components/monaco/index.ts index d6c243782..fb19fd9a4 100644 --- a/packages/ui-default/components/monaco/index.ts +++ b/packages/ui-default/components/monaco/index.ts @@ -6,8 +6,7 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { IQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput'; import list from 'monaco-themes/themes/themelist.json'; import { nanoid } from 'nanoid'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; +import { i18n, request } from 'vj/utils'; export default monaco; export const customOptions: monaco.editor.IStandaloneDiffEditorConstructionOptions = JSON.parse(localStorage.getItem('editor.config') || '{}'); diff --git a/packages/ui-default/components/monaco/languages/markdown.ts b/packages/ui-default/components/monaco/languages/markdown.ts index 3676257a8..f6f6901f8 100644 --- a/packages/ui-default/components/monaco/languages/markdown.ts +++ b/packages/ui-default/components/monaco/languages/markdown.ts @@ -1,7 +1,7 @@ import keyword from 'emojis-keywords'; import list from 'emojis-list'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; -import api, { gql } from 'vj/utils/api'; +import { api, gql } from 'vj/utils'; const qqEmojies = [ 'weixiao', diff --git a/packages/ui-default/components/monaco/loader.ts b/packages/ui-default/components/monaco/loader.ts index 8e84e35ef..b2ca5662f 100644 --- a/packages/ui-default/components/monaco/loader.ts +++ b/packages/ui-default/components/monaco/loader.ts @@ -1,4 +1,4 @@ -import loadExternalModule from 'vj/utils/loadModule'; +import { loadExternalModule } from 'vj/utils'; let loaded; const loaders = { diff --git a/packages/ui-default/components/navigation/navigation.page.js b/packages/ui-default/components/navigation/navigation.page.js index 567efb3c0..77c0c0df8 100644 --- a/packages/ui-default/components/navigation/navigation.page.js +++ b/packages/ui-default/components/navigation/navigation.page.js @@ -3,9 +3,7 @@ import Slideout from 'slideout'; import Notification from 'vj/components/notification'; import selectUser from 'vj/components/selectUser'; import { AutoloadPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; -import tpl from 'vj/utils/tpl'; +import { i18n, request, tpl } from 'vj/utils'; function handleNavLogoutClick(ev) { const $logoutLink = $(ev.currentTarget); diff --git a/packages/ui-default/components/notification/index.js b/packages/ui-default/components/notification/index.js index ed2315717..3709dbbaf 100644 --- a/packages/ui-default/components/notification/index.js +++ b/packages/ui-default/components/notification/index.js @@ -1,7 +1,6 @@ import { Intent, Position, Toaster } from '@blueprintjs/core'; import $ from 'jquery'; -import tpl from 'vj/utils/tpl'; -import zIndexManager from 'vj/utils/zIndexManager'; +import { tpl, zIndexManager } from 'vj/utils/base'; const ToasterContainer = document.createElement('div'); ToasterContainer.style.position = 'fixed'; diff --git a/packages/ui-default/components/notification/notification.page.js b/packages/ui-default/components/notification/notification.page.js index 0060a503e..eb750d4ab 100644 --- a/packages/ui-default/components/notification/notification.page.js +++ b/packages/ui-default/components/notification/notification.page.js @@ -1,6 +1,6 @@ import Notification from 'vj/components/notification/index'; import { AutoloadPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; +import { i18n } from 'vj/utils'; export default new AutoloadPage('notificationPage', (pagename) => { const message = i18n(`Hint::Page::${pagename}`); diff --git a/packages/ui-default/components/preview/preview.page.ts b/packages/ui-default/components/preview/preview.page.ts index 37c728352..6857a503a 100644 --- a/packages/ui-default/components/preview/preview.page.ts +++ b/packages/ui-default/components/preview/preview.page.ts @@ -4,10 +4,9 @@ import { nanoid } from 'nanoid'; import { ActionDialog, InfoDialog } from 'vj/components/dialog/index'; import Notification from 'vj/components/notification'; import { AutoloadPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import pjax from 'vj/utils/pjax'; -import request from 'vj/utils/request'; -import tpl from 'vj/utils/tpl'; +import { + i18n, pjax, request, tpl, +} from 'vj/utils'; async function startEdit(filename, value, fileCategory = 'file') { const { default: Editor } = await import('vj/components/editor/index'); diff --git a/packages/ui-default/components/problem/create.page.js b/packages/ui-default/components/problem/create.page.js index a95415e94..92e7aaa03 100644 --- a/packages/ui-default/components/problem/create.page.js +++ b/packages/ui-default/components/problem/create.page.js @@ -1,8 +1,7 @@ import $ from 'jquery'; import { InfoDialog } from 'vj/components/dialog'; import { NamedPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import tpl from 'vj/utils/tpl'; +import { i18n, tpl } from 'vj/utils'; export default new NamedPage(['problem_create', 'problem_edit'], () => { $('input[name="pid"]').on('blur', () => { diff --git a/packages/ui-default/components/problemconfig/BasicForm.tsx b/packages/ui-default/components/problemconfig/BasicForm.tsx index c5dbd9939..53d2d4edf 100644 --- a/packages/ui-default/components/problemconfig/BasicForm.tsx +++ b/packages/ui-default/components/problemconfig/BasicForm.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import i18n from 'vj/utils/i18n'; +import { i18n } from 'vj/utils'; import FileSelectAutoComplete from '../autocomplete/components/FileSelectAutoComplete'; import type { RootState } from './reducer/index'; diff --git a/packages/ui-default/components/problemconfig/ProblemConfigForm.tsx b/packages/ui-default/components/problemconfig/ProblemConfigForm.tsx index 33280e561..316bd699f 100644 --- a/packages/ui-default/components/problemconfig/ProblemConfigForm.tsx +++ b/packages/ui-default/components/problemconfig/ProblemConfigForm.tsx @@ -3,7 +3,7 @@ import { } from '@blueprintjs/core'; import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import i18n from 'vj/utils/i18n'; +import { i18n } from 'vj/utils'; import CustomSelectAutoComplete from '../autocomplete/components/CustomSelectAutoComplete'; import FileSelectAutoComplete from '../autocomplete/components/FileSelectAutoComplete'; import { FormItem } from './BasicForm'; diff --git a/packages/ui-default/components/problemconfig/ProblemType.tsx b/packages/ui-default/components/problemconfig/ProblemType.tsx index 8d2c653b9..a89ccbf90 100644 --- a/packages/ui-default/components/problemconfig/ProblemType.tsx +++ b/packages/ui-default/components/problemconfig/ProblemType.tsx @@ -3,7 +3,7 @@ import { } from '@blueprintjs/core'; import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import i18n from 'vj/utils/i18n'; +import { i18n } from 'vj/utils'; import { FormItem, ManagedSelect, SingleFileSelect } from './BasicForm'; import type { RootState } from './reducer/index'; diff --git a/packages/ui-default/components/problemconfig/SubtaskTable.tsx b/packages/ui-default/components/problemconfig/SubtaskTable.tsx index 7ee7b635d..1a7d49d39 100644 --- a/packages/ui-default/components/problemconfig/SubtaskTable.tsx +++ b/packages/ui-default/components/problemconfig/SubtaskTable.tsx @@ -4,7 +4,7 @@ import type { SubtaskConfig } from 'hydrooj/src/interface'; import { isEqual, pick } from 'lodash'; import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import i18n from 'vj/utils/i18n'; +import { i18n } from 'vj/utils'; import CustomSelectAutoComplete from '../autocomplete/components/CustomSelectAutoComplete'; import { FormItem } from './BasicForm'; import { RootState } from './reducer'; diff --git a/packages/ui-default/components/problemconfig/TestCasesTable.tsx b/packages/ui-default/components/problemconfig/TestCasesTable.tsx index ddd407977..253ad6a7c 100644 --- a/packages/ui-default/components/problemconfig/TestCasesTable.tsx +++ b/packages/ui-default/components/problemconfig/TestCasesTable.tsx @@ -4,7 +4,7 @@ import type { SubtaskConfig } from 'hydrooj/src/interface'; import { isEqual } from 'lodash'; import React, { useEffect, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import i18n from 'vj/utils/i18n'; +import { i18n } from 'vj/utils'; import FileSelectAutoComplete from '../autocomplete/components/FileSelectAutoComplete'; import type { RootState } from './reducer/index'; diff --git a/packages/ui-default/components/rotator/index.js b/packages/ui-default/components/rotator/index.js index 9fa1a7e57..912e610cf 100644 --- a/packages/ui-default/components/rotator/index.js +++ b/packages/ui-default/components/rotator/index.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import _ from 'lodash'; import DOMAttachedObject from 'vj/components/DOMAttachedObject'; -import delay from 'vj/utils/delay'; +import { delay } from 'vj/utils'; const ANIMATION_DURATION = 4000; diff --git a/packages/ui-default/components/scratchpad/ScratchpadPretestContainer.jsx b/packages/ui-default/components/scratchpad/ScratchpadPretestContainer.jsx index b2ef89cf3..dc36b6855 100644 --- a/packages/ui-default/components/scratchpad/ScratchpadPretestContainer.jsx +++ b/packages/ui-default/components/scratchpad/ScratchpadPretestContainer.jsx @@ -2,7 +2,7 @@ import AnsiUp from 'ansi_up'; import React from 'react'; import { connect } from 'react-redux'; import Icon from 'vj/components/react/IconComponent'; -import i18n from 'vj/utils/i18n'; +import { i18n } from 'vj/utils'; import DataInput from './DataInputComponent'; import Panel from './PanelComponent'; diff --git a/packages/ui-default/components/scratchpad/ScratchpadRecordsContainer.jsx b/packages/ui-default/components/scratchpad/ScratchpadRecordsContainer.jsx index 886c6441e..1f21cbfbe 100644 --- a/packages/ui-default/components/scratchpad/ScratchpadRecordsContainer.jsx +++ b/packages/ui-default/components/scratchpad/ScratchpadRecordsContainer.jsx @@ -2,7 +2,7 @@ import classNames from 'classnames'; import React from 'react'; import { connect } from 'react-redux'; import Icon from 'vj/components/react/IconComponent'; -import i18n from 'vj/utils/i18n'; +import { i18n } from 'vj/utils'; import Panel from './PanelComponent'; import ScratchpadRecordsRow from './ScratchpadRecordsRowContainer'; diff --git a/packages/ui-default/components/scratchpad/ScratchpadRecordsRowContainer.jsx b/packages/ui-default/components/scratchpad/ScratchpadRecordsRowContainer.jsx index af5525dcb..425f969d1 100644 --- a/packages/ui-default/components/scratchpad/ScratchpadRecordsRowContainer.jsx +++ b/packages/ui-default/components/scratchpad/ScratchpadRecordsRowContainer.jsx @@ -5,10 +5,9 @@ import React from 'react'; import { connect } from 'react-redux'; import TimeAgo from 'timeago-react'; import * as recordEnum from 'vj/constant/record'; -import emulateAnchorClick from 'vj/utils/emulateAnchorClick'; -import i18n from 'vj/utils/i18n'; -import { parse as parseMongoId } from 'vj/utils/mongoId'; -import substitute from 'vj/utils/substitute'; +import { + emulateAnchorClick, i18n, mongoId, substitute, +} from 'vj/utils'; const shouldShowDetail = (data) => recordEnum.STATUS_SCRATCHPAD_SHOW_DETAIL_FLAGS[data.status]; @@ -58,7 +57,7 @@ export default connect(mapStateToProps, null, mergeProps)(class ScratchpadRecord render() { const { data } = this.props; - const submitAt = parseMongoId(data._id).timestamp * 1000; + const submitAt = mongoId(data._id).timestamp * 1000; // Is pretest return data.contest?.toString() === '000000000000000000000000' ? null : ( this.handleRowClick(ev, data._id)}> diff --git a/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx b/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx index e774f1155..98085c15c 100644 --- a/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx +++ b/packages/ui-default/components/scratchpad/ScratchpadToolbarContainer.jsx @@ -4,9 +4,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { connect } from 'react-redux'; import Icon from 'vj/components/react/IconComponent'; -import getAvailableLangs from 'vj/utils/availableLangs'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; +import { getAvailableLangs, i18n, request } from 'vj/utils'; import Toolbar, { ToolbarButtonComponent as ToolbarButton, ToolbarItemComponent as ToolbarItem, diff --git a/packages/ui-default/components/scratchpad/reducers/ui.ts b/packages/ui-default/components/scratchpad/reducers/ui.ts index b4943a077..ecd2c8bd4 100644 --- a/packages/ui-default/components/scratchpad/reducers/ui.ts +++ b/packages/ui-default/components/scratchpad/reducers/ui.ts @@ -1,5 +1,5 @@ import Notification from 'vj/components/notification'; -import i18n from 'vj/utils/i18n'; +import { i18n } from 'vj/utils'; export default function reducer(state = { main: { diff --git a/packages/ui-default/components/selectUser.ts b/packages/ui-default/components/selectUser.ts index d6e29a163..3aa05a3b1 100644 --- a/packages/ui-default/components/selectUser.ts +++ b/packages/ui-default/components/selectUser.ts @@ -1,7 +1,6 @@ import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoComplete'; import { ActionDialog } from 'vj/components/dialog'; -import i18n from 'vj/utils/i18n'; -import tpl from 'vj/utils/tpl'; +import { i18n, tpl } from 'vj/utils'; import createHint from './hint'; let hintInserted = false; diff --git a/packages/ui-default/components/signin/signInDialog.page.js b/packages/ui-default/components/signin/signInDialog.page.js index 36ddbdc5f..911591fc9 100644 --- a/packages/ui-default/components/signin/signInDialog.page.js +++ b/packages/ui-default/components/signin/signInDialog.page.js @@ -3,9 +3,7 @@ import responsiveCutoff from 'vj/breakpoints.json'; import DomDialog from 'vj/components/dialog/DomDialog'; import { InfoDialog } from 'vj/components/dialog/index'; import { AutoloadPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import { isBelow } from 'vj/utils/mediaQuery'; -import tpl from 'vj/utils/tpl'; +import { i18n, mediaQuery, tpl } from 'vj/utils'; const signinDialogPage = new AutoloadPage('signinDialogPage', null, () => { const signInDialog = DomDialog.getOrConstruct($('.dialog--signin'), { @@ -17,7 +15,7 @@ const signinDialogPage = new AutoloadPage('signinDialogPage', null, () => { if ($('[name="nav_login"]').length > 0) { // nav $('[name="nav_login"]').on('click', (ev) => { - if (isBelow(responsiveCutoff.mobile)) return; + if (mediaQuery.isBelow(responsiveCutoff.mobile)) return; if (ev.shiftKey || ev.metaKey || ev.ctrlKey) return; signInDialog.show(); ev.preventDefault(); @@ -32,7 +30,7 @@ const signinDialogPage = new AutoloadPage('signinDialogPage', null, () => { } window.showSignInDialog = () => { - if (isBelow(responsiveCutoff.mobile)) { + if (mediaQuery.isBelow(responsiveCutoff.mobile)) { if ($('[name="nav_login"]').length > 0) { window.location.href = $('[name="nav_login"]').attr('href'); return; diff --git a/packages/ui-default/components/star/star.page.js b/packages/ui-default/components/star/star.page.js index fbb426cd3..5fbd64405 100644 --- a/packages/ui-default/components/star/star.page.js +++ b/packages/ui-default/components/star/star.page.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import { AutoloadPage } from 'vj/misc/Page'; -import request from 'vj/utils/request'; +import { request } from 'vj/utils'; function setStarButtonState($starButton, star) { if (star) { diff --git a/packages/ui-default/components/time/time.page.js b/packages/ui-default/components/time/time.page.js index 47b44e92f..dcac7329f 100644 --- a/packages/ui-default/components/time/time.page.js +++ b/packages/ui-default/components/time/time.page.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import * as timeago from 'timeago.js'; import { AutoloadPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; +import { i18n } from 'vj/utils'; try { const locales = require.context('timeago.js/lib/lang', false, /\.js$/); diff --git a/packages/ui-default/components/upload.ts b/packages/ui-default/components/upload.ts index cbc4fc1a0..83131f0ea 100644 --- a/packages/ui-default/components/upload.ts +++ b/packages/ui-default/components/upload.ts @@ -1,9 +1,8 @@ import { Dialog } from 'vj/components/dialog/index'; import Notification from 'vj/components/notification'; -import delay from 'vj/utils/delay'; -import i18n from 'vj/utils/i18n'; -import pjax from 'vj/utils/pjax'; -import request from 'vj/utils/request'; +import { + delay, i18n, pjax, request, +} from 'vj/utils'; function onBeforeUnload(e) { e.returnValue = ''; diff --git a/packages/ui-default/components/vote/vote.page.js b/packages/ui-default/components/vote/vote.page.js index ddda0ca7a..012eeaa98 100644 --- a/packages/ui-default/components/vote/vote.page.js +++ b/packages/ui-default/components/vote/vote.page.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import Rotator from 'vj/components/rotator'; import { AutoloadPage } from 'vj/misc/Page'; -import request from 'vj/utils/request'; +import { request } from 'vj/utils'; function setVoteState($container, value, status) { const $num = $container.find('.vote-number'); diff --git a/packages/ui-default/components/zipDownloader/index.js b/packages/ui-default/components/zipDownloader/index.ts similarity index 93% rename from packages/ui-default/components/zipDownloader/index.js rename to packages/ui-default/components/zipDownloader/index.ts index e75ab9d31..0b7a0a19f 100644 --- a/packages/ui-default/components/zipDownloader/index.js +++ b/packages/ui-default/components/zipDownloader/index.ts @@ -2,11 +2,9 @@ import { dump } from 'js-yaml'; import PQueue from 'p-queue'; import streamsaver from 'streamsaver'; import Notification from 'vj/components/notification'; -import api, { gql } from 'vj/utils/api'; -import i18n from 'vj/utils/i18n'; -import pipeStream from 'vj/utils/pipeStream'; -import request from 'vj/utils/request'; -import { createZipStream } from 'vj/utils/zip'; +import { + api, createZipStream, gql, i18n, pipeStream, request, +} from 'vj/utils'; let isBeforeUnloadTriggeredByLibrary = !window.isSecureContext; function onBeforeUnload(e) { @@ -29,8 +27,8 @@ export default async function download(filename, targets) { await waitForWritableStream; const fileStream = streamsaver.createWriteStream(filename); const queue = new PQueue({ concurrency: 5 }); - const abortCallbackReceiver = {}; - function stopDownload() { abortCallbackReceiver.abort(); } + const abortCallbackReceiver: any = {}; + function stopDownload() { abortCallbackReceiver.abort?.(); } let i = 0; async function downloadFile(target) { try { diff --git a/packages/ui-default/entry.js b/packages/ui-default/entry.js index 60ec942ba..6c05947f4 100644 --- a/packages/ui-default/entry.js +++ b/packages/ui-default/entry.js @@ -46,10 +46,11 @@ document.addEventListener('DOMContentLoaded', async () => { // eslint-disable-next-line camelcase try { __webpack_public_path__ = UiContext.cdn_prefix; } catch (e) { } - const [data] = await Promise.all([ - (await fetch(`/constant/${UiContext.constantVersion}`, { cache: 'force-cache' })).json(), - await import('./modules'), + const [data, HydroExports] = await Promise.all([ + fetch(`/constant/${UiContext.constantVersion}`, { cache: 'force-cache' }).then((r) => r.json()), + import('./api'), ]); + Object.assign(window, { HydroExports }); eval(data[0]); // eslint-disable-line no-eval data.shift(); window.Hydro.preload = data; diff --git a/packages/ui-default/hydro.ts b/packages/ui-default/hydro.ts index 2dceb8c2a..ca0eddd87 100644 --- a/packages/ui-default/hydro.ts +++ b/packages/ui-default/hydro.ts @@ -4,16 +4,15 @@ import $ from 'jquery'; import _ from 'lodash'; import Notification from 'vj/components/notification'; import PageLoader from 'vj/misc/PageLoader'; -import delay from 'vj/utils/delay'; +import { delay } from 'vj/utils'; declare global { interface Window { UserContext: any; UiContext: any; Hydro: any; - // eslint-disable-next-line camelcase - node_modules: any; externalModules: Record; + captureException?: (e: Error) => void; } } diff --git a/packages/ui-default/index.ts b/packages/ui-default/index.ts index b14d4d9f7..30ba83b48 100644 --- a/packages/ui-default/index.ts +++ b/packages/ui-default/index.ts @@ -51,7 +51,22 @@ export async function buildUI() { target: [ 'chrome60', ], - plugins: global.Hydro.ui.esbuildPlugins || [], + plugins: [ + ...(global.Hydro.ui.esbuildPlugins || []), + { + name: 'federation', + setup(b) { + b.onResolve({ filter: /^@hydrooj\/ui-default/ }, () => ({ + path: 'api', + namespace: 'ui-default', + })); + b.onLoad({ filter: /.*/, namespace: 'ui-default' }, () => ({ + contents: 'module.exports = window.HydroExports;', + loader: 'tsx', + })); + }, + }, + ], minify: !process.env.DEV, }); if (build.errors.length) console.error(build.errors); diff --git a/packages/ui-default/modules.js b/packages/ui-default/modules.js deleted file mode 100644 index cd77ea05e..000000000 --- a/packages/ui-default/modules.js +++ /dev/null @@ -1,37 +0,0 @@ -import './utils/delay'; -import './utils/emulateAnchorClick'; -import './utils/i18n'; -import './utils/loadReactRedux'; -import './utils/mediaQuery'; -import './utils/mongoId'; -import './utils/pipeStream'; -import './utils/pjax'; -import './utils/request'; -import './utils/slide'; -import './utils/substitute'; -import './utils/tpl'; -import './utils/zIndexManager'; -import './utils/zip'; -import './components/autocomplete'; -import './components/dialog'; -import './components/notification'; -import './components/monaco/loader'; - -import $ from 'jquery'; -import _ from 'lodash'; -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import * as redux from 'react-redux'; - -const modules = { - _, $, React, redux, ReactDOM, -}; -export default async function load(name) { - if (modules[name]) return modules[name]; - if (name === 'echarts') return import('echarts'); - if (name === 'moment') return import('moment'); - throw new Error(`Module ${name} not found`); -} -window.node_modules = { ...modules, load }; -window.$ = $; -window.jQuery = $; diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 25b2c210a..495eb2191 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -4,6 +4,7 @@ "author": "undefined ", "license": "AGPL-3.0", "main": "index.ts", + "types": "api.ts", "repository": "https://github.com/hydro-dev/Hydro.git", "preferUnplugged": true, "scripts": { @@ -29,6 +30,7 @@ "@types/markdown-it": "^12.2.3", "@types/pickadate": "^3.5.32", "@types/qrcode": "^1.5.0", + "@types/react": "^18.0.26", "@types/react-dom": "^18.0.9", "@types/redux-logger": "^3.0.9", "@types/serviceworker": "^0.0.57", diff --git a/packages/ui-default/pages/api.page.tsx b/packages/ui-default/pages/api.page.tsx index 9b7355a52..e1144dc9c 100644 --- a/packages/ui-default/pages/api.page.tsx +++ b/packages/ui-default/pages/api.page.tsx @@ -2,7 +2,7 @@ import { pick } from 'lodash'; import React from 'react'; import { createRoot } from 'react-dom/client'; import { NamedPage } from 'vj/misc/Page'; -import request from 'vj/utils/request'; +import { request } from 'vj/utils'; const defaultQuery = `\ query Example( diff --git a/packages/ui-default/pages/contest.page.ts b/packages/ui-default/pages/contest.page.ts index 08910339b..5a3cbaf96 100644 --- a/packages/ui-default/pages/contest.page.ts +++ b/packages/ui-default/pages/contest.page.ts @@ -1,7 +1,7 @@ import { formatSeconds } from '@hydrooj/utils/lib/common'; import NProgress from 'nprogress'; import { NamedPage } from 'vj/misc/Page'; -import tpl from 'vj/utils/tpl'; +import { tpl } from 'vj/utils'; const contestTimer = $(tpl``); contestTimer.appendTo(document.body); diff --git a/packages/ui-default/pages/contest_edit.page.ts b/packages/ui-default/pages/contest_edit.page.ts index 0efd92faf..89b37de4a 100644 --- a/packages/ui-default/pages/contest_edit.page.ts +++ b/packages/ui-default/pages/contest_edit.page.ts @@ -2,9 +2,7 @@ import $ from 'jquery'; import ProblemSelectAutoComplete from 'vj/components/autocomplete/ProblemSelectAutoComplete'; import { ConfirmDialog } from 'vj/components/dialog'; import { NamedPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; -import tpl from 'vj/utils/tpl'; +import { i18n, request, tpl } from 'vj/utils'; const page = new NamedPage(['contest_edit', 'contest_create', 'homework_create', 'homework_edit'], (pagename) => { ProblemSelectAutoComplete.getOrConstruct($('[name="pids"]'), { multi: true, clearDefaultValue: false }); diff --git a/packages/ui-default/pages/domain_group.page.ts b/packages/ui-default/pages/domain_group.page.ts index 9f58cc271..0269d00a5 100644 --- a/packages/ui-default/pages/domain_group.page.ts +++ b/packages/ui-default/pages/domain_group.page.ts @@ -4,10 +4,9 @@ import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoCom import { ActionDialog, ConfirmDialog } from 'vj/components/dialog'; import Notification from 'vj/components/notification'; import { NamedPage } from 'vj/misc/Page'; -import api, { gql } from 'vj/utils/api'; -import delay from 'vj/utils/delay'; -import i18n from 'vj/utils/i18n'; -import tpl from 'vj/utils/tpl'; +import { + api, delay, gql, i18n, tpl, +} from 'vj/utils'; function update(name: string, uids: number[]) { return api(gql` diff --git a/packages/ui-default/pages/domain_role.page.js b/packages/ui-default/pages/domain_role.page.js index 2f2a7d6de..27798bae7 100644 --- a/packages/ui-default/pages/domain_role.page.js +++ b/packages/ui-default/pages/domain_role.page.js @@ -3,10 +3,9 @@ import _ from 'lodash'; import { ActionDialog, ConfirmDialog } from 'vj/components/dialog'; import Notification from 'vj/components/notification'; import { NamedPage } from 'vj/misc/Page'; -import delay from 'vj/utils/delay'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; -import tpl from 'vj/utils/tpl'; +import { + delay, i18n, request, tpl, +} from 'vj/utils'; const page = new NamedPage('domain_role', () => { const createRoleDialog = new ActionDialog({ diff --git a/packages/ui-default/pages/domain_user.page.js b/packages/ui-default/pages/domain_user.page.js index d4dad3d87..e19aa1291 100644 --- a/packages/ui-default/pages/domain_user.page.js +++ b/packages/ui-default/pages/domain_user.page.js @@ -4,10 +4,9 @@ import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoCom import { ActionDialog, ConfirmDialog } from 'vj/components/dialog'; import Notification from 'vj/components/notification'; import { NamedPage } from 'vj/misc/Page'; -import delay from 'vj/utils/delay'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; -import tpl from 'vj/utils/tpl'; +import { + delay, i18n, request, tpl, +} from 'vj/utils'; const page = new NamedPage('domain_user', () => { const addUserSelector = UserSelectAutoComplete.getOrConstruct($('.dialog__body--add-user [name="user"]')); diff --git a/packages/ui-default/pages/files.page.js b/packages/ui-default/pages/files.page.js index dc466b961..dd16ab053 100644 --- a/packages/ui-default/pages/files.page.js +++ b/packages/ui-default/pages/files.page.js @@ -4,10 +4,9 @@ import { ConfirmDialog } from 'vj/components/dialog/index'; import Notification from 'vj/components/notification'; import uploadFiles from 'vj/components/upload'; import { NamedPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import pjax from 'vj/utils/pjax'; -import request from 'vj/utils/request'; -import tpl from 'vj/utils/tpl'; +import { + i18n, pjax, request, tpl, +} from 'vj/utils'; function ensureAndGetSelectedFiles() { const files = _.map( diff --git a/packages/ui-default/pages/home_messages.page.jsx b/packages/ui-default/pages/home_messages.page.jsx index b14edd17e..04cd6e3df 100644 --- a/packages/ui-default/pages/home_messages.page.jsx +++ b/packages/ui-default/pages/home_messages.page.jsx @@ -4,8 +4,7 @@ import { createRoot } from 'react-dom/client'; import VjNotification from 'vj/components/notification'; import selectUser from 'vj/components/selectUser'; import { NamedPage } from 'vj/misc/Page'; -import api, { gql } from 'vj/utils/api'; -import loadReactRedux from 'vj/utils/loadReactRedux'; +import { api, gql, loadReactRedux } from 'vj/utils'; const page = new NamedPage('home_messages', () => { let reduxStore; diff --git a/packages/ui-default/pages/home_preference.page.jsx b/packages/ui-default/pages/home_preference.page.jsx index 0fb57776d..d9a6f410d 100644 --- a/packages/ui-default/pages/home_preference.page.jsx +++ b/packages/ui-default/pages/home_preference.page.jsx @@ -1,10 +1,9 @@ import $ from 'jquery'; import { renderLanguageSelect } from 'vj/components/languageselect'; import { NamedPage } from 'vj/misc/Page'; -import getAvailableLangs from 'vj/utils/availableLangs'; -import delay from 'vj/utils/delay'; -import i18n from 'vj/utils/i18n'; -import tpl from 'vj/utils/tpl'; +import { + delay, getAvailableLangs, i18n, tpl, +} from 'vj/utils'; async function initCodeLangHelper() { const $el = $(tpl`
    `); diff --git a/packages/ui-default/pages/home_security.page.ts b/packages/ui-default/pages/home_security.page.ts index 8d649f1dd..8b83b61d4 100644 --- a/packages/ui-default/pages/home_security.page.ts +++ b/packages/ui-default/pages/home_security.page.ts @@ -3,10 +3,9 @@ import QRCode from 'qrcode'; import { ActionDialog } from 'vj/components/dialog'; import Notification from 'vj/components/notification'; import { NamedPage } from 'vj/misc/Page'; -import api, { gql } from 'vj/utils/api'; -import delay from 'vj/utils/delay'; -import i18n from 'vj/utils/i18n'; -import tpl from 'vj/utils/tpl'; +import { + api, delay, gql, i18n, tpl, +} from 'vj/utils'; export default new NamedPage('home_security', () => { $(document).on('click', '[name="tfa_enable"]', async () => { diff --git a/packages/ui-default/pages/homework_main.page.js b/packages/ui-default/pages/homework_main.page.js index 1ee7d7636..6399a2800 100644 --- a/packages/ui-default/pages/homework_main.page.js +++ b/packages/ui-default/pages/homework_main.page.js @@ -1,7 +1,6 @@ import $ from 'jquery'; import { NamedPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import { parse as parseMongoId } from 'vj/utils/mongoId'; +import { i18n, mongoId } from 'vj/utils'; const page = new NamedPage('homework_main', async () => { // Homework Calendar @@ -13,7 +12,7 @@ const page = new NamedPage('homework_main', async () => { title: doc.title, maskFrom: doc.penaltySince ? doc.penaltySince : null, maskTitle: i18n('Time Extension'), - colorIndex: parseMongoId(doc._id).timestamp % 12, + colorIndex: mongoId(doc._id).timestamp % 12, link: doc.url, })); const calendar = new Calendar(events); diff --git a/packages/ui-default/pages/manage_script.page.js b/packages/ui-default/pages/manage_script.page.js index a36ef089b..935229ab8 100644 --- a/packages/ui-default/pages/manage_script.page.js +++ b/packages/ui-default/pages/manage_script.page.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import { ActionDialog } from 'vj/components/dialog'; import Notification from 'vj/components/notification'; import { NamedPage } from 'vj/misc/Page'; -import request from 'vj/utils/request'; +import { request } from 'vj/utils'; const page = new NamedPage('manage_script', () => { const runScriptDialog = new ActionDialog({ diff --git a/packages/ui-default/pages/manage_user_import.page.js b/packages/ui-default/pages/manage_user_import.page.js index 38148f520..70f3ff8bc 100644 --- a/packages/ui-default/pages/manage_user_import.page.js +++ b/packages/ui-default/pages/manage_user_import.page.js @@ -1,9 +1,7 @@ import $ from 'jquery'; import Notification from 'vj/components/notification'; import { NamedPage } from 'vj/misc/Page'; -import delay from 'vj/utils/delay'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; +import { delay, i18n, request } from 'vj/utils'; const page = new NamedPage('manage_user_import', () => { async function post(draft) { diff --git a/packages/ui-default/pages/manage_user_priv.page.js b/packages/ui-default/pages/manage_user_priv.page.js index b64a65507..864c097ce 100644 --- a/packages/ui-default/pages/manage_user_priv.page.js +++ b/packages/ui-default/pages/manage_user_priv.page.js @@ -3,9 +3,7 @@ import { ActionDialog } from 'vj/components/dialog'; import Notification from 'vj/components/notification'; import selectUser from 'vj/components/selectUser'; import { NamedPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import pjax from 'vj/utils/pjax'; -import request from 'vj/utils/request'; +import { i18n, pjax, request } from 'vj/utils'; const page = new NamedPage('manage_user_priv', () => { const setPrivDialog = new ActionDialog({ diff --git a/packages/ui-default/pages/problem_config.page.tsx b/packages/ui-default/pages/problem_config.page.tsx index 5abea345f..525adc5c0 100644 --- a/packages/ui-default/pages/problem_config.page.tsx +++ b/packages/ui-default/pages/problem_config.page.tsx @@ -10,10 +10,9 @@ import { configYamlFormat } from 'vj/components/problemconfig/ProblemConfigEdito import uploadFiles from 'vj/components/upload'; import download from 'vj/components/zipDownloader'; import { NamedPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import loadReactRedux from 'vj/utils/loadReactRedux'; -import request from 'vj/utils/request'; -import tpl from 'vj/utils/tpl'; +import { + i18n, loadReactRedux, request, tpl, +} from 'vj/utils'; const page = new NamedPage('problem_config', () => { let reduxStore; @@ -72,7 +71,7 @@ const page = new NamedPage('problem_config', () => { async function handleClickDownloadAll() { const files = reduxStore.getState().testdata.map((i) => i.name); const { links, pdoc } = await request.post('./files', { operation: 'get_links', files, type: 'testdata' }); - const targets = []; + const targets: { filename: string, url: string }[] = []; for (const filename of Object.keys(links)) targets.push({ filename, url: links[filename] }); await download(`${pdoc.docId} ${pdoc.title}.zip`, targets); } @@ -103,7 +102,7 @@ const page = new NamedPage('problem_config', () => { store.dispatch({ type: 'CONFIG_LOAD', - payload: request.get(), + payload: request.get(''), }); const unsubscribe = store.subscribe(() => { // TODO set yaml schema @@ -124,7 +123,7 @@ const page = new NamedPage('problem_config', () => { subtasks: normalizeSubtasks(subtasks, (i) => i, state.config.time, state.config.memory, true), }); }); - createRoot($('#ProblemConfig').get(0)).render( + createRoot(document.getElementById('ProblemConfig')!).render(
    diff --git a/packages/ui-default/pages/problem_detail.page.jsx b/packages/ui-default/pages/problem_detail.page.jsx index 5c02838d1..1ad2cb1ea 100644 --- a/packages/ui-default/pages/problem_detail.page.jsx +++ b/packages/ui-default/pages/problem_detail.page.jsx @@ -6,12 +6,9 @@ import { createRoot } from 'react-dom/client'; import Notification from 'vj/components/notification'; import { downloadProblemSet } from 'vj/components/zipDownloader'; import { NamedPage } from 'vj/misc/Page'; -import delay from 'vj/utils/delay'; -import i18n from 'vj/utils/i18n'; -import loadReactRedux from 'vj/utils/loadReactRedux'; -import pjax from 'vj/utils/pjax'; -import request from 'vj/utils/request'; -import tpl from 'vj/utils/tpl'; +import { + delay, i18n, loadReactRedux, pjax, request, tpl, +} from 'vj/utils'; class ProblemPageExtender { constructor() { diff --git a/packages/ui-default/pages/problem_edit.page.js b/packages/ui-default/pages/problem_edit.page.js index 3200f2622..7ba896771 100644 --- a/packages/ui-default/pages/problem_edit.page.js +++ b/packages/ui-default/pages/problem_edit.page.js @@ -8,10 +8,9 @@ import Notification from 'vj/components/notification'; import uploadFiles from 'vj/components/upload'; import download from 'vj/components/zipDownloader'; import { NamedPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; -import { slideDown, slideUp } from 'vj/utils/slide'; -import tpl from 'vj/utils/tpl'; +import { + i18n, request, slideDown, slideUp, tpl, +} from 'vj/utils'; const categories = {}; const dirtyCategories = []; diff --git a/packages/ui-default/pages/problem_files.page.js b/packages/ui-default/pages/problem_files.page.js index 89130fd22..d8613dad9 100644 --- a/packages/ui-default/pages/problem_files.page.js +++ b/packages/ui-default/pages/problem_files.page.js @@ -7,10 +7,9 @@ import { previewFile } from 'vj/components/preview/preview.page'; import uploadFiles from 'vj/components/upload'; import download from 'vj/components/zipDownloader'; import { NamedPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import pjax from 'vj/utils/pjax'; -import request from 'vj/utils/request'; -import tpl from 'vj/utils/tpl'; +import { + i18n, pjax, request, tpl, +} from 'vj/utils'; async function downloadProblemFilesAsArchive(type, files) { const { links, pdoc } = await request.post('', { operation: 'get_links', files, type }); diff --git a/packages/ui-default/pages/problem_main.page.js b/packages/ui-default/pages/problem_main.page.js index 27bf0bbba..4e75046ab 100644 --- a/packages/ui-default/pages/problem_main.page.js +++ b/packages/ui-default/pages/problem_main.page.js @@ -6,11 +6,9 @@ import createHint from 'vj/components/hint'; import Notification from 'vj/components/notification'; import { downloadProblemSet } from 'vj/components/zipDownloader'; import { NamedPage } from 'vj/misc/Page'; -import delay from 'vj/utils/delay'; -import i18n from 'vj/utils/i18n'; -import pjax from 'vj/utils/pjax'; -import request from 'vj/utils/request'; -import tpl from 'vj/utils/tpl'; +import { + delay, i18n, pjax, request, tpl, +} from 'vj/utils'; const categories = {}; const dirtyCategories = []; diff --git a/packages/ui-default/pages/problem_sidebar.page.ts b/packages/ui-default/pages/problem_sidebar.page.ts index c5f2e2a33..35021af3a 100644 --- a/packages/ui-default/pages/problem_sidebar.page.ts +++ b/packages/ui-default/pages/problem_sidebar.page.ts @@ -2,9 +2,7 @@ import $ from 'jquery'; import { ActionDialog, ConfirmDialog } from 'vj/components/dialog'; import Notification from 'vj/components/notification'; import { NamedPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; -import tpl from 'vj/utils/tpl'; +import { i18n, request, tpl } from 'vj/utils'; const page = new NamedPage([ 'problem_create', 'problem_edit', 'problem_solution', 'problem_submit', diff --git a/packages/ui-default/pages/problem_submit.page.tsx b/packages/ui-default/pages/problem_submit.page.tsx index 8e9ce8215..a0068fb45 100644 --- a/packages/ui-default/pages/problem_submit.page.tsx +++ b/packages/ui-default/pages/problem_submit.page.tsx @@ -1,7 +1,7 @@ import $ from 'jquery'; import { renderLanguageSelect } from 'vj/components/languageselect'; import { NamedPage } from 'vj/misc/Page'; -import getAvailableLangs from 'vj/utils/availableLangs'; +import { getAvailableLangs } from 'vj/utils'; const page = new NamedPage(['problem_submit', 'contest_detail_problem_submit', 'homework_detail_problem_submit'], async () => { const { config } = UiContext.pdoc; diff --git a/packages/ui-default/pages/record_main.page.ts b/packages/ui-default/pages/record_main.page.ts index 635d8f2d2..ea7fc0d5b 100644 --- a/packages/ui-default/pages/record_main.page.ts +++ b/packages/ui-default/pages/record_main.page.ts @@ -2,8 +2,7 @@ import $ from 'jquery'; import ProblemSelectAutoComplete from 'vj/components/autocomplete/ProblemSelectAutoComplete'; import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoComplete'; import { NamedPage } from 'vj/misc/Page'; -import getAvailableLangs from 'vj/utils/availableLangs'; -import tpl from 'vj/utils/tpl'; +import { getAvailableLangs, tpl } from 'vj/utils'; const page = new NamedPage('record_main', async () => { const [{ default: WebSocket }, { DiffDOM }] = await Promise.all([ diff --git a/packages/ui-default/pages/setting.page.tsx b/packages/ui-default/pages/setting.page.tsx index b53d1ac6a..73a9c285f 100644 --- a/packages/ui-default/pages/setting.page.tsx +++ b/packages/ui-default/pages/setting.page.tsx @@ -2,7 +2,7 @@ import yaml from 'js-yaml'; import Schema from 'schemastery'; import Notification from 'vj/components/notification'; import { NamedPage } from 'vj/misc/Page'; -import request from 'vj/utils/request'; +import { request } from 'vj/utils'; const page = new NamedPage('manage_config', async () => { const schema = new Schema(UiContext.schema); diff --git a/packages/ui-default/pages/training_edit.page.ts b/packages/ui-default/pages/training_edit.page.ts index 777d5bf11..8610a7534 100644 --- a/packages/ui-default/pages/training_edit.page.ts +++ b/packages/ui-default/pages/training_edit.page.ts @@ -1,9 +1,7 @@ import $ from 'jquery'; import { ConfirmDialog } from 'vj/components/dialog'; import { NamedPage } from 'vj/misc/Page'; -import i18n from 'vj/utils/i18n'; -import request from 'vj/utils/request'; -import tpl from 'vj/utils/tpl'; +import { i18n, request, tpl } from 'vj/utils'; export default new NamedPage('training_edit', () => { let confirmed = false; diff --git a/packages/ui-default/pages/user_login.page.ts b/packages/ui-default/pages/user_login.page.ts index 20e1b064e..f16ef4417 100644 --- a/packages/ui-default/pages/user_login.page.ts +++ b/packages/ui-default/pages/user_login.page.ts @@ -1,6 +1,6 @@ import $ from 'jquery'; import { AutoloadPage } from 'vj/misc/Page'; -import api, { gql } from 'vj/utils/api'; +import { api, gql } from 'vj/utils'; export default new AutoloadPage('user_login', (pagename) => { (pagename === 'user_login' ? $(document) : $('.dialog--signin__main')).on('blur', '[name="uname"]', async () => { diff --git a/packages/ui-default/types.ts b/packages/ui-default/types.ts deleted file mode 100644 index c6859648d..000000000 --- a/packages/ui-default/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -declare global { - interface Window { - node_modules: any; - LANGS: Record; - } - - let UserContext: Record; - let UiContext: Record; -} - -export { }; diff --git a/packages/ui-default/utils/api.ts b/packages/ui-default/utils/api.ts deleted file mode 100644 index cebf72b03..000000000 --- a/packages/ui-default/utils/api.ts +++ /dev/null @@ -1,26 +0,0 @@ -import request from './request'; - -export default async (q: string, path: string[] = []) => { - let query = q.trim(); - if (!query.startsWith('query')) query = `query{${query}}`; - const res = await request.post(`/d/${UiContext.domainId}/api`, { query }); - if (res.errors) throw new Error(res.errors[0].message); - let cursor = res; - for (const p of path) { - cursor = cursor[p]; - if (!cursor) return undefined; - } - return cursor; -}; - -export const gql = ( - pieces: TemplateStringsArray, - ...templates: (string | number | string[] | number[])[] -) => { - let res = ''; - for (let i = 0; i < pieces.length; i++) { - res += pieces[i]; - if (templates[i]) res += JSON.stringify(templates[i]); - } - return res; -}; diff --git a/packages/ui-default/utils/availableLangs.ts b/packages/ui-default/utils/availableLangs.ts deleted file mode 100644 index bff341010..000000000 --- a/packages/ui-default/utils/availableLangs.ts +++ /dev/null @@ -1,12 +0,0 @@ -const prefixes = new Set(Object.keys(window.LANGS).filter((i) => i.includes('.')).map((i) => i.split('.')[0])); - -export default function getAvailableLangs(langsList?: string[]) { - const Langs = {}; - for (const key in window.LANGS) { - if (prefixes.has(key)) continue; - if (langsList && langsList.length && langsList.join('') && !langsList.includes(key)) continue; - else if (window.LANGS[key].hidden && !langsList?.includes(key)) continue; - Langs[key] = window.LANGS[key]; - } - return Langs; -} diff --git a/packages/ui-default/utils/base.ts b/packages/ui-default/utils/base.ts new file mode 100644 index 000000000..f0545b74f --- /dev/null +++ b/packages/ui-default/utils/base.ts @@ -0,0 +1,153 @@ +import $ from 'jquery'; +import _ from 'lodash'; + +export function substitute(str: string, obj: any) { + return str.replace(/\{([^{}]+)\}/g, (match, key) => { + if (obj[key] !== undefined) return obj[key].toString(); + return `{${key}}`; + }); +} + +export function i18n(str: string, ...params: any[]) { + if (!str) return ''; + return substitute((window as any).LOCALES?.[str] || str, params); +} + +export function delay(ms) { + return new Promise((resolve) => { setTimeout(resolve, ms); }); +} + +type Substitution = string | number | { templateRaw: true, html: string }; + +export function tpl(pieces: TemplateStringsArray, ...substitutions: Substitution[]) { + let result = pieces[0]; + for (let i = 0; i < substitutions.length; ++i) { + const subst = substitutions[i]; + let substHtml: string; + if (typeof subst === 'object' && subst.templateRaw) { + substHtml = subst.html; + } else substHtml = _.escape(String(subst)); + result += substHtml + pieces[i + 1]; + } + return result; +} + +tpl.typoMsg = function (msg: string, raw = false) { + if (raw) return `

    ${msg}

    `; + const lines = msg.trim().split('\n'); + return `
    ${lines.map((i) => `

    ${_.escape(i)}

    `).join('\n')}
    `; +}; + +export function rawHtml(html: string) { + return { + templateRaw: true, + html, + }; +} + +let zIndexCurrent = 1000; + +export const zIndexManager = { + getCurrent() { + return zIndexCurrent; + }, + getNext() { + return ++zIndexCurrent; + }, +}; + +export const request = { + async ajax(options: Record) { + return new Promise((resolve, reject) => { + $ + .ajax({ + dataType: 'json', + headers: { + Accept: 'application/json', + }, + ...options, + }) + .fail((jqXHR, textStatus, errorThrown: any) => { + if (textStatus === 'abort') { + const err = new Error(i18n('Aborted')) as any; + err.aborted = true; + reject(err); + } else if (jqXHR.readyState === 0) { + reject(new Error(i18n('Network error'))); + } else if (typeof jqXHR.responseJSON === 'object' && jqXHR.responseJSON.error) { + const { error } = jqXHR.responseJSON; + if (error.params) { + const message = i18n(error.message, ...error.params); + const err = new Error(message === error.message && error.params.length + ? `${error.message}: ${error.params.join(' ')}` + : message) as any; + err.rawMessage = error.message; + err.params = error.params; + reject(err); + } else reject(new Error(jqXHR.responseJSON.error.message)); + } else if (errorThrown instanceof Error) { + reject(errorThrown); + } else { + reject(new Error(textStatus)); + } + }) + .done(resolve); + }); + }, + + postFile(url: string, form: FormData, options: any = {}) { + return this.ajax({ + url, + data: form, + processData: false, + contentType: false, + type: 'POST', + dataType: undefined, + ...options, + }); + }, + + post(url: string, dataOrForm: JQueryStatic | Node | string | Record = {}, options: any = {}) { + let postData; + // @ts-ignore + if (dataOrForm instanceof $ && dataOrForm.is('form')) { + // $form + postData = (dataOrForm as any).serialize(); + } else if (dataOrForm instanceof Node && $(dataOrForm).is('form')) { + // form + postData = $(dataOrForm).serialize(); + } else if (typeof dataOrForm === 'string') { + // foo=bar&box=boz + postData = dataOrForm; + } else { + // {foo: 'bar'} + postData = JSON.stringify(dataOrForm); + options.contentType = 'application/json'; + } + return request.ajax({ + url, + method: 'post', + data: postData, + ...options, + }); + }, + + get(url: string, qs: Record = {}, options: Record = {}) { + return request.ajax({ + url, + data: qs, + method: 'get', + ...options, + }); + }, +}; + +Object.assign(window.Hydro.utils, { + i18n, + rawHtml, + substitute, + request, + tpl, + delay, + zIndexManager, +}); diff --git a/packages/ui-default/utils/delay.js b/packages/ui-default/utils/delay.js deleted file mode 100644 index 5d4474a34..000000000 --- a/packages/ui-default/utils/delay.js +++ /dev/null @@ -1,5 +0,0 @@ -export default function delay(ms) { - return new Promise((resolve) => { setTimeout(resolve, ms); }); -} - -window.Hydro.utils.delay = delay; diff --git a/packages/ui-default/utils/emulateAnchorClick.js b/packages/ui-default/utils/emulateAnchorClick.js deleted file mode 100644 index d40d94ca5..000000000 --- a/packages/ui-default/utils/emulateAnchorClick.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @param {Event} ev - * @param {String} targetUrl - * @param {Boolean} alwaysOpenInNewWindow - */ -export default function emulateAnchorClick(ev, targetUrl, alwaysOpenInNewWindow = false) { - let openInNewWindow; - if (alwaysOpenInNewWindow) { - openInNewWindow = true; - } else { - openInNewWindow = (ev.ctrlKey || ev.shiftKey || ev.metaKey); - } - if (openInNewWindow) { - window.open(targetUrl); - } else { - window.location.href = targetUrl; - } -} - -window.Hydro.utils.emulateAnchorClick = emulateAnchorClick; diff --git a/packages/ui-default/utils/i18n.js b/packages/ui-default/utils/i18n.js deleted file mode 100644 index 7cb5aab10..000000000 --- a/packages/ui-default/utils/i18n.js +++ /dev/null @@ -1,8 +0,0 @@ -import substitute from './substitute'; - -export default function i18n(str, ...params) { - if (!str) return ''; - return substitute(window.LOCALES?.[str] || str, params); -} - -window.Hydro.utils.i18n = i18n; diff --git a/packages/ui-default/utils/index.ts b/packages/ui-default/utils/index.ts new file mode 100644 index 000000000..4a9b997fc --- /dev/null +++ b/packages/ui-default/utils/index.ts @@ -0,0 +1,118 @@ +import 'streamsaver/examples/zip-stream'; + +import { request } from './base'; + +export async function api(q: string, path: string[] = []) { + let query = q.trim(); + if (!query.startsWith('query')) query = `query{${query}}`; + const res = await request.post(`/d/${UiContext.domainId}/api`, { query }); + if (res.errors) throw new Error(res.errors[0].message); + let cursor = res; + for (const p of path) { + cursor = cursor[p]; + if (!cursor) return undefined; + } + return cursor; +} + +export const gql = ( + pieces: TemplateStringsArray, + ...templates: (string | number | string[] | number[])[] +) => { + let res = ''; + for (let i = 0; i < pieces.length; i++) { + res += pieces[i]; + if (templates[i]) res += JSON.stringify(templates[i]); + } + return res; +}; + +export function getAvailableLangs(langsList?: string[]) { + const prefixes = new Set(Object.keys(window.LANGS).filter((i) => i.includes('.')).map((i) => i.split('.')[0])); + const Langs = {}; + for (const key in window.LANGS) { + if (prefixes.has(key)) continue; + if (langsList && langsList.length && langsList.join('') && !langsList.includes(key)) continue; + else if (window.LANGS[key].hidden && !langsList?.includes(key)) continue; + Langs[key] = window.LANGS[key]; + } + return Langs; +} + +export const createZipStream = (window as any).ZIP; + +export function createZipBlob(underlyingSource) { + return new Response(createZipStream(underlyingSource)).blob(); +} + +export async function pipeStream(read, write, abort) { + if (window.WritableStream && read.pipeTo) { + const abortController = new AbortController(); + if (abort) abort.abort = abortController.abort.bind(abortController); + await read.pipeTo(write, abortController); + } else { + const writer = write.getWriter(); + if (abort) abort.abort = writer.abort.bind(writer); + const reader = read.getReader(); + // eslint-disable-next-line no-constant-condition + while (1) { + const readResult = await reader.read(); + if (readResult.done) { + writer.close(); + break; + } else writer.write(readResult.value); + } + } +} + +// https://github.com/andrasq/node-mongoid-js/blob/master/mongoid.js +/* eslint-disable */ +export function mongoId(idstring: string) { + if (typeof idstring !== 'string') idstring = String(idstring); + return { + timestamp: parseInt(idstring.slice(0, 0 + 8), 16), + machineid: parseInt(idstring.slice(8, 8 + 6), 16), + pid: parseInt(idstring.slice(14, 14 + 4), 16), + sequence: parseInt(idstring.slice(18, 18 + 6), 16), + }; +} +/* eslint-enable */ + +const loaded = {}; + +export async function loadExternalModule(target: string) { + if (loaded[target]) return loaded[target]; + const ele = document.createElement('script'); + ele.src = target; + await new Promise((resolve, reject) => { + ele.onload = resolve; + ele.onerror = reject; + document.head.appendChild(ele); + }); + loaded[target] = window.exports; + return loaded[target]; +} + +export function emulateAnchorClick(ev: KeyboardEvent, targetUrl: string, alwaysOpenInNewWindow = false) { + let openInNewWindow; + if (alwaysOpenInNewWindow) openInNewWindow = true; + else openInNewWindow = (ev.ctrlKey || ev.shiftKey || ev.metaKey); + if (openInNewWindow) window.open(targetUrl); + else window.location.href = targetUrl; +} + +export { default as pjax } from './pjax'; +export { default as base64 } from './base64'; +export { default as loadReactRedux } from './loadReactRedux'; +export * as mediaQuery from './mediaQuery'; +export * from './slide'; +export * from './base'; + +const zip = { createZipStream, createZipBlob }; +Object.assign(window.Hydro.utils, { + zip, + pipeStream, + mongoId, + loadExternalModule, + emulateAnchorClick, +}); diff --git a/packages/ui-default/utils/loadModule.ts b/packages/ui-default/utils/loadModule.ts deleted file mode 100644 index 39707137e..000000000 --- a/packages/ui-default/utils/loadModule.ts +++ /dev/null @@ -1,14 +0,0 @@ -const loaded = {}; - -export default async function loadExternalModule(target: string) { - if (loaded[target]) return loaded[target]; - const ele = document.createElement('script'); - ele.src = target; - await new Promise((resolve, reject) => { - ele.onload = resolve; - ele.onerror = reject; - document.head.appendChild(ele); - }); - loaded[target] = window.exports; - return loaded[target]; -} diff --git a/packages/ui-default/utils/loadReactRedux.ts b/packages/ui-default/utils/loadReactRedux.ts index 467234576..a0233158d 100644 --- a/packages/ui-default/utils/loadReactRedux.ts +++ b/packages/ui-default/utils/loadReactRedux.ts @@ -1,4 +1,4 @@ -import { Action, AnyAction, Reducer } from 'redux'; +import type { Action, AnyAction, Reducer } from 'redux'; export default async function loadReactRedux(storeReducer: Reducer) { const React = await import('react'); diff --git a/packages/ui-default/utils/mongoId.js b/packages/ui-default/utils/mongoId.js deleted file mode 100644 index 508cfd5e5..000000000 --- a/packages/ui-default/utils/mongoId.js +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable */ -// https://github.com/andrasq/node-mongoid-js/blob/master/mongoid.js - -export function parse(idstring) { - if (typeof idstring !== 'string') { - idstring = String(idstring); - } - return { - timestamp: parseInt(idstring.slice( 0, 0+8), 16), - machineid: parseInt(idstring.slice( 8, 8+6), 16), - pid: parseInt(idstring.slice(14, 14+4), 16), - sequence: parseInt(idstring.slice(18, 18+6), 16), - }; -} - -window.Hydro.utils.mongoId = parse; diff --git a/packages/ui-default/utils/pipeStream.js b/packages/ui-default/utils/pipeStream.js deleted file mode 100644 index 8c75698bf..000000000 --- a/packages/ui-default/utils/pipeStream.js +++ /dev/null @@ -1,21 +0,0 @@ -export default async function pipeStream(read, write, abort) { - if (window.WritableStream && read.pipeTo) { - const abortController = new AbortController(); - if (abort) abort.abort = abortController.abort.bind(abortController); - await read.pipeTo(write, abortController); - } else { - const writer = write.getWriter(); - if (abort) abort.abort = writer.abort.bind(writer); - const reader = read.getReader(); - // eslint-disable-next-line no-constant-condition - while (1) { - const readResult = await reader.read(); - if (readResult.done) { - writer.close(); - break; - } else writer.write(readResult.value); - } - } -} - -window.Hydro.utils.pipeStream = pipeStream; diff --git a/packages/ui-default/utils/pjax.js b/packages/ui-default/utils/pjax.js index 01a094444..7a0f8f13b 100644 --- a/packages/ui-default/utils/pjax.js +++ b/packages/ui-default/utils/pjax.js @@ -5,7 +5,7 @@ import $ from 'jquery'; import { nanoid } from 'nanoid'; import NProgress from 'nprogress'; import Notification from 'vj/components/notification'; -import request from 'vj/utils/request'; +import { request } from './base'; const pjax = {}; diff --git a/packages/ui-default/utils/request.js b/packages/ui-default/utils/request.js deleted file mode 100644 index 4335928f6..000000000 --- a/packages/ui-default/utils/request.js +++ /dev/null @@ -1,106 +0,0 @@ -import $ from 'jquery'; -import i18n from './i18n'; - -const request = { - /** - * @param {object} options - */ - async ajax(options) { - return new Promise((resolve, reject) => { - $ - .ajax({ - dataType: 'json', - headers: { - Accept: 'application/json', - }, - ...options, - }) - .fail((jqXHR, textStatus, errorThrown) => { - if (textStatus === 'abort') { - const err = new Error(i18n('Aborted')); - err.aborted = true; - reject(err); - } else if (jqXHR.readyState === 0) { - reject(new Error(i18n('Network error'))); - } else if (typeof jqXHR.responseJSON === 'object' && jqXHR.responseJSON.error) { - const { error } = jqXHR.responseJSON; - if (error.params) { - const message = i18n(error.message, ...error.params); - const err = new Error(message === error.message && error.params.length ? `${error.message}: ${error.params.join(' ')}` : message); - err.rawMessage = error.message; - err.params = error.params; - reject(err); - } else reject(new Error(jqXHR.responseJSON.error.message)); - } else if (errorThrown instanceof Error) { - reject(errorThrown); - } else { - reject(new Error(textStatus)); - } - }) - .done(resolve); - }); - }, - - /** - * @param {string} url - * @param {FormData} FormData - * @param {object} options - */ - postFile(url, form, options = {}) { - return this.ajax({ - url, - data: form, - processData: false, - contentType: false, - type: 'POST', - dataType: undefined, - ...options, - }); - }, - - /** - * @param {string} url - * @param {JQueryStatic | Node | string | object} dataOrForm - * @param {object} options - */ - post(url, dataOrForm = {}, options = {}) { - let postData; - if (dataOrForm instanceof jQuery && dataOrForm.is('form')) { - // $form - postData = dataOrForm.serialize(); - } else if (dataOrForm instanceof Node && $(dataOrForm).is('form')) { - // form - postData = $(dataOrForm).serialize(); - } else if (typeof dataOrForm === 'string') { - // foo=bar&box=boz - postData = dataOrForm; - } else { - // {foo: 'bar'} - postData = JSON.stringify(dataOrForm); - options.contentType = 'application/json'; - } - return request.ajax({ - url, - method: 'post', - data: postData, - ...options, - }); - }, - - /** - * @param {string} url - * @param {object} qs - * @param {object} options - */ - get(url, qs = {}, options = {}) { - return request.ajax({ - url, - data: qs, - method: 'get', - ...options, - }); - }, -}; - -export default request; -window.Hydro.utils.request = request; diff --git a/packages/ui-default/utils/slide.js b/packages/ui-default/utils/slide.ts similarity index 100% rename from packages/ui-default/utils/slide.js rename to packages/ui-default/utils/slide.ts diff --git a/packages/ui-default/utils/substitute.js b/packages/ui-default/utils/substitute.js deleted file mode 100644 index 753c228fa..000000000 --- a/packages/ui-default/utils/substitute.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @param {string} str - * @param {any} obj - * @returns {string} - */ -export default function substitute(str, obj) { - return str.replace(/\{([^{}]+)\}/g, (match, key) => { - if (obj[key] !== undefined) return obj[key].toString(); - return `{${key}}`; - }); -} - -window.Hydro.utils.substitute = substitute; diff --git a/packages/ui-default/utils/tpl.ts b/packages/ui-default/utils/tpl.ts deleted file mode 100644 index 60703210b..000000000 --- a/packages/ui-default/utils/tpl.ts +++ /dev/null @@ -1,31 +0,0 @@ -import _ from 'lodash'; - -type Substitution = string | number | { templateRaw: true, html: string }; - -export default function tpl(pieces: TemplateStringsArray, ...substitutions: Substitution[]) { - let result = pieces[0]; - for (let i = 0; i < substitutions.length; ++i) { - const subst = substitutions[i]; - let substHtml: string; - if (typeof subst === 'object' && subst.templateRaw) { - substHtml = subst.html; - } else substHtml = _.escape(String(subst)); - result += substHtml + pieces[i + 1]; - } - return result; -} - -tpl.typoMsg = function (msg: string, raw = false) { - if (raw) return `

    ${msg}

    `; - const lines = msg.trim().split('\n'); - return `
    ${lines.map((i) => `

    ${_.escape(i)}

    `).join('\n')}
    `; -}; - -export function rawHtml(html: string) { - return { - templateRaw: true, - html, - }; -} - -window.Hydro.utils.tpl = tpl; diff --git a/packages/ui-default/utils/zIndexManager.js b/packages/ui-default/utils/zIndexManager.js deleted file mode 100644 index 344271382..000000000 --- a/packages/ui-default/utils/zIndexManager.js +++ /dev/null @@ -1,13 +0,0 @@ -let zIndexCurrent = 1000; - -const manager = { - getCurrent() { - return zIndexCurrent; - }, - getNext() { - return ++zIndexCurrent; - }, -}; - -export default manager; -window.Hydro.utils.zIndexManager = manager; diff --git a/packages/ui-default/utils/zip.js b/packages/ui-default/utils/zip.js deleted file mode 100644 index 266006114..000000000 --- a/packages/ui-default/utils/zip.js +++ /dev/null @@ -1,9 +0,0 @@ -import 'streamsaver/examples/zip-stream'; - -export const createZipStream = window.ZIP; - -export async function createZipBlob(underlyingSource) { - return new Response(createZipStream(underlyingSource)).blob(); -} - -window.Hydro.utils.zip = { createZipStream, createZipBlob }; From 80e3abf27b2faa42b0a22c380f67fd23dcb998cd Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 12 Dec 2022 02:43:10 +0800 Subject: [PATCH 15/16] upgrade deps --- .gitignore | 1 + package.json | 38 +++++++++++++++------- packages/hydrojudge/package.json | 8 ++--- packages/hydrojudge/src/cases.ts | 8 ++--- packages/hydrojudge/src/config.ts | 2 +- packages/hydrojudge/src/judge/objective.ts | 2 +- packages/hydrojudge/src/task.ts | 5 ++- packages/hydrooj/package.json | 26 +++++++-------- packages/ui-default/package.json | 37 ++++++--------------- packages/utils/lib/utils.ts | 2 ++ packages/utils/package.json | 4 +-- packages/vjudge/package.json | 2 +- 12 files changed, 65 insertions(+), 70 deletions(-) diff --git a/.gitignore b/.gitignore index 05c6af5ae..ef9c4f15d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ globalConfig.json /config.yaml /config.json yarn.lock +pnpm-lock.yaml tsconfig.json tsconfig.*.json diff --git a/package.json b/package.json index 29352ffed..b07ac0474 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ "lint:ci": "eslint packages plugins --ext ts", "lint:ui": "yarn workspace @hydrooj/ui-default lint --ext .js,.ts,.jsx,.tsx . --fix", "lint:ui:ci": "yarn workspace @hydrooj/ui-default lint --ext .js,.ts,.jsx,.tsx .", - "debug": "node --trace-warnings --async-stack-traces --trace-deprecation node_modules/hydrooj/bin/hydrooj --debug --template", - "start": "node_modules/.bin/hydrooj", + "debug": "node --trace-warnings --async-stack-traces --trace-deprecation packages/hydrooj/bin/hydrooj --debug --template", + "start": "packages/hydrooj/bin/hydrooj", "postinstall": "node build/prepare.js" }, "version": "1.0.0", @@ -34,14 +34,18 @@ "@types/node": "^18.11.9", "@types/semver": "^7.3.13", "@types/supertest": "^2.0.12", - "@typescript-eslint/eslint-plugin": "^5.44.0", - "@typescript-eslint/parser": "^5.44.0", + "@typescript-eslint/eslint-plugin": "^5.46.0", + "@typescript-eslint/parser": "^5.46.0", "autocannon": "^7.10.0", "cac": "^6.7.14", + "clean-webpack-plugin": "^4.0.0", + "copy-webpack-plugin": "^11.0.0", "cross-env": "^7.0.3", "cross-spawn": "^7.0.3", + "css-loader": "^6.7.2", "esbuild": "0.15.2", - "eslint": "^8.28.0", + "esbuild-loader": "^2.20.0", + "eslint": "^8.29.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-import-resolver-typescript": "^3.5.2", @@ -51,17 +55,29 @@ "eslint-plugin-react": "^7.31.11", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-simple-import-sort": "8.0.0", + "friendly-errors-webpack-plugin": "^1.7.0", "fs-extra": "^10.1.0", "globby": "13.1.2", "latest-version": "7.0.0", - "mocha": "^10.1.0", + "mini-css-extract-plugin": "^2.7.2", + "mocha": "^10.2.0", + "monaco-editor-webpack-plugin": "^7.0.1", "mongodb": "^3.7.3", - "mongodb-memory-server": "^8.10.1", + "mongodb-memory-server": "^8.10.2", "nyc": "^15.1.0", "ora": "^6.1.2", + "postcss-loader": "^7.0.1", "semver": "^7.3.8", - "supertest": "^6.3.1", - "typescript": "4.9.3" - }, - "packageManager": "yarn@3.2.3" + "style-loader": "^3.3.1", + "stylus": "^0.59.0", + "stylus-loader": "^7.1.0", + "supertest": "^6.3.3", + "ts-loader": "^9.4.2", + "typescript": "4.9.4", + "webpack": "^5.75.0", + "webpack-bundle-analyzer": "^4.7.0", + "webpack-dev-server": "^4.11.1", + "webpack-manifest-plugin": "^5.0.0", + "webpackbar": "^5.0.2" + } } diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 73a25df01..3c6e562f9 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -9,21 +9,17 @@ "@hydrooj/utils": "workspace:*", "cac": "^6.7.14", "fs-extra": "^10.1.0", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", "mongodb": "^3.7.3", "p-queue": "^7.3.0", - "schemastery": "^3.5.4", + "schemastery": "^3.6.1", "shell-quote": "^1.7.4", - "superagent": "^8.0.4", + "superagent": "^8.0.6", "ws": "^8.11.0" }, "preferUnplugged": true, "license": "AGPL-3.0-or-later", "devDependencies": { "@types/fs-extra": "^9.0.13", - "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.14.190", "@types/shell-quote": "^1.7.1", "@types/ws": "^8.5.3" } diff --git a/packages/hydrojudge/src/cases.ts b/packages/hydrojudge/src/cases.ts index b8ec6c1d2..b9e614eb5 100644 --- a/packages/hydrojudge/src/cases.ts +++ b/packages/hydrojudge/src/cases.ts @@ -1,11 +1,9 @@ /* eslint-disable no-await-in-loop */ import path from 'path'; import fs from 'fs-extra'; -import yaml from 'js-yaml'; -import { max, sum } from 'lodash'; import readYamlCases, { convertIniConfig } from '@hydrooj/utils/lib/cases'; import { normalizeSubtasks, readSubtasksFromFiles } from '@hydrooj/utils/lib/common'; -import { changeErrorType } from '@hydrooj/utils/lib/utils'; +import { changeErrorType, yaml } from '@hydrooj/utils/lib/utils'; import { ProblemConfigFile } from 'hydrooj'; import { getConfig } from './config'; import { FormatError, SystemError } from './error'; @@ -16,11 +14,11 @@ function isValidConfig(config) { if (config.count > (getConfig('testcases_max') || 100)) { throw new FormatError('Too many testcases. Cancelled.'); } - const time = sum(config.subtasks.map((subtask) => sum(subtask.cases.map((c) => c.time)))); + const time = Math.sum(config.subtasks.map((subtask) => Math.sum(subtask.cases.map((c) => c.time)))); if (time > (getConfig('total_time_limit') || 60) * 1000) { throw new FormatError('Total time limit longer than {0}s. Cancelled.', [+getConfig('total_time_limit') || 60]); } - const memMax = max(config.subtasks.map((subtask) => max(subtask.cases.map((c) => c.memory)))); + const memMax = Math.max(config.subtasks.map((subtask) => Math.max(subtask.cases.map((c) => c.memory)))); if (memMax > parseMemoryMB(getConfig('memoryMax'))) throw new FormatError('Memory limit larger than memory_max'); if (!['default', 'strict'].includes(config.checker_type || 'default') && !config.checker) { throw new FormatError('You did not specify a checker.'); diff --git a/packages/hydrojudge/src/config.ts b/packages/hydrojudge/src/config.ts index 7a98490d4..7f9dfe37c 100644 --- a/packages/hydrojudge/src/config.ts +++ b/packages/hydrojudge/src/config.ts @@ -2,8 +2,8 @@ import os from 'os'; import path from 'path'; import cac from 'cac'; import fs from 'fs-extra'; -import yaml from 'js-yaml'; import Schema from 'schemastery'; +import { yaml } from '@hydrooj/utils'; const argv = cac().parse(); diff --git a/packages/hydrojudge/src/judge/objective.ts b/packages/hydrojudge/src/judge/objective.ts index 6ede41ff8..6c5ba07ca 100644 --- a/packages/hydrojudge/src/judge/objective.ts +++ b/packages/hydrojudge/src/judge/objective.ts @@ -1,6 +1,6 @@ import assert from 'assert'; import { readFile } from 'fs-extra'; -import yaml from 'js-yaml'; +import { yaml } from '@hydrooj/utils'; import { STATUS } from '@hydrooj/utils/lib/status'; import { FormatError } from '../error'; import { Context } from './interface'; diff --git a/packages/hydrojudge/src/task.ts b/packages/hydrojudge/src/task.ts index 25dc15dfd..6b59526da 100644 --- a/packages/hydrojudge/src/task.ts +++ b/packages/hydrojudge/src/task.ts @@ -1,6 +1,5 @@ import path from 'path'; import fs from 'fs-extra'; -import { noop } from 'lodash'; import { LangConfig } from '@hydrooj/utils/lib/lang'; import { STATUS } from '@hydrooj/utils/lib/status'; import type { @@ -40,7 +39,7 @@ export class JudgeTask { folder: string; config: ParsedConfig; meta: JudgeMeta; - files?:Record; + files?: Record; next: (data: Partial) => void; end: (data: Partial) => void; env: Record; @@ -103,7 +102,7 @@ export class JudgeTask { } finally { Lock.release(`${host}/${this.source}/${this.rid}`); // eslint-disable-next-line no-await-in-loop - for (const clean of this.clean) await clean()?.catch(noop); + for (const clean of this.clean) await clean()?.catch(() => null); tmpfs.umount(this.tmpdir); fs.removeSync(this.tmpdir); } diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 2ad1b6424..c16ec109d 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -12,12 +12,12 @@ }, "preferUnplugged": true, "dependencies": { - "@aws-sdk/client-s3": "^3.216.0", - "@aws-sdk/lib-storage": "^3.216.0", - "@aws-sdk/middleware-endpoint": "^3.215.0", - "@aws-sdk/s3-presigned-post": "^3.216.0", - "@aws-sdk/s3-request-presigner": "^3.216.0", - "@graphql-tools/schema": "^9.0.10", + "@aws-sdk/client-s3": "^3.226.0", + "@aws-sdk/lib-storage": "^3.226.0", + "@aws-sdk/middleware-endpoint": "^3.226.0", + "@aws-sdk/s3-presigned-post": "^3.226.0", + "@aws-sdk/s3-request-presigner": "^3.226.0", + "@graphql-tools/schema": "^9.0.12", "@hydrooj/utils": "workspace:*", "adm-zip": "0.5.5", "cac": "^6.7.14", @@ -30,16 +30,16 @@ "graphql-scalars": "1.20.1", "isbinaryfile": "^5.0.0", "js-yaml": "^4.1.0", - "koa": "^2.13.4", + "koa": "^2.14.1", "koa-body": "^6.0.1", "koa-compress": "^5.1.0", - "koa-proxies": "^0.12.2", + "koa-proxies": "^0.12.3", "koa-router": "^12.0.0", "koa-static-cache": "^5.1.4", "lodash": "^4.17.21", "lru-cache": "7.14.1", "mime-types": "^2.1.35", - "moment-timezone": "^0.5.39", + "moment-timezone": "^0.5.40", "mongodb": "^3.7.3", "nanoid": "^4.0.0", "nodemailer": "^6.8.0", @@ -48,11 +48,11 @@ "path-to-regexp": "^6.2.1", "require-resolve-hook": "^1.1.0", "saslprep": "^1.0.3", - "schemastery": "^3.5.4", + "schemastery": "^3.6.1", "semver": "^7.3.8", "serialize-javascript": "^6.0.0", - "superagent": "^8.0.4", - "tar": "^6.1.12", + "superagent": "^8.0.6", + "tar": "^6.1.13", "thirty-two": "^1.0.2", "ws": "^8.11.0" }, @@ -64,7 +64,7 @@ "@types/koa-compress": "^4.0.3", "@types/koa-router": "^7.4.4", "@types/koa-static-cache": "^5.1.1", - "@types/lodash": "^4.14.190", + "@types/lodash": "^4.14.191", "@types/mime-types": "^2.1.1", "@types/mongodb": "^3.6.20", "@types/nodemailer": "^6.4.6", diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 495eb2191..4374c2ec6 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -11,8 +11,8 @@ "lint": "eslint" }, "devDependencies": { - "@blueprintjs/core": "4.12.0", - "@blueprintjs/select": "4.8.11", + "@blueprintjs/core": "4.13.0", + "@blueprintjs/select": "4.8.12", "@fontsource/dm-mono": "^4.5.10", "@fontsource/fira-code": "^4.5.12", "@fontsource/inconsolata": "^4.5.9", @@ -33,27 +33,22 @@ "@types/react": "^18.0.26", "@types/react-dom": "^18.0.9", "@types/redux-logger": "^3.0.9", - "@types/serviceworker": "^0.0.57", - "@types/sharedworker": "^0.0.86", + "@types/serviceworker": "^0.0.58", + "@types/sharedworker": "^0.0.87", "@types/webpack-env": "^1.18.0", "@vscode/codicons": "^0.0.32", "autoprefixer": "^10.4.13", "browser-update": "^3.3.44", - "chalk": "^5.1.2", + "chalk": "^5.2.0", "classnames": "^2.3.2", - "clean-webpack-plugin": "^4.0.0", "clipboard": "^2.0.11", - "copy-webpack-plugin": "^11.0.0", - "css-loader": "^6.7.2", "diff": "^5.1.0", "diff-dom": "^4.2.8", - "echarts": "^5.4.0", + "echarts": "^5.4.1", "emojis-keywords": "2.0.0", "emojis-list": "2.1.0", - "esbuild-loader": "^2.20.0", "fancy-log": "^2.0.0", "flatpickr": "^4.6.13", - "friendly-errors-webpack-plugin": "^1.7.0", "graphiql": "2.2.0", "gulp": "^4.0.2", "gulp-iconfont": "^11.0.1", @@ -65,11 +60,9 @@ "jquery.easing": "^1.4.1", "jquery.transit": "^0.9.12", "matchmedia-polyfill": "^0.3.2", - "mini-css-extract-plugin": "^2.7.0", "moment": "^2.29.4", "monaco-editor": "0.33.0", "monaco-editor-nls": "^2.0.0", - "monaco-editor-webpack-plugin": "^7.0.1", "monaco-themes": "^0.4.3", "monaco-yaml": "^4.0.2", "nanoid": "^4.0.0", @@ -77,8 +70,7 @@ "nprogress": "^0.2.0", "p-queue": "^7.3.0", "pickadate": "^3.6.4", - "plugin-error": "^2.0.0", - "postcss-loader": "^7.0.1", + "plugin-error": "^2.0.1", "prism-themes": "^1.9.0", "prismjs": "^1.29.0", "prop-types": "^15.8.1", @@ -99,23 +91,14 @@ "rupture": "^0.7.1", "slideout": "^1.0.1", "sticky-kit": "^1.1.3", - "style-loader": "^3.3.1", - "stylus": "^0.59.0", - "stylus-loader": "^7.1.0", "tether": "1.4.7", "tether-drop": "^1.4.2", "through2": "^4.0.2", "timeago-react": "^3.0.5", "timeago.js": "^4.0.2", - "ts-loader": "^9.4.1", "vditor": "^3.8.18", "vinyl-buffer": "^1.0.1", - "web-streams-polyfill": "^3.2.1", - "webpack": "^5.75.0", - "webpack-bundle-analyzer": "^4.7.0", - "webpack-dev-server": "^4.11.1", - "webpack-manifest-plugin": "^5.0.0", - "webpackbar": "^5.0.2" + "web-streams-polyfill": "^3.2.1" }, "dependencies": { "ansi_up": "^5.1.0", @@ -123,11 +106,11 @@ "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "jsesc": "^3.0.2", - "katex": "^0.16.3", + "katex": "^0.16.4", "lodash": "^4.17.21", "markdown-it": "^13.0.1", "markdown-it-anchor": "^8.6.5", - "markdown-it-attrs": "^4.1.4", + "markdown-it-attrs": "^4.1.6", "markdown-it-footnote": "^3.0.3", "markdown-it-imsize": "^2.0.1", "markdown-it-mark": "^3.0.1", diff --git a/packages/utils/lib/utils.ts b/packages/utils/lib/utils.ts index 1fe2a3356..7451df4c9 100644 --- a/packages/utils/lib/utils.ts +++ b/packages/utils/lib/utils.ts @@ -7,6 +7,8 @@ import fs from 'fs-extra'; import { isMoment, Moment } from 'moment-timezone'; import { ObjectID } from 'mongodb'; import Logger from 'reggol'; +export * as yaml from 'js-yaml'; +export * as fs from 'fs-extra'; Logger.levels.base = process.env.DEV ? 3 : 2; Logger.targets[0].showTime = 'dd hh:mm:ss'; diff --git a/packages/utils/package.json b/packages/utils/package.json index cab2fb842..e843ea7de 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -12,11 +12,11 @@ "esbuild": "0.15.2", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", - "moment-timezone": "^0.5.39", + "moment-timezone": "^0.5.40", "mongodb": "^3.7.3", "reggol": "^1.3.2", "source-map-support": "^0.5.21", - "systeminformation": "^5.14.4" + "systeminformation": "^5.16.5" }, "devDependencies": { "@types/fs-extra": "^9.0.13", diff --git a/packages/vjudge/package.json b/packages/vjudge/package.json index b6c51e48a..d1418e97b 100644 --- a/packages/vjudge/package.json +++ b/packages/vjudge/package.json @@ -15,7 +15,7 @@ "puppeteer-extra": "3.3.0", "puppeteer-extra-plugin-portal": "3.1.0", "puppeteer-extra-plugin-stealth": "2.10.1", - "superagent": "^8.0.4", + "superagent": "^8.0.6", "superagent-proxy": "^3.0.0" }, "devDependencies": { From 4db96d858aa81e44ee4fe18f8a25e4b6569ffccc Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 12 Dec 2022 02:47:39 +0800 Subject: [PATCH 16/16] judge: fix math.max --- packages/hydrojudge/src/cases.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/hydrojudge/src/cases.ts b/packages/hydrojudge/src/cases.ts index b9e614eb5..36d707166 100644 --- a/packages/hydrojudge/src/cases.ts +++ b/packages/hydrojudge/src/cases.ts @@ -14,11 +14,11 @@ function isValidConfig(config) { if (config.count > (getConfig('testcases_max') || 100)) { throw new FormatError('Too many testcases. Cancelled.'); } - const time = Math.sum(config.subtasks.map((subtask) => Math.sum(subtask.cases.map((c) => c.time)))); + const time = Math.sum(...config.subtasks.flatMap((subtask) => subtask.cases.map((c) => c.time))); if (time > (getConfig('total_time_limit') || 60) * 1000) { throw new FormatError('Total time limit longer than {0}s. Cancelled.', [+getConfig('total_time_limit') || 60]); } - const memMax = Math.max(config.subtasks.map((subtask) => Math.max(subtask.cases.map((c) => c.memory)))); + const memMax = Math.max(...config.subtasks.flatMap((subtask) => subtask.cases.map((c) => c.memory))); if (memMax > parseMemoryMB(getConfig('memoryMax'))) throw new FormatError('Memory limit larger than memory_max'); if (!['default', 'strict'].includes(config.checker_type || 'default') && !config.checker) { throw new FormatError('You did not specify a checker.');