feat(threads): support nested replies in thread API and panel
All checks were successful
CI / test (push) Successful in 31s
All checks were successful
CI / test (push) Successful in 31s
This commit is contained in:
@@ -87,6 +87,30 @@ export function MessageList() {
|
||||
}, [activeChatId, messagesByChat]);
|
||||
|
||||
const messagesMap = useMemo(() => new Map(messages.map((m) => [m.id, m])), [messages]);
|
||||
const threadRows = useMemo(() => {
|
||||
if (!threadRootId || !threadMessages.length) return [];
|
||||
const byId = new Map(threadMessages.map((message) => [message.id, message]));
|
||||
const memoDepth = new Map<number, number>();
|
||||
const calcDepth = (message: Message): number => {
|
||||
if (memoDepth.has(message.id)) return memoDepth.get(message.id) ?? 0;
|
||||
if (message.id === threadRootId) {
|
||||
memoDepth.set(message.id, 0);
|
||||
return 0;
|
||||
}
|
||||
const parentId = message.reply_to_message_id ?? null;
|
||||
const parent = parentId ? byId.get(parentId) : undefined;
|
||||
if (!parent) {
|
||||
memoDepth.set(message.id, 1);
|
||||
return 1;
|
||||
}
|
||||
const depth = Math.min(12, calcDepth(parent) + 1);
|
||||
memoDepth.set(message.id, depth);
|
||||
return depth;
|
||||
};
|
||||
return threadMessages
|
||||
.map((message) => ({ message, depth: calcDepth(message) }))
|
||||
.sort((a, b) => a.message.id - b.message.id);
|
||||
}, [threadMessages, threadRootId]);
|
||||
const activeChat = chats.find((chat) => chat.id === activeChatId);
|
||||
const forwardTargets = useMemo(() => {
|
||||
const q = forwardQuery.trim().toLowerCase();
|
||||
@@ -800,16 +824,18 @@ export function MessageList() {
|
||||
{threadError ? <p className="text-xs text-red-400">{threadError}</p> : null}
|
||||
{!threadLoading && !threadError && threadMessages.length === 0 ? <p className="text-xs text-slate-400">No replies yet</p> : null}
|
||||
<div className="space-y-2">
|
||||
{threadMessages.map((threadMessage) => {
|
||||
{threadRows.map(({ message: threadMessage, depth }) => {
|
||||
const own = threadMessage.sender_id === me?.id;
|
||||
const isRoot = threadMessage.id === threadRootId;
|
||||
const indent = Math.min(6, depth) * 14;
|
||||
return (
|
||||
<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"}`}
|
||||
key={`thread-${threadMessage.id}`}
|
||||
style={{ marginLeft: `${indent}px` }}
|
||||
>
|
||||
<p className={`mb-1 text-[11px] ${own ? "text-sky-300" : "text-slate-400"}`}>
|
||||
{isRoot ? "Original message" : "Reply"} • {formatTime(threadMessage.created_at)}
|
||||
{isRoot ? "Original message" : `Reply • level ${depth}`} • {formatTime(threadMessage.created_at)}
|
||||
</p>
|
||||
<div className={own ? "text-slate-100" : "text-slate-200"}>
|
||||
{renderMessageContent(threadMessage, {
|
||||
|
||||
Reference in New Issue
Block a user