ui: problem_files: pjax support

This commit is contained in:
undefined committed 2021-05-15 19:02:42 +08:00
1 parent c061c1ec93
commit 3e1d4635c4
16 files changed
+234 -184

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hydrooj",
"version": "2.26.0",
"version": "2.26.1",
"bin": "bin/hydrooj.js",
"main": "dist/loader.js",
"typings": "dist/loader.d.ts",
+2
View File
@@ -92,6 +92,7 @@ export const ValidationError = Err('ValidationError', ForbiddenError, function (
return 'Field {0} or {1} validation failed.';
});
export const ContestNotAttendedError = Err('ContestNotAttendedError', ForbiddenError, "You haven't attended this contest yet.");
export const RequireProError = Err('RequireProError', ForbiddenError, 'RequireProError');
export const ContestAlreadyAttendedError = Err('ContestAlreadyAttendedError', ForbiddenError, "You've already attended this contest.");
export const ContestNotLiveError = Err('ContestNotLiveError', ForbiddenError, 'This contest is not live.');
export const ContestScoreboardHiddenError = Err('ContestScoreboardHiddenError', ForbiddenError, 'Contest scoreboard is not visible.');
@@ -142,6 +143,7 @@ global.Hydro.error = {
OpcountExceededError,
PermissionError,
PrivilegeError,
RequireProError,
NoProblemError,
ValidationError,
ProblemNotFoundError,
+13 -2
View File
@@ -379,13 +379,24 @@ export class ProblemEditHandler extends ProblemManageHandler {
export class ProblemFilesHandler extends ProblemDetailHandler {
@param('testdata', Types.Boolean)
@param('additional_file', Types.Boolean)
async get(domainId: string, getTestdata = true, getAdditionalFile = true) {
@param('pjax', Types.Boolean)
async get(domainId: string, getTestdata = true, getAdditionalFile = true, pjax = false) {
const canReadData = this.user.own(this.pdoc)
|| this.user.hasPriv(PRIV.PRIV_READ_PROBLEM_DATA)
|| this.user.hasPerm(PERM.PERM_READ_PROBLEM_DATA);
this.response.body.testdata = (getTestdata && canReadData) ? sortFiles(this.pdoc.data || []) : [];
this.response.body.additional_file = getAdditionalFile ? sortFiles(this.pdoc.additional_file || []) : [];
this.response.template = 'problem_files.html';
if (pjax) {
const { testdata, additional_file } = this.response.body;
this.response.body = {
fragments: (await Promise.all([
this.renderHTML('partials/problem_files-testdata.html', { testdata, pdoc: this.pdoc }),
this.renderHTML('partials/problem_files-additional_file.html', { additional_file, pdoc: this.pdoc }),
this.renderHTML('partials/problem-sidebar-information.html', { pdoc: this.pdoc }),
])).map((i) => ({ html: i })),
};
this.response.template = '';
} else this.response.template = 'problem_files.html';
}
@post('files', Types.Set)
+6 -3
View File
@@ -28,6 +28,7 @@ for (const lang in global.Hydro.locales) {
export const FLAG_HIDDEN = 1;
export const FLAG_DISABLED = 2;
export const FLAG_SECRET = 4;
export const FLAG_PRO = 8;
export const PREFERENCE_SETTINGS: _Setting[] = [];
export const ACCOUNT_SETTINGS: _Setting[] = [];
@@ -167,7 +168,8 @@ SystemSetting(
Setting('setting_server', 'server.center', 'https://hydro.undefined.moe:8443/center', 'text', 'server.center', '', FLAG_HIDDEN),
Setting('setting_server', 'server.proxy', false, 'boolean', 'server.proxy', 'Use hydro center proxy to send problems'),
Setting('setting_server', 'server.name', 'Hydro', 'text', 'server.name', 'Server Name'),
Setting('setting_server', 'server.worker', 1, 'number', 'server.worker', 'Server Workers Number'),
Setting('setting_server', 'server.displayName', 'Hydro', 'text', 'server.name', 'Server Name (Global Display)', FLAG_PRO),
Setting('setting_server', 'server.worker', 1, 'number', 'server.worker', 'Server Workers Number', FLAG_PRO),
Setting('setting_server', 'server.hostname', 'oj.undefined.moe', 'text', 'server.hostname', 'Server Hostname'),
Setting('setting_server', 'server.host', 'oj.undefined.moe', 'text', 'server.host', 'Server Host'),
Setting('setting_server', 'server.url', '/', 'text', 'server.url', 'Server BaseURL'),
@@ -176,9 +178,9 @@ SystemSetting(
Setting('setting_server', 'server.xff', null, 'text', 'server.xff', 'IP Header'),
Setting('setting_server', 'server.xhost', null, 'text', 'server.xhost', 'Hostname Header'),
Setting('setting_server', 'server.language', 'zh_CN', langRange, 'server.language', 'Default display language'),
Setting('setting_server', 'server.login', true, 'boolean', 'server.login', 'Allow builtin-login'),
Setting('setting_server', 'server.login', true, 'boolean', 'server.login', 'Allow builtin-login', FLAG_PRO),
Setting('setting_limits', 'limit.problem_files_max', 100, 'number', 'limit.problem_files_max', 'Max files per problem'),
Setting('setting_basic', 'default.priv', builtin.PRIV.PRIV_DEFAULT, 'number', 'default.priv', 'Default Privilege'),
Setting('setting_basic', 'default.priv', builtin.PRIV.PRIV_DEFAULT, 'number', 'default.priv', 'Default Privilege', FLAG_PRO),
Setting('setting_basic', 'problem.categories', builtin.CATEGORIES, 'yaml', 'problem.categories', 'Problem Categories'),
Setting('setting_basic', 'pagination.problem', 100, 'number', 'pagination.problem', 'Problems per page'),
Setting('setting_basic', 'pagination.contest', 20, 'number', 'pagination.contest', 'Contests per page'),
@@ -244,6 +246,7 @@ global.Hydro.model.setting = {
FLAG_HIDDEN,
FLAG_DISABLED,
FLAG_SECRET,
FLAG_PRO,
PREFERENCE_SETTINGS,
ACCOUNT_SETTINGS,
SETTINGS,
+1 -1
View File
@@ -50,7 +50,7 @@ export class StorageModel {
static async list(target: string, recursive = true) {
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path');
if (target?.length && !target.endsWith('/')) target += '/';
if (target.length && !target.endsWith('/')) target += '/';
const results = recursive
? await StorageModel.coll.find({ path: { $regex: new RegExp(`^${escapeRegExp(target)}`, 'i') } }).toArray()
: await StorageModel.coll.find({ path: { $regex: new RegExp(`^${escapeRegExp(target)}[^/]+$`) } }).toArray();
@@ -1,6 +1,19 @@
import { Position, Toaster, Intent } from '@blueprintjs/core';
import tpl from 'vj/utils/tpl';
import zIndexManager from 'vj/utils/zIndexManager';
const ToasterContainer = document.createElement('div');
ToasterContainer.style.position = 'fixed';
ToasterContainer.style.bottom = '0px';
ToasterContainer.style.zIndex = '9999';
document.body.append(ToasterContainer);
export const AppToaster = Toaster.create({
className: 'recipe-toaster',
position: Position.LEFT_BOTTOM,
usePortal: true,
}, ToasterContainer);
export default class Notification {
constructor({
avatar, title, message, type = '', duration = 3000, action,
@@ -37,19 +50,19 @@ export default class Notification {
}
static success(message, duration) {
return new Notification({ type: 'success', message, duration }).show();
return AppToaster.show({ message, timeout: duration, intent: Intent.SUCCESS });
}
static info(message, duration) {
return new Notification({ type: 'info', message, duration }).show();
return AppToaster.show({ message, timeout: duration, intent: Intent.PRIMARY });
}
static warn(message, duration) {
return new Notification({ type: 'warn', message, duration }).show();
return AppToaster.show({ message, timeout: duration, intent: Intent.WARNING });
}
static error(message, duration) {
return new Notification({ type: 'error', message, duration }).show();
return AppToaster.show({ message, timeout: duration, intent: Intent.DANGER });
}
}
+1
View File
@@ -8,6 +8,7 @@
"allowSyntheticDefaultImports": true,
"target": "ES6",
"baseUrl": ".",
"moduleResolution": "node",
"paths": {
"vj/*": [
"./*"
+3 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/ui-default",
"version": "4.8.13",
"version": "4.8.14",
"author": "undefined <i@undefined.moe>",
"license": "AGPL-3.0",
"main": "hydro.js",
@@ -22,6 +22,8 @@
"@babel/preset-react": "^7.13.13",
"@babel/register": "^7.13.14",
"@babel/runtime-corejs3": "^7.14.0",
"@blueprintjs/core": "^3.44.2",
"@blueprintjs/icons": "^3.26.0",
"@hydrooj/utils": "^1.0.15",
"@undefined-moe/monaco-yaml": "^2.5.0",
"ansi_up": "^5.0.1",
+22 -15
View File
@@ -6,9 +6,9 @@ import { NamedPage } from 'vj/misc/Page';
import Notification from 'vj/components/notification';
import { ConfirmDialog, ActionDialog } from 'vj/components/dialog/index';
import request from 'vj/utils/request';
import pjax from 'vj/utils/pjax';
import pipeStream from 'vj/utils/pipeStream';
import tpl from 'vj/utils/tpl';
import delay from 'vj/utils/delay';
import i18n from 'vj/utils/i18n';
// Firefox have no WritableStream
@@ -96,8 +96,7 @@ const page = new NamedPage('problem_files', () => {
}
await Promise.all(tasks);
Notification.success('File uploaded successfully.');
await delay(2000);
window.location.reload();
await pjax.request({ push: false });
} catch (e) {
console.error(e);
Notification.error(`File upload fail: ${e.toString()}`);
@@ -127,8 +126,7 @@ const page = new NamedPage('problem_files', () => {
type,
});
Notification.success(i18n('Selected files have been deleted.'));
await delay(2000);
window.location.reload();
await pjax.request({ push: false });
} catch (error) {
Notification.error(error.message);
}
@@ -189,14 +187,22 @@ const page = new NamedPage('problem_files', () => {
* @param {JQuery.ClickEvent<HTMLElement, undefined, HTMLElement, HTMLElement>} ev
*/
async function handleEdit(type, ev) {
ev.preventDefault();
const filename = ev.currentTarget.closest('[data-filename]').getAttribute('data-filename');
const filesize = +ev.currentTarget.closest('[data-size]').getAttribute('data-size');
const link = $(ev.currentTarget).find('a').attr('href');
if (filesize > 8 * 1024 * 1024) Notification.error(i18n('file too large'));
Notification.info(i18n('Loading file...'));
const res = await request.get(link);
const content = await request.get(res.url, undefined, { dataType: 'text' });
if (ev) ev.preventDefault();
const filename = ev
? ev.currentTarget.closest('[data-filename]').getAttribute('data-filename')
// eslint-disable-next-line no-alert
: prompt('Filename');
const filesize = ev
? +ev.currentTarget.closest('[data-size]').getAttribute('data-size')
: 0;
let content = '';
if (ev) {
const link = $(ev.currentTarget).find('a').attr('href');
if (filesize > 8 * 1024 * 1024) Notification.error(i18n('file too large'));
Notification.info(i18n('Loading file...'));
const res = await request.get(link);
content = await request.get(res.url, undefined, { dataType: 'text' });
} else Notification.info(i18n('Loading editor...'));
const val = await startEdit(filename, content);
console.log(val);
if (typeof val !== 'string') return;
@@ -208,8 +214,7 @@ const page = new NamedPage('problem_files', () => {
data.append('operation', 'upload_file');
await request.postFile('', data);
Notification.success(i18n('File saved.'));
await delay(2000);
window.location.reload();
await pjax.request({ push: false });
}
$('.problem-files-testdata .col--name').on('click', (ev) => handleEdit('testdata', ev));
@@ -219,9 +224,11 @@ const page = new NamedPage('problem_files', () => {
$('.problem-files-testdata').on('drop', (ev) => handleDrop('testdata', ev));
$('.problem-files-additional_file').on('drop', (ev) => handleDrop('additional_file', ev));
$('[name="upload_testdata"]').on('click', () => handleClickUpload('testdata'));
$('[name="create_testdata"]').on('click', () => handleEdit('testdata'));
$('[name="download_selected_testdata"]').on('click', () => handleClickDownloadSelected('testdata'));
$('[name="remove_selected_testdata"]').on('click', () => handleClickRemoveSelected('testdata'));
$('[name="upload_file"]').on('click', () => handleClickUpload('additional_file'));
$('[name="create_file"]').on('click', () => handleEdit('additional_file'));
$('[name="download_selected_file"]').on('click', () => handleClickDownloadSelected('additional_file'));
$('[name="remove_selected_file"]').on('click', () => handleClickRemoveSelected('additional_file'));
});
@@ -0,0 +1,66 @@
{% import "components/record.html" as record with context %}
{% import "components/problem.html" as problem with context %}
{% import "components/user.html" as user with context %}
<div class="section__body typo" data-fragment-id="problem-sidebar-information">
<dl class="large horizontal">
<dt>{{ _('ID') }}</dt>
<dd>{{ pdoc.docId }}</dd>
<dt>{{ _('Time') }}</dt>
<dd>{{ pdoc.config.timeMax if pdoc.config.timeMax == pdoc.config.timeMin else pdoc.config.timeMin+'~'+pdoc.config.timeMax }}ms</dd>
<dt>{{ _('Memory') }}</dt>
<dd>{{ pdoc.config.memoryMax if pdoc.config.memoryMax == pdoc.config.memoryMin else pdoc.config.memoryMin+'~'+pdoc.config.memoryMax }}MiB</dd>
{% if page_name != 'discussion_node' and page_name != 'discussion_detail' %}
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
<dt>{{ _('Submission') }}</dt>
<dd>
{% if pdoc.psdoc.status %}
<span class="icon record-status--icon {{ model.builtin.STATUS_CODES[pdoc['psdoc']['status']] }}"></span>
<a href="{{ url('record_detail', rid=pdoc.psdoc.rid) }}" class="record-status--text {{ model.builtin.STATUS_CODES[pdoc['psdoc']['status']] }}">
{{ model.builtin.STATUS_TEXTS[pdoc['psdoc']['status']] }}
</a>
{% else %}
{{ _('(None)') }}
{% endif %}
</dd>
{% endif %}
{% endif %}
<dt>{{ _('Difficulty') }}</dt>
<dd>{{ pdoc['difficulty'] or _('(None)') }}</dd>
<dt>{{ _('Category') }}</dt>
<dd>
{% if pdoc['category'] %}
<span class="hasjs--hide" name="problem-sidebar__categories">
{%- for category in pdoc['category'] -%}
<a href="{{ url('problem_category', category=category) }}">{{ category }}</a>
{% if loop.index < pdoc['category']|length %}
{{ _('problem-subcategory-delim') }}
{% endif %}
{%- endfor -%}
</span>
<span class="nojs--hide">
<a href="javascript:;" name="problem-sidebar__show-category">{{ _('page.problem_detail.sidebar.show_category') }}</a>
</span>
{% else %}
{{ _('(None)') }}
{% endif %}
</dd>
<dt>{{ _('Tags') }}</dt>
<dd>
{{ problem.render_problem_tags(pdoc, true) }}
</dd>
<dt>{{ _('# Submissions') }}</dt>
<dd><a href="{{ url('record_main', query={pid:pdoc.docId}) }}">{{ pdoc.nSubmit }}</a></dd>
{% if page_name != 'discussion_node' and page_name != 'discussion_detail' %}
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
<dt>{{ _('# My Submissions') }}</dt>
<dd><a href="{{ url('record_main', query={pid:pdoc.docId, uidOrName:handler.user._id}) }}">{{ pdoc['psdoc'].nSubmit|default(0) }}</a></dd>
{% endif %}
{% endif %}
<dt>{{ _('Accepted') }}</dt>
<dd>{{ pdoc.nAccept }}</dd>
<dt>{{ _('Accepted Ratio') }}</dt>
<dd>{% if pdoc.nSubmit > 0 %}{{ (100 * pdoc.nAccept / pdoc.nSubmit)|round|int }}%{% else %}{{ _('?') }}{% endif %}</dd>
<dt>{{ _('Uploaded By') }}</dt>
<dd>{{ user.render_inline(owner_udoc, badge=false) }}</dd>
</dl>
</div>
@@ -0,0 +1,40 @@
{% import "components/nothing.html" as nothing with context %}
<div class="section__body no-padding problem-files-additional_file" data-fragment-id="problem-files-additional_file">
{% if additional_file.length %}
<table class="data-table">
<colgroup>
<col class="col--checkbox">
<col class="col--name">
<col class="col--size">
</colgroup>
<thead>
<tr>
<th class="col--checkbox">
<label class="compact checkbox">
<input type="checkbox" name="select_all" data-checkbox-toggle="additional_file">
</label>
</th>
<th class="col--name">{{ _('Filename') }}</th>
<th class="col--size">{{ _('Size') }}</th>
</tr>
</thead>
<tbody>
{%- for file in additional_file -%}
<tr data-filename="{{ file.name }}" data-size="{{ file.size }}">
<td class="col--checkbox">
<label class="compact checkbox">
<input type="checkbox" data-checkbox-group="additional_file" data-checkbox-range>
</label>
</td>
<td class="col--name">
<a href="{{ url('problem_file_download', {pid: pdoc.docId, filename: file.name}) }}">{{ file.name }}</a>
</td>
<td class="col--size">{{ size(file.size) }}</td>
</tr>
{%- endfor -%}
</tbody>
</table>
{% else %}
{{ nothing.render('No additional file at current.') }}
{% endif %}
</div>
@@ -0,0 +1,44 @@
{% import "components/nothing.html" as nothing with context %}
<div class="section__body no-padding problem-files-testdata" data-fragment-id="problem-files-testdata">
{% if testdata.length %}
<table class="data-table">
<colgroup>
<col class="col--checkbox">
<col class="col--name">
<col class="col--size">
</colgroup>
<thead>
<tr>
<th class="col--checkbox">
<label class="compact checkbox">
<input type="checkbox" name="select_all" data-checkbox-toggle="testdata">
</label>
</th>
<th class="col--name">{{ _('Filename') }}</th>
<th class="col--size">{{ _('Size') }}</th>
</tr>
</thead>
<tbody>
{%- for file in testdata -%}
<tr data-filename="{{ file.name }}" data-size="{{ file.size }}">
<td class="col--checkbox">
<label class="compact checkbox">
<input type="checkbox" data-checkbox-group="testdata" data-checkbox-range>
</label>
</td>
<td class="col--name">
<a href="{{ url('problem_file_download', {pid: pdoc.docId, filename: file.name, query:{type:'testdata'} }) }}">{{ file.name }}</a>
</td>
<td class="col--size">{{ size(file.size) }}</td>
</tr>
{%- endfor -%}
</tbody>
</table>
{% else %}
{% if handler.user.own(pdoc) or handler.user.hasPriv(PRIV.PRIV_READ_PROBLEM_DATA) or handler.user.hasPerm(perm.PERM_READ_PROBLEM_DATA) %}
{{ nothing.render('No testdata at current.') }}
{% else %}
{{ nothing.render("You don't have permission to read testdata.")}}
{% endif %}
{% endif %}
</div>
@@ -110,69 +110,7 @@
<div class="section__header">
<h1 class="section__title">{{ _('Information') }}</h1>
</div>
<div class="section__body typo">
<dl class="large horizontal">
<dt>{{ _('ID') }}</dt>
<dd>{{ pdoc.docId }}</dd>
<dt>{{ _('Time') }}</dt>
<dd>{{ pdoc.config.timeMax if pdoc.config.timeMax == pdoc.config.timeMin else pdoc.config.timeMin+'~'+pdoc.config.timeMax }}ms</dd>
<dt>{{ _('Memory') }}</dt>
<dd>{{ pdoc.config.memoryMax if pdoc.config.memoryMax == pdoc.config.memoryMin else pdoc.config.memoryMin+'~'+pdoc.config.memoryMax }}MiB</dd>
{% if page_name != 'discussion_node' and page_name != 'discussion_detail' %}
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
<dt>{{ _('Submission') }}</dt>
<dd>
{% if pdoc.psdoc.status %}
<span class="icon record-status--icon {{ model.builtin.STATUS_CODES[pdoc['psdoc']['status']] }}"></span>
<a href="{{ url('record_detail', rid=pdoc.psdoc.rid) }}" class="record-status--text {{ model.builtin.STATUS_CODES[pdoc['psdoc']['status']] }}">
{{ model.builtin.STATUS_TEXTS[pdoc['psdoc']['status']] }}
</a>
{% else %}
{{ _('(None)') }}
{% endif %}
</dd>
{% endif %}
{% endif %}
<dt>{{ _('Difficulty') }}</dt>
<dd>{{ pdoc['difficulty'] or _('(None)') }}</dd>
<dt>{{ _('Category') }}</dt>
<dd>
{% if pdoc['category'] %}
<span class="hasjs--hide" name="problem-sidebar__categories">
{%- for category in pdoc['category'] -%}
<a href="{{ url('problem_category', category=category) }}">{{ category }}</a>
{% if loop.index < pdoc['category']|length %}
{{ _('problem-subcategory-delim') }}
{% endif %}
{%- endfor -%}
</span>
<span class="nojs--hide">
<a href="javascript:;" name="problem-sidebar__show-category">{{ _('page.problem_detail.sidebar.show_category') }}</a>
</span>
{% else %}
{{ _('(None)') }}
{% endif %}
</dd>
<dt>{{ _('Tags') }}</dt>
<dd>
{{ problem.render_problem_tags(pdoc, true) }}
</dd>
<dt>{{ _('# Submissions') }}</dt>
<dd><a href="{{ url('record_main', query={pid:pdoc.docId}) }}">{{ pdoc.nSubmit }}</a></dd>
{% if page_name != 'discussion_node' and page_name != 'discussion_detail' %}
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
<dt>{{ _('# My Submissions') }}</dt>
<dd><a href="{{ url('record_main', query={pid:pdoc.docId, uidOrName:handler.user._id}) }}">{{ pdoc['psdoc'].nSubmit|default(0) }}</a></dd>
{% endif %}
{% endif %}
<dt>{{ _('Accepted') }}</dt>
<dd>{{ pdoc.nAccept }}</dd>
<dt>{{ _('Accepted Ratio') }}</dt>
<dd>{% if pdoc.nSubmit > 0 %}{{ (100 * pdoc.nAccept / pdoc.nSubmit)|round|int }}%{% else %}{{ _('?') }}{% endif %}</dd>
<dt>{{ _('Uploaded By') }}</dt>
<dd>{{ user.render_inline(owner_udoc, badge=false) }}</dd>
</dl>
</div>
{% include "partials/problem-sidebar-information.html" %}
</div>
{% if page_name == 'problem_detail' and (tdocs or ctdocs) %}
<div class="section side">
@@ -7,54 +7,13 @@
<h1 class="section__title">{{ _('Testdata') }}</h1>
<div class="section__tools">
{% if handler.user.own(pdoc, perm.PERM_EDIT_PROBLEM_SELF) or handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %}
<button class="primary rounded button" name="upload_testdata">{{ _('Add File') }}</button>
<button class="primary rounded button" name="create_testdata">{{ _('Create File') }}</button>
<button class="primary rounded button" name="upload_testdata">{{ _('Upload File') }}</button>
{% endif %}
</div>
</div>
{{ noscript_note.render() }}
<div class="section__body no-padding problem-files-testdata">
{% if testdata.length %}
<table class="data-table">
<colgroup>
<col class="col--checkbox">
<col class="col--name">
<col class="col--size">
</colgroup>
<thead>
<tr>
<th class="col--checkbox">
<label class="compact checkbox">
<input type="checkbox" name="select_all" data-checkbox-toggle="testdata">
</label>
</th>
<th class="col--name">{{ _('Filename') }}</th>
<th class="col--size">{{ _('Size') }}</th>
</tr>
</thead>
<tbody>
{%- for file in testdata -%}
<tr data-filename="{{ file.name }}" data-size="{{ file.size }}">
<td class="col--checkbox">
<label class="compact checkbox">
<input type="checkbox" data-checkbox-group="testdata" data-checkbox-range>
</label>
</td>
<td class="col--name">
<a href="{{ url('problem_file_download', {pid: pdoc.docId, filename: file.name, query:{type:'testdata'} }) }}">{{ file.name }}</a>
</td>
<td class="col--size">{{ size(file.size) }}</td>
</tr>
{%- endfor -%}
</tbody>
</table>
{% else %}
{% if handler.user.own(pdoc) or handler.user.hasPriv(PRIV.PRIV_READ_PROBLEM_DATA) or handler.user.hasPerm(perm.PERM_READ_PROBLEM_DATA) %}
{{ nothing.render('No testdata at current.') }}
{% else %}
{{ nothing.render("You don't have permission to read testdata.")}}
{% endif %}
{% endif %}
</div>
{% include "partials/problem_files-testdata.html" %}
{% if handler.user.own(pdoc, perm.PERM_EDIT_PROBLEM_SELF) or handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %}
{% if testdata.length %}
<div class="section__body">
@@ -71,49 +30,12 @@
<h1 class="section__title">{{ _('Additional Files') }}</h1>
<div class="section__tools">
{% if handler.user.own(pdoc, perm.PERM_EDIT_PROBLEM_SELF) or handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %}
<button class="primary rounded button" name="upload_file">{{ _('Add File') }}</button>
<button class="primary rounded button" name="create_file">{{ _('Create File') }}</button>
<button class="primary rounded button" name="upload_file">{{ _('Upload File') }}</button>
{% endif %}
</div>
</div>
<div class="section__body no-padding problem-files-additional_file">
{% if additional_file.length %}
<table class="data-table">
<colgroup>
<col class="col--checkbox">
<col class="col--name">
<col class="col--size">
</colgroup>
<thead>
<tr>
<th class="col--checkbox">
<label class="compact checkbox">
<input type="checkbox" name="select_all" data-checkbox-toggle="additional_file">
</label>
</th>
<th class="col--name">{{ _('Filename') }}</th>
<th class="col--size">{{ _('Size') }}</th>
</tr>
</thead>
<tbody>
{%- for file in additional_file -%}
<tr data-filename="{{ file.name }}" data-size="{{ file.size }}">
<td class="col--checkbox">
<label class="compact checkbox">
<input type="checkbox" data-checkbox-group="additional_file" data-checkbox-range>
</label>
</td>
<td class="col--name">
<a href="{{ url('problem_file_download', {pid: pdoc.docId, filename: file.name}) }}">{{ file.name }}</a>
</td>
<td class="col--size">{{ size(file.size) }}</td>
</tr>
{%- endfor -%}
</tbody>
</table>
{% else %}
{{ nothing.render('No additional file at current.') }}
{% endif %}
</div>
{% include "partials/problem_files-additional_file.html" %}
{% if additional_file.length %}
<div class="section__body">
<button class="rounded button" name="download_selected_file">{{ _('Download Selected') }}</button>
+2
View File
@@ -4,6 +4,8 @@ import 'pickadate/lib/themes/classic.css';
import 'pickadate/lib/themes/classic.date.css';
import 'pickadate/lib/themes/classic.time.css';
import 'katex/dist/katex.min.css';
import '@blueprintjs/core/lib/css/blueprint.css';
import '@blueprintjs/icons/lib/css/blueprint-icons.css';
import 'vj/misc/float.styl';
import 'vj/misc/typography.styl';
+9 -10
View File
@@ -70,12 +70,15 @@ pjax.request = async (opt) => {
}
incProgress();
try {
const data = await request.ajax({
const params = {
beforeSend(xhr) {
currentXHR = xhr;
},
...options,
});
};
if (params.url && params.url.includes('?')) params.url += '&pjax=1';
else params.url = `${params.url || ''}?pjax=1`;
const data = await request.ajax(params);
const meta = extractMetaData(options, data);
currentState = {
id: uuid(),
@@ -84,7 +87,7 @@ pjax.request = async (opt) => {
if (options.push) {
window.history.replaceState(currentState, null, meta.url);
}
document.title = meta.title;
if (meta.title) document.title = meta.title;
data.fragments.forEach((fragment) => {
if (process.env.NODE_ENV !== 'production') {
if (fragment.html === undefined) {
@@ -120,7 +123,7 @@ pjax.request = async (opt) => {
} catch (err) {
if (!err.aborted) {
Notification.error(err.message);
console.log(err);
console.error(err);
}
} finally {
decProgress();
@@ -128,13 +131,9 @@ pjax.request = async (opt) => {
};
function handlePopState(ev) {
if (!ev.originalEvent) {
return;
}
if (!ev.originalEvent) return;
const { state } = ev.originalEvent;
if (!state) {
return;
}
if (!state) return;
if (!state.id || (currentState && state.id === currentState.id)) {
return;
}