Monorepo for Tangled
0

Configure Feed

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

appview/pages: pull comment fixes

Lewis: May this revision serve well! <lewis@tangled.org>

authored by

Lewis and committed by
Tangled
(Jun 29, 2026, 3:26 PM +0300) a8bc4e59 28ebc96f

+109 -39
+3
appview/pages/compose_parse_test.go
··· 30 30 {"pullCompareForks", []string{"repo/pulls/fragments/pullCompareForks"}}, 31 31 {"pullCompareBranches", []string{"repo/pulls/fragments/pullCompareBranches"}}, 32 32 {"pullCompareForksBranches", []string{"repo/pulls/fragments/pullCompareForksBranches"}}, 33 + {"pull.html via repo base", []string{"layouts/base", "layouts/repobase", "repo/pulls/pull"}}, 34 + {"pullNewComment", []string{"repo/pulls/fragments/pullNewComment"}}, 35 + {"pullComment", []string{"fragments/comment/pullComment"}}, 33 36 } 34 37 35 38 for _, c := range cases {
+12
appview/pages/pages.go
··· 1877 1877 return p.executePlain("fragments/comment/commentBody", w, params) 1878 1878 } 1879 1879 1880 + type PullCommentFragmentParams struct { 1881 + LoggedInUser *oauth.MultiAccountUser 1882 + Comment models.Comment 1883 + Reactions map[models.ReactionKind]models.ReactionDisplayData 1884 + UserReacted map[models.ReactionKind]bool 1885 + HxSwapOob bool 1886 + } 1887 + 1888 + func (p *Pages) PullCommentFragment(w io.Writer, params PullCommentFragmentParams) error { 1889 + return p.executePlain("fragments/comment/pullComment", w, params) 1890 + } 1891 + 1880 1892 type CommentHeaderFragmentParams struct { 1881 1893 Comment models.Comment 1882 1894 Reactions map[models.ReactionKind]models.ReactionDisplayData
+13
appview/pages/templates/fragments/comment/pullComment.html
··· 1 + {{ define "fragments/comment/pullComment" }} 2 + <div class="flex gap-2 -ml-4 py-4 w-full mx-auto group/comment"> 3 + <!-- left column: profile picture --> 4 + <div class="flex-shrink-0 h-fit relative"> 5 + {{ template "user/fragments/picLink" (list .Comment.Did.String "size-8") }} 6 + </div> 7 + <!-- right column: name and body in two rows --> 8 + <div class="flex-1 min-w-0"> 9 + {{ template "fragments/comment/commentHeader" . }} 10 + {{ template "fragments/comment/commentBody" . }} 11 + </div> 12 + </div> 13 + {{ end }}
+3 -1
appview/pages/templates/fragments/line-quote-button.html
··· 66 66 67 67 const highlightFromHash = () => { 68 68 clearHl('line-range-hl'); 69 - const hash = decodeURIComponent(window.location.hash.slice(1)); 69 + let hash; 70 + try { hash = decodeURIComponent(window.location.hash.slice(1)); } catch (e) { return; } 70 71 if (!hash) return; 72 + if (hash.startsWith('comment-') || hash.startsWith('round-')) return; 71 73 const parts = hash.split('~'); 72 74 const startEl = document.getElementById(parts[0]); 73 75
+4 -2
appview/pages/templates/repo/pulls/fragments/pullNewComment.html
··· 5 5 class="w-full flex flex-wrap gap-2 group" 6 6 hx-post="/comment" 7 7 hx-trigger="submit, keydown[(ctrlKey || metaKey) && key=='Enter'] from:find textarea" 8 - hx-swap="none" 8 + hx-target="#pull-comments-{{ .RoundNumber }}" 9 + hx-swap="beforeend" 9 10 hx-disabled-elt="find button[type='submit']" 11 + hx-on::before-request="this.querySelector('.error').innerHTML = ''" 10 12 hx-on::after-request="if(event.detail.successful) this.reset()" 11 13 > 12 14 <input name="subject-uri" type="hidden" value="{{ .Pull.AtUri }}"> ··· 19 21 placeholder="Add to the discussion..."></textarea 20 22 > 21 23 {{ template "replyActions" . }} 22 - <div id="pull-comment"></div> 24 + <div id="comment-error-{{ .RoundNumber }}" class="error"></div> 23 25 </form> 24 26 </div> 25 27 {{ end }}
+41 -24
appview/pages/templates/repo/pulls/pull.html
··· 76 76 } 77 77 })(); 78 78 </script> 79 + <script> 80 + (function() { 81 + const isPermalink = (id) => id.startsWith('comment-') || id.startsWith('round-'); 82 + 83 + const reveal = () => { 84 + const raw = window.location.hash.slice(1); 85 + if (!raw) return; 86 + let id; 87 + try { id = decodeURIComponent(raw); } catch (e) { return; } 88 + if (!isPermalink(id)) return; 89 + const target = document.getElementById(id); 90 + if (!target) return; 91 + 92 + for (let el = target.parentElement; el; el = el.parentElement) { 93 + if (el.tagName === 'DETAILS') el.open = true; 94 + } 95 + 96 + requestAnimationFrame(() => requestAnimationFrame(() => { 97 + target.scrollIntoView({ block: 'center' }); 98 + const card = target.closest('.group\\/comment') || target; 99 + card.classList.add('comment-hl'); 100 + })); 101 + }; 102 + 103 + if (document.readyState === 'loading') { 104 + document.addEventListener('DOMContentLoaded', reveal); 105 + } else { 106 + reveal(); 107 + } 108 + window.addEventListener('hashchange', reveal); 109 + })(); 110 + </script> 79 111 {{ end }} 80 112 81 113 {{ define "repoContentLayout" }} ··· 271 303 {{ $lastIdx := index . 2 }} 272 304 {{ $root := index . 3 }} 273 305 {{ $round := $item.RoundNumber }} 274 - <div class=" 306 + <div id="round-{{ $round }}" class=" 275 307 w-full shadow-sm border overflow-clip 276 308 277 309 {{ if eq $round 0 }}rounded-b{{ else }}rounded{{ end }} ··· 362 394 {{ else }} 363 395 text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300 364 396 {{ end }} 365 - " href="#round-#{{ $round }}"> 397 + " href="#round-{{ $round }}"> 366 398 {{ template "repo/fragments/shortTime" $item.Created }} 367 399 </a> 368 400 </span> 369 401 <div class="flex gap-2 items-center"> 370 402 {{ if or $root.IsInterdiff (ne $root.ActiveRound $round) }} 371 403 <a class="btn-flat flex items-center gap-2 no-underline hover:no-underline text-sm" 372 - href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ $round }}?{{ safeUrl $root.DiffOpts.Encode }}#round-#{{ $round }}"> 404 + href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ $round }}?{{ safeUrl $root.DiffOpts.Encode }}#round-{{ $round }}"> 373 405 {{ i "diff" "w-4 h-4" }} 374 406 Diff 375 407 </a> ··· 544 576 </span> 545 577 </div> 546 578 </summary> 547 - <div> 579 + <div id="pull-comments-{{ $round }}"> 548 580 {{ range $item.Comments }} 549 - {{ template "comment" 550 - (dict "LoggedInUser" $root.LoggedInUser 551 - "Reactions" (index (asReactionMapMap $root.Reactions) .FeedCommentAtUri) 552 - "UserReacted" (index (asReactionStatusMapMap $root.UserReacted) .FeedCommentAtUri) 553 - "VouchRelationship" (index $root.VouchRelationships .Did) 554 - "Comment" .) }} 581 + {{ template "fragments/comment/pullComment" 582 + (dict "LoggedInUser" $root.LoggedInUser 583 + "Reactions" (index (asReactionMapMap $root.Reactions) .FeedCommentAtUri) 584 + "UserReacted" (index (asReactionStatusMapMap $root.UserReacted) .FeedCommentAtUri) 585 + "Comment" .) }} 555 586 {{ end }} 556 587 </div> 557 588 {{ if gt $c 0}} ··· 584 615 "Loading" (eq $lastIdx $item.RoundNumber)) }} 585 616 </div> 586 617 </details> 587 - {{ end }} 588 - 589 - {{ define "comment" }} 590 - <div class="flex gap-2 -ml-4 py-4 w-full mx-auto group/comment"> 591 - <!-- left column: profile picture --> 592 - <div class="flex-shrink-0 h-fit relative"> 593 - {{ template "user/fragments/picLink" (list .Comment.Did.String "size-8") }} 594 - </div> 595 - <!-- right column: name and body in two rows --> 596 - <div class="flex-1 min-w-0"> 597 - {{ template "fragments/comment/commentHeader" . }} 598 - {{ template "fragments/comment/commentBody" . }} 599 - </div> 600 - </div> 601 618 {{ end }} 602 619 603 620 {{ define "loginPrompt" }}
+27 -11
appview/state/comment.go
··· 94 94 noticeId := "comment-error" 95 95 ctx := r.Context() 96 96 97 + var pullRoundIdx *int 98 + if pullRoundIdxRaw := r.FormValue("pull-round-idx"); pullRoundIdxRaw != "" { 99 + roundIdx, err := strconv.Atoi(pullRoundIdxRaw) 100 + if err != nil { 101 + l.Warn("invalid round idx", "err", err) 102 + s.pages.Notice(w, noticeId, "pull round index should be valid integer") 103 + return 104 + } 105 + pullRoundIdx = &roundIdx 106 + noticeId = fmt.Sprintf("comment-error-%d", roundIdx) 107 + } 108 + 97 109 body := r.FormValue("body") 98 110 if body == "" { 99 111 s.pages.Notice(w, noticeId, "Body is required") ··· 163 175 subject := comatproto.RepoStrongRef{ 164 176 Uri: subjectUri.String(), 165 177 Cid: subjectCid.String(), 166 - } 167 - 168 - var pullRoundIdx *int 169 - if pullRoundIdxRaw := r.FormValue("pull-round-idx"); pullRoundIdxRaw != "" { 170 - roundIdx, err := strconv.Atoi(pullRoundIdxRaw) 171 - if err != nil { 172 - l.Warn("invalid round idx", "err", err) 173 - s.pages.Notice(w, noticeId, "pull round index should be valid integer") 174 - return 175 - } 176 - pullRoundIdx = &roundIdx 177 178 } 178 179 179 180 var replyTo *comatproto.RepoStrongRef ··· 294 295 } 295 296 296 297 s.notifier.NewComment(ctx, &comment, mentions) 298 + 299 + if pullRoundIdx != nil { 300 + var buf bytes.Buffer 301 + if err := s.pages.PullCommentFragment(&buf, pages.PullCommentFragmentParams{ 302 + LoggedInUser: user, 303 + Comment: comment, 304 + }); err != nil { 305 + l.Error("failed to render pull comment fragment", "err", err) 306 + s.pages.HxRefresh(w) 307 + return 308 + } 309 + w.Header().Set("Content-Type", "text/html") 310 + w.Write(buf.Bytes()) 311 + return 312 + } 297 313 298 314 // TODO: return comment or reply-comment fragment 299 315 // onattach, htmx-callback to focus on comment.
+6 -1
input.css
··· 550 550 } 551 551 552 552 .line-quote-hl, 553 - .line-range-hl { 553 + .line-range-hl, 554 + .comment-hl { 554 555 @apply !bg-yellow-200/30 dark:!bg-yellow-700/30; 555 556 } 556 557 557 558 .chunk-match-hl { 558 559 @apply rounded-sm !bg-yellow-300/70 dark:!bg-yellow-600/60; 560 + } 561 + 562 + .comment-hl { 563 + @apply rounded-md; 559 564 } 560 565 561 566 :is(.line-quote-hl, .line-range-hl) > .min-w-\[3\.5rem\] {