Monorepo for Tangled tangled.org
2

Configure Feed

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

appview: add reactions to comments

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

author
Seongmin Lee
date (May 22, 2026, 7:55 PM +0900) commit 147807ef parent 5f3433f1 change-id nzykrkun
+73 -19
+3
appview/issues/issues.go
··· 100 100 } 101 101 102 102 entities := []syntax.ATURI{issue.AtUri()} 103 + for _, c := range issue.Comments { 104 + entities = append(entities, c.AtUri()) 105 + } 103 106 reactions, err := db.ListReactionDisplayDataMap(rp.db, entities, 20) 104 107 if err != nil { 105 108 l.Error("failed to get reactions", "err", err)
+16
appview/pages/funcmap.go
··· 481 481 "isGenerated": func(path string) bool { 482 482 return enry.IsGenerated(path, nil) 483 483 }, 484 + // NOTE(boltless): I know... I hate doing this too 485 + "asReactionMapMap": func(dict any) map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData { 486 + if dict == nil { 487 + return make(map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData) 488 + } 489 + m, _ := dict.(map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData) 490 + return m 491 + }, 492 + "asReactionStatusMapMap": func(dict any) map[syntax.ATURI]map[models.ReactionKind]bool { 493 + if dict == nil { 494 + log.Println("returning empty map") 495 + return make(map[syntax.ATURI]map[models.ReactionKind]bool) 496 + } 497 + m, _ := dict.(map[syntax.ATURI]map[models.ReactionKind]bool) 498 + return m 499 + }, 484 500 // constant values used to define a template 485 501 "const": func() map[string]any { 486 502 return map[string]any{
+2 -1
appview/pages/pages.go
··· 1213 1213 } 1214 1214 1215 1215 type ThreadReactionFragmentParams struct { 1216 - ThreadAt syntax.ATURI 1217 1216 Kind models.ReactionKind 1218 1217 Count int 1219 1218 Users []string ··· 1631 1630 Owner identity.Identity 1632 1631 CommentList []models.CommentListItem 1633 1632 1633 + Reactions map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData 1634 + UserReacted map[syntax.ATURI]map[models.ReactionKind]bool 1634 1635 VouchRelationships map[syntax.DID]*models.VouchRelationship 1635 1636 } 1636 1637
+5 -1
appview/pages/templates/fragments/comment/commentBody.html
··· 1 1 {{ define "fragments/comment/commentBody" }} 2 2 <div class="comment-body"> 3 3 {{ if not .Comment.Deleted }} 4 - <div class="prose dark:prose-invert">{{ .Comment.Body.Text | markdown }}</div> 4 + <div class="prose dark:prose-invert mb-1">{{ .Comment.Body.Text | markdown }}</div> 5 + {{ template "repo/fragments/reactions" 6 + (dict "Reactions" .Reactions 7 + "UserReacted" .UserReacted 8 + "ThreadAt" .Comment.AtUri) }} 5 9 {{ else }} 6 10 <div class="prose dark:prose-invert italic text-gray-500 dark:text-gray-400">[deleted by author]</div> 7 11 {{ end }}
+5 -1
appview/pages/templates/fragments/comment/commentList.html
··· 14 14 {{ template "topLevelComment" 15 15 (dict 16 16 "LoggedInUser" $root.LoggedInUser 17 + "Reactions" (index (asReactionMapMap $root.Reactions) $item.Self.AtUri) 18 + "UserReacted" (index (asReactionStatusMapMap $root.UserReacted) $item.Self.AtUri) 17 19 "VouchRelationship" (index $root.VouchRelationships $item.Self.Did) 18 20 "Comment" $item.Self) }} 19 21 ··· 23 25 {{ template "replyComment" 24 26 (dict 25 27 "LoggedInUser" $root.LoggedInUser 28 + "Reactions" (index (asReactionMapMap $root.Reactions) $reply.AtUri) 29 + "UserReacted" (index (asReactionStatusMapMap $root.UserReacted) $reply.AtUri) 26 30 "VouchRelationship" (index $root.VouchRelationships $reply.Did) 27 31 "Comment" $reply) }} 28 32 </div> ··· 58 62 {{ end }} 59 63 60 64 {{ define "replyComment" }} 61 - <div class="py-4 pr-4 w-full mx-auto overflow-hidden flex gap-2 "> 65 + <div class="py-4 pr-4 w-full mx-auto flex gap-2 "> 62 66 <div class="flex-shrink-0"> 63 67 {{ template "user/fragments/picLink" (list .Comment.Did.String "size-8 mr-1" .VouchRelationship) }} 64 68 </div>
+3 -4
appview/pages/templates/repo/fragments/reaction.html
··· 1 1 {{ define "repo/fragments/reaction" }} 2 2 <button 3 - id="reactIndi-{{ .Kind }}" 4 3 class="flex justify-center items-center min-w-8 min-h-8 rounded border 5 4 leading-4 px-3 gap-1 relative group 6 5 {{ if eq .Count 0 }} ··· 26 25 title="{{ .Kind }}" 27 26 {{ end }} 28 27 {{ if .IsReacted }} 29 - hx-delete="/react?subject={{ .ThreadAt }}&kind={{ .Kind }}" 28 + hx-delete="/react?kind={{ .Kind }}" 30 29 {{ else }} 31 - hx-post="/react?subject={{ .ThreadAt }}&kind={{ .Kind }}" 30 + hx-post="/react?kind={{ .Kind }}" 32 31 {{ end }} 33 32 hx-swap="outerHTML" 34 - hx-trigger="click from:(#reactBtn-{{ .Kind }}, #reactIndi-{{ .Kind }})" 33 + hx-trigger="click from:(previous #reactBtn-{{ .Kind }}, closest button)" 35 34 hx-disabled-elt="this" 36 35 > 37 36 <span>{{ .Kind }}</span> <span>{{ .Count }}</span>
+6 -7
appview/pages/templates/repo/fragments/reactions.html
··· 1 1 {{ define "repo/fragments/reactions" }} 2 - <div class="flex flex-wrap items-center gap-2"> 2 + <div class="reactions flex flex-wrap items-center gap-2" hx-include="this"> 3 3 {{- $reactions := .Reactions -}} 4 4 {{- $userReacted := .UserReacted -}} 5 5 {{- $threadAt := .ThreadAt -}} 6 + 7 + <input name="subject-uri" type="hidden" value="{{ $threadAt }}"> 6 8 7 9 {{ template "reactionsPopup" }} 10 + 8 11 {{ range $kind := const.OrderedReactionKinds }} 9 12 {{ $reactionData := index $reactions $kind }} 10 13 {{ template "repo/fragments/reaction" ··· 12 15 "Kind" $kind 13 16 "Count" $reactionData.Count 14 17 "IsReacted" (index $userReacted $kind) 15 - "ThreadAt" $threadAt 16 18 "Users" $reactionData.Users) }} 17 19 {{ end }} 18 20 </div> 19 21 {{ end }} 20 22 21 23 {{ define "reactionsPopup" }} 22 - <details 23 - id="reactionsPopUp" 24 - class="relative inline-block" 25 - > 24 + <details class="relative inline-block"> 26 25 <summary 27 26 class="flex justify-center items-center min-w-8 min-h-8 rounded border border-gray-200 dark:border-gray-700 28 27 hover:bg-gray-50 ··· 44 43 > 45 44 {{ $kind }} 46 45 </button> 47 - {{ end }} 46 + {{ end }} 48 47 </div> 49 48 </details> 50 49 {{ end }}
+2
appview/pages/templates/repo/issues/issue.html
··· 118 118 template "fragments/comment/commentList" 119 119 (dict 120 120 "LoggedInUser" $.LoggedInUser 121 + "Reactions" $.Reactions 122 + "UserReacted" $.UserReacted 121 123 "VouchRelationships" $.VouchRelationships 122 124 "CommentList" $.CommentList) 123 125 }}
+4 -2
appview/pages/templates/repo/pulls/pull.html
··· 597 597 {{ range $item.Comments }} 598 598 {{/* template "submissionComment" . */}} 599 599 {{ template "comment" 600 - (dict "LoggedInUser" $root.LoggedInUser 600 + (dict "LoggedInUser" $root.LoggedInUser 601 + "Reactions" (index (asReactionMapMap $root.Reactions) .AtUri) 602 + "UserReacted" (index (asReactionStatusMapMap $root.UserReacted) .AtUri) 601 603 "VouchRelationship" (index $root.VouchRelationships .Did) 602 - "Comment" .) }} 604 + "Comment" .) }} 603 605 {{ end }} 604 606 </div> 605 607 {{ if gt $c 0}}
+2
appview/pages/templates/strings/string.html
··· 99 99 template "fragments/comment/commentList" 100 100 (dict 101 101 "LoggedInUser" .LoggedInUser 102 + "Reactions" .Reactions 103 + "UserReacted" .UserReacted 102 104 "VouchRelationships" .VouchRelationships 103 105 "CommentList" .CommentList) 104 106 }}
+5
appview/pulls/single.go
··· 161 161 } 162 162 163 163 entities := []syntax.ATURI{pull.AtUri()} 164 + for _, s := range pull.Submissions { 165 + for _, c := range s.Comments { 166 + entities = append(entities, c.AtUri()) 167 + } 168 + } 164 169 reactions, err := db.ListReactionDisplayDataMap(s.db, entities, 20) 165 170 if err != nil { 166 171 l.Error("failed to get pull reactions", "err", err)
+1 -3
appview/state/reaction.go
··· 19 19 l := s.logger.With("handler", "React") 20 20 currentUser := s.oauth.GetMultiAccountUser(r) 21 21 22 - subject := r.URL.Query().Get("subject") 22 + subject := r.FormValue("subject-uri") 23 23 if subject == "" { 24 24 l.Warn("invalid form") 25 25 return ··· 78 78 l.Info("created atproto record", "uri", resp.Uri) 79 79 80 80 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{ 81 - ThreadAt: subjectUri, 82 81 Kind: reactionKind, 83 82 Count: reactionMap[reactionKind].Count, 84 83 Users: reactionMap[reactionKind].Users, ··· 117 116 } 118 117 119 118 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{ 120 - ThreadAt: subjectUri, 121 119 Kind: reactionKind, 122 120 Count: reactionMap[reactionKind].Count, 123 121 Users: reactionMap[reactionKind].Users,
+19
appview/strings/strings.go
··· 162 162 l.Error("failed to get comments", "err", err) 163 163 } 164 164 165 + var entities []syntax.ATURI 166 + for _, c := range comments { 167 + entities = append(entities, c.AtUri()) 168 + } 169 + reactions, err := db.ListReactionDisplayDataMap(s.Db, entities, 20) 170 + if err != nil { 171 + l.Error("failed to get reactions", "err", err) 172 + } 173 + 174 + var userReactions map[syntax.ATURI]map[models.ReactionKind]bool 175 + if user != nil { 176 + userReactions, err = db.ListReactionStatusMap(s.Db, entities, syntax.DID(user.Did)) 177 + if err != nil { 178 + l.Error("failed to get user reactions", "err", err) 179 + } 180 + } 181 + 165 182 vouchRelationships := make(map[syntax.DID]*models.VouchRelationship) 166 183 if user != nil { 167 184 var participants []syntax.DID ··· 185 202 Owner: id, 186 203 CommentList: models.NewCommentList(comments), 187 204 205 + Reactions: reactions, 206 + UserReacted: userReactions, 188 207 VouchRelationships: vouchRelationships, 189 208 }) 190 209 }