feat: unread markers for channels and DMs

- Read states store: smarter hasUnread compares against latest message
- ConversationList: orange dot + bold name for unread DMs
- DMChat: auto-mark-read when viewing conversation
- WS handler: mark DM read on new message while active+focused
This commit is contained in:
2026-07-06 17:56:19 +00:00
parent c9a213ab8a
commit 4e429ca920
6 changed files with 76 additions and 11 deletions
+7 -4
View File
@@ -7,6 +7,7 @@ import { InviteModal } from './InviteModal.tsx';
import { ChannelSettingsModal } from './ChannelSettingsModal.tsx';
import { useNotificationSettingsStore } from '../stores/notificationSettings.ts';
import { useReadStatesStore } from '../stores/readStates.ts';
import { useMessageStore } from '../stores/message.ts';
import { useContextMenu } from './ContextMenu.tsx';
import { api } from '../lib/api.ts';
@@ -79,8 +80,9 @@ export function ChannelList() {
const fetchNotifSettings = useNotificationSettingsStore((state) => state.fetchSettings);
const setNotifLevel = useNotificationSettingsStore((state) => state.setLevel);
const readStates = useReadStatesStore((state) => state.states);
const fetchReadStates = useReadStatesStore((state) => state.fetchStates);
const hasUnread = useReadStatesStore((state) => state.hasUnread);
const messagesByChannel = useMessageStore((state) => state.messagesByChannel);
const { showMenu, MenuPortal } = useContextMenu();
@@ -179,8 +181,9 @@ export function ChannelList() {
return level === 'mentions' ? '🔔@' : '🔔';
};
const hasUnread = (channelId: string): boolean => {
return !(channelId in readStates);
const getLatestMessageId = (channelId: string): string | undefined => {
const msgs = messagesByChannel[channelId] || [];
return msgs.length > 0 ? msgs[msgs.length - 1].id : undefined;
};
// --- Channel context menu ---
@@ -292,7 +295,7 @@ export function ChannelList() {
{channel.type === 'forum' ? '■' : channel.type === 'calendar' ? '○' : channel.type === 'docs' ? '☰' : channel.type === 'list' ? '☑' : '#'}
</span>
<span className="truncate flex-1">{channel.name}</span>
{hasUnread(channel.id) && (
{hasUnread(channel.id, getLatestMessageId(channel.id)) && (
<span className="text-gb-orange text-xs font-bold" title="Unread messages"></span>
)}
<span className="flex items-center gap-1 text-xs pointer-events-none group-hover:pointer-events-auto">