Monorepo for Tangled tangled.org
2

Configure Feed

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

appview/pages: hide reaction actions by default

the behavior is like so now:

- comments no longer have a reaction button below them
- like linear/discord/slack: the reaction button shows up on hover in
the comment header to the right
- when existing reactions are present, a reaction button can be seen in
the bottom, *in addition* to the one in the header (just like linear's
comment threads)

Signed-off-by: oppiliappan <me@oppi.li>

author
oppiliappan
date (Jun 15, 2026, 10:10 AM +0100) commit 82899c63 parent 5aca1539 change-id sutwpqmm
+99 -76
+6 -4
appview/pages/pages.go
··· 1279 1279 } 1280 1280 1281 1281 type ThreadReactionFragmentParams struct { 1282 - Kind models.ReactionKind 1283 - Count int 1284 - Users []string 1285 - IsReacted bool 1282 + Kind models.ReactionKind 1283 + Count int 1284 + Users []string 1285 + IsReacted bool 1286 + CommentRkey string 1287 + SubjectUri string 1286 1288 } 1287 1289 1288 1290 func (p *Pages) ThreadReactionFragment(w io.Writer, params ThreadReactionFragmentParams) error {
+2 -1
appview/pages/templates/fragments/comment/commentBody.html
··· 5 5 {{ template "repo/fragments/reactions" 6 6 (dict "Reactions" .Reactions 7 7 "UserReacted" .UserReacted 8 - "ThreadAt" .Comment.FeedCommentAtUri) }} 8 + "ThreadAt" .Comment.FeedCommentAtUri 9 + "HideIfEmpty" true) }} 9 10 {{ else }} 10 11 <div class="prose dark:prose-invert italic text-gray-500 dark:text-gray-400">[deleted by author]</div> 11 12 {{ end }}
+13 -10
appview/pages/templates/fragments/comment/commentHeader.html
··· 10 10 <span class="before:content-['·']"></span> 11 11 {{ template "timestamp" . }} 12 12 {{ $isCommentOwner := and .LoggedInUser (eq .LoggedInUser.Did .Comment.Did.String) }} 13 - {{ if and $isCommentOwner (not .Comment.Deleted) }} 13 + {{ if not .Comment.Deleted }} 14 14 <div class="ml-auto flex items-center gap-4 opacity-0 group-hover/comment:opacity-100"> 15 - {{ if not .Comment.IsLegacy }} 16 - {{ template "editCommentBtn" . }} 15 + {{ if .LoggedInUser }} 16 + {{ template "repo/fragments/reactionsPopup" (dict "CommentRkey" (string .Comment.Rkey) "Scope" "header") }} 17 + {{ end }} 18 + {{ if $isCommentOwner }} 19 + {{ if not .Comment.IsLegacy }} 20 + {{ template "editCommentBtn" . }} 21 + {{ end }} 22 + {{ template "deleteCommentBtn" . }} 17 23 {{ end }} 18 - {{ template "deleteCommentBtn" . }} 19 24 </div> 20 25 {{ end }} 21 26 </div> ··· 40 45 class="text-gray-500 dark:text-gray-400 flex gap-1 items-center group cursor-pointer hover:no-underline no-underline" 41 46 hx-get="/comment/edit?aturi={{ .Comment.AtUri }}" 42 47 > 43 - {{ i "pencil" "size-3 inline group-[.htmx-request]:hidden" }} 44 - {{ i "loader-circle" "size-3 animate-spin hidden group-[.htmx-request]:inline" }} 45 - edit 48 + {{ i "pencil" "size-4 inline group-[.htmx-request]:hidden" }} 49 + {{ i "loader-circle" "size-4 animate-spin hidden group-[.htmx-request]:inline" }} 46 50 </a> 47 51 {{ end }} 48 52 ··· 52 56 hx-delete="/comment?aturi={{ .Comment.AtUri }}" 53 57 hx-confirm="Are you sure you want to delete your comment?" 54 58 > 55 - {{ i "trash-2" "size-3 inline group-[.htmx-request]:hidden" }} 56 - {{ i "loader-circle" "size-3 animate-spin hidden group-[.htmx-request]:inline" }} 57 - delete 59 + {{ i "trash-2" "size-4 inline group-[.htmx-request]:hidden" }} 60 + {{ i "loader-circle" "size-4 animate-spin hidden group-[.htmx-request]:inline" }} 58 61 </a> 59 62 {{ end }}
+16 -16
appview/pages/templates/repo/fragments/reaction.html
··· 1 1 {{ define "repo/fragments/reaction" }} 2 2 <button 3 - class="flex justify-center items-center min-w-8 min-h-8 rounded border 4 - leading-4 px-3 gap-1 relative group 3 + class="flex justify-center items-center min-h-8 rounded border 4 + leading-4 px-2 gap-1.5 text-sm relative group 5 5 {{ if eq .Count 0 }} 6 6 hidden 7 7 {{ end }} 8 8 {{ if .IsReacted }} 9 - bg-sky-100 10 - border-sky-400 11 - dark:bg-sky-900 12 - dark:border-sky-500 9 + bg-gray-100 border-gray-300 text-gray-700 10 + hover:bg-gray-200 hover:border-gray-400 11 + dark:bg-gray-700 dark:border-gray-600 dark:text-gray-100 12 + dark:hover:bg-gray-600 dark:hover:border-gray-500 13 13 {{ else }} 14 - border-gray-200 15 - hover:bg-gray-50 16 - hover:border-gray-300 17 - dark:border-gray-700 18 - dark:hover:bg-gray-700 19 - dark:hover:border-gray-600 14 + border-gray-200 text-gray-600 15 + hover:bg-gray-50 hover:border-gray-300 16 + dark:border-gray-700 dark:text-gray-300 17 + dark:hover:bg-gray-800 dark:hover:border-gray-600 20 18 {{ end }} 21 19 " 22 20 {{ if gt (length .Users) 0 }} ··· 25 23 title="{{ .Kind }}" 26 24 {{ end }} 27 25 {{ if .IsReacted }} 28 - hx-delete="/react?kind={{ .Kind }}" 26 + hx-delete="/react?kind={{ .Kind }}&subject-uri={{ .SubjectUri | urlquery }}" 29 27 {{ else }} 30 - hx-post="/react?kind={{ .Kind }}" 28 + hx-post="/react?kind={{ .Kind }}&subject-uri={{ .SubjectUri | urlquery }}" 31 29 {{ end }} 32 30 hx-swap="outerHTML" 33 - hx-trigger="click from:(previous #reactBtn-{{ .Kind }}, closest button)" 31 + hx-trigger="click from:(.reactBtn-{{ .CommentRkey }}-{{ .Kind }}, closest button)" 34 32 hx-disabled-elt="this" 35 33 > 36 - <span>{{ .Kind }}</span> <span>{{ .Count }}</span> 34 + <span class="inline group-[.htmx-request]:hidden">{{ .Kind }}</span> 35 + {{ i "loader-circle" "size-4 animate-spin hidden group-[.htmx-request]:inline" }} 36 + <span>{{ .Count }}</span> 37 37 </button> 38 38 {{ end }}
+13 -34
appview/pages/templates/repo/fragments/reactions.html
··· 3 3 {{- $reactions := .Reactions -}} 4 4 {{- $userReacted := .UserReacted -}} 5 5 {{- $threadAt := .ThreadAt -}} 6 + {{- $parts := splitOn (string $threadAt) "/" -}} 7 + {{- $commentRkey := index $parts (sub (len $parts) 1) -}} 6 8 7 9 <input name="subject-uri" type="hidden" value="{{ $threadAt }}"> 8 10 9 - {{ template "reactionsPopup" }} 10 - 11 11 {{ range $kind := const.OrderedReactionKinds }} 12 12 {{ $reactionData := index $reactions $kind }} 13 13 {{ template "repo/fragments/reaction" 14 14 (dict 15 - "Kind" $kind 16 - "Count" $reactionData.Count 17 - "IsReacted" (index $userReacted $kind) 18 - "Users" $reactionData.Users) }} 15 + "Kind" $kind 16 + "Count" $reactionData.Count 17 + "IsReacted" (index $userReacted $kind) 18 + "Users" $reactionData.Users 19 + "CommentRkey" $commentRkey 20 + "SubjectUri" $threadAt) }} 19 21 {{ end }} 20 - </div> 21 - {{ end }} 22 22 23 - {{ define "reactionsPopup" }} 24 - <details class="relative inline-block"> 25 - <summary 26 - class="flex justify-center items-center min-w-8 min-h-8 rounded border border-gray-200 dark:border-gray-700 27 - hover:bg-gray-50 28 - hover:border-gray-300 29 - dark:hover:bg-gray-700 30 - dark:hover:border-gray-600 31 - cursor-pointer list-none" 32 - > 33 - {{ i "smile" "size-4" }} 34 - </summary> 35 - <div 36 - class="absolute flex left-0 z-10 mt-4 rounded bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700 shadow-lg" 37 - > 38 - {{ range $kind := const.OrderedReactionKinds }} 39 - <button 40 - id="reactBtn-{{ $kind }}" 41 - class="size-12 hover:bg-gray-100 dark:hover:bg-gray-700" 42 - hx-on:click="this.parentElement.parentElement.removeAttribute('open')" 43 - > 44 - {{ $kind }} 45 - </button> 46 - {{ end }} 47 - </div> 48 - </details> 23 + {{ if or (not .HideIfEmpty) (gt (len $reactions) 0) }} 24 + {{ template "repo/fragments/reactionsPopup" (dict "CommentRkey" $commentRkey "Scope" "body") }} 25 + {{ end }} 26 + 27 + </div> 49 28 {{ end }}
+32
appview/pages/templates/repo/fragments/reactionsPopup.html
··· 1 + {{ define "repo/fragments/reactionsPopup" }} 2 + {{ $id := printf "%s-%s" (string .CommentRkey) .Scope }} 3 + <button 4 + type="button" 5 + popovertarget="reactions-popover-{{ $id }}" 6 + style="anchor-name: --reactions-anchor-{{ $id }}" 7 + class=" 8 + flex justify-center items-center rounded 9 + text-gray-400 dark:text-gray-400 10 + hover:text-gray-500 dark:hover:text-gray-300 11 + cursor-pointer" 12 + > 13 + {{ i "smile-plus" "size-4" }} 14 + </button> 15 + <div 16 + id="reactions-popover-{{ $id }}" 17 + popover 18 + style="position-anchor: --reactions-anchor-{{ $id }};" 19 + class="[&:popover-open]:grid grid-cols-4 [position-area:bottom_center] justify-self-center w-max inset-auto mt-1 rounded bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700 shadow-lg" 20 + > 21 + {{ range $kind := const.OrderedReactionKinds }} 22 + <button 23 + type="button" 24 + popovertarget="reactions-popover-{{ $id }}" 25 + popovertargetaction="hide" 26 + class="reactBtn-{{ $.CommentRkey }}-{{ $kind }} btn-flat px-3 text-base hover:bg-gray-100 dark:hover:bg-gray-700" 27 + > 28 + {{ $kind }} 29 + </button> 30 + {{ end }} 31 + </div> 32 + {{ end }}
+5 -3
appview/pages/templates/repo/issues/issue.html
··· 61 61 {{ $bgColor = "bg-green-600 dark:bg-green-700" }} 62 62 {{ $icon = "circle-dot" }} 63 63 {{ end }} 64 - <div class="inline-flex items-center gap-2"> 64 + <div class="flex items-center gap-2"> 65 65 <span class="inline-flex items-center rounded px-2 py-[5px] {{ $bgColor }}"> 66 66 {{ i $icon "w-3 h-3 mr-1.5 text-white dark:text-white" }} 67 67 <span class="text-white dark:text-white text-sm">{{ if eq .Issue.State "open" }}Open{{ else }}Closed{{ end }}</span> ··· 80 80 </span> 81 81 82 82 {{ if and .LoggedInUser (eq .LoggedInUser.Did .Issue.Did) }} 83 - {{ template "issueActions" . }} 83 + <div class="ml-auto flex items-center gap-2"> 84 + {{ template "issueActions" . }} 85 + </div> 84 86 {{ end }} 85 87 </div> 86 88 <div id="issue-actions-error" class="error"></div> ··· 103 105 104 106 {{ define "deleteIssue" }} 105 107 <a 106 - class="text-gray-500 dark:text-gray-400 flex gap-1 items-center group cursor-pointer" 108 + class="text-red-500 dark:text-red-400 hover:text-red-600 hover:dark:text-red-300 flex gap-1 items-center group cursor-pointer" 107 109 hx-delete="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/" 108 110 hx-confirm="Are you sure you want to delete your issue?" 109 111 hx-swap="none">
+12 -8
appview/state/reaction.go
··· 81 81 l.Info("created atproto record", "uri", resp.Uri) 82 82 83 83 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{ 84 - Kind: reactionKind, 85 - Count: reactionMap[reactionKind].Count, 86 - Users: reactionMap[reactionKind].Users, 87 - IsReacted: true, 84 + Kind: reactionKind, 85 + Count: reactionMap[reactionKind].Count, 86 + Users: reactionMap[reactionKind].Users, 87 + IsReacted: true, 88 + CommentRkey: subjectUri.RecordKey().String(), 89 + SubjectUri: subject, 88 90 }) 89 91 90 92 return ··· 119 121 } 120 122 121 123 s.pages.ThreadReactionFragment(w, pages.ThreadReactionFragmentParams{ 122 - Kind: reactionKind, 123 - Count: reactionMap[reactionKind].Count, 124 - Users: reactionMap[reactionKind].Users, 125 - IsReacted: false, 124 + Kind: reactionKind, 125 + Count: reactionMap[reactionKind].Count, 126 + Users: reactionMap[reactionKind].Users, 127 + IsReacted: false, 128 + CommentRkey: subjectUri.RecordKey().String(), 129 + SubjectUri: subject, 126 130 }) 127 131 128 132 return