Monorepo for Tangled
0
fork

Configure Feed

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

appview/pages: pull compose review step template

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

authored by

Lewis and committed by
Tangled
697c6664 dd272812

+518
+518
appview/pages/templates/repo/pulls/fragments/pullStepReview.html
··· 1 + {{ define "repo/pulls/fragments/pullStepReview" }} 2 + <section class="flex flex-col gap-3"> 3 + {{ if not .Comparison }} 4 + <div class="p-4 border border-gray-200 dark:border-gray-700 rounded bg-gray-50 dark:bg-gray-800/30 text-sm text-gray-600 dark:text-gray-400"> 5 + {{ if eq .Source "patch" }} 6 + Paste a patch above to see a comparison. 7 + {{ else }} 8 + Pick a source and target above to see a comparison. 9 + {{ end }} 10 + </div> 11 + {{ else }} 12 + {{ $commits := .Comparison.FormatPatch }} 13 + {{ if $commits }} 14 + <div class="flex flex-col gap-2"> 15 + <div class="flex items-center justify-between gap-3 min-w-0 text-sm text-gray-500 dark:text-gray-400"> 16 + {{ if .IsStacked }} 17 + <span class="inline-flex items-center gap-2 flex-shrink-0 text-gray-900 dark:text-gray-100"> 18 + {{ i "chevrons-down-up" "w-4 h-4" }} 19 + <span>Stack</span> 20 + <span class="text-gray-500 dark:text-gray-400">{{ len $commits }} pull request{{ if ne (len $commits) 1 }}s{{ end }}</span> 21 + </span> 22 + {{ else }} 23 + <span class="flex-shrink-0">{{ len $commits }} commit{{ if ne (len $commits) 1 }}s{{ end }}</span> 24 + {{ end }} 25 + {{ if and .SourceBranch .TargetBranch }} 26 + <span class="inline-flex items-center gap-2 font-mono text-gray-600 dark:text-gray-300 truncate"> 27 + <span>{{ .TargetBranch }}</span> 28 + {{ i "arrow-left-right" "w-4 h-4 flex-shrink-0" }} 29 + <span>{{ .SourceBranch }}</span> 30 + </span> 31 + {{ end }} 32 + </div> 33 + {{ if .IsStacked }} 34 + {{ template "pullReviewStackedCommits" . }} 35 + {{ else }} 36 + {{ template "pullReviewFlatCommits" . }} 37 + {{ end }} 38 + </div> 39 + {{ else if ne .Source "patch" }} 40 + <div class="p-4 border border-gray-200 dark:border-gray-700 rounded bg-gray-50 dark:bg-gray-800/30 text-sm text-gray-600 dark:text-gray-400"> 41 + {{ if and .SourceBranch .TargetBranch (eq .SourceBranch .TargetBranch) }} 42 + Source and target are the same branch, nothing to merge. 43 + {{ else }} 44 + No commits between target and source. Make sure your source branch has commits not on the target. 45 + {{ end }} 46 + </div> 47 + {{ end }} 48 + 49 + {{ if and .Diff (not .IsStacked) }} 50 + {{ template "pullComposeFlatDiff" (list .Diff .DiffOpts) }} 51 + {{ end }} 52 + 53 + {{ if and .IsStacked $commits }} 54 + {{ template "pullSubmitRow" . }} 55 + {{ template "pullStackApplyAllScript" }} 56 + {{ end }} 57 + {{ end }} 58 + </section> 59 + {{ end }} 60 + 61 + {{ define "pullStackApplyAllScript" }} 62 + <script> 63 + (() => { 64 + const checkbox = document.getElementById('stack-apply-all'); 65 + if (!checkbox || checkbox.dataset.applyAllWired === '1') return; 66 + checkbox.dataset.applyAllWired = '1'; 67 + 68 + const update = () => { 69 + const panels = document.querySelectorAll('[data-stack-labels]'); 70 + panels.forEach((panel, idx) => { 71 + if (idx === 0) return; 72 + panel.classList.toggle('hidden', checkbox.checked); 73 + }); 74 + }; 75 + checkbox.addEventListener('change', update); 76 + update(); 77 + })(); 78 + </script> 79 + {{ end }} 80 + 81 + {{ define "pullReviewFlatCommits" }} 82 + {{ $commits := .Comparison.FormatPatch }} 83 + <ul class="flex flex-col gap-2"> 84 + {{ range $commits }} 85 + <li class="border border-gray-200 dark:border-gray-700 rounded bg-white dark:bg-gray-800 px-3 py-2 flex items-center gap-3"> 86 + <span class="text-gray-700 dark:text-gray-300 flex-shrink-0"> 87 + {{ i "git-pull-request-create" "w-4 h-4" }} 88 + </span> 89 + <span class="flex-1 min-w-0 truncate text-sm dark:text-gray-200">{{ .Title }}</span> 90 + {{ template "pullReviewCommitTimestamp" (dict "Patch" .) }} 91 + <span class="-my-2 self-stretch w-px bg-gray-200 dark:bg-gray-700"></span> 92 + {{ template "pullReviewCommitActions" (dict "Patch" . "RepoInfo" $.RepoInfo) }} 93 + </li> 94 + {{ end }} 95 + </ul> 96 + {{ end }} 97 + 98 + {{ define "pullReviewStackedCommits" }} 99 + {{ $root := . }} 100 + {{ $commits := .Comparison.FormatPatch }} 101 + {{ $previewUrl := printf "/%s/pulls/new/preview" .RepoInfo.FullName }} 102 + {{ $hasSidePanel := and $root.LabelDefs $root.RepoInfo.Roles.IsPushAllowed }} 103 + <ul class="flex flex-col gap-2"> 104 + {{ range $idx, $p := $commits }} 105 + {{ $cid := $p.ChangeIdOrEmpty }} 106 + {{ $titleOverride := index $root.StackTitles $cid }} 107 + {{ $bodyOverride := index $root.StackBodies $cid }} 108 + {{ $displayTitle := $p.Title }} 109 + {{ if $titleOverride }}{{ $displayTitle = $titleOverride }}{{ end }} 110 + {{ $bodyValue := $p.Body }} 111 + {{ if $bodyOverride }}{{ $bodyValue = $bodyOverride }}{{ end }} 112 + {{ $perDiff := "" }} 113 + {{ $perOpts := dict }} 114 + {{ if lt $idx (len $root.StackedDiffs) }} 115 + {{ $sd := index $root.StackedDiffs $idx }} 116 + {{ $perDiff = $sd.Diff }} 117 + {{ $perOpts = $sd.Opts }} 118 + {{ end }} 119 + <li> 120 + <details class="group/stacked border border-gray-200 dark:border-gray-700 rounded bg-white dark:bg-gray-800"> 121 + <summary class="p-3 cursor-pointer flex items-center gap-3 list-none"> 122 + <span class="text-gray-700 dark:text-gray-300 flex-shrink-0"> 123 + {{ i "chevron-right" "w-4 h-4 group-open/stacked:hidden inline" }} 124 + {{ i "chevron-down" "w-4 h-4 hidden group-open/stacked:inline" }} 125 + </span> 126 + <span class="-my-3 self-stretch w-px bg-gray-200 dark:bg-gray-700"></span> 127 + <span class="text-gray-700 dark:text-gray-300 flex-shrink-0"> 128 + {{ i "git-pull-request-create" "w-4 h-4" }} 129 + </span> 130 + <span class="flex-1 min-w-0 truncate text-sm dark:text-gray-200">{{ $displayTitle }}</span> 131 + {{ template "pullReviewCommitTimestamp" (dict "Patch" $p) }} 132 + <span class="-my-3 self-stretch w-px bg-gray-200 dark:bg-gray-700"></span> 133 + {{ template "pullReviewCommitActions" (dict "Patch" $p "RepoInfo" $root.RepoInfo) }} 134 + </summary> 135 + {{ if $cid }} 136 + <div class="px-3 pb-3 pt-1 flex flex-col gap-3 border-t border-gray-100 dark:border-gray-700"> 137 + {{ $titleName := printf "stackTitle[%s]" $cid }} 138 + {{ $bodyName := printf "stackBody[%s]" $cid }} 139 + <div class="flex flex-col md:flex-row gap-6"> 140 + <div class="flex-1 min-w-0 flex flex-col gap-3"> 141 + <div class="flex flex-col gap-1"> 142 + <label class="text-xs uppercase tracking-wide text-gray-800 dark:text-gray-200">title</label> 143 + <input 144 + type="text" 145 + name="{{ $titleName }}" 146 + value="{{ $displayTitle }}" 147 + class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600" 148 + placeholder="{{ $p.Title }}" 149 + /> 150 + </div> 151 + {{ template "markdownEditor" (dict 152 + "Id" (printf "stack-body-%s" $cid) 153 + "Name" $bodyName 154 + "Value" $bodyValue 155 + "Rows" 4 156 + "Placeholder" "Describe this pull request. Markdown is supported." 157 + "LabelText" "description" 158 + "PreviewUrl" $previewUrl 159 + ) }} 160 + </div> 161 + {{ if $hasSidePanel }} 162 + <aside data-stack-labels="{{ $cid }}" class="w-full md:w-72 md:flex-shrink-0 flex flex-col gap-4"> 163 + {{ $labelState := $root.LabelState }} 164 + {{ if $root.StackLabelStates }} 165 + {{ $perCid := index $root.StackLabelStates $cid }} 166 + {{ if $perCid }}{{ $labelState = $perCid }}{{ end }} 167 + {{ end }} 168 + {{ $labelCtx := dict "Defs" $root.LabelDefs "State" $labelState "RepoInfo" $root.RepoInfo "Subject" "" "LoggedInUser" $root.LoggedInUser "Prefix" (printf "stackLabel[%s]" $cid) }} 169 + {{ template "editBasicLabels" $labelCtx }} 170 + {{ template "editKvLabels" $labelCtx }} 171 + {{ if eq $idx 0 }} 172 + <label class="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400 self-start"> 173 + <input type="checkbox" id="stack-apply-all" name="applyLabelsToAll" autocomplete="off" /> 174 + Apply labels and assignees to all PRs in stack 175 + </label> 176 + {{ end }} 177 + </aside> 178 + {{ end }} 179 + </div> 180 + {{ if $perDiff }} 181 + <hr class="border-gray-200 dark:border-gray-700 my-1" /> 182 + <div id="stack-diff-{{ $cid }}"> 183 + <input type="hidden" name="stackSplit[{{ $cid }}]" value="{{ if $perOpts.Split }}split{{ else }}unified{{ end }}" /> 184 + {{ template "pullStackedDiffArea" (dict "Diff" $perDiff "DiffOpts" $perOpts "Cid" $cid) }} 185 + </div> 186 + {{ end }} 187 + </div> 188 + {{ else }} 189 + <div class="px-3 pb-3 pt-1 text-sm text-yellow-700 dark:text-yellow-300 border-t border-gray-100 dark:border-gray-700"> 190 + This commit has no <span class="font-mono">Change-Id</span> header and can't be stacked. Set one on the commit and re-push. 191 + </div> 192 + {{ end }} 193 + </details> 194 + </li> 195 + {{ end }} 196 + </ul> 197 + {{ end }} 198 + 199 + {{ define "pullReviewCommitTimestamp" }} 200 + {{ $p := .Patch }} 201 + {{ if or (not $p.AuthorDate.IsZero) $p.SHA }} 202 + <span class="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400 flex-shrink-0"> 203 + {{ if not $p.AuthorDate.IsZero }} 204 + {{ template "repo/fragments/shortTimeAgo" $p.AuthorDate }} 205 + {{ end }} 206 + {{ if $p.SHA }} 207 + <span class="font-mono bg-gray-100 dark:bg-gray-900 text-gray-700 dark:text-gray-300 px-2 py-0.5 rounded">{{ slice $p.SHA 0 8 }}</span> 208 + {{ end }} 209 + </span> 210 + {{ end }} 211 + {{ end }} 212 + 213 + {{ define "pullReviewCommitActions" }} 214 + {{ $p := .Patch }} 215 + {{ $repoInfo := .RepoInfo }} 216 + {{ if $p.SHA }} 217 + <span class="flex items-center gap-1 text-gray-700 dark:text-gray-300 flex-shrink-0"> 218 + <button type="button" 219 + class="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded" 220 + title="Copy SHA" 221 + onclick="event.preventDefault(); event.stopPropagation(); navigator.clipboard.writeText('{{ $p.SHA }}'); this.innerHTML=`{{ i "copy-check" "w-4 h-4" }}`; setTimeout(() => this.innerHTML=`{{ i "copy" "w-4 h-4" }}`, 1500)"> 222 + {{ i "copy" "w-4 h-4" }} 223 + </button> 224 + <a href="/{{ $repoInfo.FullName }}/tree/{{ $p.SHA }}" 225 + onclick="event.stopPropagation()" 226 + class="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded" 227 + title="Browse repository at this commit"> 228 + {{ i "folder-code" "w-4 h-4" }} 229 + </a> 230 + </span> 231 + {{ end }} 232 + {{ end }} 233 + 234 + {{ define "pullStackedDiffArea" }} 235 + {{ $diff := .Diff }} 236 + {{ $opts := .DiffOpts }} 237 + {{ $cid := .Cid }} 238 + {{ $togId := printf "stack-%s-filesToggle" $cid }} 239 + {{ $colId := printf "stack-%s-collapseToggle" $cid }} 240 + {{ $filesId := printf "stack-%s-files" $cid }} 241 + {{ $diffAreaId := printf "stack-%s-diff-area" $cid }} 242 + {{ $filePrefix := printf "stack-%s-file-" $cid }} 243 + {{ $stat := $diff.Stats }} 244 + {{ $count := len $diff.ChangedFiles }} 245 + 246 + <style> 247 + #{{ $togId }}:checked ~ * label[for="{{ $togId }}"] .show-text { display: none; } 248 + #{{ $togId }}:checked ~ * label[for="{{ $togId }}"] .hide-text { display: inline; } 249 + #{{ $togId }}:not(:checked) ~ * label[for="{{ $togId }}"] .hide-text { display: none; } 250 + #{{ $togId }}:checked ~ * div#{{ $filesId }} { width: fit-content; max-width: 15vw; } 251 + #{{ $togId }}:not(:checked) ~ * div#{{ $filesId }} { width: 0; display: none; margin-right: 0; } 252 + </style> 253 + 254 + <input type="checkbox" id="{{ $togId }}" class="hidden"/> 255 + 256 + <div id="{{ $diffAreaId }}"> 257 + <div class="bg-slate-100 dark:bg-gray-900 flex items-center gap-2 h-12 p-2 rounded-t-md border border-b-0 border-gray-200 dark:border-gray-700"> 258 + <label title="Toggle filetree panel" for="{{ $togId }}" class="hidden md:inline-flex items-center justify-center rounded cursor-pointer text-normal font-normal normal-case"> 259 + <span class="show-text">{{ i "panel-left-open" "size-4" }}</span> 260 + <span class="hide-text">{{ i "panel-left-close" "size-4" }}</span> 261 + </label> 262 + 263 + {{ template "repo/fragments/diffStatPill" $stat }} 264 + <span class="text-xs text-gray-600 dark:text-gray-400 hidden md:inline-flex">{{ $count }} changed file{{ if ne $count 1 }}s{{ end }}</span> 265 + 266 + <div class="flex-grow"></div> 267 + 268 + <label title="Expand/Collapse diffs" for="{{ $colId }}" class="btn font-normal normal-case p-2"> 269 + <input type="checkbox" id="{{ $colId }}" class="peer/collapse hidden"/> 270 + <span class="peer-checked/collapse:hidden inline-flex items-center gap-2"> 271 + {{ i "fold-vertical" "w-4 h-4" }} 272 + <span class="hidden md:inline">Expand all</span> 273 + </span> 274 + <span class="peer-checked/collapse:inline-flex hidden flex items-center gap-2"> 275 + {{ i "unfold-vertical" "w-4 h-4" }} 276 + <span class="hidden md:inline">Collapse all</span> 277 + </span> 278 + </label> 279 + 280 + {{ template "repo/fragments/diffOpts" $opts }} 281 + </div> 282 + 283 + <div class="flex border border-gray-200 dark:border-gray-700 rounded-b-md"> 284 + <div id="{{ $filesId }}" class="hidden md:block overflow-hidden max-h-[60vh] overflow-y-auto border-r border-gray-200 dark:border-gray-700"> 285 + <section class="overflow-x-auto text-sm px-3 py-2 w-full mx-auto"> 286 + {{ template "repo/fragments/fileTreePrefixed" (dict "Tree" $diff.FileTree "Prefix" $filePrefix) }} 287 + </section> 288 + </div> 289 + 290 + <div class="flex-1 min-w-0 p-2"> 291 + <div class="flex flex-col gap-2"> 292 + {{ if eq $count 0 }} 293 + <div class="text-center text-gray-500 dark:text-gray-400 py-8"> 294 + <p>No differences found.</p> 295 + </div> 296 + {{ else if le $count 5 }} 297 + {{ range $idx, $file := $diff.ChangedFiles }} 298 + {{ template "stackedDiffFile" (dict "Idx" $idx "File" $file "IsSplit" $opts.Split "Prefix" $filePrefix) }} 299 + {{ end }} 300 + {{ else }} 301 + {{ range $idx, $file := slice $diff.ChangedFiles 0 5 }} 302 + {{ template "stackedDiffFile" (dict "Idx" $idx "File" $file "IsSplit" $opts.Split "Prefix" $filePrefix) }} 303 + {{ end }} 304 + {{ $remaining := sub $count 5 }} 305 + <details class="group/showmore"> 306 + <summary class="cursor-pointer text-sm text-gray-700 dark:text-gray-300 py-2 px-1 flex items-center gap-2 select-none hover:text-gray-900 dark:hover:text-gray-100 list-none"> 307 + <span class="group-open/showmore:hidden inline-flex items-center gap-2"> 308 + {{ i "chevron-right" "w-4 h-4" }} 309 + Show {{ $remaining }} more file{{ if ne $remaining 1 }}s{{ end }} 310 + </span> 311 + <span class="hidden group-open/showmore:inline-flex items-center gap-2"> 312 + {{ i "chevron-down" "w-4 h-4" }} 313 + Hide {{ $remaining }} file{{ if ne $remaining 1 }}s{{ end }} 314 + </span> 315 + </summary> 316 + <div class="flex flex-col gap-2 mt-2"> 317 + {{ range $idx, $file := slice $diff.ChangedFiles 5 }} 318 + {{ template "stackedDiffFile" (dict "Idx" (add $idx 5) "File" $file "IsSplit" $opts.Split "Prefix" $filePrefix) }} 319 + {{ end }} 320 + </div> 321 + </details> 322 + {{ end }} 323 + </div> 324 + </div> 325 + </div> 326 + </div> 327 + 328 + <script> 329 + (() => { 330 + const cb = document.getElementById('{{ $colId }}'); 331 + const area = document.getElementById('{{ $diffAreaId }}'); 332 + if (!cb || !area) return; 333 + const all = () => area.querySelectorAll('details[id^="{{ $filePrefix }}"]'); 334 + cb.addEventListener('change', () => { 335 + all().forEach(d => { d.open = cb.checked; }); 336 + }); 337 + area.addEventListener('toggle', (e) => { 338 + if (!e.target.matches('details[id^="{{ $filePrefix }}"]')) return; 339 + const dets = Array.from(all()); 340 + const allOpen = dets.every(d => d.open); 341 + const allClosed = dets.every(d => !d.open); 342 + if (allOpen) cb.checked = true; 343 + else if (allClosed) cb.checked = false; 344 + }, true); 345 + })(); 346 + </script> 347 + {{ end }} 348 + 349 + {{ define "stackedDiffFile" }} 350 + {{ $idx := .Idx }} 351 + {{ $file := .File }} 352 + {{ $isSplit := .IsSplit }} 353 + {{ $prefix := .Prefix }} 354 + {{ $isGenerated := false }} 355 + {{ $isDeleted := false }} 356 + {{ with $file }} 357 + {{ $n := .Names }} 358 + {{ $isDeleted = and (eq $n.New "") (ne $n.Old "") }} 359 + {{ if $n.New }} 360 + {{ $isGenerated = isGenerated $n.New }} 361 + {{ else if $n.Old }} 362 + {{ $isGenerated = isGenerated $n.Old }} 363 + {{ end }} 364 + <details id="{{ $prefix }}{{ .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 }}"> 365 + <summary class="list-none cursor-pointer group-open:border-b border-gray-200 dark:border-gray-700"> 366 + <div class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between"> 367 + <div class="p-2 flex gap-2 items-center overflow-x-auto"> 368 + <span class="group-open:hidden inline">{{ i "chevron-right" "w-4 h-4" }}</span> 369 + <span class="hidden group-open:inline">{{ i "chevron-down" "w-4 h-4" }}</span> 370 + {{ template "repo/fragments/diffStatPill" .Stats }} 371 + <div class="flex gap-2 items-center overflow-x-auto"> 372 + {{ if and $n.New $n.Old (ne $n.New $n.Old)}} 373 + {{ $n.Old }} {{ i "arrow-right" "w-4 h-4" }} {{ $n.New }} 374 + {{ else if $n.New }} 375 + {{ $n.New }} 376 + {{ else }} 377 + {{ $n.Old }} 378 + {{ end }} 379 + {{ if $isDeleted }} 380 + <span class="text-gray-400 dark:text-gray-500" title="Deleted files are collapsed by default"> 381 + {{ i "circle-question-mark" "size-4" }} 382 + </span> 383 + {{ else if $isGenerated }} 384 + <span class="text-gray-400 dark:text-gray-500" title="Generated files are collapsed by default"> 385 + {{ i "circle-question-mark" "size-4" }} 386 + </span> 387 + {{ end }} 388 + </div> 389 + </div> 390 + </div> 391 + </summary> 392 + 393 + <div class="transition-all duration-700 ease-in-out"> 394 + {{ $reason := .CanRender }} 395 + {{ if $reason }} 396 + <p class="text-center text-gray-400 dark:text-gray-500 p-4">{{ $reason }}</p> 397 + {{ else }} 398 + {{ if $isSplit }} 399 + {{- template "repo/fragments/splitDiff" .Split -}} 400 + {{ else }} 401 + {{- template "repo/fragments/unifiedDiff" . -}} 402 + {{ end }} 403 + {{- end -}} 404 + </div> 405 + </details> 406 + {{ end }} 407 + {{ end }} 408 + 409 + {{ define "pullComposeFlatDiff" }} 410 + {{ $diff := index . 0 }} 411 + {{ $opts := index . 1 }} 412 + {{ $files := $diff.ChangedFiles }} 413 + {{ $isSplit := $opts.Split }} 414 + {{ $count := len $files }} 415 + {{ $stat := $diff.Stats }} 416 + 417 + <style> 418 + #composeFilesToggle:checked ~ * label[for="composeFilesToggle"] .show-text { display: none; } 419 + #composeFilesToggle:checked ~ * label[for="composeFilesToggle"] .hide-text { display: inline; } 420 + #composeFilesToggle:not(:checked) ~ * label[for="composeFilesToggle"] .hide-text { display: none; } 421 + #composeFilesToggle:checked ~ * div#composeFiles { width: fit-content; max-width: 15vw; } 422 + #composeFilesToggle:not(:checked) ~ * div#composeFiles { width: 0; display: none; margin-right: 0; } 423 + </style> 424 + 425 + <input type="checkbox" id="composeFilesToggle" class="hidden"/> 426 + 427 + <div id="diff-area"> 428 + <div class="bg-slate-100 dark:bg-gray-900 flex items-center gap-2 h-12 p-2 rounded-t-md border border-b-0 border-gray-200 dark:border-gray-700"> 429 + <label title="Toggle filetree panel" for="composeFilesToggle" class="hidden md:inline-flex items-center justify-center rounded cursor-pointer text-normal font-normal normal-case"> 430 + <span class="show-text">{{ i "panel-left-open" "size-4" }}</span> 431 + <span class="hide-text">{{ i "panel-left-close" "size-4" }}</span> 432 + </label> 433 + 434 + {{ template "repo/fragments/diffStatPill" $stat }} 435 + <span class="text-xs text-gray-600 dark:text-gray-400 hidden md:inline-flex">{{ $count }} changed file{{ if ne $count 1 }}s{{ end }}</span> 436 + 437 + <div class="flex-grow"></div> 438 + 439 + <label title="Expand/Collapse diffs" for="composeCollapseToggle" class="btn font-normal normal-case p-2"> 440 + <input type="checkbox" id="composeCollapseToggle" class="peer/collapse hidden"/> 441 + <span class="peer-checked/collapse:hidden inline-flex items-center gap-2"> 442 + {{ i "fold-vertical" "w-4 h-4" }} 443 + <span class="hidden md:inline">Expand all</span> 444 + </span> 445 + <span class="peer-checked/collapse:inline-flex hidden flex items-center gap-2"> 446 + {{ i "unfold-vertical" "w-4 h-4" }} 447 + <span class="hidden md:inline">Collapse all</span> 448 + </span> 449 + </label> 450 + 451 + {{ template "repo/fragments/diffOpts" $opts }} 452 + </div> 453 + 454 + <div class="flex border border-gray-200 dark:border-gray-700 rounded-b-md"> 455 + <div id="composeFiles" class="hidden md:block overflow-hidden max-h-[60vh] overflow-y-auto border-r border-gray-200 dark:border-gray-700"> 456 + <section class="overflow-x-auto text-sm px-3 py-2 w-full mx-auto"> 457 + {{ template "repo/fragments/fileTree" $diff.FileTree }} 458 + </section> 459 + </div> 460 + 461 + <div class="flex-1 min-w-0 p-2"> 462 + <div class="flex flex-col gap-2"> 463 + {{ if eq $count 0 }} 464 + <div class="text-center text-gray-500 dark:text-gray-400 py-8"> 465 + <p>No differences found.</p> 466 + </div> 467 + {{ else if le $count 5 }} 468 + {{ range $idx, $file := $files }} 469 + {{ template "diffFile" (list $idx $file $isSplit true) }} 470 + {{ end }} 471 + {{ else }} 472 + {{ range $idx, $file := slice $files 0 5 }} 473 + {{ template "diffFile" (list $idx $file $isSplit true) }} 474 + {{ end }} 475 + {{ $remaining := sub $count 5 }} 476 + <details class="group/showmore"> 477 + <summary class="cursor-pointer text-sm text-gray-700 dark:text-gray-300 py-2 px-1 flex items-center gap-2 select-none hover:text-gray-900 dark:hover:text-gray-100 list-none"> 478 + <span class="group-open/showmore:hidden inline-flex items-center gap-2"> 479 + {{ i "chevron-right" "w-4 h-4" }} 480 + Show {{ $remaining }} more file{{ if ne $remaining 1 }}s{{ end }} 481 + </span> 482 + <span class="hidden group-open/showmore:inline-flex items-center gap-2"> 483 + {{ i "chevron-down" "w-4 h-4" }} 484 + Hide {{ $remaining }} file{{ if ne $remaining 1 }}s{{ end }} 485 + </span> 486 + </summary> 487 + <div class="flex flex-col gap-2 mt-2"> 488 + {{ range $idx, $file := slice $files 5 }} 489 + {{ template "diffFile" (list (add $idx 5) $file $isSplit true) }} 490 + {{ end }} 491 + </div> 492 + </details> 493 + {{ end }} 494 + </div> 495 + </div> 496 + </div> 497 + </div> 498 + 499 + <script> 500 + (() => { 501 + const checkbox = document.getElementById('composeCollapseToggle'); 502 + const diffArea = document.getElementById('diff-area'); 503 + if (!checkbox || !diffArea) return; 504 + const all = () => diffArea.querySelectorAll('details[id^="file-"]'); 505 + checkbox.addEventListener('change', () => { 506 + all().forEach(d => { d.open = checkbox.checked; }); 507 + }); 508 + diffArea.addEventListener('toggle', (e) => { 509 + if (!e.target.matches('details[id^="file-"]')) return; 510 + const dets = Array.from(all()); 511 + const allOpen = dets.every(d => d.open); 512 + const allClosed = dets.every(d => !d.open); 513 + if (allOpen) checkbox.checked = true; 514 + else if (allClosed) checkbox.checked = false; 515 + }, true); 516 + })(); 517 + </script> 518 + {{ end }}