/** * ThreadPanel -- Side panel for threaded replies on a parent message. * Renders the parent message preview, a scrollable reply list, and a composer. */ import { useEffect, useRef } from 'react' import { useMessages, isWriterRole } from 'deepspace' import { useReactions } from 'deepspace' import { useUserLookup } from 'deepspace' import { useUser } from 'deepspace' import type { Message } from 'deepspace' import type { RecordData } from 'deepspace' import { MessageItem } from './MessageItem' import { MessageInput } from './MessageInput' interface ThreadPanelProps { channelId: string parentMessageId: string onClose: () => void } export function ThreadPanel({ channelId, parentMessageId, onClose }: ThreadPanelProps) { const { messages: allMessages } = useMessages(channelId) const { messages: replies, send, edit, remove, } = useMessages(channelId, { parentMessageId, }) const { getReactionsForMessage, toggle: toggleReaction } = useReactions(channelId) const { getUser } = useUserLookup() const { user } = useUser() const canWrite = isWriterRole(user?.role) const bottomRef = useRef(null) const parentMessage = allMessages.find((m: RecordData) => m.recordId === parentMessageId) useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [replies.length]) const parentAuthor = parentMessage ? getUser(parentMessage.data.authorId || parentMessage.createdBy) : null const parentTime = parentMessage ? new Date(parentMessage.createdAt).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit', }) : '' return (

Thread

{parentMessage && (
{parentAuthor?.name ?? 'Unknown'} {parentTime}

{parentMessage.data.content}

{replies.length} {replies.length === 1 ? 'reply' : 'replies'}
)}
{replies.map((msg: RecordData) => ( toggleReaction(msg.recordId, emoji)} onOpenThread={() => {}} onEdit={(newContent) => edit(msg.recordId, newContent)} onDelete={() => remove(msg.recordId)} isFirstInGroup /> ))}
{canWrite && ( send(content, parentMessageId)} placeholder="Reply..." /> )}
) }