diff --git a/TODO b/TODO index 1e45ee9bd..a46d2ed72 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,5 @@ @requireCsrfToken 消息提醒 bug修复 -使用BigInt管理权限 \ No newline at end of file +使用BigInt管理权限 +修复discussion前端权限节点显示异常 diff --git a/hydro/handler/home.ts b/hydro/handler/home.ts index 160eba057..7787875a0 100644 --- a/hydro/handler/home.ts +++ b/hydro/handler/home.ts @@ -31,7 +31,7 @@ import { const { geoip, useragent } = global.Hydro.lib; class HomeHandler extends Handler { - async homework(domainId) { + async homework(domainId: string) { if (this.user.hasPerm(PERM.PERM_VIEW_HOMEWORK)) { const tdocs = await contest.getMulti(domainId, {}, document.TYPE_HOMEWORK) .sort('beginAt', -1) @@ -46,11 +46,11 @@ class HomeHandler extends Handler { return [[], {}]; } - async contest(domainId) { + async contest(domainId: string) { if (this.user.hasPerm(PERM.PERM_VIEW_CONTEST)) { const tdocs = await contest.getMulti(domainId) .sort('beginAt', -1) - .limit(await system.get('CONTESTS_ON_MAIN')) + .limit(await system.get('CONTEST_ON_MAIN')) .toArray(); const tsdict = await contest.getListStatus( domainId, this.user._id, tdocs.map((tdoc) => tdoc.docId), @@ -60,11 +60,11 @@ class HomeHandler extends Handler { return [[], {}]; } - async training(domainId) { + async training(domainId: string) { if (this.user.hasPerm(PERM.PERM_VIEW_TRAINING)) { const tdocs = await training.getMulti(domainId) .sort('_id', 1) - .limit(await system.get('TRAININGS_ON_MAIN')) + .limit(await system.get('TRAINING_ON_MAIN')) .toArray(); const tsdict = await training.getListStatus( domainId, this.user._id, tdocs.map((tdoc) => tdoc.docId), @@ -74,10 +74,10 @@ class HomeHandler extends Handler { return [[], {}]; } - async discussion(domainId): Promise<[any[], any]> { + async discussion(domainId: string): Promise<[any[], any]> { if (this.user.hasPerm(PERM.PERM_VIEW_DISCUSSION)) { const ddocs = await discussion.getMulti(domainId) - .limit(await system.get('DISCUSSIONS_ON_MAIN')) + .limit(await system.get('DISCUSSION_ON_MAIN')) .toArray(); const vndict = await discussion.getListVnodes(domainId, ddocs, this); return [ddocs, vndict]; diff --git a/hydro/handler/import.ts b/hydro/handler/import.ts index 573eed448..0d73a45b5 100644 --- a/hydro/handler/import.ts +++ b/hydro/handler/import.ts @@ -34,9 +34,11 @@ class ProblemImportHandler extends Handler { throw new ValidationError('remoteType'); } const [pdoc, testdata] = await global.Hydro.lib[`import.${remoteType}`](url, this); - if (pid) pdoc.pid = pid; if (hidden) pdoc.hidden = true; - const docId = await problem.add(domainId, pdoc.title, pdoc.content, this.user._id, pdoc); + const docId = await problem.add( + domainId, pid || pdoc.pid, pdoc.title, pdoc.content, this.user._id, + pdoc.tag || [], pdoc.category || [], null, pdoc.hidden, + ); if (testdata) { const file = path.resolve(os.tmpdir(), 'hydro', `import_${domainId}_${pid || pdoc.pid || docId}.zip`); const w = fs.createWriteStream(file); diff --git a/hydro/handler/problem.ts b/hydro/handler/problem.ts index cc3104ecd..b964c4f31 100644 --- a/hydro/handler/problem.ts +++ b/hydro/handler/problem.ts @@ -534,13 +534,11 @@ class ProblemCreateHandler extends Handler { } @param('title', Types.String, isTitle) - @param('pid', Types.String, isPid) + @param('pid', Types.String, isPid, true) @param('content', Types.String, isContent) @param('hidden', Types.Boolean) async post(domainId: string, title: string, pid: string, content: string, hidden = false) { - pid = await problem.add(domainId, title, content, this.user._id, { - pid, hidden, - }); + pid = await problem.add(domainId, pid, title, content, this.user._id, [], [], null, hidden); this.response.body = { pid }; this.response.redirect = this.url('problem_settings', { pid }); } diff --git a/hydro/model/problem.ts b/hydro/model/problem.ts index cfe1295ca..d7c3c2206 100644 --- a/hydro/model/problem.ts +++ b/hydro/model/problem.ts @@ -24,13 +24,10 @@ export const pdocHidden: Pdoc = { config: '', }; -export async function add(domainId: string, title: string, content: string, owner: number, { - pid = null, - data = null, - category = [], - tag = [], - hidden = false, -}) { +export async function add( + domainId: string, pid: string = null, title: string, content: string, owner: number, + tag: string[] = [], category: string[] = [], data: ObjectID = null, hidden = false, +) { const d = await domain.inc(domainId, 'pidCounter', 1); if (!pid) pid = d.pidCounter.toString(); return await document.add( diff --git a/hydro/model/setting.ts b/hydro/model/setting.ts index 1658bea2a..9278dab98 100644 --- a/hydro/model/setting.ts +++ b/hydro/model/setting.ts @@ -133,7 +133,7 @@ SystemSetting( Setting('setting_ui', 'ui.header_logo', null, '/components/header/header-logo.png', 'text', 'Header Logo'), Setting('setting_ui', 'ui.header_logo_2x', null, '/components/header/header-logo@2x.png', 'text', 'Header Logo@2x'), Setting('setting_ui', 'ui.header_background', null, '/components/header/header-background.png', 'text', 'Header Background'), - Setting('setting_ui', 'ui.header_background_2x', null, '/components/header/header-background.png', 'text', 'Header Logo@2x'), + Setting('setting_ui', 'ui.header_background_2x', null, '/components/header/header-background@2x.png', 'text', 'Header Logo@2x'), Setting('setting_ui', 'ui.nav_logo_light', null, '/components/navigation/nav-logo-small_light.png', 'text', 'Nav Logo(Light)'), Setting('setting_ui', 'ui.nav_logo_dark', null, '/components/navigation/nav-logo-small_dark.png', 'text', 'Nav Logo(Dark)'), Setting('setting_ui', 'ui.nav_logo_light_2x', null, '/components/navigation/nav-logo-small@2x_light.png', 'text', 'Nav Logo@2x(Light)'), @@ -150,6 +150,10 @@ SystemSetting( Setting('setting_constant', 'SOLUTION_PER_PAGE', null, 20, 'number', 'Solutions per Page'), Setting('setting_constant', 'TRAINING_PER_PAGE', null, 10, 'number', 'Training per Page'), Setting('setting_constant', 'REPLY_PER_PAGE', null, 50, 'number', 'Reply per Page'), + Setting('setting_constant', 'HOMEWORK_ON_MAIN', null, 5, 'number', 'HOMEWORK_ON_MAIN'), + Setting('setting_constant', 'DISCUSSION_ON_MAIN', null, 20, 'number', 'DISCUSSION_ON_MAIN'), + Setting('setting_constant', 'CONTEST_ON_MAIN', null, 10, 'number', 'CONTEST_ON_MAIN'), + Setting('setting_constant', 'TRAINING_ON_MAIN', null, 10, 'number', 'TRAINING_ON_MAIN'), ); global.Hydro.postInit.push( diff --git a/hydro/model/user.ts b/hydro/model/user.ts index 64a2feaab..83978706a 100644 --- a/hydro/model/user.ts +++ b/hydro/model/user.ts @@ -188,7 +188,7 @@ export async function create({ mailLower: mail.trim().toLowerCase(), uname, unameLower: uname.trim().toLowerCase(), - password: pwhash(password, salt), + hash: pwhash(password, salt), salt, hashType: 'hydro', regat: new Date(), diff --git a/hydro/script/install.ts b/hydro/script/install.ts index 5b5efefe1..c4c3b43a9 100644 --- a/hydro/script/install.ts +++ b/hydro/script/install.ts @@ -6,7 +6,8 @@ import pwhash from '../lib/hash.hydro'; export const description = 'Install'; export async function run({ username = '', password = '' } = {}) { - await domain.add('system', 0, 'Hydro', 'Hydro System'); + const ddoc = await domain.get('system'); + if (!ddoc) await domain.add('system', 0, 'Hydro', 'Hydro System'); if (username && password) { const udoc = await user.getById('system', -1); if (!udoc) { diff --git a/locales/zh_CN.yaml b/locales/zh_CN.yaml index 6b4be313e..3b4b7bb71 100644 --- a/locales/zh_CN.yaml +++ b/locales/zh_CN.yaml @@ -208,6 +208,7 @@ Difficulty by algorithm: 算法计算的难度 Difficulty displayed: 显示的难度 Difficulty: 难度 Disabled: 关闭 +Disconnected from the server. Reconnecting...: 到服务器的连接断开。正在重新连接... Discussion {1} not found.: 讨论 {1} 不存在。 Discussion category {1} already exists.: 讨论分类 {1} 已经存在。 Discussion category {1} not found.: 讨论分类 {1} 不存在。 diff --git a/ui/components/header/header.page.styl b/ui/components/header/header.page.styl index 7188d212d..c28d262f6 100644 --- a/ui/components/header/header.page.styl +++ b/ui/components/header/header.page.styl @@ -8,16 +8,19 @@ $header-gap-mini-domain = ($header-bg-height-mini - $nav-item-height - $header-l &.warning background: #ffe1a6 +.header__options + background-image: url(./header-background.png) + background-image: url(./header-background@2x.png) + .header position: relative height: rem($header-bg-height) - background: #5eafca url(./header-background.png) no-repeat center top + background: #5eafca no-repeat center top background-size: cover transition: height .5s transition-timing-function: ease-out-cubic overflow: hidden +retina() - background-image: url(./header-background.png) background-size: rem($header-bg-width $header-bg-height) &.mini @@ -29,6 +32,12 @@ $header-gap-mini-domain = ($header-bg-height-mini - $nav-item-height - $header-l .header__content display: none +.header__logo__options + background-image: url(./header-logo.png) + background-image: url(./header-logo@2x.png) + background-image: url(./header-logo-summer.png) + background-image: url(./header-logo-summer@2x.png) + .header__logo display: block width: rem($header-logo-width-real) diff --git a/ui/components/message/index.page.js b/ui/components/message/index.page.js new file mode 100644 index 000000000..4f36727cc --- /dev/null +++ b/ui/components/message/index.page.js @@ -0,0 +1,29 @@ +import { AutoloadPage } from 'vj/misc/PageLoader'; +import Notification from 'vj/components/notification'; +import i18n from 'vj/utils/i18n'; + +const messagePage = new AutoloadPage('messagePage', () => { + async function init(isReconnect = false) { + const { default: SockJs } = await import('sockjs-client'); + + const sock = new SockJs(`/home/messages-conn?token=${UiContext.token}`); + sock.onopen = () => { + if (isReconnect) Notification.info('Reconnected'); + }; + sock.onmessage = (message) => { + const msg = JSON.parse(message.data); + console.log(msg); + Notification.show({ title: msg.udoc.uname, message: msg.mdoc.content, duration: 0 }); + }; + sock.onclose = () => { + Notification.warn(i18n('Disconnected from the server. Reconnecting...')); + setTimeout(() => { + init(true); + }, 3000); + }; + } + + if (UserContext._id !== 0) init(); +}); + +export default messagePage; diff --git a/ui/components/navigation/navigation.page.styl b/ui/components/navigation/navigation.page.styl index 77d44838b..c979b1dfe 100644 --- a/ui/components/navigation/navigation.page.styl +++ b/ui/components/navigation/navigation.page.styl @@ -107,6 +107,12 @@ .nav__list--secondary .nav__item--round margin-left: rem(5px) +.nav__logo__options + background-image: url(./nav-logo-small_light.png) + background-image: url(./nav-logo-small_dark.png) + background-image: url(./nav-logo-small@2x_light.png) + background-image: url(./nav-logo-small@2x_dark.png) + .nav__logo display: block width: 0 diff --git a/ui/components/notification/index.js b/ui/components/notification/index.js index c0789f89f..18b402d72 100644 --- a/ui/components/notification/index.js +++ b/ui/components/notification/index.js @@ -6,45 +6,64 @@ let autoHideTimer = null; export default class Notification { static success(message, duration) { - Notification.show({ type: 'success', message, duration }); + return Notification.show({ type: 'success', message, duration }); } static info(message, duration) { - Notification.show({ type: 'info', message, duration }); + return Notification.show({ type: 'info', message, duration }); } static warn(message, duration) { - Notification.show({ type: 'warn', message, duration }); + return Notification.show({ type: 'warn', message, duration }); } static error(message, duration) { - Notification.show({ type: 'error', message, duration }); + return Notification.show({ type: 'error', message, duration }); } - static show({ message, type = 'info', duration = 3000 }) { - if (lastNotification) { + static show({ + avatar, title, message, type = 'info', duration = 3000, + actionButtons = [], + }) { + if (duration && lastNotification) { Notification.hide(); } - const $n = $(tpl`
`) + const $dom = $(tpl``); + if (avatar) $(tpl`