CommentThread.tsx
Raw
1import config from "../config.ts";
2import { displayName } from "../lib/users.ts";
3import type { SessionUser } from "../middleware/session.ts";
4import { Avatar } from "./Avatar.tsx";
5import { DateWithEdited } from "./DateWithEdited.tsx";
6import { ReactionBar, type ReactionCount } from "./ReactionBar.tsx";
7
8export interface ThreadComment {
9 id: number;
10 author_id: number | null;
11 author_username: string;
12 author_avatar_version: number | null;
13 body: string;
14 bodyHtml: string;
15 created_at: string;
16 edited_at: string | null;
17}
18
19interface CommentThreadProps {
20 user: SessionUser | null;
21 comments: ThreadComment[];
22 commentReactions: Map<number, ReactionCount[]>;
23 baseUrl: string;
24 parentStatus: string;
25}
26
27export function CommentThread({
28 user,
29 comments,
30 commentReactions,
31 baseUrl,
32 parentStatus,
33}: CommentThreadProps) {
34 const reactUrl = `${baseUrl}/react`;
35 const canEditComment = (c: ThreadComment) =>
36 user != null &&
37 (user.isAdmin || (user.id === c.author_id && parentStatus === "open"));
38 return (
39 <>
40 {comments.map((comment) => (
41 <div class="timeline-item">
42 <div class="timeline-author">
43 <Avatar
44 userId={comment.author_id}
45 version={comment.author_avatar_version}
46 size={24}
47 />
48 <strong safe>
49 {displayName(comment.author_username)}
50 </strong>
51 <div class="timeline-author-right">
52 {canEditComment(comment) && (
53 <details class="inline-edit-details">
54 <summary class="btn btn-xs">
55 <span class="when-closed">Edit</span>
56 <span class="when-open">
57 Stop editing
58 </span>
59 </summary>
60 </details>
61 )}
62 <DateWithEdited
63 date={comment.created_at}
64 editedAt={comment.edited_at}
65 />
66 </div>
67 </div>
68 {canEditComment(comment) && (
69 <div class="inline-edit-form-area">
70 <form
71 method="POST"
72 action={`${baseUrl}/comments/${comment.id}/edit`}
73 class="inline-edit-form"
74 >
75 <div class="form-group">
76 <textarea
77 class="form-input"
78 name="edit_body"
79 rows="6"
80 maxlength={config.MAX_TEXT_BODY_BYTES}
81 safe
82 >
83 {comment.body}
84 </textarea>
85 </div>
86 <div class="form-actions">
87 <button
88 type="submit"
89 class="btn btn-sm btn-primary"
90 >
91 Save
92 </button>
93 </div>
94 </form>
95 </div>
96 )}
97 <div class="timeline-body markdown-body">
98 {comment.bodyHtml as "safe"}
99 </div>
100 <ReactionBar
101 reactions={commentReactions.get(comment.id) ?? []}
102 postUrl={reactUrl}
103 commentId={comment.id}
104 user={user}
105 />
106 </div>
107 ))}
108
109 {!!user && (
110 <div class="timeline-item timeline-item-new">
111 <h3 class="section-title">Add a comment</h3>
112 <form method="POST" action={`${baseUrl}/comments`}>
113 <div class="form-group">
114 <textarea
115 name="body"
116 rows="6"
117 maxlength={config.MAX_TEXT_BODY_BYTES}
118 placeholder="Leave a comment (Markdown supported)"
119 required
120 />
121 </div>
122 <div class="form-actions">
123 <button type="submit" class="btn btn-primary">
124 Comment
125 </button>
126 </div>
127 </form>
128 </div>
129 )}
130 {!user && (
131 <p class="text-muted">
132 <a href="/login">Sign in</a> to leave a comment.
133 </p>
134 )}
135 </>
136 );
137}
138