feat(search): focus and highlight found message in chat

- store focused message id per chat
- scroll to target message and highlight it after search selection
- clear focus automatically after short timeout
This commit is contained in:
2026-03-08 02:53:03 +03:00
parent 874f9da12c
commit eef89983e0
3 changed files with 31 additions and 2 deletions

View File

@@ -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 <div className="flex h-full items-center justify-center text-slate-300/80">Select a chat</div>;
}
@@ -287,9 +303,10 @@ export function MessageList() {
) : null}
<div className={`mb-2 flex ${own ? "justify-end" : "justify-start"}`}>
<div
id={`message-${message.id}`}
className={`max-w-[86%] rounded-2xl px-3 py-2 shadow-sm md:max-w-[72%] ${
own ? "rounded-br-md bg-sky-500/90 text-slate-950" : "rounded-bl-md border border-slate-700/60 bg-slate-900/80 text-slate-100"
} ${isSelected ? "ring-2 ring-sky-400/80" : ""}`}
} ${isSelected ? "ring-2 ring-sky-400/80" : ""} ${focusedMessageId === message.id ? "ring-2 ring-amber-300" : ""}`}
onClick={() => {
if (selectedIds.size > 0) {
toggleSelected(message.id);