A vibe coded tangled fork which supports pijul.

appview/pages: split diff without needing page refresh

Signed-off-by: Lewis <lewis@tangled.org>

authored by

Lewis and committed by tangled.org c516e353 bc2f8b16

+72 -52
+45 -33
appview/pages/templates/repo/fragments/diff.html
··· 8 8 #filesToggle:not(:checked) ~ div div#resize-files { display: none; } 9 9 </style> 10 10 11 - {{ template "diffTopbar" . }} 12 - {{ block "diffLayout" . }} {{ end }} 13 - {{ template "fragments/resizable" }} 14 - {{ template "activeFileHighlight" }} 15 - {{ template "fragments/line-quote-button" }} 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> 16 18 {{ end }} 17 19 18 20 {{ define "diffTopbar" }} ··· 86 88 {{ $id := index . 0 }} 87 89 {{ $target := index . 1 }} 88 90 {{ $direction := index . 2 }} 89 - <div id="{{ $id }}" 91 + <div id="{{ $id }}" 90 92 data-resizer="vertical" 91 93 data-target="{{ $target }}" 92 94 data-direction="{{ $direction }}" ··· 217 219 </span> 218 220 </label> 219 221 <script> 220 - document.addEventListener('DOMContentLoaded', function() { 222 + (() => { 221 223 const checkbox = document.getElementById('collapseToggle'); 222 - const details = document.querySelectorAll('details[id^="file-"]'); 224 + const diffArea = document.getElementById('diff-area'); 223 225 224 - checkbox.addEventListener('change', function() { 225 - details.forEach(detail => { 226 + checkbox.addEventListener('change', () => { 227 + document.querySelectorAll('details[id^="file-"]').forEach(detail => { 226 228 detail.open = checkbox.checked; 227 229 }); 228 230 }); 229 231 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); 232 + if (window.__collapseToggleHandler) { 233 + diffArea.removeEventListener('toggle', window.__collapseToggleHandler, true); 234 + } 234 235 235 - if (allOpen) { 236 - checkbox.checked = true; 237 - } else if (allClosed) { 238 - checkbox.checked = false; 239 - } 240 - }); 241 - }); 242 - }); 236 + const handler = (e) => { 237 + if (!e.target.matches('details[id^="file-"]')) return; 238 + const details = document.querySelectorAll('details[id^="file-"]'); 239 + const allOpen = Array.from(details).every(d => d.open); 240 + const allClosed = Array.from(details).every(d => !d.open); 241 + 242 + if (allOpen) checkbox.checked = true; 243 + else if (allClosed) checkbox.checked = false; 244 + }; 245 + 246 + window.__collapseToggleHandler = handler; 247 + diffArea.addEventListener('toggle', handler, true); 248 + })(); 243 249 </script> 244 250 {{ end }} 245 251 246 252 {{ define "activeFileHighlight" }} 247 253 <script> 248 - document.addEventListener('DOMContentLoaded', function() { 249 - const diffFiles = document.querySelectorAll('details[id^="file-"]'); 254 + (() => { 255 + if (window.__activeFileScrollHandler) { 256 + document.removeEventListener('scroll', window.__activeFileScrollHandler); 257 + } 258 + 250 259 const filetreeLinks = document.querySelectorAll('.filetree-link'); 251 - if (diffFiles.length === 0 || filetreeLinks.length === 0) return; 260 + if (filetreeLinks.length === 0) return; 252 261 253 262 const linkMap = new Map(); 254 263 filetreeLinks.forEach(link => { ··· 257 266 }); 258 267 259 268 let currentActive = null; 260 - function setActive(link) { 269 + const setActive = (link) => { 261 270 if (link && link !== currentActive) { 262 271 if (currentActive) currentActive.classList.remove('font-bold'); 263 272 link.classList.add('font-bold'); 264 273 currentActive = link; 265 274 } 266 - } 275 + }; 267 276 268 277 filetreeLinks.forEach(link => { 269 278 link.addEventListener('click', () => setActive(link)); ··· 272 281 const topbar = document.querySelector('.sticky.top-0.z-30'); 273 282 const headerHeight = topbar ? topbar.offsetHeight : 0; 274 283 275 - function updateActiveFile() { 276 - for (const file of diffFiles) { 284 + const updateActiveFile = () => { 285 + const diffFiles = document.querySelectorAll('details[id^="file-"]'); 286 + Array.from(diffFiles).some(file => { 277 287 const rect = file.getBoundingClientRect(); 278 288 if (rect.top <= headerHeight && rect.bottom > headerHeight) { 279 289 setActive(linkMap.get(file.id)); 280 - return; 290 + return true; 281 291 } 282 - } 283 - } 292 + return false; 293 + }); 294 + }; 284 295 296 + window.__activeFileScrollHandler = updateActiveFile; 285 297 document.addEventListener('scroll', updateActiveFile); 286 298 updateActiveFile(); 287 - }); 299 + })(); 288 300 </script> 289 301 {{ end }}
+27 -19
appview/pages/templates/repo/fragments/diffOpts.html
··· 4 4 {{ $active = "split" }} 5 5 {{ end }} 6 6 7 - {{ $unified := 8 - (dict 9 - "Key" "unified" 10 - "Value" "unified" 11 - "Icon" "square-split-vertical" 12 - "Meta" "") }} 13 - {{ $split := 14 - (dict 15 - "Key" "split" 16 - "Value" "split" 17 - "Icon" "square-split-horizontal" 18 - "Meta" "") }} 19 - {{ $values := list $unified $split }} 7 + {{ $activeTab := "bg-white dark:bg-gray-700 shadow-sm" }} 8 + {{ $inactiveTab := "bg-gray-100 dark:bg-gray-800 shadow-inner" }} 20 9 21 - {{ template "fragments/tabSelector" 22 - (dict 23 - "Name" "diff" 24 - "Values" $values 25 - "Active" $active) }} 10 + <div class="flex justify-between divide-x divide-gray-200 dark:divide-gray-700 rounded border border-gray-200 dark:border-gray-700 overflow-hidden" 11 + hx-on::before-request="const t=event.target.closest('button'); if(!t||t.classList.contains('shadow-sm'))return event.preventDefault(); this.querySelectorAll('button').forEach(b => { const active=b===t; b.classList.toggle('bg-white',active); b.classList.toggle('dark:bg-gray-700',active); b.classList.toggle('shadow-sm',active); b.classList.toggle('bg-gray-100',!active); b.classList.toggle('dark:bg-gray-800',!active); b.classList.toggle('shadow-inner',!active); })"> 12 + <button 13 + hx-get="?diff=unified" 14 + hx-target="#diff-files" 15 + hx-select="#diff-files" 16 + hx-swap="outerHTML" 17 + hx-push-url="true" 18 + class="group p-2 whitespace-nowrap flex justify-center items-center gap-2 text-sm w-full hover:no-underline text-center {{ if eq $active "unified" }} {{ $activeTab }} {{ else }} {{ $inactiveTab }} {{ end }}"> 19 + {{ i "square-split-vertical" "size-4 inline group-[.htmx-request]:hidden" }} 20 + {{ i "loader-circle" "size-4 animate-spin hidden group-[.htmx-request]:inline" }} 21 + unified 22 + </button> 23 + <button 24 + hx-get="?diff=split" 25 + hx-target="#diff-files" 26 + hx-select="#diff-files" 27 + hx-swap="outerHTML" 28 + hx-push-url="true" 29 + class="group p-2 whitespace-nowrap flex justify-center items-center gap-2 text-sm w-full hover:no-underline text-center {{ if eq $active "split" }} {{ $activeTab }} {{ else }} {{ $inactiveTab }} {{ end }}"> 30 + {{ i "square-split-horizontal" "size-4 inline group-[.htmx-request]:hidden" }} 31 + {{ i "loader-circle" "size-4 animate-spin hidden group-[.htmx-request]:inline" }} 32 + split 33 + </button> 34 + </div> 26 35 {{ end }} 27 -