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