From 3e1d4635c4327a6efeab2a7d6cb75ae5c63b573f Mon Sep 17 00:00:00 2001 From: undefined Date: Sat, 15 May 2021 19:02:42 +0800 Subject: [PATCH] ui: problem_files: pjax support --- packages/hydrooj/package.json | 2 +- packages/hydrooj/src/error.ts | 2 + packages/hydrooj/src/handler/problem.ts | 15 +++- packages/hydrooj/src/model/setting.ts | 9 +- packages/hydrooj/src/model/storage.ts | 2 +- .../components/notification/index.js | 21 ++++- packages/ui-default/jsconfig.json | 1 + packages/ui-default/package.json | 4 +- .../ui-default/pages/problem_files.page.js | 37 ++++---- .../partials/problem-sidebar-information.html | 66 ++++++++++++++ .../problem_files-additional_file.html | 40 +++++++++ .../partials/problem_files-testdata.html | 44 +++++++++ .../partials/problem_sidebar_normal.html | 64 +------------ .../ui-default/templates/problem_files.html | 90 ++----------------- packages/ui-default/theme/default.js | 2 + packages/ui-default/utils/pjax.js | 19 ++-- 16 files changed, 234 insertions(+), 184 deletions(-) create mode 100644 packages/ui-default/templates/partials/problem-sidebar-information.html create mode 100644 packages/ui-default/templates/partials/problem_files-additional_file.html create mode 100644 packages/ui-default/templates/partials/problem_files-testdata.html diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 6a99df0ee..661a178cf 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -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", diff --git a/packages/hydrooj/src/error.ts b/packages/hydrooj/src/error.ts index 975d43a1a..c5ea707b9 100644 --- a/packages/hydrooj/src/error.ts +++ b/packages/hydrooj/src/error.ts @@ -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, diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index dadae11da..6443c2a53 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -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) diff --git a/packages/hydrooj/src/model/setting.ts b/packages/hydrooj/src/model/setting.ts index 3c8879ef0..4fefb91ea 100644 --- a/packages/hydrooj/src/model/setting.ts +++ b/packages/hydrooj/src/model/setting.ts @@ -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, diff --git a/packages/hydrooj/src/model/storage.ts b/packages/hydrooj/src/model/storage.ts index 3d43acfec..54de154de 100644 --- a/packages/hydrooj/src/model/storage.ts +++ b/packages/hydrooj/src/model/storage.ts @@ -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(); diff --git a/packages/ui-default/components/notification/index.js b/packages/ui-default/components/notification/index.js index 6fef5f78a..4a9fadc74 100644 --- a/packages/ui-default/components/notification/index.js +++ b/packages/ui-default/components/notification/index.js @@ -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 }); } } diff --git a/packages/ui-default/jsconfig.json b/packages/ui-default/jsconfig.json index f1c6a4a3b..3d07a47c8 100644 --- a/packages/ui-default/jsconfig.json +++ b/packages/ui-default/jsconfig.json @@ -8,6 +8,7 @@ "allowSyntheticDefaultImports": true, "target": "ES6", "baseUrl": ".", + "moduleResolution": "node", "paths": { "vj/*": [ "./*" diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 1e2f9c6a3..dee0a467b 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.8.13", + "version": "4.8.14", "author": "undefined ", "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", diff --git a/packages/ui-default/pages/problem_files.page.js b/packages/ui-default/pages/problem_files.page.js index 43547dc0e..76d62d8f6 100644 --- a/packages/ui-default/pages/problem_files.page.js +++ b/packages/ui-default/pages/problem_files.page.js @@ -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} 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')); }); diff --git a/packages/ui-default/templates/partials/problem-sidebar-information.html b/packages/ui-default/templates/partials/problem-sidebar-information.html new file mode 100644 index 000000000..ddaff647b --- /dev/null +++ b/packages/ui-default/templates/partials/problem-sidebar-information.html @@ -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 %} +
+
+
{{ _('ID') }}
+
{{ pdoc.docId }}
+
{{ _('Time') }}
+
{{ pdoc.config.timeMax if pdoc.config.timeMax == pdoc.config.timeMin else pdoc.config.timeMin+'~'+pdoc.config.timeMax }}ms
+
{{ _('Memory') }}
+
{{ pdoc.config.memoryMax if pdoc.config.memoryMax == pdoc.config.memoryMin else pdoc.config.memoryMin+'~'+pdoc.config.memoryMax }}MiB
+ {% if page_name != 'discussion_node' and page_name != 'discussion_detail' %} + {% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %} +
{{ _('Submission') }}
+
+ {% if pdoc.psdoc.status %} + + + {{ model.builtin.STATUS_TEXTS[pdoc['psdoc']['status']] }} + + {% else %} + {{ _('(None)') }} + {% endif %} +
+ {% endif %} + {% endif %} +
{{ _('Difficulty') }}
+
{{ pdoc['difficulty'] or _('(None)') }}
+
{{ _('Category') }}
+
+ {% if pdoc['category'] %} + + {%- for category in pdoc['category'] -%} + {{ category }} + {% if loop.index < pdoc['category']|length %} + {{ _('problem-subcategory-delim') }} + {% endif %} + {%- endfor -%} + + + {{ _('page.problem_detail.sidebar.show_category') }} + + {% else %} + {{ _('(None)') }} + {% endif %} +
+
{{ _('Tags') }}
+
+ {{ problem.render_problem_tags(pdoc, true) }} +
+
{{ _('# Submissions') }}
+
{{ pdoc.nSubmit }}
+ {% if page_name != 'discussion_node' and page_name != 'discussion_detail' %} + {% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %} +
{{ _('# My Submissions') }}
+
{{ pdoc['psdoc'].nSubmit|default(0) }}
+ {% endif %} + {% endif %} +
{{ _('Accepted') }}
+
{{ pdoc.nAccept }}
+
{{ _('Accepted Ratio') }}
+
{% if pdoc.nSubmit > 0 %}{{ (100 * pdoc.nAccept / pdoc.nSubmit)|round|int }}%{% else %}{{ _('?') }}{% endif %}
+
{{ _('Uploaded By') }}
+
{{ user.render_inline(owner_udoc, badge=false) }}
+
+
\ No newline at end of file diff --git a/packages/ui-default/templates/partials/problem_files-additional_file.html b/packages/ui-default/templates/partials/problem_files-additional_file.html new file mode 100644 index 000000000..d57bbc238 --- /dev/null +++ b/packages/ui-default/templates/partials/problem_files-additional_file.html @@ -0,0 +1,40 @@ +{% import "components/nothing.html" as nothing with context %} +
+ {% if additional_file.length %} + + + + + + + + + + + + + + + {%- for file in additional_file -%} + + + + + + {%- endfor -%} + +
+ + {{ _('Filename') }}{{ _('Size') }}
+ + + {{ file.name }} + {{ size(file.size) }}
+ {% else %} + {{ nothing.render('No additional file at current.') }} + {% endif %} +
\ No newline at end of file diff --git a/packages/ui-default/templates/partials/problem_files-testdata.html b/packages/ui-default/templates/partials/problem_files-testdata.html new file mode 100644 index 000000000..2225c6ef1 --- /dev/null +++ b/packages/ui-default/templates/partials/problem_files-testdata.html @@ -0,0 +1,44 @@ +{% import "components/nothing.html" as nothing with context %} +
+ {% if testdata.length %} + + + + + + + + + + + + + + + {%- for file in testdata -%} + + + + + + {%- endfor -%} + +
+ + {{ _('Filename') }}{{ _('Size') }}
+ + + {{ file.name }} + {{ size(file.size) }}
+ {% 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 %} +
\ No newline at end of file diff --git a/packages/ui-default/templates/partials/problem_sidebar_normal.html b/packages/ui-default/templates/partials/problem_sidebar_normal.html index 742170714..f0fad50f1 100644 --- a/packages/ui-default/templates/partials/problem_sidebar_normal.html +++ b/packages/ui-default/templates/partials/problem_sidebar_normal.html @@ -110,69 +110,7 @@

{{ _('Information') }}

-
-
-
{{ _('ID') }}
-
{{ pdoc.docId }}
-
{{ _('Time') }}
-
{{ pdoc.config.timeMax if pdoc.config.timeMax == pdoc.config.timeMin else pdoc.config.timeMin+'~'+pdoc.config.timeMax }}ms
-
{{ _('Memory') }}
-
{{ pdoc.config.memoryMax if pdoc.config.memoryMax == pdoc.config.memoryMin else pdoc.config.memoryMin+'~'+pdoc.config.memoryMax }}MiB
- {% if page_name != 'discussion_node' and page_name != 'discussion_detail' %} - {% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %} -
{{ _('Submission') }}
-
- {% if pdoc.psdoc.status %} - - - {{ model.builtin.STATUS_TEXTS[pdoc['psdoc']['status']] }} - - {% else %} - {{ _('(None)') }} - {% endif %} -
- {% endif %} - {% endif %} -
{{ _('Difficulty') }}
-
{{ pdoc['difficulty'] or _('(None)') }}
-
{{ _('Category') }}
-
- {% if pdoc['category'] %} - - {%- for category in pdoc['category'] -%} - {{ category }} - {% if loop.index < pdoc['category']|length %} - {{ _('problem-subcategory-delim') }} - {% endif %} - {%- endfor -%} - - - {{ _('page.problem_detail.sidebar.show_category') }} - - {% else %} - {{ _('(None)') }} - {% endif %} -
-
{{ _('Tags') }}
-
- {{ problem.render_problem_tags(pdoc, true) }} -
-
{{ _('# Submissions') }}
-
{{ pdoc.nSubmit }}
- {% if page_name != 'discussion_node' and page_name != 'discussion_detail' %} - {% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %} -
{{ _('# My Submissions') }}
-
{{ pdoc['psdoc'].nSubmit|default(0) }}
- {% endif %} - {% endif %} -
{{ _('Accepted') }}
-
{{ pdoc.nAccept }}
-
{{ _('Accepted Ratio') }}
-
{% if pdoc.nSubmit > 0 %}{{ (100 * pdoc.nAccept / pdoc.nSubmit)|round|int }}%{% else %}{{ _('?') }}{% endif %}
-
{{ _('Uploaded By') }}
-
{{ user.render_inline(owner_udoc, badge=false) }}
-
-
+ {% include "partials/problem-sidebar-information.html" %} {% if page_name == 'problem_detail' and (tdocs or ctdocs) %}
diff --git a/packages/ui-default/templates/problem_files.html b/packages/ui-default/templates/problem_files.html index 62270f9dc..b49dd2580 100644 --- a/packages/ui-default/templates/problem_files.html +++ b/packages/ui-default/templates/problem_files.html @@ -7,54 +7,13 @@

{{ _('Testdata') }}

{% if handler.user.own(pdoc, perm.PERM_EDIT_PROBLEM_SELF) or handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %} - + + {% endif %}
{{ noscript_note.render() }} -
- {% if testdata.length %} - - - - - - - - - - - - - - - {%- for file in testdata -%} - - - - - - {%- endfor -%} - -
- - {{ _('Filename') }}{{ _('Size') }}
- - - {{ file.name }} - {{ size(file.size) }}
- {% 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 %} -
+ {% 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 %}
@@ -71,49 +30,12 @@

{{ _('Additional Files') }}

{% if handler.user.own(pdoc, perm.PERM_EDIT_PROBLEM_SELF) or handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %} - + + {% endif %}
-
- {% if additional_file.length %} - - - - - - - - - - - - - - - {%- for file in additional_file -%} - - - - - - {%- endfor -%} - -
- - {{ _('Filename') }}{{ _('Size') }}
- - - {{ file.name }} - {{ size(file.size) }}
- {% else %} - {{ nothing.render('No additional file at current.') }} - {% endif %} -
+ {% include "partials/problem_files-additional_file.html" %} {% if additional_file.length %}
diff --git a/packages/ui-default/theme/default.js b/packages/ui-default/theme/default.js index 142b070c4..29413a7ae 100644 --- a/packages/ui-default/theme/default.js +++ b/packages/ui-default/theme/default.js @@ -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'; diff --git a/packages/ui-default/utils/pjax.js b/packages/ui-default/utils/pjax.js index bef2999f8..039681ea3 100644 --- a/packages/ui-default/utils/pjax.js +++ b/packages/ui-default/utils/pjax.js @@ -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; }