├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── fe.yml │ └── test.yml ├── .github_banner.png ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── h2o-site.conf ├── haproxy-site.cfg ├── nginx-site.conf └── vj4.service ├── package.json ├── pylintrc ├── requirements.txt ├── scripts ├── build │ ├── config │ │ ├── gulp.js │ │ └── webpack.js │ ├── index.js │ ├── main.js │ ├── plugins │ │ ├── gulpGenerateConstants.js │ │ ├── gulpGenerateLocales.js │ │ ├── gulpTouch.js │ │ └── webpackStaticManifestPlugin.js │ ├── runGulp.js │ ├── runWebpack.js │ └── utils │ │ ├── mapWebpackUrlPrefix.js │ │ ├── root.js │ │ └── transformConstant.js ├── start_server_dev.sh └── start_server_production.sh ├── setup.py ├── vj4 ├── __init__.py ├── app.py ├── db.py ├── error.py ├── handler │ ├── __init__.py │ ├── base.py │ ├── contest.py │ ├── discussion.py │ ├── domain.py │ ├── error.py │ ├── fs.py │ ├── home.py │ ├── homework.py │ ├── i18n.py │ ├── judge.py │ ├── misc.py │ ├── problem.py │ ├── ranking.py │ ├── record.py │ ├── training.py │ └── user.py ├── job │ ├── __init__.py │ ├── blacklist.py │ ├── difficulty.py │ ├── fs.py │ ├── num.py │ ├── rank.py │ ├── record.py │ └── rp.py ├── locale │ ├── en.yaml │ ├── ko.yaml │ ├── zh_CN.yaml │ └── zh_TW.yaml ├── model │ ├── __init__.py │ ├── adaptor │ │ ├── __init__.py │ │ ├── contest.py │ │ ├── defaults.py │ │ ├── discussion.py │ │ ├── problem.py │ │ ├── setting.py │ │ ├── training.py │ │ └── userfile.py │ ├── blacklist.py │ ├── builtin.py │ ├── document.py │ ├── domain.py │ ├── fs.py │ ├── message.py │ ├── opcount.py │ ├── oplog.py │ ├── record.py │ ├── system.py │ ├── token.py │ └── user.py ├── mq.py ├── pipeline │ ├── __init__.py │ └── problem_stat.py ├── server.py ├── service │ ├── __init__.py │ ├── bus.py │ ├── event.py │ ├── mailer.py │ ├── queue.py │ ├── smallcache.py │ └── staticmanifest.py ├── template.py ├── test │ ├── __init__.py │ ├── base.py │ ├── test_contest.py │ ├── test_discussion.py │ ├── test_job.py │ ├── test_misc.py │ ├── test_model.py │ ├── test_problem.py │ ├── test_pwhash.py │ ├── test_smallcache.py │ ├── test_validator.py │ └── test_view.py ├── ui │ ├── .eslintrc.js │ ├── Entry.js │ ├── README.md │ ├── breakpoints.json │ ├── common │ │ ├── color.inc.styl │ │ ├── common.inc.styl │ │ ├── easing.inc.styl │ │ ├── functions.inc.styl │ │ ├── rem.inc.styl │ │ └── variables.inc.styl │ ├── components │ │ ├── DOMAttachedObject.js │ │ ├── autocomplete │ │ │ ├── DomainSelectAutoComplete.js │ │ │ ├── UserSelectAutoComplete.js │ │ │ ├── autocomplete.page.styl │ │ │ ├── domainselectautocomplete.page.styl │ │ │ ├── index.js │ │ │ └── userselectautocomplete.page.styl │ │ ├── calendar │ │ │ ├── calendar.page.styl │ │ │ └── index.js │ │ ├── cmeditor │ │ │ ├── cmeditor.page.js │ │ │ ├── cmeditor.styl │ │ │ ├── index.js │ │ │ ├── textareaHandler.js │ │ │ └── vjcmeditor.js │ │ ├── contest │ │ │ ├── contest.page.styl │ │ │ ├── contest_sidebar.page.styl │ │ │ ├── problem-contest-bg.png │ │ │ └── problem-contest-bg@2x.png │ │ ├── datepicker │ │ │ ├── datepicker.page.js │ │ │ └── datepicker.styl │ │ ├── dialog │ │ │ ├── DomDialog.js │ │ │ ├── dialog.page.styl │ │ │ └── index.js │ │ ├── discussion │ │ │ ├── CommentBox.js │ │ │ ├── comments.page.js │ │ │ ├── comments.page.styl │ │ │ └── discussion.page.styl │ │ ├── drop │ │ │ └── drop.page.styl │ │ ├── dropdown │ │ │ ├── Dropdown.js │ │ │ ├── dropdown.page.js │ │ │ └── dropdown.page.styl │ │ ├── emoji │ │ │ ├── emoji.page.styl │ │ │ └── emojify.page.js │ │ ├── footer │ │ │ ├── footer.page.js │ │ │ └── footer.page.styl │ │ ├── form │ │ │ ├── button.page.styl │ │ │ ├── checkbox.page.styl │ │ │ ├── form.page.js │ │ │ ├── form.page.styl │ │ │ ├── multiSelectCheckbox.page.js │ │ │ ├── radiobox.page.styl │ │ │ ├── select.page.styl │ │ │ ├── textbox.page.js │ │ │ ├── textbox.page.styl │ │ │ └── var.inc.styl │ │ ├── header │ │ │ ├── header-background.png │ │ │ ├── header-background@2x.png │ │ │ ├── header-logo-summer.png │ │ │ ├── header-logo-summer@2x.png │ │ │ ├── header-logo.png │ │ │ ├── header-logo@2x.png │ │ │ └── header.page.styl │ │ ├── highlighter │ │ │ ├── codemirror.page.styl │ │ │ ├── highlighter.page.js │ │ │ ├── highlighter.page.styl │ │ │ ├── meta.js │ │ │ └── prismjs.js │ │ ├── homework │ │ │ └── homework.page.styl │ │ ├── hotkey │ │ │ └── hotkey.page.js │ │ ├── katex │ │ │ ├── katex.page.js │ │ │ └── katex.styl │ │ ├── loader │ │ │ └── loader.page.styl │ │ ├── marker │ │ │ ├── Marker.js │ │ │ ├── MarkerReactive.js │ │ │ ├── marker.page.js │ │ │ └── marker.page.styl │ │ ├── menu │ │ │ ├── menu-heading.page.js │ │ │ ├── menu.page.js │ │ │ └── menu.page.styl │ │ ├── messagepad │ │ │ ├── DialogueListItem.page.styl │ │ │ ├── DialogueListItemComponent.js │ │ │ ├── Message.page.styl │ │ │ ├── MessageComponent.js │ │ │ ├── MessagePad.page.styl │ │ │ ├── MessagePadDialogueContentContainer.js │ │ │ ├── MessagePadDialogueListContainer.js │ │ │ ├── MessagePadInputContainer.js │ │ │ ├── index.js │ │ │ └── reducers │ │ │ │ ├── activeId.js │ │ │ │ ├── dialogues.js │ │ │ │ ├── index.js │ │ │ │ ├── inputs.js │ │ │ │ └── isPosting.js │ │ ├── navigation │ │ │ ├── hamburgers.page.styl │ │ │ ├── index.js │ │ │ ├── nav-logo-small@2x_dark.png │ │ │ ├── nav-logo-small@2x_light.png │ │ │ ├── nav-logo-small_dark.png │ │ │ ├── nav-logo-small_light.png │ │ │ ├── navigation.page.js │ │ │ └── navigation.page.styl │ │ ├── notification │ │ │ ├── index.js │ │ │ └── notification.page.styl │ │ ├── nprogress │ │ │ ├── index.js │ │ │ └── nprogress.page.styl │ │ ├── pager │ │ │ └── pager.page.styl │ │ ├── problem │ │ │ ├── rp.page.styl │ │ │ └── tag.page.styl │ │ ├── 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 │ │ │ │ ├── gen_thumbnails.sh │ │ │ │ └── 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 │ │ │ └── profile.page.styl │ │ ├── react-splitpane │ │ │ ├── SplitPaneFillOverlay.page.styl │ │ │ ├── SplitPaneFillOverlayComponent.js │ │ │ └── splitpane.page.styl │ │ ├── react-tabs │ │ │ └── rc-tabs.page.js │ │ ├── react │ │ │ ├── DomComponent.js │ │ │ └── IconComponent.js │ │ ├── record │ │ │ └── record.page.styl │ │ ├── rotator │ │ │ ├── index.js │ │ │ └── rotator.page.styl │ │ ├── scratchpad │ │ │ ├── DataInput.page.styl │ │ │ ├── DataInputComponent.js │ │ │ ├── Editor.page.styl │ │ │ ├── Panel.page.styl │ │ │ ├── PanelButton.page.styl │ │ │ ├── PanelButtonComponent.js │ │ │ ├── PanelComponent.js │ │ │ ├── PanelTab.page.styl │ │ │ ├── ScratchpadEditorContainer.js │ │ │ ├── ScratchpadPretestContainer.js │ │ │ ├── ScratchpadPretestTabPaneContainer.js │ │ │ ├── ScratchpadRecordsContainer.js │ │ │ ├── ScratchpadRecordsRowContainer.js │ │ │ ├── ScratchpadRecordsTableContainer.js │ │ │ ├── ScratchpadToolbarContainer.js │ │ │ ├── Toolbar.page.styl │ │ │ ├── ToolbarComponent.js │ │ │ ├── index.js │ │ │ ├── reducers │ │ │ │ ├── editor.js │ │ │ │ ├── index.js │ │ │ │ ├── pretest.js │ │ │ │ ├── records.js │ │ │ │ └── ui.js │ │ │ ├── scratchpad.page.styl │ │ │ └── var.inc.styl │ │ ├── signin │ │ │ ├── signInDialog.page.js │ │ │ └── signin_dialog.page.styl │ │ ├── smoothscroll │ │ │ └── smoothscroll.page.js │ │ ├── star │ │ │ ├── star.page.js │ │ │ └── star.page.styl │ │ ├── sticky │ │ │ └── sticky.page.js │ │ ├── tab │ │ │ ├── Tab.js │ │ │ ├── tab.page.js │ │ │ ├── tab.page.styl │ │ │ └── var.inc.styl │ │ ├── table │ │ │ ├── StyledTable.js │ │ │ ├── styledTable.page.js │ │ │ └── table.page.styl │ │ ├── time │ │ │ └── time.page.js │ │ ├── tooltip │ │ │ ├── Tooltip.js │ │ │ ├── tooltip.page.js │ │ │ └── tooltip.page.styl │ │ ├── training │ │ │ └── training.page.styl │ │ └── vote │ │ │ └── vote.page.js │ ├── constant │ │ ├── contest.js │ │ ├── domain.js │ │ ├── language.js │ │ ├── model.js │ │ ├── record.js │ │ ├── setting.js │ │ └── util │ │ │ └── objectMeta.js │ ├── jsconfig.json │ ├── misc │ │ ├── PageLoader.js │ │ ├── float.styl │ │ ├── grid.styl │ │ ├── icons │ │ │ ├── account--circle.svg │ │ │ ├── add.svg │ │ │ ├── award.svg │ │ │ ├── block.svg │ │ │ ├── bold.svg │ │ │ ├── calendar.svg │ │ │ ├── check--circle.svg │ │ │ ├── check.svg │ │ │ ├── chevron_left.svg │ │ │ ├── chevron_right.svg │ │ │ ├── close--circle.svg │ │ │ ├── close.svg │ │ │ ├── code.svg │ │ │ ├── comment--multiple.svg │ │ │ ├── comment--text.svg │ │ │ ├── copy.svg │ │ │ ├── crown.svg │ │ │ ├── debug.svg │ │ │ ├── delete.svg │ │ │ ├── download.svg │ │ │ ├── edit.svg │ │ │ ├── enlarge.svg │ │ │ ├── erase.svg │ │ │ ├── expand_less.svg │ │ │ ├── expand_more.svg │ │ │ ├── facebook.svg │ │ │ ├── feeling-lucky.svg │ │ │ ├── file.svg │ │ │ ├── filter.svg │ │ │ ├── flag.svg │ │ │ ├── formula.svg │ │ │ ├── github.svg │ │ │ ├── global.svg │ │ │ ├── google_plus.svg │ │ │ ├── heart--outline.svg │ │ │ ├── heart.svg │ │ │ ├── help.svg │ │ │ ├── help2.svg │ │ │ ├── homework.svg │ │ │ ├── hourglass.svg │ │ │ ├── info--circle.svg │ │ │ ├── info.svg │ │ │ ├── insert--image.svg │ │ │ ├── insert--link.svg │ │ │ ├── italic.svg │ │ │ ├── lab.svg │ │ │ ├── link--external.svg │ │ │ ├── link.svg │ │ │ ├── linkedin.svg │ │ │ ├── logout.svg │ │ │ ├── mail.svg │ │ │ ├── ordered_list.svg │ │ │ ├── platform--android.svg │ │ │ ├── platform--chromeos.svg │ │ │ ├── platform--ios.svg │ │ │ ├── platform--linux.svg │ │ │ ├── platform--mac.svg │ │ │ ├── platform--mobile.svg │ │ │ ├── platform--unknown.svg │ │ │ ├── platform--windows.svg │ │ │ ├── play.svg │ │ │ ├── preview.svg │ │ │ ├── qq.svg │ │ │ ├── quote.svg │ │ │ ├── refresh.svg │ │ │ ├── reply.svg │ │ │ ├── schedule--fill.svg │ │ │ ├── schedule.svg │ │ │ ├── search.svg │ │ │ ├── security.svg │ │ │ ├── send.svg │ │ │ ├── settings.svg │ │ │ ├── shrink.svg │ │ │ ├── sliders.svg │ │ │ ├── stack.svg │ │ │ ├── star--outline.svg │ │ │ ├── star.svg │ │ │ ├── statistics.svg │ │ │ ├── tag.svg │ │ │ ├── template │ │ │ │ ├── webicon.inc.styl │ │ │ │ └── webicon.styl │ │ │ ├── twitter.svg │ │ │ ├── unordered_list.svg │ │ │ ├── upload.svg │ │ │ ├── user--multiple.svg │ │ │ ├── user.svg │ │ │ ├── vote--down.svg │ │ │ ├── vote--up.svg │ │ │ ├── warning.svg │ │ │ ├── web.svg │ │ │ ├── wechat.svg │ │ │ └── wrench.svg │ │ ├── immersive-background.jpg │ │ ├── immersive-background@2x.jpg │ │ ├── immersive.styl │ │ ├── nothing.styl │ │ ├── puzzled_twd2.svg │ │ ├── section.styl │ │ ├── slideout.styl │ │ ├── structure.styl │ │ ├── textalign.styl │ │ └── typography.styl │ ├── pages │ │ ├── contest_detail.page.styl │ │ ├── contest_main.page.js │ │ ├── contest_main.page.styl │ │ ├── contest_scoreboard.page.styl │ │ ├── discussion_detail.page.js │ │ ├── discussion_detail.page.styl │ │ ├── discussion_main.page.styl │ │ ├── discussion_node.page.js │ │ ├── discussion_node_bg │ │ │ ├── nodes@2x_advice.png │ │ │ ├── nodes@2x_qa.png │ │ │ ├── nodes@2x_share.png │ │ │ ├── nodes@2x_solution.png │ │ │ ├── nodes@2x_vijos.png │ │ │ ├── nodes_advice.png │ │ │ ├── nodes_qa.png │ │ │ ├── nodes_share.png │ │ │ ├── nodes_solution.png │ │ │ └── nodes_vijos.png │ │ ├── domain_join.page.styl │ │ ├── domain_manage_join_applications.page.js │ │ ├── domain_manage_permission.page.styl │ │ ├── domain_manage_role.page.js │ │ ├── domain_manage_role.page.styl │ │ ├── domain_manage_user.page.js │ │ ├── domain_manage_user.page.styl │ │ ├── error.page.styl │ │ ├── error │ │ │ └── twd2.svg │ │ ├── home_account.page.styl │ │ ├── home_domain.page.styl │ │ ├── home_messages.page.js │ │ ├── home_security.page.styl │ │ ├── homework_detail.page.styl │ │ ├── homework_main.page.js │ │ ├── homework_main.page.styl │ │ ├── homework_scoreboard.page.styl │ │ ├── judge_playground.page.js │ │ ├── problem_copy.page.js │ │ ├── problem_detail.page.js │ │ ├── problem_edit.page.js │ │ ├── problem_main.page.js │ │ ├── problem_main.page.styl │ │ ├── problem_settings.page.js │ │ ├── problem_solution.page.js │ │ ├── problem_submit.page.js │ │ ├── problem_submit.page.styl │ │ ├── ranking_main.page.styl │ │ ├── record_detail.page.js │ │ ├── record_detail.page.styl │ │ ├── record_main.page.js │ │ ├── record_main.page.styl │ │ ├── training_detail.page.js │ │ ├── training_detail.page.styl │ │ ├── training_main.page.styl │ │ ├── user_detail.page.js │ │ └── user_detail.page.styl │ ├── postcss.config.js │ ├── static │ │ ├── android-chrome-192x192.png │ │ ├── apple-touch-icon-180x180.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── favicon.ico │ │ ├── img │ │ │ ├── avatar.png │ │ │ └── team_avatar.png │ │ ├── manifest.json │ │ └── mstile-144x144.png │ ├── templates │ │ ├── about.html │ │ ├── ac_mail.html │ │ ├── bsod.html │ │ ├── components │ │ │ ├── comments.html │ │ │ ├── comments_base.html │ │ │ ├── comments_discussion.html │ │ │ ├── comments_solution.html │ │ │ ├── contest.html │ │ │ ├── form.html │ │ │ ├── home.html │ │ │ ├── homework.html │ │ │ ├── md_hint.html │ │ │ ├── noscript_note.html │ │ │ ├── nothing.html │ │ │ ├── paginator.html │ │ │ ├── problem.html │ │ │ ├── record.html │ │ │ ├── sidemenu.html │ │ │ └── user.html │ │ ├── contest_detail.html │ │ ├── contest_edit.html │ │ ├── contest_main.html │ │ ├── contest_scoreboard.html │ │ ├── contest_scoreboard_download_html.html │ │ ├── discussion_create.html │ │ ├── discussion_detail.html │ │ ├── discussion_edit.html │ │ ├── discussion_main_or_node.html │ │ ├── discussion_nodes_widget.html │ │ ├── domain_base.html │ │ ├── domain_join.html │ │ ├── domain_main.html │ │ ├── domain_manage_dashboard.html │ │ ├── domain_manage_discussion.html │ │ ├── domain_manage_edit.html │ │ ├── domain_manage_join_applications.html │ │ ├── domain_manage_permission.html │ │ ├── domain_manage_role.html │ │ ├── domain_manage_user.html │ │ ├── error.html │ │ ├── fs_upload.html │ │ ├── home_domain.html │ │ ├── home_domain_create.html │ │ ├── home_file.html │ │ ├── home_messages.html │ │ ├── home_security.html │ │ ├── home_settings.html │ │ ├── homework_detail.html │ │ ├── homework_edit.html │ │ ├── homework_main.html │ │ ├── judge_playground.html │ │ ├── layout │ │ │ ├── basic.html │ │ │ ├── home_base.html │ │ │ ├── html5.html │ │ │ ├── immersive.html │ │ │ ├── mail.html │ │ │ ├── simple.html │ │ │ └── wiki_base.html │ │ ├── partials │ │ │ ├── contest_sidebar.html │ │ │ ├── discussion_edit_form.html │ │ │ ├── discussion_list.html │ │ │ ├── domain_edit_form.html │ │ │ ├── footer.html │ │ │ ├── hamburger.html │ │ │ ├── header.html │ │ │ ├── header_mobile.html │ │ │ ├── homework_default_penalty_rules.yaml │ │ │ ├── homework_sidebar.html │ │ │ ├── login_dialog.html │ │ │ ├── nav.html │ │ │ ├── path.html │ │ │ ├── problem_default.md │ │ │ ├── problem_list.html │ │ │ ├── problem_lucky.html │ │ │ ├── problem_sidebar.html │ │ │ ├── problem_sidebar_contest.html │ │ │ ├── problem_sidebar_homework.html │ │ │ ├── problem_sidebar_normal.html │ │ │ ├── problem_stat.html │ │ │ └── training_default.json │ │ ├── problem_copy.html │ │ ├── problem_detail.html │ │ ├── problem_edit.html │ │ ├── problem_main.html │ │ ├── problem_settings.html │ │ ├── problem_solution.html │ │ ├── problem_statistics.html │ │ ├── problem_submit.html │ │ ├── problem_submit_tr.html │ │ ├── problem_upload.html │ │ ├── ranking_main.html │ │ ├── record_detail.html │ │ ├── record_detail_status.html │ │ ├── record_detail_summary.html │ │ ├── record_main.html │ │ ├── record_main_tr.html │ │ ├── training_detail.html │ │ ├── training_edit.html │ │ ├── training_main.html │ │ ├── user_changemail_mail.html │ │ ├── user_changemail_mail_sent.html │ │ ├── user_detail.html │ │ ├── user_login.html │ │ ├── user_logout.html │ │ ├── user_lostpass.html │ │ ├── user_lostpass_mail.html │ │ ├── user_lostpass_mail_sent.html │ │ ├── user_lostpass_with_code.html │ │ ├── user_register.html │ │ ├── user_register_mail.html │ │ ├── user_register_mail_sent.html │ │ ├── user_register_with_code.html │ │ └── wiki_help.html │ └── utils │ │ ├── delay.js │ │ ├── emulateAnchorClick.js │ │ ├── i18n.js │ │ ├── loadReactRedux.js │ │ ├── mediaQuery.js │ │ ├── mongoId.js │ │ ├── parseQueryString.js │ │ ├── pjax.js │ │ ├── request.js │ │ ├── slide.js │ │ ├── substitute.js │ │ ├── tpl.js │ │ └── zIndexManager.js ├── upgrader │ ├── __init__.py │ └── from_0_to_1.py └── util │ ├── __init__.py │ ├── argmethod.py │ ├── domainjob.py │ ├── geoip.py │ ├── json.py │ ├── locale.py │ ├── misc.py │ ├── options.py │ ├── pagination.py │ ├── pwhash.py │ ├── rank.py │ ├── tools.py │ ├── useragent.py │ ├── validator.py │ └── version.py └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | /vj4/ui/static/** -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | commonjs: true, 5 | es2020: true, 6 | node: true, 7 | }, 8 | extends: [ 9 | 'airbnb-base', 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 11, 13 | }, 14 | rules: { 15 | 'import/no-extraneous-dependencies': 'off', 16 | 'func-names': 'off', 17 | 'no-console': 'off', 18 | 'no-param-reassign': 'off', 19 | 'no-underscore-dangle': 'off', 20 | 'no-continue': 'off', 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /.github_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/.github_banner.png -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/nginx-site.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | server_name localhost; 4 | root /home/vj4/vj4/vj4/.uibuild; 5 | 6 | location / { 7 | try_files $uri @proxy_to_app; 8 | } 9 | 10 | location @proxy_to_app { 11 | proxy_pass http://unix:/home/vj4/vj4.sock; 12 | proxy_http_version 1.1; 13 | proxy_set_header Upgrade $http_upgrade; 14 | proxy_set_header Connection $connection_upgrade; 15 | proxy_set_header X-Real-IP $remote_addr; 16 | } 17 | } 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 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [FORMAT] 2 | indent-string=' ' 3 | indent-after-paren=4 4 | 5 | [MESSAGES CONTROL] 6 | disable=C0111,C0301 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp>=3.6.0,<3.8.0 2 | jinja2>=2.9.0 3 | sockjs>=0.7.0,<0.10.0 4 | misaka 5 | accept 6 | aioamqp 7 | git+https://github.com/LukeXuan/aiosmtplib # custom aiosmtlib with ssl support 8 | pytz 9 | coloredlogs 10 | httpagentparser 11 | geoip2 12 | GitPython 13 | PyYAML 14 | git+https://github.com/iceboy233/aiomongo 15 | yarl 16 | aiohttp-sentry 17 | -------------------------------------------------------------------------------- /scripts/build/index.js: -------------------------------------------------------------------------------- 1 | require('@babel/register'); 2 | require('./main.js'); 3 | -------------------------------------------------------------------------------- /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, constant } = argv; 7 | 8 | async function main() { 9 | await runGulp(argv); 10 | if (constant) return; 11 | runWebpack(argv); 12 | } 13 | 14 | process.chdir(root()); 15 | main(); 16 | -------------------------------------------------------------------------------- /scripts/build/plugins/gulpGenerateLocales.js: -------------------------------------------------------------------------------- 1 | import yaml from 'js-yaml'; 2 | 3 | import PluginError from 'plugin-error'; 4 | import through from 'through2'; 5 | import path from 'path'; 6 | 7 | export default function generateLocales() { 8 | 9 | function bufferContents(file, encoding, callback) { 10 | if (file.isNull()) { 11 | callback(); 12 | return; 13 | } 14 | if (file.isStream()) { 15 | this.emit('error', new PluginError('gulpGenerateLocales', 'Stream not supported')); 16 | callback(); 17 | return; 18 | } 19 | 20 | const doc = yaml.safeLoad(file.contents); 21 | file.contents = Buffer.from(`window.LOCALES = ${JSON.stringify(doc, null, 2)}`); 22 | file.path = path.join( 23 | path.dirname(file.path), 24 | path.basename(file.path, path.extname(file.path)) + '.js' 25 | ); 26 | 27 | this.push(file); 28 | callback(); 29 | } 30 | 31 | return through.obj(bufferContents); 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /scripts/build/plugins/gulpTouch.js: -------------------------------------------------------------------------------- 1 | import PluginError from 'plugin-error'; 2 | import through from 'through2'; 3 | import fs from 'fs-extra'; 4 | 5 | export default function touch(mtime) { 6 | 7 | async function touchFile(file) { 8 | const fd = await fs.open(file.path, 'a'); 9 | try { 10 | await fs.futimes(fd, mtime, mtime); 11 | } finally { 12 | await fs.close(fd); 13 | } 14 | } 15 | 16 | function processStream(file, encoding, callback) { 17 | if (file.isNull()) { 18 | callback(); 19 | return; 20 | } 21 | if (file.isStream()) { 22 | this.emit('error', new PluginError('gulpTouch', 'Stream not supported')); 23 | callback(); 24 | return; 25 | } 26 | touchFile(file) 27 | .catch(err => this.emit('error', err)) 28 | .then(() => { 29 | this.push(file); 30 | callback(); 31 | }); 32 | } 33 | 34 | return through.obj(processStream); 35 | 36 | }; 37 | -------------------------------------------------------------------------------- /scripts/build/runWebpack.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpack from 'webpack'; 3 | import root from './utils/root.js'; 4 | import webpackConfig from './config/webpack.js'; 5 | 6 | export default function ({ watch, production }) { 7 | 8 | const compiler = webpack(webpackConfig({ watch, production })); 9 | 10 | const outputOptions = { 11 | colors: true, 12 | errorDetails: true, 13 | optimizationBailout: production, 14 | }; 15 | 16 | function compilerCallback(err, stats) { 17 | if (err) { 18 | // config errors 19 | console.error(err.stack || err); 20 | if (err.details) console.error(err.details); 21 | process.exit(1); 22 | return; 23 | } 24 | console.log(stats.toString(outputOptions)); 25 | fs.writeFileSync(root('./.webpackStats.json'), JSON.stringify(stats.toJson(), null, 2)); 26 | if (!watch && stats.hasErrors()) { 27 | process.exitCode = 1; 28 | } 29 | } 30 | 31 | if (watch) { 32 | compiler.watch({}, compilerCallback); 33 | } else { 34 | compiler.run(compilerCallback); 35 | } 36 | 37 | }; 38 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup(name='vj4', 4 | version='4.0', 5 | author='Vijos', 6 | author_email='me@iceboy.org', 7 | description='Vijos Web Server', 8 | license='AGPL-3.0', 9 | keywords='vijos online judge web', 10 | url='https://vijos.org/', 11 | packages=[ 12 | 'vj4', 13 | 'vj4.handler', 14 | 'vj4.model', 15 | 'vj4.model.adaptor', 16 | 'vj4.pipeline', 17 | 'vj4.service', 18 | 'vj4.util', 19 | ], 20 | package_data={ 21 | 'vj4': ['locale/*.csv', 'ui/templates/*', '.uibuild/*'], 22 | }, 23 | install_requires=open('requirements.txt').readlines(), 24 | test_suite='vj4.test') 25 | -------------------------------------------------------------------------------- /vj4/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/__init__.py -------------------------------------------------------------------------------- /vj4/handler/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/handler/__init__.py -------------------------------------------------------------------------------- /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/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/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/handler/ranking.py: -------------------------------------------------------------------------------- 1 | from vj4 import app 2 | from vj4.model import builtin 3 | from vj4.model import domain 4 | from vj4.model import user 5 | from vj4.util import pagination 6 | from vj4.handler import base 7 | 8 | 9 | @app.route('/ranking', 'ranking_main') 10 | class RankingMainHandler(base.Handler): 11 | USERS_PER_PAGE = 100 12 | 13 | @base.require_priv(builtin.PRIV_USER_PROFILE) 14 | @base.require_perm(builtin.PERM_VIEW_RANKING) 15 | @base.get_argument 16 | @base.route_argument 17 | @base.sanitize 18 | async def get(self, *, page: int=1): 19 | dudocs, dupcount, _ = await pagination.paginate( 20 | domain.get_multi_user(domain_id=self.domain_id, rp={'$gt': 0.0}).sort([('rank', 1)]), 21 | page, self.USERS_PER_PAGE) 22 | udict = await user.get_dict(dudoc['uid'] for dudoc in dudocs) 23 | self.render('ranking_main.html', page=page, dupcount=dupcount, dudocs=dudocs, udict=udict) 24 | -------------------------------------------------------------------------------- /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/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/model/__init__.py -------------------------------------------------------------------------------- /vj4/model/adaptor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/model/adaptor/__init__.py -------------------------------------------------------------------------------- /vj4/model/adaptor/defaults.py: -------------------------------------------------------------------------------- 1 | DEFAULT_CODE_TEMPLATES = { 2 | 'c': r""" 3 | #include 4 | 5 | int main() 6 | { 7 | printf("hello, world\n"); 8 | } 9 | """, 10 | 'cc': r""" 11 | #include 12 | 13 | using namespace std; 14 | 15 | int main() 16 | { 17 | cout << "hello, world" << endl; 18 | } 19 | """, 20 | 'pas': r""" 21 | begin 22 | writeln('hello, world'); 23 | end. 24 | """, 25 | 'java': r""" 26 | import java.io.*; 27 | import java.util.Scanner; 28 | 29 | public class Main { 30 | public static void main(String[] args) throws IOException { 31 | Scanner sc = new Scanner(System.in); 32 | System.out.println("hello, world"); 33 | } 34 | } 35 | """, 36 | 'py': r""" 37 | print 'hello, world' 38 | """, 39 | 'py3': r""" 40 | print('hello, world') 41 | """, 42 | 'php': r""" 43 | hello, world 44 | """, 45 | 'rs': r""" 46 | fn main() { 47 | println!("hello, world!"); 48 | } 49 | """, 50 | 'hs': r""" 51 | main = putStrLn "hello, world" 52 | """, 53 | } 54 | -------------------------------------------------------------------------------- /vj4/model/blacklist.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from vj4 import db 3 | from vj4.util import argmethod 4 | 5 | 6 | @argmethod.wrap 7 | async def add(ip: str): 8 | coll = db.coll('blacklist') 9 | expire_at = datetime.datetime.utcnow() + datetime.timedelta(days=365) 10 | await coll.find_one_and_update({'_id': ip}, 11 | {'$set': {'expire_at': expire_at}}, 12 | upsert=True) 13 | 14 | 15 | @argmethod.wrap 16 | async def get(ip: str): 17 | coll = db.coll('blacklist') 18 | return await coll.find_one({'_id': ip}) 19 | 20 | 21 | @argmethod.wrap 22 | async def delete(ip: str): 23 | coll = db.coll('blacklist') 24 | await coll.delete_one({'_id': ip}) 25 | 26 | 27 | @argmethod.wrap 28 | async def ensure_indexes(): 29 | coll = db.coll('blacklist') 30 | await coll.create_index('expire_at', expireAfterSeconds=0) 31 | 32 | 33 | if __name__ == '__main__': 34 | argmethod.invoke_by_args() 35 | -------------------------------------------------------------------------------- /vj4/model/oplog.py: -------------------------------------------------------------------------------- 1 | from bson import objectid 2 | 3 | from vj4 import db 4 | from vj4.util import argmethod 5 | 6 | TYPE_DELETE_DOCUMENT = 1 7 | TYPE_DELETE_SUB_DOCUMENT = 2 8 | TYPE_REJUDGE = 3 # TODO(twd2) 9 | 10 | 11 | @argmethod.wrap 12 | async def add(uid: int, type: int, **kwargs): 13 | """Add an operation log. Returns the document id.""" 14 | obj_id = objectid.ObjectId() 15 | coll = db.coll('oplog') 16 | doc = {'_id': obj_id, 17 | 'uid': uid, 18 | 'type': type, 19 | **kwargs} 20 | await coll.insert_one(doc) 21 | return obj_id 22 | 23 | 24 | @argmethod.wrap 25 | async def ensure_indexes(): 26 | coll = db.coll('oplog') 27 | await coll.create_index('uid') 28 | # type delete document 29 | await coll.create_index([('doc.domain_id', 1), 30 | ('doc.doc_type', 1), 31 | ('doc.doc_id', 1)], sparse=True) 32 | 33 | 34 | if __name__ == '__main__': 35 | argmethod.invoke_by_args() 36 | -------------------------------------------------------------------------------- /vj4/pipeline/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/pipeline/__init__.py -------------------------------------------------------------------------------- /vj4/service/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/service/__init__.py -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /vj4/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/test/__init__.py -------------------------------------------------------------------------------- /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/test/test_view.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from vj4.handler import base 4 | 5 | PERM_DUMMY = 1 6 | PRIV_DUMMY = 2 7 | 8 | 9 | class DummyHandler(object): 10 | def __init__(self): 11 | self.perm_checked = None 12 | self.priv_checked = None 13 | 14 | def check_perm(self, perm): 15 | self.perm_checked = perm 16 | 17 | def check_priv(self, priv): 18 | self.priv_checked = priv 19 | 20 | 21 | class DecoratorTest(unittest.TestCase, DummyHandler): 22 | def setUp(self): 23 | DummyHandler.__init__(self) 24 | 25 | @base.require_perm(PERM_DUMMY) 26 | def assert_perm_checked(self, perm): 27 | self.assertEqual(self.perm_checked, perm) 28 | 29 | @base.require_priv(PRIV_DUMMY) 30 | def assert_priv_checked(self, priv): 31 | self.assertEqual(self.priv_checked, priv) 32 | 33 | def test_require_perm_func(self): 34 | self.assertIsNone(self.perm_checked) 35 | self.assert_perm_checked(PERM_DUMMY) 36 | 37 | def test_require_priv_func(self): 38 | self.assertIsNone(self.priv_checked) 39 | self.assert_priv_checked(PRIV_DUMMY) 40 | 41 | 42 | if __name__ == '__main__': 43 | unittest.main() 44 | -------------------------------------------------------------------------------- /vj4/ui/breakpoints.json: -------------------------------------------------------------------------------- 1 | { 2 | "mobile": 600, 3 | "desktop": 1000, 4 | "hd": 1250 5 | } 6 | -------------------------------------------------------------------------------- /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/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/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/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/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/components/autocomplete/domainselectautocomplete.page.styl: -------------------------------------------------------------------------------- 1 | .menu.domain-select 2 | width: rem(200px) 3 | 4 | .domain-select__id 5 | font-size: rem($font-size-small) 6 | color: $userselect-uid-color 7 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/components/contest/problem-contest-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/contest/problem-contest-bg.png -------------------------------------------------------------------------------- /vj4/ui/components/contest/problem-contest-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/contest/problem-contest-bg@2x.png -------------------------------------------------------------------------------- /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 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 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/dialog/dialog.page.styl: -------------------------------------------------------------------------------- 1 | .dialog 2 | position: fixed 3 | left: 0 4 | top: 0 5 | width: 100% 6 | height: 100% 7 | opacity: 0 8 | z-index: 1000 // managed 9 | // display flex will be added after animation completes 10 | flex-direction: column 11 | align-items: center 12 | justify-content: center 13 | display: none 14 | background: $dialog-layer-bg-color 15 | 16 | .dialog__content 17 | position: relative 18 | 19 | .dialog.withBg .dialog__content 20 | background: $dialog-bg-color 21 | border: 1px solid $dialog-border-color 22 | box-shadow: $dialog-shadow 23 | min-width: rem(500px) 24 | max-width: rem(900px) 25 | padding: rem(40px) rem(30px) 26 | 27 | .dialog__body 28 | margin-bottom: rem(20px) 29 | min-height: rem(50px) 30 | 31 | h1 32 | font-size: 1.6rem 33 | color: $dialog-title-color 34 | margin-bottom: 1rem 35 | 36 | .dialog__action button 37 | margin-left: rem(10px) 38 | margin-bottom: 0 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/ui/components/footer/footer.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | import { isBelow } from 'vj/utils/mediaQuery'; 3 | import { slideUp, slideDown } from 'vj/utils/slide'; 4 | import responsiveCutoff from 'vj/breakpoints.json'; 5 | 6 | const footerPage = new AutoloadPage('footerPage', () => { 7 | if ($('.footer').length === 0) { 8 | return; 9 | } 10 | $('.footer__category.expandable > h1').click(async ev => { 11 | if (!isBelow(responsiveCutoff.mobile)) { 12 | return; 13 | } 14 | const $category = $(ev.currentTarget).closest('.footer__category'); 15 | const $list = $category.find('.footer__category__expander'); 16 | if ($category.hasClass('animating')) { 17 | return; 18 | } 19 | $category.addClass('animating'); 20 | if ($category.hasClass('expanded')) { 21 | $category.removeClass('expanded'); 22 | await slideUp($list, 300, { opacity: 1 }, { opacity: 0 }); 23 | } else { 24 | $category.addClass('expanded'); 25 | await slideDown($list, 300, { opacity: 0 }, { opacity: 1 }); 26 | } 27 | $category.removeClass('animating'); 28 | }); 29 | }); 30 | 31 | export default footerPage; 32 | -------------------------------------------------------------------------------- /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/form/select.page.styl: -------------------------------------------------------------------------------- 1 | @import './var.inc.styl' 2 | 3 | .select 4 | form-styles() 5 | padding-right: 1.1rem 6 | background-color: $input-background-color 7 | background-image: url('~vj/misc/icons/' + $icon-expand_more-file) 8 | background-size: 16px 16px 9 | background-position: right -1rem center 10 | background-origin: content-box 11 | background-repeat: no-repeat 12 | outline: $input-outline 13 | transition: outline-color .2s, border-color .2s 14 | transition-timing-function: ease-out-cubic 15 | 16 | &:focus 17 | border-color: $input-focus-border-color 18 | outline: $input-focus-outline 19 | outline-offset: 0 20 | 21 | &:disabled 22 | opacity: 0.5 23 | 24 | .select-container.compact .select, .select.compact 25 | margin-bottom: 0 26 | height: rem($compact-control-height) 27 | line-height: rem($compact-control-height - 2) 28 | padding-top: 0 29 | padding-bottom: 0 30 | -------------------------------------------------------------------------------- /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/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/components/header/header-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/header/header-background.png -------------------------------------------------------------------------------- /vj4/ui/components/header/header-background@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/header/header-background@2x.png -------------------------------------------------------------------------------- /vj4/ui/components/header/header-logo-summer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/header/header-logo-summer.png -------------------------------------------------------------------------------- /vj4/ui/components/header/header-logo-summer@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/header/header-logo-summer@2x.png -------------------------------------------------------------------------------- /vj4/ui/components/header/header-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/header/header-logo.png -------------------------------------------------------------------------------- /vj4/ui/components/header/header-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/header/header-logo@2x.png -------------------------------------------------------------------------------- /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/components/highlighter/highlighter.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | 3 | const highlighterPage = new AutoloadPage('highlighterPage', () => { 4 | 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/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/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/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 | import('katex/dist/contrib/auto-render.min.js').then(renderKatex => { 8 | function runKatex($containers) { 9 | $containers.get().forEach(container => renderKatex.default(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/katex/katex.styl: -------------------------------------------------------------------------------- 1 | .katex 2 | font-size: 1em 3 | -------------------------------------------------------------------------------- /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/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/components/menu/menu-heading.page.js: -------------------------------------------------------------------------------- 1 | import { AutoloadPage } from 'vj/misc/PageLoader'; 2 | 3 | import tpl from 'vj/utils/tpl'; 4 | 5 | const menuHeadingPage = new AutoloadPage('menuHeadingPage', null, () => { 6 | $('[data-heading-extract-to]').get().forEach(container => { 7 | const $container = $(container); 8 | const $target = $('body').find($container.attr('data-heading-extract-to')); 9 | if ($target.length === 0) { 10 | return; 11 | } 12 | let $menu = $target.children('.menu'); 13 | if ($menu.length === 0) { 14 | $menu = $(tpl``).appendTo($target); 15 | $target.children('.menu__link').addClass('expandable'); 16 | } 17 | $container.find('[data-heading]').get().forEach(heading => { 18 | const $heading = $(heading); 19 | $(tpl` 20 | 25 | `).appendTo($menu); 26 | }); 27 | }); 28 | }); 29 | 30 | export default menuHeadingPage; 31 | -------------------------------------------------------------------------------- /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/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/messagepad/DialogueListItemComponent.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import classNames from 'classnames'; 3 | 4 | export default function DialogueListItemComponent(props) { 5 | const { 6 | userName, 7 | summary, 8 | faceUrl, 9 | active, 10 | onClick, 11 | className, 12 | ...rest 13 | } = props; 14 | const cn = classNames(className, 'messagepad__list-item media', { 15 | active, 16 | }); 17 | return ( 18 |
  • 19 | 20 |
    21 | avatar 22 |
    23 |
    24 |

    {userName}

    25 |
    {summary}
    26 |
    27 |
    28 |
  • 29 | ); 30 | } 31 | 32 | DialogueListItemComponent.propTypes = { 33 | userName: PropTypes.string.isRequired, 34 | summary: PropTypes.string.isRequired, 35 | faceUrl: PropTypes.string.isRequired, 36 | active: PropTypes.bool, 37 | onClick: PropTypes.func, 38 | className: PropTypes.string, 39 | }; 40 | -------------------------------------------------------------------------------- /vj4/ui/components/messagepad/MessageComponent.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import classNames from 'classnames'; 3 | 4 | export default function MessageComponent(props) { 5 | const { 6 | faceUrl, 7 | isSelf, 8 | className, 9 | children, 10 | ...rest 11 | } = props; 12 | const cn = classNames(className, 'messagepad__message', { 13 | 'side--self': isSelf, 14 | 'side--other': !isSelf, 15 | }); 16 | return ( 17 |
  • 18 |
    19 | avatar 20 |
    21 |
    22 | {children} 23 |
    24 |
  • 25 | ); 26 | } 27 | 28 | MessageComponent.propTypes = { 29 | isSelf: PropTypes.bool, 30 | faceUrl: PropTypes.string.isRequired, 31 | className: PropTypes.string, 32 | children: PropTypes.node, 33 | }; 34 | -------------------------------------------------------------------------------- /vj4/ui/components/messagepad/reducers/activeId.js: -------------------------------------------------------------------------------- 1 | import 'jquery.easing'; 2 | 3 | function scrollToViewport() { 4 | const BOUND_TOP = 60; 5 | const BOUND_BOTTOM = 20; 6 | const node = $('.messagepad')[0]; 7 | if (node.offsetHeight + BOUND_TOP + BOUND_BOTTOM < window.innerHeight) { 8 | const rect = node.getBoundingClientRect(); 9 | const rectBody = document.body.getBoundingClientRect(); 10 | let targetScrollTop = null; 11 | if (rect.top < BOUND_TOP) { 12 | targetScrollTop = rect.top - rectBody.top - BOUND_TOP; 13 | } else if (rect.top + node.offsetHeight > window.innerHeight) { 14 | targetScrollTop = rect.top - rectBody.top + node.offsetHeight + BOUND_BOTTOM - window.innerHeight; 15 | } 16 | if (targetScrollTop !== null) { 17 | $('html, body').stop().animate({ scrollTop: targetScrollTop }, 200, 'easeOutCubic'); 18 | } 19 | } 20 | } 21 | 22 | export default function reducer(state = null, action) { 23 | switch (action.type) { 24 | case 'DIALOGUES_SWITCH_TO': { 25 | scrollToViewport(); 26 | return action.payload; 27 | } 28 | case 'DIALOGUES_POST_SEND_FULFILLED': { 29 | return action.payload.mdoc._id; 30 | } 31 | default: 32 | return state; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /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/navigation/nav-logo-small@2x_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/navigation/nav-logo-small@2x_dark.png -------------------------------------------------------------------------------- /vj4/ui/components/navigation/nav-logo-small@2x_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/navigation/nav-logo-small@2x_light.png -------------------------------------------------------------------------------- /vj4/ui/components/navigation/nav-logo-small_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/navigation/nav-logo-small_dark.png -------------------------------------------------------------------------------- /vj4/ui/components/navigation/nav-logo-small_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/navigation/nav-logo-small_light.png -------------------------------------------------------------------------------- /vj4/ui/components/nprogress/index.js: -------------------------------------------------------------------------------- 1 | import NProgress from 'nprogress'; 2 | 3 | export default NProgress; 4 | -------------------------------------------------------------------------------- /vj4/ui/components/pager/pager.page.styl: -------------------------------------------------------------------------------- 1 | .pager 2 | margin-bottom: rem(10px) 3 | border-top: 2px solid $pager-border-color 4 | text-align: center 5 | font-size: rem($font-size-small) 6 | 7 | &:empty 8 | display: none 9 | 10 | li 11 | margin: 0 12 | display: inline-block 13 | margin-top: -2px 14 | 15 | .pager__item 16 | display: inline-block 17 | padding: rem(10px) 18 | text-decoration: none 19 | border: 0 20 | border-top: 2px solid $pager-border-color 21 | transition: border-top .2s linear, color .2s linear 22 | color: #a7a7a7 23 | 24 | &.link 25 | &, .typo & 26 | color: #a7a7a7 27 | 28 | &:hover 29 | border-color: $primary-color 30 | text-decoration: none 31 | &, .typo & 32 | color: #666 33 | 34 | &.current 35 | color: $secondary-color 36 | border-color: $secondary-color 37 | 38 | +mobile() 39 | .pager__item 40 | &.ellipsis, 41 | &.first, 42 | &.previous, 43 | &.next, 44 | &.last 45 | display: none 46 | 47 | -------------------------------------------------------------------------------- /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/components/problem/tag.page.styl: -------------------------------------------------------------------------------- 1 | .problem__tags 2 | margin: 0 !important 3 | padding: 0 !important 4 | list-style: none !important 5 | font-size: 0 6 | 7 | .problem__tag 8 | display: inline-block 9 | vertical-align: top 10 | height: rem($table-lh) 11 | margin: rem(0 2px 2px 0) !important 12 | padding: 0 !important 13 | 14 | .problem__tag-link 15 | display: inline-block 16 | padding: rem(0 7px) 17 | overflow: hidden 18 | text-overflow: ellipsis 19 | white-space: nowrap 20 | max-width: rem(130px) 21 | line-height: rem($table-lh) 22 | height: rem($table-lh) 23 | background: #F0F0F0 24 | color: $text-1-color !important 25 | font-size: rem($font-size-small) 26 | 27 | &:hover 28 | background: $primary-color 29 | color: #FFF !important 30 | text-decoration: none 31 | 32 | .col--problem-name 33 | .problem__tags 34 | float: right 35 | 36 | .problem__tag 37 | margin: rem(0 2px 0 0) !important 38 | -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/1.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/10.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/11.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/12.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/13.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/14.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/15.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/16.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/17.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/18.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/19.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/2.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/20.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/21.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/3.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/4.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/5.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/6.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/7.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/8.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/9.jpg -------------------------------------------------------------------------------- /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/components/profile/backgrounds/thumbnail/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/1.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/10.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/11.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/12.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/13.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/14.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/15.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/16.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/17.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/18.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/19.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/2.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/20.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/21.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/3.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/4.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/5.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/6.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/7.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/8.jpg -------------------------------------------------------------------------------- /vj4/ui/components/profile/backgrounds/thumbnail/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vijos/vj4/7ce61263b163a6b6a6cb6de41dfd9c4b17709de2/vj4/ui/components/profile/backgrounds/thumbnail/9.jpg -------------------------------------------------------------------------------- /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/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/components/react-splitpane/SplitPaneFillOverlayComponent.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 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: PropTypes.string, 18 | children: PropTypes.node, 19 | }; 20 | -------------------------------------------------------------------------------- /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/react-tabs/rc-tabs.page.js: -------------------------------------------------------------------------------- 1 | import 'rc-tabs/assets/index.css'; 2 | -------------------------------------------------------------------------------- /vj4/ui/components/react/DomComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class DomComponent extends React.PureComponent { 5 | componentDidMount() { 6 | this.refs.dom.appendChild(this.props.childDom); 7 | } 8 | 9 | componentWillUnmount() { 10 | $(this.refs.dom).empty(); 11 | } 12 | 13 | render() { 14 | const { 15 | childDom, 16 | ...rest 17 | } = this.props; 18 | return ( 19 |
    20 | ); 21 | } 22 | } 23 | 24 | DomComponent.propTypes = { 25 | childDom: PropTypes.instanceOf(HTMLElement).isRequired, 26 | }; 27 | -------------------------------------------------------------------------------- /vj4/ui/components/react/IconComponent.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 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: PropTypes.string.isRequired, 18 | className: PropTypes.string, 19 | }; 20 | -------------------------------------------------------------------------------- /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/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/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/components/scratchpad/DataInputComponent.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 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 |