this repo has no description

wip: rework commit views

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

Changed files
+590 -623
appview
spindle
models
types
+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 {
+1 -1
appview/pages/templates/fragments/tabSelector.html
··· 9 9 {{ range $index, $value := $all }} 10 10 {{ $isActive := eq $value.Key $active }} 11 11 <a href="?{{ $name }}={{ $value.Key }}" 12 - hx-boost 12 + hx-boost=true 13 13 {{ if $include }} 14 14 hx-get="?{{ $name }}={{ $value.Key }}" 15 15 hx-include="{{ $include }}"
+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}}
+113 -36
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: 10vw; 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" }} 15 + {{ $diff := index . 0 }} 16 + {{ $opts := index . 1 }} 17 + 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" . }} 25 + 26 + <!-- stats --> 27 + {{ template "repo/fragments/diffStatPill" $diff.Stat }} 28 + {{ $diff.Stat.FilesChanged }} changed file{{ if ne $diff.Stat.FilesChanged 1 }}s{{ end }} 29 + 30 + <!-- spacer --> 31 + <div class="flex-grow"></div> 32 + 33 + <!-- diff options --> 34 + {{ template "repo/fragments/diffOpts" $opts }} 35 + 36 + <!-- right panel toggle --> 37 + {{ block "subsToggle" $ }} {{ end }} 38 + </div> 39 + 40 + {{ end }} 41 + 42 + {{ define "diffLayout" }} 43 + {{ $diff := index . 0 }} 44 + {{ $opts := index . 1 }} 45 + 46 + <div class="flex col-span-full"> 47 + <!-- Left panel (same for both desktop and mobile) --> 48 + <div id="files" class="w-0 overflow-hidden sticky top-12 max-h-screen overflow-y-auto pb-12"> 49 + {{ template "repo/fragments/diffChangedFiles" $diff }} 50 + </div> 51 + 52 + <!-- Main content --> 53 + <div class="flex-1 min-w-0 sticky top-12 pb-12"> 54 + {{ template "diffFiles" (list $diff $opts) }} 55 + </div> 56 + 57 + </div> 58 + {{ end }} 59 + 60 + {{ define "diffFiles" }} 2 61 {{ $diff := index . 0 }} 3 62 {{ $opts := index . 1 }} 4 63 ··· 15 74 <p>No differences found between the selected revisions.</p> 16 75 </div> 17 76 {{ 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-12"> 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 }} 77 + {{ range $idx, $file := $diff }} 78 + {{ template "diffFile" (list $idx $file $isSplit) }} 79 + {{ end }} 80 + {{ end }} 81 + </div> 82 + {{ end }} 27 83 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> 84 + {{ define "diffFile" }} 85 + {{ $idx := index . 0 }} 86 + {{ $file := index . 1 }} 87 + {{ $isSplit := index . 2 }} 88 + {{ with $file }} 89 + <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 }}"> 90 + <summary class="list-none cursor-pointer sticky top-12 group-open:border-b border-gray-200 dark:border-gray-700"> 91 + <div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between"> 92 + <div id="left-side-items" class="p-2 flex gap-2 items-center overflow-x-auto"> 93 + <span class="group-open:hidden inline">{{ i "chevron-right" "w-4 h-4" }}</span> 94 + <span class="hidden group-open:inline">{{ i "chevron-down" "w-4 h-4" }}</span> 95 + {{ template "repo/fragments/diffStatPill" .Stats }} 40 96 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 -}} 97 + <div class="flex gap-2 items-center overflow-x-auto"> 98 + {{ if .IsDelete }} 99 + {{ .Name.Old }} 100 + {{ else if (or .IsCopy .IsRename) }} 101 + {{ .Name.Old }} {{ i "arrow-right" "w-4 h-4" }} {{ .Name.New }} 49 102 {{ else }} 50 - {{- template "repo/fragments/unifiedDiff" . -}} 103 + {{ .Name.New }} 51 104 {{ end }} 52 - {{- end -}} 105 + </div> 53 106 </div> 54 - </details> 55 - {{ end }} 56 - {{ end }} 57 - {{ end }} 58 - </div> 107 + </div> 108 + </summary> 109 + 110 + <div class="transition-all duration-700 ease-in-out"> 111 + {{ if .IsBinary }} 112 + <p class="text-center text-gray-400 dark:text-gray-500 p-4"> 113 + This is a binary file and will not be displayed. 114 + </p> 115 + {{ else }} 116 + {{ if $isSplit }} 117 + {{- template "repo/fragments/splitDiff" .Split -}} 118 + {{ else }} 119 + {{- template "repo/fragments/unifiedDiff" . -}} 120 + {{ end }} 121 + {{- end -}} 122 + </div> 123 + </details> 124 + {{ end }} 125 + {{ end }} 126 + 127 + {{ define "filesCheckbox" }} 128 + <input type="checkbox" id="filesToggle" class="peer/files hidden" checked/> 129 + {{ end }} 130 + 131 + {{ define "filesToggle" }} 132 + <label for="filesToggle" class="hidden md:inline-flex items-center justify-center rounded cursor-pointer text-normal font-normal normalcase"> 133 + <span class="show-text">{{ i "panel-left-open" "size-4" }}</span> 134 + <span class="hide-text">{{ i "panel-left-close" "size-4" }}</span> 135 + </label> 59 136 {{ end }}
+2 -3
appview/pages/templates/repo/fragments/interdiff.html
··· 1 1 {{ define "repo/fragments/interdiff" }} 2 - {{ $repo := index . 0 }} 3 - {{ $x := index . 1 }} 4 - {{ $opts := index . 2 }} 2 + {{ $x := index . 0 }} 3 + {{ $opts := index . 1 }} 5 4 {{ $fileTree := fileTree $x.AffectedFiles }} 6 5 {{ $diff := $x.Files }} 7 6 {{ $last := sub (len $diff) 1 }}
+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" -}}
+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>
+1 -1
appview/pages/templates/repo/pulls/fragments/summarizedPullHeader.html
··· 18 18 {{ $lastSubmission := index .Submissions $latestRound }} 19 19 {{ $commentCount := len $lastSubmission.Comments }} 20 20 {{ if and $pipeline $pipeline.Id }} 21 - {{ template "repo/pipelines/fragments/pipelineSymbol" $pipeline }} 21 + {{ template "repo/pipelines/fragments/pipelineSymbol" (dict "Pipeline" $pipeline "ShortSummary" true) }} 22 22 <span class="before:content-['·'] before:select-none text-gray-500 dark:text-gray-400"></span> 23 23 {{ end }} 24 24 <span>
+2 -20
appview/pages/templates/repo/pulls/interdiff.html
··· 34 34 {{ block "content" . }}{{ end }} 35 35 {{ end }} 36 36 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 }} 37 + {{ block "contentAfter" . }}{{ end }} 47 38 </div> 48 39 {{ end }} 49 40 50 41 {{ 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> 42 + {{ template "repo/fragments/interdiff" (list .Interdiff .DiffOpts) }} 61 43 {{end}}
+359 -445
appview/pages/templates/repo/pulls/pull.html
··· 12 12 {{ block "content" . }}{{ end }} 13 13 {{ end }} 14 14 </div> 15 + <script> 16 + (function() { 17 + const details = document.getElementById('bottomSheet'); 18 + const isDesktop = () => window.matchMedia('(min-width: 768px)').matches; 19 + 20 + // close on mobile initially 21 + if (!isDesktop()) { 22 + details.open = false; 23 + } 24 + 25 + // prevent closing on desktop 26 + details.addEventListener('toggle', function(e) { 27 + if (isDesktop() && !this.open) { 28 + this.open = true; 29 + } 30 + }); 31 + 32 + const mediaQuery = window.matchMedia('(min-width: 768px)'); 33 + mediaQuery.addEventListener('change', function(e) { 34 + if (e.matches) { 35 + // switched to desktop - keep open 36 + details.open = true; 37 + } else { 38 + // switched to mobile - close 39 + details.open = false; 40 + } 41 + }); 42 + })(); 43 + </script> 15 44 {{ end }} 16 45 17 46 {{ define "repoContentLayout" }} ··· 35 64 {{ template "repo/fragments/externalLinkPanel" $.Pull.AtUri }} 36 65 </div> 37 66 38 - <style> 39 - #filesToggle:checked ~ div label[for="filesToggle"] .show-text { display: none; } 40 - #filesToggle:checked ~ div label[for="filesToggle"] .hide-text { display: inline; } 41 - #filesToggle:not(:checked) ~ div label[for="filesToggle"] .hide-text { display: none; } 42 - 43 - #filesToggle:checked ~ div div#files { width: 10vw; margin-right: 1rem; } 44 - #filesToggle:not(:checked) ~ div div#files { width: 0; display: hidden; margin-right: 0; } 45 - 46 - #subsToggle:checked ~ div div#subs { width: 25vw; margin-left: 1rem; } 47 - #subsToggle:not(:checked) ~ div div#subs { width: 0; display: hidden; margin-left: 0; } 48 - </style> 49 - 50 - <!-- Checkboxes must come first as siblings --> 51 - <input type="checkbox" id="filesToggle" class="peer/files hidden" checked/> 52 - <input type="checkbox" id="subsToggle" class="peer/subs hidden" checked/> 53 - 54 - <!-- Top bar with controls --> 55 - <div class="sticky top-0 z-30 bg-slate-100 dark:bg-gray-900 flex items-center gap-2 col-span-full h-12"> 56 - <label for="filesToggle" class="inline-flex items-center justify-center rounded cursor-pointer p-2 text-normal font-normal normalcase"> 57 - <span class="show-text">{{ i "panel-left-open" "size-5" }}</span> 58 - <span class="hide-text">{{ i "panel-left-close" "size-5" }}</span> 59 - </label> 60 - {{ template "repo/fragments/diffStatPill" .Diff.Stat }} 61 - {{ .Diff.Stat.FilesChanged }} changed file{{ if ne .Diff.Stat.FilesChanged 1 }}s{{ end }} 62 - <div class="flex-grow"></div> 63 - {{ template "repo/fragments/diffOpts" .DiffOpts }} 64 - <label for="subsToggle" class="inline-flex items-center justify-center rounded cursor-pointer p-2"> 65 - {{ i "message-square-more" "size-5" }} 66 - </label> 67 - </div> 68 - 69 - <div class="flex col-span-full"> 70 - <!-- left panel --> 71 - <div id="files" class="w-0 overflow-hidden sticky top-12 max-h-screen overflow-y-auto pb-12"> 72 - {{ template "repo/fragments/diffChangedFiles" .Diff }} 73 - </div> 74 - 75 - <!-- main content --> 76 - <div class="flex-1 min-w-0 sticky top-12 pb-12"> 77 - {{ template "repo/fragments/diff" (list .Diff .DiffOpts) }} 78 - </div> 79 - 80 - <!-- right panel --> 81 - <div id="subs" class="w-0 overflow-hidden max-h-screen flex flex-col sticky top-12 pb-12"> 82 - <div class="z-20 sticky top-0 rounded-t p-3 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700"> 83 - <h2 class="font-bold uppercase">history</h2> 84 - </div> 85 - <div class="flex flex-col-reverse gap-4 overflow-y-auto"> 86 - {{ template "submissions2" . }} 87 - </div> 88 - </div> 89 - </div> 67 + {{ template "repo/fragments/diff" (list .Diff .DiffOpts $) }} 90 68 </div> 91 69 {{ end }} 92 70 ··· 104 82 <div id="pull-reopen"></div> 105 83 {{ end }} 106 84 107 - {{ define "submissions2" }} 108 - {{ $lastIdx := sub (len .Pull.Submissions) 1 }} 109 - {{ range $ridx, $item := reverse .Pull.Submissions }} 110 - {{ $idx := sub $lastIdx $ridx }} 111 - <div class="rounded border border-gray-200 dark:border-gray-700 w-full shadow-sm bg-gray-50 dark:bg-gray-800/50"> 112 - {{ with $item }} 113 - {{ $patches := .AsFormatPatch }} 114 - {{ $round := .RoundNumber }} 115 - <div class="rounded px-6 py-4 bg-white dark:bg-gray-800 flex gap-2"> 116 - <div class="flex-shrink-0"> 117 - <img 118 - src="{{ tinyAvatar $.Pull.OwnerDid }}" 119 - alt="" 120 - class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900" 121 - /> 122 - </div> 123 - <!-- right column: name and body in two rows --> 124 - <div class="flex-1 min-w-0 flex flex-col gap-2"> 125 - <div class="flex gap-2 items-center justify-between mb-1"> 126 - <span class="inline-flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-500"> 127 - {{ resolve $.Pull.OwnerDid }} submitted v{{ $round }} 128 - <span class="select-none before:content-['\00B7']"></span> 129 - <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500" href="#round-#{{ $round }}">{{ template "repo/fragments/shortTimeAgo" .Created }}</a> 130 - </span> 131 - {{ if ne $idx 0 }} 132 - <a class="flex items-center gap-2 no-underline hover:no-underline text-sm" 133 - hx-boost="true" 134 - href="/{{ $.RepoInfo.FullName }}/pulls/{{ $.Pull.PullId }}/round/{{$round}}/interdiff"> 135 - {{ i "chevrons-left-right-ellipsis" "w-4 h-4 rotate-90" }} 136 - <span class="hidden md:inline">interdiff</span> 137 - </a> 138 - {{ end }} 139 - </div> 140 - <div> 141 - {{ if eq 1 (len $patches) }} 142 - <!-- only one commit, just inline the message into the round header --> 143 - {{ $commit := index $patches 0 }} 144 - <span>{{ $commit.Title | description }}</span> 145 - {{ if gt (len $commit.Body) 0 }} 146 - <p id="body-{{$round}}-{{$commit.SHA}}" class="mt-1 pb-2"> 147 - {{ nl2br $commit.Body }} 148 - </p> 149 - {{ end }} 150 - {{ else }} 151 - <span>Commits:</span> 152 - {{ range $patches }} 153 - <div id="commit-{{.SHA}}" class="py-1 px-2 relative w-full md:max-w-3/5 md:w-fit flex flex-col"> 154 - <div class="flex items-center gap-2"> 155 - {{ i "git-commit-horizontal" "w-4 h-4 flex-shrink-0" }} 156 - <div class="flex items-center"> 157 - <span>{{ .Title | description }}</span> 158 - {{ if gt (len .Body) 0 }} 159 - <button 160 - 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" 161 - hx-on:click="document.getElementById('body-{{$round}}-{{.SHA}}').classList.toggle('hidden')" 162 - > 163 - {{ i "ellipsis" "w-3 h-3" }} 164 - </button> 165 - {{ end }} 166 - </div> 167 - </div> 168 - {{ if gt (len .Body) 0 }} 169 - <p id="body-{{$round}}-{{.SHA}}" class="hidden mt-1 text-sm pb-2"> 170 - {{ nl2br .Body }} 171 - </p> 172 - {{ end }} 173 - </div> 174 - {{ end }} 175 - {{ end }} 176 - </div> 177 - <div> 178 - {{ block "pipelineStatus" (list $ .) }} {{ end }} 179 - </div> 180 - </div> 181 - </div> 182 - <div class="relative ml-10 border-l-2 border-gray-200 dark:border-gray-700"> 183 - {{ range $cidx, $c := .Comments }} 184 - <div id="comment-{{$c.ID}}" class="flex gap-2 -ml-4 py-4 w-full mx-auto"> 185 - <!-- left column: profile picture --> 186 - <div class="flex-shrink-0"> 187 - <img 188 - src="{{ tinyAvatar $c.OwnerDid }}" 189 - alt="" 190 - class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900" 191 - /> 192 - </div> 193 - <!-- right column: name and body in two rows --> 194 - <div class="flex-1 min-w-0"> 195 - <!-- Row 1: Author and timestamp --> 196 - <div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1"> 197 - <span>{{ resolve $c.OwnerDid }}</span> 198 - <span class="before:content-['·']"></span> 199 - <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> 200 - </div> 201 - <!-- Row 2: Body text --> 202 - <div class="prose dark:prose-invert mt-1"> 203 - {{ $c.Body | markdown }} 204 - </div> 205 - </div> 206 - </div> 207 - {{ end }} 208 - </div> 209 - {{ end }} 210 - {{ if eq $lastIdx .RoundNumber }} 211 - {{ block "mergeStatus" $ }} {{ end }} 212 - {{ block "resubmitStatus" $ }} {{ end }} 213 - {{ end }} 214 - {{ if $.LoggedInUser }} 215 - {{ template "repo/pulls/fragments/pullActions" 216 - (dict 217 - "LoggedInUser" $.LoggedInUser 218 - "Pull" $.Pull 219 - "RepoInfo" $.RepoInfo 220 - "RoundNumber" .RoundNumber 221 - "MergeCheck" $.MergeCheck 222 - "ResubmitCheck" $.ResubmitCheck 223 - "BranchDeleteStatus" $.BranchDeleteStatus 224 - "Stack" $.Stack) }} 225 - {{ else }} 226 - <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"> 227 - <a href="/signup" class="btn-create py-0 hover:no-underline hover:text-white flex items-center gap-2"> 228 - sign up 229 - </a> 230 - <span class="text-gray-500 dark:text-gray-400">or</span> 231 - <a href="/login" class="underline">login</a> 232 - to add to the discussion 233 - </div> 234 - {{ end }} 85 + {{ define "diffLayout" }} 86 + {{ $diff := index . 0 }} 87 + {{ $opts := index . 1 }} 88 + {{ $root := index . 2 }} 89 + 90 + <div class="flex col-span-full"> 91 + <!-- left panel --> 92 + <div id="files" class="w-0 hidden md:block overflow-hidden sticky top-12 max-h-screen overflow-y-auto pb-12"> 93 + {{ template "repo/fragments/diffChangedFiles" $diff }} 235 94 </div> 236 - {{ end }} 237 - {{ end }} 238 95 239 - {{ define "newComment" }} 240 - {{ $root := index . 0 }} 241 - {{ $submission := index . 1 }} 242 - <form 243 - id="comment-form" 244 - hx-post="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ $submission.RoundNumber }}/comment" 245 - hx-on::after-request="if(event.detail.successful) this.reset()" 246 - > 247 - <div class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-4 px-4 relative w-full"> 248 - <div class="text-sm pb-2 text-gray-500 dark:text-gray-400"> 249 - {{ template "user/fragments/picHandleLink" $root.LoggedInUser.Did }} 250 - </div> 251 - <textarea 252 - id="comment-textarea" 253 - name="body" 254 - class="w-full p-2 rounded border border-gray-200 dark:border-gray-700" 255 - placeholder="Add to the discussion" 256 - rows="8" 257 - ></textarea> 258 - <div id="pull-comment"></div> 96 + <!-- main content --> 97 + <div class="flex-1 min-w-0 sticky top-12 pb-12"> 98 + {{ template "diffFiles" (list $diff $opts) }} 259 99 </div> 260 - {{ template "replyActions" . }} 261 - </form> 100 + 101 + <!-- right panel --> 102 + {{ template "subsPanel" $ }} 103 + </div> 262 104 {{ end }} 263 105 264 - {{ define "replyActions" }} 265 - <div class="flex flex-wrap items-stretch justify-end gap-2 text-gray-500 dark:text-gray-400 text-sm"> 266 - {{ template "cancel" . }} 267 - {{ template "reply" . }} 106 + {{ define "subsPanel" }} 107 + {{ $root := index . 2 }} 108 + <!-- Backdrop overlay - only visible on mobile when open --> 109 + <div class=" 110 + fixed inset-0 bg-black/50 z-50 md:hidden opacity-0 111 + pointer-events-none transition-opacity duration-300 112 + has-[~#subs_details[open]]:opacity-100 has-[~#subs_details[open]]:pointer-events-auto"> 113 + </div> 114 + <!-- right panel - bottom sheet on mobile, side panel on desktop --> 115 + <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"> 116 + <details open id="bottomSheet" class="group rounded-t-2xl md:rounded-none bg-white dark:bg-gray-800 md:bg-transparent drop-shadow-lg md:drop-shadow-none border-t border-gray-200 dark:border-gray-700"> 117 + <summary class="flex gap-4 items-center justify-between rounded-t-2xl md:rounded-none cursor-pointer list-none p-4 md:h-12 bg-white dark:bg-gray-800 drop-shadow-sm border-b border-x border-gray-200 dark:border-gray-700"> 118 + <h2 class="">Review Panel</h2> 119 + <div class="block md:hidden"> 120 + <span class="inline group-open:hidden">{{ i "chevron-up" "size-4" }}</span> 121 + <span class="hidden group-open:inline">{{ i "chevron-down" "size-4" }}</span> 122 + </div> 123 + </summary> 124 + <div class="max-h-[60vh] md:max-h-[calc(100vh-3rem-3rem)] w-full flex flex-col-reverse gap-4 overflow-y-auto dark:bg-gray-900"> 125 + {{ template "submissions" $root }} 126 + </div> 127 + </details> 268 128 </div> 269 129 {{ end }} 270 130 271 - {{ define "cancel" }} 272 - <button 273 - class="btn text-red-500 dark:text-red-400 flex gap-2 items-center group" 274 - hx-get="TODO" 275 - hx-target="TODO" 276 - hx-swap="outerHTML"> 277 - {{ i "x" "size-4" }} 278 - cancel 279 - </button> 131 + {{ define "subsCheckbox" }} 132 + <input type="checkbox" id="subsToggle" class="peer/subs hidden" checked/> 280 133 {{ end }} 281 134 282 - {{ define "reply" }} 283 - <button 284 - id="TODO" 285 - type="submit" 286 - class="btn-create flex items-center gap-2 no-underline hover:no-underline"> 287 - {{ i "reply" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 288 - {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 289 - reply 290 - </button> 135 + {{ define "subsToggle" }} 136 + <style> 137 + /* Mobile: full width */ 138 + #subsToggle:checked ~ div div#subs { 139 + width: 100%; 140 + margin-left: 0; 141 + } 142 + 143 + /* Desktop: 25vw with left margin */ 144 + @media (min-width: 768px) { 145 + #subsToggle:checked ~ div div#subs { 146 + width: 25vw; 147 + margin-left: 1rem; 148 + } 149 + /* Unchecked state */ 150 + #subsToggle:not(:checked) ~ div div#subs { 151 + width: 0; 152 + display: none; 153 + margin-left: 0; 154 + } 155 + } 156 + </style> 157 + <label for="subsToggle" class="hidden md:inline-flex items-center justify-center rounded cursor-pointer p-2"> 158 + {{ i "message-square-more" "size-4" }} 159 + </label> 291 160 {{ end }} 292 161 162 + 293 163 {{ define "submissions" }} 294 164 {{ $lastIdx := sub (len .Pull.Submissions) 1 }} 295 - {{ $targetBranch := .Pull.TargetBranch }} 296 - {{ $repoName := .RepoInfo.FullName }} 297 - {{ range $idx, $item := .Pull.Submissions }} 298 - {{ with $item }} 299 - <details {{ if eq $idx $lastIdx }}open{{ end }}> 300 - <summary id="round-#{{ .RoundNumber }}" class="list-none cursor-pointer"> 301 - <div class="flex flex-wrap gap-2 items-stretch"> 302 - <!-- round number --> 303 - <div class="rounded bg-white dark:bg-gray-800 drop-shadow-sm px-3 py-2 dark:text-white"> 304 - <span class="flex items-center">{{ i "hash" "w-4 h-4" }}{{ .RoundNumber }}</span> 305 - </div> 306 - <!-- round summary --> 307 - <div class="flex-1 rounded drop-shadow-sm bg-white dark:bg-gray-800 p-2 text-gray-500 dark:text-gray-400"> 308 - <span class="gap-1 flex items-center"> 309 - {{ $owner := resolve $.Pull.OwnerDid }} 310 - {{ $re := "re" }} 311 - {{ if eq .RoundNumber 0 }} 312 - {{ $re = "" }} 313 - {{ end }} 314 - <span class="hidden md:inline">{{$re}}submitted</span> 315 - by {{ template "user/fragments/picHandleLink" $.Pull.OwnerDid }} 316 - <span class="select-none before:content-['\00B7']"></span> 317 - <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500" href="#round-#{{ .RoundNumber }}">{{ template "repo/fragments/shortTime" .Created }}</a> 318 - <span class="select-none before:content-['·']"></span> 319 - {{ $s := "s" }} 320 - {{ if eq (len .Comments) 1 }} 321 - {{ $s = "" }} 322 - {{ end }} 323 - {{ len .Comments }} comment{{$s}} 324 - </span> 325 - </div> 165 + {{ range $ridx, $item := reverse .Pull.Submissions }} 166 + {{ $idx := sub $lastIdx $ridx }} 167 + {{ template "submission" (list $item $idx $lastIdx $) }} 168 + {{ end }} 169 + {{ end }} 326 170 327 - <a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group" 328 - hx-boost="true" 329 - href="/{{ $.RepoInfo.FullName }}/pulls/{{ $.Pull.PullId }}/round/{{.RoundNumber}}"> 330 - {{ i "file-diff" "w-4 h-4" }} 331 - <span class="hidden md:inline">diff</span> 332 - {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 333 - </a> 334 - {{ if ne $idx 0 }} 335 - <a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group" 336 - hx-boost="true" 337 - href="/{{ $.RepoInfo.FullName }}/pulls/{{ $.Pull.PullId }}/round/{{.RoundNumber}}/interdiff"> 338 - {{ i "chevrons-left-right-ellipsis" "w-4 h-4 rotate-90" }} 339 - <span class="hidden md:inline">interdiff</span> 340 - {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 341 - </a> 342 - {{ end }} 343 - <span id="interdiff-error-{{.RoundNumber}}"></span> 344 - </div> 345 - </summary> 171 + {{ define "submission" }} 172 + {{ $item := index . 0 }} 173 + {{ $idx := index . 1 }} 174 + {{ $lastIdx := index . 2 }} 175 + {{ $root := index . 3 }} 176 + <div class="rounded border border-gray-200 dark:border-gray-700 w-full shadow-sm bg-gray-50 dark:bg-gray-800/50"> 177 + {{ template "submissionHeader" $ }} 178 + {{ template "submissionComments" $ }} 346 179 347 - {{ if .IsFormatPatch }} 348 - {{ $patches := .AsFormatPatch }} 349 - {{ $round := .RoundNumber }} 350 - <details class="group py-2 md:ml-[3.5rem] text-gray-500 dark:text-gray-400 flex flex-col gap-2 relative text-sm"> 351 - <summary class="py-1 list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400"> 352 - {{ $s := "s" }} 353 - {{ if eq (len $patches) 1 }} 354 - {{ $s = "" }} 355 - {{ end }} 356 - <div class="group-open:hidden flex items-center gap-2 ml-2"> 357 - {{ i "chevrons-up-down" "w-4 h-4" }} expand {{ len $patches }} commit{{$s}} 358 - </div> 359 - <div class="hidden group-open:flex items-center gap-2 ml-2"> 360 - {{ i "chevrons-down-up" "w-4 h-4" }} hide {{ len $patches }} commit{{$s}} 361 - </div> 362 - </summary> 363 - {{ range $patches }} 364 - <div id="commit-{{.SHA}}" class="py-1 px-2 relative w-full md:max-w-3/5 md:w-fit flex flex-col"> 365 - <div class="flex items-center gap-2"> 366 - {{ i "git-commit-horizontal" "w-4 h-4" }} 367 - <div class="text-sm text-gray-500 dark:text-gray-400"> 368 - <!-- attempt to resolve $fullRepo: this is possible only on non-deleted forks and branches --> 369 - {{ $fullRepo := "" }} 370 - {{ if and $.Pull.IsForkBased $.Pull.PullSource.Repo }} 371 - {{ $fullRepo = printf "%s/%s" $owner $.Pull.PullSource.Repo.Name }} 372 - {{ else if $.Pull.IsBranchBased }} 373 - {{ $fullRepo = $.RepoInfo.FullName }} 374 - {{ end }} 180 + {{ if eq $lastIdx $item.RoundNumber }} 181 + {{ block "mergeStatus" $root }} {{ end }} 182 + {{ block "resubmitStatus" $root }} {{ end }} 183 + {{ end }} 375 184 376 - <!-- if $fullRepo was resolved, link to it, otherwise just span without a link --> 377 - {{ if $fullRepo }} 378 - <a href="/{{ $fullRepo }}/commit/{{ .SHA }}" class="font-mono text-gray-500 dark:text-gray-400">{{ slice .SHA 0 8 }}</a> 379 - {{ else }} 380 - <span class="font-mono">{{ slice .SHA 0 8 }}</span> 381 - {{ end }} 382 - </div> 383 - <div class="flex items-center"> 384 - <span>{{ .Title | description }}</span> 385 - {{ if gt (len .Body) 0 }} 386 - <button 387 - 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" 388 - hx-on:click="document.getElementById('body-{{$round}}-{{.SHA}}').classList.toggle('hidden')" 389 - > 390 - {{ i "ellipsis" "w-3 h-3" }} 391 - </button> 392 - {{ end }} 393 - </div> 394 - </div> 395 - {{ if gt (len .Body) 0 }} 396 - <p id="body-{{$round}}-{{.SHA}}" class="hidden mt-1 text-sm pb-2"> 397 - {{ nl2br .Body }} 398 - </p> 399 - {{ end }} 400 - </div> 401 - {{ end }} 402 - </details> 403 - {{ end }} 185 + {{ if $root.LoggedInUser }} 186 + {{ template "repo/pulls/fragments/pullActions" 187 + (dict 188 + "LoggedInUser" $root.LoggedInUser 189 + "Pull" $root.Pull 190 + "RepoInfo" $root.RepoInfo 191 + "RoundNumber" $item.RoundNumber 192 + "MergeCheck" $root.MergeCheck 193 + "ResubmitCheck" $root.ResubmitCheck 194 + "BranchDeleteStatus" $root.BranchDeleteStatus 195 + "Stack" $root.Stack) }} 196 + {{ else }} 197 + {{ template "loginPrompt" $ }} 198 + {{ end }} 199 + </div> 200 + {{ end }} 404 201 202 + {{ define "submissionHeader" }} 203 + {{ $item := index . 0 }} 204 + {{ $lastIdx := index . 2 }} 205 + {{ $root := index . 3 }} 206 + {{ $round := $item.RoundNumber }} 207 + <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"> 208 + <!-- left column: just profile picture --> 209 + <div class="flex-shrink-0"> 210 + <img 211 + src="{{ tinyAvatar $root.Pull.OwnerDid }}" 212 + alt="" 213 + class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900" 214 + /> 215 + </div> 216 + <!-- right column --> 217 + <div class="flex-1 min-w-0 flex flex-col gap-1"> 218 + {{ template "submissionInfo" $ }} 219 + {{ template "submissionCommits" $ }} 220 + {{ template "submissionPipeline" $ }} 221 + {{ if eq $lastIdx $round }} 222 + {{ block "mergeCheck" $root }} {{ end }} 223 + {{ end }} 224 + </div> 225 + </div> 226 + {{ end }} 405 227 406 - <div class="md:pl-[3.5rem] flex flex-col gap-2 mt-2 relative"> 407 - {{ range $cidx, $c := .Comments }} 408 - <div id="comment-{{$c.ID}}" class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-2 px-4 relative w-full"> 409 - {{ if gt $cidx 0 }} 410 - <div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div> 411 - {{ end }} 412 - <div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1"> 413 - {{ template "user/fragments/picHandleLink" $c.OwnerDid }} 414 - <span class="before:content-['·']"></span> 415 - <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> 416 - </div> 417 - <div class="prose dark:prose-invert"> 418 - {{ $c.Body | markdown }} 419 - </div> 420 - </div> 421 - {{ end }} 228 + {{ define "submissionInfo" }} 229 + {{ $item := index . 0 }} 230 + {{ $idx := index . 1 }} 231 + {{ $root := index . 3 }} 232 + {{ $round := $item.RoundNumber }} 233 + <div class="flex gap-2 items-center justify-between mb-1"> 234 + <span class="inline-flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400"> 235 + {{ resolve $root.Pull.OwnerDid }} submitted v{{ $round }} 236 + <span class="select-none before:content-['\00B7']"></span> 237 + <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500" href="#round-#{{ $round }}"> 238 + {{ template "repo/fragments/shortTimeAgo" $item.Created }} 239 + </a> 240 + </span> 241 + {{ if ne $idx 0 }} 242 + <a class="btn-flat flex items-center gap-2 no-underline hover:no-underline text-sm" 243 + hx-boost="true" 244 + href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ $round }}/interdiff"> 245 + {{ i "chevrons-left-right-ellipsis" "w-4 h-4 rotate-90" }} 246 + interdiff 247 + </a> 248 + {{ end }} 249 + </div> 250 + {{ end }} 422 251 423 - {{ block "pipelineStatus" (list $ .) }} {{ end }} 252 + {{ define "submissionCommits" }} 253 + {{ $item := index . 0 }} 254 + {{ $root := index . 3 }} 255 + {{ $round := $item.RoundNumber }} 256 + {{ $patches := $item.AsFormatPatch }} 257 + <details class="group/commit"> 258 + <summary class="list-none cursor-pointer flex items-center gap-2"> 259 + <span>{{ i "git-commit-horizontal" "w-4 h-4" }}</span> 260 + {{ len $patches }} commit{{ if ne (len $patches) 1 }}s{{ end }} 261 + <div class="text-sm text-gray-500 dark:text-gray-400"> 262 + <span class="group-open/commit:hidden inline">expand</span> 263 + <span class="hidden group-open/commit:inline">collapse</span> 264 + </div> 265 + </summary> 266 + {{ range $patches }} 267 + {{ template "submissionCommit" (list . $item $root) }} 268 + {{ end }} 269 + </details> 270 + {{ end }} 424 271 425 - {{ if eq $lastIdx .RoundNumber }} 426 - {{ block "mergeStatus" $ }} {{ end }} 427 - {{ block "resubmitStatus" $ }} {{ end }} 272 + {{ define "submissionCommit" }} 273 + {{ $patch := index . 0 }} 274 + {{ $item := index . 1 }} 275 + {{ $root := index . 2 }} 276 + {{ $round := $item.RoundNumber }} 277 + {{ with $patch }} 278 + <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"> 279 + <div class="flex items-baseline gap-2"> 280 + <div> 281 + <!-- attempt to resolve $fullRepo: this is possible only on non-deleted forks and branches --> 282 + {{ $fullRepo := "" }} 283 + {{ if and $root.Pull.IsForkBased $root.Pull.PullSource.Repo }} 284 + {{ $fullRepo = printf "%s/%s" $root.Pull.OwnerDid $root.Pull.PullSource.Repo.Name }} 285 + {{ else if $root.Pull.IsBranchBased }} 286 + {{ $fullRepo = $root.RepoInfo.FullName }} 428 287 {{ end }} 429 288 430 - {{ if $.LoggedInUser }} 431 - {{ template "repo/pulls/fragments/pullActions" 432 - (dict 433 - "LoggedInUser" $.LoggedInUser 434 - "Pull" $.Pull 435 - "RepoInfo" $.RepoInfo 436 - "RoundNumber" .RoundNumber 437 - "MergeCheck" $.MergeCheck 438 - "ResubmitCheck" $.ResubmitCheck 439 - "BranchDeleteStatus" $.BranchDeleteStatus 440 - "Stack" $.Stack) }} 289 + <!-- if $fullRepo was resolved, link to it, otherwise just span without a link --> 290 + {{ if $fullRepo }} 291 + <a href="/{{ $fullRepo }}/commit/{{ .SHA }}" class="font-mono text-gray-600 dark:text-gray-300">{{ slice .SHA 0 8 }}</a> 441 292 {{ else }} 442 - <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"> 443 - <a href="/signup" class="btn-create py-0 hover:no-underline hover:text-white flex items-center gap-2"> 444 - sign up 445 - </a> 446 - <span class="text-gray-500 dark:text-gray-400">or</span> 447 - <a href="/login" class="underline">login</a> 448 - to add to the discussion 449 - </div> 293 + <span class="font-mono">{{ slice .SHA 0 8 }}</span> 294 + {{ end }} 295 + </div> 296 + 297 + <div> 298 + <span>{{ .Title | description }}</span> 299 + {{ if gt (len .Body) 0 }} 300 + <button 301 + 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" 302 + hx-on:click="document.getElementById('body-{{$round}}-{{.SHA}}').classList.toggle('hidden')" 303 + > 304 + {{ i "ellipsis" "w-3 h-3" }} 305 + </button> 306 + {{ end }} 307 + {{ if gt (len .Body) 0 }} 308 + <p id="body-{{$round}}-{{.SHA}}" class="hidden mt-1 text-sm pb-2">{{ nl2br .Body }}</p> 450 309 {{ end }} 451 310 </div> 311 + </div> 312 + </div> 313 + {{ end }} 314 + {{ end }} 315 + 316 + {{ define "mergeCheck" }} 317 + {{ $isOpen := .Pull.State.IsOpen }} 318 + {{ if and $isOpen .MergeCheck .MergeCheck.Error }} 319 + <div class="flex items-center gap-2"> 320 + {{ i "triangle-alert" "w-4 h-4 text-red-600 dark:text-red-500" }} 321 + {{ .MergeCheck.Error }} 322 + </div> 323 + {{ else if and $isOpen .MergeCheck .MergeCheck.IsConflicted }} 324 + <details class="group/conflict"> 325 + <summary class="flex items-center justify-between cursor-pointer list-none"> 326 + <div class="flex items-center gap-2 "> 327 + {{ i "triangle-alert" "text-red-600 dark:text-red-500 w-4 h-4" }} 328 + <span class="font-medium">merge conflicts detected</span> 329 + <div class="text-sm text-gray-500 dark:text-gray-400"> 330 + <span class="group-open/conflict:hidden inline">expand</span> 331 + <span class="hidden group-open/conflict:inline">collapse</span> 332 + </div> 333 + </div> 334 + </summary> 335 + {{ if gt (len .MergeCheck.Conflicts) 0 }} 336 + <ul class="space-y-1 mt-2"> 337 + {{ range .MergeCheck.Conflicts }} 338 + {{ if .Filename }} 339 + <li class="flex items-center"> 340 + {{ i "file-warning" "inline-flex w-4 h-4 mr-1.5 text-red-600 dark:text-red-500 flex-shrink-0" }} 341 + <span class="font-mono" style="word-break: keep-all; overflow-wrap: break-word;">{{ .Filename }}</span> 342 + </li> 343 + {{ else if .Reason }} 344 + <li class="flex items-center"> 345 + {{ i "file-warning" "w-4 h-4 mr-1.5 text-red-500 dark:text-red-300" }} 346 + <span>{{.Reason}}</span> 347 + </li> 348 + {{ end }} 349 + {{ end }} 350 + </ul> 351 + {{ end }} 452 352 </details> 453 - {{ end }} 353 + {{ else if and $isOpen .MergeCheck }} 354 + <div class="flex items-center gap-2"> 355 + {{ i "check" "w-4 h-4 text-green-600 dark:text-green-500" }} 356 + <span>no conflicts, ready to merge</span> 357 + </div> 454 358 {{ end }} 455 359 {{ end }} 456 360 ··· 478 382 <span class="font-medium">This pull has been deleted (possibly by jj abandon or jj squash)</span> 479 383 </div> 480 384 </div> 481 - {{ else if and .MergeCheck .MergeCheck.Error }} 482 - <div class="bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-6 py-2 relative"> 483 - <div class="flex items-center gap-2 text-red-500 dark:text-red-300"> 484 - {{ i "triangle-alert" "w-4 h-4" }} 485 - <span class="font-medium">{{ .MergeCheck.Error }}</span> 486 - </div> 487 - </div> 488 - {{ else if and .MergeCheck .MergeCheck.IsConflicted }} 489 - <div class="bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-6 py-2 relative"> 490 - <details class="text-red-500 dark:text-red-300 group"> 491 - <summary class="flex items-center justify-between cursor-pointer list-none"> 492 - <div class="flex items-center gap-2 "> 493 - {{ i "triangle-alert" "w-4 h-4" }} 494 - <span class="font-medium">merge conflicts detected</span> 495 - </div> 496 - <div> 497 - <span class="group-open:hidden inline">{{ i "chevrons-up-down" "w-4 h-4" }}</span> 498 - <span class="hidden group-open:inline">{{ i "chevrons-down-up" "w-4 h-4" }}</span> 499 - </div> 500 - </summary> 501 - {{ if gt (len .MergeCheck.Conflicts) 0 }} 502 - <ul class="space-y-1 mt-2"> 503 - {{ range .MergeCheck.Conflicts }} 504 - {{ if .Filename }} 505 - <li class="flex items-center"> 506 - {{ i "file-warning" "inline-flex w-4 h-4 mr-1.5 text-red-500 dark:text-red-300 flex-shrink-0" }} 507 - <span class="font-mono" style="word-break: keep-all; overflow-wrap: break-word;">{{ .Filename }}</span> 508 - </li> 509 - {{ else if .Reason }} 510 - <li class="flex items-center"> 511 - {{ i "file-warning" "w-4 h-4 mr-1.5 text-red-500 dark:text-red-300" }} 512 - <span>{{.Reason}}</span> 513 - </li> 514 - {{ end }} 515 - {{ end }} 516 - </ul> 517 - {{ end }} 518 - </details> 519 - </div> 520 - {{ else if .MergeCheck }} 521 - <div class="bg-green-50 dark:bg-green-900 border border-green-500 rounded drop-shadow-sm px-6 py-2 relative"> 522 - <div class="flex items-center gap-2 text-green-500 dark:text-green-300"> 523 - {{ i "circle-check-big" "w-4 h-4" }} 524 - <span class="font-medium">no conflicts, ready to merge</span> 525 - </div> 526 - </div> 527 385 {{ end }} 528 386 {{ end }} 529 387 ··· 538 396 {{ end }} 539 397 {{ end }} 540 398 541 - {{ define "pipelineStatus" }} 542 - {{ $root := index . 0 }} 543 - {{ $submission := index . 1 }} 544 - {{ $pipeline := index $root.Pipelines $submission.SourceRev }} 399 + {{ define "submissionPipeline" }} 400 + {{ $item := index . 0 }} 401 + {{ $root := index . 3 }} 402 + {{ $pipeline := index $root.Pipelines $item.SourceRev }} 545 403 {{ with $pipeline }} 546 404 {{ $id := .Id }} 547 405 {{ if .Statuses }} 548 - <span>Workflows:</span> 549 - <div class=" 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"> 550 - {{ range $name, $all := .Statuses }} 551 - <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"> 552 - <div 553 - class="flex gap-2 items-center justify-between p-2"> 554 - {{ $lastStatus := $all.Latest }} 555 - {{ $kind := $lastStatus.Status.String }} 406 + <details class="group/pipeline"> 407 + <summary class="cursor-pointer list-none flex items-center gap-2"> 408 + {{ template "repo/pipelines/fragments/pipelineSymbol" (dict "Pipeline" $pipeline "ShortSummary" false) }} 409 + <div class="text-sm text-gray-500 dark:text-gray-400"> 410 + <span class="group-open/pipeline:hidden inline">expand</span> 411 + <span class="hidden group-open/pipeline:inline">collapse</span> 412 + </div> 413 + </summary> 414 + <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"> 415 + {{ range $name, $all := .Statuses }} 416 + <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"> 417 + <div 418 + class="flex gap-2 items-center justify-between p-2"> 419 + {{ $lastStatus := $all.Latest }} 420 + {{ $kind := $lastStatus.Status.String }} 556 421 557 - <div id="left" class="flex items-center gap-2 flex-shrink-0"> 558 - {{ template "repo/pipelines/fragments/workflowSymbol" $all }} 559 - {{ $name }} 560 - </div> 561 - <div id="right" class="flex items-center gap-2 flex-shrink-0"> 562 - <span class="font-bold">{{ $kind }}</span> 563 - {{ if .TimeTaken }} 564 - {{ template "repo/fragments/duration" .TimeTaken }} 565 - {{ else }} 566 - {{ template "repo/fragments/shortTimeAgo" $lastStatus.Created }} 567 - {{ end }} 568 - </div> 422 + <div id="left" class="flex items-center gap-2 flex-shrink-0"> 423 + {{ template "repo/pipelines/fragments/workflowSymbol" $all }} 424 + {{ $name }} 425 + </div> 426 + <div id="right" class="flex items-center gap-2 flex-shrink-0"> 427 + <span class="font-bold">{{ $kind }}</span> 428 + {{ if .TimeTaken }} 429 + {{ template "repo/fragments/duration" .TimeTaken }} 430 + {{ else }} 431 + {{ template "repo/fragments/shortTimeAgo" $lastStatus.Created }} 432 + {{ end }} 433 + </div> 434 + </div> 435 + </a> 436 + {{ end }} 569 437 </div> 570 - </a> 571 - {{ end }} 572 - </div> 438 + </details> 573 439 {{ end }} 574 440 {{ end }} 575 441 {{ end }} 442 + 443 + {{ define "submissionComments" }} 444 + {{ $item := index . 0 }} 445 + <div class="relative ml-10 border-l-2 border-gray-200 dark:border-gray-700"> 446 + {{ range $item.Comments }} 447 + {{ template "submissionComment" . }} 448 + {{ end }} 449 + </div> 450 + {{ end }} 451 + 452 + {{ define "submissionComment" }} 453 + <div id="comment-{{.ID}}" class="flex gap-2 -ml-4 py-4 w-full mx-auto"> 454 + <!-- left column: profile picture --> 455 + <div class="flex-shrink-0"> 456 + <img 457 + src="{{ tinyAvatar .OwnerDid }}" 458 + alt="" 459 + class="rounded-full size-8 mr-1 border-2 border-gray-100 dark:border-gray-900" 460 + /> 461 + </div> 462 + <!-- right column: name and body in two rows --> 463 + <div class="flex-1 min-w-0"> 464 + <!-- Row 1: Author and timestamp --> 465 + <div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1"> 466 + <span>{{ resolve .OwnerDid }}</span> 467 + <span class="before:content-['·']"></span> 468 + <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.ID}}"> 469 + {{ template "repo/fragments/time" .Created }} 470 + </a> 471 + </div> 472 + <!-- Row 2: Body text --> 473 + <div class="prose dark:prose-invert mt-1"> 474 + {{ .Body | markdown }} 475 + </div> 476 + </div> 477 + </div> 478 + {{ end }} 479 + 480 + {{ define "loginPrompt" }} 481 + <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"> 482 + <a href="/signup" class="btn-create py-0 hover:no-underline hover:text-white flex items-center gap-2"> 483 + sign up 484 + </a> 485 + <span class="text-gray-500 dark:text-gray-400">or</span> 486 + <a href="/login" class="underline">login</a> 487 + to add to the discussion 488 + </div> 489 + {{ end }}
+1 -1
appview/pages/templates/repo/pulls/pulls.html
··· 136 136 {{ $pipeline := index $.Pipelines .LatestSha }} 137 137 {{ if and $pipeline $pipeline.Id }} 138 138 <span class="before:content-['·']"></span> 139 - {{ template "repo/pipelines/fragments/pipelineSymbol" $pipeline }} 139 + {{ template "repo/pipelines/fragments/pipelineSymbol" (dict "Pipeline" $pipeline "ShortSummary" true) }} 140 140 {{ end }} 141 141 142 142 {{ $state := .Labels }}
+2 -2
appview/pulls/pulls.go
··· 528 528 529 529 interdiff := patchutil.Interdiff(previousPatch, currentPatch) 530 530 531 - s.pages.RepoPullInterdiffPage(w, pages.RepoPullInterdiffParams{ 531 + fmt.Println(s.pages.RepoPullInterdiffPage(w, pages.RepoPullInterdiffParams{ 532 532 LoggedInUser: s.oauth.GetUser(r), 533 533 RepoInfo: s.repoResolver.GetRepoInfo(r, user), 534 534 Pull: pull, 535 535 Round: roundIdInt, 536 536 Interdiff: interdiff, 537 537 DiffOpts: diffOpts, 538 - }) 538 + })) 539 539 } 540 540 541 541 func (s *Pulls) RepoPullPatchRaw(w http.ResponseWriter, r *http.Request) {
+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
+3 -3
types/diff.go
··· 32 32 FilesChanged int 33 33 } 34 34 35 - func (d *Diff) Stats() DiffStat { 35 + func (d Diff) Stats() DiffStat { 36 36 var stats DiffStat 37 37 for _, f := range d.TextFragments { 38 38 stats.Insertions += f.LinesAdded ··· 75 75 } 76 76 77 77 // used by html elements as a unique ID for hrefs 78 - func (d *Diff) Id() string { 78 + func (d Diff) Id() string { 79 79 if d.IsDelete { 80 80 return d.Name.Old 81 81 } 82 82 return d.Name.New 83 83 } 84 84 85 - func (d *Diff) Split() *SplitDiff { 85 + func (d Diff) Split() *SplitDiff { 86 86 fragments := make([]SplitFragment, len(d.TextFragments)) 87 87 for i, fragment := range d.TextFragments { 88 88 leftLines, rightLines := SeparateLines(&fragment)