diff --git a/packages/elastic/index.ts b/packages/elastic/index.ts index 31aa77683..d33f89b55 100644 --- a/packages/elastic/index.ts +++ b/packages/elastic/index.ts @@ -40,16 +40,19 @@ global.Hydro.lib.problemSearch = async (domainId, q, opts) => { }); let hits = res.hits.hits.map((i) => i._id); if (!opts.skip) { - const pdoc = await ProblemModel.get(domainId, +q || q, ProblemModel.PROJECTION_LIST); + let pdoc = await ProblemModel.get(domainId, +q || q, ProblemModel.PROJECTION_LIST); if (pdoc) { hits = hits.filter((i) => i !== `${pdoc.domainId}/${pdoc.docId}`); hits.unshift(`${pdoc.domainId}/${pdoc.docId}`); + } else if (/^P\d+$/.test(q)) { + pdoc = await ProblemModel.get(domainId, +q.substring(1), ProblemModel.PROJECTION_LIST); + if (pdoc) hits.unshift(`${pdoc.domainId}/${pdoc.docId}`); } } return { countRelation: typeof res.hits.total === 'number' ? 'eq' : res.hits.total.relation, total: typeof res.hits.total === 'number' ? res.hits.total : res.hits.total.value, - hits, + hits: Array.from(new Set(hits)), }; }; diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index a43b26f92..16408f24c 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -1,7 +1,7 @@ import AdmZip from 'adm-zip'; import { readFile, statSync } from 'fs-extra'; import { - escapeRegExp, flattenDeep, intersection, pick, uniqBy, + escapeRegExp, flattenDeep, intersection, pick, } from 'lodash'; import { Filter, ObjectId } from 'mongodb'; import { nanoid } from 'nanoid'; @@ -34,7 +34,6 @@ import user from '../model/user'; import { Handler, param, post, query, route, Types, } from '../service/server'; -import { buildProjection } from '../utils'; import { ContestDetailBaseHandler } from './contest'; export const parseCategory = (value: string) => value.replace(/,/g, ',').split(',').map((e) => e.trim()); @@ -53,16 +52,21 @@ function buildQuery(udoc: User) { const defaultSearch = async (domainId: string, q: string, options?: ProblemSearchOptions) => { const escaped = escapeRegExp(q.toLowerCase()); + const projection: (keyof ProblemDoc)[] = ['domainId', 'docId', 'pid']; const $regex = new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gmi'); const filter = { $or: [{ pid: { $regex } }, { title: { $regex } }, { tag: q }] }; - const pdocs = await problem.getMulti(domainId, filter, ['domainId', 'docId', 'pid']) + const pdocs = await problem.getMulti(domainId, filter, projection) .skip(options.skip || 0).limit(options.limit || system.get('pagination.problem')).toArray(); if (!options.skip) { - const pdoc = await problem.get(domainId, +q || q, ['domainId', 'docId', 'pid', 'title']); + let pdoc = await problem.get(domainId, Number.isSafeInteger(+q) ? +q : q, projection); if (pdoc) pdocs.unshift(pdoc); + else if (/^P\d+$/.test(q)) { + pdoc = await problem.get(domainId, +q.substring(1), projection); + if (pdoc) pdocs.unshift(pdoc); + } } return { - hits: pdocs.map((i) => `${i.domainId}/${i.docId}`), + hits: Array.from(new Set(pdocs.map((i) => `${i.domainId}/${i.docId}`))), total: Math.max(pdocs.length, await problem.count(domainId, filter)), countRelation: 'eq', }; @@ -95,7 +99,8 @@ export class ProblemMainHandler extends Handler { @param('q', Types.Content, true) @param('limit', Types.PositiveInt, true) @param('pjax', Types.Boolean) - async get(domainId: string, page = 1, q = '', limit: number, pjax = false) { + @param('quick', Types.Boolean) + async get(domainId: string, page = 1, q = '', limit: number, pjax = false, quick = false) { 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); @@ -137,7 +142,11 @@ export class ProblemMainHandler extends Handler { // eslint-disable-next-line prefer-const let [pdocs, ppcount, pcount] = this.queryContext.fail ? [[], 0, 0] - : await problem.list(domainId, query, sort.length ? 1 : page, limit, undefined, this.user._id); + : await problem.list( + domainId, query, sort.length ? 1 : page, limit, + quick ? ['title', 'pid', 'domainId', 'docId'] : undefined, + this.user._id, + ); if (total) { pcount = total; ppcount = Math.ceil(total / limit); @@ -995,28 +1004,6 @@ export class ProblemCreateHandler extends Handler { } } -export class ProblemPrefixListHandler extends Handler { - @param('prefix', Types.Name) - async get(domainId: string, prefix: string) { - const projection = ['domainId', 'docId', 'pid', 'title', 'hidden'] as const; - const [pdocs, pdoc, apdoc] = await Promise.all([ - problem.getPrefixList(domainId, prefix), - 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]) } }) - .project(buildProjection(projection)).toArray(); - pdocs.push(...docs); - } - this.response.body = uniqBy(pdocs, 'docId'); - } -} - export async function apply(ctx: Context) { ctx.Route('problem_main', '/p', ProblemMainHandler, PERM.PERM_VIEW_PROBLEM); ctx.Route('problem_random', '/problem/random', ProblemRandomHandler, PERM.PERM_VIEW_PROBLEM); @@ -1033,7 +1020,6 @@ export async function apply(ctx: Context) { ctx.Route('problem_solution_reply_raw', '/p/:pid/solution/:psid/:psrid/raw', ProblemSolutionRawHandler, PERM.PERM_VIEW_PROBLEM); ctx.Route('problem_statistics', '/p/:pid/stat', ProblemStatisticsHandler, PERM.PERM_VIEW_PROBLEM); ctx.Route('problem_create', '/problem/create', ProblemCreateHandler, PERM.PERM_CREATE_PROBLEM); - ctx.Route('problem_prefix_list', '/problem/list', ProblemPrefixListHandler, PERM.PERM_VIEW_PROBLEM); ctx.inject(['api'], ({ api }) => { api.value('FileInfo', [ ['_id', 'String!'], diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index 55e0d1357..2401995ab 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -162,7 +162,8 @@ export class ProblemModel { if (Number.isSafeInteger(+pid)) pid = +pid; const res = typeof pid === 'number' ? await document.get(domainId, document.TYPE_PROBLEM, pid, projection) - : (await document.getMulti(domainId, document.TYPE_PROBLEM, { sort: sortable(pid), pid }).toArray())[0]; + : (await document.getMulti(domainId, document.TYPE_PROBLEM, { sort: sortable(pid), pid }) + .project(buildProjection(projection)).limit(1).toArray())[0]; if (!res) return null; try { if (!rawConfig) res.config = await parseConfig(res.config); diff --git a/packages/ui-default/components/autocomplete/components/ProblemSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/components/ProblemSelectAutoComplete.tsx index e0c1b26de..64468884a 100644 --- a/packages/ui-default/components/autocomplete/components/ProblemSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/components/ProblemSelectAutoComplete.tsx @@ -8,7 +8,10 @@ const ProblemSelectAutoComplete = forwardRef, Aut ref={ref as any} cacheKey={`problem-${UiContext.domainId}`} - queryItems={(query) => request.get(`/d/${UiContext.domainId}/problem/list`, { prefix: query })} + queryItems={async (query) => { + const { pdocs } = await request.get(`/d/${UiContext.domainId}/p`, { q: query, quick: true }); + return pdocs; + }} fetchItems={(ids) => api(gql` problems(ids: ${ids.map((i) => +i)}) { docId