From 350371d6a3f84ff485cf8f97236dc284d3161d18 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Wed, 11 Jan 2023 00:41:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=A2=9E=E5=8A=A0icon=E5=B0=8F?= =?UTF-8?q?=E7=BA=A2=E7=82=B9=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E9=9F=B3=EF=BC=8C=E4=BC=98=E5=8C=96=E9=80=9A=E7=9F=A5=E5=9C=BA?= =?UTF-8?q?=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/shared/cache/cache.ts | 20 +++- client/shared/index.tsx | 1 + .../com.msgbyte.notify/assets/sounds_bing.mp3 | Bin 0 -> 5760 bytes .../plugins/com.msgbyte.notify/package.json | 7 +- .../plugins/com.msgbyte.notify/src/bubble.ts | 45 ++++++++ .../plugins/com.msgbyte.notify/src/notify.ts | 102 ++++++++++++------ .../com.msgbyte.notify/src/translate.ts | 4 + .../plugins/com.msgbyte.notify/tsconfig.json | 1 - client/web/src/plugin/common/index.ts | 1 + pnpm-lock.yaml | 16 ++- 10 files changed, 160 insertions(+), 37 deletions(-) create mode 100644 client/web/plugins/com.msgbyte.notify/assets/sounds_bing.mp3 create mode 100644 client/web/plugins/com.msgbyte.notify/src/bubble.ts diff --git a/client/shared/cache/cache.ts b/client/shared/cache/cache.ts index 8555b5a8..f4d16978 100644 --- a/client/shared/cache/cache.ts +++ b/client/shared/cache/cache.ts @@ -1,5 +1,10 @@ import { ChatConverseInfo, fetchConverseInfo } from '../model/converse'; -import { findGroupInviteByCode, GroupInvite } from '../model/group'; +import { + findGroupInviteByCode, + getGroupBasicInfo, + GroupBasicInfo, + GroupInvite, +} from '../model/group'; import { fetchLocalStaticRegistryPlugins, fetchRegistryPlugins, @@ -41,6 +46,19 @@ export async function getCachedConverseInfo( return data; } +/** + * 获取缓存的邀请码信息 + */ +export async function getCachedBaseGroupInfo( + groupId: string +): Promise { + const data = await queryClient.fetchQuery(['baseGroupInfo', groupId], () => + getGroupBasicInfo(groupId) + ); + + return data; +} + /** * 获取缓存的邀请码信息 */ diff --git a/client/shared/index.tsx b/client/shared/index.tsx index 311260c6..104ef987 100644 --- a/client/shared/index.tsx +++ b/client/shared/index.tsx @@ -9,6 +9,7 @@ export type { AppSocket } from './api/socket'; export { getCachedUserInfo, getCachedConverseInfo, + getCachedBaseGroupInfo, getCachedGroupInviteInfo, getCachedRegistryPlugins, } from './cache/cache'; diff --git a/client/web/plugins/com.msgbyte.notify/assets/sounds_bing.mp3 b/client/web/plugins/com.msgbyte.notify/assets/sounds_bing.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..2827addd44bca5667ab5fd4604181e34e1425fc2 GIT binary patch literal 5760 zcmeH~2~ZSQ8pmHx4>QxvpaY^JN75k294Lc`0#RrLN%Jb198;>3v)r%zW`SD!zB@#4kn z*Kggr)zEPN{{6pR;o}56#TDU$@3TVH^7Kx-oU*yy60cZZq#X z2k-8$cV6X{ZM9{2)57ZG{Xf#{lh#CmX)`~d+HwNkl?~rC{q^z{`Cg@fUakW`9L354 zHXk~YEjS;$r)+aF_V>a9D&$q^l9F?e=*Un$1Luth7#sP})R*b^lP|t0XMG$Ko^`l> zW|Y{n9?4}NCl{1!2H^m2@cu-Mzls#1iP_WeTtCIt)x70bz%q=@Bpbl6zQ7h)AgB%JFBPpi_vgt2 z5U@}LtT!C?iFr(mDnExa_LtagH`g`_zy62o*6{iGfs^ScrGW54Lyx^|;8hH+%-3aA zoBw(#WTVt}OrFC-}hjM<^hzP#`$!mkV#H#~B#=8N7T?aeg19 z$yU=8s0*s6e@Hz?)d9wO+u7Ct6)8+OCO67aCT8Sydts6ya|(&* zH{NV#?)0tN;^gYhKwttE`;`o3+yrDlm5+0SS0>-aUuQ1ZQVi@K-S5bueL-l#q9Am^G5}%>UdhwK> z9h^6{wiS%$S<_sS&m@)K$)9sdU!%&Ku!1!+jtOWRKBv^tBc;mSL1hCJHLwEf=ReF3 zQJ3>1K$x(Eq$ihRYfZefDsJ5{DwxRlA99v zW60sLZu2U?bdV0F6r56QPW*s^k!Ss@uhmc>DV9%FWm8GudyBw^XKopp(h>LG$>C5r z19=nx9OtE0^DX`(J3F{gNZ}e&qI z0a20P5GTX>@lVA3ND}KAN8Z{DTlCzvdV6V;k)AH;jdwUsi?+NPzs_Q+Bmsl37J7b#-0Jzk#*N?) z)g|WiJVn_rv8ybi;?@T?7}PUlDm7$9xd5o)((sKYpnQa0TAYJDu(jY5D0qRS?UGvT z>*N?pdTYyZo}g?AepMs(s(`-r=Qv754C8o;MkzJf;al5Oq&QF&jXGo>ty0~zud!NU z${s(d_3Y#rmDDxlI6rGO+wsd9k*XT#tYL#8NV6=C0SqAqYL&4b6O;rmGIF)*zSg7} z!AENpWA?rZ5OjG)zNMu70LN+6vU&JrR>*M%u^gwB({l&vnf+gSxJj@QyyD62b-WBf z3Dowjs?j~v^GM64;};1`T7eLQ$~jJrB7(6zt%4k2tf$=ygo3?MF+ILi$+to!^~u4h|22FXx&sTF7gptkQcK{cWe7cdSlX8OOP&W&eat zq-h^(14rdFPD|ZWzu|N{Sp-1rcU4-8nv~Z)?Kj9tz@lhjY=>`Bm$ATc2mKB`Ms^7A zNHRt`^ia=zEjta1V059-_xAPCIE|qkca;7wJ;ow?$hoU!L$L5tvCP)MkvRDQ#}cCV zIbm1?iOD+jh@D0QJHDY0j7#q}?xCKawCotD2kX!S?m2nUjwQ<8)`PNPSQvwO!yal| zA8^fSIOJ%0nNz=t^)LyS0*S#lQ0nAETLtzuN4JW#GzkYlA~ool+uw%H>)?)~$Nee0UC+r2+H-P-crgTJ+J d@b4D)d>c-6{oVWCy5Ds{w0{uH_?r#=e*oZXZ|wj8 literal 0 HcmV?d00001 diff --git a/client/web/plugins/com.msgbyte.notify/package.json b/client/web/plugins/com.msgbyte.notify/package.json index 19abc0cb..895d7086 100644 --- a/client/web/plugins/com.msgbyte.notify/package.json +++ b/client/web/plugins/com.msgbyte.notify/package.json @@ -3,5 +3,10 @@ "main": "src/index.tsx", "version": "0.0.0", "private": true, - "dependencies": {} + "dependencies": { + "tinycon": "^0.6.8" + }, + "devDependencies": { + "@types/tinycon": "^0.6.3" + } } diff --git a/client/web/plugins/com.msgbyte.notify/src/bubble.ts b/client/web/plugins/com.msgbyte.notify/src/bubble.ts new file mode 100644 index 00000000..ad0b0fd4 --- /dev/null +++ b/client/web/plugins/com.msgbyte.notify/src/bubble.ts @@ -0,0 +1,45 @@ +import tinycon from 'tinycon'; + +/** + * 设置小红点 + * @param num 小红点数 + */ +let bubbleNum = 0; +export function setBubble(num: number) { + bubbleNum = num; + if (num < 0) { + num = 0; + } + tinycon.setBubble(num >= 100 ? 99 : num); +} + +/** + * 增加小红点数量 + */ +export function incBubble() { + setBubble(bubbleNum + 1); +} + +const hiddenProperty = + 'hidden' in document + ? 'hidden' + : 'webkitHidden' in document + ? 'webkitHidden' + : 'mozHidden' in document + ? 'mozHidden' + : null; +const visibilityChangeEvent = hiddenProperty?.replace( + /hidden/i, + 'visibilitychange' +); +const onVisibilityChange = function () { + if (!document[hiddenProperty ?? '']) { + // 显示标签页时清空 + tinycon.setBubble(0); + } else { + // 隐藏标签页 + } +}; +if (typeof visibilityChangeEvent === 'string') { + document.addEventListener(visibilityChangeEvent, onVisibilityChange); +} diff --git a/client/web/plugins/com.msgbyte.notify/src/notify.ts b/client/web/plugins/com.msgbyte.notify/src/notify.ts index 84e2bcf2..7b852de8 100644 --- a/client/web/plugins/com.msgbyte.notify/src/notify.ts +++ b/client/web/plugins/com.msgbyte.notify/src/notify.ts @@ -2,11 +2,16 @@ import { regSocketEventListener, getGlobalState, getCachedUserInfo, + getCachedConverseInfo, + getCachedBaseGroupInfo, getServiceWorkerRegistration, navigate, } from '@capital/common'; import { Translate } from './translate'; import { hasSilent } from './silent'; +import { incBubble, setBubble } from './bubble'; + +const TAG = 'tailchat-message'; export function initNotify() { if (Notification.permission === 'default') { @@ -15,7 +20,6 @@ export function initNotify() { const registration: ServiceWorkerRegistration | null = getServiceWorkerRegistration(); - if (registration) { registration.addEventListener('notificationclick', (e: any) => { const tag = e.notification.tag; @@ -25,6 +29,13 @@ export function initNotify() { }); } + let isBlur = false; + window.addEventListener('focus', () => { + setBubble(0); // 点击时清空 + isBlur = false; + }); + window.addEventListener('blur', () => (isBlur = true)); + regSocketEventListener({ eventName: 'chat.message.add', eventFn: (message) => { @@ -32,46 +43,59 @@ export function initNotify() { const currentUserId = getGlobalState()?.user.info._id; if (hasSilent(converseId)) { - // 免打扰 + // 手动设置了当前会话免打扰 return; } - if (currentUserId !== message.author) { - // 创建通知 + if (currentUserId === message.author) { + // 忽略本人消息 + return; + } + + const hidden = window.document.hidden ?? false; + if (hidden || isBlur) { + // 如果当前不是活跃窗口或处于隐藏状态,则创建通知 - // TODO: 需要增加所在群组 if (Notification.permission === 'granted') { - Promise.all([getCachedUserInfo(message.author)]).then( - ([userInfo]) => { - const nickname = userInfo?.nickname ?? ''; - const icon = userInfo?.avatar ?? undefined; - const content = message.content; - - const title = `${Translate.from} ${nickname}`; - const options: NotificationOptions = { - body: content, - icon, - tag: 'tailchat-message', - renotify: true, - data: message, - }; + // TODO: 需要增加显示所在群组 + Promise.all([ + getCachedUserInfo(message.author), + message.groupId + ? getCachedBaseGroupInfo(message.groupId).then((d) => d.name) + : Promise.resolve(Translate.dm), + ]).then(([userInfo, scopeName]) => { + const nickname = userInfo?.nickname ?? ''; + const icon = userInfo?.avatar ?? undefined; + const content = message.content; - if (registration && registration.showNotification) { - registration.showNotification(title, options); - } else { - // fallback - const notification = new Notification(title, options); - notification.onclick = (e: any) => { - const tag = e.target.tag; - const data = e.target.data; - - handleMessageNotifyClick(tag, data); - }; - } + const title = `${Translate.from} [${scopeName}] ${nickname}`; + const options: NotificationOptions = { + body: content, + icon, + tag: TAG, + renotify: true, + data: message, + silent: true, // 因为有提示音了,所以禁音默认音 + }; + + if (registration && registration.showNotification) { + registration.showNotification(title, options); + } else { + // fallback + const notification = new Notification(title, options); + notification.onclick = (e: any) => { + const tag = e.target.tag; + const data = e.target.data; + + handleMessageNotifyClick(tag, data); + }; } - ); + }); } + + incBubble(); } + tryPlayNotificationSound(); // 不管当前是不是处于活跃状态,都发出提示音 }, }); } @@ -97,6 +121,18 @@ function handleMessageNotifyClick(tag, data) { navigate(`/main/personal/converse/${converseId}`); } } +} - console.log(tag, data); +/** + * 尝试播放通知声音 + */ +function tryPlayNotificationSound() { + try { + const audio = new Audio( + '/plugins/com.msgbyte.notify/assets/sounds_bing.mp3' + ); + audio.play(); + } catch (err) { + console.error(err); + } } diff --git a/client/web/plugins/com.msgbyte.notify/src/translate.ts b/client/web/plugins/com.msgbyte.notify/src/translate.ts index 89adea9c..befd0f3c 100644 --- a/client/web/plugins/com.msgbyte.notify/src/translate.ts +++ b/client/web/plugins/com.msgbyte.notify/src/translate.ts @@ -10,4 +10,8 @@ export const Translate = { 'zh-CN': '来自', 'en-US': 'From', }), + dm: localTrans({ + 'zh-CN': '私信', + 'en-US': 'DM', + }), }; diff --git a/client/web/plugins/com.msgbyte.notify/tsconfig.json b/client/web/plugins/com.msgbyte.notify/tsconfig.json index 465a28b5..6dfff4f0 100644 --- a/client/web/plugins/com.msgbyte.notify/tsconfig.json +++ b/client/web/plugins/com.msgbyte.notify/tsconfig.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "baseUrl": "./src", "esModuleInterop": true, "jsx": "react", "paths": { diff --git a/client/web/src/plugin/common/index.ts b/client/web/src/plugin/common/index.ts index ef0a584e..06ce2a89 100644 --- a/client/web/src/plugin/common/index.ts +++ b/client/web/src/plugin/common/index.ts @@ -37,6 +37,7 @@ export { getServiceUrl, getCachedUserInfo, getCachedConverseInfo, + getCachedBaseGroupInfo, localTrans, getLanguage, sharedEvent, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 225da449..5c105454 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -644,7 +644,13 @@ importers: miao-lang: 1.0.5 client/web/plugins/com.msgbyte.notify: - specifiers: {} + specifiers: + '@types/tinycon': ^0.6.3 + tinycon: ^0.6.8 + dependencies: + tinycon: 0.6.8 + devDependencies: + '@types/tinycon': 0.6.3 client/web/plugins/com.msgbyte.openapi: specifiers: {} @@ -11687,6 +11693,10 @@ packages: dependencies: '@types/node': 18.11.16 + /@types/tinycon/0.6.3: + resolution: {integrity: sha512-TC42m8KAyp3AqyZKRXVkk5Qy+oIU8zo2U3362i16Qan0JgZNzLawO7oYnin4BJOy8FSZfOadYYvUWQnpaXoZwg==} + dev: true + /@types/trusted-types/2.0.2: resolution: {integrity: sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==} dev: true @@ -33655,6 +33665,10 @@ packages: resolution: {integrity: sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==} dev: false + /tinycon/0.6.8: + resolution: {integrity: sha512-bF8Lxm4JUXF6Cw0XlZdugJ44GV575OinZ0Pt8vQPr8ooNqd2yyNkoFdCHzmdpHlgoqfSLfcyk4HDP1EyllT+ug==} + dev: false + /title-case/2.1.1: resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==} dependencies: