| 137 | 137 | | setMessages((now) => applyLive(now, event, channelId) as Shown[]); |
| 138 | 138 | | }, [channelId]); |
| 139 | 139 | | |
| 140 | + | // Read up to the newest message while the panel is open and in view, as |
| 141 | + | // the conversation in Chat does: it is the same direct message, so what |
| 142 | + | // was read here is not left unread there. |
| 143 | + | const lastRead = useRef<string | null>(null); |
| 144 | + | useEffect(() => { |
| 145 | + | if (!open || !channelId) return; |
| 146 | + | const markRead = () => { |
| 147 | + | if (document.visibilityState !== "visible") return; |
| 148 | + | const newest = [...messages].reverse().find((m) => !m.pending); |
| 149 | + | if (!newest || newest.id === lastRead.current) return; |
| 150 | + | lastRead.current = newest.id; |
| 151 | + | void chatApi(slug, { intent: "read", channel_id: channelId, id: newest.id }); |
| 152 | + | window.dispatchEvent(new CustomEvent("g1t:chat-read", { detail: channelId })); |
| 153 | + | }; |
| 154 | + | const timer = setTimeout(markRead, 400); |
| 155 | + | document.addEventListener("visibilitychange", markRead); |
| 156 | + | window.addEventListener("focus", markRead); |
| 157 | + | return () => { |
| 158 | + | clearTimeout(timer); |
| 159 | + | document.removeEventListener("visibilitychange", markRead); |
| 160 | + | window.removeEventListener("focus", markRead); |
| 161 | + | }; |
| 162 | + | }, [open, channelId, messages, slug]); |
| 163 | + | |
| 140 | 164 | | const ask = useCallback( |
| 141 | 165 | | async (text: string, pathname: string) => { |
| 142 | 166 | | const body = text.trim(); |