this repo has no description
1{{ define "title" }}
2 {{ .Pull.Title }} · pull #{{ .Pull.PullId }} · {{ .RepoInfo.FullName }}
3{{ end }}
4
5{{ define "extrameta" }}
6 {{ template "repo/pulls/fragments/og" (dict "RepoInfo" .RepoInfo "Pull" .Pull) }}
7{{ end }}
8
9{{ define "mainLayout" }}
10 <div class="px-1 col-span-full flex-grow flex flex-col gap-4">
11 {{ block "contentLayout" . }}
12 {{ block "content" . }}{{ end }}
13 {{ end }}
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>
44{{ end }}
45
46{{ define "repoContentLayout" }}
47 <div class="grid grid-cols-1 md:grid-cols-10 gap-y-2 gap-x-4 w-full">
48 <div class="col-span-1 md:col-span-7">
49 <section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto dark:text-white h-full">
50 {{ block "repoContent" . }}{{ end }}
51 </section>
52 {{ block "repoAfter" . }}{{ end }}
53 </div>
54 <div class="col-span-1 md:col-span-3 flex flex-col gap-6">
55 {{ template "repo/fragments/labelPanel"
56 (dict "RepoInfo" $.RepoInfo
57 "Defs" $.LabelDefs
58 "Subject" $.Pull.AtUri
59 "State" $.Pull.Labels) }}
60 {{ template "repo/fragments/participants" $.Pull.Participants }}
61 {{ template "repo/fragments/backlinks"
62 (dict "RepoInfo" $.RepoInfo
63 "Backlinks" $.Backlinks) }}
64 {{ template "repo/fragments/externalLinkPanel" $.Pull.AtUri }}
65 </div>
66
67 {{ template "repo/fragments/diff" (list .Diff .DiffOpts $) }}
68 </div>
69{{ end }}
70
71{{ define "repoContent" }}
72 {{ template "repo/pulls/fragments/pullHeader" . }}
73 {{ if .Pull.IsStacked }}
74 <div class="mt-8">
75 {{ template "repo/pulls/fragments/pullStack" . }}
76 </div>
77 {{ end }}
78{{ end }}
79
80{{ define "repoAfter" }}
81 <div id="pull-close"></div>
82 <div id="pull-reopen"></div>
83{{ end }}
84
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 }}
94 </div>
95
96 <!-- main content -->
97 <div class="flex-1 min-w-0 sticky top-12 pb-12">
98 {{ template "diffFiles" (list $diff $opts) }}
99 </div>
100
101 <!-- right panel -->
102 {{ template "subsPanel" $ }}
103 </div>
104{{ end }}
105
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>
128 </div>
129{{ end }}
130
131{{ define "subsCheckbox" }}
132 <input type="checkbox" id="subsToggle" class="peer/subs hidden" checked/>
133{{ end }}
134
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>
160{{ end }}
161
162
163{{ define "submissions" }}
164 {{ $lastIdx := sub (len .Pull.Submissions) 1 }}
165 {{ range $ridx, $item := reverse .Pull.Submissions }}
166 {{ $idx := sub $lastIdx $ridx }}
167 {{ template "submission" (list $item $idx $lastIdx $) }}
168 {{ end }}
169{{ end }}
170
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" $ }}
179
180 {{ if eq $lastIdx $item.RoundNumber }}
181 {{ block "mergeStatus" $root }} {{ end }}
182 {{ block "resubmitStatus" $root }} {{ end }}
183 {{ end }}
184
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 }}
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 }}
227
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 }}
251
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 }}
271
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 }}
287 {{ end }}
288
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>
292 {{ else }}
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>
309 {{ end }}
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 }}
352 </details>
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>
358 {{ end }}
359{{ end }}
360
361{{ define "mergeStatus" }}
362 {{ if .Pull.State.IsClosed }}
363 <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">
364 <div class="flex items-center gap-2 text-black dark:text-white">
365 {{ i "ban" "w-4 h-4" }}
366 <span class="font-medium">closed without merging</span
367 >
368 </div>
369 </div>
370 {{ else if .Pull.State.IsMerged }}
371 <div class="bg-purple-50 dark:bg-purple-900 border border-purple-500 rounded drop-shadow-sm px-6 py-2 relative">
372 <div class="flex items-center gap-2 text-purple-500 dark:text-purple-300">
373 {{ i "git-merge" "w-4 h-4" }}
374 <span class="font-medium">pull request successfully merged</span
375 >
376 </div>
377 </div>
378 {{ else if .Pull.State.IsDeleted }}
379 <div class="bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-6 py-2 relative">
380 <div class="flex items-center gap-2 text-red-500 dark:text-red-300">
381 {{ i "git-pull-request-closed" "w-4 h-4" }}
382 <span class="font-medium">This pull has been deleted (possibly by jj abandon or jj squash)</span>
383 </div>
384 </div>
385 {{ end }}
386{{ end }}
387
388{{ define "resubmitStatus" }}
389 {{ if .ResubmitCheck.Yes }}
390 <div class="bg-amber-50 dark:bg-amber-900 border border-amber-500 rounded drop-shadow-sm px-6 py-2 relative">
391 <div class="flex items-center gap-2 text-amber-500 dark:text-amber-300">
392 {{ i "triangle-alert" "w-4 h-4" }}
393 <span class="font-medium">this branch has been updated, consider resubmitting</span>
394 </div>
395 </div>
396 {{ end }}
397{{ end }}
398
399{{ define "submissionPipeline" }}
400 {{ $item := index . 0 }}
401 {{ $root := index . 3 }}
402 {{ $pipeline := index $root.Pipelines $item.SourceRev }}
403 {{ with $pipeline }}
404 {{ $id := .Id }}
405 {{ if .Statuses }}
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 }}
421
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 }}
437 </div>
438 </details>
439 {{ end }}
440 {{ end }}
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 }}