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>{displayName(comment.author_username)}</strong>
49 <div class="timeline-author-right">
50 {canEditComment(comment) && (
51 <details class="inline-edit-details">
52 <summary class="btn btn-xs">
53 <span class="when-closed">Edit</span>
54 <span class="when-open">
55 Stop editing
56 </span>
57 </summary>
58 </details>
59 )}
60 <DateWithEdited
61 date={comment.created_at}
62 editedAt={comment.edited_at}
63 />
64 </div>
65 </div>
66 {canEditComment(comment) && (
67 <div class="inline-edit-form-area">
68 <form
69 method="POST"
70 action={`${baseUrl}/comments/${comment.id}/edit`}
71 class="inline-edit-form"
72 >
73 <div class="form-group">
74 <textarea
75 class="form-input"
76 name="edit_body"
77 rows="6"
78 maxlength={config.MAX_TEXT_BODY_BYTES}
79 >
80 {comment.body}
81 </textarea>
82 </div>
83 <div class="form-actions">
84 <button
85 type="submit"
86 class="btn btn-sm btn-primary"
87 >
88 Save
89 </button>
90 </div>
91 </form>
92 </div>
93 )}
94 <div class="timeline-body markdown-body">
95 {comment.bodyHtml}
96 </div>
97 <ReactionBar
98 reactions={commentReactions.get(comment.id) ?? []}
99 postUrl={reactUrl}
100 commentId={comment.id}
101 user={user}
102 />
103 </div>
104 ))}
105
106 {user && (
107 <div class="timeline-item timeline-item-new">
108 <h3 class="section-title">Add a comment</h3>
109 <form method="POST" action={`${baseUrl}/comments`}>
110 <div class="form-group">
111 <textarea
112 name="body"
113 rows="6"
114 maxlength={config.MAX_TEXT_BODY_BYTES}
115 placeholder="Leave a comment (Markdown supported)"
116 required
117 />
118 </div>
119 <div class="form-actions">
120 <button type="submit" class="btn btn-primary">
121 Comment
122 </button>
123 </div>
124 </form>
125 </div>
126 )}
127 {!user && (
128 <p class="text-muted">
129 <a href="/login">Sign in</a> to leave a comment.
130 </p>
131 )}
132 </>
133 );
134}
135