diff --git a/README.md b/README.md index 7ab39a30b..96eb92206 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,6 @@ Hydro 用户群:1085853538 ## Unreleased -- judge: breaking: 不再支持在 checker 等的编译阶段读取选手代码 - judge: 添加 checker 编译缓存 - ui: 优化题目详情页面 OGP 信息 - core&ui: 升级 simplewebauthn @@ -123,19 +122,25 @@ Hydro 用户群:1085853538 - ui&judge: 添加栈空间回显 - install: 在树莓派中自动启用 cgroup.memory - install: 添加 shm 空间大小警告 -- vjudge: 使用 vjudge.mount 表替代 domain.mount - core: 升级到 cordis@4 - framework: 支持同时启用多个 renderer - core: 分离 HMR 和 Watcher 组件 - core: i18n: 添加 interface 选项 - judge: 添加 kattis checker 支持 -- core: 移除旧版本 bus 调用 - migrate: 修复 hustoj 自动运行 -- core: 强制要求使用 inject 声明插件依赖 -- core: 移除 global.Hydro.service.db -- core: 移除 global.Hydro.service.service -- core: 移除 global.Hydro.service.storage - core: Settings: 支持使用 Schemastery +- ui: 更新系统设置页面样式 +- import-qduoj: 修复 spj=null + +### Breaking API Changes + +- vjudge: 使用 vjudge.mount 表替代 domain.mount +- judge: breaking: 不再支持在 checker 等的编译阶段读取选手代码 +- core: 强制要求使用 inject 声明插件依赖 +- core: 移除旧版本 bus 调用 +- core: 移除 global.Hydro.service +- core: 移除 global.Hydro.ui.template +- core: 移除 global.Hydro.lib ## Hydro 4.19.0 / UI 4.57.0 diff --git a/packages/elastic/index.ts b/packages/elastic/index.ts index 5f9db740d..ae8a228aa 100644 --- a/packages/elastic/index.ts +++ b/packages/elastic/index.ts @@ -19,7 +19,7 @@ const processDocument = (doc: Partial) => { return _.omit(doc, indexOmit); }; -global.Hydro.lib.problemSearch = async (domainId, q, opts) => { +async function problemSearch(domainId, q, opts) { const allowedSize = SystemModel.get('elasic-search.indexSize') || 10000; const size = opts?.limit || SystemModel.get('pagination.problem'); const from = Math.min(allowedSize - size, opts?.skip || 0); @@ -56,7 +56,7 @@ global.Hydro.lib.problemSearch = async (domainId, q, opts) => { total: typeof res.hits.total === 'number' ? res.hits.total : res.hits.total.value, hits: Array.from(new Set(hits)), }; -}; +} async function run({ domainId }, report) { try { @@ -109,4 +109,5 @@ export const apply = (ctx: Context) => { }), run, ); + ctx.provideModule('problemSearch', 'elastic', problemSearch); }; diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 1c3473408..568d76298 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -109,7 +109,7 @@ export class ProblemMainHandler extends Handler { // eslint-disable-next-line @typescript-eslint/no-shadow const query = this.queryContext.query; const psdict = {}; - const search = global.Hydro.lib.problemSearch || defaultSearch; + const search = Object.values(global.Hydro.module.problemSearch)[0] || defaultSearch; const parsed = parser.parse(q, { keywords: ['category', 'difficulty', 'namespace'], offsets: false, diff --git a/packages/hydrooj/src/init.ts b/packages/hydrooj/src/init.ts index f56f4d230..3e1a01864 100644 --- a/packages/hydrooj/src/init.ts +++ b/packages/hydrooj/src/init.ts @@ -11,8 +11,6 @@ if (!global.Hydro) { // @ts-ignore model: {}, script: {}, - // @ts-ignore - lib: {}, module: new Proxy({} as any, { get(self, key) { self[key] ||= {}; diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 8a9a4823e..29b7a1823 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -613,15 +613,6 @@ export interface ProblemSearchOptions { export type ProblemSearch = (domainId: string, q: string, options?: ProblemSearchOptions) => Promise; -export interface Lib extends Record { - difficulty: typeof import('./lib/difficulty').default; - buildContent: typeof import('./lib/content').buildContent; - mail: typeof import('./lib/mail'); - rating: typeof import('./lib/rating').default; - testdataConfig: typeof import('./lib/testdataConfig'); - problemSearch: ProblemSearch; -} - export type UIInjectableFields = 'ProblemAdd' | 'Notification' | 'Nav' | 'UserDropdown' | 'DomainManage' | 'ControlPanel'; export interface UI { nodes: Record, @@ -638,13 +629,13 @@ export interface ModuleInterfaces { lockUsername?: boolean; }; hash: (password: string, salt: string, user: User) => boolean | string | Promise; + problemSearch: ProblemSearch; } export interface HydroGlobal { version: Record; model: Model; script: Record; - lib: Lib; module: { [K in keyof ModuleInterfaces]: Record }; ui: UI; error: typeof import('./error'); diff --git a/packages/hydrooj/src/lib/avatar.ts b/packages/hydrooj/src/lib/avatar.ts index 809415fe7..ea962f997 100644 --- a/packages/hydrooj/src/lib/avatar.ts +++ b/packages/hydrooj/src/lib/avatar.ts @@ -34,4 +34,3 @@ export function validate(input: string) { } export default avatar; -global.Hydro.lib.avatar = avatar; diff --git a/packages/hydrooj/src/lib/content.ts b/packages/hydrooj/src/lib/content.ts index b777c4177..f79078635 100644 --- a/packages/hydrooj/src/lib/content.ts +++ b/packages/hydrooj/src/lib/content.ts @@ -76,5 +76,3 @@ export function buildContent(source: ProblemSource, type: 'markdown' | 'html' = ...source.source ? [`## ${_('Source')}`, '', source.source, ''] : [], ].join('\n'); } - -global.Hydro.lib.buildContent = buildContent; diff --git a/packages/hydrooj/src/lib/difficulty.ts b/packages/hydrooj/src/lib/difficulty.ts index 231376806..32311633b 100644 --- a/packages/hydrooj/src/lib/difficulty.ts +++ b/packages/hydrooj/src/lib/difficulty.ts @@ -43,4 +43,3 @@ function difficultyAlgorithm(nSubmit: number, nAccept: number) { } export default difficultyAlgorithm; -global.Hydro.lib.difficulty = difficultyAlgorithm; diff --git a/packages/hydrooj/src/lib/mail.ts b/packages/hydrooj/src/lib/mail.ts index dbaa5779c..87a364cad 100644 --- a/packages/hydrooj/src/lib/mail.ts +++ b/packages/hydrooj/src/lib/mail.ts @@ -37,7 +37,3 @@ export async function sendMail(to: string, subject: string, text: string, html: } return t; } - -global.Hydro.lib.mail = { - sendMail, -}; diff --git a/packages/hydrooj/src/lib/rating.ts b/packages/hydrooj/src/lib/rating.ts index 5c89c6678..bb4f14207 100644 --- a/packages/hydrooj/src/lib/rating.ts +++ b/packages/hydrooj/src/lib/rating.ts @@ -119,5 +119,3 @@ function calculate(users: RatingInputUser[]): RatingOutputUser[] { } export default calculate; - -global.Hydro.lib.rating = calculate; diff --git a/packages/hydrooj/src/lib/testdataConfig.ts b/packages/hydrooj/src/lib/testdataConfig.ts index c451b0257..512f0ce5f 100644 --- a/packages/hydrooj/src/lib/testdataConfig.ts +++ b/packages/hydrooj/src/lib/testdataConfig.ts @@ -37,5 +37,3 @@ export async function parseConfig(config: string | ProblemConfigFile = {}) { if (cfg.filename && result.type === 'default') result.subType = cfg.filename; return result; } - -global.Hydro.lib.testdataConfig = { parseConfig }; diff --git a/packages/hydrooj/src/plugin-api.ts b/packages/hydrooj/src/plugin-api.ts index 1c5782b28..775d3ed51 100644 --- a/packages/hydrooj/src/plugin-api.ts +++ b/packages/hydrooj/src/plugin-api.ts @@ -44,6 +44,8 @@ export { default as mime } from './lib/mime'; export { default as difficultyAlgorithm } from './lib/difficulty'; export { default as rating } from './lib/rating'; export { default as avatar } from './lib/avatar'; +export { parseConfig as testdataConfig } from './lib/testdataConfig'; +export { sendMail } from './lib/mail'; /** @deprecated use Handler.paginate instead */ export const paginate = db.paginate.bind(db); /** @deprecated use db.ranked instead */ diff --git a/packages/hydrooj/src/service/hmr.ts b/packages/hydrooj/src/service/hmr.ts index 1ecb0549c..b87962169 100644 --- a/packages/hydrooj/src/service/hmr.ts +++ b/packages/hydrooj/src/service/hmr.ts @@ -144,7 +144,7 @@ export default class HMR extends Service { } } - private triggerLocalReload() { + private async triggerLocalReload() { this.analyzeChanges(); /** plugins pending classification */ @@ -207,10 +207,11 @@ export default class HMR extends Service { return rollback(); } - const reload = (plugin: any, runtime?: Plugin.Runtime) => { + const reload = async (plugin: any, runtime?: Plugin.Runtime) => { if (!runtime) return; for (const oldFiber of runtime.scopes) { - oldFiber.parent.plugin(plugin, oldFiber.config); + // eslint-disable-next-line no-await-in-loop + await oldFiber.parent.plugin(plugin, oldFiber.config); } }; @@ -226,7 +227,8 @@ export default class HMR extends Service { } try { - reload(attempts[filename], runtime); + // eslint-disable-next-line no-await-in-loop + await reload(attempts[filename], runtime); logger.info('reload plugin at %c', path); } catch (err) { logger.warn('failed to reload plugin at %c', path); @@ -240,7 +242,7 @@ export default class HMR extends Service { for (const [plugin, { filename, runtime }] of reloads) { try { this.ctx.registry.delete(attempts[filename]); - reload(plugin, runtime); + await reload(plugin, runtime); } catch (err) { logger.warn(err); } diff --git a/packages/sonic/index.ts b/packages/sonic/index.ts index 74c4e4414..2435cdaa3 100644 --- a/packages/sonic/index.ts +++ b/packages/sonic/index.ts @@ -10,7 +10,7 @@ export const inject = { 'sonic': { required: false } }; export function apply(ctx: Context) { ctx.plugin(SonicService); - global.Hydro.lib.problemSearch = async (domainId, query, opts) => { + ctx.provideModule('problemSearch', 'sonic', async (domainId, query, opts) => { const limit = opts?.limit || SystemModel.get('pagination.problem'); let hits = await ctx.sonic.query('problem', `${domainId}@title`, query, { limit }); if (!opts.skip) { @@ -29,7 +29,7 @@ export function apply(ctx: Context) { total: hits.length, hits, }; - }; + }); async function run({ domainId }, report) { if (domainId) await ctx.sonic.flushb('problem', domainId); diff --git a/packages/ui-default/backendlib/builder.ts b/packages/ui-default/backendlib/builder.ts index 0bc30d32f..ee878675d 100644 --- a/packages/ui-default/backendlib/builder.ts +++ b/packages/ui-default/backendlib/builder.ts @@ -3,7 +3,6 @@ import { Context, fs, Handler, Logger, NotFoundError, param, SettingModel, sha1, size, SystemModel, Types, UiContextBase, } from 'hydrooj'; -import { debounce } from 'lodash'; import { tmpdir } from 'os'; import { basename, join, relative, resolve, @@ -118,6 +117,7 @@ export async function buildUI() { } for (const lang in global.Hydro.locales) { if (!/^[a-zA-Z_]+$/.test(lang)) continue; + if (!global.Hydro.locales[lang].__interface) continue; const str = `window.LOCALES=${JSON.stringify(global.Hydro.locales[lang][Symbol.for('iterate')])};`; addFile(`lang-${lang}.js`, str); } @@ -160,7 +160,7 @@ export async function apply(ctx: Context) { ctx.Route('constant', '/lazy/:version/:name', UiConstantsHandler); ctx.Route('constant', '/resource/:version/:name', UiConstantsHandler); ctx.on('app/started', buildUI); - const debouncedBuildUI = debounce(buildUI, 2000, { trailing: true }); + const debouncedBuildUI = ctx.debounce(buildUI, 2000); const triggerHotUpdate = (path?: string) => { if (path && !path.includes('/ui-default/') && !path.includes('/public/') && !path.includes('/frontend/')) return; debouncedBuildUI(); diff --git a/packages/ui-default/backendlib/markdown.js b/packages/ui-default/backendlib/markdown.js index ad675fa9a..65de87cda 100644 --- a/packages/ui-default/backendlib/markdown.js +++ b/packages/ui-default/backendlib/markdown.js @@ -59,6 +59,6 @@ function renderInline(text) { return md.renderInline(text); } -global.Hydro.lib.markdown = module.exports = { +module.exports = { md, plugin, render, renderInline, }; diff --git a/packages/ui-default/backendlib/template.ts b/packages/ui-default/backendlib/template.ts index c78b6db81..cc4ec2106 100644 --- a/packages/ui-default/backendlib/template.ts +++ b/packages/ui-default/backendlib/template.ts @@ -133,7 +133,6 @@ class Nunjucks extends nunjucks.Environment { this.addGlobal('utils', { status }); this.addGlobal('avatarUrl', avatar); this.addGlobal('formatSeconds', misc.formatSeconds); - this.addGlobal('lib', global.Hydro.lib); this.addGlobal('model', global.Hydro.model); this.addGlobal('ui', global.Hydro.ui); this.addGlobal('isIE', (str) => {