├── lib.js
├── components
├── katex
│ ├── katex.styl
│ └── katex.page.js
├── scratchpad
│ ├── var.inc.styl
│ ├── Panel.page.styl
│ ├── Editor.page.styl
│ ├── reducers
│ │ ├── index.js
│ │ ├── editor.js
│ │ ├── pretest.js
│ │ └── records.js
│ ├── PanelButton.page.styl
│ ├── DataInput.page.styl
│ ├── PanelButtonComponent.jsx
│ ├── PanelComponent.jsx
│ ├── DataInputComponent.jsx
│ ├── scratchpad.page.styl
│ └── Toolbar.page.styl
├── nprogress
│ └── index.js
├── header
│ ├── header-logo.png
│ ├── header-logo@2x.png
│ ├── header-background.png
│ ├── header-logo-summer.png
│ ├── header-background@2x.png
│ └── header-logo-summer@2x.png
├── problem
│ ├── rp.page.styl
│ └── tag.page.styl
├── profile
│ ├── backgrounds
│ │ ├── 1.jpg
│ │ ├── 2.jpg
│ │ ├── 3.jpg
│ │ ├── 4.jpg
│ │ ├── 5.jpg
│ │ ├── 6.jpg
│ │ ├── 7.jpg
│ │ ├── 8.jpg
│ │ ├── 9.jpg
│ │ ├── 10.jpg
│ │ ├── 11.jpg
│ │ ├── 12.jpg
│ │ ├── 13.jpg
│ │ ├── 14.jpg
│ │ ├── 15.jpg
│ │ ├── 16.jpg
│ │ ├── 17.jpg
│ │ ├── 18.jpg
│ │ ├── 19.jpg
│ │ ├── 20.jpg
│ │ ├── 21.jpg
│ │ ├── thumbnail
│ │ │ ├── 1.jpg
│ │ │ ├── 2.jpg
│ │ │ ├── 3.jpg
│ │ │ ├── 4.jpg
│ │ │ ├── 5.jpg
│ │ │ ├── 6.jpg
│ │ │ ├── 7.jpg
│ │ │ ├── 8.jpg
│ │ │ ├── 9.jpg
│ │ │ ├── 10.jpg
│ │ │ ├── 11.jpg
│ │ │ ├── 12.jpg
│ │ │ ├── 13.jpg
│ │ │ ├── 14.jpg
│ │ │ ├── 15.jpg
│ │ │ ├── 16.jpg
│ │ │ ├── 17.jpg
│ │ │ ├── 18.jpg
│ │ │ ├── 19.jpg
│ │ │ ├── 20.jpg
│ │ │ └── 21.jpg
│ │ └── gen_thumbnails.sh
│ └── profile.page.styl
├── contest
│ ├── problem-contest-bg.png
│ ├── problem-contest-bg@2x.png
│ ├── contest_sidebar.page.styl
│ └── contest.page.styl
├── navigation
│ ├── nav-logo-small_dark.png
│ ├── nav-logo-small_light.png
│ ├── nav-logo-small@2x_dark.png
│ └── nav-logo-small@2x_light.png
├── tab
│ ├── var.inc.styl
│ └── tab.page.js
├── emoji
│ ├── emoji.page.styl
│ └── emojify.page.js
├── autocomplete
│ ├── userselectautocomplete.page.styl
│ ├── domainselectautocomplete.page.styl
│ └── autocomplete.page.styl
├── react-splitpane
│ ├── SplitPaneFillOverlay.page.styl
│ ├── SplitPaneFillOverlayComponent.jsx
│ └── splitpane.page.styl
├── marker
│ ├── marker.page.js
│ └── marker.page.styl
├── dropdown
│ ├── dropdown.page.js
│ └── dropdown.page.styl
├── table
│ └── styledTable.page.js
├── form
│ ├── var.inc.styl
│ ├── form.page.js
│ ├── textbox.page.js
│ └── select.page.styl
├── messagepad
│ ├── reducers
│ │ ├── index.js
│ │ ├── inputs.js
│ │ ├── activeId.js
│ │ ├── isPosting.js
│ │ └── dialogues.js
│ ├── DialogueListItem.page.styl
│ ├── MessageComponent.jsx
│ └── DialogueListItemComponent.jsx
├── star
│ ├── star.page.styl
│ └── star.page.js
├── rotator
│ └── rotator.page.styl
├── discussion
│ └── discussion.page.styl
├── react
│ ├── IconComponent.jsx
│ └── DomComponent.jsx
├── highlighter
│ ├── highlighter.page.js
│ ├── codemirror.page.styl
│ └── meta.js
├── menu
│ ├── menu.page.js
│ └── menu-heading.page.js
├── cmeditor
│ ├── cmeditor.page.js
│ └── textareaHandler.js
├── loader
│ └── loader.page.styl
├── datepicker
│ └── datepicker.page.js
├── record
│ └── record.page.styl
├── training
│ └── training.page.styl
├── dialog
│ └── dialog.page.styl
├── tooltip
│ ├── tooltip.page.js
│ └── Tooltip.js
├── pager
│ └── pager.page.styl
├── hitokoto
│ └── index.page.js
├── footer
│ └── footer.page.js
├── message
│ └── index.page.js
├── notification
│ └── notification.page.styl
├── vote
│ └── vote.page.js
└── drop
│ └── drop.page.styl
├── .vscode
├── settings.json
└── extensions.json
├── constant
├── message.js
├── util
│ └── objectMeta.js
├── setting.js
├── model.js
└── domain.js
├── breakpoints.json
├── static
├── favicon.ico
├── img
│ ├── avatar.png
│ └── team_avatar.png
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon-96x96.png
├── mstile-144x144.png
├── android-chrome-192x192.png
├── apple-touch-icon-180x180.png
└── manifest.json
├── postcss.config.js
├── .gitignore
├── misc
├── immersive-background.jpg
├── immersive-background@2x.jpg
├── icons
│ ├── send.svg
│ ├── add.svg
│ ├── check.svg
│ ├── flag.svg
│ ├── chevron_left.svg
│ ├── chevron_right.svg
│ ├── expand_less.svg
│ ├── expand_more.svg
│ ├── italic.svg
│ ├── upload.svg
│ ├── download.svg
│ ├── formula.svg
│ ├── statistics.svg
│ ├── warning.svg
│ ├── star.svg
│ ├── code.svg
│ ├── quote.svg
│ ├── close.svg
│ ├── reply.svg
│ ├── delete.svg
│ ├── logout.svg
│ ├── play.svg
│ ├── hourglass.svg
│ ├── info--circle.svg
│ ├── check--circle.svg
│ ├── edit.svg
│ ├── platform--windows.svg
│ ├── user.svg
│ ├── security.svg
│ ├── heart.svg
│ ├── link--external.svg
│ ├── copy.svg
│ ├── stack.svg
│ ├── vote--up.svg
│ ├── calendar.svg
│ ├── template
│ │ ├── webicon.inc.styl
│ │ └── webicon.styl
│ ├── close--circle.svg
│ ├── star--outline.svg
│ ├── lab.svg
│ ├── vote--down.svg
│ ├── facebook.svg
│ ├── mail.svg
│ ├── insert--image.svg
│ ├── ordered_list.svg
│ ├── platform--mobile.svg
│ ├── shrink.svg
│ ├── enlarge.svg
│ ├── refresh.svg
│ ├── schedule--fill.svg
│ ├── info.svg
│ ├── tag.svg
│ ├── comment--text.svg
│ ├── crown.svg
│ ├── account--circle.svg
│ ├── comment--multiple.svg
│ ├── google_plus.svg
│ ├── help2.svg
│ ├── erase.svg
│ ├── linkedin.svg
│ ├── bold.svg
│ ├── user--multiple.svg
│ ├── global.svg
│ ├── heart--outline.svg
│ ├── search.svg
│ ├── platform--unknown.svg
│ ├── filter.svg
│ ├── block.svg
│ ├── platform--ios.svg
│ ├── schedule.svg
│ ├── insert--link.svg
│ ├── award.svg
│ ├── debug.svg
│ ├── homework.svg
│ ├── sliders.svg
│ ├── twitter.svg
│ ├── qq.svg
│ ├── platform--chromeos.svg
│ ├── file.svg
│ ├── preview.svg
│ ├── wrench.svg
│ ├── feeling-lucky.svg
│ ├── unordered_list.svg
│ ├── github.svg
│ ├── settings.svg
│ ├── wechat.svg
│ ├── help.svg
│ ├── web.svg
│ ├── platform--android.svg
│ ├── platform--mac.svg
│ ├── link.svg
│ └── platform--linux.svg
├── textalign.styl
├── float.styl
├── nothing.styl
├── immersive.styl
├── slideout.styl
└── PageLoader.js
├── pages
├── discussion_node_bg
│ ├── nodes_qa.png
│ ├── nodes@2x_qa.png
│ ├── nodes_advice.png
│ ├── nodes_share.png
│ ├── nodes_vijos.png
│ ├── nodes@2x_share.png
│ ├── nodes@2x_vijos.png
│ ├── nodes_solution.png
│ ├── nodes@2x_advice.png
│ └── nodes@2x_solution.png
├── domain_role.page.styl
├── problem_files.page.styl
├── problem_import.page.js
├── domain_user.page.styl
├── home_account.page.styl
├── domain_join.page.styl
├── problem_submit.page.styl
├── status.page.styl
├── contest_detail.page.styl
├── homework_detail.page.styl
├── contest_main.page.js
├── ranking.page.styl
├── problem_submit.page.js
├── discussion_node.page.js
├── homework_main.page.styl
├── problem_solution.page.js
├── discussion_detail.page.js
├── home_domain.page.styl
├── domain_permission.page.styl
├── discussion_detail.page.styl
├── homework_scoreboard.page.styl
├── record_detail.page.styl
├── contest_scoreboard.page.styl
├── home_security.page.styl
├── record_detail.page.js
├── domain_join_applications.page.js
├── training_detail.page.styl
├── user_detail.page.js
├── training_edit.page.js
├── manage_user_import.page.js
├── manage_script.page.js
├── error.page.styl
├── discussion_main.page.styl
├── training_main.page.styl
├── record_main.page.styl
├── record_main.page.js
├── manage_dashboard.page.js
└── homework_main.page.js
├── templates
├── layout
│ ├── simple.html
│ ├── home_base.html
│ ├── wiki_base.html
│ ├── basic.html
│ └── immersive.html
├── partials
│ ├── homework_default_penalty_rules.yaml
│ ├── problem_stat.html
│ ├── hamburger.html
│ ├── header.html
│ ├── training_default.json
│ ├── header_mobile.html
│ ├── problem_lucky.html
│ ├── problem_default.md
│ ├── problem_sidebar.html
│ ├── discussion_nodes_widget.html
│ ├── path.html
│ └── category.html
├── domain_edit.html
├── manage_setting.html
├── components
│ ├── nothing.html
│ ├── contest.html
│ ├── homework.html
│ ├── noscript_note.html
│ ├── sidemenu.html
│ ├── record.html
│ ├── home.html
│ └── paginator.html
├── home_settings.html
├── user_register_mail_sent.html
├── user_changemail_mail_sent.html
├── user_lostpass_mail_sent.html
├── user_register_mail.html
├── user_lostpass_mail.html
├── user_changemail_mail.html
├── user_delete_pending.html
├── problem_submit_tr.html
├── user_logout.html
├── problem_statistics.html
├── record_detail_summary.html
├── error.html
├── home_messages.html
├── manage_user_import.html
├── problem_upload.html
├── user_lostpass.html
├── user_register.html
├── manage_base.html
├── fs_upload.html
├── problem_import.html
├── user_lostpass_with_code.html
├── contest_boardcast.html
├── domain_dashboard.html
├── manage_dashboard.html
├── discussion_create.html
├── contest_scoreboard_download_html.html
├── user_register_with_code.html
├── discussion_edit.html
├── domain_join.html
└── domain_base.html
├── entry.js
├── .npmignore
├── common
├── rem.inc.styl
├── functions.inc.styl
├── color.inc.styl
└── common.inc.styl
├── utils
├── delay.js
├── zIndexManager.js
├── i18n.js
├── substitute.js
├── zip.js
├── parseQueryString.js
├── mediaQuery.js
├── mongoId.js
├── emulateAnchorClick.js
├── tpl.js
├── pipeStream.js
└── loadReactRedux.js
├── jsconfig.json
├── .gitpod.yml
├── README.md
├── modules.js
├── .github
└── workflows
│ └── build.yml
├── theme
└── default.js
└── babel.config.js
/lib.js:
--------------------------------------------------------------------------------
1 | require('./backendlib/template');
2 |
--------------------------------------------------------------------------------
/components/katex/katex.styl:
--------------------------------------------------------------------------------
1 | .katex
2 | font-size: 1.2em
3 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.eol": "\n",
3 | "editor.tabSize": 2
4 | }
--------------------------------------------------------------------------------
/constant/message.js:
--------------------------------------------------------------------------------
1 | export const FLAG_UNREAD = 1;
2 | export const FLAG_ALERT = 2;
3 |
--------------------------------------------------------------------------------
/breakpoints.json:
--------------------------------------------------------------------------------
1 | {
2 | "mobile": 600,
3 | "desktop": 1000,
4 | "hd": 1250
5 | }
6 |
--------------------------------------------------------------------------------
/components/scratchpad/var.inc.styl:
--------------------------------------------------------------------------------
1 | $panel-header-height = 34px
2 | $tab-color = #fff170
3 |
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/static/favicon.ico
--------------------------------------------------------------------------------
/static/img/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/static/img/avatar.png
--------------------------------------------------------------------------------
/components/nprogress/index.js:
--------------------------------------------------------------------------------
1 | import NProgress from 'nprogress';
2 |
3 | export default NProgress;
4 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {},
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 | *.log
3 | node_modules/
4 | public/
5 | __*
6 | misc/.iconfont
7 | static/locale
8 |
--------------------------------------------------------------------------------
/static/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/static/favicon-16x16.png
--------------------------------------------------------------------------------
/static/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/static/favicon-32x32.png
--------------------------------------------------------------------------------
/static/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/static/favicon-96x96.png
--------------------------------------------------------------------------------
/static/img/team_avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/static/img/team_avatar.png
--------------------------------------------------------------------------------
/static/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/static/mstile-144x144.png
--------------------------------------------------------------------------------
/misc/immersive-background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/misc/immersive-background.jpg
--------------------------------------------------------------------------------
/components/header/header-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/header/header-logo.png
--------------------------------------------------------------------------------
/components/problem/rp.page.styl:
--------------------------------------------------------------------------------
1 | .problem__rp-tag
2 | font-size: 0.8em
3 | padding: rem(0 5px)
4 | color: #AAA
5 |
--------------------------------------------------------------------------------
/misc/immersive-background@2x.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/misc/immersive-background@2x.jpg
--------------------------------------------------------------------------------
/static/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/static/android-chrome-192x192.png
--------------------------------------------------------------------------------
/components/header/header-logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/header/header-logo@2x.png
--------------------------------------------------------------------------------
/components/profile/backgrounds/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/1.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/2.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/3.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/4.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/5.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/6.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/7.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/8.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/9.jpg
--------------------------------------------------------------------------------
/static/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/static/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/components/profile/backgrounds/10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/10.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/11.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/12.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/13.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/13.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/14.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/14.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/15.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/15.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/16.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/16.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/17.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/17.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/18.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/19.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/19.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/20.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/20.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/21.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/21.jpg
--------------------------------------------------------------------------------
/pages/discussion_node_bg/nodes_qa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/pages/discussion_node_bg/nodes_qa.png
--------------------------------------------------------------------------------
/components/contest/problem-contest-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/contest/problem-contest-bg.png
--------------------------------------------------------------------------------
/components/header/header-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/header/header-background.png
--------------------------------------------------------------------------------
/components/header/header-logo-summer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/header/header-logo-summer.png
--------------------------------------------------------------------------------
/pages/discussion_node_bg/nodes@2x_qa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/pages/discussion_node_bg/nodes@2x_qa.png
--------------------------------------------------------------------------------
/pages/discussion_node_bg/nodes_advice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/pages/discussion_node_bg/nodes_advice.png
--------------------------------------------------------------------------------
/pages/discussion_node_bg/nodes_share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/pages/discussion_node_bg/nodes_share.png
--------------------------------------------------------------------------------
/pages/discussion_node_bg/nodes_vijos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/pages/discussion_node_bg/nodes_vijos.png
--------------------------------------------------------------------------------
/pages/domain_role.page.styl:
--------------------------------------------------------------------------------
1 | .page--domain_role
2 | .col--checkbox
3 | width: 60px
4 |
5 | .col--users
6 | width: 100px
7 |
--------------------------------------------------------------------------------
/pages/problem_files.page.styl:
--------------------------------------------------------------------------------
1 | .page--problem_files
2 | .col--checkbox
3 | width: 60px
4 |
5 | .col--size
6 | width: 120px
7 |
--------------------------------------------------------------------------------
/components/header/header-background@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/header/header-background@2x.png
--------------------------------------------------------------------------------
/components/header/header-logo-summer@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/header/header-logo-summer@2x.png
--------------------------------------------------------------------------------
/pages/discussion_node_bg/nodes@2x_share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/pages/discussion_node_bg/nodes@2x_share.png
--------------------------------------------------------------------------------
/pages/discussion_node_bg/nodes@2x_vijos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/pages/discussion_node_bg/nodes@2x_vijos.png
--------------------------------------------------------------------------------
/pages/discussion_node_bg/nodes_solution.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/pages/discussion_node_bg/nodes_solution.png
--------------------------------------------------------------------------------
/templates/layout/simple.html:
--------------------------------------------------------------------------------
1 | {% set layout_name = "basic" %}
2 | {% extends "layout/html5.html" %}
3 | {% block body %}
4 | {% endblock %}
5 |
--------------------------------------------------------------------------------
/templates/partials/homework_default_penalty_rules.yaml:
--------------------------------------------------------------------------------
1 | # Format:
2 | # hours: coefficient
3 | 1: 0.9
4 | 3: 0.8
5 | 12: 0.75
6 | 9999: 0.5
7 |
--------------------------------------------------------------------------------
/components/contest/problem-contest-bg@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/contest/problem-contest-bg@2x.png
--------------------------------------------------------------------------------
/components/navigation/nav-logo-small_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/navigation/nav-logo-small_dark.png
--------------------------------------------------------------------------------
/components/navigation/nav-logo-small_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/navigation/nav-logo-small_light.png
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/1.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/2.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/3.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/4.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/5.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/6.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/7.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/8.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/9.jpg
--------------------------------------------------------------------------------
/misc/icons/send.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/discussion_node_bg/nodes@2x_advice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/pages/discussion_node_bg/nodes@2x_advice.png
--------------------------------------------------------------------------------
/pages/discussion_node_bg/nodes@2x_solution.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/pages/discussion_node_bg/nodes@2x_solution.png
--------------------------------------------------------------------------------
/components/navigation/nav-logo-small@2x_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/navigation/nav-logo-small@2x_dark.png
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/10.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/11.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/12.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/13.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/13.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/14.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/14.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/15.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/15.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/16.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/16.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/17.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/17.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/18.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/19.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/19.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/20.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/20.jpg
--------------------------------------------------------------------------------
/components/profile/backgrounds/thumbnail/21.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/profile/backgrounds/thumbnail/21.jpg
--------------------------------------------------------------------------------
/entry.js:
--------------------------------------------------------------------------------
1 | window.Hydro = {
2 | extraPages: [],
3 | components: {},
4 | utils: {},
5 | node_modules: {},
6 | };
7 |
8 | import('./hydro');
9 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .cache/
3 | build/
4 | common/
5 | components/
6 | constant/
7 | misc/
8 | pages/
9 | static/
10 | utils/
11 | __*
12 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/components/navigation/nav-logo-small@2x_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hydro-dev/ui-default/HEAD/components/navigation/nav-logo-small@2x_light.png
--------------------------------------------------------------------------------
/components/tab/var.inc.styl:
--------------------------------------------------------------------------------
1 | $tab-item-height = 35px
2 | $tab-item-shadow-size = 10px
3 | $tab-item-height-mobile = 32px // 32 * (13 / 16) is an integer
4 |
--------------------------------------------------------------------------------
/misc/icons/add.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/check.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/flag.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/partials/problem_stat.html:
--------------------------------------------------------------------------------
1 |
2 |
{{ _('{0} problems').format(pcount) }}
3 |
--------------------------------------------------------------------------------
/misc/icons/chevron_left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/chevron_right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/expand_less.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/expand_more.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/misc/icons/italic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/utils/delay.js:
--------------------------------------------------------------------------------
1 | export default function delay(ms) {
2 | return new Promise((resolve) => setTimeout(resolve, ms));
3 | }
4 |
5 | window.Hydro.utils.delay = delay;
6 |
--------------------------------------------------------------------------------
/misc/icons/upload.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/download.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/formula.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/statistics.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/warning.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/problem_import.page.js:
--------------------------------------------------------------------------------
1 | import { NamedPage } from 'vj/misc/Page';
2 |
3 | const page = new NamedPage('problem_import', async () => {
4 | });
5 |
6 | export default page;
7 |
--------------------------------------------------------------------------------
/misc/icons/star.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/domain_user.page.styl:
--------------------------------------------------------------------------------
1 | .page--domain_user
2 | .col--checkbox
3 | width: 60px
4 |
5 | .col--uid
6 | width: 100px
7 |
8 | .col--role
9 | width: 160px
10 |
--------------------------------------------------------------------------------
/misc/icons/code.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/quote.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/domain_edit.html:
--------------------------------------------------------------------------------
1 | {% extends "domain_base.html" %}
2 | {% block domain_content %}
3 |
4 | {% include "partials/setting.html" %}
5 |
6 | {% endblock %}
7 |
--------------------------------------------------------------------------------
/templates/manage_setting.html:
--------------------------------------------------------------------------------
1 | {% extends "manage_base.html" %}
2 | {% block manage_content %}
3 |
4 | {% include "partials/setting.html" %}
5 |
6 | {% endblock %}
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/misc/icons/close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/reply.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/home_account.page.styl:
--------------------------------------------------------------------------------
1 | .page--home_account
2 | [name="form_item_background_img"] .radiobox__image
3 | width: rem(100px)
4 | height: rem(62px)
5 | background-size: rem(100px 62px)
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/misc/icons/delete.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/logout.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/play.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "gruntfuggly.todo-tree",
5 | "ronnidc.nunjucks",
6 | "sysoev.language-stylus"
7 | ]
8 | }
--------------------------------------------------------------------------------
/misc/textalign.styl:
--------------------------------------------------------------------------------
1 | .text-center
2 | text-align: center
3 |
4 | .text-right
5 | text-align: right
6 |
7 | .text-left
8 | text-align: left
9 |
10 | .text-justify
11 | text-align: justify
12 |
--------------------------------------------------------------------------------
/misc/icons/hourglass.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Hydro",
3 | "icons": [
4 | {
5 | "src": "\/android-chrome-192x192.png",
6 | "sizes": "192x192",
7 | "type": "image\/png",
8 | "density": 4
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/misc/icons/info--circle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/check--circle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/edit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/platform--windows.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/user.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/profile/backgrounds/gen_thumbnails.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | for f in *.jpg
4 | do
5 | echo "$f..."
6 | convert "$f" -strip -interlace Plane -quality 92 -resize 200x124^ -gravity center -extent 200x124 "thumbnail/$f"
7 | done
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/templates/components/nothing.html:
--------------------------------------------------------------------------------
1 | {% macro render(text, compact=false) %}
2 |
3 |
4 | {{ _(text) }}
5 |
6 | {% endmacro %}
7 |
--------------------------------------------------------------------------------
/misc/icons/security.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/constant/util/objectMeta.js:
--------------------------------------------------------------------------------
1 | export default function attachObjectMeta(obj, key, value) {
2 | Object.defineProperty(obj, `__${key}`, {
3 | value,
4 | enumerable: false,
5 | configurable: false,
6 | writable: false,
7 | });
8 | }
9 |
--------------------------------------------------------------------------------
/misc/icons/heart.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/link--external.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/domain_join.page.styl:
--------------------------------------------------------------------------------
1 | .page--domain_join
2 | .domain_join__container
3 | +above(rupture.mobile-cutoff)
4 | width: rem(500px)
5 | margin: rem(50px) auto
6 |
7 | +above(rupture.desktop-cutoff)
8 | width: rem(700px)
9 |
--------------------------------------------------------------------------------
/templates/home_settings.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/home_base.html" %}
2 | {% block home_content %}
3 |
4 | {% include "partials/setting.html" %}
5 |
6 | {% endblock %}
7 |
--------------------------------------------------------------------------------
/components/scratchpad/Panel.page.styl:
--------------------------------------------------------------------------------
1 | @import './var.inc.styl'
2 |
3 | .scratchpad__panel-title
4 | background: $primary-color
5 | line-height: rem($panel-header-height)
6 | padding: rem(0 5px)
7 | font-size: rem($font-size-small)
8 | color: #FFF
9 |
--------------------------------------------------------------------------------
/misc/float.styl:
--------------------------------------------------------------------------------
1 | .float-left
2 | float: left
3 |
4 | .float-right
5 | float: right
6 |
7 | .clearfix:after
8 | visibility: hidden
9 | display: block
10 | font-size: 0
11 | content: ' '
12 | clear: both
13 | height: 0
14 | zoom: 1
15 |
--------------------------------------------------------------------------------
/misc/icons/copy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/partials/hamburger.html:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/components/tab/tab.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
2 | import Tab from './Tab';
3 |
4 | const tabPage = new AutoloadPage('tabPage', () => {
5 | Tab.initAll();
6 | Tab.initEventListeners();
7 | });
8 |
9 | export default tabPage;
10 |
--------------------------------------------------------------------------------
/misc/icons/stack.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/vote--up.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/marker/marker.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
2 | import MarkerReactive from './MarkerReactive';
3 |
4 | const markerPage = new AutoloadPage('markerPage', () => {
5 | MarkerReactive.initAll();
6 | });
7 |
8 | export default markerPage;
9 |
--------------------------------------------------------------------------------
/misc/icons/calendar.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/template/webicon.inc.styl:
--------------------------------------------------------------------------------
1 | $icon-font-name = '{{ options.fontName }}'
2 |
3 | {% for icon in glyphs %}
4 | $icon-{{ icon.name }} = '\{{ icon.unicode[0].charCodeAt(0).toString(16)|upper }}'
5 | $icon-{{ icon.name }}-file = '{{ icon.name }}.svg'
6 | {% endfor %}
7 |
--------------------------------------------------------------------------------
/components/dropdown/dropdown.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
2 | import Dropdown from './Dropdown';
3 |
4 | const dropdownPage = new AutoloadPage('dropdownPage', () => {
5 | Dropdown.registerLifeCycleHooks();
6 | });
7 |
8 | export default dropdownPage;
9 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "experimentalDecorators": true,
4 | "allowSyntheticDefaultImports": true,
5 | "target": "ES6",
6 | "baseUrl": ".",
7 | "paths": {
8 | "vj/*": [
9 | "./*"
10 | ]
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/misc/icons/close--circle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/star--outline.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/problem_submit.page.styl:
--------------------------------------------------------------------------------
1 | .page--problem_submit
2 | .col--status
3 | width: rem(150px)
4 |
5 | .col--submit-at
6 | width: rem(160px)
7 | text-align: center
8 |
9 | +mobile()
10 | .col--memory,
11 | .col--time
12 | display: none
13 |
--------------------------------------------------------------------------------
/misc/icons/lab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/vote--down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/facebook.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/mail.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/table/styledTable.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
2 | import StyledTable from './StyledTable';
3 |
4 | const styledTablePage = new AutoloadPage('styledTablePage', () => {
5 | StyledTable.registerLifeCycleHooks();
6 | });
7 |
8 | export default styledTablePage;
9 |
--------------------------------------------------------------------------------
/pages/status.page.styl:
--------------------------------------------------------------------------------
1 | .page--status
2 | .col--id
3 | width: 70px
4 |
5 | .col--status
6 | width: 100px
7 |
8 | .col--memory
9 | width: 150px
10 |
11 | .col--request
12 | width: 100px
13 |
14 | +mobile()
15 | .col--os
16 | .col--cpu
17 | display: none
--------------------------------------------------------------------------------
/utils/zIndexManager.js:
--------------------------------------------------------------------------------
1 | let zIndexCurrent = 1000;
2 |
3 | const manager = {
4 | getCurrent() {
5 | return zIndexCurrent;
6 | },
7 | getNext() {
8 | return ++zIndexCurrent;
9 | },
10 | };
11 |
12 | export default manager;
13 | window.Hydro.utils.zIndexManager = manager;
14 |
--------------------------------------------------------------------------------
/misc/icons/insert--image.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/partials/header.html:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/misc/icons/ordered_list.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/contest_detail.page.styl:
--------------------------------------------------------------------------------
1 | .page--contest_detail
2 | .col--status
3 | width: rem(150px)
4 |
5 | .col--submit-at
6 | width: rem(150px)
7 |
8 | .col--problem
9 | border-left: 1px solid $table-border-color
10 |
11 | +mobile()
12 | .col--submit-at
13 | display: none
14 |
--------------------------------------------------------------------------------
/pages/homework_detail.page.styl:
--------------------------------------------------------------------------------
1 | .page--homework_detail
2 | .col--status
3 | width: rem(150px)
4 |
5 | .col--submit-at
6 | width: rem(150px)
7 |
8 | .col--problem
9 | border-left: 1px solid $table-border-color
10 |
11 | +mobile()
12 | .col--submit-at
13 | display: none
--------------------------------------------------------------------------------
/misc/icons/platform--mobile.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/shrink.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | image: gitpod/workspace-mongodb
2 | tasks:
3 | - init: yarn && yarn build
4 | vscode:
5 | extensions:
6 | - dbaeumer.vscode-eslint@2.1.3:1NRvj3UKNTNwmYjptmUmIw==
7 | - vscode-icons-team.vscode-icons@10.2.0:DrQUJPtFm0woSFiyitha8Q==
8 | - samuelcolvin.jinjahtml@0.12.0:lFI6zKplznEZPdWvFvRHwg==
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/misc/icons/enlarge.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/refresh.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/schedule--fill.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/contest_main.page.js:
--------------------------------------------------------------------------------
1 | import { NamedPage } from 'vj/misc/Page';
2 |
3 | const page = new NamedPage('contest_main', () => {
4 | // Contest Filter
5 | $('[name="filter-form"] [name="rule"]').change(() => {
6 | $('[name="filter-form"]').submit();
7 | });
8 | });
9 |
10 | export default page;
11 |
--------------------------------------------------------------------------------
/utils/i18n.js:
--------------------------------------------------------------------------------
1 | import substitute from './substitute';
2 |
3 | export default function i18n(str, ...params) {
4 | if (window.LOCALES && window.LOCALES[str]) {
5 | return substitute(window.LOCALES[str], params);
6 | }
7 | return substitute(str, params);
8 | }
9 |
10 | window.Hydro.utils.i18n = i18n;
11 |
--------------------------------------------------------------------------------
/utils/substitute.js:
--------------------------------------------------------------------------------
1 | export default function substitute(str, obj) {
2 | return str.replace(/\{([^{}]+)\}/g, (match, key) => {
3 | if (obj[key] !== undefined) {
4 | return String(obj[key]);
5 | }
6 | return `{${key}}`;
7 | });
8 | }
9 |
10 | window.Hydro.utils.substitute = substitute;
11 |
--------------------------------------------------------------------------------
/misc/icons/info.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/tag.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/utils/zip.js:
--------------------------------------------------------------------------------
1 | import 'streamsaver/examples/zip-stream';
2 |
3 | export const createZipStream = window.ZIP;
4 |
5 | export async function createZipBlob(underlyingSource) {
6 | return new Response(createZipStream(underlyingSource)).blob();
7 | }
8 |
9 | window.Hydro.utils.zip = { createZipStream, createZipBlob };
10 |
--------------------------------------------------------------------------------
/pages/ranking.page.styl:
--------------------------------------------------------------------------------
1 | .page--ranking
2 | .col--rank
3 | width: rem(70px)
4 | position: relative
5 |
6 | .col--user
7 | width: rem(200px)
8 | text-align: center
9 |
10 | .col--rp
11 | width: rem(80px)
12 | text-align: right
13 |
14 | +mobile()
15 | .col--bio
16 | display: none
17 |
--------------------------------------------------------------------------------
/templates/partials/training_default.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "_id": 1,
4 | "title": "最初的最初 - A+B Problem",
5 | "requireNids": [],
6 | "pids": [1000, "56c52ade2fefd90539000005"]
7 | },
8 | {
9 | "_id": 2,
10 | "title": "最初的进阶",
11 | "requireNids": [1],
12 | "pids": [1001, 1002]
13 | }
14 | ]
15 |
--------------------------------------------------------------------------------
/misc/icons/comment--text.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/crown.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/user_register_mail_sent.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/immersive.html" %}
2 | {% block content %}
3 |
4 |
{{ _('Sign Up') }}
5 |
6 | {{ _('Sign up mail has been sent to your email.') }}
7 |
8 |
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/misc/icons/account--circle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/comment--multiple.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/user_changemail_mail_sent.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/immersive.html" %}
2 | {% block content %}
3 |
4 |
{{ _('New Email') }}
5 |
6 | {{ _('Confirmation mail has been sent to your new email.') }}
7 |
8 |
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/templates/user_lostpass_mail_sent.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/immersive.html" %}
2 | {% block content %}
3 |
4 |
{{ _('Lost Password') }}
5 |
6 | {{ _('Password reset mail has been sent to your email.') }}
7 |
8 |
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/components/scratchpad/Editor.page.styl:
--------------------------------------------------------------------------------
1 | .ScratchpadMonacoEditor
2 | flex: 1
3 | position: relative
4 | overflow: hidden
5 |
6 | .cm-s-vjcm
7 | position: absolute
8 | left: 0
9 | top: 0
10 | width: 100%
11 | height: 100%
12 | font-family: $code-font-family
13 | font-size: rem($font-size-secondary)
14 | line-height: 1.2
15 |
--------------------------------------------------------------------------------
/misc/icons/google_plus.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/nothing.styl:
--------------------------------------------------------------------------------
1 | .nothing-placeholder
2 | padding: rem(80px 0)
3 | text-align: center
4 | font-size: rem($font-size-title)
5 | color: #AAA
6 |
7 | &.compact
8 | padding: rem(20px 0)
9 |
10 | .nothing-icon
11 | background: url(./puzzled_twd2.svg) no-repeat center center
12 | height: 184px
13 | margin-bottom: rem(20px)
14 |
--------------------------------------------------------------------------------
/templates/user_register_mail.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/mail.html" %}
2 | {% block title %}{{ _('Sign Up') }}{% endblock %}
3 | {% block content %}
4 | {{ _('Hello! You can click following link to sign up your Hydro account:') }}
5 | {{ url_prefix|safe }}{{ path|safe }}
6 | {% endblock %}
7 |
--------------------------------------------------------------------------------
/templates/components/contest.html:
--------------------------------------------------------------------------------
1 | {% import "components/user.html" as user with context %}
2 | {% macro render_time(tdoc_time) %}
3 | {{ datetimeSpan(tdoc_time, false, '%Y-%m-%d %H:%M')|safe }}
4 | {% endmacro %}
5 |
6 | {% macro render_duration(tdoc) %}
7 | {{ ((tdoc.endAt.getTime() - tdoc.beginAt.getTime()) /1000 / 3600)|round(1) }}
8 | {% endmacro %}
9 |
--------------------------------------------------------------------------------
/components/scratchpad/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import ui from './ui';
3 | import editor from './editor';
4 | import pretest from './pretest';
5 | import records from './records';
6 |
7 | const reducer = combineReducers({
8 | ui,
9 | editor,
10 | pretest,
11 | records,
12 | });
13 |
14 | export default reducer;
15 |
--------------------------------------------------------------------------------
/misc/icons/help2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/components/homework.html:
--------------------------------------------------------------------------------
1 | {% import "components/user.html" as user with context %}
2 | {% macro render_time(tdoc_time) %}
3 | {{ datetimeSpan(tdoc_time, false, '%Y-%m-%d %H:%M')|safe }}
4 | {% endmacro %}
5 |
6 | {% macro render_extension(tdoc) %}
7 | {{ ((tdoc['endAt'].getTime() - tdoc['penaltySince'].getTime())/ 1000 / 3600)|round(1) }}
8 | {% endmacro %}
9 |
--------------------------------------------------------------------------------
/templates/user_lostpass_mail.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/mail.html" %}
2 | {% block title %}{{ _('Lost Password') }}{% endblock %}
3 | {% block content %}
4 | {{ _('Hello, {0}! You can click following link to reset the password of your Hydro account:').format(uname) }}
5 | {{ url_prefix }}{{ url }}
6 | {% endblock %}
7 |
--------------------------------------------------------------------------------
/pages/problem_submit.page.js:
--------------------------------------------------------------------------------
1 | import { NamedPage } from 'vj/misc/Page';
2 |
3 | const page = new NamedPage('problem_submit', async () => {
4 | $(document).on('click', '[name="problem-sidebar__show-category"]', (ev) => {
5 | $(ev.currentTarget).hide();
6 | $('[name="problem-sidebar__categories"]').show();
7 | });
8 | });
9 |
10 | export default page;
11 |
--------------------------------------------------------------------------------
/templates/user_changemail_mail.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/mail.html" %}
2 | {% block title %}{{ _('Change Email') }}{% endblock %}
3 | {% block content %}
4 | {{ _('Hello, {0}! You can click following link to active your new email of your Hydro account:').format(uname) }}
5 | {{ url_prefix }}{{ url }}
6 | {% endblock %}
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/misc/icons/erase.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/discussion_node.page.js:
--------------------------------------------------------------------------------
1 | import { NamedPage } from 'vj/misc/Page';
2 |
3 | const page = new NamedPage('discussion_node', async () => {
4 | $(document).on('click', '[name="problem-sidebar__show-category"]', (ev) => {
5 | $(ev.currentTarget).hide();
6 | $('[name="problem-sidebar__categories"]').show();
7 | });
8 | });
9 |
10 | export default page;
11 |
--------------------------------------------------------------------------------
/pages/homework_main.page.styl:
--------------------------------------------------------------------------------
1 | $highlight-button-color = #F6DF45
2 |
3 | .page--homework_main
4 | .homework__title
5 | line-height: 1.2em
6 | margin-bottom: rem(10px)
7 | font-size: rem($font-size-title)
8 |
9 | .homework__info-attended
10 | color: $success-color
11 |
12 | .homework__date
13 | margin-right: rem(10px)
14 | white-space: nowrap
--------------------------------------------------------------------------------
/pages/problem_solution.page.js:
--------------------------------------------------------------------------------
1 | import { NamedPage } from 'vj/misc/Page';
2 |
3 | const page = new NamedPage('problem_solution', async () => {
4 | $(document).on('click', '[name="problem-sidebar__show-category"]', (ev) => {
5 | $(ev.currentTarget).hide();
6 | $('[name="problem-sidebar__categories"]').show();
7 | });
8 | });
9 |
10 | export default page;
11 |
--------------------------------------------------------------------------------
/pages/discussion_detail.page.js:
--------------------------------------------------------------------------------
1 | import { NamedPage } from 'vj/misc/Page';
2 |
3 | const page = new NamedPage('discussion_detail', async () => {
4 | $(document).on('click', '[name="problem-sidebar__show-category"]', (ev) => {
5 | $(ev.currentTarget).hide();
6 | $('[name="problem-sidebar__categories"]').show();
7 | });
8 | });
9 |
10 | export default page;
11 |
--------------------------------------------------------------------------------
/components/scratchpad/PanelButton.page.styl:
--------------------------------------------------------------------------------
1 | @import './var.inc.styl'
2 |
3 | .scratchpad__panel-button
4 | background: none
5 | border: 0
6 | line-height: rem($panel-header-height)
7 | margin: 0
8 | color: #FFF
9 | padding: rem(0 5px)
10 | cursor: pointer
11 | font-size: rem($font-size-small)
12 | outline: 0
13 |
14 | &:hover
15 | color: $tab-color
16 |
--------------------------------------------------------------------------------
/misc/icons/linkedin.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/user_delete_pending.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/immersive.html" %}
2 | {% block content %}
3 |
4 |
{{ _('Done') }}
5 |
6 | {{ _('Your account will be deleted in 7 days.') }}
7 | {{ _('You can cancel this at user setting page.') }}
8 |
9 |
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/misc/icons/bold.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/user--multiple.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/home_domain.page.styl:
--------------------------------------------------------------------------------
1 | .page--home_domain
2 | .col--icon
3 | width: 60px
4 | line-height: 1
5 |
6 | img
7 | border-radius: 50%
8 |
9 | .col--role
10 | width: rem(160px)
11 | border-left: 1px solid $table-border-color
12 |
13 | .col--action
14 | width: rem(135px)
15 |
16 | +mobile()
17 | .col--icon, .col--role
18 | display: none
19 |
--------------------------------------------------------------------------------
/components/star/star.page.styl:
--------------------------------------------------------------------------------
1 | .star
2 | border: 0
3 | background: none
4 | outline: 0
5 | padding: 0
6 | margin: 0
7 | color: gray
8 |
9 | .starred--show
10 | display: none
11 | .starred--hide
12 | display: block
13 |
14 | &.activated
15 | color: orange
16 | .starred--show
17 | display: block
18 | .starred--hide
19 | display: none
20 |
21 |
--------------------------------------------------------------------------------
/misc/icons/global.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/heart--outline.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/misc/icons/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/platform--unknown.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/filter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/block.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/platform--ios.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/utils/parseQueryString.js:
--------------------------------------------------------------------------------
1 | export default function parseQueryString(str) {
2 | const obj = {};
3 | (str || document.location.search)
4 | .replace(/(^\?)/, '')
5 | .split('&')
6 | .forEach((n) => {
7 | const [key, value] = n.split('=').map((v) => decodeURIComponent(v));
8 | obj[key] = value;
9 | });
10 | return obj;
11 | }
12 |
13 | window.Hydro.utils.parseQueryString = parseQueryString;
14 |
--------------------------------------------------------------------------------
/misc/icons/schedule.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/domain_permission.page.styl:
--------------------------------------------------------------------------------
1 | .page--domain_permission
2 | .col--p
3 | width: 80px
4 | text-align: center
5 | border-left: 1px solid $table-border-color
6 |
7 | td.col--description
8 | padding-left: rem(30px) !important
9 |
10 | .col--family
11 | line-height: 2
12 | color: #FFF
13 | background: lighten(saturate($primary-color, 70%), 30%)
14 | border-top: 2px solid $primary-color
15 |
--------------------------------------------------------------------------------
/misc/icons/insert--link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/partials/header_mobile.html:
--------------------------------------------------------------------------------
1 | {% if no_path_section %}
2 |
12 | {% endif %}
13 |
--------------------------------------------------------------------------------
/templates/problem_submit_tr.html:
--------------------------------------------------------------------------------
1 | {% import "components/record.html" as record with context %}
2 |
3 | {{ record.render_status_td(rdoc) }}
4 |
5 | {{ (rdoc.memory / 1000)|round(0, 'ceil')|int }} MB
6 |
7 |
8 | {{ rdoc.time|round|int }}ms
9 |
10 | {{ datetimeSpan(rdoc._id)|safe }}
11 |
12 |
--------------------------------------------------------------------------------
/misc/icons/award.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/debug.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/homework.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/sliders.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/twitter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/qq.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/templates/partials/problem_lucky.html:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/misc/icons/platform--chromeos.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/layout/home_base.html:
--------------------------------------------------------------------------------
1 | {% import "components/home.html" as home with context %}
2 | {% extends "layout/basic.html" %}
3 | {% block content %}
4 |
5 |
6 | {% block home_content %}{% endblock %}
7 |
8 |
9 | {{ home.render_sidebar() }}
10 | {% block home_sidebar %}{% endblock %}
11 |
12 |
13 | {% endblock %}
14 |
--------------------------------------------------------------------------------
/misc/icons/preview.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/wrench.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/partials/problem_default.md:
--------------------------------------------------------------------------------
1 | # Background
2 | Special for beginners, ^_^
3 |
4 | # Description
5 | Given two integers x and y, print the sum.
6 |
7 | # Format
8 |
9 | ## Input
10 | Two integers x and y, satisfying $0\leq x,y\leq 32767$ .
11 |
12 | ## Output
13 | One integer, the sum of x and y.
14 |
15 | # Sample 1
16 |
17 | ## Input
18 |
19 | ```
20 | 123 500
21 | ```
22 |
23 | ## Output
24 |
25 | ```
26 | 623
27 | ```
28 |
29 | # Limitation
30 | 1s, 1024KiB for each test case.
31 |
--------------------------------------------------------------------------------
/utils/mediaQuery.js:
--------------------------------------------------------------------------------
1 | export function isAbove(width) {
2 | if (window.matchMedia) {
3 | return window.matchMedia(`(min-width: ${width}px)`).matches;
4 | }
5 | return window.innerWidth >= width;
6 | }
7 |
8 | export function isBelow(width) {
9 | if (window.matchMedia) {
10 | return window.matchMedia(`(max-width: ${width}px)`).matches;
11 | }
12 | return window.innerWidth <= width;
13 | }
14 |
15 | window.Hydro.utils.isAbove = isAbove;
16 | window.Hydro.utils.isBelow = isBelow;
17 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/components/react/IconComponent.jsx:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/components/katex/katex.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
2 |
3 | const katexPage = new AutoloadPage('katexPage', () => {
4 | import('katex/dist/contrib/auto-render.min.js').then(({ default: katex }) => {
5 | function runKatex($containers) {
6 | $containers.get().forEach((container) => katex(container));
7 | }
8 | runKatex($('.typo'));
9 | $(document).on('vjContentNew', (e) => runKatex($(e.target).find('.typo')));
10 | });
11 | });
12 |
13 | export default katexPage;
14 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/pages/discussion_detail.page.styl:
--------------------------------------------------------------------------------
1 | .page--discussion_detail
2 |
3 | .topic__content
4 | margin: rem(20px 0)
5 |
6 | .profile__bg
7 | background-size: cover
8 | background-position: center center
9 | height: rem(60px)
10 | margin-bottom: rem(-32px)
11 |
12 | .sidebar-user-profile
13 | .media__body
14 | padding-left: rem(10px)
15 | padding-top: rem(20px)
16 |
17 | .user-profile-name
18 | font-size: 1.2rem
19 | color: #444
20 | vertical-align: middle
21 |
--------------------------------------------------------------------------------
/components/highlighter/highlighter.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
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 |
--------------------------------------------------------------------------------
/components/scratchpad/reducers/editor.js:
--------------------------------------------------------------------------------
1 | export default function reducer(state = {
2 | lang: Context.codeLang,
3 | code: Context.codeTemplate,
4 | }, action) {
5 | switch (action.type) {
6 | case 'SCRATCHPAD_EDITOR_UPDATE_CODE': {
7 | return {
8 | ...state,
9 | code: action.payload,
10 | };
11 | }
12 | case 'SCRATCHPAD_EDITOR_SET_LANG': {
13 | return {
14 | ...state,
15 | lang: action.payload,
16 | };
17 | }
18 | default:
19 | return state;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/misc/icons/feeling-lucky.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/unordered_list.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/icons/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/components/noscript_note.html:
--------------------------------------------------------------------------------
1 | {% macro render() %}
2 | {% if not isIE(handler.request.headers['user-agent']) %}
3 |
4 |
5 | {{ _('This page needs JavaScript to work.') }}
6 |
7 |
8 | {% else %}
9 |
10 |
11 | {{ _('This page needs JavaScript to work.') }}
12 |
13 |
14 | {% endif %}
15 | {% endmacro %}
16 |
--------------------------------------------------------------------------------
/components/form/form.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
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 |
--------------------------------------------------------------------------------
/components/menu/menu.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
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 |
--------------------------------------------------------------------------------
/templates/layout/wiki_base.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/basic.html" %}
2 | {% block content %}
3 |
4 |
5 | {% block wiki_content %}{% endblock %}
6 |
7 |
15 |
16 | {% endblock %}
--------------------------------------------------------------------------------
/components/scratchpad/PanelButtonComponent.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import classNames from 'classnames';
3 |
4 | export default function PanelButtonComponent(props) {
5 | const {
6 | className,
7 | children,
8 | ...rest
9 | } = props;
10 | const cn = classNames(className, 'scratchpad__panel-button');
11 | return (
12 | {children}
13 | );
14 | }
15 |
16 | PanelButtonComponent.propTypes = {
17 | className: PropTypes.string,
18 | children: PropTypes.node,
19 | };
20 |
--------------------------------------------------------------------------------
/templates/user_logout.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/immersive.html" %}
2 | {% block content %}
3 |
4 |
5 |
{{ _('Logout') }}
6 |
12 |
13 |
14 | {% endblock %}
15 |
--------------------------------------------------------------------------------
/utils/mongoId.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // https://github.com/andrasq/node-mongoid-js/blob/master/mongoid.js
3 |
4 | export function parse(idstring) {
5 | if (typeof idstring !== 'string') {
6 | idstring = String(idstring);
7 | }
8 | return {
9 | timestamp: parseInt(idstring.slice( 0, 0+8), 16),
10 | machineid: parseInt(idstring.slice( 8, 8+6), 16),
11 | pid: parseInt(idstring.slice(14, 14+4), 16),
12 | sequence: parseInt(idstring.slice(18, 18+6), 16),
13 | };
14 | }
15 |
16 | window.Hydro.utils.mongoId = parse;
17 |
--------------------------------------------------------------------------------
/pages/homework_scoreboard.page.styl:
--------------------------------------------------------------------------------
1 | .page--homework_scoreboard
2 | .col--rank
3 | width: rem(70px)
4 | border-right: 1px solid $table-border-color
5 |
6 | .col--user
7 | width: rem(140px)
8 |
9 | .col--total_score, .col--total_time_str, .col--solved_problems
10 | width: rem(100px)
11 | text-align: center
12 |
13 | .col--problem_detail
14 | border-left: 1px solid $table-border-color
15 | text-align: center
16 |
17 | +mobile()
18 | .col--user
19 | width: auto
20 |
21 | .col--problem_detail
22 | display: none
--------------------------------------------------------------------------------
/pages/record_detail.page.styl:
--------------------------------------------------------------------------------
1 | .page--record_detail
2 |
3 | .compiler-text
4 | padding: rem(20px 0)
5 | font-size: rem($font-size-small)
6 | &:empty
7 | display: none
8 |
9 | .col--case
10 | width: rem(70px)
11 |
12 | .col--memory, .col--time
13 | width: rem(120px)
14 |
15 | .col--case, .col--status
16 | border-right: 1px solid $table-border-color
17 |
18 | +mobile()
19 | .col--case
20 | display: none
21 |
22 | .col--time
23 | width: rem(70px)
24 |
25 | .col--memory
26 | width: rem(100px)
27 |
--------------------------------------------------------------------------------
/templates/layout/basic.html:
--------------------------------------------------------------------------------
1 | {% set layout_name = "basic" %}
2 | {% extends "layout/html5.html" %}
3 | {% block body %}
4 |
5 | {% include "partials/nav.html" %}
6 |
7 |
8 |
9 | {% include "partials/header_mobile.html" %}
10 | {% include "partials/header.html" %}
11 |
12 | {% block content %}{% endblock %}
13 |
14 | {% include "partials/footer.html" %}
15 |
16 |
17 | {% include "partials/login_dialog.html" %}
18 |
19 | {% endblock %}
20 |
--------------------------------------------------------------------------------
/components/react-splitpane/SplitPaneFillOverlayComponent.jsx:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/constant/setting.js:
--------------------------------------------------------------------------------
1 | import attachObjectMeta from './util/objectMeta';
2 |
3 | export const PRIVACY_PUBLIC = 0;
4 | export const PRIVACY_REGISTERED_ONLY = 1;
5 | export const PRIVACY_SECRET = 2;
6 | export const PRIVACY_RANGE = {
7 | [PRIVACY_PUBLIC]: 'Public',
8 | [PRIVACY_REGISTERED_ONLY]: 'Visible to registered users',
9 | [PRIVACY_SECRET]: 'Secret',
10 | };
11 | attachObjectMeta(PRIVACY_RANGE, 'intKey', true);
12 |
13 | export const FUNCTION_RANGE = {
14 | 0: 'Disabled',
15 | 1: 'Enabled',
16 | };
17 | attachObjectMeta(FUNCTION_RANGE, 'intKey', true);
18 |
--------------------------------------------------------------------------------
/pages/contest_scoreboard.page.styl:
--------------------------------------------------------------------------------
1 | .page--contest_scoreboard
2 | .col--rank
3 | width: rem(70px)
4 | border-right: 1px solid $table-border-color
5 |
6 | .col--user
7 | width: rem(140px)
8 |
9 | .col--total_score, .col--total_time_str, .col--solved_problems
10 | width: rem(100px)
11 | text-align: center
12 |
13 | .col--problem_detail
14 | border-left: 1px solid $table-border-color
15 | text-align: center
16 |
17 | +mobile()
18 | .col--user
19 | width: auto
20 |
21 | .col--problem_detail
22 | display: none
23 |
--------------------------------------------------------------------------------
/templates/components/sidemenu.html:
--------------------------------------------------------------------------------
1 | {% macro render_item(icon_name, page_name, args={}, label=none) %}
2 | {% set target_url = url(page_name, args) if page_name else '#' %}
3 |
15 | {% endmacro %}
16 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/misc/immersive.styl:
--------------------------------------------------------------------------------
1 | .layout--immersive
2 | #panel
3 | background: #80b2d5 url('immersive-background.jpg') no-repeat
4 | background-size: 1920px 1080px
5 | background-size: cover
6 | +retina()
7 | background-image: url('immersive-background@2x.jpg')
8 |
9 | .immersive--content
10 | color: $immersive-text-color
11 |
12 | h1, h2, h3, h4, h5, h6
13 | color: $immersive-header-color
14 |
15 | +above(rupture.mobile-cutoff)
16 | .immersive--center
17 | width: 200px
18 | margin: 0 auto
19 |
20 | .immersive--center
21 | h1
22 | text-align: center
23 | margin: 2em 0
24 |
--------------------------------------------------------------------------------
/misc/icons/settings.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/partials/problem_sidebar.html:
--------------------------------------------------------------------------------
1 | {% if tdoc %}
2 | {% if tdoc.docType == 60 %}
3 | {% include "partials/problem_sidebar_homework.html" %}
4 | {% if not owner_udoc %}
5 | {% set owner_udoc = udoc %}
6 | {% endif %}
7 | {% include "partials/homework_sidebar.html" %}
8 | {% else %}
9 | {% include "partials/problem_sidebar_contest.html" %}
10 | {% if not owner_udoc %}
11 | {% set owner_udoc = udoc %}
12 | {% endif %}
13 | {% include "partials/contest_sidebar.html" %}
14 | {% endif %}
15 | {% else %}
16 | {% include "partials/problem_sidebar_normal.html" %}
17 | {% endif %}
18 |
--------------------------------------------------------------------------------
/misc/icons/wechat.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/problem_statistics.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/basic.html" %}
2 | {% block content %}
3 |
4 |
5 |
6 |
9 |
10 | {{ _('Swx Template') }}
11 |
12 |
13 |
14 |
15 | {% set owner_udoc = udoc %}
16 | {% include "partials/problem_sidebar.html" %}
17 | {% endwith %}
18 |
19 |
20 | {% endblock %}
21 |
--------------------------------------------------------------------------------
/utils/emulateAnchorClick.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {Event} ev
3 | * @param {String} targetUrl
4 | * @param {Boolean} alwaysOpenInNewWindow
5 | */
6 | export default function emulateAnchorClick(ev, targetUrl, alwaysOpenInNewWindow = false) {
7 | let openInNewWindow;
8 | if (alwaysOpenInNewWindow) {
9 | openInNewWindow = true;
10 | } else {
11 | openInNewWindow = (ev.ctrlKey || ev.shiftKey || ev.metaKey);
12 | }
13 | if (openInNewWindow) {
14 | window.open(targetUrl);
15 | } else {
16 | window.location.href = targetUrl;
17 | }
18 | }
19 |
20 | window.Hydro.utils.emulateAnchorClick = emulateAnchorClick;
21 |
--------------------------------------------------------------------------------
/utils/tpl.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 | export default function tpl(pieces, ...substitutions) {
4 | let result = pieces[0];
5 | for (let i = 0; i < substitutions.length; ++i) {
6 | const subst = substitutions[i];
7 | let substHtml;
8 | if (typeof subst === 'object' && subst.templateRaw) {
9 | substHtml = subst.html;
10 | } else substHtml = _.escape(String(subst));
11 | result += substHtml + pieces[i + 1];
12 | }
13 | return result;
14 | }
15 |
16 | export function rawHtml(html) {
17 | return {
18 | templateRaw: true,
19 | html,
20 | };
21 | }
22 |
23 | global.Hydro.utils.tpl = tpl;
24 |
--------------------------------------------------------------------------------
/components/react/DomComponent.jsx:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/misc/icons/help.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/layout/immersive.html:
--------------------------------------------------------------------------------
1 | {% set layout_name = "immersive" %}
2 | {% set no_path_section = true %}
3 | {% extends "layout/html5.html" %}
4 | {% block body %}
5 |
6 | {% include "partials/nav.html" %}
7 |
8 |
9 |
10 | {% include "partials/header_mobile.html" %}
11 |
12 |
13 | {% block content %}{% endblock %}
14 |
15 |
16 | {% set show_topics = false %}
17 | {% include "partials/footer.html" %}
18 |
19 |
20 | {% include "partials/login_dialog.html" %}
21 |
22 | {% endblock %}
23 |
--------------------------------------------------------------------------------
/components/cmeditor/cmeditor.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
2 | import delay from 'vj/utils/delay';
3 | import CmEditor from '.';
4 |
5 | function runSubstitute($container) {
6 | const selector = ['textarea[data-markdown]'];
7 | $container.find(selector.join(', ')).get().forEach((element) => {
8 | CmEditor.getOrConstruct($(element));
9 | });
10 | }
11 |
12 | const cmEditorPage = new AutoloadPage('cmEditorPage', () => {
13 | runSubstitute($('body'));
14 | $(document).on('vjContentNew', async (e) => {
15 | await delay(0);
16 | runSubstitute($(e.target));
17 | });
18 | });
19 |
20 | export default cmEditorPage;
21 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/components/profile/profile.page.styl:
--------------------------------------------------------------------------------
1 | .user-profile-avatar
2 | border-radius: 50%
3 |
4 | .user-profile-badge
5 | display: inline-block
6 | font-size: rem(12px)
7 | padding: rem(3px 4px)
8 | line-height: 1
9 |
10 | &:hover
11 | text-decoration: none
12 |
13 | for n in (1..21)
14 | .user-profile-bg--{n}
15 | background-image: url('backgrounds/' + n + '.jpg')
16 |
17 | .user-profile-bg--thumbnail-{n}
18 | background-image: url('backgrounds/thumbnail/' + n + '.jpg')
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 |
--------------------------------------------------------------------------------
/components/datepicker/datepicker.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
2 |
3 | const datepickerPage = new AutoloadPage('datepickerPage', async () => {
4 | if ($('[data-pick-date]').length > 0) {
5 | await import('pickadate/lib/picker.date');
6 | $('[data-pick-date]').pickadate({
7 | format: 'yyyy-m-d',
8 | clear: false,
9 | });
10 | }
11 | if ($('[data-pick-time]').length > 0) {
12 | await import('pickadate/lib/picker.time');
13 | $('[data-pick-time]').pickatime({
14 | format: 'H:i',
15 | interval: 15,
16 | clear: false,
17 | });
18 | }
19 | });
20 |
21 | export default datepickerPage;
22 |
--------------------------------------------------------------------------------
/misc/icons/web.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/templates/record_detail_summary.html:
--------------------------------------------------------------------------------
1 |
2 | {{ _('Score') }}
3 | {{ rdoc['score'] }}
4 | {{ _('Total Time') }}
5 | {% if rdoc['status'] == STATUS.STATUS_TIME_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_MEMORY_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED %}≥{% endif %}{{ rdoc.time|round|int }}ms
6 | {{ _('Peak Memory') }}
7 | {% if rdoc['status'] == STATUS.STATUS_TIME_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_MEMORY_LIMIT_EXCEEDED or rdoc['status'] == STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED %}≥{% endif %}{{ size(rdoc.memory, 1024) }}
8 |
9 |
--------------------------------------------------------------------------------
/components/emoji/emojify.page.js:
--------------------------------------------------------------------------------
1 | import emojify from 'emojify.js';
2 |
3 | import { AutoloadPage } from 'vj/misc/Page';
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Moved to https://github.com/hydro-dev/Hydro/blob/master/packages/ui-default
2 |
3 | # Hydro-UI-Default
4 | [](https://app.fossa.com/projects/git%2Bgithub.com%2Fhydro-dev%2Fui-default?ref=badge_shield)
5 |
6 |
7 | Based on Vijos-UI-Framework
8 |
9 | See [vijos/vj4/vj4/ui/README.md](https://github.com/vijos/vj4/blob/master/vj4/ui/README.md)
10 |
11 |
12 | ## License
13 | [](https://app.fossa.com/projects/git%2Bgithub.com%2Fhydro-dev%2Fui-default?ref=badge_large)
14 |
--------------------------------------------------------------------------------
/components/form/textbox.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
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 |
--------------------------------------------------------------------------------
/constant/model.js:
--------------------------------------------------------------------------------
1 | import attachObjectMeta from './util/objectMeta';
2 |
3 | export const USER_GENDER_MALE = 0;
4 | export const USER_GENDER_FEMALE = 1;
5 | export const USER_GENDER_OTHER = 2;
6 | export const USER_GENDERS = [USER_GENDER_MALE, USER_GENDER_FEMALE, USER_GENDER_OTHER];
7 | export const USER_GENDER_RANGE = {
8 | [USER_GENDER_MALE]: 'Boy ♂',
9 | [USER_GENDER_FEMALE]: 'Girl ♀',
10 | [USER_GENDER_OTHER]: 'Other',
11 | };
12 | attachObjectMeta(USER_GENDER_RANGE, 'intKey', true);
13 | export const USER_GENDER_ICONS = {
14 | [USER_GENDER_MALE]: '♂',
15 | [USER_GENDER_FEMALE]: '♀',
16 | [USER_GENDER_OTHER]: '?',
17 | };
18 | attachObjectMeta(USER_GENDER_ICONS, 'intKey', true);
19 |
--------------------------------------------------------------------------------
/misc/icons/platform--android.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/pages/home_security.page.styl:
--------------------------------------------------------------------------------
1 | .page--home_security
2 |
3 | .sessionlist
4 | font-size: rem($font-size-secondary)
5 |
6 | .sessionlist__icon
7 | font-size: rem(30px)
8 | color: #888
9 | margin: rem(0 20px)
10 |
11 | .sessionlist__item
12 | margin: rem(10px 0)
13 | padding: rem(10px 0)
14 | border-bottom: 1px solid #DDD
15 |
16 | input
17 | margin: 0
18 |
19 | .sessionlist__current-session
20 | line-height: rem($form-control-height)
21 | color: green
22 |
23 | +mobile()
24 | .sessionlist__item
25 | .media__left
26 | display: none
27 |
28 | .media__body, .media__right
29 | display: block
30 | padding: 0
31 | margin: 0
32 |
--------------------------------------------------------------------------------
/templates/error.html:
--------------------------------------------------------------------------------
1 | {% set no_path_section = true %}
2 | {% extends "layout/basic.html" %}
3 | {% block content %}
4 |
5 |
8 |
9 |
{{ _('Oops!') }}
10 |
{{ _(error.message).formatFromArray(error.params) }}
11 |
{{ _('Technical Information') }}:
12 |
{{ _('Type') }}: {{ error.code }}
13 |
{{ _('Arguments') }}:
14 |
15 | {%- for param in error.params -%}
16 | {{ param }}
17 | {%- endfor -%}
18 |
19 |
20 |
21 |
22 | {% endblock %}
23 |
--------------------------------------------------------------------------------
/components/training/training.page.styl:
--------------------------------------------------------------------------------
1 | .training-status--icon
2 | display: inline-block
3 | width: 1.15em
4 | for key, value in $training-status-icon
5 | &.{key}:before
6 | content: value
7 | color: $training-status-color[key]
8 |
9 | .training-status--text
10 | for key, value in $training-status-color
11 | &.{key}
12 | color: value
13 |
14 | .training-section-status--icon
15 | display: inline-block
16 | width: 1.15em
17 | for key, value in $training-section-status-icon
18 | &.{key}:before
19 | content: value
20 | color: $training-status-color[key]
21 |
22 | .training-section-status--text
23 | for key, value in $training-section-status-color
24 | &.{key}
25 | color: value
26 |
--------------------------------------------------------------------------------
/misc/icons/platform--mac.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/modules.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import _ from 'lodash';
3 |
4 | import './utils/delay';
5 | import './utils/emulateAnchorClick';
6 | import './utils/i18n';
7 | import './utils/loadReactRedux';
8 | import './utils/mediaQuery';
9 | import './utils/mongoId';
10 | import './utils/parseQueryString';
11 | import './utils/pipeStream';
12 | import './utils/pjax';
13 | import './utils/request';
14 | import './utils/slide';
15 | import './utils/substitute';
16 | import './utils/tpl';
17 | import './utils/zIndexManager';
18 | import './utils/zip';
19 |
20 | import './components/autocomplete';
21 | import './components/dialog';
22 | import './components/notification';
23 | import './components/nprogress';
24 |
25 | window.node_modules = { _, $ };
26 |
--------------------------------------------------------------------------------
/templates/home_messages.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/home_base.html" %}
2 | {% block home_content %}
3 |
4 |
5 |
6 |
{{ _('Select User') }}
7 |
8 |
9 |
10 |
11 | {{ _('Username / UID') }}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {{ noscript_note.render() }}
21 |
22 |
23 |
24 | {% endblock %}
25 |
--------------------------------------------------------------------------------
/templates/partials/discussion_nodes_widget.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | {%- for category, nodes in vnodes|groupby('content') -%}
7 |
8 | {{ category }}
9 |
10 | {%- for node in nodes -%}
11 | {{ node.docId }}
12 | {%- endfor -%}
13 |
14 |
15 | {%- endfor -%}
16 |
17 |
--------------------------------------------------------------------------------
/pages/record_detail.page.js:
--------------------------------------------------------------------------------
1 | import { NamedPage } from 'vj/misc/Page';
2 |
3 | const page = new NamedPage('record_detail', async () => {
4 | const { default: SockJs } = await import('../components/socket');
5 | const { DiffDOM } = await import('diff-dom');
6 |
7 | const sock = new SockJs(Context.socketUrl);
8 | const dd = new DiffDOM();
9 | sock.onmessage = (message) => {
10 | const msg = JSON.parse(message.data);
11 | const newStatus = $(msg.status_html);
12 | const oldStatus = $('#status');
13 | dd.apply(oldStatus[0], dd.diff(oldStatus[0], newStatus[0]));
14 | const newSummary = $(msg.summary_html);
15 | const oldSummary = $('#summary');
16 | dd.apply(oldSummary[0], dd.diff(oldSummary[0], newSummary[0]));
17 | };
18 | });
19 |
20 | export default page;
21 |
--------------------------------------------------------------------------------
/components/scratchpad/reducers/pretest.js:
--------------------------------------------------------------------------------
1 | export default function reducer(state = {
2 | input: '',
3 | output: '',
4 | rid: null,
5 | }, action) {
6 | if (action.type === 'SCRATCHPAD_PRETEST_DATA_CHANGE') {
7 | const { type, value } = action.payload;
8 | return {
9 | ...state,
10 | [type]: value,
11 | };
12 | }
13 | if (action.type === 'SCRATCHPAD_RECORDS_PUSH') {
14 | const { rdoc } = action.payload;
15 | if (rdoc._id === state.rid && rdoc.testCases[0]) {
16 | return {
17 | ...state,
18 | output: rdoc.testCases[0].message,
19 | };
20 | }
21 | }
22 | if (action.type === 'SCRATCHPAD_POST_PRETEST_FULFILLED') {
23 | return {
24 | ...state,
25 | rid: action.payload.rid,
26 | };
27 | }
28 | return state;
29 | }
30 |
--------------------------------------------------------------------------------
/templates/components/record.html:
--------------------------------------------------------------------------------
1 | {% macro render_status_td(rdoc, rid_key='_id') %}
2 |
3 |
9 | {% if rdoc.status == STATUS.STATUS_JUDGING %}
10 |
13 | {% endif %}
14 |
15 | {% endmacro %}
16 |
--------------------------------------------------------------------------------
/templates/manage_user_import.html:
--------------------------------------------------------------------------------
1 | {% import "components/user.html" as user %}
2 | {% extends "manage_base.html" %}
3 | {% block manage_content %}
4 |
5 |
8 |
9 | {{ noscript_note.render() }}
10 |
11 | {{ form.form_textarea({
12 | columns:null,
13 | label:'Users',
14 | name:'users'
15 | }) }}
16 |
17 | {{ _('Preview') }}
18 | {{ _('Submit') }}
19 |
20 |
21 |
22 | {% endblock %}
23 |
--------------------------------------------------------------------------------
/utils/pipeStream.js:
--------------------------------------------------------------------------------
1 | export default async function pipeStream(read, write, abort) {
2 | if (window.WritableStream && read.pipeTo) {
3 | const abortController = new AbortController();
4 | if (abort) abort.abort = abortController.abort.bind(abortController);
5 | await read.pipeTo(write, abortController);
6 | } else {
7 | const writer = write.getWriter();
8 | if (abort) abort.abort = writer.abort.bind(writer);
9 | const reader = read.getReader();
10 | // eslint-disable-next-line no-constant-condition
11 | while (1) {
12 | const readResult = await reader.read();
13 | if (readResult.done) {
14 | writer.close();
15 | break;
16 | } else writer.write(readResult.value);
17 | }
18 | }
19 | }
20 |
21 | window.Hydro.utils.pipeStream = pipeStream;
22 |
--------------------------------------------------------------------------------
/templates/partials/path.html:
--------------------------------------------------------------------------------
1 |
2 | {% if not no_path_section %}
3 |
18 | {% endif %}
19 |
20 |
--------------------------------------------------------------------------------
/templates/problem_upload.html:
--------------------------------------------------------------------------------
1 | {% extends 'layout/simple.html' %}
2 | {% block body %}
3 |
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/templates/partials/category.html:
--------------------------------------------------------------------------------
1 |
2 | {%- for category, sub_categories in model.system.get('problem.categories')|parseYaml -%}
3 |
4 |
7 | {% if sub_categories | length > 0 %}
8 |
9 | {%- for sub_category in sub_categories -%}
10 |
11 | {{ sub_category }}
12 |
13 | {%- endfor -%}
14 |
15 | {% endif %}
16 |
17 | {%- endfor -%}
18 |
--------------------------------------------------------------------------------
/components/scratchpad/PanelComponent.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 | import SplitPaneFillOverlay from 'vj/components/react-splitpane/SplitPaneFillOverlayComponent';
5 |
6 | export default function PanelComponent(props) {
7 | const {
8 | title,
9 | className,
10 | children,
11 | ...rest
12 | } = props;
13 | const cn = classNames(className, 'flex-col');
14 | return (
15 |
16 | {title}
17 | {children}
18 |
19 | );
20 | }
21 |
22 | PanelComponent.propTypes = {
23 | title: PropTypes.node,
24 | className: PropTypes.string,
25 | children: PropTypes.node,
26 | };
27 |
--------------------------------------------------------------------------------
/misc/icons/template/webicon.styl:
--------------------------------------------------------------------------------
1 | @font-face
2 | font-family: $icon-font-name
3 | src: url('./{{ options.fontName }}.eot')
4 | src: url('./{{ options.fontName }}.eot#iefix') format('embedded-opentype'),
5 | url('./{{ options.fontName }}.woff2') format('woff2'),
6 | url('./{{ options.fontName }}.woff') format('woff'),
7 | url('./{{ options.fontName }}.ttf') format('truetype')
8 | font-weight: normal
9 | font-style: normal
10 |
11 | .icon
12 | font-family: $icon-font-name !important
13 | speak: none
14 | font-style: normal
15 | font-weight: normal
16 | font-variant: normal
17 | text-transform: none
18 | line-height: 1
19 | -webkit-font-smoothing: antialiased
20 | -moz-osx-font-smoothing: grayscale
21 |
22 | {% for icon in glyphs %}
23 | .icon-{{ icon.name }}:before
24 | content: $icon-{{ icon.name }}
25 | {% endfor %}
26 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/pages/domain_join_applications.page.js:
--------------------------------------------------------------------------------
1 | import { NamedPage } from 'vj/misc/Page';
2 | import * as domainEnum from 'vj/constant/domain';
3 |
4 | const page = new NamedPage('domain_join_applications', () => {
5 | const $role = $('[name="role"]');
6 | const $expire = $('[name="expire"]');
7 | const $code = $('[name="invitationCode"]');
8 | function updateFormState() {
9 | const method = parseInt($('[name="method"]').val(), 10);
10 | $role.prop('disabled', method === domainEnum.JOIN_METHOD_NONE).trigger('vjFormDisableUpdate');
11 | $expire.prop('disabled', method === domainEnum.JOIN_METHOD_NONE).trigger('vjFormDisableUpdate');
12 | $code.prop('disabled', method !== domainEnum.JOIN_METHOD_CODE).trigger('vjFormDisableUpdate');
13 | }
14 | updateFormState();
15 | $('[name="method"]').change(() => updateFormState());
16 | });
17 |
18 | export default page;
19 |
--------------------------------------------------------------------------------
/templates/components/home.html:
--------------------------------------------------------------------------------
1 | {% import "components/sidemenu.html" as sidemenu with context %}
2 | {% macro render_sidebar() %}
3 |
4 |
14 |
15 | {% endmacro %}
16 |
--------------------------------------------------------------------------------
/templates/user_lostpass.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/immersive.html" %}
2 | {% block content %}
3 |
4 |
5 |
{{ _('Lost Password') }}
6 |
20 |
21 |
22 | {% endblock %}
23 |
--------------------------------------------------------------------------------
/pages/training_detail.page.styl:
--------------------------------------------------------------------------------
1 | .page--training_detail
2 | .col--status
3 | width: rem(150px)
4 | position: relative
5 |
6 | .col--submit_n, .col--ac_rate, .col--difficulty
7 | width: rem(70px)
8 | text-align: center
9 |
10 | .col--status
11 | border-right: 1px solid $table-border-color
12 |
13 | .col--submit_n, .col--difficulty
14 | border-left: 1px solid $table-border-color
15 |
16 | +mobile()
17 | .col--status,
18 | .col--submit_n,
19 | .col--difficulty
20 | display: none
21 |
22 | .col--ac_rate
23 | border-left: 1px solid $table-border-color
24 |
25 | .training__section
26 | &.invalid
27 | .training__problems
28 | opacity: 0.6
29 |
30 | &.collapsed .collapsed--hidden,
31 | &.expanded .expanded--hidden
32 | display: none
33 |
34 | &.collapsed .training__section__detail
35 | display: none
36 |
--------------------------------------------------------------------------------
/components/scratchpad/DataInputComponent.jsx:
--------------------------------------------------------------------------------
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 |
26 | );
27 | }
28 |
29 | DataInputComponent.propTypes = {
30 | title: PropTypes.node,
31 | value: PropTypes.string,
32 | onChange: PropTypes.func,
33 | className: PropTypes.string,
34 | };
35 |
--------------------------------------------------------------------------------
/pages/user_detail.page.js:
--------------------------------------------------------------------------------
1 | import base64 from 'base-64';
2 | import Clipboard from 'clipboard';
3 |
4 | import { NamedPage } from 'vj/misc/Page';
5 | import substitute from 'vj/utils/substitute';
6 | import Notification from 'vj/components/notification';
7 | import i18n from 'vj/utils/i18n';
8 |
9 | const page = new NamedPage('user_detail', async () => {
10 | $('[name="profile_contact_copy"]').get().forEach((el) => {
11 | const data = $(el).attr('data-content');
12 | const decoded = base64.decode(data);
13 | const clip = new Clipboard(el, { text: () => decoded });
14 | clip.on('success', () => {
15 | Notification.success(substitute(i18n('"{data}" copied to clipboard!'), { data: decoded }), 2000);
16 | });
17 | clip.on('error', () => {
18 | Notification.error(substitute(i18n('Copy "{data}" failed :('), { data: decoded }));
19 | });
20 | });
21 | });
22 |
23 | export default page;
24 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/templates/user_register.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/immersive.html" %}
2 | {% block content %}
3 |
4 |
5 |
{{ _('Sign Up') }}
6 |
21 |
22 |
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/misc/slideout.styl:
--------------------------------------------------------------------------------
1 | html
2 | height: 100%
3 |
4 | body
5 | width: 100%
6 |
7 | #panel
8 | display: flex
9 | flex-direction: column
10 |
11 | body, #panel
12 | min-height: 100%
13 | min-height: 100vh
14 |
15 | .slideout-panel
16 | position: relative
17 | z-index: 1
18 |
19 | .slideout-overlay
20 | display: none
21 | position: absolute
22 | left: 0
23 | top: 0
24 | width: 100%
25 | height: 100%
26 | z-index: 99999
27 |
28 | +mobile()
29 | .nav.slideout-menu
30 | position: fixed
31 | top: 0
32 | bottom: 0
33 | right: 0
34 | left: auto
35 | z-index: 0
36 | width: 200px
37 | overflow-y: auto
38 | -webkit-overflow-scrolling: touch
39 | display: none
40 | height: auto
41 | min-height: 100vh
42 |
43 | .slideout-open,
44 | .slideout-open body,
45 | .slideout-open .slideout-panel
46 | overflow: hidden
47 |
48 | .slideout-open .slideout-menu
49 | display: block !important
50 |
--------------------------------------------------------------------------------
/templates/manage_base.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/basic.html" %}
2 | {% block content %}
3 |
4 |
5 | {% block manage_content %}{% endblock %}
6 |
7 |
24 |
25 | {% endblock %}
26 |
--------------------------------------------------------------------------------
/components/tooltip/tooltip.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
2 | import Tooltip from './Tooltip';
3 |
4 | function getClosestTooltipElement(element) {
5 | const MAX_DEPTH = 3;
6 | for (let i = 0, e = element; i < MAX_DEPTH && e !== null && e !== document; e = e.parentNode, i++) {
7 | if (e.getAttribute('data-tooltip')) {
8 | return e;
9 | }
10 | }
11 | return null;
12 | }
13 |
14 | const tooltipPage = new AutoloadPage('tooltipPage', () => {
15 | $(document).on('mouseover', (e) => {
16 | const element = getClosestTooltipElement(e.target);
17 | if (!element) {
18 | return;
19 | }
20 | const $element = $(element);
21 | const options = {};
22 | if ($element.attr('data-tooltip-pos')) {
23 | options.position = $element.attr('data-tooltip-pos');
24 | }
25 | const tooltip = Tooltip.getOrConstruct($element, options);
26 | tooltip.open();
27 | });
28 | });
29 |
30 | export default tooltipPage;
31 |
--------------------------------------------------------------------------------
/components/messagepad/MessageComponent.jsx:
--------------------------------------------------------------------------------
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 |
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 |
--------------------------------------------------------------------------------
/constant/domain.js:
--------------------------------------------------------------------------------
1 | import attachObjectMeta from './util/objectMeta';
2 |
3 | export const JOIN_METHOD_NONE = 0;
4 | export const JOIN_METHOD_ALL = 1;
5 | export const JOIN_METHOD_CODE = 2;
6 | export const JOIN_METHOD_RANGE = {
7 | [JOIN_METHOD_NONE]: 'No user is allowed to join this domain',
8 | [JOIN_METHOD_ALL]: 'Any user is allowed to join this domain',
9 | [JOIN_METHOD_CODE]: 'Any user is allowed to join this domain with an invitation code',
10 | };
11 | attachObjectMeta(JOIN_METHOD_RANGE, 'intKey', true);
12 |
13 | export const JOIN_EXPIRATION_KEEP_CURRENT = 0;
14 | export const JOIN_EXPIRATION_UNLIMITED = -1;
15 |
16 | export const JOIN_EXPIRATION_RANGE = {
17 | [JOIN_EXPIRATION_KEEP_CURRENT]: 'Keep current expiration',
18 | 3: 'In 3 hours',
19 | 24: 'In 1 day',
20 | [24 * 3]: 'In 3 days',
21 | [24 * 7]: 'In 1 week',
22 | [24 * 30]: 'In 1 month',
23 | [JOIN_EXPIRATION_UNLIMITED]: 'Never expire',
24 | };
25 | attachObjectMeta(JOIN_EXPIRATION_RANGE, 'intKey', true);
26 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on:
3 | push:
4 | branches:
5 | - master
6 | pull_request:
7 | branches:
8 | - master
9 | jobs:
10 | build:
11 | name: Build
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 | - uses: actions/setup-node@v1
16 | with:
17 | node-version: '14.x'
18 | - run: |
19 | yarn
20 | yarn build:production
21 | - uses: JS-DevTools/npm-publish@v1
22 | if: ${{ github.event_name == 'push' }}
23 | with:
24 | token: ${{ secrets.NPM_TOKEN }}
25 | - if: steps.publish.outputs.type != 'none'
26 | run: |
27 | rm -r public
28 | yarn build
29 | node build/ver --dev
30 | env:
31 | INPUT_TOKEN: ''
32 | - uses: JS-DevTools/npm-publish@v1
33 | if: ${{ github.event_name == 'push' }}
34 | with:
35 | token: ${{ secrets.NPM_TOKEN }}
36 | tag: dev
37 | env:
38 | INPUT_TOKEN: ''
39 |
--------------------------------------------------------------------------------
/misc/icons/link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/training_edit.page.js:
--------------------------------------------------------------------------------
1 | import { NamedPage } from 'vj/misc/Page';
2 | import { ConfirmDialog } from 'vj/components/dialog';
3 | import tpl from 'vj/utils/tpl';
4 | import i18n from 'vj/utils/i18n';
5 | import request from 'vj/utils/request';
6 |
7 | export default new NamedPage('training_edit', () => {
8 | let confirmed = false;
9 | $(document).on('click', '[name="operation"]', (ev) => {
10 | ev.preventDefault();
11 | if (confirmed) {
12 | return request.post('.', { operation: 'delete' }).then((res) => {
13 | window.location.href = res.url;
14 | });
15 | }
16 | const message = 'Confirm deleting this training? Its status will be deleted as well.';
17 | return new ConfirmDialog({
18 | $body: tpl`
19 |
20 |
${i18n(message)}
21 |
`,
22 | }).open().then((action) => {
23 | if (action !== 'yes') return;
24 | confirmed = true;
25 | ev.target.click();
26 | });
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/components/scratchpad/reducers/records.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 | export default function reducer(state = {
4 | rows: [],
5 | items: {},
6 | }, action) {
7 | switch (action.type) {
8 | case 'SCRATCHPAD_RECORDS_LOAD_SUBMISSIONS_FULFILLED': {
9 | const { rdocs } = action.payload;
10 | return {
11 | ...state,
12 | rows: _.map(rdocs, '_id'),
13 | items: _.keyBy(rdocs, '_id'),
14 | };
15 | }
16 | case 'SCRATCHPAD_RECORDS_PUSH': {
17 | const { rdoc } = action.payload;
18 | const rows = [...state.rows];
19 | if (!rows.includes(rdoc._id)) {
20 | return {
21 | ...state,
22 | rows: [rdoc._id, ...state.rows],
23 | items: {
24 | ...state.items,
25 | [rdoc._id]: rdoc,
26 | },
27 | };
28 | }
29 | return {
30 | ...state,
31 | items: {
32 | ...state.items,
33 | [rdoc._id]: rdoc,
34 | },
35 | };
36 | }
37 | default:
38 | return state;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/pages/manage_user_import.page.js:
--------------------------------------------------------------------------------
1 | import { NamedPage } from 'vj/misc/Page';
2 | import Notification from 'vj/components/notification';
3 | import i18n from 'vj/utils/i18n';
4 | import request from 'vj/utils/request';
5 | import delay from 'vj/utils/delay';
6 |
7 | const page = new NamedPage('manage_user_import', () => {
8 | async function post(draft) {
9 | try {
10 | const res = await request.post('', {
11 | users: $('[name="users"]').val(),
12 | draft,
13 | });
14 | if (!draft) {
15 | Notification.success(i18n('Created {0} users.', res.users.length));
16 | await delay(2000);
17 | window.location.reload();
18 | } else {
19 | $('[name="messages"]').text(res.messages.join('\n'));
20 | }
21 | } catch (error) {
22 | Notification.error(error.message);
23 | }
24 | }
25 |
26 | $('[name="preview"]').click(() => post(true));
27 | $('[name="submit"]').click(() => post(false));
28 | });
29 |
30 | export default page;
31 |
--------------------------------------------------------------------------------
/templates/fs_upload.html:
--------------------------------------------------------------------------------
1 | {% extends 'layout/simple.html' %}
2 | {% block body %}
3 | {% if fdoc %}
4 | {{ _('Uploaded file info') }}:
5 | {{ _('ID') }}: {{ fdoc['_id'] }}
6 | {{ _('Userfile doc_id') }}: {{ ufid }}
7 | {{ _('MD5') }}: {{ fdoc['md5'] }}
8 | {{ _('URL') }}: {{ model.file.url(fdoc) }}
9 | {% endif %}
10 |
21 | {% endblock %}
22 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/pages/manage_script.page.js:
--------------------------------------------------------------------------------
1 | import { NamedPage } from 'vj/misc/Page';
2 | import Notification from 'vj/components/notification';
3 | import { ActionDialog } from 'vj/components/dialog';
4 |
5 | import request from 'vj/utils/request';
6 |
7 | const page = new NamedPage('manage_script', () => {
8 | const runScriptDialog = new ActionDialog({
9 | $body: $('.dialog__body--run-script > div'),
10 | });
11 | runScriptDialog.clear = function () {
12 | this.$dom.find('[name="args"]').val('');
13 | return this;
14 | };
15 |
16 | window.runScript = async function (id) {
17 | const action = await runScriptDialog.clear().open();
18 | if (action !== 'ok') return;
19 | const args = runScriptDialog.$dom.find('[name="args"]').val();
20 | try {
21 | const res = await request.post('', {
22 | args,
23 | id,
24 | });
25 | window.location.href = res.url;
26 | } catch (error) {
27 | Notification.error(error.message);
28 | }
29 | };
30 | });
31 |
32 | export default page;
33 |
--------------------------------------------------------------------------------
/theme/default.js:
--------------------------------------------------------------------------------
1 | import 'normalize.css/normalize.css';
2 | import 'codemirror/lib/codemirror.css';
3 | import 'vj-simplemde/src/css/simplemde.css';
4 | import 'pickadate/lib/themes/classic.css';
5 | import 'pickadate/lib/themes/classic.date.css';
6 | import 'pickadate/lib/themes/classic.time.css';
7 | import 'katex/dist/katex.min.css';
8 |
9 | import 'vj/misc/float.styl';
10 | import 'vj/misc/typography.styl';
11 | import 'vj/misc/textalign.styl';
12 | import 'vj/misc/grid.styl';
13 | import 'vj/misc/slideout.styl';
14 |
15 | import 'vj/misc/.iconfont/webicon.styl';
16 | import 'vj/misc/immersive.styl';
17 | import 'vj/misc/structure.styl';
18 | import 'vj/misc/section.styl';
19 | import 'vj/misc/nothing.styl';
20 |
21 | import 'vj/components/cmeditor/cmeditor.styl';
22 | import 'vj/components/datepicker/datepicker.styl';
23 | import 'vj/components/katex/katex.styl';
24 |
25 | // load all page stylesheets
26 | const pageStyleReq = require.context('../', true, /\.page\.styl$/i);
27 | pageStyleReq.keys().map((key) => pageStyleReq(key).default);
28 |
--------------------------------------------------------------------------------
/pages/error.page.styl:
--------------------------------------------------------------------------------
1 | $error_width = 650px
2 | $twd2_width = 270px
3 |
4 | .page--error
5 | .error__icon-container,
6 | .error__text-container
7 | text-align: center
8 | margin: rem(20px 0)
9 |
10 | .error__text-container
11 | color: #777
12 | line-height: 1.5
13 |
14 | h1
15 | font-size: 4rem
16 | color: #999
17 | margin-bottom: rem(20px)
18 |
19 | li
20 | list-style: decimal inside
21 |
22 | .error__twd2
23 | background: url('error/twd2.svg') no-repeat
24 | width: $twd2_width
25 | height: $twd2_width
26 | display: inline-block
27 |
28 | +above(rupture.mobile-cutoff)
29 | .error__container
30 | width: rem(700px)
31 | margin: rem(50px) auto
32 | position: relative
33 |
34 | .error__icon-container
35 | float: left
36 | width: $twd2_width
37 |
38 | .error__text-container
39 | left: rem($twd2_width + 50px)
40 | position: absolute
41 | top: 50%
42 | transform: translateY(-50%)
43 | text-align: left
44 | margin: 0
45 |
--------------------------------------------------------------------------------
/components/hitokoto/index.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
2 | import request from 'vj/utils/request';
3 | import i18n from 'vj/utils/i18n';
4 | import tpl from 'vj/utils/tpl';
5 |
6 | const hitokotoPage = new AutoloadPage('hitokotoPage', () => {
7 | function getHitokoto($containers) {
8 | $containers.get().forEach((container) => {
9 | request.get('https://v1.hitokoto.cn?c=a&c=b&c=c&c=d&c=e&c=f')
10 | .then((hitokoto) => {
11 | const dom = $(tpl`${hitokoto.hitokoto}
`);
12 | dom.appendTo(container);
13 | })
14 | .catch((e) => {
15 | console.error(e);
16 | const dom = $(tpl`${i18n('Cannot get hitokoto.')}
`);
17 | dom.appendTo(container);
18 | });
19 | });
20 | }
21 | if ($('[name="hitokoto"]')) getHitokoto($('[name="hitokoto"]'));
22 | $(document).on('vjContentNew', (e) => {
23 | const elem = $(e.target).find('[name="hitokoto"]');
24 | if (elem.get) getHitokoto(elem);
25 | });
26 | });
27 |
28 | export default hitokotoPage;
29 |
--------------------------------------------------------------------------------
/components/star/star.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
2 | import request from 'vj/utils/request';
3 |
4 | function setStarButtonState($starButton, star) {
5 | if (star) {
6 | $starButton.addClass('activated');
7 | } else {
8 | $starButton.removeClass('activated');
9 | }
10 | }
11 |
12 | const starPage = new AutoloadPage('starPage', () => {
13 | $(document).on('click', '.star', (ev) => {
14 | const $button = $(ev.currentTarget);
15 | const currentState = $button.hasClass('activated');
16 | const $form = $button.closest('form');
17 | $form.find('[name="operation"]').val(currentState ? 'unstar' : 'star');
18 | setStarButtonState($button, !currentState);
19 | request
20 | .post($form.attr('action'), $form)
21 | .then((data) => {
22 | setStarButtonState($button, data.star);
23 | })
24 | .catch(() => {
25 | // TODO: notify failure
26 | setStarButtonState($button, currentState);
27 | });
28 | return false;
29 | });
30 | });
31 |
32 | export default starPage;
33 |
--------------------------------------------------------------------------------
/templates/problem_import.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/basic.html" %}
2 | {% block content %}
3 |
4 |
17 |
18 |
19 |
20 |
{{ _('What is this?') }}
21 |
{{ _('With this feature, you can import problems that you can view from a site to here. Their title, content, tags and categories will be imported.') }}
22 |
23 |
24 |
25 |
26 | {% endblock %}
27 |
--------------------------------------------------------------------------------
/components/menu/menu-heading.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
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) return;
10 | let $menu = $target.children('.menu');
11 | if ($menu.length === 0) {
12 | $menu = $(tpl``).appendTo($target);
13 | $target.children('.menu__link').addClass('expandable');
14 | }
15 | $container.find('[data-heading]').get().forEach((heading) => {
16 | const $heading = $(heading);
17 | $(tpl`
18 |
23 | `).appendTo($menu);
24 | });
25 | });
26 | });
27 |
28 | export default menuHeadingPage;
29 |
--------------------------------------------------------------------------------
/templates/user_lostpass_with_code.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/immersive.html" %}
2 | {% block content %}
3 |
4 |
{{ _('Lost Password') }}
5 |
29 |
30 | {% endblock %}
31 |
--------------------------------------------------------------------------------
/templates/contest_boardcast.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/basic.html" %}
2 | {% block content %}
3 |
4 |
27 |
28 | {% include 'components/md_hint.html' %}
29 |
30 |
31 | {% endblock %}
32 |
--------------------------------------------------------------------------------
/templates/domain_dashboard.html:
--------------------------------------------------------------------------------
1 | {% import "components/user.html" as user %}
2 | {% extends "domain_base.html" %}
3 | {% block domain_content %}
4 | {% if domain.bulletin %}
5 |
6 |
7 | {{ domain.bulletin|markdown|safe }}
8 |
9 |
10 | {% endif %}
11 |
12 |
21 |
22 |
23 | {{ _('Gravatar') }}
24 | {{ _('Name') }} {{ domain.name }}
25 |
26 |
27 |
28 | {% endblock %}
29 |
--------------------------------------------------------------------------------
/misc/icons/platform--linux.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/discussion_main.page.styl:
--------------------------------------------------------------------------------
1 | .page--discussion_main,
2 | .page--discussion_node,
3 | .page--user_detail
4 | .discussion__item
5 | border-left: rem(5px) solid transparent
6 | padding-left: rem(5px)
7 |
8 | &.highlight
9 | border-left: rem(5px) solid $highlight-color
10 |
11 | .discussion__title
12 | font-size: rem($font-size-title)
13 | line-height: 1.2em
14 | margin-bottom: rem(10px)
15 |
16 | a:visited
17 | color: lighten($text-1-color, 60%)
18 |
19 | .discussion__replies
20 | width: rem(70px)
21 |
22 | .discussion-node__bg
23 | height: 0
24 | box-sizing: content-box
25 | padding-bottom: 40%
26 | background-size: cover
27 | background-position: left center
28 | background-repeat: no-repeat
29 |
30 | for id in 'qa' 'share' 'vijos' 'solution' 'advice'
31 | &.{id}
32 | background-image: url('./discussion_node_bg/nodes_' + id + '.png')
33 | +retina()
34 | background-image: url('./discussion_node_bg/nodes@2x_' + id + '.png')
35 |
36 | +below(rupture.desktop-cutoff)
37 | display: none
38 |
--------------------------------------------------------------------------------
/misc/PageLoader.js:
--------------------------------------------------------------------------------
1 | import { Page } from './Page';
2 |
3 | export default class PageLoader {
4 | constructor() {
5 | const pages = require.context('../pages/', true, /\.page\.jsx?$/i);
6 | const components = require.context('../components/', true, /\.page\.jsx?$/i);
7 | this.pageInstances = [
8 | ...pages.keys().map((key) => {
9 | const page = pages(key).default;
10 | if (!page || !(page instanceof Page)) return null;
11 | return page;
12 | }),
13 | ...components.keys().map((key) => {
14 | const page = components(key).default;
15 | if (!page || !(page instanceof Page)) return null;
16 | return page;
17 | }),
18 | ];
19 | if (window.Hydro.extraPages) this.pageInstances.push(...window.Hydro.extraPages);
20 | window.Hydro.pageInstances = this.pageInstances;
21 | }
22 |
23 | getAutoloadPages() {
24 | return this.pageInstances.filter((page) => page && page.autoload);
25 | }
26 |
27 | getNamedPage(pageName) {
28 | return this.pageInstances.filter((page) => page && page.isNameMatch(pageName));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/templates/manage_dashboard.html:
--------------------------------------------------------------------------------
1 | {% import "components/user.html" as user %}
2 | {% extends "manage_base.html" %}
3 | {% block manage_content %}
4 |
10 |
11 |
20 |
21 |
22 | {{ _('Gravatar') }}
23 | {{ _('Name') }} {{ domain.name }}
24 |
25 |
26 |
27 | {% endblock %}
28 |
--------------------------------------------------------------------------------
/components/messagepad/reducers/inputs.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 | export default function reducer(state = {}, action) {
4 | switch (action.type) {
5 | case 'DIALOGUES_LOAD_DIALOGUES_FULFILLED': {
6 | const dialogues = action.payload.messages;
7 | return _.fromPairs(_.map(dialogues, (d) => [d._id, '']));
8 | }
9 | case 'DIALOGUES_CREATE': {
10 | const { user } = action.payload;
11 | return {
12 | ...state,
13 | [user._id]: '',
14 | };
15 | }
16 | case 'DIALOGUES_INPUT_CHANGED': {
17 | const id = action.meta.dialogueId;
18 | return {
19 | ...state,
20 | [id]: action.payload,
21 | };
22 | }
23 | case 'DIALOGUES_POST_SEND_FULFILLED': {
24 | const id = action.meta.dialogueId;
25 | return {
26 | ...state,
27 | [id]: '',
28 | };
29 | }
30 | case 'DIALOGUES_MESSAGE_PUSH': {
31 | const { udoc } = action.payload;
32 | if (!state[udoc._id]) {
33 | return {
34 | ...state,
35 | [udoc._id]: '',
36 | };
37 | }
38 | return state;
39 | }
40 | default:
41 | return state;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/utils/loadReactRedux.js:
--------------------------------------------------------------------------------
1 | export default async function loadReactRedux(storeReducer) {
2 | const React = await import('react');
3 | const { render, unmountComponentAtNode } = await import('react-dom');
4 | const { Provider } = await import('react-redux');
5 | const { createStore, applyMiddleware } = await import('redux');
6 | const { default: reduxThunk } = await import('redux-thunk');
7 | const { default: reduxPromise } = await import('redux-promise-middleware');
8 |
9 | const reduxMiddlewares = [];
10 | reduxMiddlewares.push(reduxThunk);
11 | reduxMiddlewares.push(reduxPromise);
12 |
13 | if (process.env.NODE_ENV !== 'production') {
14 | const { createLogger } = await import('redux-logger');
15 | reduxMiddlewares.push(createLogger({
16 | collapsed: true,
17 | duration: true,
18 | }));
19 | }
20 |
21 | const store = createStore(storeReducer, applyMiddleware(...reduxMiddlewares));
22 |
23 | return {
24 | React,
25 | render,
26 | unmountComponentAtNode,
27 | Provider,
28 | store,
29 | };
30 | }
31 |
32 | window.Hydro.utils.loadReactRedux = loadReactRedux;
33 |
--------------------------------------------------------------------------------
/templates/discussion_create.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/basic.html" %}
2 | {% block content %}
3 |
4 |
25 |
26 | {% include 'components/md_hint.html' %}
27 |
28 |
29 | {% endblock %}
30 |
--------------------------------------------------------------------------------
/components/footer/footer.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
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 |
--------------------------------------------------------------------------------
/components/scratchpad/scratchpad.page.styl:
--------------------------------------------------------------------------------
1 | .scratchpad-container
2 | position: absolute
3 | background: #FFF
4 |
5 | #scratchpad
6 | position: absolute
7 | left: 0
8 | top: $nav-item-height
9 | width: 100%
10 | bottom: 0
11 |
12 | .scratchpad__problem
13 | position: absolute
14 | left: 0
15 | top: 0
16 | width: 100%
17 | height: 100%
18 | overflow: auto
19 |
20 | .scratchpad__toolbar__pretest.enabled:hover .icon
21 | color: red
22 |
23 | .scratchpad__toolbar__submit.enabled:hover .icon
24 | color: green
25 |
26 | .scratchpad__records__table
27 | &.loading
28 | // make a flash by setting opacity, otherwise users
29 | // won't think that refreshing is completed!
30 | opacity: 0.6
31 |
32 | .col--at
33 | width: rem(120px)
34 |
35 | .col--time,
36 | .col--memory
37 | width: rem(70px)
38 |
39 | .col--detail
40 | border-right: 1px solid $table-border-color
41 |
42 | .icol
43 | display: inline-block
44 |
45 | .icol--pretest
46 | width: rem(60px)
47 |
48 | .flag
49 | font-size: rem(12px)
50 | color: #FFF
51 | padding: rem(2px 3px)
52 |
53 | .icol--stat
54 | color: #AAA
55 | width: rem(60px)
56 |
--------------------------------------------------------------------------------
/pages/training_main.page.styl:
--------------------------------------------------------------------------------
1 | .page--training_main
2 | .training__title
3 | line-height: 1.2
4 | margin-bottom: 0.4em
5 | font-size: rem($font-size-title)
6 |
7 | .training__intro
8 | color: lighten($text-1-color, 20%)
9 | font-size: rem($font-size-secondary)
10 | margin: 1em 0
11 |
12 | .training__item
13 | padding-left: rem(5px)
14 |
15 | .training__participants
16 | width: rem(80px)
17 |
18 | .training__progress
19 | line-height: rem(17px)
20 | vertical-align: top
21 |
22 | .training__progress-bar
23 | display: inline-block
24 | width: rem(100px)
25 | height: rem(5px)
26 | position: relative
27 | background: #EEE
28 | margin: rem(6px 0)
29 | line-height: 1
30 | vertical-align: top
31 |
32 | .training__progress-track
33 | position: absolute
34 | left: 0
35 | top: 0
36 | height: 100%
37 | width: 0%
38 | background-color: green
39 |
40 | .secondary.training__list
41 | .training__item
42 | margin-bottom: rem(15px)
43 | .training__title
44 | font-size: rem($font-size)
45 |
46 | &.my
47 | .training-status--icon
48 | font-size: rem($font-size-icon * 1.2)
49 |
--------------------------------------------------------------------------------
/pages/record_main.page.styl:
--------------------------------------------------------------------------------
1 | .page--record_main
2 | .col--status
3 | width: rem(170px)
4 | position: relative
5 |
6 | .col--submit-at
7 | width: rem(120px)
8 | text-align: center
9 |
10 | &.nojs .col--submit-at
11 | width: rem(160px)
12 |
13 | .col--submit-by
14 | width: rem(120px)
15 | text-align: right
16 |
17 | .col--time, .col--memory, .col--lang
18 | width: rem(100px)
19 | text-align: center
20 |
21 | .col--status, .col--submit-by, .col--lang
22 | border-right: 1px solid $table-border-color
23 |
24 | .col--status__text
25 | position: relative
26 | z-index: 2
27 |
28 | .col--status__progress-container
29 | position: absolute
30 | left: 0
31 | top: 0
32 | width: 100%
33 | height: 100%
34 | z-index: 1
35 |
36 | .col--status__progress
37 | position: absolute
38 | left: 0
39 | top: 0
40 | height: 100%
41 | background: #fff6d1
42 |
43 | +tablet()
44 | .col--time,
45 | .col--memory,
46 | .col--lang
47 | display: none
48 |
49 | +mobile()
50 | .col--submit-by,
51 | .col--submit-at,
52 | .col--time,
53 | .col--memory,
54 | .col--lang
55 | display: none
56 |
--------------------------------------------------------------------------------
/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.meta.dialogueId;
30 | }
31 | default:
32 | return state;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/pages/record_main.page.js:
--------------------------------------------------------------------------------
1 | import { NamedPage } from 'vj/misc/Page';
2 | import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoComplete';
3 |
4 | const page = new NamedPage('record_main', async () => {
5 | const { default: SockJs } = await import('../components/socket');
6 | const { DiffDOM } = await import('diff-dom');
7 |
8 | const sock = new SockJs(Context.socketUrl);
9 | const dd = new DiffDOM();
10 |
11 | sock.onopen = () => {
12 | sock.send(JSON.stringify({ rids: Context.rids }));
13 | };
14 | sock.onmessage = (message) => {
15 | const msg = JSON.parse(message.data);
16 | const $newTr = $(msg.html);
17 | const $oldTr = $(`.record_main__table tr[data-rid="${$newTr.attr('data-rid')}"]`);
18 | if ($oldTr.length) {
19 | $oldTr.trigger('vjContentRemove');
20 | dd.apply($oldTr[0], dd.diff($oldTr[0], $newTr[0]));
21 | $oldTr.trigger('vjContentNew');
22 | } else {
23 | $('.record_main__table tbody').prepend($newTr);
24 | $newTr.trigger('vjContentNew');
25 | }
26 | };
27 | UserSelectAutoComplete.getOrConstruct($('.filter-user [name="uidOrName"]'), {
28 | clearDefaultValue: false,
29 | });
30 | });
31 |
32 | export default page;
33 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | 'lodash',
4 | '@babel/plugin-transform-runtime',
5 | '@babel/plugin-syntax-dynamic-import',
6 | '@babel/plugin-syntax-import-meta',
7 | '@babel/plugin-proposal-json-strings',
8 | ['@babel/plugin-proposal-class-properties', { loose: true }],
9 | '@babel/plugin-proposal-function-sent',
10 | '@babel/plugin-proposal-export-namespace-from',
11 | '@babel/plugin-proposal-numeric-separator',
12 | '@babel/plugin-proposal-throw-expressions',
13 | [
14 | 'prismjs',
15 | {
16 | languages: [
17 | 'clike',
18 | 'c',
19 | 'cpp',
20 | 'pascal',
21 | 'java',
22 | 'python',
23 | 'java',
24 | 'python',
25 | 'php',
26 | 'rust',
27 | 'haskell',
28 | 'javascript',
29 | 'go',
30 | 'ruby',
31 | 'csharp',
32 | ],
33 | plugins: [
34 | 'toolbar',
35 | 'line-numbers',
36 | ],
37 | css: true,
38 | },
39 | ],
40 | ],
41 | presets: [
42 | ['@babel/preset-env', { loose: true, modules: false }],
43 | '@babel/preset-react',
44 | ],
45 | };
46 |
--------------------------------------------------------------------------------
/components/messagepad/DialogueListItemComponent.jsx:
--------------------------------------------------------------------------------
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 |
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 |
--------------------------------------------------------------------------------
/templates/contest_scoreboard_download_html.html:
--------------------------------------------------------------------------------
1 |
2 |
27 |
28 |
29 |
30 | {%- for column in rows[0] -%}
31 |
32 | {{ column['value'] }}
33 |
34 | {%- endfor -%}
35 |
36 |
37 |
38 | {%- for row in rows -%}{% if loop.index0 %}
39 |
40 | {%- for column in row -%}
41 |
42 | {{ column['value'] }}
43 |
44 | {%- endfor -%}
45 |
46 | {% endif %}{%- endfor -%}
47 |
48 |
49 |
--------------------------------------------------------------------------------
/templates/user_register_with_code.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/immersive.html" %}
2 | {% block content %}
3 |
4 |
{{ _('Sign Up') }}
5 |
35 |
36 | {% endblock %}
37 |
--------------------------------------------------------------------------------
/components/message/index.page.js:
--------------------------------------------------------------------------------
1 | import { AutoloadPage } from 'vj/misc/Page';
2 | import { InfoDialog } from 'vj/components/dialog';
3 | import Notification from 'vj/components/notification';
4 | import { FLAG_ALERT } from 'vj/constant/message';
5 | import i18n from 'vj/utils/i18n';
6 | import tpl from 'vj/utils/tpl';
7 |
8 | const messagePage = new AutoloadPage('messagePage', () => {
9 | async function init() {
10 | const { default: SockJs } = await import('../socket');
11 |
12 | const sock = new SockJs('/home/messages-conn');
13 | sock.onmessage = (message) => {
14 | const msg = JSON.parse(message.data);
15 | if (msg.mdoc.flag | FLAG_ALERT) {
16 | // Is alert
17 | const dialog = new InfoDialog({
18 | $body: tpl`
19 |
20 |
${i18n(msg.udoc.content)}
21 |
`,
22 | });
23 | dialog.open();
24 | } else {
25 | // Is message
26 | Notification.show({
27 | title: msg.udoc.uname,
28 | avatar: msg.udoc.gravatar,
29 | message: msg.mdoc.content,
30 | });
31 | }
32 | };
33 | }
34 |
35 | if (UserContext._id !== 0) init();
36 | });
37 |
38 | export default messagePage;
39 |
--------------------------------------------------------------------------------
/components/scratchpad/Toolbar.page.styl:
--------------------------------------------------------------------------------
1 | .scratchpad__toolbar
2 | background: $toolbar-bg
3 | padding: rem(8px)
4 | font-size: rem($font-size-small)
5 | font-weight: normal
6 |
7 | .scratchpad__toolbar__item
8 | margin-right: rem(5px)
9 |
10 | .select
11 | margin: 0
12 | height: auto
13 | line-height: 1
14 |
15 | .scratchpad__toolbar__button
16 | padding: rem(5px)
17 | font-size: rem($font-size-small)
18 | font-weight: normal
19 | background: none
20 | border: 1px solid transparent
21 | color: $toolbar-fore
22 | outline: 0
23 | transition: .2s linear opacity
24 |
25 | // Hack for Firefox bug #984869
26 | &>div
27 | display: flex
28 | flex-direction: row
29 | align-items: center
30 |
31 | &.enabled
32 | cursor: pointer
33 |
34 | .icon
35 | font-size: rem($font-size-icon)
36 | margin-right: 2px
37 |
38 | &.enabled:hover
39 | color: $toolbar-fore-hover
40 | background: $toolbar-bg-hover
41 |
42 | &.activated
43 | color: $toolbar-fore-selected !important
44 | background: $toolbar-bg-selected !important
45 |
46 | &.disabled
47 | pointer-events: none
48 | opacity: $toolbar-disabled-opacity
49 |
50 | .scratchpad__toolbar__split
51 | width: 1px
52 | height: 1rem
53 | background: #DDD
54 |
--------------------------------------------------------------------------------
/pages/manage_dashboard.page.js:
--------------------------------------------------------------------------------
1 | import { NamedPage } from 'vj/misc/Page';
2 |
3 | const page = new NamedPage('manage_dashboard', async () => {
4 | const { default: SockJs } = await import('../components/socket');
5 |
6 | const sock = new SockJs('/manage/check-conn');
7 | sock.onopen = () => {
8 | $('Connection opened.
').appendTo('#messages');
9 | };
10 |
11 | sock.onmessage = (message) => {
12 | const msg = JSON.parse(message.data);
13 | if (msg.type === 'log') {
14 | const div = $('').appendTo('#messages');
15 | $(`${msg.payload}
`).appendTo(div);
16 | } else if (msg.type === 'warn') {
17 | const div = $('').appendTo('#messages');
18 | $(`${msg.payload}
`).appendTo(div);
19 | } else if (msg.type === 'error') {
20 | const div = $('').appendTo('#messages');
21 | $(`${msg.payload}
`).appendTo(div);
22 | }
23 | };
24 |
25 | sock.onclose = (message) => {
26 | $(`
27 |
28 | Connection closed,reason=${JSON.stringify(message.reason)}
29 |
30 | `).appendTo('#messages');
31 | };
32 | });
33 |
34 | export default page;
35 |
--------------------------------------------------------------------------------
/templates/discussion_edit.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/basic.html" %}
2 | {% block content %}
3 |
4 |
27 |
28 | {% include 'components/md_hint.html' %}
29 |
30 |
31 | {% endblock %}
32 |
--------------------------------------------------------------------------------
/components/marker/marker.page.styl:
--------------------------------------------------------------------------------
1 | [data-marker-enabled]
2 | outline: 0
3 |
4 | .marker
5 | position: fixed
6 | z-index: 100
7 | display: none
8 | box-shadow: 0 2px 7px rgba(#000, 0.3)
9 | background: #FFF
10 | font-size: rem($font-size-small)
11 | transition: transform .1s ease-out, opacity .1s ease-out
12 | transform: scale(0.8)
13 | transform-origin: 0 100%
14 | pointer-events: none
15 | background: #FFF
16 | padding: 1px
17 | border: 1px solid #AAA
18 |
19 | &.open
20 | transform: none
21 | pointer-events: auto
22 |
23 | &__toolbar
24 | padding: 1px
25 | background: $toolbar-bg
26 | white-space: nowrap
27 | font-size: 0
28 |
29 | &__action
30 | display: inline-block
31 | text-align: center
32 | font-size: rem($font-size-icon)
33 | vertical-align: top
34 | cursor: pointer
35 | padding: rem(8px)
36 | color: $toolbar-fore
37 | position: relative
38 |
39 | &:hover
40 | background: $toolbar-bg-hover
41 | color: $toolbar-fore-hover
42 |
43 | &__icon
44 | display: block
45 | margin: rem(2px)
46 | width: rem(12px)
47 | height: rem(12px)
48 | border-radius: 50%
49 |
50 | &.icon-yellow
51 | background: #ffc100
52 |
53 | &.icon-green
54 | background: #54d651
55 |
--------------------------------------------------------------------------------
/pages/homework_main.page.js:
--------------------------------------------------------------------------------
1 | import { NamedPage } from 'vj/misc/Page';
2 | import Calendar from 'vj/components/calendar';
3 | import i18n from 'vj/utils/i18n';
4 | import { parse as parseMongoId } from 'vj/utils/mongoId';
5 |
6 | const page = new NamedPage('homework_main', () => {
7 | // Homework Calendar
8 | if (Context.docs) {
9 | const events = Context.docs.map((doc) => ({
10 | beginAt: doc.beginAt,
11 | endAt: doc.endAt,
12 | title: doc.title,
13 | maskFrom: doc.penaltySince ? doc.penaltySince : null,
14 | maskTitle: i18n('Time Extension'),
15 | colorIndex: parseMongoId(doc._id).timestamp % 12,
16 | link: doc.url,
17 | }));
18 | const calendar = new Calendar(events);
19 | calendar.getDom().appendTo('[name="calendar_entry"]');
20 | $('[name="homework_display"]').change((ev) => {
21 | switch (ev.currentTarget.value) {
22 | case 'calendar':
23 | $('.homework__list').hide();
24 | $('[name="calendar_entry"]').show();
25 | break;
26 | case 'list':
27 | $('.homework__list').show();
28 | $('[name="calendar_entry"]').hide();
29 | break;
30 | default:
31 | throw new Error('Unexpected display parameter');
32 | }
33 | });
34 | }
35 | });
36 |
37 | export default page;
38 |
--------------------------------------------------------------------------------
/components/notification/notification.page.styl:
--------------------------------------------------------------------------------
1 | .notification
2 | position: fixed
3 | left: rem(20px)
4 | bottom: rem(20px)
5 | padding: rem(10px)
6 | border-radius: rem(2px)
7 | background: rgba(#222, 0.9)
8 | color: #FFF
9 | font-size: rem($font-size-secondary)
10 | transition: transform .2s, opacity .2s
11 | transition-timing-function: ease-out-cubic
12 | z-index: 1000 // managed
13 | line-height: rem(20px)
14 | box-shadow: 0 4px 10px rgba(#000, 0.4)
15 | min-width: rem(150px)
16 |
17 | img
18 | border-radius: 50%
19 |
20 | &.hide
21 | opacity: 0
22 | transform: translateY(20px) scale(0.9)
23 |
24 | &:before
25 | display: inline-block
26 | font-family: $icon-font-name !important
27 | margin-right: rem(10px)
28 | font-size: rem($font-size-icon)
29 | line-height: 1
30 | vertical-align: top
31 | margin-top: rem((20px - $font-size-icon) / 2)
32 |
33 | for key, value in $notification-color
34 | &.{key}
35 | border-left: 5px solid value
36 | &:before
37 | color: value
38 |
39 | for key, value in $notification-icon
40 | &.{key}:before
41 | content: value
42 |
43 | +mobile()
44 | .notification
45 | padding: rem(20px 15px)
46 | left: 0
47 | width: 100%
48 | min-width: auto
49 | bottom: 0
50 |
--------------------------------------------------------------------------------
/components/tooltip/Tooltip.js:
--------------------------------------------------------------------------------
1 | import Drop from 'tether-drop';
2 | import _ from 'lodash';
3 | import DOMAttachedObject from 'vj/components/DOMAttachedObject';
4 |
5 | export default class Tooltip extends DOMAttachedObject {
6 | static DOMAttachKey = 'vjTooltipInstance';
7 |
8 | constructor($dom, options = {}) {
9 | super($dom, true);
10 | this.dropRemoved = false;
11 | this.drop = new Drop({
12 | target: $dom[0],
13 | classes: 'tooltip',
14 | position: options.position || 'top center',
15 | openOn: 'hover',
16 | constrainToWindow: true,
17 | constrainToScrollParent: false,
18 | content: $dom.attr('data-tooltip'),
19 | });
20 | this.isOpen = false;
21 | this.drop.on('open', this.onOpen, this);
22 | this.drop.on('close', this.onClose, this);
23 | this.delayDetach = _.debounce(this.detach.bind(this), 200);
24 | }
25 |
26 | onOpen() {
27 | this.delayDetach.cancel();
28 | }
29 |
30 | onClose() {
31 | this.delayDetach();
32 | }
33 |
34 | detach() {
35 | if (this.detached) {
36 | return;
37 | }
38 | super.detach();
39 | this.drop.destroy();
40 | }
41 |
42 | close() {
43 | this.drop.close();
44 | }
45 |
46 | open() {
47 | this.drop.open();
48 | }
49 | }
50 |
51 | _.assign(Tooltip, DOMAttachedObject);
52 |
--------------------------------------------------------------------------------
/templates/components/paginator.html:
--------------------------------------------------------------------------------
1 | {% macro render(page, num_pages, add_qs='') %}
2 | {% if num_pages > 0 %}
3 |
24 | {% endif %}
25 | {% endmacro %}
26 |
--------------------------------------------------------------------------------
/components/messagepad/reducers/isPosting.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import Notification from 'vj/components/notification';
3 |
4 | export default function reducer(state = {}, action) {
5 | switch (action.type) {
6 | case 'DIALOGUES_LOAD_DIALOGUES_FULFILLED': {
7 | const dialogues = action.payload.messages;
8 | return _.fromPairs(_.map(dialogues, (d) => [d._id, false]));
9 | }
10 | case 'DIALOGUES_CREATE': {
11 | const { user } = action.payload;
12 | return {
13 | ...state,
14 | [user._id]: false,
15 | };
16 | }
17 | case 'DIALOGUES_POST_SEND_PENDING': {
18 | const id = action.meta.dialogueId;
19 | return {
20 | ...state,
21 | [id]: true,
22 | };
23 | }
24 | case 'DIALOGUES_POST_SEND_REJECTED': {
25 | Notification.error(action.payload.message);
26 | const id = action.meta.dialogueId;
27 | return {
28 | ...state,
29 | [id]: false,
30 | };
31 | }
32 | case 'DIALOGUES_POST_SEND_FULFILLED': {
33 | const id = action.meta.dialogueId;
34 | return {
35 | ...state,
36 | [id]: false,
37 | };
38 | }
39 | case 'DIALOGUES_MESSAGE_PUSH': {
40 | const { udoc } = action.payload;
41 | return {
42 | ...state,
43 | [udoc._id]: false,
44 | };
45 | }
46 | default:
47 | return state;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/components/vote/vote.page.js:
--------------------------------------------------------------------------------
1 | import Rotator from 'vj/components/rotator';
2 | import { AutoloadPage } from 'vj/misc/Page';
3 | import request from 'vj/utils/request';
4 |
5 | function setVoteState($container, value, status) {
6 | const $num = $container.find('.vote-number');
7 | Rotator.get($num).setValue(value);
8 | $container.find('.vote-button').removeClass('active');
9 | if (status === 1) {
10 | $container.find('.upvote').addClass('active');
11 | } else if (status === -1) {
12 | $container.find('.downvote').addClass('active');
13 | }
14 | }
15 |
16 | function applyRotator(element) {
17 | Rotator.getOrConstruct($(element));
18 | }
19 |
20 | const votePage = new AutoloadPage('votePage', () => {
21 | $('.vote-number.rotator--enabled').get().forEach((element) => applyRotator(element));
22 | $(document).on('click', '.vote-button', (ev) => {
23 | const $button = $(ev.currentTarget);
24 | const $container = $button.closest('.vote');
25 | const $form = $button.closest('form');
26 | request
27 | .post($form.attr('action'), $form)
28 | .then((data) => {
29 | setVoteState($container, data.vote, data.user_vote);
30 | })
31 | .catch(() => {
32 | // TODO(iceboy): notify failure
33 | });
34 | return false;
35 | });
36 | });
37 |
38 | export default votePage;
39 |
--------------------------------------------------------------------------------
/templates/domain_join.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/basic.html" %}
2 | {% block content %}
3 |
4 |
5 |
6 |
11 |
12 |
{{ _('By clicking the button, you will become a member of the domain {0}.').format(handler.domain['name']) }}
13 |
14 |
15 |
30 |
31 |
32 |
33 |
34 | {% endblock %}
--------------------------------------------------------------------------------
/templates/domain_base.html:
--------------------------------------------------------------------------------
1 | {% extends "layout/basic.html" %}
2 | {% block content %}
3 |
4 |
5 | {% block domain_content %}{% endblock %}
6 |
7 |
33 |
34 | {% endblock %}
35 |
--------------------------------------------------------------------------------
/components/messagepad/reducers/dialogues.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 | export default function reducer(state = {}, action) {
4 | switch (action.type) {
5 | case 'DIALOGUES_LOAD_DIALOGUES_FULFILLED': {
6 | const { messages } = action.payload;
7 | return _.keyBy(messages, 'udoc._id');
8 | }
9 | case 'DIALOGUES_CREATE': {
10 | const { user } = action.payload;
11 | return {
12 | ...state,
13 | [user._id]: {
14 | _id: user._id,
15 | udoc: { ...user },
16 | messages: [],
17 | },
18 | };
19 | }
20 | case 'DIALOGUES_POST_SEND_FULFILLED': {
21 | const id = action.meta.dialogueId;
22 | const { mdoc } = action.payload;
23 | return {
24 | ...state,
25 | [id]: {
26 | ...state[id],
27 | messages: [
28 | ...state[id].messages,
29 | mdoc,
30 | ],
31 | },
32 | };
33 | }
34 | case 'DIALOGUES_MESSAGE_PUSH': {
35 | const { mdoc, udoc } = action.payload;
36 | const to = mdoc.from === UserContext._id ? mdoc.to : mdoc.from;
37 | return {
38 | ...state,
39 | [to]: {
40 | ...state[to] || {},
41 | udoc,
42 | messages: [
43 | ...state[to]?.messages || [],
44 | mdoc,
45 | ],
46 | },
47 | };
48 | }
49 | default:
50 | return state;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/components/drop/drop.page.styl:
--------------------------------------------------------------------------------
1 | .drop-element,
2 | .drop-element:after,
3 | .drop-element:before,
4 | .drop-element *,
5 | .drop-element *:after,
6 | .drop-element *:before
7 | box-sizing: border-box
8 |
9 | .drop-element
10 | position: absolute
11 | display: none
12 | opacity: 0
13 | transition: opacity .1s ease-in-out
14 | z-index: 1000 // managed
15 |
16 | &.drop-open
17 | display: block
18 |
19 | &.drop-open-transitionend
20 | display: block
21 |
22 | &.drop-after-open
23 | opacity: 1
24 |
25 | .drop-target-attached-top.drop-target-attached-left .drop-content
26 | transform-origin: -10% 110%
27 | .drop-target-attached-top.drop-target-attached-center .drop-content
28 | transform-origin: 50% 110%
29 | .drop-target-attached-top.drop-target-attached-right .drop-content
30 | transform-origin: 110% 110%
31 | .drop-target-attached-bottom.drop-target-attached-left .drop-content
32 | transform-origin: -10% -10%
33 | .drop-target-attached-bottom.drop-target-attached-center .drop-content
34 | transform-origin: 50% -10%
35 | .drop-target-attached-bottom.drop-target-attached-right .drop-content
36 | transform-origin: 110% -10%
37 | .drop-target-attached-center.drop-target-attached-right .drop-content
38 | transform-origin: -10% 50%
39 | .drop-target-attached-center.drop-target-attached-left .drop-content
40 | transform-origin: 110% 50%
41 |
--------------------------------------------------------------------------------