├── vj4 ├── __init__.py ├── handler │ ├── __init__.py │ ├── error.py │ ├── i18n.py │ └── misc.py ├── model │ ├── __init__.py │ ├── adaptor │ │ ├── __init__.py │ │ └── defaults.py │ └── oplog.py ├── service │ ├── __init__.py │ ├── staticmanifest.py │ ├── queue.py │ └── mailer.py ├── test │ ├── __init__.py │ ├── test_misc.py │ └── test_view.py ├── util │ ├── __init__.py │ ├── rank.py │ ├── pagination.py │ ├── version.py │ ├── tools.py │ ├── domainjob.py │ ├── json.py │ ├── useragent.py │ ├── geoip.py │ └── locale.py ├── pipeline │ └── __init__.py ├── upgrader │ └── __init__.py ├── ui │ ├── components │ │ ├── katex │ │ │ ├── katex.styl │ │ │ └── katex.page.js │ │ ├── react-tabs │ │ │ └── rc-tabs.page.js │ │ ├── scratchpad │ │ │ ├── var.inc.styl │ │ │ ├── Panel.page.styl │ │ │ ├── reducers │ │ │ │ ├── index.js │ │ │ │ ├── editor.js │ │ │ │ └── records.js │ │ │ ├── PanelButton.page.styl │ │ │ ├── DataInput.page.styl │ │ │ ├── Editor.page.styl │ │ │ ├── PanelButtonComponent.js │ │ │ ├── PanelComponent.js │ │ │ ├── DataInputComponent.js │ │ │ └── ScratchpadRecordsTableContainer.js │ │ ├── nprogress │ │ │ └── index.js │ │ ├── problem │ │ │ ├── rp.page.styl │ │ │ └── tag.page.styl │ │ ├── header │ │ │ ├── header-logo.png │ │ │ ├── header-logo-mano.png │ │ │ ├── header-logo@2x.png │ │ │ ├── header-background.png │ │ │ ├── header-logo-summer.png │ │ │ ├── header-background@2x.png │ │ │ ├── header-logo-mano@2x.png │ │ │ ├── header-logo-summer@2x.png │ │ │ └── header-background.png.bak.png │ │ ├── profile │ │ │ ├── backgrounds │ │ │ │ ├── 1.jpg │ │ │ │ ├── 10.jpg │ │ │ │ ├── 11.jpg │ │ │ │ ├── 12.jpg │ │ │ │ ├── 13.jpg │ │ │ │ ├── 14.jpg │ │ │ │ ├── 15.jpg │ │ │ │ ├── 16.jpg │ │ │ │ ├── 17.jpg │ │ │ │ ├── 18.jpg │ │ │ │ ├── 19.jpg │ │ │ │ ├── 2.jpg │ │ │ │ ├── 20.jpg │ │ │ │ ├── 21.jpg │ │ │ │ ├── 3.jpg │ │ │ │ ├── 4.jpg │ │ │ │ ├── 5.jpg │ │ │ │ ├── 6.jpg │ │ │ │ ├── 7.jpg │ │ │ │ ├── 8.jpg │ │ │ │ ├── 9.jpg │ │ │ │ ├── thumbnail │ │ │ │ │ ├── 1.jpg │ │ │ │ │ ├── 10.jpg │ │ │ │ │ ├── 11.jpg │ │ │ │ │ ├── 12.jpg │ │ │ │ │ ├── 13.jpg │ │ │ │ │ ├── 14.jpg │ │ │ │ │ ├── 15.jpg │ │ │ │ │ ├── 16.jpg │ │ │ │ │ ├── 17.jpg │ │ │ │ │ ├── 18.jpg │ │ │ │ │ ├── 19.jpg │ │ │ │ │ ├── 2.jpg │ │ │ │ │ ├── 20.jpg │ │ │ │ │ ├── 21.jpg │ │ │ │ │ ├── 3.jpg │ │ │ │ │ ├── 4.jpg │ │ │ │ │ ├── 5.jpg │ │ │ │ │ ├── 6.jpg │ │ │ │ │ ├── 7.jpg │ │ │ │ │ ├── 8.jpg │ │ │ │ │ └── 9.jpg │ │ │ │ └── gen_thumbnails.sh │ │ │ └── profile.page.styl │ │ ├── contest │ │ │ ├── problem-contest-bg.png │ │ │ ├── problem-contest-bg@2x.png │ │ │ ├── contest_sidebar.page.styl │ │ │ └── contest.page.styl │ │ ├── homework │ │ │ └── homework.page.styl │ │ ├── navigation │ │ │ ├── nav-logo-small_dark.png │ │ │ ├── nav-logo-small@2x_dark.png │ │ │ ├── nav-logo-small_light.png │ │ │ └── nav-logo-small@2x_light.png │ │ ├── tab │ │ │ ├── var.inc.styl │ │ │ └── tab.page.js │ │ ├── emoji │ │ │ ├── emoji.page.styl │ │ │ └── emojify.page.js │ │ ├── autocomplete │ │ │ ├── userselectautocomplete.page.styl │ │ │ └── autocomplete.page.styl │ │ ├── react-splitpane │ │ │ ├── SplitPaneFillOverlay.page.styl │ │ │ ├── SplitPaneFillOverlayComponent.js │ │ │ └── splitpane.page.styl │ │ ├── marker │ │ │ └── marker.page.js │ │ ├── dropdown │ │ │ ├── dropdown.page.js │ │ │ └── dropdown.page.styl │ │ ├── table │ │ │ └── styledTable.page.js │ │ ├── form │ │ │ ├── select.page.js │ │ │ ├── var.inc.styl │ │ │ ├── form.page.js │ │ │ └── textbox.page.js │ │ ├── messagepad │ │ │ ├── reducers │ │ │ │ ├── index.js │ │ │ │ └── activeId.js │ │ │ ├── DialogueListItem.page.styl │ │ │ └── MessageComponent.js │ │ ├── star │ │ │ ├── star.page.styl │ │ │ └── star.page.js │ │ ├── rotator │ │ │ └── rotator.page.styl │ │ ├── discussion │ │ │ └── discussion.page.styl │ │ ├── react │ │ │ ├── IconComponent.js │ │ │ └── DomComponent.js │ │ ├── highlighter │ │ │ ├── highlighter.page.js │ │ │ ├── codemirror.page.styl │ │ │ └── meta.js │ │ ├── menu │ │ │ ├── menu.page.js │ │ │ └── menu-heading.page.js │ │ ├── loader │ │ │ └── loader.page.styl │ │ ├── record │ │ │ └── record.page.styl │ │ ├── training │ │ │ └── training.page.styl │ │ ├── cmeditor │ │ │ ├── cmeditor.page.js │ │ │ └── textareaHandler.js │ │ ├── datepicker │ │ │ └── datepicker.page.js │ │ ├── dialog │ │ │ └── dialog.page.styl │ │ ├── tooltip │ │ │ └── tooltip.page.js │ │ ├── pager │ │ │ └── pager.page.styl │ │ └── footer │ │ │ └── footer.page.js │ ├── breakpoints.json │ ├── postcss.config.js │ ├── static │ │ ├── favicon.ico │ │ ├── img │ │ │ └── avatar.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── mstile-144x144.png │ │ ├── android-chrome-192x192.png │ │ ├── apple-touch-icon-180x180.png │ │ └── manifest.json │ ├── utils │ │ ├── delay.js │ │ ├── zIndexManager.js │ │ ├── substitute.js │ │ ├── i18n.js │ │ ├── parseQueryString.js │ │ ├── mediaQuery.js │ │ ├── mongoId.js │ │ ├── emulateAnchorClick.js │ │ ├── tpl.js │ │ └── loadReactRedux.js │ ├── misc │ │ ├── immersive-background.jpg │ │ ├── immersive-background@2x.jpg │ │ ├── icons │ │ │ ├── send.svg │ │ │ ├── check.svg │ │ │ ├── flag.svg │ │ │ ├── add.svg │ │ │ ├── expand_less.svg │ │ │ ├── expand_more.svg │ │ │ ├── chevron_left.svg │ │ │ ├── chevron_right.svg │ │ │ ├── italic.svg │ │ │ ├── download.svg │ │ │ ├── statistics.svg │ │ │ ├── upload.svg │ │ │ ├── warning.svg │ │ │ ├── formula.svg │ │ │ ├── star.svg │ │ │ ├── code.svg │ │ │ ├── quote.svg │ │ │ ├── close.svg │ │ │ ├── delete.svg │ │ │ ├── play.svg │ │ │ ├── reply.svg │ │ │ ├── logout.svg │ │ │ ├── hourglass.svg │ │ │ ├── edit.svg │ │ │ ├── info--circle.svg │ │ │ ├── platform--windows.svg │ │ │ ├── user.svg │ │ │ ├── check--circle.svg │ │ │ ├── security.svg │ │ │ ├── link--external.svg │ │ │ ├── heart.svg │ │ │ ├── stack.svg │ │ │ ├── calendar.svg │ │ │ ├── template │ │ │ │ ├── webicon.inc.styl │ │ │ │ └── webicon.styl │ │ │ ├── vote--up.svg │ │ │ ├── close--circle.svg │ │ │ ├── star--outline.svg │ │ │ ├── lab.svg │ │ │ ├── vote--down.svg │ │ │ ├── facebook.svg │ │ │ ├── mail.svg │ │ │ ├── insert--image.svg │ │ │ ├── ordered_list.svg │ │ │ ├── enlarge.svg │ │ │ ├── platform--mobile.svg │ │ │ ├── shrink.svg │ │ │ ├── info.svg │ │ │ ├── refresh.svg │ │ │ ├── schedule--fill.svg │ │ │ ├── tag.svg │ │ │ ├── comment--text.svg │ │ │ ├── crown.svg │ │ │ ├── account--circle.svg │ │ │ ├── comment--multiple.svg │ │ │ ├── google_plus.svg │ │ │ ├── help2.svg │ │ │ ├── erase.svg │ │ │ ├── linkedin.svg │ │ │ ├── bold.svg │ │ │ ├── user--multiple.svg │ │ │ ├── global.svg │ │ │ ├── heart--outline.svg │ │ │ ├── search.svg │ │ │ ├── filter.svg │ │ │ ├── platform--unknown.svg │ │ │ ├── platform--ios.svg │ │ │ ├── block.svg │ │ │ ├── schedule.svg │ │ │ ├── insert--link.svg │ │ │ ├── debug.svg │ │ │ ├── homework.svg │ │ │ ├── award.svg │ │ │ ├── sliders.svg │ │ │ ├── twitter.svg │ │ │ ├── qq.svg │ │ │ ├── platform--chromeos.svg │ │ │ ├── file.svg │ │ │ ├── preview.svg │ │ │ ├── wrench.svg │ │ │ ├── feeling-lucky.svg │ │ │ ├── unordered_list.svg │ │ │ ├── github.svg │ │ │ ├── settings.svg │ │ │ ├── wechat.svg │ │ │ ├── help.svg │ │ │ ├── web.svg │ │ │ ├── platform--android.svg │ │ │ ├── platform--mac.svg │ │ │ ├── link.svg │ │ │ └── platform--linux.svg │ │ ├── textalign.styl │ │ ├── float.styl │ │ ├── nothing.styl │ │ ├── immersive.styl │ │ └── slideout.styl │ ├── pages │ │ ├── discussion_node_bg │ │ │ ├── nodes_qa.png │ │ │ ├── nodes@2x_qa.png │ │ │ ├── nodes_share.png │ │ │ ├── nodes_vijos.png │ │ │ ├── nodes@2x_share.png │ │ │ ├── nodes@2x_vijos.png │ │ │ ├── nodes_advice.png │ │ │ ├── nodes_solution.png │ │ │ ├── nodes@2x_advice.png │ │ │ └── nodes@2x_solution.png │ │ ├── domain_manage_role.page.styl │ │ ├── domain_manage_user.page.styl │ │ ├── home_account.page.styl │ │ ├── domain_join.page.styl │ │ ├── problem_submit.page.styl │ │ ├── contest_detail.page.styl │ │ ├── problem_edit.page.js │ │ ├── discussion_node.page.js │ │ ├── problem_submit.page.js │ │ ├── discussion_detail.page.js │ │ ├── problem_solution.page.js │ │ ├── home_domain.page.styl │ │ ├── domain_manage_permission.page.styl │ │ ├── discussion_detail.page.styl │ │ ├── record_detail.page.styl │ │ ├── contest_scoreboard.page.styl │ │ ├── contest_system_test.page.js │ │ ├── problem_settings.page.js │ │ ├── home_security.page.styl │ │ ├── record_detail.page.js │ │ ├── training_detail.page.styl │ │ ├── domain_manage_join_applications.page.js │ │ ├── user_detail.page.js │ │ ├── error.page.styl │ │ ├── discussion_main.page.styl │ │ ├── record_main.page.js │ │ ├── training_main.page.styl │ │ └── record_main.page.styl │ ├── templates │ │ ├── layout │ │ │ ├── simple.html │ │ │ ├── home_base.html │ │ │ ├── wiki_base.html │ │ │ ├── basic.html │ │ │ └── immersive.html │ │ ├── partials │ │ │ ├── homework_default_penalty_rules.yaml │ │ │ ├── problem_stat.html │ │ │ ├── hamburger.html │ │ │ ├── training_default.json │ │ │ ├── problem_lucky.html │ │ │ ├── problem_sidebar.html │ │ │ ├── discussion_edit_form.html │ │ │ ├── path.html │ │ │ ├── header.html │ │ │ ├── domain_edit_form.html │ │ │ └── header_mobile.html │ │ ├── judge_playground.html │ │ ├── components │ │ │ ├── nothing.html │ │ │ ├── noscript_note.html │ │ │ ├── contest.html │ │ │ ├── homework.html │ │ │ ├── sidemenu.html │ │ │ ├── home.html │ │ │ ├── record.html │ │ │ └── md_hint.html │ │ ├── user_register_mail.html │ │ ├── user_register_mail_sent.html │ │ ├── user_changemail_mail_sent.html │ │ ├── user_lostpass_mail_sent.html │ │ ├── user_changemail_mail.html │ │ ├── user_lostpass_mail.html │ │ ├── ac_mail.html │ │ ├── user_logout.html │ │ ├── problem_upload.html │ │ ├── problem_statistics.html │ │ ├── problem_submit_tr.html │ │ ├── discussion_nodes_widget.html │ │ ├── error.html │ │ ├── domain_manage_edit.html │ │ ├── home_messages.html │ │ ├── domain_manage_discussion.html │ │ ├── user_lostpass.html │ │ ├── user_register.html │ │ ├── domain_manage_dashboard.html │ │ ├── record_detail_summary.html │ │ ├── user_lostpass_with_code.html │ │ ├── discussion_create.html │ │ ├── user_register_with_code.html │ │ ├── domain_join.html │ │ ├── contest_scoreboard_download_html.html │ │ └── user_login.html │ ├── common │ │ ├── rem.inc.styl │ │ ├── functions.inc.styl │ │ ├── color.inc.styl │ │ └── common.inc.styl │ ├── jsconfig.json │ └── constant │ │ ├── util │ │ └── objectMeta.js │ │ ├── model.js │ │ ├── domain.js │ │ ├── contest.js │ │ └── setting.js ├── job │ └── __init__.py └── db.py ├── .dockerignore ├── pm ├── scripts ├── build │ ├── index.js │ ├── utils │ │ ├── root.js │ │ └── mapWebpackUrlPrefix.js │ ├── main.js │ ├── plugins │ │ ├── webpackDummyOutputPlugin.js │ │ ├── gulpGenerateLocales.js │ │ └── gulpTouch.js │ ├── runGulp.js │ └── runWebpack.js ├── start_server_dev.sh ├── start_server_production.sh └── fix_abroad_shrinkwrap.js ├── .github_banner.png ├── pylintrc ├── examples ├── docker-compose.env ├── haproxy-site.cfg ├── h2o-site.conf ├── vj4.service └── nginx-site.conf ├── Jenkinsfile ├── .gitignore ├── docker-compose.yml ├── sources.list ├── setup.py └── requirements.txt /vj4/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/handler/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/model/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/service/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/util/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/pipeline/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/upgrader/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/model/adaptor/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | vj4/.uibuild 2 | vj4/constant 3 | -------------------------------------------------------------------------------- /vj4/ui/components/katex/katex.styl: -------------------------------------------------------------------------------- 1 | .katex 2 | font-size: 1em 3 | -------------------------------------------------------------------------------- /pm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python3 -m $@ --db-host=$DB_HOST --db-name=$DB_NAME -------------------------------------------------------------------------------- /scripts/build/index.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | require('./main.js'); 3 | -------------------------------------------------------------------------------- /vj4/ui/components/react-tabs/rc-tabs.page.js: -------------------------------------------------------------------------------- 1 | import 'rc-tabs/assets/index.css'; 2 | -------------------------------------------------------------------------------- /.github_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/.github_banner.png -------------------------------------------------------------------------------- /vj4/ui/breakpoints.json: -------------------------------------------------------------------------------- 1 | { 2 | "mobile": 450, 3 | "desktop": 1000, 4 | "hd": 1250 5 | } 6 | -------------------------------------------------------------------------------- /vj4/ui/components/scratchpad/var.inc.styl: -------------------------------------------------------------------------------- 1 | $panel-header-height = 34px 2 | $tab-color = #fff170 3 | -------------------------------------------------------------------------------- /vj4/ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'autoprefixer': {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /vj4/ui/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/static/favicon.ico -------------------------------------------------------------------------------- /vj4/ui/components/nprogress/index.js: -------------------------------------------------------------------------------- 1 | import NProgress from 'nprogress'; 2 | 3 | export default NProgress; 4 | -------------------------------------------------------------------------------- /vj4/ui/static/img/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/static/img/avatar.png -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [FORMAT] 2 | indent-string=' ' 3 | indent-after-paren=4 4 | 5 | [MESSAGES CONTROL] 6 | disable=C0111,C0301 7 | -------------------------------------------------------------------------------- /vj4/ui/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/static/favicon-16x16.png -------------------------------------------------------------------------------- /vj4/ui/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/static/favicon-32x32.png -------------------------------------------------------------------------------- /vj4/ui/static/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/static/favicon-96x96.png -------------------------------------------------------------------------------- /vj4/ui/static/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/static/mstile-144x144.png -------------------------------------------------------------------------------- /vj4/ui/components/problem/rp.page.styl: -------------------------------------------------------------------------------- 1 | .problem__rp-tag 2 | font-size: 0.8em 3 | padding: rem(0 5px) 4 | color: #AAA 5 | -------------------------------------------------------------------------------- /vj4/ui/utils/delay.js: -------------------------------------------------------------------------------- 1 | export default function (ms) { 2 | return new Promise(resolve => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /examples/docker-compose.env: -------------------------------------------------------------------------------- 1 | URL_PREFIX=http://joj.sstia.tech 2 | OAUTH=jaccount 3 | OAUTH_CLIENT_ID=xxx 4 | OAUTH_CLIENT_SECRET=xxx -------------------------------------------------------------------------------- /vj4/ui/misc/immersive-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/misc/immersive-background.jpg -------------------------------------------------------------------------------- /vj4/ui/components/header/header-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/header/header-logo.png -------------------------------------------------------------------------------- /vj4/ui/misc/immersive-background@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/misc/immersive-background@2x.jpg -------------------------------------------------------------------------------- /vj4/ui/static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /vj4/ui/static/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/static/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /vj4/ui/components/header/header-logo-mano.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/header/header-logo-mano.png -------------------------------------------------------------------------------- /vj4/ui/components/header/header-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/header/header-logo@2x.png -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/1.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/10.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/11.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/12.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/13.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/14.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/15.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/16.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/17.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/18.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/19.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/2.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/20.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/21.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/3.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/4.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/5.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/6.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/7.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/8.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/9.jpg -------------------------------------------------------------------------------- /vj4/ui/pages/discussion_node_bg/nodes_qa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/pages/discussion_node_bg/nodes_qa.png -------------------------------------------------------------------------------- /vj4/ui/components/header/header-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/header/header-background.png -------------------------------------------------------------------------------- /vj4/ui/components/header/header-logo-summer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/header/header-logo-summer.png -------------------------------------------------------------------------------- /vj4/ui/pages/discussion_node_bg/nodes@2x_qa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/pages/discussion_node_bg/nodes@2x_qa.png -------------------------------------------------------------------------------- /vj4/ui/pages/discussion_node_bg/nodes_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/pages/discussion_node_bg/nodes_share.png -------------------------------------------------------------------------------- /vj4/ui/pages/discussion_node_bg/nodes_vijos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/pages/discussion_node_bg/nodes_vijos.png -------------------------------------------------------------------------------- /vj4/ui/templates/layout/simple.html: -------------------------------------------------------------------------------- 1 | {% set layout_name = "basic" %} 2 | {% extends "layout/html5.html" %} 3 | {% block body %} 4 | {% endblock %} 5 | -------------------------------------------------------------------------------- /vj4/ui/templates/partials/homework_default_penalty_rules.yaml: -------------------------------------------------------------------------------- 1 | # Format: 2 | # hours: coefficient 3 | 1: 0.9 4 | 3: 0.8 5 | 12: 0.75 6 | 9999: 0.5 7 | -------------------------------------------------------------------------------- /vj4/ui/components/contest/problem-contest-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/contest/problem-contest-bg.png -------------------------------------------------------------------------------- /vj4/ui/components/header/header-background@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/header/header-background@2x.png -------------------------------------------------------------------------------- /vj4/ui/components/header/header-logo-mano@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/header/header-logo-mano@2x.png -------------------------------------------------------------------------------- /vj4/ui/components/header/header-logo-summer@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/header/header-logo-summer@2x.png -------------------------------------------------------------------------------- /vj4/ui/components/homework/homework.page.styl: -------------------------------------------------------------------------------- 1 | .homework-status--text 2 | for key, value in $homework-status-color 3 | &.{key} 4 | color: value 5 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/send.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/pages/discussion_node_bg/nodes@2x_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/pages/discussion_node_bg/nodes@2x_share.png -------------------------------------------------------------------------------- /vj4/ui/pages/discussion_node_bg/nodes@2x_vijos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/pages/discussion_node_bg/nodes@2x_vijos.png -------------------------------------------------------------------------------- /vj4/ui/pages/discussion_node_bg/nodes_advice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/pages/discussion_node_bg/nodes_advice.png -------------------------------------------------------------------------------- /vj4/ui/pages/discussion_node_bg/nodes_solution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/pages/discussion_node_bg/nodes_solution.png -------------------------------------------------------------------------------- /vj4/ui/components/contest/problem-contest-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/contest/problem-contest-bg@2x.png -------------------------------------------------------------------------------- /vj4/ui/components/navigation/nav-logo-small_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/navigation/nav-logo-small_dark.png -------------------------------------------------------------------------------- /vj4/ui/misc/icons/check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/flag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/pages/discussion_node_bg/nodes@2x_advice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/pages/discussion_node_bg/nodes@2x_advice.png -------------------------------------------------------------------------------- /scripts/build/utils/root.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | export default function root(fn = '.') { 4 | return path.resolve(__dirname, '../../../', fn); 5 | } 6 | -------------------------------------------------------------------------------- /vj4/ui/common/rem.inc.styl: -------------------------------------------------------------------------------- 1 | rem() { 2 | ret = (); 3 | for arg in arguments { 4 | push(ret, unit(arg / $font-size, 'rem')); 5 | } 6 | return ret; 7 | } 8 | -------------------------------------------------------------------------------- /vj4/ui/components/header/header-background.png.bak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/header/header-background.png.bak.png -------------------------------------------------------------------------------- /vj4/ui/components/navigation/nav-logo-small@2x_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/navigation/nav-logo-small@2x_dark.png -------------------------------------------------------------------------------- /vj4/ui/components/navigation/nav-logo-small_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/navigation/nav-logo-small_light.png -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/1.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/10.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/11.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/12.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/13.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/14.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/15.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/16.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/17.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/18.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/19.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/2.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/20.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/21.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/3.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/4.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/5.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/6.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/7.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/8.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/profile/backgrounds/thumbnail/9.jpg -------------------------------------------------------------------------------- /vj4/ui/components/tab/var.inc.styl: -------------------------------------------------------------------------------- 1 | $tab-item-height = 35px 2 | $tab-item-shadow-size = 10px 3 | $tab-item-height-mobile = 32px // 32 * (13 / 16) is an integer 4 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/expand_less.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/expand_more.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/pages/discussion_node_bg/nodes@2x_solution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/pages/discussion_node_bg/nodes@2x_solution.png -------------------------------------------------------------------------------- /vj4/job/__init__.py: -------------------------------------------------------------------------------- 1 | from vj4.job import rank 2 | from vj4.job import record 3 | from vj4.job import rp 4 | from vj4.job import num 5 | from vj4.job import difficulty 6 | -------------------------------------------------------------------------------- /vj4/ui/components/navigation/nav-logo-small@2x_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joint-online-judge/cb4/HEAD/vj4/ui/components/navigation/nav-logo-small@2x_light.png -------------------------------------------------------------------------------- /vj4/ui/misc/icons/chevron_left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/chevron_right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/start_server_dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PROJECT_DIR=$( dirname "${BASH_SOURCE[0]}" )/.. 3 | cd "$PROJECT_DIR" 4 | 5 | npm run build 6 | python3.5 -m vj4.server --debug 7 | -------------------------------------------------------------------------------- /vj4/ui/components/emoji/emoji.page.styl: -------------------------------------------------------------------------------- 1 | .emoji 2 | width: 1em 3 | height: 1em 4 | display: inline-block 5 | background-size: contain 6 | vertical-align: baseline 7 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/italic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "baseUrl": ".", 5 | "paths": { 6 | "vj/*": ["./*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/statistics.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/upload.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/warning.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/formula.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/templates/partials/problem_stat.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ _('{0} problems').format(pcount) }}

3 |
4 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | stages { 4 | stage('Build') { 5 | steps { 6 | sh 'docker build -t jioj/cb4:latest .' 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /scripts/start_server_production.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PROJECT_DIR=$( dirname "${BASH_SOURCE[0]}" )/.. 3 | cd "$PROJECT_DIR" 4 | 5 | npm run build:production 6 | python3.5 -m vj4.unix_server 7 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/code.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/quote.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/handler/error.py: -------------------------------------------------------------------------------- 1 | from vj4 import error 2 | from vj4.handler import base 3 | 4 | 5 | class NotFoundHandler(base.Handler): 6 | async def get(self): 7 | raise error.NotFoundError(self.url) 8 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/pages/domain_manage_role.page.styl: -------------------------------------------------------------------------------- 1 | .page--domain_manage_role 2 | .col--checkbox 3 | width: 60px 4 | 5 | .col--id 6 | width: 140px 7 | 8 | .col--users 9 | width: 100px 10 | -------------------------------------------------------------------------------- /vj4/ui/pages/domain_manage_user.page.styl: -------------------------------------------------------------------------------- 1 | .page--domain_manage_user 2 | .col--checkbox 3 | width: 60px 4 | 5 | .col--uid 6 | width: 160px 7 | 8 | .col--role 9 | width: 160px 10 | -------------------------------------------------------------------------------- /vj4/ui/components/autocomplete/userselectautocomplete.page.styl: -------------------------------------------------------------------------------- 1 | .menu.user-select 2 | width: rem(200px) 3 | 4 | .user-select__uid 5 | font-size: rem($font-size-small) 6 | color: $userselect-uid-color 7 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/delete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/reply.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/pages/home_account.page.styl: -------------------------------------------------------------------------------- 1 | .page--home_account 2 | [name="form_item_background_img"] .radiobox__image 3 | width: rem(100px) 4 | height: rem(62px) 5 | background-size: rem(100px 62px) 6 | -------------------------------------------------------------------------------- /vj4/ui/templates/judge_playground.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/basic.html" %} 2 | {% block content %} 3 |
4 |
5 |
6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /examples/haproxy-site.cfg: -------------------------------------------------------------------------------- 1 | frontend vj4-frontend 2 | bind *:8080 3 | default_backend vj4-backend 4 | 5 | backend vj4-backend 6 | # Need to disable chroot for haproxy. 7 | server sock1 /home/vj4/vj4.sock 8 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/logout.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/textalign.styl: -------------------------------------------------------------------------------- 1 | .text-center 2 | text-align: center 3 | 4 | .text-right 5 | text-align: right 6 | 7 | .text-left 8 | text-align: left 9 | 10 | .text-justify 11 | text-align: justify 12 | -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/gen_thumbnails.sh: -------------------------------------------------------------------------------- 1 | for f in *.jpg 2 | do 3 | echo "$f..." 4 | convert "$f" -strip -interlace Plane -quality 92 -resize 200x124^ -gravity center -extent 200x124 "thumbnail/$f" 5 | done 6 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/hourglass.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vijos", 3 | "icons": [ 4 | { 5 | "src": "\/android-chrome-192x192.png", 6 | "sizes": "192x192", 7 | "type": "image\/png", 8 | "density": 4 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/info--circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/platform--windows.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/check--circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/templates/components/nothing.html: -------------------------------------------------------------------------------- 1 | {% macro render(text, compact=false) %} 2 |
3 |
4 | {{ _(text) }} 5 |
6 | {% endmacro %} 7 | -------------------------------------------------------------------------------- /examples/h2o-site.conf: -------------------------------------------------------------------------------- 1 | listen: 2 | port: 8080 3 | hosts: 4 | "*": 5 | paths: 6 | /: 7 | proxy.reverse.url: "http://[unix:/home/vj4/vj4.sock]" 8 | proxy.preserve-host: ON 9 | proxy.websocket: ON 10 | 11 | -------------------------------------------------------------------------------- /vj4/ui/components/react-splitpane/SplitPaneFillOverlay.page.styl: -------------------------------------------------------------------------------- 1 | .splitpane-fill 2 | display: flex 3 | position: absolute 4 | overflow: hidden 5 | left: 0 6 | width: 100% 7 | top: 0 8 | height: 100% 9 | user-select: none 10 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/security.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/constant/util/objectMeta.js: -------------------------------------------------------------------------------- 1 | export default function attachObjectMeta(obj, key, value) { 2 | Object.defineProperty(obj, `__${key}`, { 3 | value, 4 | enumerable: false, 5 | configurable: false, 6 | writable: false, 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/link--external.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/pages/domain_join.page.styl: -------------------------------------------------------------------------------- 1 | .page--domain_join 2 | .domain_join__container 3 | +above(rupture.mobile-cutoff) 4 | width: rem(500px) 5 | margin: rem(50px) auto 6 | 7 | +above(rupture.desktop-cutoff) 8 | width: rem(700px) 9 | -------------------------------------------------------------------------------- /vj4/ui/utils/zIndexManager.js: -------------------------------------------------------------------------------- 1 | let zIndexCurrent = 1000; 2 | 3 | const manager = { 4 | getCurrent() { 5 | return zIndexCurrent; 6 | }, 7 | getNext() { 8 | return ++zIndexCurrent; 9 | }, 10 | }; 11 | 12 | export default manager; 13 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/utils/substitute.js: -------------------------------------------------------------------------------- 1 | export default function substitute(str, obj) { 2 | return str.replace(/\{([^{}]+)\}/g, (match, key) => { 3 | if (obj[key] !== undefined) { 4 | return String(obj[key]); 5 | } 6 | return `{${key}}`; 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /vj4/ui/components/scratchpad/Panel.page.styl: -------------------------------------------------------------------------------- 1 | @import './var.inc.styl' 2 | 3 | .scratchpad__panel-title 4 | background: $primary-color 5 | line-height: rem($panel-header-height) 6 | padding: rem(0 5px) 7 | font-size: rem($font-size-small) 8 | color: #FFF 9 | -------------------------------------------------------------------------------- /vj4/ui/templates/partials/hamburger.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /vj4/ui/misc/float.styl: -------------------------------------------------------------------------------- 1 | .float-left 2 | float: left 3 | 4 | .float-right 5 | float: right 6 | 7 | .clearfix:after 8 | visibility: hidden 9 | display: block 10 | font-size: 0 11 | content: ' ' 12 | clear: both 13 | height: 0 14 | zoom: 1 15 | -------------------------------------------------------------------------------- /vj4/ui/common/functions.inc.styl: -------------------------------------------------------------------------------- 1 | word-wrap() 2 | overflow-wrap: break-word 3 | word-wrap: break-word 4 | -ms-word-break: break-all 5 | word-break: break-word 6 | -ms-hyphens: auto 7 | -moz-hyphens: auto 8 | -webkit-hyphens: auto 9 | hyphens: auto 10 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/stack.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/utils/i18n.js: -------------------------------------------------------------------------------- 1 | import substitute from './substitute'; 2 | 3 | export default function i18n(str, ...params) { 4 | if (window.LOCALES && window.LOCALES[str]) { 5 | return substitute(window.LOCALES[str], params); 6 | } 7 | return substitute(str, params); 8 | } 9 | -------------------------------------------------------------------------------- /vj4/ui/components/tab/tab.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | import Tab from './Tab'; 3 | 4 | const tabPage = new AutoloadPage('tabPage', () => { 5 | Tab.initAll(); 6 | Tab.initEventListeners(); 7 | }); 8 | 9 | export default tabPage; 10 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/calendar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/template/webicon.inc.styl: -------------------------------------------------------------------------------- 1 | $icon-font-name = '{{ options.fontName }}' 2 | 3 | {% for icon in glyphs %} 4 | $icon-{{ icon.name }} = '\{{ icon.unicode[0].charCodeAt(0).toString(16)|upper }}' 5 | $icon-{{ icon.name }}-file = '{{ icon.name }}.svg' 6 | {% endfor %} 7 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/vote--up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/templates/components/noscript_note.html: -------------------------------------------------------------------------------- 1 | {% macro render() %} 2 | 7 | {% endmacro %} 8 | -------------------------------------------------------------------------------- /vj4/ui/components/marker/marker.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | import MarkerReactive from './MarkerReactive'; 3 | 4 | const markerPage = new AutoloadPage('markerPage', () => { 5 | MarkerReactive.initAll(); 6 | }); 7 | 8 | export default markerPage; 9 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/close--circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/star--outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/pages/problem_submit.page.styl: -------------------------------------------------------------------------------- 1 | .page--problem_submit 2 | .col--status 3 | width: rem(150px) 4 | 5 | .col--submit-at 6 | width: rem(160px) 7 | text-align: center 8 | 9 | +mobile() 10 | .col--memory, 11 | .col--time 12 | display: none 13 | -------------------------------------------------------------------------------- /vj4/ui/components/dropdown/dropdown.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | import Dropdown from './Dropdown'; 3 | 4 | const dropdownPage = new AutoloadPage('dropdownPage', () => { 5 | Dropdown.registerLifeCycleHooks(); 6 | }); 7 | 8 | export default dropdownPage; 9 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/lab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/vote--down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/facebook.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/mail.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/components/table/styledTable.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | import StyledTable from './StyledTable'; 3 | 4 | const styledTablePage = new AutoloadPage('styledTablePage', () => { 5 | StyledTable.registerLifeCycleHooks(); 6 | }); 7 | 8 | export default styledTablePage; 9 | -------------------------------------------------------------------------------- /vj4/ui/components/form/select.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | import 'select2'; 3 | import 'select2/dist/css/select2.css'; 4 | 5 | const selectPage = new AutoloadPage('selectPage', () => { 6 | $('.select.select2').select2(); 7 | }); 8 | 9 | export default selectPage; 10 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/insert--image.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/ordered_list.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/pages/contest_detail.page.styl: -------------------------------------------------------------------------------- 1 | .page--contest_detail 2 | .col--status 3 | width: rem(150px) 4 | 5 | .col--submit-at 6 | width: rem(150px) 7 | 8 | .col--problem 9 | border-left: 1px solid $table-border-color 10 | 11 | +mobile() 12 | .col--submit-at 13 | display: none 14 | -------------------------------------------------------------------------------- /vj4/ui/components/autocomplete/autocomplete.page.styl: -------------------------------------------------------------------------------- 1 | .drop-element.autocomplete 2 | max-width: rem(250px) 3 | 4 | .empty-row 5 | color: $autocomplete-empty-row-color 6 | font-size: $(font-size-small) 7 | padding: rem(10px) 8 | 9 | .menu 10 | max-height: rem(250px) 11 | overflow: auto 12 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/enlarge.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/platform--mobile.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/shrink.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/info.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/schedule--fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/tag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/templates/user_register_mail.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/mail.html" %} 2 | {% block title %}{{ _('Sign Up') }}{% endblock %} 3 | {% block content %} 4 |

{{ _('Hello! You can click following link to sign up your Vijos account:') }}

5 |

{{ url_prefix }}{{ url }}

6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/comment--text.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/crown.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/templates/partials/training_default.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": 1, 4 | "title": "最初的最初 - A+B Problem", 5 | "require_nids": [], 6 | "pids": [1000, "56c52ade2fefd90539000005"] 7 | }, 8 | { 9 | "_id": 2, 10 | "title": "最初的进阶", 11 | "require_nids": [1], 12 | "pids": [1001, 1002] 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /vj4/ui/templates/user_register_mail_sent.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/immersive.html" %} 2 | {% block content %} 3 |
4 |

{{ _('Sign Up') }}

5 |
6 | {{ _('Sign up mail has been sent to your email.') }} 7 |
8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/account--circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/comment--multiple.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/nothing.styl: -------------------------------------------------------------------------------- 1 | .nothing-placeholder 2 | padding: rem(80px 0) 3 | text-align: center 4 | font-size: rem($font-size-title) 5 | color: #AAA 6 | 7 | &.compact 8 | padding: rem(20px 0) 9 | 10 | .nothing-icon 11 | background: url(./puzzled_twd2.svg) no-repeat center center 12 | height: 184px 13 | margin-bottom: rem(20px) 14 | -------------------------------------------------------------------------------- /vj4/ui/templates/components/contest.html: -------------------------------------------------------------------------------- 1 | {% import "components/user.html" as user with context %} 2 | {% macro render_time(tdoc_time) %} 3 | {{ datetime_span(tdoc_time, false, '%Y-%m-%d %H:%M') }} 4 | {% endmacro %} 5 | 6 | {% macro render_duration(tdoc) %} 7 | {{ ((tdoc['end_at'] - tdoc['begin_at']).total_seconds() / 3600)|round(1) }} 8 | {% endmacro %} 9 | -------------------------------------------------------------------------------- /vj4/ui/templates/user_changemail_mail_sent.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/immersive.html" %} 2 | {% block content %} 3 |
4 |

{{ _('New Email') }}

5 |
6 | {{ _('Confirmation mail has been sent to your new email.') }} 7 |
8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /vj4/ui/templates/user_lostpass_mail_sent.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/immersive.html" %} 2 | {% block content %} 3 |
4 |

{{ _('Lost Password') }}

5 |
6 | {{ _('Password reset mail has been sent to your email.') }} 7 |
8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/google_plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/templates/components/homework.html: -------------------------------------------------------------------------------- 1 | {% import "components/user.html" as user with context %} 2 | {% macro render_time(tdoc_time) %} 3 | {{ datetime_span(tdoc_time, false, '%Y-%m-%d %H:%M') }} 4 | {% endmacro %} 5 | 6 | {% macro render_extension(tdoc) %} 7 | {{ ((tdoc['end_at'] - tdoc['penalty_since']).total_seconds() / 3600)|round(1) }} 8 | {% endmacro %} 9 | -------------------------------------------------------------------------------- /vj4/ui/utils/parseQueryString.js: -------------------------------------------------------------------------------- 1 | export default function parseQueryString(str) { 2 | const obj = {}; 3 | (str || document.location.search) 4 | .replace(/(^\?)/, '') 5 | .split('&') 6 | .forEach((n) => { 7 | const s = n.split('=').map(v => decodeURIComponent(v)); 8 | obj[s[0]] = s[1]; 9 | }); 10 | return obj; 11 | } 12 | -------------------------------------------------------------------------------- /scripts/build/main.js: -------------------------------------------------------------------------------- 1 | import { argv } from 'yargs'; 2 | import root from './utils/root.js'; 3 | import runGulp from './runGulp.js'; 4 | import runWebpack from './runWebpack.js'; 5 | 6 | const { watch, production } = argv; 7 | 8 | async function main() { 9 | await runGulp(argv); 10 | runWebpack(argv); 11 | } 12 | 13 | process.chdir(root()); 14 | main(); 15 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/help2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/components/scratchpad/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import ui from './ui'; 3 | import editor from './editor'; 4 | import pretest from './pretest'; 5 | import records from './records'; 6 | 7 | const reducer = combineReducers({ 8 | ui, 9 | editor, 10 | pretest, 11 | records, 12 | }); 13 | 14 | export default reducer; 15 | -------------------------------------------------------------------------------- /vj4/ui/templates/user_changemail_mail.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/mail.html" %} 2 | {% block title %}{{ _('Change Email') }}{% endblock %} 3 | {% block content %} 4 |

{{ _('Hello, {0}! You can click following link to active your new email of your Vijos account:').format(uname) }}

5 |

{{ url_prefix }}{{ url }}

6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /vj4/ui/templates/user_lostpass_mail.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/mail.html" %} 2 | {% block title %}{{ _('Lost Password') }}{% endblock %} 3 | {% block content %} 4 |

{{ _('Hello, {0}! You can click following link to reset the password of your Vijos account:').format(uname) }}

5 |

{{ url_prefix }}{{ url }}

6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /vj4/ui/components/form/var.inc.styl: -------------------------------------------------------------------------------- 1 | form-styles() 2 | appearance: none 3 | display: block 4 | width: 100% 5 | font-size: rem($font-size-secondary) 6 | margin: $input-margin 7 | height: rem($form-control-height) 8 | line-height: 1.2 9 | padding: rem(5px) 10 | border: $input-border 11 | 12 | &.inline 13 | display: inline-block 14 | width: auto 15 | -------------------------------------------------------------------------------- /vj4/ui/pages/problem_edit.page.js: -------------------------------------------------------------------------------- 1 | import { NamedPage } from 'vj/misc/PageLoader'; 2 | 3 | const page = new NamedPage('problem_edit', async () => { 4 | $(document).on('click', '[name="problem-sidebar__show-category"]', (ev) => { 5 | $(ev.currentTarget).hide(); 6 | $('[name="problem-sidebar__categories"]').show(); 7 | }); 8 | }); 9 | 10 | export default page; 11 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/erase.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/pages/discussion_node.page.js: -------------------------------------------------------------------------------- 1 | import { NamedPage } from 'vj/misc/PageLoader'; 2 | 3 | const page = new NamedPage('discussion_node', async () => { 4 | $(document).on('click', '[name="problem-sidebar__show-category"]', (ev) => { 5 | $(ev.currentTarget).hide(); 6 | $('[name="problem-sidebar__categories"]').show(); 7 | }); 8 | }); 9 | 10 | export default page; 11 | -------------------------------------------------------------------------------- /vj4/ui/pages/problem_submit.page.js: -------------------------------------------------------------------------------- 1 | import { NamedPage } from 'vj/misc/PageLoader'; 2 | 3 | const page = new NamedPage('problem_submit', async () => { 4 | $(document).on('click', '[name="problem-sidebar__show-category"]', (ev) => { 5 | $(ev.currentTarget).hide(); 6 | $('[name="problem-sidebar__categories"]').show(); 7 | }); 8 | }); 9 | 10 | export default page; 11 | -------------------------------------------------------------------------------- /vj4/handler/i18n.py: -------------------------------------------------------------------------------- 1 | from vj4 import app 2 | from vj4.handler import base 3 | 4 | 5 | @app.route('/lang/{lang}', 'language_set', global_route=True) 6 | class LanguageHandler(base.Handler): 7 | @base.route_argument 8 | @base.sanitize 9 | async def get(self, *, lang: str): 10 | await self.set_settings(view_lang=lang) 11 | self.json_or_redirect(self.referer_or_main) 12 | -------------------------------------------------------------------------------- /vj4/ui/components/scratchpad/PanelButton.page.styl: -------------------------------------------------------------------------------- 1 | @import './var.inc.styl' 2 | 3 | .scratchpad__panel-button 4 | background: none 5 | border: 0 6 | line-height: rem($panel-header-height) 7 | margin: 0 8 | color: #FFF 9 | padding: rem(0 5px) 10 | cursor: pointer 11 | font-size: rem($font-size-small) 12 | outline: 0 13 | 14 | &:hover 15 | color: $tab-color 16 | -------------------------------------------------------------------------------- /vj4/ui/pages/discussion_detail.page.js: -------------------------------------------------------------------------------- 1 | import { NamedPage } from 'vj/misc/PageLoader'; 2 | 3 | const page = new NamedPage('discussion_detail', async () => { 4 | $(document).on('click', '[name="problem-sidebar__show-category"]', (ev) => { 5 | $(ev.currentTarget).hide(); 6 | $('[name="problem-sidebar__categories"]').show(); 7 | }); 8 | }); 9 | 10 | export default page; 11 | -------------------------------------------------------------------------------- /vj4/ui/pages/problem_solution.page.js: -------------------------------------------------------------------------------- 1 | import { NamedPage } from 'vj/misc/PageLoader'; 2 | 3 | const page = new NamedPage('problem_solution', async () => { 4 | $(document).on('click', '[name="problem-sidebar__show-category"]', (ev) => { 5 | $(ev.currentTarget).hide(); 6 | $('[name="problem-sidebar__categories"]').show(); 7 | }); 8 | }); 9 | 10 | export default page; 11 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/linkedin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/bold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/user--multiple.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/pages/home_domain.page.styl: -------------------------------------------------------------------------------- 1 | .page--home_domain 2 | .col--icon 3 | width: 55px 4 | line-height: 1 5 | 6 | img 7 | border-radius: 50% 8 | 9 | .col--role 10 | width: rem(160px) 11 | border-left: 1px solid $table-border-color 12 | 13 | .col--action 14 | width: rem(135px) 15 | 16 | +mobile() 17 | .col--icon, .col--role 18 | display: none 19 | -------------------------------------------------------------------------------- /vj4/ui/components/messagepad/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import activeId from './activeId'; 3 | import dialogues from './dialogues'; 4 | import inputs from './inputs'; 5 | import isPosting from './isPosting'; 6 | 7 | const reducer = combineReducers({ 8 | activeId, 9 | dialogues, 10 | inputs, 11 | isPosting, 12 | }); 13 | 14 | export default reducer; 15 | -------------------------------------------------------------------------------- /vj4/ui/components/star/star.page.styl: -------------------------------------------------------------------------------- 1 | .star 2 | border: 0 3 | background: none 4 | outline: 0 5 | padding: 0 6 | margin: 0 7 | color: gray 8 | 9 | .starred--show 10 | display: none 11 | .starred--hide 12 | display: block 13 | 14 | &.activated 15 | color: orange 16 | .starred--show 17 | display: block 18 | .starred--hide 19 | display: none 20 | 21 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/global.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/build/plugins/webpackDummyOutputPlugin.js: -------------------------------------------------------------------------------- 1 | export default class DummyOutputPlugin { 2 | constructor(file) { 3 | this.file = file; 4 | } 5 | apply(compiler) { 6 | compiler.plugin('emit', (compilation, callback) => { 7 | compilation.assets[this.file] = { 8 | source: () => '', 9 | size: () => 0, 10 | }; 11 | callback(); 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/heart--outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/common/color.inc.styl: -------------------------------------------------------------------------------- 1 | aqua = #7FDBFF 2 | blue = #0074D9 3 | navy = #001F3F 4 | teal = #39CCCC 5 | green = #2ECC40 6 | olive = #3D9970 7 | lime = #01FF70 8 | yellow = #FFDC00 9 | orange = #FF851B 10 | red = #FF4136 11 | fuchsia = #F012BE 12 | purple = #B10DC9 13 | maroon = #85144B 14 | white = #ffffff 15 | silver = #dddddd 16 | gray = #aaaaaa 17 | black = #111111 18 | -------------------------------------------------------------------------------- /vj4/util/rank.py: -------------------------------------------------------------------------------- 1 | def ranked(diter, equ_func=lambda a, b: a == b): 2 | last_doc = None 3 | r = 0 4 | count = 0 5 | for doc in diter: 6 | count += 1 7 | if count == 1 or not equ_func(last_doc, doc): 8 | r = count 9 | last_doc = doc 10 | yield (r, doc) 11 | 12 | 13 | if __name__ == '__main__': 14 | for r, v in ranked(sorted([1, 2, 2, 2, 2, 3, 3, 3, 4, 5, 6])): 15 | print(r, v) 16 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/filter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/platform--unknown.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/utils/mediaQuery.js: -------------------------------------------------------------------------------- 1 | export function isAbove(width) { 2 | if (window.matchMedia) { 3 | return window.matchMedia(`(min-width: ${width}px)`).matches; 4 | } 5 | return window.innerWidth >= width; 6 | } 7 | 8 | export function isBelow(width) { 9 | if (window.matchMedia) { 10 | return window.matchMedia(`(max-width: ${width}px)`).matches; 11 | } 12 | return window.innerWidth <= width; 13 | } 14 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/platform--ios.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/block.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/schedule.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/insert--link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/debug.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/homework.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/pages/domain_manage_permission.page.styl: -------------------------------------------------------------------------------- 1 | .page--domain_manage_permission 2 | .col--p 3 | width: 80px 4 | text-align: center 5 | border-left: 1px solid $table-border-color 6 | 7 | td.col--description 8 | padding-left: rem(30px) !important 9 | 10 | .col--family 11 | line-height: 2 12 | color: #FFF 13 | background: lighten(saturate($primary-color, 70%), 30%) 14 | border-top: 2px solid $primary-color 15 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/award.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/sliders.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/qq.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/components/rotator/rotator.page.styl: -------------------------------------------------------------------------------- 1 | .rotator 2 | position: relative 3 | height: 1em 4 | 5 | .rotator__item 6 | position: absolute 7 | left: 0 8 | top: 0 9 | width: 100% 10 | height: 100% 11 | transition: transform .2s, opacity .2s 12 | transition-timing-function: ease-out-cubic 13 | 14 | &.pos--above 15 | transform: translateY(-100%) 16 | opacity: 0 17 | 18 | &.pos--below 19 | transform: translateY(100%) 20 | opacity: 0 21 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/platform--chromeos.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/templates/layout/home_base.html: -------------------------------------------------------------------------------- 1 | {% import "components/home.html" as home with context %} 2 | {% extends "layout/basic.html" %} 3 | {% block content %} 4 |
5 |
6 | {% block home_content %}{% endblock %} 7 |
8 |
9 | {{ home.render_sidebar() }} 10 | {% block home_sidebar %}{% endblock %} 11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/preview.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/wrench.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/templates/partials/problem_lucky.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | {{ _('Lucky') }} 4 |

5 |
6 |

{{ _('Pick a problem randomly based on current filter') }}

7 |
8 |
9 | -------------------------------------------------------------------------------- /vj4/ui/components/discussion/discussion.page.styl: -------------------------------------------------------------------------------- 1 | .discussion-node-tag 2 | font-size: rem($font-size-small) 3 | display: inline-block 4 | line-height: rem(22px) 5 | padding: rem(0 10px) 6 | vertical-align: middle 7 | border-radius: rem(12px) 8 | border: 1px solid $supplementary-border-color 9 | 10 | &:hover 11 | background: $primary-color 12 | color: #FFF !important 13 | text-decoration: none 14 | border-color: $primary-color 15 | 16 | .icon 17 | margin-right: rem(5px) 18 | -------------------------------------------------------------------------------- /examples/vj4.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Vj4 3 | After=syslog.target network.target mongodb.service rabbitmq.service 4 | 5 | [Service] 6 | Type=simple 7 | User=vj4 8 | Group=vj4 9 | WorkingDirectory=/home/vj4/vj4 10 | ExecStart=/home/vj4/python-venv/bin/python3.5 -m vj4.server \ 11 | --listen=unix:/home/vj4/vj4.sock \ 12 | --prefork=8 \ 13 | --ip-header="X-Real-IP" \ 14 | 15 | Restart=always 16 | StandardOutput=syslog 17 | StandardError=syslog 18 | 19 | [Install] 20 | WantedBy=multi-user.target 21 | -------------------------------------------------------------------------------- /vj4/ui/components/react/IconComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | export default function IconComponent(props) { 5 | const { 6 | name, 7 | className, 8 | ...rest 9 | } = props; 10 | const cn = classNames(className, `icon icon-${name}`); 11 | return ( 12 | 13 | ); 14 | } 15 | 16 | IconComponent.propTypes = { 17 | name: React.PropTypes.string.isRequired, 18 | className: React.PropTypes.string, 19 | }; 20 | -------------------------------------------------------------------------------- /vj4/ui/common/common.inc.styl: -------------------------------------------------------------------------------- 1 | // This file is imported in all stylus source code 2 | breakpoints = json('../breakpoints.json', { hash: true }) 3 | rupture.mobile-cutoff = unit(breakpoints.mobile, 'px') 4 | rupture.desktop-cutoff = unit(breakpoints.desktop, 'px') 5 | rupture.hd-cutoff = unit(breakpoints.hd, 'px') 6 | 7 | @import '~vj/misc/.iconfont/webicon.inc.styl' 8 | @import 'color.inc.styl' 9 | @import 'variables.inc.styl' 10 | @import 'easing.inc.styl' 11 | @import 'rem.inc.styl' 12 | @import 'functions.inc.styl' 13 | -------------------------------------------------------------------------------- /vj4/ui/components/scratchpad/DataInput.page.styl: -------------------------------------------------------------------------------- 1 | .scratchpad__data-input 2 | border: 0 3 | border-left: 3px solid #E0E0E0 4 | background: #F8F8F8 5 | padding: rem(8px 4px) 6 | font-family: $code-font-family 7 | font-size: rem($font-size-secondary) 8 | outline: 0 9 | resize: none 10 | color: #666 11 | height: 100% 12 | width: 100% 13 | transition: background-color .2s linear, border-color .2s linear 14 | 15 | &:focus 16 | background: #FFF 17 | border-color: lighten($primary-color, 70%) 18 | -------------------------------------------------------------------------------- /vj4/ui/utils/mongoId.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // https://github.com/andrasq/node-mongoid-js/blob/master/mongoid.js 4 | 5 | export function parse(idstring) { 6 | if (typeof idstring !== 'string') { 7 | idstring = String(idstring); 8 | } 9 | return { 10 | timestamp: parseInt(idstring.slice( 0, 0+8), 16), 11 | machineid: parseInt(idstring.slice( 8, 8+6), 16), 12 | pid: parseInt(idstring.slice(14, 14+4), 16), 13 | sequence: parseInt(idstring.slice(18, 18+6), 16), 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /vj4/ui/components/scratchpad/reducers/editor.js: -------------------------------------------------------------------------------- 1 | export default function reducer(state = { 2 | lang: Context.code_lang, 3 | code: Context.code_template, 4 | }, action) { 5 | switch (action.type) { 6 | case 'SCRATCHPAD_EDITOR_UPDATE_CODE': { 7 | return { 8 | ...state, 9 | code: action.payload, 10 | }; 11 | } 12 | case 'SCRATCHPAD_EDITOR_SET_LANG': { 13 | return { 14 | ...state, 15 | lang: action.payload, 16 | }; 17 | } 18 | default: 19 | return state; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /vj4/ui/pages/discussion_detail.page.styl: -------------------------------------------------------------------------------- 1 | .page--discussion_detail 2 | 3 | .topic__content 4 | margin: rem(20px 0) 5 | 6 | .profile__bg 7 | background-size: cover 8 | background-position: center center 9 | height: rem(60px) 10 | margin-bottom: rem(-32px) 11 | 12 | .sidebar-user-profile 13 | .media__body 14 | padding-left: rem(10px) 15 | padding-top: rem(20px) 16 | 17 | .user-profile-name 18 | font-size: 1.2rem 19 | color: #444 20 | vertical-align: middle 21 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/feeling-lucky.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/unordered_list.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/components/highlighter/highlighter.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | 3 | const highlighterPage = new AutoloadPage('highlighterPage', () => { 4 | System.import('./prismjs').then((module) => { 5 | const prismjs = module.default; 6 | function runHighlight($container) { 7 | prismjs.highlightBlocks($container); 8 | } 9 | runHighlight($('body')); 10 | $(document).on('vjContentNew', e => runHighlight($(e.target))); 11 | }); 12 | }); 13 | 14 | export default highlighterPage; 15 | -------------------------------------------------------------------------------- /vj4/ui/templates/ac_mail.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/mail.html" %} 2 | {% block title %}{{ _('P{0} - {1} Accepted!').format(pdoc['doc_id'], pdoc['title']) }}{% endblock %} 3 | {% block content %} 4 |

{{ _('Congratulations! Your submission is accepted.') }}

5 |

{{ _('Problem') }}: P{{ pdoc['doc_id'] }} {{ pdoc['title'] }}

6 |

{{ _('Code') }}:

7 |
 8 | {{ rdoc['code'] }}
 9 | 
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/test/test_misc.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from vj4.util import misc 4 | 5 | 6 | class Test(unittest.TestCase): 7 | def test_dedupe(self): 8 | self.assertListEqual(misc.dedupe([2,1,1,3,2,3]),[2,1,3]) 9 | self.assertListEqual(misc.dedupe([]),[]) 10 | self.assertListEqual(misc.dedupe(map(int,['2','1','1','3','2','3'])),[2,1,3]) 11 | self.assertListEqual(misc.dedupe(['b','a','b','c','b']),['b','a','c']) 12 | self.assertListEqual(misc.dedupe([0]),[0]) 13 | 14 | 15 | if __name__ == '__main__': 16 | unittest.main() 17 | -------------------------------------------------------------------------------- /vj4/ui/templates/partials/problem_sidebar.html: -------------------------------------------------------------------------------- 1 | {% if tdoc %} 2 | {% if tdoc['doc_type'] == vj4.model.document.TYPE_CONTEST %} 3 | {% include "partials/problem_sidebar_contest.html" %} 4 | {% with owner_udoc=udoc %} 5 | {% include "partials/contest_sidebar.html" %} 6 | {% endwith %} 7 | {% else %} 8 | {% include "partials/problem_sidebar_homework.html" %} 9 | {% include "partials/homework_sidebar.html" %} 10 | {% endif %} 11 | {% else %} 12 | {% include "partials/problem_sidebar_normal.html" %} 13 | {% endif %} 14 | -------------------------------------------------------------------------------- /vj4/ui/components/form/form.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | 3 | const formPage = new AutoloadPage('formPage', () => { 4 | $(document).on('vjFormDisableUpdate', 'input, select, textarea', (ev) => { 5 | const $input = $(ev.currentTarget); 6 | const $formItem = $input.closest('.form__item'); 7 | if ($input.prop('disabled')) { 8 | $formItem.addClass('is--disabled'); 9 | } else { 10 | $formItem.removeClass('is--disabled'); 11 | } 12 | }); 13 | }); 14 | 15 | export default formPage; 16 | -------------------------------------------------------------------------------- /vj4/ui/components/menu/menu.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | import { slideDown } from 'vj/utils/slide'; 3 | import delay from 'vj/utils/delay'; 4 | 5 | function expandMenu($menu) { 6 | slideDown($menu, 500, { opacity: 0 }, { opacity: 1 }); 7 | } 8 | 9 | async function expandAllMenus() { 10 | await delay(200); 11 | $('.menu.collapsed').get().forEach(menu => expandMenu($(menu))); 12 | } 13 | 14 | const menuPage = new AutoloadPage('menuPage', () => { 15 | expandAllMenus(); 16 | }); 17 | 18 | export default menuPage; 19 | -------------------------------------------------------------------------------- /vj4/ui/templates/layout/wiki_base.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/basic.html" %} 2 | {% block content %} 3 |
4 |
5 | {% block wiki_content %}{% endblock %} 6 |
7 |
8 |
9 | 13 |
14 |
15 |
16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /vj4/ui/templates/user_logout.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/immersive.html" %} 2 | {% block content %} 3 |
4 |
5 |

{{ _('Logout') }}

6 |
7 | 8 |
9 | 10 |
11 |
12 |
13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /vj4/ui/components/scratchpad/Editor.page.styl: -------------------------------------------------------------------------------- 1 | .ReactCodeMirror 2 | flex: 1 3 | position: relative 4 | overflow: hidden 5 | 6 | .cm-s-vjcm 7 | position: absolute 8 | left: 0 9 | top: 0 10 | width: 100% 11 | height: 100% 12 | font-family: $code-font-family 13 | font-size: rem($font-size-secondary) 14 | line-height: 1.2 15 | 16 | &.CodeMirror-focused .CodeMirror-selected 17 | background: #cee4f2 18 | 19 | .CodeMirror-gutters 20 | border-right: 0 21 | background: none 22 | 23 | .CodeMirror-linenumber 24 | color: #DDD 25 | -------------------------------------------------------------------------------- /vj4/ui/components/scratchpad/PanelButtonComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | export default function PanelButtonComponent(props) { 5 | const { 6 | className, 7 | children, 8 | ...rest 9 | } = props; 10 | const cn = classNames(className, 'scratchpad__panel-button'); 11 | return ( 12 | 13 | ); 14 | } 15 | 16 | PanelButtonComponent.propTypes = { 17 | className: React.PropTypes.string, 18 | children: React.PropTypes.node, 19 | }; 20 | -------------------------------------------------------------------------------- /vj4/ui/templates/layout/basic.html: -------------------------------------------------------------------------------- 1 | {% set layout_name = "basic" %} 2 | {% extends "layout/html5.html" %} 3 | {% block body %} 4 | 5 | {% include "partials/nav.html" %} 6 | 7 |
8 |
9 | {% include "partials/header_mobile.html" %} 10 | {% include "partials/header.html" %} 11 |
12 | {% block content %}{% endblock %} 13 |
14 | {% include "partials/footer.html" %} 15 |
16 | 17 | {% include "partials/login_dialog.html" %} 18 | 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /vj4/ui/templates/problem_upload.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout/simple.html' %} 2 | {% block body %} 3 |
4 |

{{ _('Current dataset: {0}').format(pdoc['data']|default(_('(None)'))) }}

5 |

{{ _('MD5: {0}').format(md5|default(_('(None)'))) }}

6 |

7 | {{ _('New dataset') }}: 8 | 9 | 10 | 11 |

12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /vj4/ui/pages/record_detail.page.styl: -------------------------------------------------------------------------------- 1 | .page--record_detail 2 | 3 | .compiler-text 4 | padding: rem(20px 0) 5 | font-size: rem($font-size-small) 6 | &:empty 7 | display: none 8 | 9 | .col--case 10 | width: rem(70px) 11 | 12 | .col--memory, .col--time 13 | width: rem(120px) 14 | 15 | .col--case, .col--status 16 | border-right: 1px solid $table-border-color 17 | 18 | +mobile() 19 | .col--case 20 | display: none 21 | 22 | .col--time 23 | width: rem(70px) 24 | 25 | .col--memory 26 | width: rem(100px) 27 | -------------------------------------------------------------------------------- /vj4/util/pagination.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from vj4 import error 3 | 4 | 5 | async def paginate(cursor, page: int, page_size: int): 6 | if page <= 0: 7 | raise error.ValidationError('page') 8 | count, page_docs = await asyncio.gather(cursor.count(), 9 | cursor.skip((page - 1) * page_size) \ 10 | .limit(page_size) \ 11 | .to_list()) 12 | num_pages = (count + page_size - 1) // page_size 13 | return page_docs, num_pages, count 14 | -------------------------------------------------------------------------------- /scripts/build/utils/mapWebpackUrlPrefix.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export default function mapWebpackUrlPrefix(mapList) { 4 | const rules = mapList.map(mappingRule => { 5 | const regex = mappingRule.prefix.split('/').map(s => _.escapeRegExp(s)).join('[\\/\\\\]'); 6 | return { 7 | from: new RegExp(`^${regex}`, mappingRule.flag), 8 | to: mappingRule.replace, 9 | }; 10 | }); 11 | return function mapUrl(url) { 12 | rules.forEach(rule => { 13 | url = url.replace(rule.from, rule.to); 14 | }); 15 | return url; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /vj4/ui/components/react-splitpane/SplitPaneFillOverlayComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | export default function SplitPaneFillOverlayComponent(props) { 5 | const { 6 | className, 7 | children, 8 | ...rest 9 | } = props; 10 | const cn = classNames(className, 'splitpane-fill'); 11 | return ( 12 |
{children}
13 | ); 14 | } 15 | 16 | SplitPaneFillOverlayComponent.propTypes = { 17 | className: React.PropTypes.string, 18 | children: React.PropTypes.node, 19 | }; 20 | -------------------------------------------------------------------------------- /vj4/ui/utils/emulateAnchorClick.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {Event} ev 4 | * @param {String} targetUrl 5 | * @param {Boolean} alwaysOpenInNewWindow 6 | */ 7 | export default function emulateAnchorClick(ev, targetUrl, alwaysOpenInNewWindow = false) { 8 | let openInNewWindow; 9 | if (alwaysOpenInNewWindow) { 10 | openInNewWindow = true; 11 | } else { 12 | openInNewWindow = (ev.ctrlKey || ev.shiftKey || ev.metaKey); 13 | } 14 | if (openInNewWindow) { 15 | window.open(targetUrl); 16 | } else { 17 | window.location.href = targetUrl; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vj4/ui/templates/components/sidemenu.html: -------------------------------------------------------------------------------- 1 | {% macro render_item(icon_name, page_name, label=none) %} 2 | 15 | {% endmacro %} 16 | -------------------------------------------------------------------------------- /vj4/ui/components/dropdown/dropdown.page.styl: -------------------------------------------------------------------------------- 1 | .dropdown-target 2 | display: none 3 | 4 | .drop .dropdown-target 5 | display: block 6 | 7 | .dropdown 8 | .menu 9 | min-width: rem(150px) 10 | box-shadow: $menu-drop-shadow 11 | background: $menu-drop-bg-color 12 | font-size: rem($font-size-small) 13 | 14 | .menu__link 15 | padding: rem(8px 10px) 16 | 17 | .dropdown 18 | .drop-content 19 | transition: transform .1s ease-in-out 20 | transform: scale(0.9) translateZ(0) 21 | 22 | &.drop-after-open 23 | .drop-content 24 | transform: scale(1) translateZ(0) 25 | -------------------------------------------------------------------------------- /vj4/ui/components/katex/katex.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | 3 | import 'katex/dist/katex.min.css'; 4 | import './katex.styl'; 5 | 6 | const katexPage = new AutoloadPage('katexPage', () => { 7 | System.import('katex/dist/contrib/auto-render.min.js').then((renderKatex) => { 8 | function runKatex($containers) { 9 | $containers.get().forEach(container => renderKatex(container)); 10 | } 11 | runKatex($('.typo')); 12 | $(document).on('vjContentNew', e => runKatex($(e.target).find('.typo'))); 13 | }); 14 | }); 15 | 16 | export default katexPage; 17 | -------------------------------------------------------------------------------- /vj4/ui/components/react/DomComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class DomComponent extends React.PureComponent { 4 | componentDidMount() { 5 | this.refs.dom.appendChild(this.props.childDom); 6 | } 7 | componentWillUnmount() { 8 | $(this.refs.dom).empty(); 9 | } 10 | render() { 11 | const { 12 | childDom, 13 | ...rest 14 | } = this.props; 15 | return ( 16 |
17 | ); 18 | } 19 | } 20 | 21 | DomComponent.propTypes = { 22 | childDom: React.PropTypes.instanceOf(HTMLElement).isRequired, 23 | }; 24 | -------------------------------------------------------------------------------- /vj4/ui/templates/partials/discussion_edit_form.html: -------------------------------------------------------------------------------- 1 | {% import "components/form.html" as form with context %} 2 | {% if ddoc['highlight'] or handler.has_perm(vj4.model.builtin.PERM_HIGHLIGHT_DISCUSSION) %} 3 | {{ form.form_checkbox(label='Highlight', name='highlight', value=ddoc['highlight']|default(false)) }} 4 | {% endif %} 5 | {{ form.form_text(label='Title', name='title', value=ddoc['title']|default(''), autofocus=true, required=true) }} 6 | {{ form.form_textarea(columns=12, label='Content', name='content', value=ddoc['content']|default(''), hotkeys='ctrl+enter:submit', markdown=true, required=true) }} 7 | -------------------------------------------------------------------------------- /vj4/ui/components/contest/contest_sidebar.page.styl: -------------------------------------------------------------------------------- 1 | .contest-sidebar__bg 2 | display: block 3 | background: #67AABB url(problem-contest-bg.png) right top no-repeat 4 | color: #FFF !important 5 | font-size: rem($font-size) 6 | padding: rem(20px 0) 7 | 8 | +retina() 9 | background-image: url(problem-contest-bg@2x.png) 10 | background-size: 131px 150px 11 | 12 | h1 13 | font-size: rem($font-size-title) 14 | color: #FFF 15 | text-shadow: 0 1px 3px rgba(#000, 0.5) 16 | 17 | &:hover 18 | text-decoration: none 19 | 20 | .contest-sidebar__status 21 | margin-top: rem(20px) 22 | -------------------------------------------------------------------------------- /vj4/ui/misc/immersive.styl: -------------------------------------------------------------------------------- 1 | .layout--immersive 2 | #panel 3 | background: #80b2d5 url('immersive-background.jpg') no-repeat 4 | background-size: 1920px 1080px 5 | background-size: cover 6 | +retina() 7 | background-image: url('immersive-background@2x.jpg') 8 | 9 | .immersive--content 10 | color: $immersive-text-color 11 | 12 | h1, h2, h3, h4, h5, h6 13 | color: $immersive-header-color 14 | 15 | +above(rupture.mobile-cutoff) 16 | .immersive--center 17 | width: 200px 18 | margin: 0 auto 19 | 20 | .immersive--center 21 | h1 22 | text-align: center 23 | margin: 2em 0 24 | -------------------------------------------------------------------------------- /vj4/ui/components/highlighter/codemirror.page.styl: -------------------------------------------------------------------------------- 1 | @css { 2 | span.cm-comment { color: #008000; } 3 | span.cm-keyword, span.cm-variable-3 { line-height: 1em; color: #00f; } 4 | span.cm-string { color: #a31515; } 5 | span.cm-builtin { line-height: 1em; font-weight: bold; color: #077; } 6 | span.cm-special { line-height: 1em; font-weight: bold; color: #0aa; } 7 | span.cm-variable { color: black; } 8 | span.cm-meta { color: #2b91af; } 9 | span.cm-link { color: #3a3; } 10 | .CodeMirror-activeline-background { background: #e8f2ff; } 11 | .CodeMirror-matchingbracket { outline:1px solid grey; color:black !important; } 12 | } 13 | -------------------------------------------------------------------------------- /vj4/ui/pages/contest_scoreboard.page.styl: -------------------------------------------------------------------------------- 1 | .page--contest_scoreboard 2 | .col--rank 3 | width: rem(70px) 4 | border-right: 1px solid $table-border-color 5 | 6 | .col--user 7 | width: rem(140px) 8 | 9 | .col--uid 10 | width: rem(140px) 11 | 12 | .col--total_score, .col--total_time_str, .col--solved_problems 13 | width: rem(100px) 14 | text-align: center 15 | 16 | .col--problem_detail 17 | border-left: 1px solid $table-border-color 18 | text-align: center 19 | 20 | +mobile() 21 | .col--user 22 | width: auto 23 | 24 | .col--problem_detail 25 | display: none 26 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/utils/tpl.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export default function tpl(pieces, ...substitutions) { 4 | let result = pieces[0]; 5 | for (let i = 0; i < substitutions.length; ++i) { 6 | const subst = substitutions[i]; 7 | let substHtml; 8 | if (typeof subst === 'object' && subst.templateRaw) { 9 | substHtml = subst.html; 10 | } else { 11 | substHtml = _.escape(String(subst)); 12 | } 13 | result += substHtml + pieces[i + 1]; 14 | } 15 | return result; 16 | } 17 | 18 | export function rawHtml(html) { 19 | return { 20 | templateRaw: true, 21 | html, 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /vj4/util/version.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import git 3 | import logging 4 | from os import path 5 | 6 | import vj4 7 | from vj4.util import argmethod 8 | 9 | _logger = logging.getLogger(__name__) 10 | 11 | 12 | @functools.lru_cache() 13 | @argmethod.wrap 14 | def get(): 15 | try: 16 | return git.Repo(path.dirname(path.dirname(vj4.__file__))).git.describe(always=True, tags=True) 17 | except (git.InvalidGitRepositoryError, git.GitCommandError) as e: 18 | _logger.error('Failed to get repository: %s', repr(e)) 19 | return 'unknown' 20 | 21 | 22 | if __name__ == '__main__': 23 | argmethod.invoke_by_args() 24 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/wechat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/pages/contest_system_test.page.js: -------------------------------------------------------------------------------- 1 | import { NamedPage } from 'vj/misc/PageLoader'; 2 | import { LANG_MOSS_WILDCARDS } from 'vj/constant/language'; 3 | 4 | const page = new NamedPage('contest_system_test', async () => { 5 | const $language = $('[name="language"]'); 6 | const $wildcards = $('[name="wildcards"]'); 7 | 8 | const changeWildcards = () => { 9 | const lang = $language.val(); 10 | const wildcards = LANG_MOSS_WILDCARDS[lang] || []; 11 | $wildcards.val(wildcards.join(', ')); 12 | }; 13 | 14 | $language.on('change', changeWildcards); 15 | changeWildcards(); 16 | }); 17 | 18 | export default page; 19 | -------------------------------------------------------------------------------- /vj4/ui/templates/problem_statistics.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/basic.html" %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |

{{ _('Submission Statistics') }}

8 |
9 |
10 | {{ _('Swx Template') }} 11 |
12 |
13 |
14 |
15 | {% with owner_udoc=udoc %} 16 | {% include "partials/problem_sidebar.html" %} 17 | {% endwith %} 18 |
19 |
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /vj4/service/staticmanifest.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from vj4.util import json 3 | 4 | 5 | MANIFEST_FILE = 'static-manifest.json' 6 | _manifest_dir = None 7 | _manifest_path = None 8 | _manifest = {} 9 | 10 | 11 | def init(static_dir): 12 | global _manifest_dir, _manifest_path, _manifest 13 | _manifest_dir = static_dir 14 | _manifest_path = path.join(_manifest_dir, MANIFEST_FILE) 15 | try: 16 | with open(_manifest_path, 'r') as manifest_file: 17 | data = json.decode(manifest_file.read()) 18 | _manifest = data 19 | except Exception: 20 | pass 21 | 22 | 23 | def get(name): 24 | return _manifest.get(name, name) 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Editor temporaries. 2 | *~ 3 | *.swp 4 | *.sublime-project 5 | *.sublime-workspace 6 | .idea/ 7 | .vscode/ 8 | 9 | # Python cache. 10 | __pycache__/ 11 | *.pyc 12 | *.egg-info/ 13 | 14 | # UI stuffs. 15 | node_modules/ 16 | npm-debug.log 17 | .uibuild/ 18 | .cache/ 19 | 20 | # Vagrant dirts. 21 | Vagrantfile 22 | .vagrant/ 23 | 24 | # MaxMind DB. 25 | *.mmdb 26 | 27 | # Generated fonts 28 | vj4/ui/misc/.iconfont 29 | 30 | # Generated constants 31 | vj4/constant 32 | 33 | # Generated locales 34 | vj4/ui/static/locale 35 | 36 | # Webpack Stats 37 | .webpackStats.json 38 | 39 | !.gitkeep 40 | config.yaml 41 | 42 | venv 43 | .env 44 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/help.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/templates/problem_submit_tr.html: -------------------------------------------------------------------------------- 1 | {% import "components/record.html" as record with context %} 2 | 3 | {{ record.render_status_td(rdoc) }} 4 | 5 | {% if (rdoc['judge_category']) %} 6 | {{ ', '.join(rdoc['judge_category']) }} 7 | {% else %} 8 | pretest 9 | {% endif %} 10 | 11 | 12 | {{ rdoc['memory_kb'] |format_size(1024) }} 13 | 14 | 15 | {{ rdoc['time_ms'] }}ms 16 | 17 | {{ datetime_span(rdoc['submit_time'] or rdoc['_id'].generation_time) }} 18 | 19 | -------------------------------------------------------------------------------- /examples/nginx-site.conf: -------------------------------------------------------------------------------- 1 | upstream app_server { 2 | server unix:/home/vj4/vj4.sock fail_timeout=0; 3 | } 4 | 5 | map $http_upgrade $connection_upgrade { 6 | default upgrade; 7 | '' close; 8 | } 9 | 10 | server { 11 | listen 8080; 12 | server_name localhost; 13 | root /home/vj4/vj4/vj4/.uibuild; 14 | 15 | location / { 16 | try_files $uri @proxy_to_app; 17 | } 18 | 19 | location @proxy_to_app { 20 | proxy_pass http://app_server; 21 | proxy_http_version 1.1; 22 | proxy_set_header Upgrade $http_upgrade; 23 | proxy_set_header Connection $connection_upgrade; 24 | proxy_set_header X-Real-IP $remote_addr; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vj4/ui/components/loader/loader.page.styl: -------------------------------------------------------------------------------- 1 | .loader-container 2 | position: absolute 3 | left: 0 4 | width: 100% 5 | top: 0 6 | height: 100% 7 | 8 | .loader 9 | text-indent: -9999em 10 | border: rem(5px) solid rgba($primary-color, .2) 11 | border-left: rem(5px) solid $primary-color 12 | animation: load8 1.1s infinite linear 13 | border-radius: 50% 14 | width: rem(50px) 15 | height: rem(50px) 16 | margin-left: rem(-25px) 17 | margin-top: rem(-25px) 18 | left: 50% 19 | top: 50% 20 | position: absolute 21 | 22 | @keyframes load8 23 | 0% 24 | transform: rotate(0deg) 25 | 26 | 100% 27 | transform: rotate(360deg) 28 | 29 | 30 | -------------------------------------------------------------------------------- /vj4/ui/components/profile/profile.page.styl: -------------------------------------------------------------------------------- 1 | .user-profile-avatar 2 | border-radius: 50% 3 | 4 | for n in (1..21) 5 | .user-profile-bg--{n} 6 | background-image: url('backgrounds/' + n + '.jpg') 7 | 8 | .user-profile-bg--thumbnail-{n} 9 | background-image: url('backgrounds/thumbnail/' + n + '.jpg') 10 | 11 | .user-profile-badge 12 | display: inline-block 13 | font-size: rem(12px) 14 | padding: rem(3px 4px) 15 | line-height: 1 16 | 17 | &:hover 18 | text-decoration: none 19 | 20 | for key, _ in $badge-bg-color 21 | .badge--{key} 22 | background-color: $badge-bg-color[key] !important 23 | color: $badge-text-color[key] !important 24 | -------------------------------------------------------------------------------- /vj4/ui/templates/layout/immersive.html: -------------------------------------------------------------------------------- 1 | {% set layout_name = "immersive" %} 2 | {% set no_path_section = true %} 3 | {% extends "layout/html5.html" %} 4 | {% block body %} 5 | 6 | {% include "partials/nav.html" %} 7 | 8 |
9 |
10 | {% include "partials/header_mobile.html" %} 11 | 12 |
13 | {% block content %}{% endblock %} 14 |
15 | 16 | {% with show_topics = false %} 17 | {% include "partials/footer.html" %} 18 | {% endwith %} 19 | 20 |
21 | 22 | {% include "partials/login_dialog.html" %} 23 | 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /vj4/ui/components/highlighter/meta.js: -------------------------------------------------------------------------------- 1 | // The following content is extracted from: 2 | // https://github.com/codemirror/CodeMirror/blob/master/mode/meta.js 3 | 4 | export default [ 5 | { name: 'C', ext: ['c', 'h'] }, 6 | { name: 'C++', ext: ['cpp', 'c++', 'cc', 'cxx', 'hpp', 'h++', 'hh', 'hxx'] }, 7 | { name: 'Go', ext: ['go'] }, 8 | { name: 'Haskell', ext: ['hs'] }, 9 | { name: 'Java', ext: ['java'] }, 10 | { name: 'JavaScript', ext: ['js'] }, 11 | { name: 'Pascal', ext: ['p', 'pas'] }, 12 | { name: 'PHP', ext: ['php', 'php3', 'php4', 'php5', 'php7', 'phtml'] }, 13 | { name: 'Python', ext: ['BUILD', 'bzl', 'py', 'pyw'] }, 14 | { name: 'Rust', ext: ['rs'] }, 15 | ]; 16 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/web.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/components/emoji/emojify.page.js: -------------------------------------------------------------------------------- 1 | import emojify from 'emojify.js'; 2 | 3 | import { AutoloadPage } from 'vj/misc/PageLoader'; 4 | 5 | function runEmojify($container) { 6 | if ($container.is('[data-emoji-enabled]')) { 7 | emojify.run($container[0]); 8 | return; 9 | } 10 | $container.find('[data-emoji-enabled]').get().forEach(element => emojify.run(element)); 11 | } 12 | 13 | const emojifyPage = new AutoloadPage('emojifyPage', () => { 14 | emojify.setConfig({ 15 | img_dir: `${UiContext.cdn_prefix}img/emoji`, 16 | }); 17 | runEmojify($('body')); 18 | $(document).on('vjContentNew', e => runEmojify($(e.target))); 19 | }); 20 | 21 | export default emojifyPage; 22 | -------------------------------------------------------------------------------- /vj4/util/tools.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pkgutil 3 | from os import path 4 | 5 | from vj4.util import argmethod 6 | 7 | _logger = logging.getLogger(__name__) 8 | 9 | 10 | @argmethod.wrap 11 | async def ensure_all_indexes(): 12 | model_path = path.join(path.dirname(path.dirname(__file__)), 'model') 13 | for module_finder, name, ispkg in pkgutil.iter_modules([model_path]): 14 | if not ispkg: 15 | module = module_finder.find_module(name).load_module() 16 | if 'ensure_indexes' in dir(module): 17 | _logger.info('Ensuring indexes for "%s".' % name) 18 | await module.ensure_indexes() 19 | 20 | 21 | if __name__ == '__main__': 22 | argmethod.invoke_by_args() 23 | -------------------------------------------------------------------------------- /vj4/ui/components/form/textbox.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | 3 | const textboxPage = new AutoloadPage('textboxPage', () => { 4 | $(document).on('focusin', '.textbox.material input', (ev) => { 5 | $(ev.currentTarget).parent().addClass('focus'); 6 | }); 7 | 8 | $(document).on('focusout', '.textbox.material input', (ev) => { 9 | $(ev.currentTarget).parent().removeClass('focus'); 10 | }); 11 | 12 | const $focusElement = $(document.activeElement); 13 | if ($focusElement.prop('tagName') === 'INPUT' && 14 | $focusElement.parent().is('.textbox.material') 15 | ) { 16 | $focusElement.focusin(); 17 | } 18 | }); 19 | 20 | export default textboxPage; 21 | -------------------------------------------------------------------------------- /vj4/ui/constant/model.js: -------------------------------------------------------------------------------- 1 | import attachObjectMeta from './util/objectMeta'; 2 | 3 | export const USER_GENDER_MALE = 0; 4 | export const USER_GENDER_FEMALE = 1; 5 | export const USER_GENDER_OTHER = 2; 6 | export const USER_GENDERS = [USER_GENDER_MALE, USER_GENDER_FEMALE, USER_GENDER_OTHER]; 7 | export const USER_GENDER_RANGE = { 8 | [USER_GENDER_MALE]: 'Boy ♂', 9 | [USER_GENDER_FEMALE]: 'Girl ♀', 10 | [USER_GENDER_OTHER]: 'Other', 11 | }; 12 | attachObjectMeta(USER_GENDER_RANGE, 'intKey', true); 13 | export const USER_GENDER_ICONS = { 14 | [USER_GENDER_MALE]: '♂', 15 | [USER_GENDER_FEMALE]: '♀', 16 | [USER_GENDER_OTHER]: '?', 17 | }; 18 | attachObjectMeta(USER_GENDER_ICONS, 'intKey', true); 19 | -------------------------------------------------------------------------------- /vj4/ui/pages/problem_settings.page.js: -------------------------------------------------------------------------------- 1 | import { NamedPage } from 'vj/misc/PageLoader'; 2 | 3 | async function handleCategoryClick(ev) { 4 | const $target = $(ev.currentTarget); 5 | const $txt = $('[name="category"]'); 6 | console.log($target); 7 | console.log($txt); 8 | $txt.val(`${$txt.val()}, ${$target.data('category')}`); 9 | } 10 | 11 | const page = new NamedPage('problem_settings', async () => { 12 | $(document).on('click', '.category-a', handleCategoryClick); 13 | $(document).on('click', '[name="problem-sidebar__show-category"]', (ev) => { 14 | $(ev.currentTarget).hide(); 15 | $('[name="problem-sidebar__categories"]').show(); 16 | }); 17 | }); 18 | 19 | export default page; 20 | -------------------------------------------------------------------------------- /vj4/ui/templates/partials/path.html: -------------------------------------------------------------------------------- 1 |
2 | {% if not no_path_section %} 3 |
4 |
5 |
6 |
7 | {% for c in path_components %} 8 | / {% if c[1] %}{{ c[0] }}{% endif %} 9 | {%- endfor %} 10 |
11 |

{{ path_components[-1][0] }}

12 |
13 |
14 | {% include "partials/hamburger.html" %} 15 |
16 |
17 |
18 | {% endif %} 19 |
20 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/platform--android.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/templates/discussion_nodes_widget.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ _('Discussion Nodes') }}

4 |
5 |
    6 | {% for category, nodes in discussion_nodes.items() %} 7 |
  • 8 |

    {{ category }}

    9 |
      10 | {% for node in nodes %} 11 |
    1. {{ node['name'] }}
    2. 12 | {% endfor %} 13 |
    14 |
  • 15 | {% endfor %} 16 |
17 |
18 | -------------------------------------------------------------------------------- /vj4/ui/templates/error.html: -------------------------------------------------------------------------------- 1 | {% set no_path_section = true %} 2 | {% extends "layout/basic.html" %} 3 | {% block content %} 4 |
5 |
6 |
7 |
8 |
9 |

{{ _('Oops!') }}

10 |

{{ _(error.message).format(*error.args) }}

11 |

{{ _('Technical Information') }}:

12 |

{{ _('Type') }}: {{ error.__class__.__name__ }}

13 |

{{ _('Arguments') }}: 14 |

    15 | {% for arg in error.args %} 16 |
  1. {{ arg }}
  2. 17 | {% endfor %} 18 |
19 |

20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /vj4/ui/components/record/record.page.styl: -------------------------------------------------------------------------------- 1 | .record-status--text 2 | for key, value in $record-status-color 3 | &.{key} 4 | color: value !important 5 | 6 | .record-status--icon 7 | display: inline-block 8 | width: 1.15em 9 | for key, value in $record-status-icon 10 | &.{key}:before 11 | content: value 12 | color: $record-status-color[key] 13 | 14 | .record-status--border 15 | border-left: rem(3px) solid transparent 16 | for key, value in $record-status-color 17 | &.{key} 18 | border-left: rem(3px) solid lighten(value, 10%) 19 | 20 | .record-status--background 21 | color: #FFF 22 | 23 | for key, value in $record-status-color 24 | &.{key} 25 | background: value 26 | 27 | -------------------------------------------------------------------------------- /vj4/ui/pages/home_security.page.styl: -------------------------------------------------------------------------------- 1 | .page--home_security 2 | 3 | .sessionlist 4 | font-size: rem($font-size-secondary) 5 | 6 | .sessionlist__icon 7 | font-size: rem(30px) 8 | color: #888 9 | margin: rem(0 20px) 10 | 11 | .sessionlist__item 12 | margin: rem(10px 0) 13 | padding: rem(10px 0) 14 | border-bottom: 1px solid #DDD 15 | 16 | input 17 | margin: 0 18 | 19 | .sessionlist__current-session 20 | line-height: rem($form-control-height) 21 | color: green 22 | 23 | +mobile() 24 | .sessionlist__item 25 | .media__left 26 | display: none 27 | 28 | .media__body, .media__right 29 | display: block 30 | padding: 0 31 | margin: 0 32 | -------------------------------------------------------------------------------- /vj4/handler/misc.py: -------------------------------------------------------------------------------- 1 | from vj4 import app 2 | from vj4.handler import base 3 | from vj4.util import misc 4 | 5 | 6 | @app.route('/about', 'about', global_route=True) 7 | class AboutHandler(base.Handler): 8 | async def get(self): 9 | self.render('about.html') 10 | 11 | 12 | @app.route('/wiki/help', 'wiki_help', global_route=True) 13 | class AboutHandler(base.Handler): 14 | async def get(self): 15 | self.render('wiki_help.html') 16 | 17 | 18 | @app.route('/preview', 'preview', global_route=True) 19 | class PreviewHandler(base.Handler): 20 | @base.post_argument 21 | @base.sanitize 22 | async def post(self, *, text: str): 23 | self.response.content_type = 'text/html' 24 | self.response.text = misc.markdown(text) 25 | -------------------------------------------------------------------------------- /vj4/ui/components/training/training.page.styl: -------------------------------------------------------------------------------- 1 | .training-status--icon 2 | display: inline-block 3 | width: 1.15em 4 | for key, value in $training-status-icon 5 | &.{key}:before 6 | content: value 7 | color: $training-status-color[key] 8 | 9 | .training-status--text 10 | for key, value in $training-status-color 11 | &.{key} 12 | color: value 13 | 14 | .training-section-status--icon 15 | display: inline-block 16 | width: 1.15em 17 | for key, value in $training-section-status-icon 18 | &.{key}:before 19 | content: value 20 | color: $training-status-color[key] 21 | 22 | .training-section-status--text 23 | for key, value in $training-section-status-color 24 | &.{key} 25 | color: value 26 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/platform--mac.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vj4/ui/components/cmeditor/cmeditor.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | import delay from 'vj/utils/delay'; 3 | import CmEditor from '.'; 4 | 5 | import 'vj-simplemde/src/css/simplemde.css'; 6 | import './cmeditor.styl'; 7 | 8 | function runSubstitute($container) { 9 | const selector = ['textarea[data-markdown]']; 10 | $container.find(selector.join(', ')).get().forEach((element) => { 11 | CmEditor.getOrConstruct($(element)); 12 | }); 13 | } 14 | 15 | const cmEditorPage = new AutoloadPage('cmEditorPage', () => { 16 | runSubstitute($('body')); 17 | $(document).on('vjContentNew', async (e) => { 18 | await delay(0); 19 | runSubstitute($(e.target)); 20 | }); 21 | }); 22 | 23 | export default cmEditorPage; 24 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | cb4: 4 | restart: always 5 | image: jioj/cb4 6 | container_name: cb4 7 | depends_on: 8 | - mongo 9 | - rabbitmq 10 | ports: 11 | - "34765:34765" 12 | networks: 13 | - default 14 | environment: 15 | DB_HOST: mongo 16 | MQ_HOST: rabbitmq 17 | HOST: cb4 18 | env_file: .env 19 | mongo: 20 | restart: always 21 | image: mvertes/alpine-mongo 22 | container_name: mongo 23 | volumes: 24 | - ~/mongo_data:/data/db 25 | networks: 26 | - default 27 | rabbitmq: 28 | restart: always 29 | image: rabbitmq:alpine 30 | container_name: rabbitmq 31 | networks: 32 | - default 33 | networks: 34 | default: 35 | -------------------------------------------------------------------------------- /vj4/service/queue.py: -------------------------------------------------------------------------------- 1 | import bson 2 | 3 | from vj4 import mq 4 | from vj4.util import options 5 | 6 | options.define('queue_prefetch', default=1, help='Queue prefetch count.') 7 | 8 | 9 | async def publish(key, **kwargs): 10 | channel = await mq.channel('queue') 11 | await channel.queue_declare(key) 12 | await channel.basic_publish(bson.BSON.encode(kwargs), '', key) 13 | 14 | 15 | async def consume(key, on_message): 16 | channel = await mq.channel() 17 | await channel.queue_declare(key) 18 | await channel.basic_qos(prefetch_count=options.queue_prefetch) 19 | await channel.basic_consume((lambda channel, body, envelope, properties: 20 | on_message(envelope.delivery_tag, **bson.BSON.decode(body))), key) 21 | return channel 22 | -------------------------------------------------------------------------------- /vj4/ui/templates/partials/header.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {% if domain_id == vj4.model.builtin.DOMAIN_ID_SYSTEM %} 4 | 5 | {% else %} 6 | 7 | 8 | {% endif %} 9 |
10 | {% include "partials/path.html" %} 11 |
12 | -------------------------------------------------------------------------------- /vj4/ui/templates/domain_manage_edit.html: -------------------------------------------------------------------------------- 1 | {% extends "domain_base.html" %} 2 | {% block domain_content %} 3 |
4 |
5 |
6 | {% with ddoc=handler.domain %} 7 | {% include "partials/domain_edit_form.html" %} 8 | {% endwith %} 9 |
10 | 11 | 14 | 17 |
18 |
19 |
20 |
21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /vj4/ui/templates/home_messages.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/home_base.html" %} 2 | {% block home_content %} 3 | 18 |
19 |
20 | {{ noscript_note.render() }} 21 |
22 |
23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /vj4/ui/templates/domain_manage_discussion.html: -------------------------------------------------------------------------------- 1 | {% extends "domain_base.html" %} 2 | {% block domain_content %} 3 |
4 |
5 |

{{ _('Initialize') }}

6 |
7 |
8 |
9 | 10 | 13 |
14 |
15 |
16 |
17 |
18 | 19 |
20 |
21 | {% include 'discussion_nodes_widget.html' %} 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /vj4/ui/templates/components/home.html: -------------------------------------------------------------------------------- 1 | {% import "components/sidemenu.html" as sidemenu with context %} 2 | {% macro render_sidebar() %} 3 |
4 | 15 |
16 | {% endmacro %} 17 | -------------------------------------------------------------------------------- /vj4/ui/pages/record_detail.page.js: -------------------------------------------------------------------------------- 1 | import { NamedPage } from 'vj/misc/PageLoader'; 2 | 3 | const page = new NamedPage('record_detail', async () => { 4 | const SockJs = await System.import('sockjs-client'); 5 | const DiffDOM = await System.import('diff-dom'); 6 | 7 | const sock = new SockJs(Context.socketUrl); 8 | const dd = new DiffDOM(); 9 | 10 | sock.onmessage = (message) => { 11 | const msg = JSON.parse(message.data); 12 | const newStatus = $(msg.status_html); 13 | const oldStatus = $('#status'); 14 | dd.apply(oldStatus[0], dd.diff(oldStatus[0], newStatus[0])); 15 | const newSummary = $(msg.summary_html); 16 | const oldSummary = $('#summary'); 17 | dd.apply(oldSummary[0], dd.diff(oldSummary[0], newSummary[0])); 18 | }; 19 | }); 20 | 21 | export default page; 22 | -------------------------------------------------------------------------------- /vj4/ui/templates/partials/domain_edit_form.html: -------------------------------------------------------------------------------- 1 | {% import "components/form.html" as form with context %} 2 | {% if page_name == 'domain_manage_edit' %} 3 | {{ form.form_text(label='ID', name='id', value=ddoc['_id'], disabled=true) }} 4 | {% elif page_name == 'home_domain_create' %} 5 | {{ form.form_text(label='ID', name='id', autofocus=true, required=true) }} 6 | {% endif %} 7 | {{ form.form_text(label='Name', name='name', value=ddoc['name']|default(''), autofocus=(page_name == 'domain_manage_edit'), required=true) }} 8 | {{ form.form_text(label='Gravatar Email', help_text='Will be used as the domain icon.', name='gravatar', value=ddoc['gravatar']|default('')) }} 9 | {{ form.form_textarea(columns=12, label='Bulletin', name='bulletin', value=ddoc['bulletin']|default(''), markdown=true, required=false) }} 10 | -------------------------------------------------------------------------------- /vj4/ui/templates/user_lostpass.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/immersive.html" %} 2 | {% block content %} 3 |
4 |
5 |

{{ _('Lost Password') }}

6 |
7 |
8 | 12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /vj4/ui/templates/user_register.html: -------------------------------------------------------------------------------- 1 | {% extends "layout/immersive.html" %} 2 | {% block content %} 3 |
4 |
5 |

{{ _('Sign Up') }}

6 |
7 |
8 | 12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /vj4/util/domainjob.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from vj4.model import builtin 4 | from vj4.model import domain 5 | from vj4.util import argmethod 6 | 7 | 8 | _logger = logging.getLogger(__name__) 9 | 10 | 11 | def wrap(method): 12 | async def run(): 13 | _logger.info('Built in domains') 14 | for ddoc in builtin.DOMAINS: 15 | _logger.info('Domain: {0}'.format(ddoc['_id'])) 16 | await method(ddoc['_id']) 17 | _logger.info('User domains') 18 | ddocs = domain.get_multi(fields={'_id': 1}) 19 | async for ddoc in ddocs: 20 | _logger.info('Domain: {0}'.format(ddoc['_id'])) 21 | await method(ddoc['_id']) 22 | 23 | if method.__module__ == '__main__': 24 | argmethod._methods[method.__name__] = method 25 | argmethod._methods[method.__name__ + '_all'] = run 26 | return method 27 | -------------------------------------------------------------------------------- /vj4/ui/components/messagepad/DialogueListItem.page.styl: -------------------------------------------------------------------------------- 1 | $stripe-width = 2px 2 | 3 | .messagepad__list-item 4 | display: block 5 | padding: rem(20px $section-gap-h 20px ($section-gap-h - $stripe-width)) 6 | border-left: $stripe-width solid transparent 7 | cursor: pointer 8 | transition: border-color .1s linear, background-color .1s linear 9 | overflow: hidden 10 | text-overflow: ellipsis 11 | 12 | &:hover 13 | border-color: #DDD 14 | background: #F4F4F4 15 | text-decoration: none 16 | 17 | &.active 18 | border-color: $secondary-color 19 | background: rgba($secondary-color, 0.07) 20 | 21 | .messagepad__desc 22 | font-size: rem($font-size-small) 23 | color: #888 24 | max-height: 2em 25 | margin-top: rem(5px) 26 | overflow: hidden 27 | text-overflow: ellipsis 28 | word-wrap() 29 | -------------------------------------------------------------------------------- /vj4/ui/components/react-splitpane/splitpane.page.styl: -------------------------------------------------------------------------------- 1 | $resizer-extend = 4px 2 | $resizer-extend-color = rgba($primary-color, 0.3) 3 | 4 | .Resizer 5 | background: #000 6 | opacity: .2 7 | z-index: 1 8 | box-sizing: border-box 9 | background-clip: padding-box 10 | 11 | .Resizer.horizontal 12 | height: ($resizer-extend * 2 + 1px) 13 | margin: -($resizer-extend) 0 14 | border-top: $resizer-extend solid transparent 15 | border-bottom: $resizer-extend solid transparent 16 | cursor: row-resize 17 | width: 100% 18 | 19 | .Resizer.vertical 20 | width: ($resizer-extend * 2 + 1px) 21 | margin: 0 -($resizer-extend) 22 | border-left: $resizer-extend solid transparent 23 | border-right: $resizer-extend solid transparent 24 | cursor: col-resize 25 | 26 | .Resizer:hover 27 | border-color: $resizer-extend-color 28 | -------------------------------------------------------------------------------- /vj4/ui/components/scratchpad/PanelComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import SplitPaneFillOverlay from 'vj/components/react-splitpane/SplitPaneFillOverlayComponent'; 4 | 5 | export default function PanelComponent(props) { 6 | const { 7 | title, 8 | className, 9 | children, 10 | ...rest 11 | } = props; 12 | const cn = classNames(className, 'flex-col'); 13 | return ( 14 | 15 |
{title}
16 |
{children}
17 |
18 | ); 19 | } 20 | 21 | PanelComponent.propTypes = { 22 | title: React.PropTypes.node, 23 | className: React.PropTypes.string, 24 | children: React.PropTypes.node, 25 | }; 26 | -------------------------------------------------------------------------------- /vj4/ui/templates/domain_manage_dashboard.html: -------------------------------------------------------------------------------- 1 | {% import "components/user.html" as user %} 2 | {% extends "domain_base.html" %} 3 | {% block domain_content %} 4 | {% if handler.domain['bulletin'] %} 5 |
6 |
7 | {{ handler.domain['bulletin']|markdown }} 8 |
9 |
10 | {% endif %} 11 |
12 |
13 |

{{ _('Information') }}

14 |
15 |
16 |
17 |
{{ _('Gravatar') }}
18 |
{{ _('Name') }}
{{ handler.domain['name'] }}
19 |
20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /vj4/ui/templates/record_detail_summary.html: -------------------------------------------------------------------------------- 1 |
2 | {% if rdoc['score'] is defined %} 3 |
{{ _('Score') }}
4 |
{{ rdoc['score'] }}
5 | {% endif %} 6 |
{{ _('Total Time') }}
7 |
{% if rdoc['status'] == vj4.constant.record.STATUS_TIME_LIMIT_EXCEEDED or rdoc['status'] == vj4.constant.record.STATUS_MEMORY_LIMIT_EXCEEDED or rdoc['status'] == vj4.constant.record.STATUS_OUTPUT_LIMIT_EXCEEDED %}≥{% endif %}{{ rdoc['time_ms'] }}ms
8 |
{{ _('Peak Memory') }}
9 |
{% if rdoc['status'] == vj4.constant.record.STATUS_TIME_LIMIT_EXCEEDED or rdoc['status'] == vj4.constant.record.STATUS_MEMORY_LIMIT_EXCEEDED or rdoc['status'] == vj4.constant.record.STATUS_OUTPUT_LIMIT_EXCEEDED %}≥{% endif %}{{ rdoc['memory_kb']|format_size(1024) }}
10 |
11 | -------------------------------------------------------------------------------- /vj4/ui/components/contest/contest.page.styl: -------------------------------------------------------------------------------- 1 | .contest-type-tag 2 | .contest-tag 3 | font-size: rem($font-size-small) 4 | display: inline-block 5 | line-height: rem(22px) 6 | padding: rem(0 10px) 7 | vertical-align: middle 8 | border-radius: rem(12px) 9 | 10 | &:hover 11 | text-decoration: none 12 | color: #FFF !important 13 | 14 | .contest-type-tag 15 | &, &:visited 16 | &, .supplementary & 17 | color: #FFF 18 | 19 | .icon 20 | margin-right: rem(5px) 21 | 22 | for key, value in $contest-color 23 | .contest-type--{key} .contest-type-tag 24 | background: value 25 | 26 | &:hover 27 | background: lighten(value, 20%) 28 | 29 | .contest-tag 30 | border: 1px solid $supplementary-border-color 31 | 32 | &:hover 33 | background: $primary-color 34 | border-color: $primary-color 35 | -------------------------------------------------------------------------------- /scripts/fix_abroad_shrinkwrap.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var tree = JSON.parse(fs.readFileSync('./package-lock.json').toString()); 3 | 4 | function fixTree(subtree) { 5 | if (subtree.dependencies) { 6 | for (var key in subtree.dependencies) { 7 | var resolved = subtree.dependencies[key].resolved; 8 | if (typeof resolved === 'string') { 9 | resolved = resolved.replace( 10 | /^https?:\/\/registry.npm.taobao.org\//, 11 | 'https://registry.npmjs.org/' 12 | ); 13 | subtree.dependencies[key].resolved = resolved; 14 | } 15 | if (subtree.dependencies[key].dependencies !== undefined) { 16 | fixTree(subtree.dependencies[key]); 17 | } 18 | } 19 | } 20 | } 21 | 22 | fixTree(tree); 23 | 24 | fs.writeFileSync('./package-lock.json', JSON.stringify(tree, null, 2)); 25 | -------------------------------------------------------------------------------- /vj4/ui/misc/icons/template/webicon.styl: -------------------------------------------------------------------------------- 1 | @font-face 2 | font-family: $icon-font-name 3 | src: url('./{{ options.fontName }}.eot') 4 | src: url('./{{ options.fontName }}.eot#iefix') format('embedded-opentype'), 5 | url('./{{ options.fontName }}.woff2') format('woff2'), 6 | url('./{{ options.fontName }}.woff') format('woff'), 7 | url('./{{ options.fontName }}.ttf') format('truetype') 8 | font-weight: normal 9 | font-style: normal 10 | 11 | .icon 12 | font-family: $icon-font-name !important 13 | speak: none 14 | font-style: normal 15 | font-weight: normal 16 | font-variant: normal 17 | text-transform: none 18 | line-height: 1 19 | -webkit-font-smoothing: antialiased 20 | -moz-osx-font-smoothing: grayscale 21 | 22 | {% for icon in glyphs %} 23 | .icon-{{ icon.name }}:before 24 | content: $icon-{{ icon.name }} 25 | {% endfor %} 26 | -------------------------------------------------------------------------------- /vj4/ui/components/datepicker/datepicker.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | 3 | import 'pickadate/lib/themes/classic.css'; 4 | import 'pickadate/lib/themes/classic.date.css'; 5 | import 'pickadate/lib/themes/classic.time.css'; 6 | import './datepicker.styl'; 7 | 8 | const datepickerPage = new AutoloadPage('datepickerPage', async () => { 9 | if ($('[data-pick-date]').length > 0) { 10 | await System.import('pickadate/lib/picker.date'); 11 | $('[data-pick-date]').pickadate({ 12 | format: 'yyyy-m-d', 13 | clear: false, 14 | }); 15 | } 16 | if ($('[data-pick-time]').length > 0) { 17 | await System.import('pickadate/lib/picker.time'); 18 | $('[data-pick-time]').pickatime({ 19 | format: 'H:i', 20 | interval: 15, 21 | clear: false, 22 | }); 23 | } 24 | }); 25 | 26 | export default datepickerPage; 27 | -------------------------------------------------------------------------------- /vj4/ui/components/cmeditor/textareaHandler.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import DOMAttachedObject from 'vj/components/DOMAttachedObject'; 3 | import CmEditor from '.'; 4 | 5 | export default class TextareaHandler extends DOMAttachedObject { 6 | static DOMAttachKey = 'vjTextareaHandlerInstance'; 7 | 8 | getCmEditor() { 9 | return CmEditor.get(this.$dom); 10 | } 11 | 12 | isCmEditor() { 13 | const editor = this.getCmEditor(); 14 | return (editor !== undefined && editor.isValid()); 15 | } 16 | 17 | val(...argv) { 18 | if (this.isCmEditor()) { 19 | return this.getCmEditor().value(...argv); 20 | } 21 | return this.$dom.val(...argv); 22 | } 23 | 24 | focus() { 25 | if (this.isCmEditor()) { 26 | this.getCmEditor().focus(); 27 | } 28 | this.$dom.focus(); 29 | } 30 | } 31 | 32 | _.assign(TextareaHandler, DOMAttachedObject); 33 | -------------------------------------------------------------------------------- /vj4/ui/templates/components/record.html: -------------------------------------------------------------------------------- 1 | {% macro render_status_td(rdoc, rid_key='_id') %} 2 | 3 |
4 | 5 | 6 | {{ vj4.constant.record.STATUS_TEXTS[rdoc['status']] }} 7 | 8 |
9 | {% if rdoc['status'] == vj4.constant.record.STATUS_JUDGING %} 10 |
11 |
12 |
13 | {% endif %} 14 | 15 | {% endmacro %} 16 | -------------------------------------------------------------------------------- /vj4/ui/pages/training_detail.page.styl: -------------------------------------------------------------------------------- 1 | .page--training_detail 2 | .col--status 3 | width: rem(150px) 4 | position: relative 5 | 6 | .col--submit_n, .col--ac_rate, .col--difficulty 7 | width: rem(70px) 8 | text-align: center 9 | 10 | .col--status 11 | border-right: 1px solid $table-border-color 12 | 13 | .col--submit_n, .col--difficulty 14 | border-left: 1px solid $table-border-color 15 | 16 | +mobile() 17 | .col--status, 18 | .col--submit_n, 19 | .col--difficulty 20 | display: none 21 | 22 | .col--ac_rate 23 | border-left: 1px solid $table-border-color 24 | 25 | .training__section 26 | &.invalid 27 | .training__problems 28 | opacity: 0.6 29 | 30 | &.collapsed .collapsed--hidden, 31 | &.expanded .expanded--hidden 32 | display: none 33 | 34 | &.collapsed .training__section__detail 35 | display: none 36 | -------------------------------------------------------------------------------- /sources.list: -------------------------------------------------------------------------------- 1 | deb http://mirrors.163.com/ubuntu/ bionic main restricted universe multiverse 2 | deb http://mirrors.163.com/ubuntu/ bionic-security main restricted universe multiverse 3 | deb http://mirrors.163.com/ubuntu/ bionic-updates main restricted universe multiverse 4 | deb http://mirrors.163.com/ubuntu/ bionic-proposed main restricted universe multiverse 5 | deb http://mirrors.163.com/ubuntu/ bionic-backports main restricted universe multiverse 6 | deb-src http://mirrors.163.com/ubuntu/ bionic main restricted universe multiverse 7 | deb-src http://mirrors.163.com/ubuntu/ bionic-security main restricted universe multiverse 8 | deb-src http://mirrors.163.com/ubuntu/ bionic-updates main restricted universe multiverse 9 | deb-src http://mirrors.163.com/ubuntu/ bionic-proposed main restricted universe multiverse 10 | deb-src http://mirrors.163.com/ubuntu/ bionic-backports main restricted universe multiverse 11 | 12 | -------------------------------------------------------------------------------- /vj4/ui/templates/partials/header_mobile.html: -------------------------------------------------------------------------------- 1 | {% if no_path_section %} 2 |
3 |
4 |
5 | {% if domain_id == vj4.model.builtin.DOMAIN_ID_SYSTEM %} 6 |   7 | {% else %} 8 | {{ domain['name'] }} 9 | Powered by JOJ 10 | {% endif %} 11 |
12 |
13 | {% include "partials/hamburger.html" %} 14 |
15 |
16 |
17 | {% endif %} 18 | -------------------------------------------------------------------------------- /vj4/ui/pages/domain_manage_join_applications.page.js: -------------------------------------------------------------------------------- 1 | import { NamedPage } from 'vj/misc/PageLoader'; 2 | 3 | import * as domainEnum from 'vj/constant/domain'; 4 | 5 | const page = new NamedPage('domain_manage_join_applications', () => { 6 | const $role = $('[name="role"]'); 7 | const $expire = $('[name="expire"]'); 8 | const $code = $('[name="invitation_code"]'); 9 | function updateFormState() { 10 | const method = parseInt($('[name="method"]:checked').val(), 10); 11 | $role.prop('disabled', method === domainEnum.JOIN_METHOD_NONE).trigger('vjFormDisableUpdate'); 12 | $expire.prop('disabled', method === domainEnum.JOIN_METHOD_NONE).trigger('vjFormDisableUpdate'); 13 | $code.prop('disabled', method !== domainEnum.JOIN_METHOD_CODE).trigger('vjFormDisableUpdate'); 14 | } 15 | updateFormState(); 16 | $('[name="method"]').change(() => updateFormState()); 17 | }); 18 | 19 | export default page; 20 | -------------------------------------------------------------------------------- /vj4/ui/components/scratchpad/DataInputComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | export default function DataInputComponent(props) { 5 | const { 6 | title, 7 | value, 8 | onChange, 9 | className, 10 | ...rest 11 | } = props; 12 | const cn = classNames(className, 'flex-col flex-fill'); 13 | return ( 14 |
15 |