新消息提示:标签页标题滚动+未读数、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
@@ -0,0 +1,20 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
/**
|
||||
* Transient cross-component instruction: `lib/notifications/push.ts` lives outside React (it reacts
|
||||
* to raw SDK topic events, same as topics.ts itself) and has no way to call `useNavigate()` directly
|
||||
* when a desktop Notification is clicked. Matches this codebase's established pattern for exactly
|
||||
* this shape of problem (stores/calls.ts, stores/messageJump.ts) rather than threading a navigate
|
||||
* callback down into a non-React module.
|
||||
*/
|
||||
interface NotificationNavState {
|
||||
pendingTopic: string | null;
|
||||
requestNavigate: (topic: string) => void;
|
||||
clear: () => void;
|
||||
}
|
||||
|
||||
export const useNotificationNavStore = create<NotificationNavState>((set) => ({
|
||||
pendingTopic: null,
|
||||
requestNavigate: (topic) => set({ pendingTopic: topic }),
|
||||
clear: () => set({ pendingTopic: null }),
|
||||
}));
|
||||
Reference in New Issue
Block a user