This repository has no description
1{{ define "repo/fragments/diff" }}
2 <style>
3 #filesToggle:checked ~ div label[for="filesToggle"] .show-text { display: none; }
4 #filesToggle:checked ~ div label[for="filesToggle"] .hide-text { display: inline; }
5 #filesToggle:not(:checked) ~ div label[for="filesToggle"] .hide-text { display: none; }
6 #filesToggle:checked ~ div div#files { width: fit-content; max-width: 15vw; }
7 #filesToggle:not(:checked) ~ div div#files { width: 0; display: none; margin-right: 0; }
8 #filesToggle:not(:checked) ~ div div#resize-files { display: none; }
9 </style>
10
11 <div id="diff-area">
12 {{ template "diffTopbar" . }}
13 {{ block "diffLayout" . }} {{ end }}
14 {{ template "fragments/resizable" }}
15 {{ template "activeFileHighlight" }}
16 {{ template "fragments/line-quote-button" }}
17 </div>
18{{ end }}
19
20{{ define "diffTopbar" }}
21 {{ $diff := index . 0 }}
22 {{ $opts := index . 1 }}
23 {{ $root := "" }}
24 {{ if gt (len .) 2 }}
25 {{ $root = index . 2 }}
26 {{ end }}
27
28 {{ block "filesCheckbox" $ }} {{ end }}
29 {{ block "subsCheckbox" $ }} {{ end }}
30
31 <!-- top bar -->
32 <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 {{ if $root }}mt-4{{ end }}">
33 <!-- left panel toggle -->
34 {{ template "filesToggle" . }}
35
36 <!-- stats -->
37 {{ $stat := $diff.Stats }}
38 {{ $count := len $diff.ChangedFiles }}
39 {{ template "repo/fragments/diffStatPill" $stat }}
40 <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>
41
42 {{ if $root }}
43 {{ if $root.IsInterdiff }}
44 <!-- interdiff indicator -->
45 <div class="flex items-center gap-2 before:content-['|'] before:text-gray-300 dark:before:text-gray-600 before:mr-2">
46 <span class="text-xs text-gray-600 dark:text-gray-400 uppercase tracking-wide">Interdiff</span>
47 <a
48 href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ sub $root.ActiveRound 1 }}"
49 class="px-2 py-0.5 bg-white dark:bg-gray-700 rounded font-mono text-xs hover:bg-gray-50 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-600"
50 >
51 #{{ sub $root.ActiveRound 1 }}
52 </a>
53 <span class="text-gray-400 text-xs">→</span>
54 <a
55 href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $root.Pull.PullId }}/round/{{ $root.ActiveRound }}"
56 class="px-2 py-0.5 bg-white dark:bg-gray-700 rounded font-mono text-xs hover:bg-gray-50 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-600"
57 >
58 #{{ $root.ActiveRound }}
59 </a>
60 </div>
61 {{ else if ne $root.ActiveRound nil }}
62 <!-- diff round indicator -->
63 <div class="flex items-center gap-2 before:content-['|'] before:text-gray-300 dark:before:text-gray-600 before:mr-2">
64 <span class="text-xs text-gray-600 dark:text-gray-400 uppercase tracking-wide">Diff</span>
65 <span class="px-2 py-0.5 bg-white dark:bg-gray-700 rounded font-mono text-xs border border-gray-300 dark:border-gray-600">
66 <span class="hidden md:inline">round </span>#{{ $root.ActiveRound }}
67 </span>
68 </div>
69 {{ end }}
70 {{ end }}
71
72 <!-- spacer -->
73 <div class="flex-grow"></div>
74
75 <!-- collapse diffs -->
76 {{ template "collapseToggle" }}
77
78 <!-- diff options -->
79 {{ template "repo/fragments/diffOpts" $opts }}
80
81 <!-- right panel toggle -->
82 {{ block "subsToggle" $ }} {{ end }}
83 </div>
84
85{{ end }}
86
87{{ define "resize-grip" }}
88 {{ $id := index . 0 }}
89 {{ $target := index . 1 }}
90 {{ $direction := index . 2 }}
91 <div id="{{ $id }}"
92 data-resizer="vertical"
93 data-target="{{ $target }}"
94 data-direction="{{ $direction }}"
95 class="resizer-vertical hidden md:flex w-4 sticky top-12 max-h-screen flex-col items-center justify-center group">
96 <div class="w-1 h-16 group-hover:h-24 group-[.resizing]:h-24 transition-all rounded-full bg-gray-400 dark:bg-gray-500 group-hover:bg-gray-500 group-hover:dark:bg-gray-400"></div>
97 </div>
98{{ end }}
99
100{{ define "diffLayout" }}
101 {{ $diff := index . 0 }}
102 {{ $opts := index . 1 }}
103
104 <div class="flex col-span-full flex-grow">
105 <!-- left panel -->
106 <div id="files" class="w-0 hidden md:block overflow-hidden sticky top-12 max-h-screen overflow-y-auto pb-12">
107 <section class="overflow-x-auto text-sm px-6 py-2 border border-gray-200 dark:border-gray-700 w-full mx-auto min-h-full rounded bg-white dark:bg-gray-800 drop-shadow-sm">
108 {{ template "repo/fragments/fileTree" $diff.FileTree }}
109 </section>
110 </div>
111
112 {{ template "resize-grip" (list "resize-files" "files" "before") }}
113
114 <!-- main content -->
115 <div id="diff-files" class="flex-1 min-w-0 sticky top-12 pb-12">
116 {{ template "diffFiles" (list $diff $opts) }}
117 </div>
118
119 </div>
120{{ end }}
121
122{{ define "diffFiles" }}
123 {{ $diff := index . 0 }}
124 {{ $opts := index . 1 }}
125 {{ $files := $diff.ChangedFiles }}
126 {{ $isSplit := $opts.Split }}
127 <div class="flex flex-col gap-4">
128 {{ if eq (len $files) 0 }}
129 <div class="text-center text-gray-500 dark:text-gray-400 py-8">
130 <p>No differences found between the selected revisions.</p>
131 </div>
132 {{ else }}
133 {{ range $idx, $file := $files }}
134 {{ template "diffFile" (list $idx $file $isSplit) }}
135 {{ end }}
136 {{ end }}
137 </div>
138{{ end }}
139
140{{ define "diffFile" }}
141 {{ $idx := index . 0 }}
142 {{ $file := index . 1 }}
143 {{ $isSplit := index . 2 }}
144 {{ $isGenerated := false }}
145 {{ $isDeleted := false }}
146 {{ with $file }}
147 {{ $n := .Names }}
148 {{ $isDeleted = and (eq $n.New "") (ne $n.Old "") }}
149 {{ if $n.New }}
150 {{ $isGenerated = isGenerated $n.New }}
151 {{ else if $n.Old }}
152 {{ $isGenerated = isGenerated $n.Old }}
153 {{ end }}
154 <details {{ if and (not $isGenerated) (not $isDeleted) }}open{{ end }} 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 }}">
155 <summary class="list-none cursor-pointer sticky top-12 group-open:border-b border-gray-200 dark:border-gray-700">
156 <div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between">
157 <div id="left-side-items" class="p-2 flex gap-2 items-center overflow-x-auto">
158 <span class="group-open:hidden inline">{{ i "chevron-right" "w-4 h-4" }}</span>
159 <span class="hidden group-open:inline">{{ i "chevron-down" "w-4 h-4" }}</span>
160 {{ template "repo/fragments/diffStatPill" .Stats }}
161
162 <div class="flex gap-2 items-center overflow-x-auto">
163 {{ if and $n.New $n.Old (ne $n.New $n.Old)}}
164 {{ $n.Old }} {{ i "arrow-right" "w-4 h-4" }} {{ $n.New }}
165 {{ else if $n.New }}
166 {{ $n.New }}
167 {{ else }}
168 {{ $n.Old }}
169 {{ end }}
170 {{ if $isDeleted }}
171 <span class="text-gray-400 dark:text-gray-500" title="Deleted files are collapsed by default">
172 {{ i "circle-question-mark" "size-4" }}
173 </span>
174 {{ else if $isGenerated }}
175 <span class="text-gray-400 dark:text-gray-500" title="Generated files are collapsed by default">
176 {{ i "circle-question-mark" "size-4" }}
177 </span>
178 {{ end }}
179 </div>
180 </div>
181 </div>
182 </summary>
183
184 <div class="transition-all duration-700 ease-in-out">
185 {{ $reason := .CanRender }}
186 {{ if $reason }}
187 <p class="text-center text-gray-400 dark:text-gray-500 p-4">{{ $reason }}</p>
188 {{ else }}
189 {{ if $isSplit }}
190 {{- template "repo/fragments/splitDiff" .Split -}}
191 {{ else }}
192 {{- template "repo/fragments/unifiedDiff" . -}}
193 {{ end }}
194 {{- end -}}
195 </div>
196 </details>
197 {{ end }}
198{{ end }}
199
200{{ define "filesCheckbox" }}
201 <input type="checkbox" id="filesToggle" class="peer/files hidden" checked/>
202{{ end }}
203
204{{ define "filesToggle" }}
205 <label title="Toggle filetree panel" for="filesToggle" class="hidden md:inline-flex items-center justify-center rounded cursor-pointer text-normal font-normal normalcase">
206 <span class="show-text">{{ i "panel-left-open" "size-4" }}</span>
207 <span class="hide-text">{{ i "panel-left-close" "size-4" }}</span>
208 </label>
209{{ end }}
210
211{{ define "collapseToggle" }}
212 <label
213 title="Expand/Collapse diffs"
214 for="collapseToggle"
215 class="btn font-normal normal-case p-2"
216 >
217 <input type="checkbox" id="collapseToggle" class="peer/collapse hidden" checked/>
218 <span class="peer-checked/collapse:hidden inline-flex items-center gap-2">
219 {{ i "fold-vertical" "w-4 h-4" }}
220 <span class="hidden md:inline">expand all</span>
221 </span>
222 <span class="peer-checked/collapse:inline-flex hidden flex items-center gap-2">
223 {{ i "unfold-vertical" "w-4 h-4" }}
224 <span class="hidden md:inline">collapse all</span>
225 </span>
226 </label>
227 <script>
228 (() => {
229 const checkbox = document.getElementById('collapseToggle');
230 const diffArea = document.getElementById('diff-area');
231
232 checkbox.addEventListener('change', () => {
233 document.querySelectorAll('details[id^="file-"]').forEach(detail => {
234 detail.open = checkbox.checked;
235 });
236 });
237
238 if (window.__collapseToggleHandler) {
239 diffArea.removeEventListener('toggle', window.__collapseToggleHandler, true);
240 }
241
242 const handler = (e) => {
243 if (!e.target.matches('details[id^="file-"]')) return;
244 const details = document.querySelectorAll('details[id^="file-"]');
245 const allOpen = Array.from(details).every(d => d.open);
246 const allClosed = Array.from(details).every(d => !d.open);
247
248 if (allOpen) checkbox.checked = true;
249 else if (allClosed) checkbox.checked = false;
250 };
251
252 window.__collapseToggleHandler = handler;
253 diffArea.addEventListener('toggle', handler, true);
254 })();
255 </script>
256{{ end }}
257
258{{ define "activeFileHighlight" }}
259 <script>
260 (() => {
261 if (window.__activeFileScrollHandler) {
262 document.removeEventListener('scroll', window.__activeFileScrollHandler);
263 }
264
265 const filetreeLinks = document.querySelectorAll('.filetree-link');
266 if (filetreeLinks.length === 0) return;
267
268 const linkMap = new Map();
269 filetreeLinks.forEach(link => {
270 const path = link.getAttribute('data-path');
271 if (path) linkMap.set('file-' + path, link);
272 });
273
274 let currentActive = null;
275 const setActive = (link) => {
276 if (link && link !== currentActive) {
277 if (currentActive) currentActive.classList.remove('font-bold');
278 link.classList.add('font-bold');
279 currentActive = link;
280 }
281 };
282
283 filetreeLinks.forEach(link => {
284 link.addEventListener('click', () => setActive(link));
285 });
286
287 const topbar = document.querySelector('.sticky.top-0.z-30');
288 const headerHeight = topbar ? topbar.offsetHeight : 0;
289
290 const updateActiveFile = () => {
291 const diffFiles = document.querySelectorAll('details[id^="file-"]');
292 Array.from(diffFiles).some(file => {
293 const rect = file.getBoundingClientRect();
294 if (rect.top <= headerHeight && rect.bottom > headerHeight) {
295 setActive(linkMap.get(file.id));
296 return true;
297 }
298 return false;
299 });
300 };
301
302 window.__activeFileScrollHandler = updateActiveFile;
303 document.addEventListener('scroll', updateActiveFile);
304 updateActiveFile();
305 })();
306 </script>
307{{ end }}