mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core&ui: problem_main: support sort
This commit is contained in:
10 files changed
+45
-15
No files matched your search
@@ -310,7 +310,7 @@ class HomeSecurityHandler extends Handler {
|
|||||||
userName: `${this.user.uname}(${this.user.mail})`,
|
userName: `${this.user.uname}(${this.user.mail})`,
|
||||||
attestationType: 'direct',
|
attestationType: 'direct',
|
||||||
excludeCredentials: this.user._authenticators.map((c) => ({
|
excludeCredentials: this.user._authenticators.map((c) => ({
|
||||||
id: isoBase64URL.fromBuffer(c.credentialID.buffer),
|
id: isoBase64URL.fromBuffer(new Uint8Array(c.credentialID.buffer)),
|
||||||
type: 'public-key',
|
type: 'public-key',
|
||||||
})),
|
})),
|
||||||
authenticatorSelection: {
|
authenticatorSelection: {
|
||||||
|
|||||||
@@ -105,10 +105,12 @@ export class ProblemMainHandler extends Handler {
|
|||||||
@param('limit', Types.PositiveInt, true)
|
@param('limit', Types.PositiveInt, true)
|
||||||
@param('pjax', Types.Boolean)
|
@param('pjax', Types.Boolean)
|
||||||
@param('quick', 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';
|
this.response.template = 'problem_main.html';
|
||||||
if (!limit || limit > this.ctx.setting.get('pagination.problem') || page > 1) limit = this.ctx.setting.get('pagination.problem');
|
if (!limit || limit > this.ctx.setting.get('pagination.problem') || page > 1) limit = this.ctx.setting.get('pagination.problem');
|
||||||
this.queryContext.query = buildQuery(this.user);
|
this.queryContext.query = buildQuery(this.user);
|
||||||
|
if (sortStrategy === 'recent') this.queryContext.hint = 'basic';
|
||||||
// eslint-disable-next-line ts/no-shadow
|
// eslint-disable-next-line ts/no-shadow
|
||||||
const query = this.queryContext.query;
|
const query = this.queryContext.query;
|
||||||
const psdict = {};
|
const psdict = {};
|
||||||
@@ -145,11 +147,15 @@ export class ProblemMainHandler extends Handler {
|
|||||||
}
|
}
|
||||||
const sort = this.queryContext.sort;
|
const sort = this.queryContext.sort;
|
||||||
await this.ctx.parallel('problem/list', query, this, 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
|
let [pdocs, ppcount, pcount] = this.queryContext.fail
|
||||||
? [[], 0, 0]
|
? [[], 0, 0]
|
||||||
: await this.paginate(
|
: await this.paginate(
|
||||||
problem.getMulti(domainId, query, quick ? ['title', 'pid', 'domainId', 'docId'] : undefined)
|
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,
|
sort.length ? 1 : page, limit,
|
||||||
);
|
);
|
||||||
if (total) {
|
if (total) {
|
||||||
@@ -169,7 +175,7 @@ export class ProblemMainHandler extends Handler {
|
|||||||
title: this.renderTitle(this.translate('problem_main')),
|
title: this.renderTitle(this.translate('problem_main')),
|
||||||
fragments: (await Promise.all([
|
fragments: (await Promise.all([
|
||||||
this.renderHTML('partials/problem_list.html', {
|
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_stat.html', { pcount, pcountRelation: this.queryContext.pcountRelation }),
|
||||||
this.renderHTML('partials/problem_lucky.html', { qs: q }),
|
this.renderHTML('partials/problem_lucky.html', { qs: q }),
|
||||||
@@ -184,6 +190,7 @@ export class ProblemMainHandler extends Handler {
|
|||||||
pdocs,
|
pdocs,
|
||||||
psdict,
|
psdict,
|
||||||
qs: q,
|
qs: q,
|
||||||
|
sort: sortStrategy,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ class UserWebauthnHandler extends Handler {
|
|||||||
if (!udoc._id) throw new UserNotFoundError(uname || 'user');
|
if (!udoc._id) throw new UserNotFoundError(uname || 'user');
|
||||||
if (!udoc.authn) throw new AuthOperationError('authn', 'disabled');
|
if (!udoc.authn) throw new AuthOperationError('authn', 'disabled');
|
||||||
allowCredentials = udoc._authenticators.map((authenticator) => ({
|
allowCredentials = udoc._authenticators.map((authenticator) => ({
|
||||||
id: isoBase64URL.fromBuffer(authenticator.credentialID.buffer),
|
id: isoBase64URL.fromBuffer(new Uint8Array(authenticator.credentialID.buffer)),
|
||||||
}));
|
}));
|
||||||
uid = udoc._id;
|
uid = udoc._id;
|
||||||
}
|
}
|
||||||
@@ -194,8 +194,8 @@ class UserWebauthnHandler extends Handler {
|
|||||||
expectedRPID: this.getAuthnHost(),
|
expectedRPID: this.getAuthnHost(),
|
||||||
credential: {
|
credential: {
|
||||||
...authenticator,
|
...authenticator,
|
||||||
id: isoBase64URL.fromBuffer(authenticator.credentialID.buffer),
|
id: isoBase64URL.fromBuffer(new Uint8Array(authenticator.credentialID.buffer)),
|
||||||
publicKey: authenticator.credentialPublicKey.buffer,
|
publicKey: new Uint8Array(authenticator.credentialPublicKey.buffer),
|
||||||
},
|
},
|
||||||
}).catch(() => null);
|
}).catch(() => null);
|
||||||
if (!verification?.verified) throw new ValidationError('authenticator');
|
if (!verification?.verified) throw new ValidationError('authenticator');
|
||||||
|
|||||||
@@ -414,9 +414,9 @@ export async function apply(ctx: Context) {
|
|||||||
coll.deleteMany({ domainId }),
|
coll.deleteMany({ domainId }),
|
||||||
collStatus.deleteMany({ domainId }),
|
collStatus.deleteMany({ domainId }),
|
||||||
]));
|
]));
|
||||||
await db.clearIndexes(coll, ['tag', 'hidden']);
|
await ctx.db.clearIndexes(coll, ['tag', 'hidden']);
|
||||||
const onlyFor = (docType: number) => ({ partialFilterExpression: { docType } });
|
const onlyFor = (docType: number) => ({ partialFilterExpression: { docType } });
|
||||||
await db.ensureIndexes(
|
await ctx.db.ensureIndexes(
|
||||||
coll,
|
coll,
|
||||||
{ key: { domainId: 1, docType: 1, docId: 1 }, name: 'basic', unique: true },
|
{ key: { domainId: 1, docType: 1, docId: 1 }, name: 'basic', unique: true },
|
||||||
{ key: { domainId: 1, docType: 1, owner: 1, docId: -1 }, name: 'owner' },
|
{ key: { domainId: 1, docType: 1, owner: 1, docId: -1 }, name: 'owner' },
|
||||||
|
|||||||
+2
-2
@@ -9,7 +9,7 @@ const ProblemSelectAutoComplete = forwardRef<AutoCompleteHandle<ProblemDoc>, Aut
|
|||||||
ref={ref as any}
|
ref={ref as any}
|
||||||
cacheKey={`problem-${UiContext.domainId}`}
|
cacheKey={`problem-${UiContext.domainId}`}
|
||||||
queryItems={async (query) => {
|
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;
|
return pdocs;
|
||||||
}}
|
}}
|
||||||
fetchItems={(ids) => api('problems', { ids: ids.map((i) => +i) }, ['docId', 'pid', 'title'])}
|
fetchItems={(ids) => api('problems', { ids: ids.map((i) => +i) }, ['docId', 'pid', 'title'])}
|
||||||
@@ -29,7 +29,7 @@ const ProblemSelectAutoComplete = forwardRef<AutoCompleteHandle<ProblemDoc>, Aut
|
|||||||
listStyle: {},
|
listStyle: {},
|
||||||
multi: false,
|
multi: false,
|
||||||
selectedKeys: [],
|
selectedKeys: [],
|
||||||
allowEmptyQuery: false,
|
allowEmptyQuery: true,
|
||||||
freeSolo: false,
|
freeSolo: false,
|
||||||
freeSoloConverter: (input) => input,
|
freeSoloConverter: (input) => input,
|
||||||
...props,
|
...props,
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ problem_import: Import Problem
|
|||||||
problem_main: Problem Set
|
problem_main: Problem Set
|
||||||
problem_solution: Problem Solution
|
problem_solution: Problem Solution
|
||||||
problem_submit: Problem Submit
|
problem_submit: Problem Submit
|
||||||
|
problem_type.communication: 'Type: Communication'
|
||||||
problem_type.default: 'Type: Default'
|
problem_type.default: 'Type: Default'
|
||||||
problem_type.interactive: 'Type: Interactive'
|
problem_type.interactive: 'Type: Interactive'
|
||||||
problem_type.communication: 'Type: Communication'
|
|
||||||
problem_type.objective: 'Type: Objective'
|
problem_type.objective: 'Type: Objective'
|
||||||
problem_type.remote_judge: 'Type: RemoteJudge'
|
problem_type.remote_judge: 'Type: RemoteJudge'
|
||||||
problem_type.submit_answer: 'Type: SubmitAnswer'
|
problem_type.submit_answer: 'Type: SubmitAnswer'
|
||||||
@@ -54,6 +54,8 @@ setting_info: Personal Info
|
|||||||
setting_preference: Preference
|
setting_preference: Preference
|
||||||
setting_privacy: Privacy
|
setting_privacy: Privacy
|
||||||
setting_usage: Usage Preference
|
setting_usage: Usage Preference
|
||||||
|
sort::default: Default
|
||||||
|
sort::recent: Recent
|
||||||
tasks_list: Task list
|
tasks_list: Task list
|
||||||
timeago_locale: en_US
|
timeago_locale: en_US
|
||||||
training_create: Training Create
|
training_create: Training Create
|
||||||
|
|||||||
@@ -877,6 +877,8 @@ Sorry, there are no training plans.: 目前没有训练计划。
|
|||||||
Sorry, there are no users in this domain.: 对不起,此域中没有用户。
|
Sorry, there are no users in this domain.: 对不起,此域中没有用户。
|
||||||
Sorry: 对不起
|
Sorry: 对不起
|
||||||
Sort by: 排序
|
Sort by: 排序
|
||||||
|
sort::default: 默认排序
|
||||||
|
sort::recent: 最新题目
|
||||||
Source type: 来源类型
|
Source type: 来源类型
|
||||||
Source: 来源
|
Source: 来源
|
||||||
Standard mode: 标准模式
|
Standard mode: 标准模式
|
||||||
|
|||||||
@@ -204,7 +204,7 @@
|
|||||||
color: #000000
|
color: #000000
|
||||||
|
|
||||||
.search > button
|
.search > button
|
||||||
width: 40px
|
width: 37px
|
||||||
height: 36px
|
height: 36px
|
||||||
border: 1px solid #00B4CC
|
border: 1px solid #00B4CC
|
||||||
background: #00B4CC
|
background: #00B4CC
|
||||||
@@ -215,6 +215,16 @@
|
|||||||
|
|
||||||
.search > div
|
.search > div
|
||||||
position: absolute
|
position: absolute
|
||||||
right: 50px
|
right: 170px
|
||||||
top: 8px
|
top: 10px
|
||||||
font-size: rem(17px)
|
font-size: rem(17px)
|
||||||
|
|
||||||
|
.search-sort
|
||||||
|
position: absolute
|
||||||
|
border: none
|
||||||
|
top: 1px
|
||||||
|
height: 34px
|
||||||
|
right: 37px
|
||||||
|
color: #000000
|
||||||
|
cursor: pointer
|
||||||
|
width: 120px
|
||||||
@@ -85,9 +85,12 @@ function updateSelection() {
|
|||||||
|
|
||||||
function loadQuery() {
|
function loadQuery() {
|
||||||
const q = $('[name="q"]').val().toString();
|
const q = $('[name="q"]').val().toString();
|
||||||
|
const sort = $('[name="sort"]').val().toString();
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
if (!q) url.searchParams.delete('q');
|
if (!q) url.searchParams.delete('q');
|
||||||
else url.searchParams.set('q', 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');
|
url.searchParams.delete('page');
|
||||||
pjax.request({ url: url.toString() });
|
pjax.request({ url: url.toString() });
|
||||||
}
|
}
|
||||||
@@ -443,6 +446,7 @@ const page = new NamedPage(['problem_main'], () => {
|
|||||||
});
|
});
|
||||||
$('#searchForm').on('submit', inputChanged);
|
$('#searchForm').on('submit', inputChanged);
|
||||||
$('#searchForm').find('input').on('input', _.debounce(inputChanged, 500));
|
$('#searchForm').find('input').on('input', _.debounce(inputChanged, 500));
|
||||||
|
$('#searchForm').find('select[name="sort"]').on('change', inputChanged);
|
||||||
$('.dialog-button').on('click', (ev) => {
|
$('.dialog-button').on('click', (ev) => {
|
||||||
categoryDialog.clear().open();
|
categoryDialog.clear().open();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|||||||
@@ -10,6 +10,11 @@
|
|||||||
<div class="search">
|
<div class="search">
|
||||||
<input name="q" type="text" value="{{ qs }}" placeholder="{{ _('Search') }}">
|
<input name="q" type="text" value="{{ qs }}" placeholder="{{ _('Search') }}">
|
||||||
{% include "partials/problem_stat.html" %}
|
{% include "partials/problem_stat.html" %}
|
||||||
|
<select name="sort" class="search-sort">
|
||||||
|
{%- for sortType in ["default", "recent"] -%}
|
||||||
|
<option value="{{ sortType }}" {{ "selected" if sort == sortType else "" }}>{{ _('sort::' + sortType) }}</option>
|
||||||
|
{%- endfor -%}
|
||||||
|
</select>
|
||||||
<button type="submit">
|
<button type="submit">
|
||||||
<span class="icon icon-search"></span>
|
<span class="icon icon-search"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in new issue
Block a user