fix(comments) : show linked conversations in the normal comment list
This commit is contained in:
@@ -14,14 +14,13 @@ import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@/components/ui/accordion";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import { CommentSkeleton, CommentThreadSkeleton } from "./comment-skeleton";
|
||||
import { CommentSkeleton } from "./comment-skeleton";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { TableCell, TableRow } from "@/components/ui/table";
|
||||
import type { CommentHistoryItem, CommentImage, CommentItem, CommentViewer } from "@/lib/comments/types";
|
||||
import { CommentImageView } from "./comment-image";
|
||||
import { CommentComposer } from "./comment-composer";
|
||||
import { commentRequest, commentSignInHref } from "./client";
|
||||
import { useCommentEvents } from "./use-comment-events";
|
||||
import { useCommentFeed } from "./use-comment-feed";
|
||||
|
||||
// A single minute clock for all timestamps, with a stable SSR snapshot.
|
||||
@@ -55,7 +54,7 @@ function CommentTimestamp({ value }: { value: string }) {
|
||||
}
|
||||
return <time dateTime={value} title={value} className="text-xs text-muted-foreground">{now > 0
|
||||
? relativeTime()
|
||||
: `${value.slice(0, 16).replace("T", " ")} UTC`}</time>;
|
||||
: `${value.slice(0, 16).replace("T")}`}</time>;
|
||||
}
|
||||
|
||||
export function CommentImages({ images }: { images: CommentImage[] }) {
|
||||
@@ -87,7 +86,7 @@ export function CommentImages({ images }: { images: CommentImage[] }) {
|
||||
}
|
||||
|
||||
function CommentText({ text }: { text: string }) {
|
||||
return <p className="whitespace-pre-wrap break-words text-sm leading-relaxed">{text.split(/(https?:\/\/[^\s<>]+)/g).map((part, i) =>
|
||||
return <p className="whitespace-pre-wrap wrap-break-word text-sm leading-relaxed">{text.split(/(https?:\/\/[^\s<>]+)/g).map((part, i) =>
|
||||
/^https?:\/\//.test(part) ? <a key={i} href={part} rel="nofollow ugc noopener noreferrer" target="_blank" className="text-primary underline underline-offset-4">{part}</a> : part)}</p>;
|
||||
}
|
||||
|
||||
@@ -233,7 +232,7 @@ export function CommentCard({ item, viewer, onChange, revision = 0, inbox = fals
|
||||
{item.version > 1 && canReadHistory && <Button variant="link" size="xs" onClick={() => setHistory(true)}>{"แก้ไขแล้ว"}</Button>}
|
||||
</div>
|
||||
{/* Inbox replies reach across the 280px content column and 24px cell padding. */}
|
||||
{!inbox && optionsMenu && <div className={cn("shrink-0", inboxReply && "md:absolute md:top-2 md:right-[-304px]")}>{optionsMenu}</div>}
|
||||
{!inbox && optionsMenu && <div className={cn("shrink-0", inboxReply && "md:absolute md:top-2 md:-right-76")}>{optionsMenu}</div>}
|
||||
</div>
|
||||
<div className={cn("contents", fullWidthMobile && "max-sm:col-span-2 max-sm:flex max-sm:min-w-0 max-sm:flex-col max-sm:gap-2")}>
|
||||
{item.replyToName && <p className="text-xs text-muted-foreground">{"ตอบกลับ"} {item.replyToName}</p>}
|
||||
@@ -339,32 +338,3 @@ function CommentReplyTree({ items, viewer, onChange, focusReplyId, moderationDet
|
||||
}
|
||||
return <div className="flex flex-col gap-4">{branch(null)}</div>;
|
||||
}
|
||||
|
||||
export function CommentThread({ rootId, expectedTarget, onChange, focusReplyId }: { focusReplyId?: string; rootId: string; expectedTarget: string; onChange: () => void | Promise<unknown> }) {
|
||||
const [data, setData] = useState<{ item: CommentItem; viewer: CommentViewer | null } | null>(null);
|
||||
const [error, setError] = useState("");
|
||||
const [revision, setRevision] = useState(0);
|
||||
const lastThread = useRef("");
|
||||
const load = useCallback(async (forceReplies = false) => {
|
||||
try {
|
||||
const result = await commentRequest<{ item: CommentItem; viewer: CommentViewer | null }>(`/api/comments/${rootId}`);
|
||||
if (result.item.target !== expectedTarget) throw new Error("การสนทนานี้เป็นของไกด์อื่น");
|
||||
const payload = JSON.stringify(result);
|
||||
if (lastThread.current !== payload) {
|
||||
lastThread.current = payload; setData(result); setRevision((value) => value + 1);
|
||||
} else if (forceReplies) setRevision((value) => value + 1);
|
||||
setError("");
|
||||
} catch (cause) { lastThread.current = ""; setData(null); setError(cause instanceof Error ? cause.message : "ไม่สามารถโหลดการสนทนาได้"); }
|
||||
}, [rootId, expectedTarget]);
|
||||
useCommentEvents({ target: expectedTarget }, load);
|
||||
useEffect(() => {
|
||||
void Promise.resolve().then(() => load());
|
||||
|
||||
}, [load]);
|
||||
return <div className="flex min-w-0 flex-col gap-3" aria-busy={!data && !error}>
|
||||
{error && <Alert variant="destructive"><AlertDescription>{error}</AlertDescription></Alert>}
|
||||
{data && <CommentCard item={data.item} viewer={data.viewer} moderationDetails={Boolean(data.viewer?.admin)} defaultExpanded focusReplyId={focusReplyId} revision={revision} onChange={async () => { await load(true); await onChange(); }} />}
|
||||
{!data && !error && <CommentThreadSkeleton />}
|
||||
{error && <Button variant="outline" onClick={() => void load()}>{"ลองใหม่"}</Button>}
|
||||
</div>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user