commentthread.go
⎇
Raw
1package views
2
3import (
4 "strconv"
5
6 "hearthforge/internal/config"
7 "hearthforge/internal/db"
8 "hearthforge/internal/util"
9
10 g "maragu.dev/gomponents"
11 . "maragu.dev/gomponents/html"
12)
13
14// ThreadComment is one comment of an issue or patch, with its rendered body.
15type ThreadComment struct {
16 ID int64
17 AuthorID *int64
18 AuthorUsername string
19 AuthorAvatarVersion *int64
20 Body string
21 BodyHTML string
22 CreatedAt string
23 EditedAt *string
24}
25
26// CommentThread renders the comment list and the new comment form.
27// parentStatus is the status of the issue or patch, "open" allows author edits.
28func CommentThread(cfg *config.Config, user *db.SessionUser, comments []ThreadComment,
29 commentReactions map[int64][]ReactionCount, baseURL, parentStatus string,
30) g.Node {
31 reactURL := baseURL + "/react"
32 canEdit := func(c ThreadComment) bool {
33 if user == nil {
34 return false
35 }
36 return user.IsAdmin || (c.AuthorID != nil && user.ID == *c.AuthorID && parentStatus == "open")
37 }
38 return g.Group{
39 g.Map(comments, func(comment ThreadComment) g.Node {
40 return Div(Class("timeline-item"),
41 Div(Class("timeline-author"),
42 Avatar(comment.AuthorID, comment.AuthorAvatarVersion, 24),
43 Strong(g.Text(util.DisplayName(comment.AuthorUsername, cfg.OwnerDisplayName))),
44 Div(Class("timeline-author-right"),
45 g.If(canEdit(comment), Details(Class("inline-edit-details"),
46 Summary(Class("btn btn-xs"),
47 Span(Class("when-closed"), g.Text("Edit")),
48 Span(Class("when-open"), g.Text("Stop editing")),
49 ),
50 )),
51 DateWithEdited(comment.CreatedAt, comment.EditedAt),
52 ),
53 ),
54 g.If(canEdit(comment), Div(Class("inline-edit-form-area"),
55 Form(Method("POST"),
56 Action(baseURL+"/comments/"+strconv.FormatInt(comment.ID, 10)+"/edit"),
57 Class("inline-edit-form"),
58 Div(Class("form-group"),
59 Textarea(Class("form-input"), Name("edit_body"), Rows("6"),
60 MaxLength(strconv.Itoa(cfg.MaxTextBodyBytes)),
61 g.Text(comment.Body)),
62 ),
63 Div(Class("form-actions"),
64 Button(Type("submit"), Class("btn btn-sm btn-primary"), g.Text("Save")),
65 ),
66 ),
67 )),
68 // The markdown renderer already sanitised this HTML.
69 Div(Class("timeline-body markdown-body"), g.Raw(comment.BodyHTML)),
70 ReactionBar(commentReactions[comment.ID], reactURL, comment.ID, user),
71 )
72 }),
73 g.If(user != nil, Div(Class("timeline-item timeline-item-new"),
74 H3(Class("section-title"), g.Text("Add a comment")),
75 Form(Method("POST"), Action(baseURL+"/comments"),
76 Div(Class("form-group"),
77 Textarea(Name("body"), Rows("6"), MaxLength(strconv.Itoa(cfg.MaxTextBodyBytes)),
78 Placeholder("Leave a comment (Markdown supported)"), Required()),
79 ),
80 Div(Class("form-actions"),
81 Button(Type("submit"), Class("btn btn-primary"), g.Text("Comment")),
82 ),
83 ),
84 )),
85 g.If(user == nil, P(Class("text-muted"),
86 A(Href("/login"), g.Text("Sign in")), g.Text(" to leave a comment."))),
87 }
88}
89