Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).

appview/pages: split diff without needing page refresh

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

authored by oyster.cafe 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" }} ··· 88 86 {{ $id := index . 0 }} 89 87 {{ $target := index . 1 }} 90 88 {{ $direction := index . 2 }} 91 - <div id="{{ $id }}" 89 + <div id="{{ $id }}" 92 90 data-resizer="vertical" 93 91 data-target="{{ $target }}" 94 92 data-direction="{{ $direction }}" ··· 219 217 </span> 220 218 </label> 221 219 <script> 222 - document.addEventListener('DOMContentLoaded', function() { 220 + (() => { 223 221 const checkbox = document.getElementById('collapseToggle'); 224 - const details = document.querySelectorAll('details[id^="file-"]'); 222 + const diffArea = document.getElementById('diff-area'); 225 223 226 - checkbox.addEventListener('change', function() { 227 - details.forEach(detail => { 224 + checkbox.addEventListener('change', () => { 225 + document.querySelectorAll('details[id^="file-"]').forEach(detail => { 228 226 detail.open = checkbox.checked; 229 227 }); 230 228 }); 231 229 232 - details.forEach(detail => { 233 - detail.addEventListener('toggle', function() { 234 - const allOpen = Array.from(details).every(d => d.open); 235 - const allClosed = Array.from(details).every(d => !d.open); 230 + if (window.__collapseToggleHandler) { 231 + diffArea.removeEventListener('toggle', window.__collapseToggleHandler, true); 232 + } 236 233 237 - if (allOpen) { 238 - checkbox.checked = true; 239 - } else if (allClosed) { 240 - checkbox.checked = false; 241 - } 242 - }); 243 - }); 244 - }); 234 + const handler = (e) => { 235 + if (!e.target.matches('details[id^="file-"]')) return; 236 + const details = document.querySelectorAll('details[id^="file-"]'); 237 + const allOpen = Array.from(details).every(d => d.open); 238 + const allClosed = Array.from(details).every(d => !d.open); 239 + 240 + if (allOpen) checkbox.checked = true; 241 + else if (allClosed) checkbox.checked = false; 242 + }; 243 + 244 + window.__collapseToggleHandler = handler; 245 + diffArea.addEventListener('toggle', handler, true); 246 + })(); 245 247 </script> 246 248 {{ end }} 247 249 248 250 {{ define "activeFileHighlight" }} 249 251 <script> 250 - document.addEventListener('DOMContentLoaded', function() { 251 - const diffFiles = document.querySelectorAll('details[id^="file-"]'); 252 + (() => { 253 + if (window.__activeFileScrollHandler) { 254 + document.removeEventListener('scroll', window.__activeFileScrollHandler); 255 + } 256 + 252 257 const filetreeLinks = document.querySelectorAll('.filetree-link'); 253 - if (diffFiles.length === 0 || filetreeLinks.length === 0) return; 258 + if (filetreeLinks.length === 0) return; 254 259 255 260 const linkMap = new Map(); 256 261 filetreeLinks.forEach(link => { ··· 266 257 }); 267 258 268 259 let currentActive = null; 269 - function setActive(link) { 260 + const setActive = (link) => { 270 261 if (link && link !== currentActive) { 271 262 if (currentActive) currentActive.classList.remove('font-bold'); 272 263 link.classList.add('font-bold'); 273 264 currentActive = link; 274 265 } 275 - } 266 + }; 276 267 277 268 filetreeLinks.forEach(link => { 278 269 link.addEventListener('click', () => setActive(link)); ··· 281 272 const topbar = document.querySelector('.sticky.top-0.z-30'); 282 273 const headerHeight = topbar ? topbar.offsetHeight : 0; 283 274 284 - function updateActiveFile() { 285 - for (const file of diffFiles) { 275 + const updateActiveFile = () => { 276 + const diffFiles = document.querySelectorAll('details[id^="file-"]'); 277 + Array.from(diffFiles).some(file => { 286 278 const rect = file.getBoundingClientRect(); 287 279 if (rect.top <= headerHeight && rect.bottom > headerHeight) { 288 280 setActive(linkMap.get(file.id)); 289 - return; 281 + return true; 290 282 } 291 - } 292 - } 283 + return false; 284 + }); 285 + }; 293 286 287 + window.__activeFileScrollHandler = updateActiveFile; 294 288 document.addEventListener('scroll', updateActiveFile); 295 289 updateActiveFile(); 296 - }); 290 + })(); 297 291 </script> 298 292 {{ 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 -