diff --git a/packages/hydrooj/src/handler/home.ts b/packages/hydrooj/src/handler/home.ts index 8f735a849..4f4635100 100644 --- a/packages/hydrooj/src/handler/home.ts +++ b/packages/hydrooj/src/handler/home.ts @@ -310,7 +310,7 @@ class HomeSecurityHandler extends Handler { userName: `${this.user.uname}(${this.user.mail})`, attestationType: 'direct', excludeCredentials: this.user._authenticators.map((c) => ({ - id: isoBase64URL.fromBuffer(c.credentialID.buffer), + id: isoBase64URL.fromBuffer(new Uint8Array(c.credentialID.buffer)), type: 'public-key', })), authenticatorSelection: { diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 50d95b7be..99ba1cf8f 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -105,10 +105,12 @@ export class ProblemMainHandler extends Handler { @param('limit', Types.PositiveInt, true) @param('pjax', Types.Boolean) @param('quick', Types.Boolean) - async get(domainId: string, page = 1, q = '', limit: number, pjax = false, quick = false) { + @param('sort', Types.Range(['default', 'recent']), true) + async get(domainId: string, page = 1, q = '', limit: number, pjax = false, quick = false, sortStrategy = 'default') { this.response.template = 'problem_main.html'; if (!limit || limit > this.ctx.setting.get('pagination.problem') || page > 1) limit = this.ctx.setting.get('pagination.problem'); this.queryContext.query = buildQuery(this.user); + if (sortStrategy === 'recent') this.queryContext.hint = 'basic'; // eslint-disable-next-line ts/no-shadow const query = this.queryContext.query; const psdict = {}; @@ -145,11 +147,15 @@ export class ProblemMainHandler extends Handler { } const sort = this.queryContext.sort; await this.ctx.parallel('problem/list', query, this, sort); + const sortKey = ({ + default: { sort: 1, docId: 1 }, + recent: { docId: -1 }, + } as const)[sortStrategy]; let [pdocs, ppcount, pcount] = this.queryContext.fail ? [[], 0, 0] : await this.paginate( problem.getMulti(domainId, query, quick ? ['title', 'pid', 'domainId', 'docId'] : undefined) - .sort({ sort: 1, docId: 1 }).hint(this.queryContext.hint), + .sort(sortKey).hint(this.queryContext.hint), sort.length ? 1 : page, limit, ); if (total) { @@ -169,7 +175,7 @@ export class ProblemMainHandler extends Handler { title: this.renderTitle(this.translate('problem_main')), fragments: (await Promise.all([ this.renderHTML('partials/problem_list.html', { - page, ppcount, pcount, pdocs, psdict, qs: q, + page, ppcount, pcount, pdocs, psdict, qs: q, sort: sortStrategy, }), this.renderHTML('partials/problem_stat.html', { pcount, pcountRelation: this.queryContext.pcountRelation }), this.renderHTML('partials/problem_lucky.html', { qs: q }), @@ -184,6 +190,7 @@ export class ProblemMainHandler extends Handler { pdocs, psdict, qs: q, + sort: sortStrategy, }; } } diff --git a/packages/hydrooj/src/handler/user.ts b/packages/hydrooj/src/handler/user.ts index 9d5cb0a3f..f93f44e66 100644 --- a/packages/hydrooj/src/handler/user.ts +++ b/packages/hydrooj/src/handler/user.ts @@ -158,7 +158,7 @@ class UserWebauthnHandler extends Handler { if (!udoc._id) throw new UserNotFoundError(uname || 'user'); if (!udoc.authn) throw new AuthOperationError('authn', 'disabled'); allowCredentials = udoc._authenticators.map((authenticator) => ({ - id: isoBase64URL.fromBuffer(authenticator.credentialID.buffer), + id: isoBase64URL.fromBuffer(new Uint8Array(authenticator.credentialID.buffer)), })); uid = udoc._id; } @@ -194,8 +194,8 @@ class UserWebauthnHandler extends Handler { expectedRPID: this.getAuthnHost(), credential: { ...authenticator, - id: isoBase64URL.fromBuffer(authenticator.credentialID.buffer), - publicKey: authenticator.credentialPublicKey.buffer, + id: isoBase64URL.fromBuffer(new Uint8Array(authenticator.credentialID.buffer)), + publicKey: new Uint8Array(authenticator.credentialPublicKey.buffer), }, }).catch(() => null); if (!verification?.verified) throw new ValidationError('authenticator'); diff --git a/packages/hydrooj/src/model/document.ts b/packages/hydrooj/src/model/document.ts index c0d09a242..6c6199ed0 100644 --- a/packages/hydrooj/src/model/document.ts +++ b/packages/hydrooj/src/model/document.ts @@ -414,9 +414,9 @@ export async function apply(ctx: Context) { coll.deleteMany({ domainId }), collStatus.deleteMany({ domainId }), ])); - await db.clearIndexes(coll, ['tag', 'hidden']); + await ctx.db.clearIndexes(coll, ['tag', 'hidden']); const onlyFor = (docType: number) => ({ partialFilterExpression: { docType } }); - await db.ensureIndexes( + await ctx.db.ensureIndexes( coll, { key: { domainId: 1, docType: 1, docId: 1 }, name: 'basic', unique: true }, { key: { domainId: 1, docType: 1, owner: 1, docId: -1 }, name: 'owner' }, diff --git a/packages/ui-default/components/autocomplete/components/ProblemSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/components/ProblemSelectAutoComplete.tsx index c735f5f5e..f962680f2 100644 --- a/packages/ui-default/components/autocomplete/components/ProblemSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/components/ProblemSelectAutoComplete.tsx @@ -9,7 +9,7 @@ const ProblemSelectAutoComplete = forwardRef, Aut ref={ref as any} cacheKey={`problem-${UiContext.domainId}`} queryItems={async (query) => { - const { pdocs } = await request.get(`/d/${UiContext.domainId}/p`, { q: query, quick: true }); + const { pdocs } = await request.get(`/d/${UiContext.domainId}/p`, { q: query, quick: true, sort: query ? 'default' : 'recent' }); return pdocs; }} fetchItems={(ids) => api('problems', { ids: ids.map((i) => +i) }, ['docId', 'pid', 'title'])} @@ -29,7 +29,7 @@ const ProblemSelectAutoComplete = forwardRef, Aut listStyle: {}, multi: false, selectedKeys: [], - allowEmptyQuery: false, + allowEmptyQuery: true, freeSolo: false, freeSoloConverter: (input) => input, ...props, diff --git a/packages/ui-default/locales/en.yaml b/packages/ui-default/locales/en.yaml index a18b06ef7..0e1150ea0 100644 --- a/packages/ui-default/locales/en.yaml +++ b/packages/ui-default/locales/en.yaml @@ -27,9 +27,9 @@ problem_import: Import Problem problem_main: Problem Set problem_solution: Problem Solution problem_submit: Problem Submit +problem_type.communication: 'Type: Communication' problem_type.default: 'Type: Default' problem_type.interactive: 'Type: Interactive' -problem_type.communication: 'Type: Communication' problem_type.objective: 'Type: Objective' problem_type.remote_judge: 'Type: RemoteJudge' problem_type.submit_answer: 'Type: SubmitAnswer' @@ -54,6 +54,8 @@ setting_info: Personal Info setting_preference: Preference setting_privacy: Privacy setting_usage: Usage Preference +sort::default: Default +sort::recent: Recent tasks_list: Task list timeago_locale: en_US training_create: Training Create diff --git a/packages/ui-default/locales/zh.yaml b/packages/ui-default/locales/zh.yaml index 007b564cc..297b6bda1 100644 --- a/packages/ui-default/locales/zh.yaml +++ b/packages/ui-default/locales/zh.yaml @@ -877,6 +877,8 @@ Sorry, there are no training plans.: 目前没有训练计划。 Sorry, there are no users in this domain.: 对不起,此域中没有用户。 Sorry: 对不起 Sort by: 排序 +sort::default: 默认排序 +sort::recent: 最新题目 Source type: 来源类型 Source: 来源 Standard mode: 标准模式 diff --git a/packages/ui-default/pages/problem_main.page.styl b/packages/ui-default/pages/problem_main.page.styl index 9b4a4c887..20d9aedaa 100644 --- a/packages/ui-default/pages/problem_main.page.styl +++ b/packages/ui-default/pages/problem_main.page.styl @@ -204,7 +204,7 @@ color: #000000 .search > button - width: 40px + width: 37px height: 36px border: 1px solid #00B4CC background: #00B4CC @@ -215,6 +215,16 @@ .search > div position: absolute - right: 50px - top: 8px - font-size: rem(17px) \ No newline at end of file + right: 170px + top: 10px + font-size: rem(17px) + +.search-sort + position: absolute + border: none + top: 1px + height: 34px + right: 37px + color: #000000 + cursor: pointer + width: 120px diff --git a/packages/ui-default/pages/problem_main.page.tsx b/packages/ui-default/pages/problem_main.page.tsx index 5a0316d27..0fe3bdeeb 100644 --- a/packages/ui-default/pages/problem_main.page.tsx +++ b/packages/ui-default/pages/problem_main.page.tsx @@ -85,9 +85,12 @@ function updateSelection() { function loadQuery() { const q = $('[name="q"]').val().toString(); + const sort = $('[name="sort"]').val().toString(); const url = new URL(window.location.href); if (!q) url.searchParams.delete('q'); else url.searchParams.set('q', q); + if (sort && sort !== 'default') url.searchParams.set('sort', sort); + else url.searchParams.delete('sort'); url.searchParams.delete('page'); pjax.request({ url: url.toString() }); } @@ -443,6 +446,7 @@ const page = new NamedPage(['problem_main'], () => { }); $('#searchForm').on('submit', inputChanged); $('#searchForm').find('input').on('input', _.debounce(inputChanged, 500)); + $('#searchForm').find('select[name="sort"]').on('change', inputChanged); $('.dialog-button').on('click', (ev) => { categoryDialog.clear().open(); ev.preventDefault(); diff --git a/packages/ui-default/templates/problem_main.html b/packages/ui-default/templates/problem_main.html index 9548cbdae..29e183bbd 100644 --- a/packages/ui-default/templates/problem_main.html +++ b/packages/ui-default/templates/problem_main.html @@ -10,6 +10,11 @@