web: add jump-to-message navigation from thread panel
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-03-08 22:42:24 +03:00
parent 16f3d91c3b
commit 10eb82c82d
2 changed files with 23 additions and 5 deletions

View File

@@ -16,7 +16,7 @@ Legend:
7. Chat Creation - `DONE` (private/group/channel) 7. Chat Creation - `DONE` (private/group/channel)
8. Messages (base) - `DONE` (send/read/edit/delete/delete for all; 7-day edit window enforced and covered by integration tests; group UI shows sender names over bubbles + sender avatars on incoming message clusters) 8. Messages (base) - `DONE` (send/read/edit/delete/delete for all; 7-day edit window enforced and covered by integration tests; group UI shows sender names over bubbles + sender avatars on incoming message clusters)
9. Message Types - `PARTIAL` (text/photo/video/docs/audio/voice/circle; GIF/stickers via dedicated system missing) 9. Message Types - `PARTIAL` (text/photo/video/docs/audio/voice/circle; GIF/stickers via dedicated system missing)
10. Reply/Quote/Threads - `PARTIAL` (reply + quote-like UI + thread panel with nested replies, no dedicated full thread navigation yet) 10. Reply/Quote/Threads - `PARTIAL` (reply + quote-like UI + thread panel with nested replies; web thread panel now supports direct `Jump in chat` navigation to any thread message; dedicated full standalone thread route is still pending)
11. Forwarding - `DONE` (single + bulk + without author) 11. Forwarding - `DONE` (single + bulk + without author)
12. Pinning - `DONE` (message/chat pin-unpin) 12. Pinning - `DONE` (message/chat pin-unpin)
13. Reactions - `DONE` 13. Reactions - `DONE`

View File

@@ -310,6 +310,11 @@ export function MessageList() {
container.scrollTo({ top: container.scrollHeight, behavior: "smooth" }); container.scrollTo({ top: container.scrollHeight, behavior: "smooth" });
} }
function jumpToMessageInChat(messageId: number) {
setFocusedMessage(chatId, messageId);
setThreadRootId(null);
}
async function ensureReactionsLoaded(messageId: number) { async function ensureReactionsLoaded(messageId: number) {
if (reactionsByMessage[messageId]) { if (reactionsByMessage[messageId]) {
return; return;
@@ -956,13 +961,26 @@ export function MessageList() {
const indent = Math.min(6, depth) * 14; const indent = Math.min(6, depth) * 14;
return ( return (
<div <div
className={`rounded-xl border px-3 py-2 ${isRoot ? "border-sky-400/60 bg-sky-500/10" : "border-slate-700/70 bg-slate-800/60"}`} className={`cursor-pointer rounded-xl border px-3 py-2 transition-colors ${isRoot ? "border-sky-400/60 bg-sky-500/10" : "border-slate-700/70 bg-slate-800/60 hover:bg-slate-800/80"}`}
key={`thread-${threadMessage.id}`} key={`thread-${threadMessage.id}`}
onClick={() => jumpToMessageInChat(threadMessage.id)}
style={{ marginLeft: `${indent}px` }} style={{ marginLeft: `${indent}px` }}
> >
<p className={`mb-1 text-[11px] ${own ? "text-sky-300" : "text-slate-400"}`}> <div className="mb-1 flex items-center justify-between gap-2">
{isRoot ? "Original message" : `Reply • level ${depth}`} {formatTime(threadMessage.created_at)} <p className={`text-[11px] ${own ? "text-sky-300" : "text-slate-400"}`}>
</p> {isRoot ? "Original message" : `Reply • level ${depth}`} {formatTime(threadMessage.created_at)}
</p>
<button
className="rounded bg-slate-700/80 px-2 py-1 text-[10px] font-semibold text-slate-200 hover:bg-slate-600"
onClick={(event) => {
event.stopPropagation();
jumpToMessageInChat(threadMessage.id);
}}
type="button"
>
Jump in chat
</button>
</div>
<div className={own ? "text-slate-100" : "text-slate-200"}> <div className={own ? "text-slate-100" : "text-slate-200"}>
{renderMessageContent(threadMessage, { {renderMessageContent(threadMessage, {
attachments: attachmentsByMessage[threadMessage.id] ?? [], attachments: attachmentsByMessage[threadMessage.id] ?? [],