This repository has no description
1export interface CommentView {
2 uri: string;
3 cid?: string;
4 rkey: string;
5 authorDid: string;
6 authorHandle: string;
7 createdAt: string;
8 body: string;
9 bodyHtml: string | null;
10 // the record is gone but we keep it to anchor its replies
11 deleted?: boolean;
12}
13
14export interface CommentThread {
15 self: CommentView;
16 replies: CommentView[];
17}
18
19// pairs a rendered view with the parent uri it replies to
20export interface ThreadInput {
21 comment: CommentView;
22 replyTo: string | null;
23 // lets us reply to a parent even after it's deleted
24 replyToCid?: string;
25}
26
27const byCreatedAt = (a: CommentView, b: CommentView) => a.createdAt.localeCompare(b.createdAt);
28
29// stand-in for a deleted parent
30export function deletedComment(uri: string, createdAt: string, cid?: string): CommentView {
31 return {
32 uri,
33 cid,
34 rkey: uri.split("/").pop() ?? "",
35 authorDid: "",
36 authorHandle: "",
37 createdAt,
38 body: "",
39 bodyHtml: null,
40 deleted: true
41 };
42}
43
44export function buildCommentThreads(inputs: ThreadInput[]): CommentThread[] {
45 const threads = new Map<string, CommentThread>();
46
47 for (const { comment, replyTo } of inputs) {
48 if (replyTo === null) {
49 threads.set(comment.uri, { self: comment, replies: [] });
50 }
51 }
52
53 for (const { comment, replyTo, replyToCid } of inputs) {
54 if (replyTo === null) continue;
55 let parent = threads.get(replyTo);
56 if (!parent) {
57 parent = { self: deletedComment(replyTo, comment.createdAt), replies: [] };
58 threads.set(replyTo, parent);
59 }
60 if (parent.self.deleted && replyToCid && !parent.self.cid) {
61 parent.self.cid = replyToCid;
62 }
63 parent.replies.push(comment);
64 }
65
66 const list = [...threads.values()].sort((a, b) => byCreatedAt(a.self, b.self));
67 for (const thread of list) thread.replies.sort(byCreatedAt);
68 return list;
69}