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:
@@ -3,14 +3,18 @@ import { api } from '../lib/api';
|
||||
|
||||
interface ReadStatesState {
|
||||
states: Record<string, string>; // channel_id -> last_read_message_id
|
||||
convStates: Record<string, string>; // conversation_id -> last_read_message_id
|
||||
loaded: boolean;
|
||||
fetchStates: () => Promise<void>;
|
||||
markRead: (channelId: string, messageId: string) => Promise<void>;
|
||||
hasUnread: (channelId: string) => boolean; // true if no read state (never viewed)
|
||||
markConvRead: (conversationId: string, messageId: string) => void;
|
||||
hasUnread: (channelId: string, latestMessageId?: string) => boolean;
|
||||
hasConvUnread: (conversationId: string, latestMessageId?: string) => boolean;
|
||||
}
|
||||
|
||||
export const useReadStatesStore = create<ReadStatesState>()((set, get) => ({
|
||||
states: {},
|
||||
convStates: {},
|
||||
loaded: false,
|
||||
|
||||
fetchStates: async () => {
|
||||
@@ -33,9 +37,27 @@ export const useReadStatesStore = create<ReadStatesState>()((set, get) => ({
|
||||
}
|
||||
},
|
||||
|
||||
hasUnread: (channelId: string): boolean => {
|
||||
markConvRead: (conversationId: string, messageId: string) => {
|
||||
set((state) => ({
|
||||
convStates: { ...state.convStates, [conversationId]: messageId },
|
||||
}));
|
||||
},
|
||||
|
||||
hasUnread: (channelId: string, latestMessageId?: string): boolean => {
|
||||
const state = get().states;
|
||||
// No read state means the user has never viewed this channel
|
||||
return !(channelId in state);
|
||||
const lastRead = state[channelId];
|
||||
// never viewed: unread if there are messages
|
||||
if (!lastRead) return !!latestMessageId;
|
||||
// viewed but newer messages exist
|
||||
if (latestMessageId && lastRead !== latestMessageId) return true;
|
||||
return false;
|
||||
},
|
||||
|
||||
hasConvUnread: (conversationId: string, latestMessageId?: string): boolean => {
|
||||
const state = get().convStates;
|
||||
const lastRead = state[conversationId];
|
||||
if (!lastRead) return !!latestMessageId;
|
||||
if (latestMessageId && lastRead !== latestMessageId) return true;
|
||||
return false;
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useTypingStore } from './typing.ts';
|
||||
import { useVoiceStore } from './voice.ts';
|
||||
import { useAuthStore } from './auth.ts';
|
||||
import { useConversationStore } from './conversation.ts';
|
||||
import { useReadStatesStore } from './readStates.ts';
|
||||
import type { Message } from './message.ts';
|
||||
import type { ConversationMessage } from './conversation.ts';
|
||||
import type { Channel } from './channel.ts';
|
||||
@@ -132,6 +133,11 @@ export const useWebSocketStore = create<WebSocketState>((set, get) => ({
|
||||
if (payload.conversation_id) {
|
||||
const msg = payload as unknown as ConversationMessage;
|
||||
useConversationStore.getState().addMessage(msg);
|
||||
// auto-mark DM as read if this conversation is active
|
||||
const activeConvId = useConversationStore.getState().activeConversationId;
|
||||
if (msg.conversation_id === activeConvId && document.hasFocus()) {
|
||||
useReadStatesStore.getState().markConvRead(msg.conversation_id, msg.id);
|
||||
}
|
||||
} else {
|
||||
const msg = payload as unknown as Message;
|
||||
addMessage(msg);
|
||||
|
||||
Reference in New Issue
Block a user