diff --git a/build/prepare.js b/build/prepare.js index e44c4dc6e..2c599932f 100644 --- a/build/prepare.js +++ b/build/prepare.js @@ -60,7 +60,7 @@ const configFlat = (name) => ({ ], }, }, - include: ['**/*.ts'], + include: ['**/*.ts', '**/*.tsx'], exclude, }); diff --git a/packages/components/frontend/autocomplete/AutoComplete.tsx b/packages/components/frontend/autocomplete/AutoComplete.tsx index 693bc2a18..419e7eaa3 100644 --- a/packages/components/frontend/autocomplete/AutoComplete.tsx +++ b/packages/components/frontend/autocomplete/AutoComplete.tsx @@ -310,18 +310,20 @@ const AutoComplete = forwardRef(function Impl(props: AutoCompleteProps, re )} {focused && itemList.length > 0 && ( )} diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index 1d7075747..31d3235a7 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -305,11 +305,12 @@ export class ContestEditHandler extends Handler { @param('contestDuration', Types.Float, true) @param('maintainer', Types.NumericArray, true) @param('allowViewCode', Types.Boolean) + @param('langs', Types.CommaSeperatedArray, true) async postUpdate( domainId: string, tid: ObjectId, beginAtDate: string, beginAtTime: string, duration: number, title: string, content: string, rule: string, _pids: string, rated = false, _code = '', autoHide = false, assign: string[] = [], lock: number = null, - contestDuration: number = null, maintainer: number[] = [], allowViewCode = false, + contestDuration: number = null, maintainer: number[] = [], allowViewCode = false, langs: string[] = [], ) { if (autoHide) this.checkPerm(PERM.PERM_EDIT_PROBLEM); const pids = _pids.replace(/,/g, ',').split(',').map((i) => +i).filter((i) => i); @@ -350,7 +351,7 @@ export class ContestEditHandler extends Handler { }); } await contest.edit(domainId, tid, { - assign, _code, autoHide, lockAt, maintainer, allowViewCode, + assign, _code, autoHide, lockAt, maintainer, allowViewCode, langs, }); this.response.body = { tid }; this.response.redirect = this.url('contest_detail', { tid }); diff --git a/packages/hydrooj/src/handler/homework.ts b/packages/hydrooj/src/handler/homework.ts index 683b574a8..033708717 100644 --- a/packages/hydrooj/src/handler/homework.ts +++ b/packages/hydrooj/src/handler/homework.ts @@ -191,11 +191,12 @@ class HomeworkEditHandler extends Handler { @param('rated', Types.Boolean) @param('maintainer', Types.NumericArray, true) @param('assign', Types.CommaSeperatedArray, true) + @param('langs', Types.CommaSeperatedArray, true) async postUpdate( domainId: string, tid: ObjectId, beginAtDate: string, beginAtTime: string, penaltySinceDate: string, penaltySinceTime: string, extensionDays: number, penaltyRules: PenaltyRules, title: string, content: string, _pids: string, rated = false, - maintainer: number[] = [], assign: string[] = [], + maintainer: number[] = [], assign: string[] = [], langs: string[] = [], ) { const pids = _pids.replace(/,/g, ',').split(',').map((i) => +i).filter((i) => i); const tdoc = tid ? await contest.get(domainId, tid) : null; @@ -226,6 +227,7 @@ class HomeworkEditHandler extends Handler { rated, maintainer, assign, + langs, }); if (tdoc.beginAt !== beginAt.toDate() || tdoc.endAt !== endAt.toDate() diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 2a266849c..463cf2606 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -304,6 +304,7 @@ export class ProblemDetailHandler extends ContestDetailBaseHandler { if (this.pdoc.config.langs) t.push(this.pdoc.config.langs); if (ddoc.langs) t.push(ddoc.langs.split(',').map((i) => i.trim()).filter((i) => i)); if (this.domain.langs) t.push(this.domain.langs.split(',').map((i) => i.trim()).filter((i) => i)); + if (this.tdoc?.langs) t.push(this.tdoc.langs); if (this.pdoc.config.type === 'remote_judge') { const p = this.pdoc.config.subType; const dl = Object.keys(setting.langs).filter((i) => i.startsWith(`${p}.`) || setting.langs[i].validAs[p]); diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 8935b795f..04ab4e805 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -269,6 +269,7 @@ export interface Tdoc extends Document { autoHide?: boolean; balloon?: Record; score?: Record; + langs?: string[]; /** * In hours diff --git a/packages/ui-default/components/autocomplete/CustomSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/CustomSelectAutoComplete.tsx index a51275796..1dcb1905f 100644 --- a/packages/ui-default/components/autocomplete/CustomSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/CustomSelectAutoComplete.tsx @@ -1,55 +1,25 @@ import { CustomSelectAutoComplete as CustomSelectAutoCompleteFC } from '@hydrooj/components'; -import React from 'react'; import AutoComplete, { AutoCompleteOptions } from '.'; interface CustomSelectOptions { data: any[] } -const Component = React.forwardRef((props, ref) => { - const [value, setValue] = React.useState(props.value); - return ( - i.trim()).filter((i) => i)} - onChange={(v) => { - setValue(v); - props.onChange(v); - }} - data={props.data} - multi={props.multi} - /> - ); -}); - export default class CustomSelectAutoComplete extends AutoComplete { static DOMAttachKey = 'ucwCustomSelectAutoCompleteInstance'; constructor($dom, options: CustomSelectOptions & AutoCompleteOptions) { super($dom, { classes: 'custom-select', + component: CustomSelectAutoCompleteFC, + props: { + data: options.data, + multi: options.multi, + height: 'auto', + }, ...options, }); } - - value(): Multi extends true ? number[] : string { - if (this.options.multi) return this.ref?.getSelectedItemKeys().map((i) => +i) ?? this.$dom.val(); - return this.ref?.getSelectedItems()[0] ?? null; - } - - attach() { - const value = this.$dom.val(); - this.component.render( - { this.ref = ref; }} - data={this.options.data} - value={value} - multi={this.options.multi} - onChange={this.onChange} - />, - ); - } } window.Hydro.components.CustomSelectAutoComplete = CustomSelectAutoComplete; diff --git a/packages/ui-default/components/autocomplete/DomainSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/DomainSelectAutoComplete.tsx index 14b5bcde8..4f566c4bf 100644 --- a/packages/ui-default/components/autocomplete/DomainSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/DomainSelectAutoComplete.tsx @@ -1,42 +1,20 @@ -import React from 'react'; -import AutoComplete from '.'; +import AutoComplete, { AutoCompleteOptions } from '.'; import DomainSelectAutoCompleteFC from './components/DomainSelectAutoComplete'; -const Component = React.forwardRef((props: { value: string, multi: boolean, onChange: (v: string) => void }, ref) => { - const [value, setValue] = React.useState(props.value ?? ''); - return ( - i.trim()).filter((i) => i)} - onChange={(v) => { - setValue(v); - props.onChange(v); - }} - multi={props.multi} - /> - ); -}); - -export default class DomainSelectAutoComplete extends AutoComplete { +export default class DomainSelectAutoComplete extends AutoComplete { static DOMAttachKey = 'ucwDomainSelectAutoCompleteInstance'; - constructor($dom, options) { + constructor($dom, options: AutoCompleteOptions = {}) { super($dom, { classes: 'domain-select', + component: DomainSelectAutoCompleteFC, + props: { + multi: options.multi, + height: '34px', + }, ...options, }); } - - attach() { - const value = this.$dom.val(); - this.component.render( { this.ref = ref; }} - value={value} - onChange={this.onChange} - multi={this.options.multi} - />); - } } window.Hydro.components.DomainSelectAutoComplete = DomainSelectAutoComplete; diff --git a/packages/ui-default/components/autocomplete/FileSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/FileSelectAutoComplete.tsx index 2bec61478..2e051279e 100644 --- a/packages/ui-default/components/autocomplete/FileSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/FileSelectAutoComplete.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import AutoComplete, { AutoCompleteOptions } from '.'; import FileSelectAutoCompleteFC from './components/FileSelectAutoComplete'; @@ -9,50 +8,21 @@ interface FileSelectOptions { }[]; } -const Component = React.forwardRef((props, ref) => { - const [value, setValue] = React.useState(props.value); - return ( - i.trim()).filter((i) => i)} - onChange={(v) => { - setValue(v); - props.onChange(v); - }} - data={props.data} - multi={props.multi} - /> - ); -}); - export default class FileSelectAutoComplete extends AutoComplete { static DOMAttachKey = 'ucwFileSelectAutoCompleteInstance'; constructor($dom, options: FileSelectOptions & AutoCompleteOptions) { super($dom, { - classes: 'custom-select', + classes: 'file-select', + component: FileSelectAutoCompleteFC, + props: { + data: options.data, + multi: options.multi, + height: 'auto', + }, ...options, }); } - - value(): Multi extends true ? number[] : string { - if (this.options.multi) return this.ref?.getSelectedItemKeys().map((i) => +i) ?? this.$dom.val(); - return this.ref?.getSelectedItems()[0] ?? null; - } - - attach() { - const value = this.$dom.val(); - this.component.render( - { this.ref = ref; }} - data={this.options.data} - value={value} - multi={this.options.multi} - onChange={this.onChange} - />, - ); - } } window.Hydro.components.FileSelectAutoComplete = FileSelectAutoComplete; diff --git a/packages/ui-default/components/autocomplete/LanguageSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/LanguageSelectAutoComplete.tsx new file mode 100644 index 000000000..63664c75c --- /dev/null +++ b/packages/ui-default/components/autocomplete/LanguageSelectAutoComplete.tsx @@ -0,0 +1,26 @@ +import AutoComplete, { AutoCompleteOptions } from '.'; +import LanguageSelectAutoCompleteFC from './components/LanguageSelectAutoComplete'; + +interface LanguageSelectOptions { + multi?: boolean; + withAuto?: boolean; +} + +export default class LanguageSelectAutoComplete extends AutoComplete { + static DOMAttachKey = 'ucwLanguageSelectAutoCompleteInstance'; + + constructor($dom, options: LanguageSelectOptions & AutoCompleteOptions) { + super($dom, { + classes: 'language-select', + component: LanguageSelectAutoCompleteFC, + props: { + multi: options.multi, + withAuto: options.withAuto, + height: 'auto', + }, + ...options, + }); + } +} + +window.Hydro.components.LanguageSelectAutoComplete = LanguageSelectAutoComplete; diff --git a/packages/ui-default/components/autocomplete/ProblemSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/ProblemSelectAutoComplete.tsx index bc5e88fe3..5e456537a 100644 --- a/packages/ui-default/components/autocomplete/ProblemSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/ProblemSelectAutoComplete.tsx @@ -1,44 +1,20 @@ -import React from 'react'; import AutoComplete from '.'; import ProblemSelectAutoCompleteFC from './components/ProblemSelectAutoComplete'; -const Component = React.forwardRef((props, ref) => { - const [value, setValue] = React.useState(props.value ?? ''); - return ( - i.trim()).filter((i) => i)} - onChange={(v) => { - setValue(v); - props.onChange(v); - }} - multi={props.multi} - /> - ); -}); - export default class ProblemSelectAutoComplete extends AutoComplete { static DOMAttachKey = 'ucwProblemSelectAutoCompleteInstance'; constructor($dom, options) { super($dom, { classes: 'problem-select', + component: ProblemSelectAutoCompleteFC, + props: { + multi: options.multi, + height: 'auto', + }, ...options, }); } - - attach() { - const value = this.$dom.val(); - this.component.render( - { this.ref = ref; }} - value={value} - multi={this.options.multi} - onChange={this.onChange} - />, - ); - } } window.Hydro.components.ProblemSelectAutoComplete = ProblemSelectAutoComplete; diff --git a/packages/ui-default/components/autocomplete/UserSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/UserSelectAutoComplete.tsx index f6c444c9a..fc67d282e 100644 --- a/packages/ui-default/components/autocomplete/UserSelectAutoComplete.tsx +++ b/packages/ui-default/components/autocomplete/UserSelectAutoComplete.tsx @@ -1,29 +1,17 @@ -import React from 'react'; import AutoComplete, { AutoCompleteOptions } from '.'; import UserSelectAutoCompleteFC from './components/UserSelectAutoComplete'; -const Component = React.forwardRef((props, ref) => { - const [value, setValue] = React.useState(props.value ?? ''); - return ( - i.trim()).filter((i) => i)} - onChange={(v) => { - setValue(v); - props.onChange(v); - }} - multi={props.multi} - /> - ); -}); - export default class UserSelectAutoComplete extends AutoComplete { static DOMAttachKey = 'ucwUserSelectAutoCompleteInstance'; - constructor($dom, options: AutoCompleteOptions) { + constructor($dom, options: AutoCompleteOptions = {}) { super($dom, { classes: 'user-select', + component: UserSelectAutoCompleteFC, + props: { + multi: options.multi, + height: 'auto', + }, ...options, }); } @@ -32,18 +20,6 @@ export default class UserSelectAutoComplete extends AutoC if (this.options.multi) return this.ref?.getSelectedItemKeys().map((i) => +i) ?? this.$dom.val(); return this.ref?.getSelectedItems()[0] ?? null; } - - attach() { - const value = this.$dom.val(); - this.component.render( - { this.ref = ref; }} - value={value} - multi={this.options.multi} - onChange={this.onChange} - />, - ); - } } window.Hydro.components.UserSelectAutoComplete = UserSelectAutoComplete; diff --git a/packages/ui-default/components/autocomplete/components/LanguageSelectAutoComplete.tsx b/packages/ui-default/components/autocomplete/components/LanguageSelectAutoComplete.tsx new file mode 100644 index 000000000..966345ff6 --- /dev/null +++ b/packages/ui-default/components/autocomplete/components/LanguageSelectAutoComplete.tsx @@ -0,0 +1,43 @@ +import { CustomSelectAutoComplete } from '@hydrooj/components'; +import PropTypes from 'prop-types'; +import React from 'react'; + +const prefixes = new Set(Object.keys(window.LANGS).filter((i) => i.includes('.')).map((i) => i.split('.')[0])); +const data = Object.keys(window.LANGS).filter((i) => !prefixes.has(i)) + .map((i) => ({ name: `${i.includes('.') ? `${window.LANGS[i.split('.')[0]].display || ''}/` : ''}${window.LANGS[i].display}`, _id: i })); +const withAuto = [...data, { name: 'Auto', _id: 'auto' }]; + +const LanguageSelectAutoComplete = React.forwardRef((props, ref) => ( + (prefixes.has(item._id) ? '' : `${item.name}`)} + onChange={(val) => { + let value = val.split(','); + const active = new Set(value.filter((i) => i.includes('.')).map((i) => i.split('.')[0])); + value = value.filter((i) => !prefixes.has(i) || active.has(i)); + for (const i of active) if (!value.includes(i)) value.push(i); + props.onChange(value.join(',')); + }} + multi={!!props.multi} + /> +)); + +LanguageSelectAutoComplete.propTypes = { + width: PropTypes.string, + height: PropTypes.string, + listStyle: PropTypes.object, + onChange: PropTypes.func.isRequired, + multi: PropTypes.bool, + selectedKeys: PropTypes.arrayOf(PropTypes.string), + allowEmptyQuery: PropTypes.bool, + freeSolo: PropTypes.bool, + freeSoloConverter: PropTypes.func, + data: PropTypes.arrayOf(PropTypes.any), + withAuto: PropTypes.bool, +}; + +LanguageSelectAutoComplete.displayName = 'LanguageSelectAutoComplete'; + +export default LanguageSelectAutoComplete; diff --git a/packages/ui-default/components/autocomplete/index.tsx b/packages/ui-default/components/autocomplete/index.tsx index c538acc4a..6f67a9124 100644 --- a/packages/ui-default/components/autocomplete/index.tsx +++ b/packages/ui-default/components/autocomplete/index.tsx @@ -1,4 +1,5 @@ import { AutoComplete as AutoCompleteFC } from '@hydrooj/components'; +import React from 'react'; import ReactDOM from 'react-dom/client'; import DOMAttachedObject from 'vj/components/DOMAttachedObject'; @@ -18,23 +19,23 @@ export interface AutoCompleteOptions { text?: () => string; } -export default class AutoComplete = {}> extends DOMAttachedObject { +export default class AutoComplete = {}, Multi extends boolean = boolean> extends DOMAttachedObject { static DOMAttachKey = 'ucwAutoCompleteInstance'; ref = null; container = document.createElement('div'); - options: AutoCompleteOptions & Options; + options: AutoCompleteOptions & Options; component = ReactDOM.createRoot(this.container); changeListener = [ (val) => this.$dom.val(val), ]; - constructor($dom, options = {} as Options) { + constructor($dom, options = {} as Options & { component?: React.ComponentType, props?: Record }) { super($dom); this.options = { items: async () => [], render: () => '', text: () => null, - multi: false, + multi: false as Multi, ...options, }; this.clear = this.clear.bind(this); @@ -63,20 +64,21 @@ export default class AutoComplete = {}> exte } attach() { - const value = this.$dom.val(); - this.component.render( - { + const [value, setValue] = React.useState(props.value); + return { this.ref = ref; }} + onChange={(v) => { + setValue(v); + this.onChange(v); + }} + selectedKeys={(Array.isArray(value) ? value : value.split(',')).map((i) => i.trim()).filter((i) => i)} height="34px" - queryItems={this.options.items} - renderItem={this.options.render} - itemText={this.options.text} - selectedKeys={value.split(',').map((i) => i.trim())} - onChange={this.onChange} - multi={this.options.multi} - freeSolo={this.options.multi} - />, - ); + {...this.options.props} + />; + }; + this.component.render(); } open() { @@ -89,8 +91,8 @@ export default class AutoComplete = {}> exte this.ref.closeList(); } - value(): any { - if (this.options.multi) return this.$dom.val(); + value(): Multi extends true ? (string | number)[] : string { + if (this.options.multi) return this.ref?.getSelectedItemKeys() ?? this.$dom.val(); return this.ref?.getSelectedItems()[0] ?? null; } diff --git a/packages/ui-default/components/problemconfig/BasicForm.tsx b/packages/ui-default/components/problemconfig/BasicForm.tsx index 79f24ad81..10bf16646 100644 --- a/packages/ui-default/components/problemconfig/BasicForm.tsx +++ b/packages/ui-default/components/problemconfig/BasicForm.tsx @@ -1,8 +1,8 @@ -import { CustomSelectAutoComplete } from '@hydrooj/components'; import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { i18n } from 'vj/utils'; import FileSelectAutoComplete from '../autocomplete/components/FileSelectAutoComplete'; +import LanguageSelectAutoComplete from '../autocomplete/components/LanguageSelectAutoComplete'; import type { RootState } from './reducer/index'; export function FormItem({ @@ -56,11 +56,6 @@ export function ManagedSelect({ options, formKey }: { options: string[], formKey ); } -const prefixes = new Set(Object.keys(window.LANGS).filter((i) => i.includes('.')).map((i) => i.split('.')[0])); -const data = Object.keys(window.LANGS).filter((i) => !prefixes.has(i)) - .map((i) => ({ name: `${i.includes('.') ? `${window.LANGS[i.split('.')[0]].display || ''}/` : ''}${window.LANGS[i].display}`, _id: i })); -data.unshift({ name: 'Auto', _id: 'auto' }); - export function SingleFileSelect({ formKey, withLang = false, label = 'Checker' }: { formKey: FileSelectKey, withLang?: boolean, label?: string }) { const value = useSelector((state: RootState) => state.config[formKey]); const Files = useSelector((state: RootState) => state.testdata); @@ -82,10 +77,10 @@ export function SingleFileSelect({ formKey, withLang = false, label = 'Checker' /> - update(selectedFile, val)} + withAuto /> ) : ( state.config.langs) || []; - const prefixes = new Set(Object.keys(window.LANGS).filter((i) => i.includes('.')).map((i) => i.split('.')[0])); - const data = Object.keys(window.LANGS).filter((i) => !prefixes.has(i)) - .map((i) => ({ name: `${i.includes('.') ? `${window.LANGS[i.split('.')[0]].display || ''}/` : ''}${window.LANGS[i].display}`, _id: i })); const dispatch = useDispatch(); - const selectedKeys = langs.filter((i) => !prefixes.has(i)); return (
- { - const value = val.split(','); - value.push(...Array.from(new Set(value.filter((i) => i.includes('.')).map((i) => i.split('.')[0])))); - dispatch({ type: 'CONFIG_FORM_UPDATE', key: 'langs', value }); - }} + dispatch({ type: 'CONFIG_FORM_UPDATE', key: 'langs', value })} multi /> diff --git a/packages/ui-default/locales/zh.yaml b/packages/ui-default/locales/zh.yaml index dfe0e7e17..964537e0f 100644 --- a/packages/ui-default/locales/zh.yaml +++ b/packages/ui-default/locales/zh.yaml @@ -821,7 +821,9 @@ Section: 章节 Security: 安全 Select a node to create discussion.: 选择一个节点来发表讨论。 Select a role: 选择一个角色 +Select All: 全选 Select Category: 选择标签 +Select None: 全不选 Select User: 选择用户 Selected categories: 已选标签 Selected files have been deleted.: 所选文件已被删除。 @@ -884,6 +886,7 @@ Start at: 开始于 Statistics: 统计 Status is hidden: 状态已隐藏 Status: 状态 +Submission language limit: 提交语言限制 Submission Statistics: 递交统计 Submission: 递交 Submissions: 递交 diff --git a/packages/ui-default/pages/contest_edit.page.ts b/packages/ui-default/pages/contest_edit.page.ts index 12e941991..6a6d59d06 100644 --- a/packages/ui-default/pages/contest_edit.page.ts +++ b/packages/ui-default/pages/contest_edit.page.ts @@ -1,5 +1,6 @@ import $ from 'jquery'; import moment from 'moment'; +import LanguageSelectAutoComplete from 'vj/components/autocomplete/LanguageSelectAutoComplete'; import ProblemSelectAutoComplete from 'vj/components/autocomplete/ProblemSelectAutoComplete'; import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoComplete'; import { ConfirmDialog } from 'vj/components/dialog'; @@ -9,6 +10,7 @@ import { i18n, request, tpl } from 'vj/utils'; const page = new NamedPage(['contest_edit', 'contest_create', 'homework_create', 'homework_edit'], (pagename) => { ProblemSelectAutoComplete.getOrConstruct($('[name="pids"]'), { multi: true, clearDefaultValue: false }); UserSelectAutoComplete.getOrConstruct($('[name="maintainer"]'), { multi: true, clearDefaultValue: false }); + LanguageSelectAutoComplete.getOrConstruct($('[name=langs]'), { multi: true }); $('[name="rule"]').on('change', () => { const rule = $('[name="rule"]').val(); $('.contest-rule-settings input').attr('disabled', 'disabled'); diff --git a/packages/ui-default/pages/domain_edit.page.ts b/packages/ui-default/pages/domain_edit.page.ts deleted file mode 100644 index 742812c18..000000000 --- a/packages/ui-default/pages/domain_edit.page.ts +++ /dev/null @@ -1,21 +0,0 @@ -import CustomSelectAutoComplete from 'vj/components/autocomplete/CustomSelectAutoComplete'; -import { NamedPage } from 'vj/misc/Page'; - -const page = new NamedPage('domain_edit', () => { - const prefixes = new Set(Object.keys(window.LANGS).filter((i) => i.includes('.')).map((i) => i.split('.')[0])); - const langs = Object.keys(window.LANGS).filter((i) => !prefixes.has(i)).map((i) => ( - { name: `${i.includes('.') ? `${window.LANGS[i.split('.')[0]].display}/` : ''}${window.LANGS[i].display}`, _id: i } - )); - - const $langSelect = $('[name=langs]'); - $langSelect.val(($langSelect.val() as string).split(',').filter((i) => !prefixes.has(i))); - - const select: CustomSelectAutoComplete = CustomSelectAutoComplete.getOrConstruct($('[name=langs]'), { multi: true, data: langs }); - select.onChange((val) => { - const value = val.split(','); - value.push(...new Set(value.filter((i) => i.includes('.')).map((i) => i.split('.')[0]))); - $langSelect.val(value.join(',')); - }); -}); - -export default page; diff --git a/packages/ui-default/pages/domain_edit.page.tsx b/packages/ui-default/pages/domain_edit.page.tsx new file mode 100644 index 000000000..a52d1361c --- /dev/null +++ b/packages/ui-default/pages/domain_edit.page.tsx @@ -0,0 +1,8 @@ +import LanguageSelectAutoComplete from 'vj/components/autocomplete/LanguageSelectAutoComplete'; +import { NamedPage } from 'vj/misc/Page'; + +const page = new NamedPage('domain_edit', () => { + LanguageSelectAutoComplete.getOrConstruct($('[name=langs]'), { multi: true }); +}); + +export default page; diff --git a/packages/ui-default/pages/home_domain.page.tsx b/packages/ui-default/pages/home_domain.page.tsx index e89dba948..d737e690b 100644 --- a/packages/ui-default/pages/home_domain.page.tsx +++ b/packages/ui-default/pages/home_domain.page.tsx @@ -1,4 +1,5 @@ import $ from 'jquery'; +import React from 'react'; import DomainSelectAutoComplete from 'vj/components/autocomplete/DomainSelectAutoComplete'; import { ActionDialog } from 'vj/components/dialog'; import { NamedPage } from 'vj/misc/Page'; diff --git a/packages/ui-default/templates/contest_edit.html b/packages/ui-default/templates/contest_edit.html index ebf3a1760..903213ec0 100644 --- a/packages/ui-default/templates/contest_edit.html +++ b/packages/ui-default/templates/contest_edit.html @@ -132,6 +132,14 @@

{{ _('Contest Settings') }}

+ {{ form.form_text({ + columns:12, + label:'Submission language limit', + name:'langs', + value:tdoc.langs|default(''), + placeholder:_("Seperated with ','"), + row:true + }) }}
{{ form.form_checkbox({ columns:2, diff --git a/packages/ui-default/templates/homework_edit.html b/packages/ui-default/templates/homework_edit.html index f57760480..b29aaf9a7 100644 --- a/packages/ui-default/templates/homework_edit.html +++ b/packages/ui-default/templates/homework_edit.html @@ -97,6 +97,14 @@ value:tdoc['content']|default(''), markdown:true }) }} + {{ form.form_text({ + columns:12, + label:'Submission language limit', + name:'langs', + value:tdoc.langs|default(''), + placeholder:_("Seperated with ','"), + row:true + }) }}
{% if page_name == 'homework_edit' %}