Monorepo for Tangled tangled.org
5

Configure Feed

Select the types of activity you want to include in your feed.

appview: move `CommentList` out of `Issue`

So that we can render reply comments from non-issue threads.

Signed-off-by: Seongmin Lee <git@boltless.me>

author
Seongmin Lee
date (May 22, 2026, 7:55 PM +0900) commit 9481d616 parent 8970e77c change-id zuqqxyok
+84 -89
+1 -1
appview/issues/issues.go
··· 145 145 LoggedInUser: user, 146 146 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 147 147 Issue: issue, 148 - CommentList: issue.CommentList(), 148 + CommentList: models.NewCommentList(issue.Comments), 149 149 Backlinks: backlinks, 150 150 Reactions: reactionMap, 151 151 UserReacted: userReactions,
+72
appview/models/comment.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 + "sort" 5 6 "strings" 6 7 "time" 7 8 ··· 157 158 PullRoundIdx: pullRoundIdx, 158 159 }, nil 159 160 } 161 + 162 + type CommentListItem struct { 163 + Self *Comment 164 + Replies []*Comment 165 + } 166 + 167 + func (it *CommentListItem) Participants() []syntax.DID { 168 + participantSet := make(map[syntax.DID]struct{}) 169 + participants := []syntax.DID{} 170 + 171 + addParticipant := func(did syntax.DID) { 172 + if _, exists := participantSet[did]; !exists { 173 + participantSet[did] = struct{}{} 174 + participants = append(participants, did) 175 + } 176 + } 177 + 178 + addParticipant(syntax.DID(it.Self.Did)) 179 + 180 + for _, c := range it.Replies { 181 + addParticipant(syntax.DID(c.Did)) 182 + } 183 + 184 + return participants 185 + } 186 + 187 + func NewCommentList(comments []Comment) []CommentListItem { 188 + // Create a map to quickly find comments by their aturi 189 + toplevel := make(map[syntax.ATURI]*CommentListItem) 190 + var replies []*Comment 191 + 192 + // collect top level comments into the map 193 + for _, comment := range comments { 194 + if comment.IsTopLevel() { 195 + toplevel[comment.AtUri()] = &CommentListItem{ 196 + Self: &comment, 197 + } 198 + } else { 199 + replies = append(replies, &comment) 200 + } 201 + } 202 + 203 + for _, r := range replies { 204 + if r.ReplyTo == nil { 205 + continue 206 + } 207 + if parent, exists := toplevel[syntax.ATURI(r.ReplyTo.Uri)]; exists { 208 + parent.Replies = append(parent.Replies, r) 209 + } 210 + } 211 + 212 + var listing []CommentListItem 213 + for _, v := range toplevel { 214 + listing = append(listing, *v) 215 + } 216 + 217 + // sort everything 218 + sortFunc := func(a, b *Comment) bool { 219 + return a.Created.Before(b.Created) 220 + } 221 + sort.Slice(listing, func(i, j int) bool { 222 + return sortFunc(listing[i].Self, listing[j].Self) 223 + }) 224 + for _, r := range listing { 225 + sort.Slice(r.Replies, func(i, j int) bool { 226 + return sortFunc(r.Replies[i], r.Replies[j]) 227 + }) 228 + } 229 + 230 + return listing 231 + }
-72
appview/models/issue.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 - "sort" 6 5 "time" 7 6 8 7 "github.com/bluesky-social/indigo/atproto/syntax" ··· 60 59 return "open" 61 60 } 62 61 return "closed" 63 - } 64 - 65 - type CommentListItem struct { 66 - Self *Comment 67 - Replies []*Comment 68 - } 69 - 70 - func (it *CommentListItem) Participants() []syntax.DID { 71 - participantSet := make(map[syntax.DID]struct{}) 72 - participants := []syntax.DID{} 73 - 74 - addParticipant := func(did syntax.DID) { 75 - if _, exists := participantSet[did]; !exists { 76 - participantSet[did] = struct{}{} 77 - participants = append(participants, did) 78 - } 79 - } 80 - 81 - addParticipant(syntax.DID(it.Self.Did)) 82 - 83 - for _, c := range it.Replies { 84 - addParticipant(syntax.DID(c.Did)) 85 - } 86 - 87 - return participants 88 - } 89 - 90 - func (i *Issue) CommentList() []CommentListItem { 91 - // Create a map to quickly find comments by their aturi 92 - toplevel := make(map[syntax.ATURI]*CommentListItem) 93 - var replies []*Comment 94 - 95 - // collect top level comments into the map 96 - for _, comment := range i.Comments { 97 - if comment.IsTopLevel() { 98 - toplevel[comment.AtUri()] = &CommentListItem{ 99 - Self: &comment, 100 - } 101 - } else { 102 - replies = append(replies, &comment) 103 - } 104 - } 105 - 106 - for _, r := range replies { 107 - if r.ReplyTo == nil { 108 - continue 109 - } 110 - if parent, exists := toplevel[syntax.ATURI(r.ReplyTo.Uri)]; exists { 111 - parent.Replies = append(parent.Replies, r) 112 - } 113 - } 114 - 115 - var listing []CommentListItem 116 - for _, v := range toplevel { 117 - listing = append(listing, *v) 118 - } 119 - 120 - // sort everything 121 - sortFunc := func(a, b *Comment) bool { 122 - return a.Created.Before(b.Created) 123 - } 124 - sort.Slice(listing, func(i, j int) bool { 125 - return sortFunc(listing[i].Self, listing[j].Self) 126 - }) 127 - for _, r := range listing { 128 - sort.Slice(r.Replies, func(i, j int) bool { 129 - return sortFunc(r.Replies[i], r.Replies[j]) 130 - }) 131 - } 132 - 133 - return listing 134 62 } 135 63 136 64 func (i *Issue) Participants() []syntax.DID {
+1 -1
appview/notify/db/db.go
··· 123 123 parent := *comment.ReplyTo 124 124 125 125 // find the parent thread, and add all DIDs from here to the recipient list 126 - for _, t := range issue.CommentList() { 126 + for _, t := range models.NewCommentList(issue.Comments) { 127 127 if t.Self.AtUri() == syntax.ATURI(parent.Uri) { 128 128 for _, p := range t.Participants() { 129 129 recipients.Insert(p)
+7 -9
appview/pages/templates/repo/issues/fragments/commentList.html appview/pages/templates/fragments/comment/commentList.html
··· 1 - {{ define "repo/issues/fragments/commentList" }} 1 + {{ define "fragments/comment/commentList" }} 2 2 <div class="flex flex-col gap-4"> 3 3 {{ range $item := .CommentList }} 4 4 {{ template "commentListItem" (list $ .) }} ··· 9 9 {{ define "commentListItem" }} 10 10 {{ $root := index . 0 }} 11 11 {{ $item := index . 1 }} 12 - {{ $params := 13 - (dict 14 - "LoggedInUser" $root.LoggedInUser 15 - "VouchRelationship" (index $root.VouchRelationships $item.Self.Did) 16 - "Comment" $item.Self) }} 17 12 18 13 <div class="rounded border border-gray-200 dark:border-gray-700 w-full overflow-hidden shadow-sm bg-gray-50 dark:bg-gray-800/50"> 19 - {{ template "topLevelComment" $params }} 14 + {{ template "topLevelComment" 15 + (dict 16 + "LoggedInUser" $root.LoggedInUser 17 + "VouchRelationship" (index $root.VouchRelationships $item.Self.Did) 18 + "Comment" $item.Self) }} 20 19 21 20 <div class="relative ml-10 border-l-2 border-gray-200 dark:border-gray-700"> 22 21 {{ range $index, $reply := $item.Replies }} 23 22 <div class="-ml-4"> 24 - {{ 25 - template "replyComment" 23 + {{ template "replyComment" 26 24 (dict 27 25 "LoggedInUser" $root.LoggedInUser 28 26 "VouchRelationship" (index $root.VouchRelationships $reply.Did)
+3 -6
appview/pages/templates/repo/issues/issue.html
··· 114 114 {{ define "repoAfter" }} 115 115 <div class="flex flex-col gap-4 mt-4"> 116 116 {{ 117 - template "repo/issues/fragments/commentList" 117 + template "fragments/comment/commentList" 118 118 (dict 119 - "RepoInfo" $.RepoInfo 120 - "LoggedInUser" $.LoggedInUser 121 - "Issue" $.Issue 122 - "CommentList" $.Issue.CommentList 119 + "LoggedInUser" $.LoggedInUser 123 120 "VouchRelationships" $.VouchRelationships 124 - ) 121 + "CommentList" $.CommentList) 125 122 }} 126 123 127 124 {{ template "repo/issues/fragments/newComment" . }}