this repo has no description

Compare changes

Choose any two refs to compare.

+39
appview/models/pipeline.go
··· 1 1 package models 2 2 3 3 import ( 4 + "fmt" 4 5 "slices" 6 + "strings" 5 7 "time" 6 8 7 9 "github.com/bluesky-social/indigo/atproto/syntax" ··· 50 52 } 51 53 52 54 return 0 55 + } 56 + 57 + // produces short summary of successes: 58 + // - "0/4" when zero successes of 4 workflows 59 + // - "4/4" when all successes of 4 workflows 60 + // - "0/0" when no workflows run in this pipeline 61 + func (p Pipeline) ShortStatusSummary() string { 62 + counts := make(map[spindle.StatusKind]int) 63 + for _, w := range p.Statuses { 64 + counts[w.Latest().Status] += 1 65 + } 66 + 67 + total := len(p.Statuses) 68 + successes := counts[spindle.StatusKindSuccess] 69 + 70 + return fmt.Sprintf("%d/%d", successes, total) 71 + } 72 + 73 + // produces a string of the form "3/4 success, 2/4 failed, 1/4 pending" 74 + func (p Pipeline) LongStatusSummary() string { 75 + counts := make(map[spindle.StatusKind]int) 76 + for _, w := range p.Statuses { 77 + counts[w.Latest().Status] += 1 78 + } 79 + 80 + total := len(p.Statuses) 81 + 82 + var result []string 83 + // finish states first, followed by start states 84 + states := append(spindle.FinishStates[:], spindle.StartStates[:]...) 85 + for _, state := range states { 86 + if count, ok := counts[state]; ok { 87 + result = append(result, fmt.Sprintf("%d/%d %s", count, total, state.String())) 88 + } 89 + } 90 + 91 + return strings.Join(result, ", ") 53 92 } 54 93 55 94 func (p Pipeline) Counts() map[string]int {
+7 -17
appview/models/pull.go
··· 171 171 return syntax.ATURI(p.CommentAt) 172 172 } 173 173 174 - // func (p *PullComment) AsRecord() tangled.RepoPullComment { 175 - // mentions := make([]string, len(p.Mentions)) 176 - // for i, did := range p.Mentions { 177 - // mentions[i] = string(did) 178 - // } 179 - // references := make([]string, len(p.References)) 180 - // for i, uri := range p.References { 181 - // references[i] = string(uri) 182 - // } 183 - // return tangled.RepoPullComment{ 184 - // Pull: p.PullAt, 185 - // Body: p.Body, 186 - // Mentions: mentions, 187 - // References: references, 188 - // CreatedAt: p.Created.Format(time.RFC3339), 189 - // } 190 - // } 174 + func (p *Pull) TotalComments() int { 175 + total := 0 176 + for _, s := range p.Submissions { 177 + total += len(s.Comments) 178 + } 179 + return total 180 + } 191 181 192 182 func (p *Pull) LastRoundNumber() int { 193 183 return len(p.Submissions) - 1
+20 -9
appview/pages/funcmap.go
··· 26 26 "github.com/go-enry/go-enry/v2" 27 27 "github.com/yuin/goldmark" 28 28 emoji "github.com/yuin/goldmark-emoji" 29 - "tangled.org/core/appview/filetree" 30 29 "tangled.org/core/appview/models" 31 30 "tangled.org/core/appview/pages/markup" 32 31 "tangled.org/core/crypto" ··· 334 333 }, 335 334 "deref": func(v any) any { 336 335 val := reflect.ValueOf(v) 337 - if val.Kind() == reflect.Ptr && !val.IsNil() { 336 + if val.Kind() == reflect.Pointer && !val.IsNil() { 338 337 return val.Elem().Interface() 339 338 } 340 339 return nil ··· 348 347 return template.HTML(data) 349 348 }, 350 349 "cssContentHash": p.CssContentHash, 351 - "fileTree": filetree.FileTree, 352 350 "pathEscape": func(s string) string { 353 351 return url.PathEscape(s) 354 352 }, ··· 366 364 return p.AvatarUrl(handle, "") 367 365 }, 368 366 "langColor": enry.GetColor, 369 - "layoutSide": func() string { 370 - return "col-span-1 md:col-span-2 lg:col-span-3" 371 - }, 372 - "layoutCenter": func() string { 373 - return "col-span-1 md:col-span-8 lg:col-span-6" 374 - }, 367 + "reverse": func(s any) any { 368 + if s == nil { 369 + return nil 370 + } 375 371 372 + v := reflect.ValueOf(s) 373 + 374 + if v.Kind() != reflect.Slice { 375 + return s 376 + } 377 + 378 + length := v.Len() 379 + reversed := reflect.MakeSlice(v.Type(), length, length) 380 + 381 + for i := range length { 382 + reversed.Index(i).Set(v.Index(length - 1 - i)) 383 + } 384 + 385 + return reversed.Interface() 386 + }, 376 387 "normalizeForHtmlId": func(s string) string { 377 388 normalized := strings.ReplaceAll(s, ":", "_") 378 389 normalized = strings.ReplaceAll(normalized, ".", "_")
+3 -3
appview/pages/pages.go
··· 648 648 IsStarred bool 649 649 SubjectAt syntax.ATURI 650 650 StarCount int 651 - HxSwapOob bool 652 651 } 653 652 654 653 func (p *Pages) StarBtnFragment(w io.Writer, params StarBtnFragmentParams) error { 655 - params.HxSwapOob = true 656 - return p.executePlain("fragments/starBtn", w, params) 654 + return p.executePlain("fragments/starBtn-oob", w, params) 657 655 } 658 656 659 657 type RepoIndexParams struct { ··· 1105 1103 MergeCheck types.MergeCheckResponse 1106 1104 ResubmitCheck ResubmitResult 1107 1105 Pipelines map[string]models.Pipeline 1106 + Diff *types.NiceDiff 1107 + DiffOpts types.DiffOpts 1108 1108 1109 1109 OrderedReactionKinds []models.ReactionKind 1110 1110 Reactions map[models.ReactionKind]models.ReactionDisplayData
+5
appview/pages/templates/fragments/starBtn-oob.html
··· 1 + {{ define "fragments/starBtn-oob" }} 2 + <div hx-swap-oob='outerHTML:#starBtn[data-star-subject-at="{{ .SubjectAt }}"]'> 3 + {{ template "fragments/starBtn" . }} 4 + </div> 5 + {{ end }}
-1
appview/pages/templates/fragments/starBtn.html
··· 9 9 {{ else }} 10 10 hx-post="/star?subject={{ .SubjectAt }}&countHint={{ .StarCount }}" 11 11 {{ end }} 12 - {{ if .HxSwapOob }}hx-swap-oob='outerHTML:#starBtn[data-star-subject-at="{{ .SubjectAt }}"]'{{ end }} 13 12 14 13 hx-trigger="click" 15 14 hx-disabled-elt="#starBtn"
+1 -1
appview/pages/templates/layouts/repobase.html
··· 1 1 {{ define "title" }}{{ .RepoInfo.FullName }}{{ end }} 2 2 3 3 {{ define "content" }} 4 - <section id="repo-header" class="mb-4 p-2 dark:text-white"> 4 + <section id="repo-header" class="mb-2 py-2 px-4 dark:text-white"> 5 5 <div class="text-lg flex flex-col sm:flex-row items-start gap-4 justify-between"> 6 6 <!-- left items --> 7 7 <div class="flex flex-col gap-2">
+1 -18
appview/pages/templates/repo/commit.html
··· 116 116 {{ block "content" . }}{{ end }} 117 117 {{ end }} 118 118 119 - {{ block "contentAfterLayout" . }} 120 - <div class="flex-grow grid grid-cols-1 md:grid-cols-12 gap-4"> 121 - <div class="flex flex-col gap-4 col-span-1 md:col-span-2"> 122 - {{ block "contentAfterLeft" . }} {{ end }} 123 - </div> 124 - <main class="col-span-1 md:col-span-10"> 125 - {{ block "contentAfter" . }}{{ end }} 126 - </main> 127 - </div> 128 - {{ end }} 119 + {{ block "contentAfter" . }}{{ end }} 129 120 </div> 130 121 {{ end }} 131 122 ··· 139 130 {{ template "repo/fragments/diff" (list .Diff .DiffOpts) }} 140 131 {{end}} 141 132 142 - {{ define "contentAfterLeft" }} 143 - <div class="flex flex-col gap-4 col-span-1 md:col-span-2"> 144 - {{ template "repo/fragments/diffOpts" .DiffOpts }} 145 - </div> 146 - <div class="sticky top-0 flex-grow max-h-screen overflow-y-auto"> 147 - {{ template "repo/fragments/diffChangedFiles" .Diff }} 148 - </div> 149 - {{end}}
+1 -19
appview/pages/templates/repo/compare/compare.html
··· 22 22 {{ block "content" . }}{{ end }} 23 23 {{ end }} 24 24 25 - {{ block "contentAfterLayout" . }} 26 - <div class="flex-grow grid grid-cols-1 md:grid-cols-12 gap-4"> 27 - <div class="flex flex-col gap-4 col-span-1 md:col-span-2"> 28 - {{ block "contentAfterLeft" . }} {{ end }} 29 - </div> 30 - <main class="col-span-1 md:col-span-10"> 31 - {{ block "contentAfter" . }}{{ end }} 32 - </main> 33 - </div> 34 - {{ end }} 25 + {{ block "contentAfter" . }}{{ end }} 35 26 </div> 36 27 {{ end }} 37 28 ··· 44 35 {{ define "contentAfter" }} 45 36 {{ template "repo/fragments/diff" (list .Diff .DiffOpts) }} 46 37 {{end}} 47 - 48 - {{ define "contentAfterLeft" }} 49 - <div class="flex flex-col gap-4 col-span-1 md:col-span-2"> 50 - {{ template "repo/fragments/diffOpts" .DiffOpts }} 51 - </div> 52 - <div class="sticky top-0 flex-grow max-h-screen overflow-y-auto"> 53 - {{ template "repo/fragments/diffChangedFiles" .Diff }} 54 - </div> 55 - {{end}}
+164 -43
appview/pages/templates/repo/fragments/diff.html
··· 1 1 {{ define "repo/fragments/diff" }} 2 + <style> 3 + #filesToggle:checked ~ div label[for="filesToggle"] .show-text { display: none; } 4 + #filesToggle:checked ~ div label[for="filesToggle"] .hide-text { display: inline; } 5 + #filesToggle:not(:checked) ~ div label[for="filesToggle"] .hide-text { display: none; } 6 + #filesToggle:checked ~ div div#files { width: fit-content; max-width: 15vw; margin-right: 1rem; } 7 + #filesToggle:not(:checked) ~ div div#files { width: 0; display: hidden; margin-right: 0; } 8 + </style> 9 + 10 + {{ template "diffTopbar" . }} 11 + {{ block "diffLayout" . }} {{ end }} 12 + {{ end }} 13 + 14 + {{ define "diffTopbar" }} 2 15 {{ $diff := index . 0 }} 3 16 {{ $opts := index . 1 }} 4 17 5 - {{ $commit := $diff.Commit }} 6 - {{ $diff := $diff.Diff }} 7 - {{ $isSplit := $opts.Split }} 8 - {{ $this := $commit.This }} 9 - {{ $parent := $commit.Parent }} 10 - {{ $last := sub (len $diff) 1 }} 18 + {{ block "filesCheckbox" $ }} {{ end }} 19 + {{ block "subsCheckbox" $ }} {{ end }} 20 + 21 + <!-- top bar --> 22 + <div class="sticky top-0 z-30 bg-slate-100 dark:bg-gray-900 flex items-center gap-2 col-span-full h-12 p-2"> 23 + <!-- left panel toggle --> 24 + {{ template "filesToggle" . }} 11 25 26 + <!-- stats --> 27 + {{ $stat := $diff.Stats }} 28 + {{ $count := len $diff.ChangedFiles }} 29 + {{ template "repo/fragments/diffStatPill" $stat }} 30 + {{ $count }} changed file{{ if ne $count 1 }}s{{ end }} 31 + 32 + <!-- spacer --> 33 + <div class="flex-grow"></div> 34 + 35 + <!-- collapse diffs --> 36 + {{ template "collapseToggle" }} 37 + 38 + <!-- diff options --> 39 + {{ template "repo/fragments/diffOpts" $opts }} 40 + 41 + <!-- right panel toggle --> 42 + {{ block "subsToggle" $ }} {{ end }} 43 + </div> 44 + 45 + {{ end }} 46 + 47 + {{ define "diffLayout" }} 48 + {{ $diff := index . 0 }} 49 + {{ $opts := index . 1 }} 50 + 51 + <div class="flex col-span-full flex-grow"> 52 + <!-- left panel --> 53 + <div id="files" class="w-0 hidden md:block overflow-hidden sticky top-12 max-h-screen overflow-y-auto pb-12"> 54 + <section class="overflow-x-auto text-sm px-6 py-2 border border-gray-200 dark:border-gray-700 w-full mx-auto min-h-full rounded bg-white dark:bg-gray-800 drop-shadow-sm"> 55 + {{ template "repo/fragments/fileTree" $diff.FileTree }} 56 + </section> 57 + </div> 58 + 59 + <!-- main content --> 60 + <div class="flex-1 min-w-0 sticky top-12 pb-12"> 61 + {{ template "diffFiles" (list $diff $opts) }} 62 + </div> 63 + 64 + </div> 65 + {{ end }} 66 + 67 + {{ define "diffFiles" }} 68 + {{ $diff := index . 0 }} 69 + {{ $opts := index . 1 }} 70 + {{ $files := $diff.ChangedFiles }} 71 + {{ $isSplit := $opts.Split }} 12 72 <div class="flex flex-col gap-4"> 13 - {{ if eq (len $diff) 0 }} 73 + {{ if eq (len $files) 0 }} 14 74 <div class="text-center text-gray-500 dark:text-gray-400 py-8"> 15 75 <p>No differences found between the selected revisions.</p> 16 76 </div> 17 77 {{ else }} 18 - {{ range $idx, $hunk := $diff }} 19 - {{ with $hunk }} 20 - <details open id="file-{{ .Id }}" class="group border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm" tabindex="{{ add $idx 1 }}"> 21 - <summary class="list-none cursor-pointer sticky top-0"> 22 - <div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between"> 23 - <div id="left-side-items" class="p-2 flex gap-2 items-center overflow-x-auto"> 24 - <span class="group-open:hidden inline">{{ i "chevron-right" "w-4 h-4" }}</span> 25 - <span class="hidden group-open:inline">{{ i "chevron-down" "w-4 h-4" }}</span> 26 - {{ template "repo/fragments/diffStatPill" .Stats }} 78 + {{ range $idx, $file := $files }} 79 + {{ template "diffFile" (list $idx $file $isSplit) }} 80 + {{ end }} 81 + {{ end }} 82 + </div> 83 + {{ end }} 27 84 28 - <div class="flex gap-2 items-center overflow-x-auto"> 29 - {{ if .IsDelete }} 30 - {{ .Name.Old }} 31 - {{ else if (or .IsCopy .IsRename) }} 32 - {{ .Name.Old }} {{ i "arrow-right" "w-4 h-4" }} {{ .Name.New }} 33 - {{ else }} 34 - {{ .Name.New }} 35 - {{ end }} 36 - </div> 37 - </div> 38 - </div> 39 - </summary> 85 + {{ define "diffFile" }} 86 + {{ $idx := index . 0 }} 87 + {{ $file := index . 1 }} 88 + {{ $isSplit := index . 2 }} 89 + {{ with $file }} 90 + <details open id="file-{{ .Id }}" class="group border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm" tabindex="{{ add $idx 1 }}"> 91 + <summary class="list-none cursor-pointer sticky top-12 group-open:border-b border-gray-200 dark:border-gray-700"> 92 + <div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between"> 93 + <div id="left-side-items" class="p-2 flex gap-2 items-center overflow-x-auto"> 94 + <span class="group-open:hidden inline">{{ i "chevron-right" "w-4 h-4" }}</span> 95 + <span class="hidden group-open:inline">{{ i "chevron-down" "w-4 h-4" }}</span> 96 + {{ template "repo/fragments/diffStatPill" .Stats }} 40 97 41 - <div class="transition-all duration-700 ease-in-out"> 42 - {{ if .IsBinary }} 43 - <p class="text-center text-gray-400 dark:text-gray-500 p-4"> 44 - This is a binary file and will not be displayed. 45 - </p> 46 - {{ else }} 47 - {{ if $isSplit }} 48 - {{- template "repo/fragments/splitDiff" .Split -}} 98 + <div class="flex gap-2 items-center overflow-x-auto"> 99 + {{ $n := .Names }} 100 + {{ if and $n.New $n.Old (ne $n.New $n.Old)}} 101 + {{ $n.Old }} {{ i "arrow-right" "w-4 h-4" }} {{ $n.New }} 102 + {{ else if $n.New }} 103 + {{ $n.New }} 49 104 {{ else }} 50 - {{- template "repo/fragments/unifiedDiff" . -}} 105 + {{ $n.Old }} 51 106 {{ end }} 52 - {{- end -}} 107 + </div> 53 108 </div> 54 - </details> 55 - {{ end }} 56 - {{ end }} 57 - {{ end }} 58 - </div> 109 + </div> 110 + </summary> 111 + 112 + <div class="transition-all duration-700 ease-in-out"> 113 + {{ $reason := .CanRender }} 114 + {{ if $reason }} 115 + <p class="text-center text-gray-400 dark:text-gray-500 p-4">{{ $reason }}</p> 116 + {{ else }} 117 + {{ if $isSplit }} 118 + {{- template "repo/fragments/splitDiff" .Split -}} 119 + {{ else }} 120 + {{- template "repo/fragments/unifiedDiff" . -}} 121 + {{ end }} 122 + {{- end -}} 123 + </div> 124 + </details> 125 + {{ end }} 126 + {{ end }} 127 + 128 + {{ define "filesCheckbox" }} 129 + <input type="checkbox" id="filesToggle" class="peer/files hidden" checked/> 130 + {{ end }} 131 + 132 + {{ define "filesToggle" }} 133 + <label title="Toggle filetree panel" for="filesToggle" class="hidden md:inline-flex items-center justify-center rounded cursor-pointer text-normal font-normal normalcase"> 134 + <span class="show-text">{{ i "panel-left-open" "size-4" }}</span> 135 + <span class="hide-text">{{ i "panel-left-close" "size-4" }}</span> 136 + </label> 137 + {{ end }} 138 + 139 + {{ define "collapseToggle" }} 140 + <label 141 + title="Expand/Collapse diffs" 142 + for="collapseToggle" 143 + class="btn font-normal normal-case p-2" 144 + > 145 + <input type="checkbox" id="collapseToggle" class="peer/collapse hidden" checked/> 146 + <span class="peer-checked/collapse:hidden inline-flex items-center gap-2"> 147 + {{ i "fold-vertical" "w-4 h-4" }} 148 + <span class="hidden md:inline">expand all</span> 149 + </span> 150 + <span class="peer-checked/collapse:inline-flex hidden flex items-center gap-2"> 151 + {{ i "unfold-vertical" "w-4 h-4" }} 152 + <span class="hidden md:inline">collapse all</span> 153 + </span> 154 + </label> 155 + <script> 156 + document.addEventListener('DOMContentLoaded', function() { 157 + const checkbox = document.getElementById('collapseToggle'); 158 + const details = document.querySelectorAll('details[id^="file-"]'); 159 + 160 + checkbox.addEventListener('change', function() { 161 + details.forEach(detail => { 162 + detail.open = checkbox.checked; 163 + }); 164 + }); 165 + 166 + details.forEach(detail => { 167 + detail.addEventListener('toggle', function() { 168 + const allOpen = Array.from(details).every(d => d.open); 169 + const allClosed = Array.from(details).every(d => !d.open); 170 + 171 + if (allOpen) { 172 + checkbox.checked = true; 173 + } else if (allClosed) { 174 + checkbox.checked = false; 175 + } 176 + }); 177 + }); 178 + }); 179 + </script> 59 180 {{ end }}
-13
appview/pages/templates/repo/fragments/diffChangedFiles.html
··· 1 - {{ define "repo/fragments/diffChangedFiles" }} 2 - {{ $stat := .Stat }} 3 - {{ $fileTree := fileTree .ChangedFiles }} 4 - <section class="overflow-x-auto text-sm px-6 py-2 border border-gray-200 dark:border-gray-700 w-full mx-auto min-h-full rounded bg-white dark:bg-gray-800 drop-shadow-sm"> 5 - <div class="diff-stat"> 6 - <div class="flex gap-2 items-center"> 7 - <strong class="text-sm uppercase dark:text-gray-200">Changed files</strong> 8 - {{ template "repo/fragments/diffStatPill" $stat }} 9 - </div> 10 - {{ template "repo/fragments/fileTree" $fileTree }} 11 - </div> 12 - </section> 13 - {{ end }}
+22 -25
appview/pages/templates/repo/fragments/diffOpts.html
··· 1 1 {{ define "repo/fragments/diffOpts" }} 2 - <section class="flex flex-col gap-2 overflow-x-auto text-sm px-6 py-2 border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm"> 3 - <strong class="text-sm uppercase dark:text-gray-200">options</strong> 4 - {{ $active := "unified" }} 5 - {{ if .Split }} 6 - {{ $active = "split" }} 7 - {{ end }} 2 + {{ $active := "unified" }} 3 + {{ if .Split }} 4 + {{ $active = "split" }} 5 + {{ end }} 8 6 9 - {{ $unified := 10 - (dict 11 - "Key" "unified" 12 - "Value" "unified" 13 - "Icon" "square-split-vertical" 14 - "Meta" "") }} 15 - {{ $split := 16 - (dict 17 - "Key" "split" 18 - "Value" "split" 19 - "Icon" "square-split-horizontal" 20 - "Meta" "") }} 21 - {{ $values := list $unified $split }} 7 + {{ $unified := 8 + (dict 9 + "Key" "unified" 10 + "Value" "unified" 11 + "Icon" "square-split-vertical" 12 + "Meta" "") }} 13 + {{ $split := 14 + (dict 15 + "Key" "split" 16 + "Value" "split" 17 + "Icon" "square-split-horizontal" 18 + "Meta" "") }} 19 + {{ $values := list $unified $split }} 22 20 23 - {{ template "fragments/tabSelector" 24 - (dict 25 - "Name" "diff" 26 - "Values" $values 27 - "Active" $active) }} 28 - </section> 21 + {{ template "fragments/tabSelector" 22 + (dict 23 + "Name" "diff" 24 + "Values" $values 25 + "Active" $active) }} 29 26 {{ end }} 30 27
-67
appview/pages/templates/repo/fragments/interdiff.html
··· 1 - {{ define "repo/fragments/interdiff" }} 2 - {{ $repo := index . 0 }} 3 - {{ $x := index . 1 }} 4 - {{ $opts := index . 2 }} 5 - {{ $fileTree := fileTree $x.AffectedFiles }} 6 - {{ $diff := $x.Files }} 7 - {{ $last := sub (len $diff) 1 }} 8 - {{ $isSplit := $opts.Split }} 9 - 10 - <div class="flex flex-col gap-4"> 11 - {{ range $idx, $hunk := $diff }} 12 - {{ with $hunk }} 13 - <details {{ if not (.Status.IsOnlyInOne) }}open{{end}} id="file-{{ .Name }}" class="border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm"> 14 - <summary class="list-none cursor-pointer sticky top-0"> 15 - <div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between"> 16 - <div id="left-side-items" class="p-2 flex gap-2 items-center overflow-x-auto"> 17 - <div class="flex gap-1 items-center" style="direction: ltr;"> 18 - {{ $markerstyle := "diff-type p-1 mr-1 font-mono text-sm rounded select-none" }} 19 - {{ if .Status.IsOk }} 20 - <span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">CHANGED</span> 21 - {{ else if .Status.IsUnchanged }} 22 - <span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">UNCHANGED</span> 23 - {{ else if .Status.IsOnlyInOne }} 24 - <span class="bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400 {{ $markerstyle }}">REVERTED</span> 25 - {{ else if .Status.IsOnlyInTwo }} 26 - <span class="bg-green-100 text-green-700 dark:bg-green-800/50 dark:text-green-400 {{ $markerstyle }}">NEW</span> 27 - {{ else if .Status.IsRebased }} 28 - <span class="bg-amber-100 text-amber-700 dark:bg-amber-800/50 dark:text-amber-400 {{ $markerstyle }}">REBASED</span> 29 - {{ else }} 30 - <span class="bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400 {{ $markerstyle }}">ERROR</span> 31 - {{ end }} 32 - </div> 33 - 34 - <div class="flex gap-2 items-center overflow-x-auto" style="direction: rtl;">{{ .Name }}</div> 35 - </div> 36 - 37 - </div> 38 - </summary> 39 - 40 - <div class="transition-all duration-700 ease-in-out"> 41 - {{ if .Status.IsUnchanged }} 42 - <p class="text-center text-gray-400 dark:text-gray-500 p-4"> 43 - This file has not been changed. 44 - </p> 45 - {{ else if .Status.IsRebased }} 46 - <p class="text-center text-gray-400 dark:text-gray-500 p-4"> 47 - This patch was likely rebased, as context lines do not match. 48 - </p> 49 - {{ else if .Status.IsError }} 50 - <p class="text-center text-gray-400 dark:text-gray-500 p-4"> 51 - Failed to calculate interdiff for this file. 52 - </p> 53 - {{ else }} 54 - {{ if $isSplit }} 55 - {{- template "repo/fragments/splitDiff" .Split -}} 56 - {{ else }} 57 - {{- template "repo/fragments/unifiedDiff" . -}} 58 - {{ end }} 59 - {{- end -}} 60 - </div> 61 - 62 - </details> 63 - {{ end }} 64 - {{ end }} 65 - </div> 66 - {{ end }} 67 -
-11
appview/pages/templates/repo/fragments/interdiffFiles.html
··· 1 - {{ define "repo/fragments/interdiffFiles" }} 2 - {{ $fileTree := fileTree .AffectedFiles }} 3 - <section class="px-6 py-2 border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm min-h-full text-sm"> 4 - <div class="diff-stat"> 5 - <div class="flex gap-2 items-center"> 6 - <strong class="text-sm uppercase dark:text-gray-200">files</strong> 7 - </div> 8 - {{ template "repo/fragments/fileTree" $fileTree }} 9 - </div> 10 - </section> 11 - {{ end }}
+1 -1
appview/pages/templates/repo/fragments/splitDiff.html
··· 3 3 {{- $lineNrStyle := "min-w-[3.5rem] flex-shrink-0 select-none text-right bg-white dark:bg-gray-800" -}} 4 4 {{- $linkStyle := "text-gray-400 dark:text-gray-500 hover:underline" -}} 5 5 {{- $lineNrSepStyle := "pr-2 border-r border-gray-200 dark:border-gray-700" -}} 6 - {{- $containerStyle := "inline-flex w-full items-center target:border target:rounded-sm target:border-yellow-200 target:dark:border-yellow-700 scroll-mt-20" -}} 6 + {{- $containerStyle := "inline-flex w-full items-center target:bg-yellow-200 target:dark:bg-yellow-700 scroll-mt-48" -}} 7 7 {{- $emptyStyle := "bg-gray-200/30 dark:bg-gray-700/30" -}} 8 8 {{- $addStyle := "bg-green-100 dark:bg-green-800/30 text-green-700 dark:text-green-400" -}} 9 9 {{- $delStyle := "bg-red-100 dark:bg-red-800/30 text-red-700 dark:text-red-400 " -}}
+1 -1
appview/pages/templates/repo/fragments/unifiedDiff.html
··· 7 7 {{- $linkStyle := "text-gray-400 dark:text-gray-500 hover:underline" -}} 8 8 {{- $lineNrSepStyle1 := "" -}} 9 9 {{- $lineNrSepStyle2 := "pr-2 border-r border-gray-200 dark:border-gray-700" -}} 10 - {{- $containerStyle := "inline-flex w-full items-center target:border target:rounded-sm target:border-yellow-200 target:dark:border-yellow-700 scroll-mt-20" -}} 10 + {{- $containerStyle := "inline-flex w-full items-center target:bg-yellow-200 target:dark:bg-yellow-700 scroll-mt-48" -}} 11 11 {{- $addStyle := "bg-green-100 dark:bg-green-800/30 text-green-700 dark:text-green-400 " -}} 12 12 {{- $delStyle := "bg-red-100 dark:bg-red-800/30 text-red-700 dark:text-red-400 " -}} 13 13 {{- $ctxStyle := "bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400" -}}
+35 -22
appview/pages/templates/repo/issues/fragments/commentList.html
··· 1 1 {{ define "repo/issues/fragments/commentList" }} 2 - <div class="flex flex-col gap-8"> 2 + <div class="flex flex-col gap-4"> 3 3 {{ range $item := .CommentList }} 4 4 {{ template "commentListing" (list $ .) }} 5 5 {{ end }} ··· 19 19 <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"> 20 20 {{ template "topLevelComment" $params }} 21 21 22 - <div class="relative ml-4 border-l-2 border-gray-200 dark:border-gray-700"> 22 + <div class="relative ml-10 border-l-2 border-gray-200 dark:border-gray-700"> 23 23 {{ range $index, $reply := $comment.Replies }} 24 - <div class="relative "> 25 - <!-- Horizontal connector --> 26 - <div class="absolute left-0 top-6 w-4 h-1 bg-gray-200 dark:bg-gray-700"></div> 27 - 28 - <div class="pl-2"> 29 - {{ 30 - template "replyComment" 31 - (dict 32 - "RepoInfo" $root.RepoInfo 33 - "LoggedInUser" $root.LoggedInUser 34 - "Issue" $root.Issue 35 - "Comment" $reply) 36 - }} 37 - </div> 24 + <div class="-ml-4"> 25 + {{ 26 + template "replyComment" 27 + (dict 28 + "RepoInfo" $root.RepoInfo 29 + "LoggedInUser" $root.LoggedInUser 30 + "Issue" $root.Issue 31 + "Comment" $reply) 32 + }} 38 33 </div> 39 34 {{ end }} 40 35 </div> ··· 44 39 {{ end }} 45 40 46 41 {{ define "topLevelComment" }} 47 - <div class="rounded px-6 py-4 bg-white dark:bg-gray-800"> 48 - {{ template "repo/issues/fragments/issueCommentHeader" . }} 49 - {{ template "repo/issues/fragments/issueCommentBody" . }} 42 + <div class="rounded px-6 py-4 bg-white dark:bg-gray-800 flex gap-2 "> 43 + <div class="flex-shrink-0"> 44 + <img 45 + src="{{ tinyAvatar .Comment.Did }}" 46 + alt="" 47 + class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900" 48 + /> 49 + </div> 50 + <div class="flex-1 min-w-0"> 51 + {{ template "repo/issues/fragments/issueCommentHeader" . }} 52 + {{ template "repo/issues/fragments/issueCommentBody" . }} 53 + </div> 50 54 </div> 51 55 {{ end }} 52 56 53 57 {{ define "replyComment" }} 54 - <div class="p-4 w-full mx-auto overflow-hidden"> 55 - {{ template "repo/issues/fragments/issueCommentHeader" . }} 56 - {{ template "repo/issues/fragments/issueCommentBody" . }} 58 + <div class="py-4 pr-4 w-full mx-auto overflow-hidden flex gap-2 "> 59 + <div class="flex-shrink-0"> 60 + <img 61 + src="{{ tinyAvatar .Comment.Did }}" 62 + alt="" 63 + class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900" 64 + /> 65 + </div> 66 + <div class="flex-1 min-w-0"> 67 + {{ template "repo/issues/fragments/issueCommentHeader" . }} 68 + {{ template "repo/issues/fragments/issueCommentBody" . }} 69 + </div> 57 70 </div> 58 71 {{ end }}
-63
appview/pages/templates/repo/issues/fragments/globalIssueListing.html
··· 1 - {{ define "repo/issues/fragments/globalIssueListing" }} 2 - <div class="flex flex-col gap-2"> 3 - {{ range .Issues }} 4 - <div class="rounded drop-shadow-sm bg-white px-6 py-4 dark:bg-gray-800 dark:border-gray-700"> 5 - <div class="pb-2 mb-3"> 6 - <div class="flex items-center gap-3 mb-2"> 7 - <a 8 - href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}" 9 - class="text-blue-600 dark:text-blue-400 font-medium hover:underline text-sm" 10 - > 11 - {{ resolve .Repo.Did }}/{{ .Repo.Name }} 12 - </a> 13 - </div> 14 - <a 15 - href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}/issues/{{ .IssueId }}" 16 - class="no-underline hover:underline" 17 - > 18 - {{ .Title | description }} 19 - <span class="text-gray-500">#{{ .IssueId }}</span> 20 - </a> 21 - </div> 22 - <div class="text-sm text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-1"> 23 - {{ $bgColor := "bg-gray-800 dark:bg-gray-700" }} 24 - {{ $icon := "ban" }} 25 - {{ $state := "closed" }} 26 - {{ if .Open }} 27 - {{ $bgColor = "bg-green-600 dark:bg-green-700" }} 28 - {{ $icon = "circle-dot" }} 29 - {{ $state = "open" }} 30 - {{ end }} 31 - 32 - <span class="inline-flex items-center rounded px-2 py-[5px] {{ $bgColor }} text-sm"> 33 - {{ i $icon "w-3 h-3 mr-1.5 text-white dark:text-white" }} 34 - <span class="text-white dark:text-white">{{ $state }}</span> 35 - </span> 36 - 37 - <span class="ml-1"> 38 - {{ template "user/fragments/picHandleLink" .Did }} 39 - </span> 40 - 41 - <span class="before:content-['ยท']"> 42 - {{ template "repo/fragments/time" .Created }} 43 - </span> 44 - 45 - <span class="before:content-['ยท']"> 46 - {{ $s := "s" }} 47 - {{ if eq (len .Comments) 1 }} 48 - {{ $s = "" }} 49 - {{ end }} 50 - <a href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}/issues/{{ .IssueId }}" class="text-gray-500 dark:text-gray-400">{{ len .Comments }} comment{{$s}}</a> 51 - </span> 52 - 53 - {{ $state := .Labels }} 54 - {{ range $k, $d := $.LabelDefs }} 55 - {{ range $v, $s := $state.GetValSet $d.AtUri.String }} 56 - {{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }} 57 - {{ end }} 58 - {{ end }} 59 - </div> 60 - </div> 61 - {{ end }} 62 - </div> 63 - {{ end }}
+2 -1
appview/pages/templates/repo/issues/fragments/issueCommentHeader.html
··· 1 1 {{ define "repo/issues/fragments/issueCommentHeader" }} 2 2 <div class="flex flex-wrap items-center gap-2 text-sm text-gray-500 dark:text-gray-400 "> 3 - {{ template "user/fragments/picHandleLink" .Comment.Did }} 3 + {{ resolve .Comment.Did }} 4 4 {{ template "hats" $ }} 5 + <span class="before:content-['ยท']"></span> 5 6 {{ template "timestamp" . }} 6 7 {{ $isCommentOwner := and .LoggedInUser (eq .LoggedInUser.Did .Comment.Did) }} 7 8 {{ if and $isCommentOwner (not .Comment.Deleted) }}
+2 -2
appview/pages/templates/repo/issues/fragments/issueListing.html
··· 21 21 {{ $state = "open" }} 22 22 {{ end }} 23 23 24 - <span class="inline-flex items-center rounded px-2 py-[5px] {{ $bgColor }} text-sm"> 24 + <span class="inline-flex items-center rounded px-2 py-[5px] {{ $bgColor }}"> 25 25 {{ i $icon "w-3 h-3 mr-1.5 text-white dark:text-white" }} 26 - <span class="text-white dark:text-white">{{ $state }}</span> 26 + <span class="text-white dark:text-white text-sm">{{ $state }}</span> 27 27 </span> 28 28 29 29 <span class="ml-1">
+1 -1
appview/pages/templates/repo/issues/fragments/putIssue.html
··· 18 18 <textarea 19 19 name="body" 20 20 id="body" 21 - rows="6" 21 + rows="15" 22 22 class="w-full resize-y" 23 23 placeholder="Describe your issue. Markdown is supported." 24 24 >{{ if .Issue }}{{ .Issue.Body }}{{ end }}</textarea>
+3 -3
appview/pages/templates/repo/issues/fragments/replyIssueCommentPlaceholder.html
··· 1 1 {{ define "repo/issues/fragments/replyIssueCommentPlaceholder" }} 2 - <div class="p-2 border-t flex gap-2 items-center border-gray-300 dark:border-gray-700"> 2 + <div class="py-2 px-6 border-t flex gap-2 items-center border-gray-300 dark:border-gray-700"> 3 3 {{ if .LoggedInUser }} 4 4 <img 5 5 src="{{ tinyAvatar .LoggedInUser.Did }}" 6 6 alt="" 7 - class="rounded-full h-6 w-6 mr-1 border border-gray-300 dark:border-gray-700" 7 + class="rounded-full size-8 mr-1 border-2 border-gray-300 dark:border-gray-700" 8 8 /> 9 9 {{ end }} 10 10 <input 11 - class="w-full py-2 border-none focus:outline-none" 11 + class="w-full p-0 border-none focus:outline-none" 12 12 placeholder="Leave a reply..." 13 13 hx-get="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/comment/{{ .Comment.Id }}/reply" 14 14 hx-trigger="focus"
+5 -5
appview/pages/templates/repo/issues/issue.html
··· 58 58 {{ $icon = "circle-dot" }} 59 59 {{ end }} 60 60 <div class="inline-flex items-center gap-2"> 61 - <div id="state" 62 - class="inline-flex items-center rounded px-3 py-1 {{ $bgColor }}"> 63 - {{ i $icon "w-4 h-4 mr-1.5 text-white" }} 64 - <span class="text-white">{{ .Issue.State }}</span> 65 - </div> 61 + <span class="inline-flex items-center rounded px-2 py-[5px] {{ $bgColor }}"> 62 + {{ i $icon "w-3 h-3 mr-1.5 text-white dark:text-white" }} 63 + <span class="text-white dark:text-white text-sm">{{ .Issue.State }}</span> 64 + </span> 65 + 66 66 <span class="text-gray-500 dark:text-gray-400 text-sm flex flex-wrap items-center gap-1"> 67 67 opened by 68 68 {{ template "user/fragments/picHandleLink" .Issue.Did }}
+60 -69
appview/pages/templates/repo/pipelines/fragments/pipelineSymbol.html
··· 1 1 {{ define "repo/pipelines/fragments/pipelineSymbol" }} 2 - <div class="cursor-pointer"> 3 - {{ $c := .Counts }} 4 - {{ $statuses := .Statuses }} 5 - {{ $total := len $statuses }} 6 - {{ $success := index $c "success" }} 7 - {{ $fail := index $c "failed" }} 8 - {{ $timeout := index $c "timeout" }} 9 - {{ $empty := eq $total 0 }} 10 - {{ $allPass := eq $success $total }} 11 - {{ $allFail := eq $fail $total }} 12 - {{ $allTimeout := eq $timeout $total }} 13 - 14 - {{ if $empty }} 15 - <div class="flex gap-1 items-center"> 16 - {{ i "hourglass" "size-4 text-gray-600 dark:text-gray-400 " }} 17 - <span>0/{{ $total }}</span> 18 - </div> 19 - {{ else if $allPass }} 20 - <div class="flex gap-1 items-center"> 21 - {{ i "check" "size-4 text-green-600" }} 22 - <span>{{ $total }}/{{ $total }}</span> 23 - </div> 24 - {{ else if $allFail }} 25 - <div class="flex gap-1 items-center"> 26 - {{ i "x" "size-4 text-red-500" }} 27 - <span>0/{{ $total }}</span> 28 - </div> 29 - {{ else if $allTimeout }} 30 - <div class="flex gap-1 items-center"> 31 - {{ i "clock-alert" "size-4 text-orange-500" }} 32 - <span>0/{{ $total }}</span> 33 - </div> 2 + <div class="cursor-pointer flex gap-2 items-center"> 3 + {{ template "symbol" .Pipeline }} 4 + {{ if .ShortSummary }} 5 + {{ .Pipeline.ShortStatusSummary }} 34 6 {{ else }} 35 - {{ $radius := f64 8 }} 36 - {{ $circumference := mulf64 2.0 (mulf64 3.1416 $radius) }} 37 - {{ $offset := 0.0 }} 38 - <div class="flex gap-1 items-center"> 39 - <svg class="w-4 h-4 transform -rotate-90" viewBox="0 0 20 20"> 40 - <circle cx="10" cy="10" r="{{ $radius }}" fill="none" stroke="#f3f4f633" stroke-width="2"/> 7 + {{ .Pipeline.LongStatusSummary }} 8 + {{ end }} 9 + </div> 10 + {{ end }} 41 11 42 - {{ range $kind, $count := $c }} 43 - {{ $color := "" }} 44 - {{ if or (eq $kind "pending") (eq $kind "running") }} 45 - {{ $color = "#eab308" }} {{/* amber-500 */}} 46 - {{ else if eq $kind "success" }} 47 - {{ $color = "#10b981" }} {{/* green-500 */}} 48 - {{ else if eq $kind "cancelled" }} 49 - {{ $color = "#6b7280" }} {{/* gray-500 */}} 50 - {{ else if eq $kind "timeout" }} 51 - {{ $color = "#fb923c" }} {{/* orange-400 */}} 52 - {{ else }} 53 - {{ $color = "#ef4444" }} {{/* red-500 for failed or unknown */}} 54 - {{ end }} 12 + {{ define "symbol" }} 13 + {{ $c := .Counts }} 14 + {{ $statuses := .Statuses }} 15 + {{ $total := len $statuses }} 16 + {{ $success := index $c "success" }} 17 + {{ $fail := index $c "failed" }} 18 + {{ $timeout := index $c "timeout" }} 19 + {{ $empty := eq $total 0 }} 20 + {{ $allPass := eq $success $total }} 21 + {{ $allFail := eq $fail $total }} 22 + {{ $allTimeout := eq $timeout $total }} 55 23 56 - {{ $percent := divf64 (f64 $count) (f64 $total) }} 57 - {{ $length := mulf64 $percent $circumference }} 58 - 59 - <circle 60 - cx="10" cy="10" r="{{ $radius }}" 61 - fill="none" 62 - stroke="{{ $color }}" 63 - stroke-width="2" 64 - stroke-dasharray="{{ printf "%.2f %.2f" $length (subf64 $circumference $length) }}" 65 - stroke-dashoffset="{{ printf "%.2f" (negf64 $offset) }}" 66 - /> 67 - {{ $offset = addf64 $offset $length }} 68 - {{ end }} 69 - </svg> 70 - <span>{{ $success }}/{{ $total }}</span> 71 - </div> 72 - {{ end }} 73 - </div> 24 + {{ if $empty }} 25 + {{ i "hourglass" "size-4 text-gray-600 dark:text-gray-400 " }} 26 + {{ else if $allPass }} 27 + {{ i "check" "size-4 text-green-600 dark:text-green-500" }} 28 + {{ else if $allFail }} 29 + {{ i "x" "size-4 text-red-600 dark:text-red-500" }} 30 + {{ else if $allTimeout }} 31 + {{ i "clock-alert" "size-4 text-orange-500" }} 32 + {{ else }} 33 + {{ $radius := f64 8 }} 34 + {{ $circumference := mulf64 2.0 (mulf64 3.1416 $radius) }} 35 + {{ $offset := 0.0 }} 36 + <svg class="w-4 h-4 transform -rotate-90" viewBox="0 0 20 20"> 37 + <circle cx="10" cy="10" r="{{ $radius }}" fill="none" class="stroke-gray-200 dark:stroke-gray-700" stroke-width="2"/> 38 + {{ range $kind, $count := $c }} 39 + {{ $colorClass := "" }} 40 + {{ if or (eq $kind "pending") (eq $kind "running") }} 41 + {{ $colorClass = "stroke-yellow-600 dark:stroke-yellow-500" }} 42 + {{ else if eq $kind "success" }} 43 + {{ $colorClass = "stroke-green-600 dark:stroke-green-500" }} 44 + {{ else if eq $kind "cancelled" }} 45 + {{ $colorClass = "stroke-gray-600 dark:stroke-gray-500" }} 46 + {{ else if eq $kind "timeout" }} 47 + {{ $colorClass = "stroke-orange-600 dark:stroke-orange-500" }} 48 + {{ else }} 49 + {{ $colorClass = "stroke-red-600 dark:stroke-red-500" }} 50 + {{ end }} 51 + {{ $percent := divf64 (f64 $count) (f64 $total) }} 52 + {{ $length := mulf64 $percent $circumference }} 53 + <circle 54 + cx="10" cy="10" r="{{ $radius }}" 55 + fill="none" 56 + class="{{ $colorClass }}" 57 + stroke-width="2" 58 + stroke-dasharray="{{ printf "%.2f %.2f" $length (subf64 $circumference $length) }}" 59 + stroke-dashoffset="{{ printf "%.2f" (negf64 $offset) }}" 60 + /> 61 + {{ $offset = addf64 $offset $length }} 62 + {{ end }} 63 + </svg> 64 + {{ end }} 74 65 {{ end }}
+1 -1
appview/pages/templates/repo/pipelines/fragments/pipelineSymbolLong.html
··· 4 4 <div class="relative inline-block"> 5 5 <details class="relative"> 6 6 <summary class="cursor-pointer list-none"> 7 - {{ template "repo/pipelines/fragments/pipelineSymbol" .Pipeline }} 7 + {{ template "repo/pipelines/fragments/pipelineSymbol" (dict "Pipeline" $pipeline "ShortSummary" true) }} 8 8 </summary> 9 9 {{ template "repo/pipelines/fragments/tooltip" $ }} 10 10 </details>
+17 -17
appview/pages/templates/repo/pulls/fragments/pullActions.html
··· 22 22 {{ $isLastRound := eq $roundNumber $lastIdx }} 23 23 {{ $isSameRepoBranch := .Pull.IsBranchBased }} 24 24 {{ $isUpToDate := .ResubmitCheck.No }} 25 - <div id="actions-{{$roundNumber}}" class="flex flex-wrap gap-2 relative"> 25 + <div id="actions-{{$roundNumber}}" class="flex flex-wrap gap-2 relative p-2"> 26 26 <button 27 27 hx-get="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/round/{{ $roundNumber }}/comment" 28 28 hx-target="#actions-{{$roundNumber}}" 29 29 hx-swap="outerHtml" 30 - class="btn p-2 flex items-center gap-2 no-underline hover:no-underline group"> 31 - {{ i "message-square-plus" "w-4 h-4" }} 32 - <span>comment</span> 30 + class="btn-flat p-2 flex items-center gap-2 no-underline hover:no-underline group"> 31 + {{ i "message-square-plus" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 33 32 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 33 + comment 34 34 </button> 35 35 {{ if .BranchDeleteStatus }} 36 36 <button 37 37 hx-delete="/{{ .BranchDeleteStatus.Repo.Did }}/{{ .BranchDeleteStatus.Repo.Name }}/branches" 38 38 hx-vals='{"branch": "{{ .BranchDeleteStatus.Branch }}" }' 39 39 hx-swap="none" 40 - class="btn p-2 flex items-center gap-2 no-underline hover:no-underline group text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"> 40 + class="btn-flat p-2 flex items-center gap-2 no-underline hover:no-underline group text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"> 41 41 {{ i "git-branch" "w-4 h-4" }} 42 42 <span>delete branch</span> 43 43 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} ··· 52 52 hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/merge" 53 53 hx-swap="none" 54 54 hx-confirm="Are you sure you want to merge pull #{{ .Pull.PullId }} into the `{{ .Pull.TargetBranch }}` branch?" 55 - class="btn p-2 flex items-center gap-2 group" {{ $disabled }}> 56 - {{ i "git-merge" "w-4 h-4" }} 57 - <span>merge{{if $stackCount}} {{$stackCount}}{{end}}</span> 55 + class="btn-flat p-2 flex items-center gap-2 group" {{ $disabled }}> 56 + {{ i "git-merge" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 58 57 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 58 + merge{{if $stackCount}} {{$stackCount}}{{end}} 59 59 </button> 60 60 {{ end }} 61 61 ··· 74 74 {{ end }} 75 75 76 76 hx-disabled-elt="#resubmitBtn" 77 - class="btn p-2 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed group" {{ $disabled }} 77 + class="btn-flat p-2 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed group" {{ $disabled }} 78 78 79 79 {{ if $disabled }} 80 80 title="Update this branch to resubmit this pull request" ··· 82 82 title="Resubmit this pull request" 83 83 {{ end }} 84 84 > 85 - {{ i "rotate-ccw" "w-4 h-4" }} 86 - <span>resubmit</span> 85 + {{ i "rotate-ccw" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 87 86 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 87 + resubmit 88 88 </button> 89 89 {{ end }} 90 90 ··· 92 92 <button 93 93 hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/close" 94 94 hx-swap="none" 95 - class="btn p-2 flex items-center gap-2 group"> 96 - {{ i "ban" "w-4 h-4" }} 97 - <span>close</span> 95 + class="btn-flat p-2 flex items-center gap-2 group"> 96 + {{ i "ban" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 98 97 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 98 + close 99 99 </button> 100 100 {{ end }} 101 101 ··· 103 103 <button 104 104 hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/reopen" 105 105 hx-swap="none" 106 - class="btn p-2 flex items-center gap-2 group"> 107 - {{ i "refresh-ccw-dot" "w-4 h-4" }} 108 - <span>reopen</span> 106 + class="btn-flat p-2 flex items-center gap-2 group"> 107 + {{ i "refresh-ccw-dot" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 109 108 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 109 + reopen 110 110 </button> 111 111 {{ end }} 112 112 </div>
+6 -7
appview/pages/templates/repo/pulls/fragments/pullHeader.html
··· 1 1 {{ define "repo/pulls/fragments/pullHeader" }} 2 - <header class="pb-4"> 2 + <header class="pb-2"> 3 3 <h1 class="text-2xl dark:text-white"> 4 4 {{ .Pull.Title | description }} 5 5 <span class="text-gray-500 dark:text-gray-400">#{{ .Pull.PullId }}</span> ··· 17 17 {{ $icon = "git-merge" }} 18 18 {{ end }} 19 19 20 - <section class="mt-2"> 20 + <section> 21 21 <div class="flex items-center gap-2"> 22 - <div 23 - id="state" 24 - class="inline-flex items-center rounded px-3 py-1 {{ $bgColor }}" 22 + <span 23 + class="inline-flex items-center rounded px-2 py-[5px] {{ $bgColor }} text-sm" 25 24 > 26 - {{ i $icon "w-4 h-4 mr-1.5 text-white" }} 25 + {{ i $icon "w-3 h-3 mr-1.5 text-white" }} 27 26 <span class="text-white">{{ .Pull.State.String }}</span> 28 - </div> 27 + </span> 29 28 <span class="text-gray-500 dark:text-gray-400 text-sm flex flex-wrap items-center gap-1"> 30 29 opened by 31 30 {{ template "user/fragments/picHandleLink" .Pull.OwnerDid }}
+39 -24
appview/pages/templates/repo/pulls/fragments/pullNewComment.html
··· 1 1 {{ define "repo/pulls/fragments/pullNewComment" }} 2 2 <div 3 3 id="pull-comment-card-{{ .RoundNumber }}" 4 - class="bg-white dark:bg-gray-800 rounded drop-shadow-sm p-4 relative w-full flex flex-col gap-2"> 5 - <div class="text-sm text-gray-500 dark:text-gray-400"> 6 - {{ resolve .LoggedInUser.Did }} 7 - </div> 4 + class="w-full flex flex-col gap-2"> 5 + {{ template "user/fragments/picHandleLink" .LoggedInUser.Did }} 8 6 <form 9 7 hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/round/{{ .RoundNumber }}/comment" 10 - hx-indicator="#create-comment-spinner" 11 8 hx-swap="none" 12 - class="w-full flex flex-wrap gap-2" 9 + hx-on::after-request="if(event.detail.successful) this.reset()" 10 + hx-disabled-elt="#reply-{{ .RoundNumber }}" 11 + class="w-full flex flex-wrap gap-2 group" 13 12 > 14 13 <textarea 15 14 name="body" 16 15 class="w-full p-2 rounded border border-gray-200" 16 + rows=8 17 17 placeholder="Add to the discussion..."></textarea 18 18 > 19 - <button type="submit" class="btn flex items-center gap-2"> 20 - {{ i "message-square" "w-4 h-4" }} 21 - <span>comment</span> 22 - <span id="create-comment-spinner" class="group"> 23 - {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 24 - </span> 25 - </button> 26 - <button 27 - type="button" 28 - class="btn flex items-center gap-2 group" 29 - hx-get="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/round/{{ .RoundNumber }}/actions" 30 - hx-swap="outerHTML" 31 - hx-target="#pull-comment-card-{{ .RoundNumber }}" 32 - > 33 - {{ i "x" "w-4 h-4" }} 34 - <span>cancel</span> 35 - {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 36 - </button> 19 + {{ template "replyActions" . }} 37 20 <div id="pull-comment"></div> 38 21 </form> 39 22 </div> 40 23 {{ end }} 24 + 25 + {{ define "replyActions" }} 26 + <div class="flex flex-wrap items-stretch justify-end gap-2 text-gray-500 dark:text-gray-400 text-sm w-full"> 27 + {{ template "cancel" . }} 28 + {{ template "reply" . }} 29 + </div> 30 + {{ end }} 31 + 32 + {{ define "cancel" }} 33 + <button 34 + type="button" 35 + class="btn text-red-500 dark:text-red-400 flex gap-2 items-center group" 36 + hx-get="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/round/{{ .RoundNumber }}/actions" 37 + hx-swap="outerHTML" 38 + hx-target="#actions-{{.RoundNumber}}" 39 + > 40 + {{ i "x" "w-4 h-4" }} 41 + <span>cancel</span> 42 + </button> 43 + {{ end }} 44 + 45 + {{ define "reply" }} 46 + <button 47 + type="submit" 48 + id="reply-{{ .RoundNumber }}" 49 + class="btn-create flex items-center gap-2"> 50 + {{ i "reply" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 51 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 52 + reply 53 + </button> 54 + {{ end }} 55 +
+2 -3
appview/pages/templates/repo/pulls/fragments/summarizedPullHeader.html
··· 15 15 16 16 <div class="flex-shrink-0 flex items-center gap-2"> 17 17 {{ $latestRound := .LastRoundNumber }} 18 - {{ $lastSubmission := index .Submissions $latestRound }} 19 - {{ $commentCount := len $lastSubmission.Comments }} 18 + {{ $commentCount := .TotalComments }} 20 19 {{ if and $pipeline $pipeline.Id }} 21 - {{ template "repo/pipelines/fragments/pipelineSymbol" $pipeline }} 20 + {{ template "repo/pipelines/fragments/pipelineSymbol" (dict "Pipeline" $pipeline "ShortSummary" true) }} 22 21 <span class="before:content-['ยท'] before:select-none text-gray-500 dark:text-gray-400"></span> 23 22 {{ end }} 24 23 <span>
+2 -21
appview/pages/templates/repo/pulls/interdiff.html
··· 25 25 {{ template "repo/pulls/fragments/pullHeader" . }} 26 26 </header> 27 27 </section> 28 - 29 28 {{ end }} 30 29 31 30 {{ define "mainLayout" }} ··· 34 33 {{ block "content" . }}{{ end }} 35 34 {{ end }} 36 35 37 - {{ block "contentAfterLayout" . }} 38 - <div class="flex-grow grid grid-cols-1 md:grid-cols-12 gap-4"> 39 - <div class="flex flex-col gap-4 col-span-1 md:col-span-2"> 40 - {{ block "contentAfterLeft" . }} {{ end }} 41 - </div> 42 - <main class="col-span-1 md:col-span-10"> 43 - {{ block "contentAfter" . }}{{ end }} 44 - </main> 45 - </div> 46 - {{ end }} 36 + {{ block "contentAfter" . }}{{ end }} 47 37 </div> 48 38 {{ end }} 49 39 50 40 {{ define "contentAfter" }} 51 - {{ template "repo/fragments/interdiff" (list .RepoInfo.FullName .Interdiff .DiffOpts) }} 52 - {{end}} 53 - 54 - {{ define "contentAfterLeft" }} 55 - <div class="flex flex-col gap-4 col-span-1 md:col-span-2"> 56 - {{ template "repo/fragments/diffOpts" .DiffOpts }} 57 - </div> 58 - <div class="sticky top-0 flex-grow max-h-screen overflow-y-auto"> 59 - {{ template "repo/fragments/interdiffFiles" .Interdiff }} 60 - </div> 41 + {{ template "repo/fragments/diff" (list .Interdiff .DiffOpts) }} 61 42 {{end}}
+419 -232
appview/pages/templates/repo/pulls/pull.html
··· 6 6 {{ template "repo/pulls/fragments/og" (dict "RepoInfo" .RepoInfo "Pull" .Pull) }} 7 7 {{ end }} 8 8 9 - {{ define "repoContentLayout" }} 10 - <div class="grid grid-cols-1 md:grid-cols-10 gap-4 w-full"> 11 - <div class="col-span-1 md:col-span-8"> 12 - <section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto dark:text-white"> 13 - {{ block "repoContent" . }}{{ end }} 14 - </section> 15 - {{ block "repoAfter" . }}{{ end }} 9 + {{ define "mainLayout" }} 10 + <div class="px-1 flex-grow flex flex-col gap-4"> 11 + <div class="max-w-screen-lg mx-auto"> 12 + {{ block "contentLayout" . }} 13 + {{ block "content" . }}{{ end }} 14 + {{ end }} 16 15 </div> 17 - <div class="col-span-1 md:col-span-2 flex flex-col gap-6"> 16 + {{ block "contentAfterLayout" . }} 17 + <main> 18 + {{ block "contentAfter" . }}{{ end }} 19 + </main> 20 + {{ end }} 21 + </div> 22 + <script> 23 + (function() { 24 + const details = document.getElementById('bottomSheet'); 25 + const isDesktop = () => window.matchMedia('(min-width: 768px)').matches; 26 + 27 + // close on mobile initially 28 + if (!isDesktop()) { 29 + details.open = false; 30 + } 31 + 32 + // prevent closing on desktop 33 + details.addEventListener('toggle', function(e) { 34 + if (isDesktop() && !this.open) { 35 + this.open = true; 36 + } 37 + }); 38 + 39 + const mediaQuery = window.matchMedia('(min-width: 768px)'); 40 + mediaQuery.addEventListener('change', function(e) { 41 + if (e.matches) { 42 + // switched to desktop - keep open 43 + details.open = true; 44 + } else { 45 + // switched to mobile - close 46 + details.open = false; 47 + } 48 + }); 49 + })(); 50 + </script> 51 + {{ end }} 52 + 53 + {{ define "repoContentLayout" }} 54 + <div class="grid grid-cols-1 md:grid-cols-10 gap-4"> 55 + <section class="bg-white col-span-1 md:col-span-8 dark:bg-gray-800 p-6 rounded relative w-full mx-auto dark:text-white h-full flex-shrink"> 56 + {{ block "repoContent" . }}{{ end }} 57 + </section> 58 + <div class="flex flex-col gap-6 col-span-1 md:col-span-2"> 18 59 {{ template "repo/fragments/labelPanel" 19 60 (dict "RepoInfo" $.RepoInfo 20 61 "Defs" $.LabelDefs ··· 29 70 </div> 30 71 {{ end }} 31 72 73 + {{ define "contentAfter" }} 74 + {{ template "repo/fragments/diff" (list .Diff .DiffOpts $) }} 75 + {{ end }} 76 + 32 77 {{ define "repoContent" }} 33 78 {{ template "repo/pulls/fragments/pullHeader" . }} 34 - 35 79 {{ if .Pull.IsStacked }} 36 80 <div class="mt-8"> 37 81 {{ template "repo/pulls/fragments/pullStack" . }} ··· 39 83 {{ end }} 40 84 {{ end }} 41 85 42 - {{ define "repoAfter" }} 43 - <section id="submissions" class="mt-4"> 44 - <div class="flex flex-col gap-4"> 45 - {{ block "submissions" . }} {{ end }} 86 + {{ define "diffLayout" }} 87 + {{ $diff := index . 0 }} 88 + {{ $opts := index . 1 }} 89 + {{ $root := index . 2 }} 90 + 91 + <div class="flex col-span-full"> 92 + <!-- left panel --> 93 + <div id="files" class="w-0 hidden md:block overflow-hidden sticky top-12 max-h-screen overflow-y-auto pb-12"> 94 + <section class="overflow-x-auto text-sm px-6 py-2 border border-gray-200 dark:border-gray-700 w-full mx-auto min-h-full rounded bg-white dark:bg-gray-800 drop-shadow-sm"> 95 + {{ template "repo/fragments/fileTree" $diff.FileTree }} 96 + </section> 97 + </div> 98 + 99 + <!-- main content --> 100 + <div class="flex-1 min-w-0 sticky top-12 pb-12"> 101 + {{ template "diffFiles" (list $diff $opts) }} 102 + </div> 103 + 104 + <!-- right panel --> 105 + {{ template "subsPanel" $ }} 106 + </div> 107 + {{ end }} 108 + 109 + {{ define "subsPanel" }} 110 + {{ $root := index . 2 }} 111 + {{ $pull := $root.Pull }} 112 + {{ $commentCount := $pull.TotalComments }} 113 + 114 + <!-- backdrop overlay - only visible on mobile when open --> 115 + <div class=" 116 + fixed inset-0 bg-black/50 z-50 md:hidden opacity-0 117 + pointer-events-none transition-opacity duration-300 118 + has-[~#subs_details[open]]:opacity-100 has-[~#subs_details[open]]:pointer-events-auto"> 119 + </div> 120 + <!-- right panel - bottom sheet on mobile, side panel on desktop --> 121 + <div id="subs" class="fixed bottom-0 left-0 right-0 z-50 w-full md:static md:z-auto md:max-h-screen md:sticky md:top-12 overflow-hidden"> 122 + <details open id="bottomSheet" class="group rounded-t-2xl md:rounded-t-sm drop-shadow-lg md:drop-shadow-none"> 123 + <summary class=" 124 + flex gap-4 items-center justify-between 125 + rounded-t-2xl md:rounded-t-sm cursor-pointer list-none p-4 md:h-12 126 + text-white md:text-black md:dark:text-white 127 + bg-green-600 dark:bg-green-600 128 + md:bg-white md:dark:bg-gray-800 129 + drop-shadow-sm 130 + md:border-b md:border-x border-gray-200 dark:border-gray-700"> 131 + <h2 class="">Review Panel </h2> 132 + <div class="flex items-center gap-2"> 133 + <span class="inline-flex items-center gap-2"> 134 + {{ $commentCount }} comment{{if ne $commentCount 1}}s{{end}} 135 + </span> 136 + <span class="before:content-['ยท'] before:select-none"></span> 137 + <span>round <span class="font-mono">#{{ $pull.LastRoundNumber }}</span> 138 + </span> 139 + <span class="md:hidden inline"> 140 + <span class="inline group-open:hidden">{{ i "chevron-up" "size-4" }}</span> 141 + <span class="hidden group-open:inline">{{ i "chevron-down" "size-4" }}</span> 142 + </span> 143 + </div> 144 + </summary> 145 + <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"> 146 + {{ template "submissions" $root }} 46 147 </div> 47 - </section> 148 + </details> 149 + </div> 150 + {{ end }} 48 151 49 - <div id="pull-close"></div> 50 - <div id="pull-reopen"></div> 152 + {{ define "subsCheckbox" }} 153 + <input type="checkbox" id="subsToggle" class="peer/subs hidden" checked/> 51 154 {{ end }} 52 155 156 + {{ define "subsToggle" }} 157 + <style> 158 + /* Mobile: full width */ 159 + #subsToggle:checked ~ div div#subs { 160 + width: 100%; 161 + margin-left: 0; 162 + } 163 + #subsToggle:checked ~ div label[for="subsToggle"] .show-toggle { display: none; } 164 + #subsToggle:checked ~ div label[for="subsToggle"] .hide-toggle { display: inline; } 165 + #subsToggle:not(:checked) ~ div label[for="subsToggle"] .hide-toggle { display: none; } 166 + 167 + /* Desktop: 25vw with left margin */ 168 + @media (min-width: 768px) { 169 + #subsToggle:checked ~ div div#subs { 170 + width: 25vw; 171 + margin-left: 1rem; 172 + } 173 + /* Unchecked state */ 174 + #subsToggle:not(:checked) ~ div div#subs { 175 + width: 0; 176 + display: none; 177 + margin-left: 0; 178 + } 179 + } 180 + </style> 181 + <label title="Toggle review panel" for="subsToggle" class="hidden md:inline-flex items-center justify-center rounded cursor-pointer p-2"> 182 + <span class="show-toggle">{{ i "message-square-more" "size-4" }}</span> 183 + <span class="hide-toggle">{{ i "message-square" "size-4" }}</span> 184 + </label> 185 + {{ end }} 186 + 187 + 53 188 {{ define "submissions" }} 54 189 {{ $lastIdx := sub (len .Pull.Submissions) 1 }} 55 - {{ $targetBranch := .Pull.TargetBranch }} 56 - {{ $repoName := .RepoInfo.FullName }} 57 - {{ range $idx, $item := .Pull.Submissions }} 58 - {{ with $item }} 59 - <details {{ if eq $idx $lastIdx }}open{{ end }}> 60 - <summary id="round-#{{ .RoundNumber }}" class="list-none cursor-pointer"> 61 - <div class="flex flex-wrap gap-2 items-stretch"> 62 - <!-- round number --> 63 - <div class="rounded bg-white dark:bg-gray-800 drop-shadow-sm px-3 py-2 dark:text-white"> 64 - <span class="flex items-center">{{ i "hash" "w-4 h-4" }}{{ .RoundNumber }}</span> 65 - </div> 66 - <!-- round summary --> 67 - <div class="flex-1 rounded drop-shadow-sm bg-white dark:bg-gray-800 p-2 text-gray-500 dark:text-gray-400"> 68 - <span class="gap-1 flex items-center"> 69 - {{ $owner := resolve $.Pull.OwnerDid }} 70 - {{ $re := "re" }} 71 - {{ if eq .RoundNumber 0 }} 72 - {{ $re = "" }} 73 - {{ end }} 74 - <span class="hidden md:inline">{{$re}}submitted</span> 75 - by {{ template "user/fragments/picHandleLink" $.Pull.OwnerDid }} 76 - <span class="select-none before:content-['\00B7']"></span> 77 - <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500" href="#round-#{{ .RoundNumber }}">{{ template "repo/fragments/shortTime" .Created }}</a> 78 - <span class="select-none before:content-['ยท']"></span> 79 - {{ $s := "s" }} 80 - {{ if eq (len .Comments) 1 }} 81 - {{ $s = "" }} 82 - {{ end }} 83 - {{ len .Comments }} comment{{$s}} 84 - </span> 85 - </div> 190 + {{ range $ridx, $item := reverse .Pull.Submissions }} 191 + {{ $idx := sub $lastIdx $ridx }} 192 + {{ template "submission" (list $item $idx $lastIdx $) }} 193 + {{ end }} 194 + {{ end }} 86 195 87 - <a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group" 88 - hx-boost="true" 89 - href="/{{ $.RepoInfo.FullName }}/pulls/{{ $.Pull.PullId }}/round/{{.RoundNumber}}"> 90 - {{ i "file-diff" "w-4 h-4" }} 91 - <span class="hidden md:inline">diff</span> 92 - {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 93 - </a> 94 - {{ if ne $idx 0 }} 95 - <a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group" 96 - hx-boost="true" 97 - href="/{{ $.RepoInfo.FullName }}/pulls/{{ $.Pull.PullId }}/round/{{.RoundNumber}}/interdiff"> 98 - {{ i "chevrons-left-right-ellipsis" "w-4 h-4 rotate-90" }} 99 - <span class="hidden md:inline">interdiff</span> 100 - {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 101 - </a> 102 - {{ end }} 103 - <span id="interdiff-error-{{.RoundNumber}}"></span> 104 - </div> 105 - </summary> 196 + {{ define "submission" }} 197 + {{ $item := index . 0 }} 198 + {{ $idx := index . 1 }} 199 + {{ $lastIdx := index . 2 }} 200 + {{ $root := index . 3 }} 201 + <div class="rounded border border-gray-200 dark:border-gray-700 w-full shadow-sm bg-gray-50 dark:bg-gray-800/50"> 202 + {{ template "submissionHeader" $ }} 203 + {{ template "submissionComments" $ }} 106 204 107 - {{ if .IsFormatPatch }} 108 - {{ $patches := .AsFormatPatch }} 109 - {{ $round := .RoundNumber }} 110 - <details class="group py-2 md:ml-[3.5rem] text-gray-500 dark:text-gray-400 flex flex-col gap-2 relative text-sm"> 111 - <summary class="py-1 list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400"> 112 - {{ $s := "s" }} 113 - {{ if eq (len $patches) 1 }} 114 - {{ $s = "" }} 115 - {{ end }} 116 - <div class="group-open:hidden flex items-center gap-2 ml-2"> 117 - {{ i "chevrons-up-down" "w-4 h-4" }} expand {{ len $patches }} commit{{$s}} 118 - </div> 119 - <div class="hidden group-open:flex items-center gap-2 ml-2"> 120 - {{ i "chevrons-down-up" "w-4 h-4" }} hide {{ len $patches }} commit{{$s}} 121 - </div> 122 - </summary> 123 - {{ range $patches }} 124 - <div id="commit-{{.SHA}}" class="py-1 px-2 relative w-full md:max-w-3/5 md:w-fit flex flex-col"> 125 - <div class="flex items-center gap-2"> 126 - {{ i "git-commit-horizontal" "w-4 h-4" }} 127 - <div class="text-sm text-gray-500 dark:text-gray-400"> 128 - <!-- attempt to resolve $fullRepo: this is possible only on non-deleted forks and branches --> 129 - {{ $fullRepo := "" }} 130 - {{ if and $.Pull.IsForkBased $.Pull.PullSource.Repo }} 131 - {{ $fullRepo = printf "%s/%s" $owner $.Pull.PullSource.Repo.Name }} 132 - {{ else if $.Pull.IsBranchBased }} 133 - {{ $fullRepo = $.RepoInfo.FullName }} 134 - {{ end }} 205 + {{ if eq $lastIdx $item.RoundNumber }} 206 + {{ block "mergeStatus" $root }} {{ end }} 207 + {{ block "resubmitStatus" $root }} {{ end }} 208 + {{ end }} 135 209 136 - <!-- if $fullRepo was resolved, link to it, otherwise just span without a link --> 137 - {{ if $fullRepo }} 138 - <a href="/{{ $fullRepo }}/commit/{{ .SHA }}" class="font-mono text-gray-500 dark:text-gray-400">{{ slice .SHA 0 8 }}</a> 139 - {{ else }} 140 - <span class="font-mono">{{ slice .SHA 0 8 }}</span> 141 - {{ end }} 142 - </div> 143 - <div class="flex items-center"> 144 - <span>{{ .Title | description }}</span> 145 - {{ if gt (len .Body) 0 }} 146 - <button 147 - class="py-1/2 px-1 mx-2 bg-gray-200 hover:bg-gray-400 rounded dark:bg-gray-700 dark:hover:bg-gray-600" 148 - hx-on:click="document.getElementById('body-{{$round}}-{{.SHA}}').classList.toggle('hidden')" 149 - > 150 - {{ i "ellipsis" "w-3 h-3" }} 151 - </button> 152 - {{ end }} 153 - </div> 154 - </div> 155 - {{ if gt (len .Body) 0 }} 156 - <p id="body-{{$round}}-{{.SHA}}" class="hidden mt-1 text-sm pb-2"> 157 - {{ nl2br .Body }} 158 - </p> 159 - {{ end }} 160 - </div> 161 - {{ end }} 162 - </details> 163 - {{ end }} 210 + {{ if $root.LoggedInUser }} 211 + {{ template "repo/pulls/fragments/pullActions" 212 + (dict 213 + "LoggedInUser" $root.LoggedInUser 214 + "Pull" $root.Pull 215 + "RepoInfo" $root.RepoInfo 216 + "RoundNumber" $item.RoundNumber 217 + "MergeCheck" $root.MergeCheck 218 + "ResubmitCheck" $root.ResubmitCheck 219 + "BranchDeleteStatus" $root.BranchDeleteStatus 220 + "Stack" $root.Stack) }} 221 + {{ else }} 222 + {{ template "loginPrompt" $ }} 223 + {{ end }} 224 + </div> 225 + {{ end }} 164 226 227 + {{ define "submissionHeader" }} 228 + {{ $item := index . 0 }} 229 + {{ $lastIdx := index . 2 }} 230 + {{ $root := index . 3 }} 231 + {{ $round := $item.RoundNumber }} 232 + <div class="rounded px-6 pr-4 py-4 bg-white dark:bg-gray-800 flex gap-2 sticky top-0 z-20 border-b border-gray-200 dark:border-gray-700"> 233 + <!-- left column: just profile picture --> 234 + <div class="flex-shrink-0"> 235 + <img 236 + src="{{ tinyAvatar $root.Pull.OwnerDid }}" 237 + alt="" 238 + class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900" 239 + /> 240 + </div> 241 + <!-- right column --> 242 + <div class="flex-1 min-w-0 flex flex-col gap-1"> 243 + {{ template "submissionInfo" $ }} 244 + {{ template "submissionCommits" $ }} 245 + {{ template "submissionPipeline" $ }} 246 + {{ if eq $lastIdx $round }} 247 + {{ block "mergeCheck" $root }} {{ end }} 248 + {{ end }} 249 + </div> 250 + </div> 251 + {{ end }} 165 252 166 - <div class="md:pl-[3.5rem] flex flex-col gap-2 mt-2 relative"> 167 - {{ range $cidx, $c := .Comments }} 168 - <div id="comment-{{$c.ID}}" class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-2 px-4 relative w-full"> 169 - {{ if gt $cidx 0 }} 170 - <div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div> 171 - {{ end }} 172 - <div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1"> 173 - {{ template "user/fragments/picHandleLink" $c.OwnerDid }} 174 - <span class="before:content-['ยท']"></span> 175 - <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.ID}}">{{ template "repo/fragments/time" $c.Created }}</a> 176 - </div> 177 - <div class="prose dark:prose-invert"> 178 - {{ $c.Body | markdown }} 179 - </div> 180 - </div> 181 - {{ end }} 253 + {{ define "submissionInfo" }} 254 + {{ $item := index . 0 }} 255 + {{ $idx := index . 1 }} 256 + {{ $root := index . 3 }} 257 + {{ $round := $item.RoundNumber }} 258 + <div class="flex gap-2 items-center justify-between mb-1"> 259 + <span class="inline-flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400"> 260 + {{ resolve $root.Pull.OwnerDid }} submitted v{{ $round }} 261 + <span class="select-none before:content-['\00B7']"></span> 262 + <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500" href="#round-#{{ $round }}"> 263 + {{ template "repo/fragments/shortTimeAgo" $item.Created }} 264 + </a> 265 + </span> 266 + {{ if ne $idx 0 }} 267 + <a class="btn-flat flex items-center gap-2 no-underline hover:no-underline text-sm" 268 + href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ $round }}/interdiff"> 269 + {{ i "chevrons-left-right-ellipsis" "w-4 h-4 rotate-90" }} 270 + interdiff 271 + </a> 272 + {{ end }} 273 + </div> 274 + {{ end }} 182 275 183 - {{ block "pipelineStatus" (list $ .) }} {{ end }} 276 + {{ define "submissionCommits" }} 277 + {{ $item := index . 0 }} 278 + {{ $root := index . 3 }} 279 + {{ $round := $item.RoundNumber }} 280 + {{ $patches := $item.AsFormatPatch }} 281 + {{ if $patches }} 282 + <details class="group/commit"> 283 + <summary class="list-none cursor-pointer flex items-center gap-2"> 284 + <span>{{ i "git-commit-horizontal" "w-4 h-4" }}</span> 285 + {{ len $patches }} commit{{ if ne (len $patches) 1 }}s{{ end }} 286 + <div class="text-sm text-gray-500 dark:text-gray-400"> 287 + <span class="group-open/commit:hidden inline">expand</span> 288 + <span class="hidden group-open/commit:inline">collapse</span> 289 + </div> 290 + </summary> 291 + {{ range $patches }} 292 + {{ template "submissionCommit" (list . $item $root) }} 293 + {{ end }} 294 + </details> 295 + {{ end }} 296 + {{ end }} 184 297 185 - {{ if eq $lastIdx .RoundNumber }} 186 - {{ block "mergeStatus" $ }} {{ end }} 187 - {{ block "resubmitStatus" $ }} {{ end }} 298 + {{ define "submissionCommit" }} 299 + {{ $patch := index . 0 }} 300 + {{ $item := index . 1 }} 301 + {{ $root := index . 2 }} 302 + {{ $round := $item.RoundNumber }} 303 + {{ with $patch }} 304 + <div id="commit-{{.SHA}}" class="py-1 relative w-full md:max-w-3/5 md:w-fit flex flex-col text-gray-600 dark:text-gray-300"> 305 + <div class="flex items-baseline gap-2"> 306 + <div class="text-xs"> 307 + <!-- attempt to resolve $fullRepo: this is possible only on non-deleted forks and branches --> 308 + {{ $fullRepo := "" }} 309 + {{ if and $root.Pull.IsForkBased $root.Pull.PullSource.Repo }} 310 + {{ $fullRepo = printf "%s/%s" $root.Pull.OwnerDid $root.Pull.PullSource.Repo.Name }} 311 + {{ else if $root.Pull.IsBranchBased }} 312 + {{ $fullRepo = $root.RepoInfo.FullName }} 188 313 {{ end }} 189 314 190 - {{ if $.LoggedInUser }} 191 - {{ template "repo/pulls/fragments/pullActions" 192 - (dict 193 - "LoggedInUser" $.LoggedInUser 194 - "Pull" $.Pull 195 - "RepoInfo" $.RepoInfo 196 - "RoundNumber" .RoundNumber 197 - "MergeCheck" $.MergeCheck 198 - "ResubmitCheck" $.ResubmitCheck 199 - "BranchDeleteStatus" $.BranchDeleteStatus 200 - "Stack" $.Stack) }} 315 + <!-- if $fullRepo was resolved, link to it, otherwise just span without a link --> 316 + {{ if $fullRepo }} 317 + <a href="/{{ $fullRepo }}/commit/{{ .SHA }}" class="font-mono text-gray-600 dark:text-gray-300">{{ slice .SHA 0 8 }}</a> 201 318 {{ else }} 202 - <div class="bg-amber-50 dark:bg-amber-900 border border-amber-500 rounded drop-shadow-sm p-2 relative flex gap-2 items-center w-fit"> 203 - <a href="/signup" class="btn-create py-0 hover:no-underline hover:text-white flex items-center gap-2"> 204 - sign up 205 - </a> 206 - <span class="text-gray-500 dark:text-gray-400">or</span> 207 - <a href="/login" class="underline">login</a> 208 - to add to the discussion 209 - </div> 319 + <span class="font-mono">{{ slice .SHA 0 8 }}</span> 320 + {{ end }} 321 + </div> 322 + 323 + <div> 324 + <span>{{ .Title | description }}</span> 325 + {{ if gt (len .Body) 0 }} 326 + <button 327 + class="py-1/2 px-1 mx-2 bg-gray-200 hover:bg-gray-400 rounded dark:bg-gray-700 dark:hover:bg-gray-600" 328 + hx-on:click="document.getElementById('body-{{$round}}-{{.SHA}}').classList.toggle('hidden')" 329 + > 330 + {{ i "ellipsis" "w-3 h-3" }} 331 + </button> 332 + {{ end }} 333 + {{ if gt (len .Body) 0 }} 334 + <p id="body-{{$round}}-{{.SHA}}" class="hidden mt-1 pb-2">{{ nl2br .Body }}</p> 210 335 {{ end }} 211 336 </div> 337 + </div> 338 + </div> 339 + {{ end }} 340 + {{ end }} 341 + 342 + {{ define "mergeCheck" }} 343 + {{ $isOpen := .Pull.State.IsOpen }} 344 + {{ if and $isOpen .MergeCheck .MergeCheck.Error }} 345 + <div class="flex items-center gap-2"> 346 + {{ i "triangle-alert" "w-4 h-4 text-red-600 dark:text-red-500" }} 347 + {{ .MergeCheck.Error }} 348 + </div> 349 + {{ else if and $isOpen .MergeCheck .MergeCheck.IsConflicted }} 350 + <details class="group/conflict"> 351 + <summary class="flex items-center justify-between cursor-pointer list-none"> 352 + <div class="flex items-center gap-2 "> 353 + {{ i "triangle-alert" "text-red-600 dark:text-red-500 w-4 h-4" }} 354 + <span class="font-medium">merge conflicts detected</span> 355 + <div class="text-sm text-gray-500 dark:text-gray-400"> 356 + <span class="group-open/conflict:hidden inline">expand</span> 357 + <span class="hidden group-open/conflict:inline">collapse</span> 358 + </div> 359 + </div> 360 + </summary> 361 + {{ if gt (len .MergeCheck.Conflicts) 0 }} 362 + <ul class="space-y-1 mt-2 overflow-x-auto"> 363 + {{ range .MergeCheck.Conflicts }} 364 + {{ if .Filename }} 365 + <li class="flex items-center whitespace-nowrap"> 366 + {{ i "file-warning" "inline-flex w-4 h-4 mr-1.5 text-red-600 dark:text-red-500 flex-shrink-0" }} 367 + <span class="font-mono">{{ .Filename }}</span> 368 + </li> 369 + {{ else if .Reason }} 370 + <li class="flex items-center whitespace-nowrap"> 371 + {{ i "file-warning" "w-4 h-4 mr-1.5 text-red-500 dark:text-red-300" }} 372 + <span>{{.Reason}}</span> 373 + </li> 374 + {{ end }} 375 + {{ end }} 376 + </ul> 377 + {{ end }} 212 378 </details> 213 - {{ end }} 379 + {{ else if and $isOpen .MergeCheck }} 380 + <div class="flex items-center gap-2"> 381 + {{ i "check" "w-4 h-4 text-green-600 dark:text-green-500" }} 382 + <span>no conflicts, ready to merge</span> 383 + </div> 214 384 {{ end }} 215 385 {{ end }} 216 386 217 387 {{ define "mergeStatus" }} 218 388 {{ if .Pull.State.IsClosed }} 219 - <div class="bg-gray-50 dark:bg-gray-700 border border-black dark:border-gray-500 rounded drop-shadow-sm px-6 py-2 relative w-fit"> 389 + <div class="bg-gray-50 dark:bg-gray-700 border border-black dark:border-gray-500 rounded drop-shadow-sm px-6 py-2 relative"> 220 390 <div class="flex items-center gap-2 text-black dark:text-white"> 221 391 {{ i "ban" "w-4 h-4" }} 222 392 <span class="font-medium">closed without merging</span ··· 224 394 </div> 225 395 </div> 226 396 {{ else if .Pull.State.IsMerged }} 227 - <div class="bg-purple-50 dark:bg-purple-900 border border-purple-500 rounded drop-shadow-sm px-6 py-2 relative w-fit"> 397 + <div class="bg-purple-50 dark:bg-purple-900 border border-purple-500 rounded drop-shadow-sm px-6 py-2 relative"> 228 398 <div class="flex items-center gap-2 text-purple-500 dark:text-purple-300"> 229 399 {{ i "git-merge" "w-4 h-4" }} 230 400 <span class="font-medium">pull request successfully merged</span ··· 232 402 </div> 233 403 </div> 234 404 {{ else if .Pull.State.IsDeleted }} 235 - <div class="bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-6 py-2 relative w-fit"> 405 + <div class="bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-6 py-2 relative"> 236 406 <div class="flex items-center gap-2 text-red-500 dark:text-red-300"> 237 407 {{ i "git-pull-request-closed" "w-4 h-4" }} 238 408 <span class="font-medium">This pull has been deleted (possibly by jj abandon or jj squash)</span> 239 409 </div> 240 410 </div> 241 - {{ else if and .MergeCheck .MergeCheck.Error }} 242 - <div class="bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-6 py-2 relative w-fit"> 243 - <div class="flex items-center gap-2 text-red-500 dark:text-red-300"> 244 - {{ i "triangle-alert" "w-4 h-4" }} 245 - <span class="font-medium">{{ .MergeCheck.Error }}</span> 246 - </div> 247 - </div> 248 - {{ else if and .MergeCheck .MergeCheck.IsConflicted }} 249 - <div class="bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-6 py-2 relative w-fit"> 250 - <div class="flex flex-col gap-2 text-red-500 dark:text-red-300"> 251 - <div class="flex items-center gap-2"> 252 - {{ i "triangle-alert" "w-4 h-4" }} 253 - <span class="font-medium">merge conflicts detected</span> 254 - </div> 255 - {{ if gt (len .MergeCheck.Conflicts) 0 }} 256 - <ul class="space-y-1"> 257 - {{ range .MergeCheck.Conflicts }} 258 - {{ if .Filename }} 259 - <li class="flex items-center"> 260 - {{ i "file-warning" "w-4 h-4 mr-1.5 text-red-500 dark:text-red-300" }} 261 - <span class="font-mono">{{ .Filename }}</span> 262 - </li> 263 - {{ else if .Reason }} 264 - <li class="flex items-center"> 265 - {{ i "file-warning" "w-4 h-4 mr-1.5 text-red-500 dark:text-red-300" }} 266 - <span>{{.Reason}}</span> 267 - </li> 268 - {{ end }} 269 - {{ end }} 270 - </ul> 271 - {{ end }} 272 - </div> 273 - </div> 274 - {{ else if .MergeCheck }} 275 - <div class="bg-green-50 dark:bg-green-900 border border-green-500 rounded drop-shadow-sm px-6 py-2 relative w-fit"> 276 - <div class="flex items-center gap-2 text-green-500 dark:text-green-300"> 277 - {{ i "circle-check-big" "w-4 h-4" }} 278 - <span class="font-medium">no conflicts, ready to merge</span> 279 - </div> 280 - </div> 281 411 {{ end }} 282 412 {{ end }} 283 413 284 414 {{ define "resubmitStatus" }} 285 415 {{ if .ResubmitCheck.Yes }} 286 - <div class="bg-amber-50 dark:bg-amber-900 border border-amber-500 rounded drop-shadow-sm px-6 py-2 relative w-fit"> 416 + <div class="bg-amber-50 dark:bg-amber-900 border border-amber-500 rounded drop-shadow-sm px-6 py-2 relative"> 287 417 <div class="flex items-center gap-2 text-amber-500 dark:text-amber-300"> 288 418 {{ i "triangle-alert" "w-4 h-4" }} 289 419 <span class="font-medium">this branch has been updated, consider resubmitting</span> ··· 292 422 {{ end }} 293 423 {{ end }} 294 424 295 - {{ define "pipelineStatus" }} 296 - {{ $root := index . 0 }} 297 - {{ $submission := index . 1 }} 298 - {{ $pipeline := index $root.Pipelines $submission.SourceRev }} 425 + {{ define "submissionPipeline" }} 426 + {{ $item := index . 0 }} 427 + {{ $root := index . 3 }} 428 + {{ $pipeline := index $root.Pipelines $item.SourceRev }} 299 429 {{ with $pipeline }} 300 430 {{ $id := .Id }} 301 431 {{ if .Statuses }} 302 - <div class="max-w-80 grid grid-cols-1 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700"> 303 - {{ range $name, $all := .Statuses }} 304 - <a href="/{{ $root.RepoInfo.FullName }}/pipelines/{{ $id }}/workflow/{{ $name }}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"> 305 - <div 306 - class="flex gap-2 items-center justify-between p-2"> 307 - {{ $lastStatus := $all.Latest }} 308 - {{ $kind := $lastStatus.Status.String }} 432 + <details class="group/pipeline"> 433 + <summary class="cursor-pointer list-none flex items-center gap-2"> 434 + {{ template "repo/pipelines/fragments/pipelineSymbol" (dict "Pipeline" $pipeline "ShortSummary" false) }} 435 + <div class="text-sm text-gray-500 dark:text-gray-400"> 436 + <span class="group-open/pipeline:hidden inline">expand</span> 437 + <span class="hidden group-open/pipeline:inline">collapse</span> 438 + </div> 439 + </summary> 440 + <div class="my-2 grid grid-cols-1 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700"> 441 + {{ range $name, $all := .Statuses }} 442 + <a href="/{{ $root.RepoInfo.FullName }}/pipelines/{{ $id }}/workflow/{{ $name }}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"> 443 + <div 444 + class="flex gap-2 items-center justify-between p-2"> 445 + {{ $lastStatus := $all.Latest }} 446 + {{ $kind := $lastStatus.Status.String }} 309 447 310 - <div id="left" class="flex items-center gap-2 flex-shrink-0"> 311 - {{ template "repo/pipelines/fragments/workflowSymbol" $all }} 312 - {{ $name }} 313 - </div> 314 - <div id="right" class="flex items-center gap-2 flex-shrink-0"> 315 - <span class="font-bold">{{ $kind }}</span> 316 - {{ if .TimeTaken }} 317 - {{ template "repo/fragments/duration" .TimeTaken }} 318 - {{ else }} 319 - {{ template "repo/fragments/shortTimeAgo" $lastStatus.Created }} 320 - {{ end }} 321 - </div> 448 + <div id="left" class="flex items-center gap-2 flex-shrink-0"> 449 + {{ template "repo/pipelines/fragments/workflowSymbol" $all }} 450 + {{ $name }} 451 + </div> 452 + <div id="right" class="flex items-center gap-2 flex-shrink-0"> 453 + <span class="font-bold">{{ $kind }}</span> 454 + {{ if .TimeTaken }} 455 + {{ template "repo/fragments/duration" .TimeTaken }} 456 + {{ else }} 457 + {{ template "repo/fragments/shortTimeAgo" $lastStatus.Created }} 458 + {{ end }} 459 + </div> 460 + </div> 461 + </a> 462 + {{ end }} 322 463 </div> 464 + </details> 465 + {{ end }} 466 + {{ end }} 467 + {{ end }} 468 + 469 + {{ define "submissionComments" }} 470 + {{ $item := index . 0 }} 471 + <div class="relative ml-10 border-l-2 border-gray-200 dark:border-gray-700"> 472 + {{ range $item.Comments }} 473 + {{ template "submissionComment" . }} 474 + {{ end }} 475 + </div> 476 + {{ end }} 477 + 478 + {{ define "submissionComment" }} 479 + <div id="comment-{{.ID}}" class="flex gap-2 -ml-4 py-4 w-full mx-auto"> 480 + <!-- left column: profile picture --> 481 + <div class="flex-shrink-0"> 482 + <img 483 + src="{{ tinyAvatar .OwnerDid }}" 484 + alt="" 485 + class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900" 486 + /> 487 + </div> 488 + <!-- right column: name and body in two rows --> 489 + <div class="flex-1 min-w-0"> 490 + <!-- Row 1: Author and timestamp --> 491 + <div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1"> 492 + <span>{{ resolve .OwnerDid }}</span> 493 + <span class="before:content-['ยท']"></span> 494 + <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.ID}}"> 495 + {{ template "repo/fragments/time" .Created }} 323 496 </a> 324 - {{ end }} 325 497 </div> 326 - {{ end }} 327 - {{ end }} 498 + <!-- Row 2: Body text --> 499 + <div class="prose dark:prose-invert mt-1"> 500 + {{ .Body | markdown }} 501 + </div> 502 + </div> 503 + </div> 504 + {{ end }} 505 + 506 + {{ define "loginPrompt" }} 507 + <div class="bg-amber-50 dark:bg-amber-900 border border-amber-500 rounded drop-shadow-sm p-2 relative flex gap-2 items-center"> 508 + <a href="/signup" class="btn-create py-0 hover:no-underline hover:text-white flex items-center gap-2"> 509 + sign up 510 + </a> 511 + <span class="text-gray-500 dark:text-gray-400">or</span> 512 + <a href="/login" class="underline">login</a> 513 + to add to the discussion 514 + </div> 328 515 {{ end }}
+3 -12
appview/pages/templates/repo/pulls/pulls.html
··· 112 112 {{ template "repo/fragments/time" .Created }} 113 113 </span> 114 114 115 - 116 - {{ $latestRound := .LastRoundNumber }} 117 - {{ $lastSubmission := index .Submissions $latestRound }} 118 - 119 115 <span class="before:content-['ยท']"> 120 - {{ $commentCount := len $lastSubmission.Comments }} 121 - {{ $s := "s" }} 122 - {{ if eq $commentCount 1 }} 123 - {{ $s = "" }} 124 - {{ end }} 125 - 126 - {{ len $lastSubmission.Comments}} comment{{$s}} 116 + {{ $commentCount := .TotalComments }} 117 + {{ $commentCount }} comment{{ if ne $commentCount 1 }}s{{ end }} 127 118 </span> 128 119 129 120 <span class="before:content-['ยท']"> ··· 136 127 {{ $pipeline := index $.Pipelines .LatestSha }} 137 128 {{ if and $pipeline $pipeline.Id }} 138 129 <span class="before:content-['ยท']"></span> 139 - {{ template "repo/pipelines/fragments/pipelineSymbol" $pipeline }} 130 + {{ template "repo/pipelines/fragments/pipelineSymbol" (dict "Pipeline" $pipeline "ShortSummary" true) }} 140 131 {{ end }} 141 132 142 133 {{ $state := .Labels }}
+2 -2
appview/pulls/opengraph.go
··· 18 18 "tangled.org/core/types" 19 19 ) 20 20 21 - func (s *Pulls) drawPullSummaryCard(pull *models.Pull, repo *models.Repo, commentCount int, diffStats types.DiffStat, filesChanged int) (*ogcard.Card, error) { 21 + func (s *Pulls) drawPullSummaryCard(pull *models.Pull, repo *models.Repo, commentCount int, diffStats types.DiffFileStat, filesChanged int) (*ogcard.Card, error) { 22 22 width, height := ogcard.DefaultSize() 23 23 mainCard, err := ogcard.NewCard(width, height) 24 24 if err != nil { ··· 284 284 commentCount := len(comments) 285 285 286 286 // Calculate diff stats from latest submission using patchutil 287 - var diffStats types.DiffStat 287 + var diffStats types.DiffFileStat 288 288 filesChanged := 0 289 289 if len(pull.Submissions) > 0 { 290 290 latestSubmission := pull.Submissions[len(pull.Submissions)-1]
+11 -2
appview/pulls/pulls.go
··· 232 232 defs[l.AtUri().String()] = &l 233 233 } 234 234 235 - s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{ 235 + patch := pull.LatestSubmission().CombinedPatch() 236 + diff := patchutil.AsNiceDiff(patch, pull.TargetBranch) 237 + var diffOpts types.DiffOpts 238 + if d := r.URL.Query().Get("diff"); d == "split" { 239 + diffOpts.Split = true 240 + } 241 + 242 + log.Println(s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{ 236 243 LoggedInUser: user, 237 244 RepoInfo: s.repoResolver.GetRepoInfo(r, user), 238 245 Pull: pull, ··· 243 250 MergeCheck: mergeCheckResponse, 244 251 ResubmitCheck: resubmitResult, 245 252 Pipelines: m, 253 + Diff: &diff, 254 + DiffOpts: diffOpts, 246 255 247 256 OrderedReactionKinds: models.OrderedReactionKinds, 248 257 Reactions: reactionMap, 249 258 UserReacted: userReactions, 250 259 251 260 LabelDefs: defs, 252 - }) 261 + })) 253 262 } 254 263 255 264 func (s *Pulls) mergeCheck(r *http.Request, f *models.Repo, pull *models.Pull, stack models.Stack) types.MergeCheckResponse {
+6 -5
docs/template.html
··· 120 120 $endif$ 121 121 $endif$ 122 122 </header> 123 - $if(abstract)$ 124 - <article class="prose dark:prose-invert max-w-none"> 125 - $abstract$ 126 - </article> 127 - $endif$ 123 + $endif$ 124 + 125 + $if(abstract)$ 126 + <article class="prose dark:prose-invert max-w-none"> 127 + $abstract$ 128 + </article> 128 129 $endif$ 129 130 130 131 <article class="prose dark:prose-invert max-w-none">
+13 -4
input.css
··· 124 124 dark:text-gray-100 dark:before:bg-gray-800 dark:before:border-gray-700; 125 125 } 126 126 127 + .btn-flat { 128 + @apply relative z-10 inline-flex min-h-[30px] cursor-pointer items-center justify-center 129 + bg-transparent px-2 pb-[0.2rem] text-sm text-gray-900 130 + before:absolute before:inset-0 before:-z-10 before:block before:rounded 131 + before:border before:border-gray-200 before:bg-white 132 + before:content-[''] before:transition-all before:duration-150 before:ease-in-out 133 + hover:before:bg-gray-50 134 + dark:hover:before:bg-gray-700 135 + focus:outline-none focus-visible:before:outline focus-visible:before:outline-2 focus-visible:before:outline-gray-400 136 + disabled:cursor-not-allowed disabled:opacity-50 137 + dark:text-gray-100 dark:before:bg-gray-800 dark:before:border-gray-700; 138 + } 139 + 127 140 .btn-create { 128 141 @apply btn text-white 129 142 before:bg-green-600 hover:before:bg-green-700 ··· 131 144 before:border before:border-green-700 hover:before:border-green-800 132 145 focus-visible:before:outline-green-500 133 146 disabled:before:bg-green-400 dark:disabled:before:bg-green-600; 134 - } 135 - 136 - .prose { 137 - overflow-wrap: anywhere; 138 147 } 139 148 140 149 .prose hr {
+3 -8
knotserver/git/diff.go
··· 64 64 65 65 for _, tf := range d.TextFragments { 66 66 ndiff.TextFragments = append(ndiff.TextFragments, *tf) 67 - for _, l := range tf.Lines { 68 - switch l.Op { 69 - case gitdiff.OpAdd: 70 - nd.Stat.Insertions += 1 71 - case gitdiff.OpDelete: 72 - nd.Stat.Deletions += 1 73 - } 74 - } 67 + nd.Stat.Insertions += tf.LinesAdded 68 + nd.Stat.Deletions += tf.LinesDeleted 75 69 } 76 70 77 71 nd.Diff = append(nd.Diff, ndiff) 78 72 } 79 73 74 + nd.Stat.FilesChanged += len(diffs) 80 75 nd.Commit.FromGoGitCommit(c) 81 76 82 77 return &nd, nil
-3
nix/modules/appview.nix
··· 1 1 { 2 - pkgs, 3 2 config, 4 3 lib, 5 4 ... ··· 260 259 after = ["redis-appview.service" "network-online.target"]; 261 260 requires = ["redis-appview.service"]; 262 261 wants = ["network-online.target"]; 263 - 264 - path = [pkgs.diffutils]; 265 262 266 263 serviceConfig = { 267 264 Type = "simple";
+66 -10
patchutil/interdiff.go
··· 5 5 "strings" 6 6 7 7 "github.com/bluekeyes/go-gitdiff/gitdiff" 8 + "tangled.org/core/appview/filetree" 8 9 "tangled.org/core/types" 9 10 ) 10 11 ··· 12 13 Files []*InterdiffFile 13 14 } 14 15 15 - func (i *InterdiffResult) AffectedFiles() []string { 16 - files := make([]string, len(i.Files)) 17 - for _, f := range i.Files { 18 - files = append(files, f.Name) 16 + func (i *InterdiffResult) Stats() types.DiffStat { 17 + var ins, del int64 18 + for _, s := range i.ChangedFiles() { 19 + stat := s.Stats() 20 + ins += stat.Insertions 21 + del += stat.Deletions 22 + } 23 + return types.DiffStat{ 24 + Insertions: ins, 25 + Deletions: del, 26 + FilesChanged: len(i.Files), 19 27 } 20 - return files 28 + } 29 + 30 + func (i *InterdiffResult) ChangedFiles() []types.DiffFileRenderer { 31 + drs := make([]types.DiffFileRenderer, len(i.Files)) 32 + for i, s := range i.Files { 33 + drs[i] = s 34 + } 35 + return drs 36 + } 37 + 38 + func (i *InterdiffResult) FileTree() *filetree.FileTreeNode { 39 + fs := make([]string, len(i.Files)) 40 + for i, s := range i.Files { 41 + fs[i] = s.Name 42 + } 43 + return filetree.FileTree(fs) 21 44 } 22 45 23 46 func (i *InterdiffResult) String() string { ··· 36 59 Status InterdiffFileStatus 37 60 } 38 61 39 - func (s *InterdiffFile) Split() *types.SplitDiff { 62 + func (s *InterdiffFile) Id() string { 63 + return s.Name 64 + } 65 + 66 + func (s *InterdiffFile) Split() types.SplitDiff { 40 67 fragments := make([]types.SplitFragment, len(s.TextFragments)) 41 68 42 69 for i, fragment := range s.TextFragments { ··· 49 76 } 50 77 } 51 78 52 - return &types.SplitDiff{ 79 + return types.SplitDiff{ 53 80 Name: s.Id(), 54 81 TextFragments: fragments, 55 82 } 56 83 } 57 84 58 - // used by html elements as a unique ID for hrefs 59 - func (s *InterdiffFile) Id() string { 60 - return s.Name 85 + func (s *InterdiffFile) CanRender() string { 86 + if s.Status.IsUnchanged() { 87 + return "This file has not been changed." 88 + } else if s.Status.IsRebased() { 89 + return "This patch was likely rebased, as context lines do not match." 90 + } else if s.Status.IsError() { 91 + return "Failed to calculate interdiff for this file." 92 + } else { 93 + return "" 94 + } 95 + } 96 + 97 + func (s *InterdiffFile) Names() types.DiffFileName { 98 + var n types.DiffFileName 99 + n.New = s.Name 100 + return n 101 + } 102 + 103 + func (s *InterdiffFile) Stats() types.DiffFileStat { 104 + var ins, del int64 105 + 106 + if s.File != nil { 107 + for _, f := range s.TextFragments { 108 + ins += f.LinesAdded 109 + del += f.LinesDeleted 110 + } 111 + } 112 + 113 + return types.DiffFileStat{ 114 + Insertions: ins, 115 + Deletions: del, 116 + } 61 117 } 62 118 63 119 func (s *InterdiffFile) String() string {
+9
patchutil/patchutil_test.go
··· 4 4 "errors" 5 5 "reflect" 6 6 "testing" 7 + 8 + "tangled.org/core/types" 7 9 ) 8 10 9 11 func TestIsPatchValid(t *testing.T) { ··· 323 325 }) 324 326 } 325 327 } 328 + 329 + func TestImplsInterfaces(t *testing.T) { 330 + id := &InterdiffResult{} 331 + _ = isDiffsRenderer(id) 332 + } 333 + 334 + func isDiffsRenderer[S types.DiffRenderer](S) bool { return true }
+2 -2
spindle/models/models.go
··· 53 53 StatusKindRunning, 54 54 } 55 55 FinishStates [4]StatusKind = [4]StatusKind{ 56 - StatusKindCancelled, 57 56 StatusKindFailed, 58 - StatusKindSuccess, 59 57 StatusKindTimeout, 58 + StatusKindCancelled, 59 + StatusKindSuccess, 60 60 } 61 61 ) 62 62
+66 -30
types/diff.go
··· 2 2 3 3 import ( 4 4 "github.com/bluekeyes/go-gitdiff/gitdiff" 5 + "tangled.org/core/appview/filetree" 5 6 ) 6 7 7 8 type DiffOpts struct { 8 9 Split bool `json:"split"` 9 10 } 10 11 11 - type TextFragment struct { 12 - Header string `json:"comment"` 13 - Lines []gitdiff.Line `json:"lines"` 12 + // A nicer git diff representation. 13 + type NiceDiff struct { 14 + Commit Commit `json:"commit"` 15 + Stat DiffStat `json:"stat"` 16 + Diff []Diff `json:"diff"` 14 17 } 15 18 16 19 type Diff struct { ··· 26 29 IsRename bool `json:"is_rename"` 27 30 } 28 31 29 - type DiffStat struct { 30 - Insertions int64 31 - Deletions int64 32 - } 33 - 34 - func (d *Diff) Stats() DiffStat { 35 - var stats DiffStat 32 + func (d Diff) Stats() DiffFileStat { 33 + var stats DiffFileStat 36 34 for _, f := range d.TextFragments { 37 35 stats.Insertions += f.LinesAdded 38 36 stats.Deletions += f.LinesDeleted ··· 40 38 return stats 41 39 } 42 40 43 - // A nicer git diff representation. 44 - type NiceDiff struct { 45 - Commit Commit `json:"commit"` 46 - Stat struct { 47 - FilesChanged int `json:"files_changed"` 48 - Insertions int `json:"insertions"` 49 - Deletions int `json:"deletions"` 50 - } `json:"stat"` 51 - Diff []Diff `json:"diff"` 41 + type DiffStat struct { 42 + Insertions int64 `json:"insertions"` 43 + Deletions int64 `json:"deletions"` 44 + FilesChanged int `json:"files_changed"` 45 + } 46 + 47 + type DiffFileStat struct { 48 + Insertions int64 49 + Deletions int64 52 50 } 53 51 54 52 type DiffTree struct { ··· 58 56 Diff []*gitdiff.File `json:"diff"` 59 57 } 60 58 61 - func (d *NiceDiff) ChangedFiles() []string { 62 - files := make([]string, len(d.Diff)) 59 + type DiffFileName struct { 60 + Old string 61 + New string 62 + } 63 63 64 - for i, f := range d.Diff { 65 - if f.IsDelete { 66 - files[i] = f.Name.Old 64 + func (d NiceDiff) ChangedFiles() []DiffFileRenderer { 65 + drs := make([]DiffFileRenderer, len(d.Diff)) 66 + for i, s := range d.Diff { 67 + drs[i] = s 68 + } 69 + return drs 70 + } 71 + 72 + func (d NiceDiff) FileTree() *filetree.FileTreeNode { 73 + fs := make([]string, len(d.Diff)) 74 + for i, s := range d.Diff { 75 + n := s.Names() 76 + if n.New == "" { 77 + fs[i] = n.Old 67 78 } else { 68 - files[i] = f.Name.New 79 + fs[i] = n.New 69 80 } 70 81 } 82 + return filetree.FileTree(fs) 83 + } 71 84 72 - return files 85 + func (d NiceDiff) Stats() DiffStat { 86 + return d.Stat 73 87 } 74 88 75 - // used by html elements as a unique ID for hrefs 76 - func (d *Diff) Id() string { 89 + func (d Diff) Id() string { 77 90 if d.IsDelete { 78 91 return d.Name.Old 79 92 } 80 93 return d.Name.New 81 94 } 82 95 83 - func (d *Diff) Split() *SplitDiff { 96 + func (d Diff) Names() DiffFileName { 97 + var n DiffFileName 98 + if d.IsDelete { 99 + n.Old = d.Name.Old 100 + return n 101 + } else if d.IsCopy || d.IsRename { 102 + n.Old = d.Name.Old 103 + n.New = d.Name.New 104 + return n 105 + } else { 106 + n.New = d.Name.New 107 + return n 108 + } 109 + } 110 + 111 + func (d Diff) CanRender() string { 112 + if d.IsBinary { 113 + return "This is a binary file and will not be displayed." 114 + } 115 + 116 + return "" 117 + } 118 + 119 + func (d Diff) Split() SplitDiff { 84 120 fragments := make([]SplitFragment, len(d.TextFragments)) 85 121 for i, fragment := range d.TextFragments { 86 122 leftLines, rightLines := SeparateLines(&fragment) ··· 91 127 } 92 128 } 93 129 94 - return &SplitDiff{ 130 + return SplitDiff{ 95 131 Name: d.Id(), 96 132 TextFragments: fragments, 97 133 }
+31
types/diff_renderer.go
··· 1 + package types 2 + 3 + import "tangled.org/core/appview/filetree" 4 + 5 + type DiffRenderer interface { 6 + // list of file affected by these diffs 7 + ChangedFiles() []DiffFileRenderer 8 + 9 + // filetree 10 + FileTree() *filetree.FileTreeNode 11 + 12 + Stats() DiffStat 13 + } 14 + 15 + type DiffFileRenderer interface { 16 + // html ID for each file in the diff 17 + Id() string 18 + 19 + // produce a splitdiff 20 + Split() SplitDiff 21 + 22 + // stats for this single file 23 + Stats() DiffFileStat 24 + 25 + // old and new name of file 26 + Names() DiffFileName 27 + 28 + // whether this diff can be displayed, 29 + // returns a reason if not, and the empty string if it can 30 + CanRender() string 31 + }
+11 -2
types/diff_test.go
··· 1 1 package types 2 2 3 - import "testing" 3 + import ( 4 + "testing" 5 + ) 4 6 5 7 func TestDiffId(t *testing.T) { 6 8 tests := []struct { ··· 105 107 } 106 108 107 109 for i, diff := range nd.Diff { 108 - if changedFiles[i] != diff.Id() { 110 + if changedFiles[i].Id() != diff.Id() { 109 111 t.Errorf("ChangedFiles()[%d] = %q, but Diff.Id() = %q", i, changedFiles[i], diff.Id()) 110 112 } 111 113 } 112 114 } 115 + 116 + func TestImplsInterfaces(t *testing.T) { 117 + nd := NiceDiff{} 118 + _ = isDiffsRenderer(nd) 119 + } 120 + 121 + func isDiffsRenderer[S DiffRenderer](S) bool { return true }
+1 -2
types/split.go
··· 22 22 TextFragments []SplitFragment `json:"fragments"` 23 23 } 24 24 25 - // used by html elements as a unique ID for hrefs 26 - func (d *SplitDiff) Id() string { 25 + func (d SplitDiff) Id() string { 27 26 return d.Name 28 27 } 29 28