mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
站内消息即时通知,测试断线重连
This commit is contained in:
1 parent
a93da2badb
commit
900e19382b
14 files changed
+107
-33
No files matched your search
@@ -2,3 +2,4 @@
|
|||||||
消息提醒
|
消息提醒
|
||||||
bug修复
|
bug修复
|
||||||
使用BigInt管理权限
|
使用BigInt管理权限
|
||||||
|
修复discussion前端权限节点显示异常
|
||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
const { geoip, useragent } = global.Hydro.lib;
|
const { geoip, useragent } = global.Hydro.lib;
|
||||||
|
|
||||||
class HomeHandler extends Handler {
|
class HomeHandler extends Handler {
|
||||||
async homework(domainId) {
|
async homework(domainId: string) {
|
||||||
if (this.user.hasPerm(PERM.PERM_VIEW_HOMEWORK)) {
|
if (this.user.hasPerm(PERM.PERM_VIEW_HOMEWORK)) {
|
||||||
const tdocs = await contest.getMulti(domainId, {}, document.TYPE_HOMEWORK)
|
const tdocs = await contest.getMulti(domainId, {}, document.TYPE_HOMEWORK)
|
||||||
.sort('beginAt', -1)
|
.sort('beginAt', -1)
|
||||||
@@ -46,11 +46,11 @@ class HomeHandler extends Handler {
|
|||||||
return [[], {}];
|
return [[], {}];
|
||||||
}
|
}
|
||||||
|
|
||||||
async contest(domainId) {
|
async contest(domainId: string) {
|
||||||
if (this.user.hasPerm(PERM.PERM_VIEW_CONTEST)) {
|
if (this.user.hasPerm(PERM.PERM_VIEW_CONTEST)) {
|
||||||
const tdocs = await contest.getMulti(domainId)
|
const tdocs = await contest.getMulti(domainId)
|
||||||
.sort('beginAt', -1)
|
.sort('beginAt', -1)
|
||||||
.limit(await system.get('CONTESTS_ON_MAIN'))
|
.limit(await system.get('CONTEST_ON_MAIN'))
|
||||||
.toArray();
|
.toArray();
|
||||||
const tsdict = await contest.getListStatus(
|
const tsdict = await contest.getListStatus(
|
||||||
domainId, this.user._id, tdocs.map((tdoc) => tdoc.docId),
|
domainId, this.user._id, tdocs.map((tdoc) => tdoc.docId),
|
||||||
@@ -60,11 +60,11 @@ class HomeHandler extends Handler {
|
|||||||
return [[], {}];
|
return [[], {}];
|
||||||
}
|
}
|
||||||
|
|
||||||
async training(domainId) {
|
async training(domainId: string) {
|
||||||
if (this.user.hasPerm(PERM.PERM_VIEW_TRAINING)) {
|
if (this.user.hasPerm(PERM.PERM_VIEW_TRAINING)) {
|
||||||
const tdocs = await training.getMulti(domainId)
|
const tdocs = await training.getMulti(domainId)
|
||||||
.sort('_id', 1)
|
.sort('_id', 1)
|
||||||
.limit(await system.get('TRAININGS_ON_MAIN'))
|
.limit(await system.get('TRAINING_ON_MAIN'))
|
||||||
.toArray();
|
.toArray();
|
||||||
const tsdict = await training.getListStatus(
|
const tsdict = await training.getListStatus(
|
||||||
domainId, this.user._id, tdocs.map((tdoc) => tdoc.docId),
|
domainId, this.user._id, tdocs.map((tdoc) => tdoc.docId),
|
||||||
@@ -74,10 +74,10 @@ class HomeHandler extends Handler {
|
|||||||
return [[], {}];
|
return [[], {}];
|
||||||
}
|
}
|
||||||
|
|
||||||
async discussion(domainId): Promise<[any[], any]> {
|
async discussion(domainId: string): Promise<[any[], any]> {
|
||||||
if (this.user.hasPerm(PERM.PERM_VIEW_DISCUSSION)) {
|
if (this.user.hasPerm(PERM.PERM_VIEW_DISCUSSION)) {
|
||||||
const ddocs = await discussion.getMulti(domainId)
|
const ddocs = await discussion.getMulti(domainId)
|
||||||
.limit(await system.get('DISCUSSIONS_ON_MAIN'))
|
.limit(await system.get('DISCUSSION_ON_MAIN'))
|
||||||
.toArray();
|
.toArray();
|
||||||
const vndict = await discussion.getListVnodes(domainId, ddocs, this);
|
const vndict = await discussion.getListVnodes(domainId, ddocs, this);
|
||||||
return [ddocs, vndict];
|
return [ddocs, vndict];
|
||||||
|
|||||||
@@ -34,9 +34,11 @@ class ProblemImportHandler extends Handler {
|
|||||||
throw new ValidationError('remoteType');
|
throw new ValidationError('remoteType');
|
||||||
}
|
}
|
||||||
const [pdoc, testdata] = await global.Hydro.lib[`import.${remoteType}`](url, this);
|
const [pdoc, testdata] = await global.Hydro.lib[`import.${remoteType}`](url, this);
|
||||||
if (pid) pdoc.pid = pid;
|
|
||||||
if (hidden) pdoc.hidden = true;
|
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) {
|
if (testdata) {
|
||||||
const file = path.resolve(os.tmpdir(), 'hydro', `import_${domainId}_${pid || pdoc.pid || docId}.zip`);
|
const file = path.resolve(os.tmpdir(), 'hydro', `import_${domainId}_${pid || pdoc.pid || docId}.zip`);
|
||||||
const w = fs.createWriteStream(file);
|
const w = fs.createWriteStream(file);
|
||||||
|
|||||||
@@ -534,13 +534,11 @@ class ProblemCreateHandler extends Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@param('title', Types.String, isTitle)
|
@param('title', Types.String, isTitle)
|
||||||
@param('pid', Types.String, isPid)
|
@param('pid', Types.String, isPid, true)
|
||||||
@param('content', Types.String, isContent)
|
@param('content', Types.String, isContent)
|
||||||
@param('hidden', Types.Boolean)
|
@param('hidden', Types.Boolean)
|
||||||
async post(domainId: string, title: string, pid: string, content: string, hidden = false) {
|
async post(domainId: string, title: string, pid: string, content: string, hidden = false) {
|
||||||
pid = await problem.add(domainId, title, content, this.user._id, {
|
pid = await problem.add(domainId, pid, title, content, this.user._id, [], [], null, hidden);
|
||||||
pid, hidden,
|
|
||||||
});
|
|
||||||
this.response.body = { pid };
|
this.response.body = { pid };
|
||||||
this.response.redirect = this.url('problem_settings', { pid });
|
this.response.redirect = this.url('problem_settings', { pid });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,10 @@ export const pdocHidden: Pdoc = {
|
|||||||
config: '',
|
config: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function add(domainId: string, title: string, content: string, owner: number, {
|
export async function add(
|
||||||
pid = null,
|
domainId: string, pid: string = null, title: string, content: string, owner: number,
|
||||||
data = null,
|
tag: string[] = [], category: string[] = [], data: ObjectID = null, hidden = false,
|
||||||
category = [],
|
) {
|
||||||
tag = [],
|
|
||||||
hidden = false,
|
|
||||||
}) {
|
|
||||||
const d = await domain.inc(domainId, 'pidCounter', 1);
|
const d = await domain.inc(domainId, 'pidCounter', 1);
|
||||||
if (!pid) pid = d.pidCounter.toString();
|
if (!pid) pid = d.pidCounter.toString();
|
||||||
return await document.add(
|
return await document.add(
|
||||||
|
|||||||
@@ -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', 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_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', 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_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_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)'),
|
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', 'SOLUTION_PER_PAGE', null, 20, 'number', 'Solutions per Page'),
|
||||||
Setting('setting_constant', 'TRAINING_PER_PAGE', null, 10, 'number', 'Training 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', '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(
|
global.Hydro.postInit.push(
|
||||||
|
|||||||
+1
-1
@@ -188,7 +188,7 @@ export async function create({
|
|||||||
mailLower: mail.trim().toLowerCase(),
|
mailLower: mail.trim().toLowerCase(),
|
||||||
uname,
|
uname,
|
||||||
unameLower: uname.trim().toLowerCase(),
|
unameLower: uname.trim().toLowerCase(),
|
||||||
password: pwhash(password, salt),
|
hash: pwhash(password, salt),
|
||||||
salt,
|
salt,
|
||||||
hashType: 'hydro',
|
hashType: 'hydro',
|
||||||
regat: new Date(),
|
regat: new Date(),
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import pwhash from '../lib/hash.hydro';
|
|||||||
export const description = 'Install';
|
export const description = 'Install';
|
||||||
|
|
||||||
export async function run({ username = '', password = '' } = {}) {
|
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) {
|
if (username && password) {
|
||||||
const udoc = await user.getById('system', -1);
|
const udoc = await user.getById('system', -1);
|
||||||
if (!udoc) {
|
if (!udoc) {
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ Difficulty by algorithm: 算法计算的难度
|
|||||||
Difficulty displayed: 显示的难度
|
Difficulty displayed: 显示的难度
|
||||||
Difficulty: 难度
|
Difficulty: 难度
|
||||||
Disabled: 关闭
|
Disabled: 关闭
|
||||||
|
Disconnected from the server. Reconnecting...: 到服务器的连接断开。正在重新连接...
|
||||||
Discussion {1} not found.: 讨论 {1} 不存在。
|
Discussion {1} not found.: 讨论 {1} 不存在。
|
||||||
Discussion category {1} already exists.: 讨论分类 {1} 已经存在。
|
Discussion category {1} already exists.: 讨论分类 {1} 已经存在。
|
||||||
Discussion category {1} not found.: 讨论分类 {1} 不存在。
|
Discussion category {1} not found.: 讨论分类 {1} 不存在。
|
||||||
|
|||||||
@@ -8,16 +8,19 @@ $header-gap-mini-domain = ($header-bg-height-mini - $nav-item-height - $header-l
|
|||||||
&.warning
|
&.warning
|
||||||
background: #ffe1a6
|
background: #ffe1a6
|
||||||
|
|
||||||
|
.header__options
|
||||||
|
background-image: url(./header-background.png)
|
||||||
|
background-image: url(./header-background@2x.png)
|
||||||
|
|
||||||
.header
|
.header
|
||||||
position: relative
|
position: relative
|
||||||
height: rem($header-bg-height)
|
height: rem($header-bg-height)
|
||||||
background: #5eafca url(./header-background.png) no-repeat center top
|
background: #5eafca no-repeat center top
|
||||||
background-size: cover
|
background-size: cover
|
||||||
transition: height .5s
|
transition: height .5s
|
||||||
transition-timing-function: ease-out-cubic
|
transition-timing-function: ease-out-cubic
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
+retina()
|
+retina()
|
||||||
background-image: url(./header-background.png)
|
|
||||||
background-size: rem($header-bg-width $header-bg-height)
|
background-size: rem($header-bg-width $header-bg-height)
|
||||||
|
|
||||||
&.mini
|
&.mini
|
||||||
@@ -29,6 +32,12 @@ $header-gap-mini-domain = ($header-bg-height-mini - $nav-item-height - $header-l
|
|||||||
.header__content
|
.header__content
|
||||||
display: none
|
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
|
.header__logo
|
||||||
display: block
|
display: block
|
||||||
width: rem($header-logo-width-real)
|
width: rem($header-logo-width-real)
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -107,6 +107,12 @@
|
|||||||
.nav__list--secondary .nav__item--round
|
.nav__list--secondary .nav__item--round
|
||||||
margin-left: rem(5px)
|
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
|
.nav__logo
|
||||||
display: block
|
display: block
|
||||||
width: 0
|
width: 0
|
||||||
|
|||||||
@@ -6,35 +6,50 @@ let autoHideTimer = null;
|
|||||||
|
|
||||||
export default class Notification {
|
export default class Notification {
|
||||||
static success(message, duration) {
|
static success(message, duration) {
|
||||||
Notification.show({ type: 'success', message, duration });
|
return Notification.show({ type: 'success', message, duration });
|
||||||
}
|
}
|
||||||
|
|
||||||
static info(message, duration) {
|
static info(message, duration) {
|
||||||
Notification.show({ type: 'info', message, duration });
|
return Notification.show({ type: 'info', message, duration });
|
||||||
}
|
}
|
||||||
|
|
||||||
static warn(message, duration) {
|
static warn(message, duration) {
|
||||||
Notification.show({ type: 'warn', message, duration });
|
return Notification.show({ type: 'warn', message, duration });
|
||||||
}
|
}
|
||||||
|
|
||||||
static error(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 }) {
|
static show({
|
||||||
if (lastNotification) {
|
avatar, title, message, type = 'info', duration = 3000,
|
||||||
|
actionButtons = [],
|
||||||
|
}) {
|
||||||
|
if (duration && lastNotification) {
|
||||||
Notification.hide();
|
Notification.hide();
|
||||||
}
|
}
|
||||||
const $n = $(tpl`<div class="notification ${type} hide">${message}</div>`)
|
const $dom = $(tpl`<div class="notification ${type} hide"></div>`);
|
||||||
|
if (avatar) $(tpl`<img width="64" height="64" class="avatar" src="${avatar}"></img>`).appendTo($dom);
|
||||||
|
if (title) $(tpl`${title} `).appendTo($dom);
|
||||||
|
$(tpl`${message}`).appendTo($dom);
|
||||||
|
actionButtons.forEach((button) => {
|
||||||
|
$(tpl`<button class="${button.class}">${button.name}</button>`).appendTo($dom);
|
||||||
|
});
|
||||||
|
console.log($dom);
|
||||||
|
const $n = $dom
|
||||||
.css('z-index', zIndexManager.getNext())
|
.css('z-index', zIndexManager.getNext())
|
||||||
.appendTo('body');
|
.appendTo('body');
|
||||||
$n.width(); // force reflow
|
$n.width(); // force reflow
|
||||||
$n.removeClass('hide');
|
$n.removeClass('hide');
|
||||||
|
if (duration) {
|
||||||
lastNotification = $n;
|
lastNotification = $n;
|
||||||
autoHideTimer = setTimeout(Notification.hide, duration);
|
autoHideTimer = setTimeout(Notification.hide, duration);
|
||||||
}
|
}
|
||||||
|
return $n;
|
||||||
|
}
|
||||||
|
|
||||||
static hide() {
|
static hide($node) {
|
||||||
|
if (!$node) {
|
||||||
if (!lastNotification) {
|
if (!lastNotification) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -46,5 +61,9 @@ export default class Notification {
|
|||||||
$n.addClass('hide');
|
$n.addClass('hide');
|
||||||
setTimeout(() => $n.remove(), 200);
|
setTimeout(() => $n.remove(), 200);
|
||||||
lastNotification = null;
|
lastNotification = null;
|
||||||
|
} else {
|
||||||
|
$node.addClass('hide');
|
||||||
|
setTimeout(() => $node.remove(), 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,13 @@
|
|||||||
&:hover
|
&:hover
|
||||||
text-decoration: none
|
text-decoration: none
|
||||||
|
|
||||||
|
for n in (1..21)
|
||||||
|
.user-profile-bg--{n}
|
||||||
|
background-image: url('backgrounds/' + n + '.jpg')
|
||||||
|
|
||||||
|
.user-profile-bg--thumbnail-{n}
|
||||||
|
background-image: url('backgrounds/thumbnail/' + n + '.jpg')
|
||||||
|
|
||||||
for key, _ in $badge-bg-color
|
for key, _ in $badge-bg-color
|
||||||
.badge--{key}
|
.badge--{key}
|
||||||
background-color: $badge-bg-color[key] !important
|
background-color: $badge-bg-color[key] !important
|
||||||
|
|||||||
Reference in new issue
Block a user