render_post.gno
4.45 Kb · 180 lines
1package boards2
2
3import (
4 "strconv"
5 "strings"
6
7 "gno.land/p/gnoland/boards"
8 "gno.land/p/moul/md"
9)
10
11func renderPost(post *boards.Post, path, indent string, levels int) string {
12 var b strings.Builder
13
14 // Thread reposts might not have a title, if so get title from source thread
15 title := post.Title
16 if boards.IsRepost(post) && title == "" {
17 if board, ok := gBoards.Get(post.OriginalBoardID); ok {
18 if src, ok := board.Threads.Get(post.ParentID); ok {
19 title = src.Title
20 }
21 }
22 }
23
24 if title != "" { // Replies don't have a title
25 b.WriteString(md.H1(title))
26 }
27
28 b.WriteString(indent + "\n")
29 b.WriteString(renderPostContent(post, indent, levels))
30
31 if post.Replies.Size() == 0 {
32 return b.String()
33 }
34
35 // XXX: This triggers for reply views
36 if levels == 0 {
37 b.WriteString(indent + "\n")
38 return b.String()
39 }
40
41 if path != "" {
42 b.WriteString(renderTopLevelReplies(post, path, indent, levels-1))
43 } else {
44 b.WriteString(renderSubReplies(post, indent, levels-1))
45 }
46 return b.String()
47}
48
49func renderPostContent(post *boards.Post, indent string, levels int) string {
50 var b strings.Builder
51
52 if post.Hidden {
53 // Flagged comment should be hidden, but replies still visible (see: #3480)
54 // Flagged threads will be hidden by render function caller.
55 return indentBody(indent, md.Italic("⚠ Reply is hidden as it has been flagged as inappropriate")) + "\n"
56 }
57
58 srcContent, srcPost := renderSourcePost(post, indent)
59 if boards.IsRepost(post) && srcPost != nil {
60 originLink := md.Link("another thread", makeThreadURI(srcPost))
61 b.WriteString(" \nThis thread is a repost of " + originLink + ": \n")
62 }
63
64 b.WriteString(srcContent)
65
66 if boards.IsRepost(post) && srcPost == nil && len(post.Body) > 0 {
67 // Add a newline to separate source deleted message from repost body content
68 b.WriteString("\n")
69 }
70
71 b.WriteString(indentBody(indent, post.Body))
72 b.WriteString("\n")
73
74 if boards.IsThread(post) {
75 // Split content and controls for threads.
76 b.WriteString("\n")
77 }
78
79 // Buttons & counters
80 b.WriteString(indent)
81 if !boards.IsThread(post) {
82 b.WriteString(" \n")
83 b.WriteString(indent)
84 }
85
86 creatorLink := userLink(post.Creator)
87 date := post.CreatedAt.Format(dateFormat)
88 b.WriteString("Created by " + creatorLink + " on " + date)
89
90 // Add a reply view link to each top level reply
91 if !boards.IsThread(post) {
92 b.WriteString(", " + md.Link("#"+post.ID.String(), makeReplyURI(post)))
93 } else if post.Reposts.Size() > 0 { // Post is a thread
94 b.WriteString(", " + strconv.Itoa(post.Reposts.Size()) + " repost(s)")
95 }
96
97 b.WriteString(" \n")
98
99 actions := []string{
100 md.Link("Flag", makeFlagURI(post)),
101 }
102
103 if boards.IsThread(post) {
104 actions = append(actions, md.Link("Repost", makeCreateRepostURI(post)))
105 }
106
107 isReadonly := post.Readonly || post.Board.Readonly
108 if !isReadonly {
109 actions = append(
110 actions,
111 md.Link("Reply", makeCreateReplyURI(post)),
112 md.Link("Edit", makeEditPostURI(post)),
113 md.Link("Delete", makeDeletePostURI(post)),
114 )
115 }
116
117 if levels == 0 {
118 if boards.IsThread(post) {
119 actions = append(actions, md.Link("Show all Replies", makeThreadURI(post)))
120 } else {
121 actions = append(actions, md.Link("View Thread", makeThreadURI(post)))
122 }
123 }
124
125 b.WriteString(strings.Join(actions, " • ") + " \n")
126 return b.String()
127}
128
129func renderPostInner(post *boards.Post) string {
130 if boards.IsThread(post) {
131 return ""
132 }
133
134 var (
135 s string
136 threadID = post.ThreadID
137 thread, _ = post.Board.Threads.Get(threadID)
138 )
139
140 // Fully render parent if it's not a repost.
141 if !boards.IsRepost(post) {
142 parentID := post.ParentID
143 parent := thread
144
145 if thread.ID != parentID {
146 parent, _ = thread.Replies.Get(parentID)
147 }
148
149 s += renderPost(parent, "", "", 0) + "\n"
150 }
151
152 s += renderPost(post, "", "> ", 5)
153 return s
154}
155
156func renderSourcePost(post *boards.Post, indent string) (string, *boards.Post) {
157 if !boards.IsRepost(post) {
158 return "", nil
159 }
160
161 indent += "> "
162
163 // TODO: figure out a way to decouple posts from a global storage.
164 board, ok := gBoards.Get(post.OriginalBoardID)
165 if !ok {
166 // TODO: Boards can't be deleted so this might be redundant
167 return indentBody(indent, md.Italic("⚠ Source board has been deleted")+"\n"), nil
168 }
169
170 srcPost, ok := board.Threads.Get(post.ParentID)
171 if !ok {
172 return indentBody(indent, md.Italic("⚠ Source post has been deleted")+"\n"), nil
173 }
174
175 if srcPost.Hidden {
176 return indentBody(indent, md.Italic("⚠ Source post has been flagged as inappropriate")+"\n"), nil
177 }
178
179 return indentBody(indent, srcPost.Summary()) + "\n\n", srcPost
180}