From ca00dda57557f80877b0aff38bb0e3dd6d68f1aa Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 20 Sep 2021 22:42:24 +0800 Subject: [PATCH] core: support mention notification --- packages/hydrooj/locales/zh.yaml | 1 + packages/hydrooj/package.json | 2 +- packages/hydrooj/src/handler/discussion.ts | 20 ++++++++++++++++++- packages/hydrooj/src/handler/home.ts | 6 +----- packages/hydrooj/src/model/message.ts | 3 ++- .../components/message/index.page.js | 6 ++++++ .../MessagePadDialogueContentContainer.jsx | 15 +++++++++++++- .../MessagePadDialogueListContainer.jsx | 8 +++++++- .../components/monaco/languages/markdown.ts | 14 ++++++------- packages/ui-default/locales/zh.yaml | 2 ++ packages/ui-default/package.json | 2 +- .../ui-default/pages/home_messages.page.jsx | 5 ++++- packages/ui-default/utils/api.ts | 2 +- packages/ui-default/utils/i18n.js | 5 +---- 14 files changed, 67 insertions(+), 24 deletions(-) diff --git a/packages/hydrooj/locales/zh.yaml b/packages/hydrooj/locales/zh.yaml index 122997067..1ba705f04 100644 --- a/packages/hydrooj/locales/zh.yaml +++ b/packages/hydrooj/locales/zh.yaml @@ -812,6 +812,7 @@ Use admin specificed: 由管理员指定 Use algorithm calculated: 使用算法计算 Use average of above: 使用上面的平均值 User {0} already exists.: 用户 {0} 已存在。 +User {0} mentioned you in {1:link}: 用户 {0} 在 {1:link} 中提到了你。 User {0} not found.: 用户 {0} 不存在。 User can join this domain by visiting the following URL: 用户可以访问此链接来加入此域 User ID: 用户 ID diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index aa70a8aa8..afe2956cc 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.36.13", + "version": "2.36.14", "bin": "bin/hydrooj.js", "main": "src/loader", "module": "src/loader", diff --git a/packages/hydrooj/src/handler/discussion.ts b/packages/hydrooj/src/handler/discussion.ts index 9d94fc652..65982047f 100644 --- a/packages/hydrooj/src/handler/discussion.ts +++ b/packages/hydrooj/src/handler/discussion.ts @@ -6,6 +6,7 @@ import paginate from '../lib/paginate'; import { PERM, PRIV } from '../model/builtin'; import * as discussion from '../model/discussion'; import * as document from '../model/document'; +import message from '../model/message'; import * as oplog from '../model/oplog'; import * as system from '../model/system'; import user from '../model/user'; @@ -224,7 +225,15 @@ class DiscussionDetailHandler extends DiscussionHandler { async postReply(domainId: string, did: ObjectID, content: string) { this.checkPerm(PERM.PERM_REPLY_DISCUSSION); await this.limitRate('add_discussion', 3600, 60); - // Notify related users + const targets = new Set(Array.from(content.matchAll(/@\[\]\(\/user\/(\d+)\)/g)).map((i) => +i[1])); + const uids = Object.keys(await user.getList(domainId, Array.from(targets))).map((i) => +i); + const msg = JSON.stringify({ + message: 'User {0} mentioned you in {1:link}', + params: [this.user.uname, this.request.path], + }); + for (const uid of uids) { + message.send(1, uid, msg, message.FLAG_RICHTEXT | message.FLAG_UNREAD); + } await discussion.addReply(domainId, did, this.user._id, content, this.request.ip); this.back(); } @@ -234,6 +243,15 @@ class DiscussionDetailHandler extends DiscussionHandler { async postTailReply(domainId: string, drid: ObjectID, content: string) { this.checkPerm(PERM.PERM_REPLY_DISCUSSION); await this.limitRate('add_discussion', 3600, 60); + const targets = new Set(Array.from(content.matchAll(/@\[\]\(\/user\/(\d+)\)/g)).map((i) => +i[1])); + const uids = Object.keys(await user.getList(domainId, Array.from(targets))).map((i) => +i); + const msg = JSON.stringify({ + message: 'User {0} mentioned you in {1:link}', + params: [this.user.uname, this.request.path], + }); + for (const uid of uids) { + message.send(1, uid, msg, message.FLAG_RICHTEXT | message.FLAG_UNREAD); + } await discussion.addTailReply(domainId, drid, this.user._id, content, this.request.ip); this.back(); } diff --git a/packages/hydrooj/src/handler/home.ts b/packages/hydrooj/src/handler/home.ts index e553ad472..80bcc9ffc 100644 --- a/packages/hydrooj/src/handler/home.ts +++ b/packages/hydrooj/src/handler/home.ts @@ -368,12 +368,8 @@ class HomeMessagesHandler extends Handler { } parsed[target].messages.push(m); } - const path = [ - ['Hydro', 'homepage'], - ['home_messages', null], - ]; await user.setById(this.user._id, { unreadMsg: 0 }); - this.response.body = { messages: parsed, path }; + this.response.body = { messages: parsed }; this.response.template = 'home_messages.html'; } diff --git a/packages/hydrooj/src/model/message.ts b/packages/hydrooj/src/model/message.ts index ce17bcace..6338e30da 100644 --- a/packages/hydrooj/src/model/message.ts +++ b/packages/hydrooj/src/model/message.ts @@ -10,6 +10,7 @@ const coll = db.collection('message'); class MessageModel { static FLAG_UNREAD = 1; static FLAG_ALERT = 2; + static FLAG_RICHTEXT = 4; @ArgMethod static async send( @@ -33,7 +34,7 @@ class MessageModel { @ArgMethod static async getByUser(uid: number): Promise { - return await coll.find({ $or: [{ from: uid }, { to: uid }] }).sort('_id', 1).toArray(); + return await coll.find({ $or: [{ from: uid }, { to: uid }] }).sort('_id', 1).limit(1000).toArray(); } static async getMany(query: FilterQuery, sort: any, page: number, limit: number): Promise { diff --git a/packages/ui-default/components/message/index.page.js b/packages/ui-default/components/message/index.page.js index f1e36c807..25e05b3f3 100644 --- a/packages/ui-default/components/message/index.page.js +++ b/packages/ui-default/components/message/index.page.js @@ -40,6 +40,12 @@ const messagePage = new AutoloadPage('messagePage', (pagename) => {

${i18n(msg.mdoc.content)}

`, }).open(); + } else if (msg.udoc._id === 1 && msg.mdoc.flag & 4) { + new VjNotification({ + message: 'You received a system message, click here to view.', + duration: 15000, + action: () => window.open(`/home/messages?uid=${msg.udoc._id}`, '_blank'), + }).show(); } else { // Is message new VjNotification({ diff --git a/packages/ui-default/components/messagepad/MessagePadDialogueContentContainer.jsx b/packages/ui-default/components/messagepad/MessagePadDialogueContentContainer.jsx index d25675c4e..2f5aacfc9 100644 --- a/packages/ui-default/components/messagepad/MessagePadDialogueContentContainer.jsx +++ b/packages/ui-default/components/messagepad/MessagePadDialogueContentContainer.jsx @@ -41,11 +41,24 @@ export default connect(mapStateToProps)(class MessagePadDialogueContentContainer } renderContent(msg) { + // TODO: FLAG_RICHTEXT if (msg.from === 1) { // Is system message try { const data = JSON.parse(msg.content); - return i18n(data.message, ...data.params); + const str = i18n(data.message).replace(/\{([^{}]+)\}/g, (match, key) => `%substitude%${key}%substitude%`); + const arr = str.split('%substitude%'); + data.params = data.params || {}; + for (let i = 1; i < arr.length; i += 2) { + if (arr[i].endsWith(':link')) { + const link = data.params[arr[i].split(':link')[0]]; + if (!link) continue; + arr[i] = {link}; + } else { + arr[i] = {data.params[arr[i]]}; + } + } + return arr; } catch (e) { } return i18n(msg.content); } diff --git a/packages/ui-default/components/messagepad/MessagePadDialogueListContainer.jsx b/packages/ui-default/components/messagepad/MessagePadDialogueListContainer.jsx index b0db10b67..67fbcb2bd 100644 --- a/packages/ui-default/components/messagepad/MessagePadDialogueListContainer.jsx +++ b/packages/ui-default/components/messagepad/MessagePadDialogueListContainer.jsx @@ -3,6 +3,7 @@ import _ from 'lodash'; import { connect } from 'react-redux'; import 'jquery-scroll-lock'; +import i18n from 'vj/utils/i18n'; import ListItem from './DialogueListItemComponent'; const mapStateToProps = (state) => ({ @@ -38,7 +39,12 @@ export default connect(mapStateToProps, mapDispatchToProps)(class MessagePadDial this.props.handleClick(dialogue._id)} diff --git a/packages/ui-default/components/monaco/languages/markdown.ts b/packages/ui-default/components/monaco/languages/markdown.ts index fce52b108..56f56aeb4 100644 --- a/packages/ui-default/components/monaco/languages/markdown.ts +++ b/packages/ui-default/components/monaco/languages/markdown.ts @@ -123,12 +123,10 @@ monaco.editor.registerCommand('hydro.openUserPage', (accesser, uid) => { monaco.languages.registerCodeLensProvider('markdown', { async provideCodeLenses(model) { const users = model.findMatches('\\[\\]\\(/user/(\\d+)\\)', true, true, true, null, true); - const { data } = await api(` - query { - users(ids: [${users.map((i) => i.matches[1]).join(',')}]) { - _id - uname - } + const { data } = await api(gql` + users(ids: ${users.map((i) => +i.matches[1])}) { + _id + uname } `); return { @@ -172,6 +170,7 @@ monaco.languages.registerCompletionItemProvider('markdown', { _id uname avatarUrl + priv } `, ['data', 'users']); return { @@ -179,8 +178,9 @@ monaco.languages.registerCompletionItemProvider('markdown', { label: `@${i.uname} (UID=${i._id})`, kind: monaco.languages.CompletionItemKind.Property, documentation: { value: `![monaco_avatar](${i.avatarUrl})`, isTrusted: true }, - insertText: `[](/user/${i._id}) `, + insertText: `@[](/user/${i._id}) `, range, + sortText: i.priv === 0 ? '0' : '1', tags: i.priv === 0 ? [1] : [], })), }; diff --git a/packages/ui-default/locales/zh.yaml b/packages/ui-default/locales/zh.yaml index e96efb915..3fb008a6c 100644 --- a/packages/ui-default/locales/zh.yaml +++ b/packages/ui-default/locales/zh.yaml @@ -1,5 +1,6 @@ __langname: 简体中文 '"{data}" copied to clipboard!': “{data}”已复制到剪贴板! +'[Richtext message]': '[富文本消息]' '{} is a moderator of this domain.': '{}是这个域的管理者之一。' '{0} comments': '{0} 条评论' '{0} discussions': '{0} 条讨论' @@ -873,6 +874,7 @@ You have uncompleted domain creation operation. You can click "Continue" to fini You haven't attended this contest yet.: 您还没有参加本次比赛。 You haven't claimed this homework yet.: 您还未认领过该作业。 You need to enter the invitation code to join the domain.: 您需要输入邀请码来加入此域。 +You received a system message, click here to view.: 您收到一条系统通知,点击查看。 You're not logged in.: 您没有登录。 You've already attended this contest.: 您已经参加本次比赛。 You've already claimed this homework.: 您已认领过该作业。 diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 3b9161ce2..974e6703a 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.21.11", + "version": "4.22.0", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", diff --git a/packages/ui-default/pages/home_messages.page.jsx b/packages/ui-default/pages/home_messages.page.jsx index ec43ef749..8f776276d 100644 --- a/packages/ui-default/pages/home_messages.page.jsx +++ b/packages/ui-default/pages/home_messages.page.jsx @@ -44,7 +44,10 @@ const page = new NamedPage('home_messages', () => { avatar: msg.udoc.avatarUrl, message: msg.mdoc.content, duration: 15000, - action: () => createDialog(msg.udoc), + action: () => store.dispatch({ + type: 'DIALOGUES_SWITCH_TO', + payload: msg.udoc._id, + }), }).show(); }; diff --git a/packages/ui-default/utils/api.ts b/packages/ui-default/utils/api.ts index 0cc8dc67e..2a70ae302 100644 --- a/packages/ui-default/utils/api.ts +++ b/packages/ui-default/utils/api.ts @@ -15,7 +15,7 @@ export default async (q: string, path: string[] = []) => { export const gql = ( pieces: TemplateStringsArray, - ...templates: (string | number)[] + ...templates: (string | number | string[] | number[])[] ) => { let res = ''; for (let i = 0; i < pieces.length; i++) { diff --git a/packages/ui-default/utils/i18n.js b/packages/ui-default/utils/i18n.js index 11417aca5..bc5b6464d 100644 --- a/packages/ui-default/utils/i18n.js +++ b/packages/ui-default/utils/i18n.js @@ -1,10 +1,7 @@ import substitute from './substitute'; export default function i18n(str, ...params) { - if (window.LOCALES && window.LOCALES[str]) { - return substitute(window.LOCALES[str], params); - } - return substitute(str, params); + return substitute(window.LOCALES?.[str] || str, params); } window.Hydro.utils.i18n = i18n;