"use client"; import { useEffect, useRef } from "react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; export type ChatMessageView = { id: string; sequence: string; body: string; authorUserId: string | null; createdAt: Date | string; editedAt?: Date | string | null; deletedAt?: Date | string | null; status?: string; revision?: number; replyCount?: number; parentMessageId?: string | null; }; export function ChatMessageList({ messages, onViewed, onThread, onEdit, onDelete, canModerate = false, currentUserId, readOnly = false, pending = [], }: { messages: ChatMessageView[]; onViewed?: (sequence: string) => void; onThread?: (id: string) => void; onEdit?: (message: ChatMessageView) => void; onDelete?: (message: ChatMessageView) => void; canModerate?: boolean; currentUserId?: string; readOnly?: boolean; pending?: { key: string; body: string; failed: boolean; error?: string; retry: () => void; }[]; }) { const viewed = useRef(onViewed); useEffect(() => { viewed.current = onViewed; }, [onViewed]); const list = useRef(null); useEffect(() => { if ( !viewed.current || !list.current || typeof IntersectionObserver === "undefined" ) return; const observer = new IntersectionObserver( (entries) => { if (document.visibilityState !== "visible") return; const visible = entries .filter((entry) => entry.isIntersecting) .map((entry) => (entry.target as HTMLElement).dataset.sequence) .filter((sequence): sequence is string => !!sequence); const highest = visible.reduce( (max, sequence) => max === null || BigInt(sequence) > BigInt(max) ? sequence : max, null, ); if (highest) viewed.current?.(highest); }, { threshold: 0.5 }, ); list.current .querySelectorAll("[data-sequence]") .forEach((element) => observer.observe(element)); return () => observer.disconnect(); }, [messages, pending]); return (
    {messages.map((message) => (
  1. {message.authorUserId} {message.editedAt && ( (edited) )}

    {message.deletedAt || message.status === "deleted" ? "Message deleted" : message.body}

    {!readOnly && !message.deletedAt && message.status !== "deleted" && (
    {onEdit && message.authorUserId === currentUserId && ( )} {onDelete && (message.authorUserId === currentUserId || canModerate) && ( )}
    )} {onThread && ( )}
  2. ))} {pending.map((message) => (
  3. {message.body}

    {message.failed ? message.error : "Sending…"}

    {message.failed && ( )}
  4. ))}
); }