新消息提示:标签页标题滚动+未读数、favicon角标、桌面通知(含何时不弹的判断)
三件套,参照Slack/Telegram Web/WhatsApp Web等主流IM网页客户端的标准做法: - lib/notifications/tabIndicator.ts:标签页被切到后台且有未读时,标题在 "📨 (N条新消息) <原标题>"上做左向滚动,用户切回来的瞬间(visibilitychange) 立即恢复原标题,不等下一次未读数变化才发现。favicon右上角画一个红色圆点+ 白色数字角标(canvas绘制成PNG data URL,写回现有<link rel="icon">,不是 另加一个可能被浏览器忽略的竞争链接)。两者都由AppShell.tsx里已经算好的 同一个unreadMessages号驱动,不另起一套可能对不上的计数。 - lib/notifications/push.ts:真正的浏览器桌面通知(Notification API)。只 在标签页还开着(哪怕最小化/切到后台)时生效——做不到浏览器整个关掉也能收到, 那需要Service Worker+推送订阅+后端配合,是完全另一个量级的工程。 抑制规则("什么时候不弹"): · 消息来自自己(比如另一个标签页/设备同步过来的回显) · 正在看的就是这个会话本身(会话已打开且标签页确实可见——两者都要满足, 一个被切到后台的标签页哪怕挂载着这个会话也不算"正在看") · 该会话设置了"消息免打扰"(contact.private.mute_notifications) · 浏览器未授予通知权限 同一会话的连续通知用tag合并显示为一条,不会叠加刷屏;点击通知会聚焦标签页 并跳转到对应会话(通过stores/notificationNav.ts中转,push.ts在React之外, 没有useNavigate可用,做法与stores/calls.ts、messageJump.ts是同一个模式)。 判断"是否是真的新消息"用的是防抖后的seq对比(防止刚打开一个从未见过的会话时, 历史消息分批到达被逐条误判成"新消息")——第一次见到某个会话时记录基准而不是 照单全收。**这里踩到一个真实bug并当场修复**:最初版本把"第一次见到这个会话" 一律当成"历史消息,不通知",结果全新好友的第一条消息(对这个会话来说seq=1, 同时也是"第一次见到")被误判成历史消息静默吞掉——用两个全新互加好友的测试 账号实机复现后,改成额外看消息时间戳:一分钟以内的算真正新消息,无论是不是 第一次见到这个会话,否则才当历史记录处理。 实机验证(systest01/systest02两个全新互加好友的测试账号,全部通过mock Notification类捕获+SDK本地状态直接读取的方式验证,因为这个自动化浏览器环境 本身把Notification.permission锁死成denied,没法测试真正弹出的系统通知): - 真实新消息:通知的title/body/tag均正确(对方昵称/消息预览文本/会话id) - 免打扰会话:确认不弹 - 已打开且标签页可见:确认不弹 - 标题栏滚动+favicon角标:实机确认能正确开始/停止滚动、favicon生成的是 真实可解码的64×64 PNG Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
8a593f50c3
commit
cd6b7675d4
@@ -26,6 +26,7 @@ import { availableActions, recallRemainingSeconds, type MessageAction } from '@/
|
||||
import { isRecalled } from '@/lib/messages/dispatch';
|
||||
import { renderDrafty } from '@/lib/messages/drafty';
|
||||
import { buildMentionedContent, detectMentionTrigger, type PendingMention } from '@/lib/messages/mentions';
|
||||
import { setActiveConversationTopic } from '@/lib/notifications/push';
|
||||
import { sendFile, sendImage, sendVoice } from '@/lib/tinode/attachments';
|
||||
import { useChatList, useTinode, useTopicMessages } from '@/lib/tinode/hooks';
|
||||
import { forwardMessage, sendPrebuiltContent, sendReply, sendText } from '@/lib/tinode/send';
|
||||
@@ -242,6 +243,16 @@ export function ConversationView() {
|
||||
setHighlightedSeq(null);
|
||||
}, [topicId]);
|
||||
|
||||
// Tells lib/notifications/push.ts "the user is already looking at this one" so a message arriving
|
||||
// here doesn't also pop a desktop notification for something already on screen -- cleared on
|
||||
// switch/unmount so leaving this screen (closing the tab, navigating to /contacts, etc.) doesn't
|
||||
// leave some OTHER, no-longer-open conversation permanently exempt from notifications.
|
||||
useEffect(() => {
|
||||
if (!topicId) return undefined;
|
||||
setActiveConversationTopic(topicId);
|
||||
return () => setActiveConversationTopic(null);
|
||||
}, [topicId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoScrollRef.current) {
|
||||
streamRef.current?.scrollTo({ top: streamRef.current.scrollHeight });
|
||||
|
||||
Reference in New Issue
Block a user