Monorepo for Tangled
0

Configure Feed

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

appview/pages: add subscribe button to issue and pull pages

author
Anirudh Oppiliappan
committer
Anirudh Oppiliappan
date (Jul 17, 2026, 9:10 AM +0300) commit 4a683fa1 parent 63b6d343 change-id olopsrnk
+129 -24
+30 -2
appview/pages/pages.go
··· 583 583 584 584 type UserNotificationSettingsParams struct { 585 585 BaseParams 586 - Preferences *models.NotificationPreferences 587 - Tab string 586 + Preferences *models.NotificationPreferences 587 + HasVerifiedEmail bool 588 + Tab string 588 589 } 589 590 590 591 func (p *Pages) UserNotificationSettings(w io.Writer, params UserNotificationSettingsParams) error { ··· 1338 1339 Reactions map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData 1339 1340 UserReacted map[syntax.ATURI]map[models.ReactionKind]bool 1340 1341 VouchRelationships map[syntax.DID]*models.VouchRelationship 1342 + 1343 + // IsSubscribed is nil when the user is not logged in, true when subscribed, 1344 + // false when explicitly unsubscribed, and nil when no explicit subscription. 1345 + IsSubscribed *bool 1341 1346 } 1342 1347 1343 1348 func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error { 1344 1349 params.Active = "issues" 1345 1350 return p.executeRepo("repo/issues/issue", w, params) 1351 + } 1352 + 1353 + type IssueSubscribeParams struct { 1354 + RepoInfo repoinfo.RepoInfo 1355 + IssueId int 1356 + IsSubscribed *bool 1357 + } 1358 + 1359 + func (p *Pages) IssueSubscribeFragment(w io.Writer, params IssueSubscribeParams) error { 1360 + return p.executePlain("repo/issues/fragments/subscribeButton", w, params) 1361 + } 1362 + 1363 + type PullSubscribeParams struct { 1364 + RepoInfo repoinfo.RepoInfo 1365 + PullId int 1366 + IsSubscribed *bool 1367 + } 1368 + 1369 + func (p *Pages) PullSubscribeFragment(w io.Writer, params PullSubscribeParams) error { 1370 + return p.executePlain("repo/pulls/fragments/subscribeButton", w, params) 1346 1371 } 1347 1372 1348 1373 type EditIssueParams struct { ··· 1500 1525 LabelDefs map[string]*models.LabelDefinition 1501 1526 VouchRelationships map[syntax.DID]*models.VouchRelationship 1502 1527 VouchSkips map[syntax.DID]bool 1528 + 1529 + // IsSubscribed is nil when not logged in, true when subscribed, false when explicitly unsubscribed. 1530 + IsSubscribed *bool 1503 1531 } 1504 1532 1505 1533 func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error {
+34
appview/pages/templates/repo/issues/fragments/subscribeButton.html
··· 1 + {{ define "repo/issues/fragments/subscribeButton" }} 2 + {{ $subscribed := false }} 3 + {{ if .IsSubscribed }}{{ $subscribed = .IsSubscribed }}{{ end }} 4 + <div id="issue-subscribe-btn" class="px-2 md:px-0"> 5 + <div class="py-1 flex items-center text-sm"> 6 + <span class="font-bold text-gray-500 dark:text-gray-400">Notifications</span> 7 + </div> 8 + {{ if $subscribed }} 9 + <form 10 + hx-post="/{{ .RepoInfo.FullName }}/issues/{{ .IssueId }}/subscribe" 11 + hx-target="#issue-subscribe-btn" 12 + hx-swap="outerHTML"> 13 + <input type="hidden" name="subscribe" value="false"> 14 + <button type="submit" class="flex items-center gap-1.5 text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors group"> 15 + {{ i "bell" "w-4 h-4" }} 16 + <span>Subscribed</span> 17 + {{ i "loader-circle" "w-3 h-3 animate-spin hidden group-[.htmx-request]:inline" }} 18 + </button> 19 + </form> 20 + {{ else }} 21 + <form 22 + hx-post="/{{ .RepoInfo.FullName }}/issues/{{ .IssueId }}/subscribe" 23 + hx-target="#issue-subscribe-btn" 24 + hx-swap="outerHTML"> 25 + <input type="hidden" name="subscribe" value="true"> 26 + <button type="submit" class="flex items-center gap-1.5 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors group"> 27 + {{ i "bell-off" "w-4 h-4" }} 28 + <span>Subscribe</span> 29 + {{ i "loader-circle" "w-3 h-3 animate-spin hidden group-[.htmx-request]:inline" }} 30 + </button> 31 + </form> 32 + {{ end }} 33 + </div> 34 + {{ end }}
+9 -3
appview/pages/templates/repo/issues/issue.html
··· 6 6 {{ end }} 7 7 8 8 {{ define "repoContentLayout" }} 9 - <div class="grid grid-cols-1 md:grid-cols-10 gap-4 w-full"> 10 - <div class="col-span-1 md:col-span-8"> 9 + <div class="grid grid-cols-1 lg:grid-cols-10 gap-4 w-full"> 10 + <div class="col-span-1 lg:col-span-8"> 11 11 <section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto dark:text-white"> 12 12 {{ block "repoContent" . }}{{ end }} 13 13 </section> 14 14 {{ block "repoAfter" . }}{{ end }} 15 15 </div> 16 - <div class="col-span-1 md:col-span-2 flex flex-col gap-6"> 16 + <div class="col-span-1 lg:col-span-2 flex flex-col gap-6"> 17 17 {{ template "repo/fragments/labelPanel" 18 18 (dict "RepoInfo" $.RepoInfo 19 19 "Defs" $.LabelDefs 20 20 "Subject" $.Issue.AtUri 21 21 "State" $.Issue.Labels) }} 22 22 {{ template "repo/fragments/participants" $.Issue.Participants }} 23 + {{ if $.LoggedInUser }} 24 + {{ template "repo/issues/fragments/subscribeButton" 25 + (dict "RepoInfo" $.RepoInfo 26 + "IssueId" $.Issue.IssueId 27 + "IsSubscribed" $.IsSubscribed) }} 28 + {{ end }} 23 29 {{ template "repo/fragments/backlinks" 24 30 (dict "RepoInfo" $.RepoInfo 25 31 "Backlinks" $.Backlinks) }}
+31
appview/pages/templates/repo/pulls/fragments/subscribeButton.html
··· 1 + {{ define "repo/pulls/fragments/subscribeButton" }} 2 + {{ $subscribed := false }} 3 + {{ if .IsSubscribed }}{{ $subscribed = .IsSubscribed }}{{ end }} 4 + <div id="pull-subscribe-btn"> 5 + {{ if $subscribed }} 6 + <form 7 + hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .PullId }}/subscribe" 8 + hx-target="#pull-subscribe-btn" 9 + hx-swap="outerHTML"> 10 + <input type="hidden" name="subscribe" value="false"> 11 + <button type="submit" class="flex items-center gap-1.5 text-sm text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors group"> 12 + {{ i "bell" "w-4 h-4" }} 13 + <span>Subscribed</span> 14 + {{ i "loader-circle" "w-3 h-3 animate-spin hidden group-[.htmx-request]:inline" }} 15 + </button> 16 + </form> 17 + {{ else }} 18 + <form 19 + hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .PullId }}/subscribe" 20 + hx-target="#pull-subscribe-btn" 21 + hx-swap="outerHTML"> 22 + <input type="hidden" name="subscribe" value="true"> 23 + <button type="submit" class="flex items-center gap-1.5 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors group"> 24 + {{ i "bell-off" "w-4 h-4" }} 25 + <span>Subscribe</span> 26 + {{ i "loader-circle" "w-3 h-3 animate-spin hidden group-[.htmx-request]:inline" }} 27 + </button> 28 + </form> 29 + {{ end }} 30 + </div> 31 + {{ end }}
+25 -19
appview/pages/templates/repo/pulls/pull.html
··· 23 23 (function() { 24 24 const details = document.getElementById('bottomSheet'); 25 25 const backdrop = document.getElementById('bottomSheetBackdrop'); 26 - const isDesktop = () => window.matchMedia('(min-width: 768px)').matches; 26 + const isDesktop = () => window.matchMedia('(min-width: 1024px)').matches; 27 27 28 28 // function to update backdrop 29 29 const updateBackdrop = () => { ··· 54 54 updateBackdrop(); 55 55 }); 56 56 57 - const mediaQuery = window.matchMedia('(min-width: 768px)'); 57 + const mediaQuery = window.matchMedia('(min-width: 1024px)'); 58 58 mediaQuery.addEventListener('change', function(e) { 59 59 if (e.matches) { 60 60 // switched to desktop - keep open ··· 111 111 {{ end }} 112 112 113 113 {{ define "repoContentLayout" }} 114 - <div class="grid grid-cols-1 md:grid-cols-10 gap-4 w-full"> 115 - <div class="col-span-1 md:col-span-8 flex flex-col gap-4"> 114 + <div class="grid grid-cols-1 lg:grid-cols-10 gap-4 w-full"> 115 + <div class="col-span-1 lg:col-span-8 flex flex-col gap-4"> 116 116 <section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto dark:text-white h-full flex-shrink"> 117 117 {{ block "repoContent" . }}{{ end }} 118 118 </section> 119 119 {{ template "repo/pulls/fragments/pullVouchNudge" . }} 120 120 </div> 121 - <div class="flex flex-col gap-6 col-span-1 md:col-span-2"> 121 + <div class="flex flex-col gap-6 col-span-1 lg:col-span-2"> 122 122 {{ template "repo/fragments/labelPanel" 123 123 (dict "RepoInfo" $.RepoInfo 124 124 "Defs" $.LabelDefs 125 125 "Subject" $.Pull.AtUri 126 126 "State" $.Pull.Labels) }} 127 127 {{ template "repo/fragments/participants" $.Pull.Participants }} 128 + {{ if $.LoggedInUser }} 129 + {{ template "repo/pulls/fragments/subscribeButton" 130 + (dict "RepoInfo" $.RepoInfo 131 + "PullId" $.Pull.PullId 132 + "IsSubscribed" $.IsSubscribed) }} 133 + {{ end }} 128 134 {{ template "repo/fragments/backlinks" 129 135 (dict "RepoInfo" $.RepoInfo 130 136 "Backlinks" $.Backlinks) }} ··· 158 164 data-resizer="vertical" 159 165 data-target="{{ $target }}" 160 166 data-direction="{{ $direction }}" 161 - class="resizer-vertical hidden md:flex w-4 sticky top-12 max-h-screen flex-col items-center justify-center group"> 167 + class="resizer-vertical hidden lg:flex w-4 sticky top-12 max-h-screen flex-col items-center justify-center group"> 162 168 <div class="w-1 h-16 group-hover:h-24 group-[.resizing]:h-24 transition-all rounded-full bg-gray-400 dark:bg-gray-500 group-hover:bg-gray-500 group-hover:dark:bg-gray-400"></div> 163 169 </div> 164 170 {{ end }} ··· 170 176 171 177 <div class="flex col-span-full"> 172 178 <!-- left panel --> 173 - <div id="files" class="w-0 hidden md:block overflow-hidden sticky top-12 max-h-screen overflow-y-auto pb-12"> 179 + <div id="files" class="w-0 hidden lg:block overflow-hidden sticky top-12 max-h-screen overflow-y-auto pb-12"> 174 180 <section class="overflow-x-auto text-sm px-6 py-2 border-b border-x border-gray-200 dark:border-gray-700 w-full mx-auto min-h-full rounded-b rounded-t-none bg-white dark:bg-gray-800 shadow-sm"> 175 181 {{ template "repo/fragments/fileTree" $diff.FileTree }} 176 182 </section> ··· 204 210 {{ end }} 205 211 206 212 <!-- backdrop overlay - only visible on mobile when open --> 207 - <div id="bottomSheetBackdrop" class="fixed inset-0 bg-black/50 md:hidden opacity-0 pointer-events-none transition-opacity duration-300 z-20"></div> 213 + <div id="bottomSheetBackdrop" class="fixed inset-0 bg-black/50 lg:hidden opacity-0 pointer-events-none transition-opacity duration-300 z-20"></div> 208 214 <!-- right panel - bottom sheet on mobile, side panel on desktop --> 209 - <div id="subs" class="fixed bottom-0 left-0 right-0 z-30 w-full md:static md:z-auto md:max-h-screen md:sticky md:top-12 overflow-hidden"> 210 - <details open id="bottomSheet" class="rounded-t-2xl md:rounded-t shadow-lg md:shadow-none group/panel"> 215 + <div id="subs" class="fixed bottom-0 left-0 right-0 z-30 w-full lg:static lg:z-auto lg:max-h-screen lg:sticky lg:top-12 overflow-hidden"> 216 + <details open id="bottomSheet" class="rounded-t-2xl lg:rounded-t shadow-lg lg:shadow-none group/panel"> 211 217 <summary class=" 212 218 flex gap-4 items-center justify-between 213 - rounded-t-2xl md:rounded-t cursor-pointer list-none p-4 md:h-12 214 - text-white md:text-black md:dark:text-white 219 + rounded-t-2xl lg:rounded-t cursor-pointer list-none p-4 lg:h-12 220 + text-white lg:text-black lg:dark:text-white 215 221 {{ $bgColor }} 216 - md:bg-white md:dark:bg-gray-800 217 - shadow-sm border-t md:border-x border-gray-200 dark:border-gray-700 218 - md:pointer-events-none 222 + lg:bg-white lg:dark:bg-gray-800 223 + shadow-sm border-t lg:border-x border-gray-200 dark:border-gray-700 224 + lg:pointer-events-none 219 225 "> 220 226 <h2 class="">History</h2> 221 227 <span class="pointer-events-auto cursor-text"> 222 228 {{ template "subsPanelSummary" $ }} 223 229 </span> 224 230 </summary> 225 - <div class="max-h-[85vh] md:max-h-[calc(100vh-3rem-3rem)] w-full flex flex-col-reverse gap-4 overflow-y-auto bg-slate-100 dark:bg-gray-900 md:bg-transparent"> 231 + <div class="max-h-[85vh] lg:max-h-[calc(100vh-3rem-3rem)] w-full flex flex-col-reverse gap-4 overflow-y-auto bg-slate-100 dark:bg-gray-900 lg:bg-transparent"> 226 232 {{ template "submissions" $root }} 227 233 </div> 228 234 </details> ··· 243 249 {{ $comments }} comment{{ if ne $comments 1 }}s{{ end }} 244 250 </span> 245 251 246 - <span class="md:hidden inline"> 252 + <span class="lg:hidden inline"> 247 253 <span class="inline group-open:hidden">{{ i "chevron-up" "size-4" }}</span> 248 254 <span class="hidden group-open:inline">{{ i "chevron-down" "size-4" }}</span> 249 255 </span> ··· 264 270 #subsToggle:checked ~ div label[for="subsToggle"] .hide-toggle { display: flex; } 265 271 #subsToggle:not(:checked) ~ div label[for="subsToggle"] .hide-toggle { display: none; } 266 272 267 - @media (min-width: 768px) { 273 + @media (min-width: 1024px) { 268 274 #subsToggle:checked ~ div div#subs { 269 275 width: 25vw; 270 276 max-width: 50vw; ··· 279 285 } 280 286 } 281 287 </style> 282 - <label title="Toggle review panel" for="subsToggle" class="hidden md:flex items-center justify-end pointer-events-none"> 288 + <label title="Toggle review panel" for="subsToggle" class="hidden lg:flex items-center justify-end pointer-events-none"> 283 289 <span class="show-toggle hit-area hit-area-4 hit-area-x-2 pointer-events-auto cursor-pointer">{{ i "message-square-more" "size-4" }}</span> 284 290 <span class="hide-toggle w-[25vw] justify-end"><span class="hit-area hit-area-4 hit-area-x-2 pointer-events-auto cursor-pointer">{{ i "message-square" "size-4" }}</span></span> 285 291 </label>