This repository has no description
1// client-side comment threading, ported from NewCommentList in appview/models/comment.go.
2// bobbin returns comments flat; we group replies under the top-level comment their
3// replyTo strongRef points at, and sort everything oldest-first.
4
5export interface CommentView {
6 uri: string;
7 // content hash, needed to strongRef this comment when replying to it
8 cid?: string;
9 rkey: string;
10 authorDid: string;
11 authorHandle: string;
12 createdAt: string;
13 body: string;
14 bodyHtml: string | null;
15}
16
17export interface CommentThread {
18 self: CommentView;
19 replies: CommentView[];
20}
21
22// pairs a rendered view with the parent uri it replies to (null for top-level).
23export interface ThreadInput {
24 comment: CommentView;
25 replyTo: string | null;
26}
27
28const byCreatedAt = (a: CommentView, b: CommentView) => a.createdAt.localeCompare(b.createdAt);
29
30export function buildCommentThreads(inputs: ThreadInput[]): CommentThread[] {
31 const threads = new Map<string, CommentThread>();
32 const orphanReplies: ThreadInput[] = [];
33
34 for (const input of inputs) {
35 if (input.replyTo === null) {
36 threads.set(input.comment.uri, { self: input.comment, replies: [] });
37 }
38 }
39
40 for (const input of inputs) {
41 if (input.replyTo === null) continue;
42 const parent = threads.get(input.replyTo);
43 if (parent) {
44 parent.replies.push(input.comment);
45 } else {
46 // parent not found (e.g. legacy/cross-collection ref): surface as top-level
47 // so nothing is dropped.
48 orphanReplies.push(input);
49 }
50 }
51
52 for (const orphan of orphanReplies) {
53 threads.set(orphan.comment.uri, { self: orphan.comment, replies: [] });
54 }
55
56 const list = [...threads.values()];
57 list.sort((a, b) => byCreatedAt(a.self, b.self));
58 for (const thread of list) thread.replies.sort(byCreatedAt);
59 return list;
60}