diff --git a/web/src/components/MessageList.tsx b/web/src/components/MessageList.tsx
index 5d77118..8b6fb54 100644
--- a/web/src/components/MessageList.tsx
+++ b/web/src/components/MessageList.tsx
@@ -28,6 +28,8 @@ export function MessageList() {
const loadingMoreByChat = useChatStore((s) => s.loadingMoreByChat);
const loadMoreMessages = useChatStore((s) => s.loadMoreMessages);
const unreadBoundaryByChat = useChatStore((s) => s.unreadBoundaryByChat);
+ const focusedMessageIdByChat = useChatStore((s) => s.focusedMessageIdByChat);
+ const setFocusedMessage = useChatStore((s) => s.setFocusedMessage);
const chats = useChatStore((s) => s.chats);
const setReplyToMessage = useChatStore((s) => s.setReplyToMessage);
const updateChatPinnedMessage = useChatStore((s) => s.updateChatPinnedMessage);
@@ -64,6 +66,7 @@ export function MessageList() {
}, [chats, forwardQuery]);
const unreadBoundaryCount = activeChatId ? (unreadBoundaryByChat[activeChatId] ?? 0) : 0;
const unreadBoundaryIndex = unreadBoundaryCount > 0 ? Math.max(0, messages.length - unreadBoundaryCount) : -1;
+ const focusedMessageId = activeChatId ? (focusedMessageIdByChat[activeChatId] ?? null) : null;
const hasMore = Boolean(activeChatId && hasMoreByChat[activeChatId]);
const isLoadingMore = Boolean(activeChatId && loadingMoreByChat[activeChatId]);
const selectedMessages = useMemo(
@@ -104,6 +107,19 @@ export function MessageList() {
return () => window.clearInterval(interval);
}, [pendingDelete]);
+ useEffect(() => {
+ if (!activeChatId || !focusedMessageId) {
+ return;
+ }
+ const element = document.getElementById(`message-${focusedMessageId}`);
+ if (!element) {
+ return;
+ }
+ element.scrollIntoView({ behavior: "smooth", block: "center" });
+ const timer = window.setTimeout(() => setFocusedMessage(activeChatId, null), 2500);
+ return () => window.clearTimeout(timer);
+ }, [activeChatId, focusedMessageId, messages.length, setFocusedMessage]);
+
if (!activeChatId) {
return
Select a chat
;
}
@@ -287,9 +303,10 @@ export function MessageList() {
) : null}
{
if (selectedIds.size > 0) {
toggleSelected(message.id);
diff --git a/web/src/pages/ChatsPage.tsx b/web/src/pages/ChatsPage.tsx
index 317f5d4..1e60744 100644
--- a/web/src/pages/ChatsPage.tsx
+++ b/web/src/pages/ChatsPage.tsx
@@ -17,6 +17,7 @@ export function ChatsPage() {
const activeChatId = useChatStore((s) => s.activeChatId);
const chats = useChatStore((s) => s.chats);
const setActiveChatId = useChatStore((s) => s.setActiveChatId);
+ const setFocusedMessage = useChatStore((s) => s.setFocusedMessage);
const loadMessages = useChatStore((s) => s.loadMessages);
const activeChat = chats.find((chat) => chat.id === activeChatId);
const isReadOnlyChannel = Boolean(activeChat && activeChat.type === "channel" && activeChat.my_role === "member");
@@ -147,6 +148,7 @@ export function ChatsPage() {
key={`search-msg-${message.id}`}
onClick={() => {
setActiveChatId(message.chat_id);
+ setFocusedMessage(message.chat_id, message.id);
setSearchOpen(false);
}}
>
diff --git a/web/src/store/chatStore.ts b/web/src/store/chatStore.ts
index e22f902..057449e 100644
--- a/web/src/store/chatStore.ts
+++ b/web/src/store/chatStore.ts
@@ -12,6 +12,7 @@ interface ChatState {
typingByChat: Record;
replyToByChat: Record;
unreadBoundaryByChat: Record;
+ focusedMessageIdByChat: Record;
loadChats: (query?: string) => Promise;
setActiveChatId: (chatId: number | null) => void;
loadMessages: (chatId: number) => Promise;
@@ -44,6 +45,7 @@ interface ChatState {
applyPresenceEvent: (chatId: number, userId: number, isOnline: boolean, lastSeenAt?: string) => void;
setDraft: (chatId: number, text: string) => void;
clearDraft: (chatId: number) => void;
+ setFocusedMessage: (chatId: number, messageId: number | null) => void;
}
export const useChatStore = create((set, get) => ({
@@ -56,6 +58,7 @@ export const useChatStore = create((set, get) => ({
typingByChat: {},
replyToByChat: {},
unreadBoundaryByChat: {},
+ focusedMessageIdByChat: {},
loadChats: async (query) => {
const chats = await getChats(query);
const currentActive = get().activeChatId;
@@ -342,5 +345,12 @@ export const useChatStore = create((set, get) => ({
const next = { ...state.draftsByChat };
delete next[chatId];
return { draftsByChat: next };
- })
+ }),
+ setFocusedMessage: (chatId, messageId) =>
+ set((state) => ({
+ focusedMessageIdByChat: {
+ ...state.focusedMessageIdByChat,
+ [chatId]: messageId
+ }
+ }))
}));