mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-19 17:38:19 +08:00
ui: problem_edit tools
This commit is contained in:
1 parent
1910a57dbb
commit
3664df4202
10 files changed
+187
-58
No files matched your search
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hydrooj",
|
||||
"version": "2.23.29",
|
||||
"version": "2.23.30",
|
||||
"bin": "bin/hydrooj.js",
|
||||
"main": "dist/loader.js",
|
||||
"typings": "dist/loader.d.ts",
|
||||
|
||||
@@ -55,7 +55,8 @@ export class ProblemHandler extends Handler {
|
||||
export class ProblemMainHandler extends ProblemHandler {
|
||||
@param('page', Types.PositiveInt, true)
|
||||
@param('q', Types.Content, true)
|
||||
async get(domainId: string, page = 1, q = '') {
|
||||
@param('category', Types.Name, true, null, parseCategory)
|
||||
async get(domainId: string, page = 1, q = '', category = []) {
|
||||
this.response.template = 'problem_main.html';
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const query: FilterQuery<Pdoc> = {};
|
||||
@@ -66,9 +67,17 @@ export class ProblemMainHandler extends ProblemHandler {
|
||||
];
|
||||
const search = global.Hydro.lib.problemSearch;
|
||||
let sort: number[];
|
||||
if (q) {
|
||||
if (category.length) {
|
||||
query.$and = [];
|
||||
for (const tag of category) query.$and.push({ tag });
|
||||
}
|
||||
if (q) category.push(q);
|
||||
if (category.length) {
|
||||
this.extraTitleContent = category.join('+');
|
||||
path[1][1] = 'problem_main';
|
||||
path.push([`${this.translate('Keyword')}: ${q}`, null, null, true]);
|
||||
path.push([category.join('+'), null, null, true]);
|
||||
}
|
||||
if (q) {
|
||||
if (search) {
|
||||
const result = await search(domainId, q);
|
||||
query.docId = { $in: result };
|
||||
@@ -94,7 +103,7 @@ export class ProblemMainHandler extends ProblemHandler {
|
||||
);
|
||||
}
|
||||
this.response.body = {
|
||||
path, page, pcount, ppcount, pdocs, psdict, category: q,
|
||||
path, page, pcount, ppcount, pdocs, psdict, category: category.join('+'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -109,37 +118,41 @@ export class ProblemMainHandler extends ProblemHandler {
|
||||
await problem.setStar(domainId, pid, this.user._id, false);
|
||||
this.back({ star: false });
|
||||
}
|
||||
}
|
||||
|
||||
export class ProblemCategoryHandler extends ProblemHandler {
|
||||
@param('page', Types.PositiveInt, true)
|
||||
@param('category', Types.Name, null, parseCategory)
|
||||
async get(domainId: string, page = 1, category: string[]) {
|
||||
this.response.template = 'problem_main.html';
|
||||
const q: any = { $and: [] };
|
||||
for (const tag of category) q.$and.push({ tag });
|
||||
let psdict = {};
|
||||
if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) q.hidden = false;
|
||||
await bus.serial('problem/list', q, this);
|
||||
const [pdocs, ppcount, pcount] = await paginate(
|
||||
problem.getMulti(domainId, q).sort({ pid: 1, docId: 1 }),
|
||||
page,
|
||||
system.get('pagination.problem'),
|
||||
);
|
||||
if (this.user.hasPriv(PRIV.PRIV_USER_PROFILE)) {
|
||||
psdict = await problem.getListStatus(
|
||||
domainId, this.user._id, pdocs.map((pdoc) => pdoc.docId),
|
||||
);
|
||||
@param('pids', Types.NumericArray)
|
||||
async postDelete(domainId: string, pids: number[]) {
|
||||
for (const pid of pids) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const pdoc = await problem.get(domainId, pid);
|
||||
if (pdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_PROBLEM);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await problem.del(domainId, pid);
|
||||
}
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
['problem_main', 'problem_main'],
|
||||
[category, null, null, true],
|
||||
];
|
||||
this.response.body = {
|
||||
path, page, pcount, ppcount, pdocs, psdict, category: category.join('+'),
|
||||
};
|
||||
this.extraTitleContent = category.join('+');
|
||||
this.back();
|
||||
}
|
||||
|
||||
@param('pids', Types.NumericArray)
|
||||
async postHide(domainId: string, pids: number[]) {
|
||||
for (const pid of pids) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const pdoc = await problem.get(domainId, pid);
|
||||
if (pdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_PROBLEM);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await problem.edit(domainId, pid, { hidden: true });
|
||||
}
|
||||
this.back();
|
||||
}
|
||||
|
||||
@param('pids', Types.NumericArray)
|
||||
async postUnhide(domainId: string, pids: number[]) {
|
||||
for (const pid of pids) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const pdoc = await problem.get(domainId, pid);
|
||||
if (pdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_PROBLEM);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await problem.edit(domainId, pid, { hidden: false });
|
||||
}
|
||||
this.back();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,7 +612,7 @@ export class ProblemPrefixListHandler extends Handler {
|
||||
|
||||
export async function apply() {
|
||||
Route('problem_main', '/p', ProblemMainHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
Route('problem_category', '/p/category/:category', ProblemCategoryHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
Route('problem_category', '/p/category/:category', ProblemMainHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
Route('problem_random', '/problem/random', ProblemRandomHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
Route('problem_detail', '/p/:pid', ProblemDetailHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
Route('problem_submit', '/p/:pid/submit', ProblemSubmitHandler, PERM.PERM_SUBMIT_PROBLEM);
|
||||
|
||||
@@ -567,7 +567,7 @@ export class Handler extends HandlerCommon {
|
||||
}
|
||||
|
||||
async putResponse() {
|
||||
if (this.response.disposition) this.ctx.set('Content-Disposition', this.response.disposition);
|
||||
if (this.response.disposition) this.ctx.set('Content-Disposition', encodeURIComponent(this.response.disposition));
|
||||
if (!this.response.body) return;
|
||||
if (this.response.redirect && !this.request.json) {
|
||||
this.ctx.response.type = 'application/octet-stream';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@hydrooj/ui-default",
|
||||
"version": "4.6.21",
|
||||
"version": "4.6.22",
|
||||
"author": "undefined <i@undefined.moe>",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "hydro.js",
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { NamedPage } from 'vj/misc/Page';
|
||||
import Notification from 'vj/components/notification';
|
||||
import { ConfirmDialog } from 'vj/components/dialog';
|
||||
import Dropdown from 'vj/components/dropdown/Dropdown';
|
||||
import pjax from 'vj/utils/pjax';
|
||||
import substitute from 'vj/utils/substitute';
|
||||
import request from 'vj/utils/request';
|
||||
import tpl from 'vj/utils/tpl';
|
||||
import delay from 'vj/utils/delay';
|
||||
import i18n from 'vj/utils/i18n';
|
||||
|
||||
const categories = {};
|
||||
const dirtyCategories = [];
|
||||
@@ -56,6 +62,8 @@ async function updateSelection(sendRequest = true) {
|
||||
.join('+'), // build a beautiful URL
|
||||
});
|
||||
}
|
||||
const q = $('[name="q"]').val();
|
||||
if (q) url += `?q=${q}`;
|
||||
pjax.request({ url });
|
||||
}
|
||||
}
|
||||
@@ -160,9 +168,58 @@ function parseCategorySelection() {
|
||||
updateSelection(false);
|
||||
}
|
||||
|
||||
function ensureAndGetSelectedPids() {
|
||||
const pids = _.map(
|
||||
$(`tbody [data-checkbox-group="problem"]:checked`),
|
||||
(ch) => $(ch).closest('tr').attr('data-pid'),
|
||||
);
|
||||
if (pids.length === 0) {
|
||||
Notification.error(i18n('Please select at least one problem to perform this operation.'));
|
||||
return null;
|
||||
}
|
||||
return pids;
|
||||
}
|
||||
|
||||
async function handleOperation(operation) {
|
||||
const pids = ensureAndGetSelectedPids();
|
||||
if (pids === null) return;
|
||||
if (operation === 'delete') {
|
||||
const action = await new ConfirmDialog({
|
||||
$body: tpl`
|
||||
<div class="typo">
|
||||
<p>${i18n('Confirm to delete the selected problems?')}</p>
|
||||
</div>`,
|
||||
}).open();
|
||||
if (action !== 'yes') return;
|
||||
}
|
||||
try {
|
||||
await request.post('', { operation, pids });
|
||||
Notification.success(i18n(`Selected problems have been ${operation}d.`));
|
||||
await delay(2000);
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
Notification.error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
const page = new NamedPage(['problem_main', 'problem_category'], () => {
|
||||
const doc = document.documentElement;
|
||||
doc.className = doc.className + ' display-mode';
|
||||
buildCategoryFilter();
|
||||
parseCategorySelection();
|
||||
$('[name="leave-edit-mode"]').on('click', () => {
|
||||
doc.className = doc.className.replace(' edit-mode', ' display-mode');
|
||||
});
|
||||
$('[name="enter-edit-mode"]').on('click', () => {
|
||||
doc.className = doc.className.replace(' display-mode', ' edit-mode');
|
||||
});
|
||||
$('[name="remove_selected_problems"]').on('click', () => handleOperation('delete'));
|
||||
$('[name="hide_selected_problems"]').on('click', () => handleOperation('hide'));
|
||||
$('[name="unhide_selected_problems"]').on('click', () => handleOperation('unhide'));
|
||||
$('#search').on('click', (ev) => {
|
||||
ev.preventDefault();
|
||||
updateSelection();
|
||||
});
|
||||
});
|
||||
|
||||
export default page;
|
||||
@@ -1,8 +1,14 @@
|
||||
.page--problem_main
|
||||
.page--problem_category
|
||||
.page--problem_settings
|
||||
.col--checkbox
|
||||
width: rem(60px)
|
||||
|
||||
.col--pid
|
||||
width: rem(80px)
|
||||
|
||||
.col--status
|
||||
width: rem(150px)
|
||||
width: rem(140px)
|
||||
position: relative
|
||||
|
||||
.col--submit_n, .col--ac_rate, .col--difficulty
|
||||
@@ -97,3 +103,10 @@
|
||||
margin-left: 0
|
||||
margin-right: 0
|
||||
margin-top: rem(floor($menu-drop-triangle-size * -1.414))
|
||||
|
||||
|
||||
.display-mode .display-mode-hide
|
||||
display: none
|
||||
|
||||
.edit-mode .edit-mode-hide
|
||||
display: none
|
||||
@@ -1,5 +1,5 @@
|
||||
{% macro render_status_td(rdoc, rid_key='_id') %}
|
||||
<td class="col--status record-status--border {{ model.builtin.STATUS_CODES[rdoc.status] }}">
|
||||
{% macro render_status_td(rdoc, rid_key='_id', class='') %}
|
||||
<td class="col--status record-status--border {{ class }} {{ model.builtin.STATUS_CODES[rdoc.status] }}">
|
||||
<div class="col--status__text">
|
||||
<span class="icon record-status--icon {{ model.builtin.STATUS_CODES[rdoc.status] }}"></span>
|
||||
<a href="{{ url('record_detail', rid=rdoc[rid_key]) }}" class="record-status--text {{ model.builtin.STATUS_CODES[rdoc.status] }}">
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
{% else %}
|
||||
<table class="data-table">
|
||||
<colgroup>
|
||||
{% if handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %}
|
||||
<col class="col--checkbox display-mode-hide">
|
||||
<col class="col--pid display-mode-hide">
|
||||
{% endif %}
|
||||
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
||||
<col class="col--status">
|
||||
<col class="col--status edit-mode-hide">
|
||||
{% endif %}
|
||||
<col class="col--name">
|
||||
<col class="col--submit_n">
|
||||
@@ -18,8 +22,16 @@
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
{% if handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %}
|
||||
<th class="col--checkbox display-mode-hide">
|
||||
<label class="compact checkbox">
|
||||
<input type="checkbox" name="select_all" data-checkbox-toggle="problem">
|
||||
</label>
|
||||
</th>
|
||||
<th class="col--pid display-mode-hide">{{ _('ID') }}</th>
|
||||
{% endif %}
|
||||
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
||||
<th class="col--status record-status--border">{{ _('Status') }}</th>
|
||||
<th class="col--status record-status--border edit-mode-hide">{{ _('Status') }}</th>
|
||||
{% endif %}
|
||||
<th class="col--name">{{ _('Problem') }}</th>
|
||||
<th class="col--submit_n">{{ _('Submit') }}</th>
|
||||
@@ -28,17 +40,24 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{%- for pdoc in pdocs -%}
|
||||
{% set psdoc = psdict[pdoc['docId']] %}
|
||||
<tr>
|
||||
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
||||
{% if psdoc['rid'] %}
|
||||
{{ record.render_status_td(psdoc, rid_key='rid') }}
|
||||
{% else %}
|
||||
<td class="col--status record-status--border">
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{%- for pdoc in pdocs -%}
|
||||
{% set psdoc = psdict[pdoc['docId']] %}
|
||||
<tr data-pid="{{ pdoc.docId }}">
|
||||
{% if handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %}
|
||||
<td class="col--checkbox display-mode-hide">
|
||||
<label class="compact checkbox">
|
||||
<input type="checkbox" data-checkbox-group="problem" data-checkbox-range>
|
||||
</label>
|
||||
</td>
|
||||
<td class="col--pid display-mode-hide">{{ pdoc.docId }}</td>
|
||||
{% endif %}
|
||||
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
||||
{% if psdoc['rid'] %}
|
||||
{{ record.render_status_td(psdoc, rid_key='rid', class="edit-mode-hide") }}
|
||||
{% else %}
|
||||
<td class="col--status record-status--border edit-mode-hide"></td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<td class="col--name col--problem-name">
|
||||
{{ problem.render_problem_title(pdoc) }}
|
||||
</td>
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<div class="section__body">
|
||||
<button class="rounded button" name="download_selected_testdata">{{ _('Download Selected') }}</button>
|
||||
<button class="rounded button" name="remove_selected_testdata">{{ _('Remove Selected') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -129,5 +129,4 @@
|
||||
{% include "partials/problem_sidebar.html" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -16,14 +16,42 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="medium-3 columns">
|
||||
{% if handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %}
|
||||
<div class="section side">
|
||||
<ol class="menu">
|
||||
<a href="javascript:;" class="menu__link edit-mode-hide" name="enter-edit-mode">
|
||||
<span class="icon icon-edit"></span>
|
||||
{{ _('Enter Edit Mode') }}
|
||||
</a>
|
||||
<a href="javascript:;" class="menu__link display-mode-hide" name="leave-edit-mode">
|
||||
<span class="icon icon-edit"></span>
|
||||
{{ _('Leave Edit Mode') }}
|
||||
</a>
|
||||
{% if pdocs.length %}
|
||||
<a href="javascript:;" class="menu__link display-mode-hide" name="hide_selected_problems">
|
||||
<span class="icon icon-close"></span>
|
||||
{{ _('Hide Selected') }}
|
||||
</a>
|
||||
<a href="javascript:;" class="menu__link display-mode-hide" name="unhide_selected_problems">
|
||||
<span class="icon icon-check"></span>
|
||||
{{ _('Unhide Selected') }}
|
||||
</a>
|
||||
<a href="javascript:;" class="menu__link display-mode-hide" name="remove_selected_problems">
|
||||
<span class="icon icon-delete"></span>
|
||||
{{ _('Remove Selected') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</ol>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="section side">
|
||||
<div class="section__header">
|
||||
<div class="section__header edit-mode-hide">
|
||||
<h1 class="section__title">{{ _('Categories') }}</h1>
|
||||
</div>
|
||||
<div class="section__body">
|
||||
{% include "partials/category.html" %}
|
||||
</div>
|
||||
<div class="section__body">
|
||||
<div class="section__body edit-mode-hide">
|
||||
{% include "partials/problem_lucky.html" %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -36,7 +64,7 @@
|
||||
<label>
|
||||
<input name="q" type="text" class="textbox" value="{{ category }}" placeholder="1001">
|
||||
</label>
|
||||
<button type="submit" class="primary button">{{ _('Search') }}</button>
|
||||
<button type="submit" id="search" class="primary button">{{ _('Search') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user