cdn

Log | Files | Refs | Activity

commit 463644b6ea09774892d092485f3912deb1986252
parent ae8982c1b3161a9c63a6d8cd876c9c07613bb50d
Author: Amit Dutta <mail@amit.is-a.dev>
Date:   Fri,  4 Sep 2026 16:54:20 +0530

Enhance download ZIP functionality and modals

Refactor ZIP button logic and add error modal handling.
Diffstat:
Mapp/templates/index.html | 182+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
1 file changed, 104 insertions(+), 78 deletions(-)

diff --git a/app/templates/index.html b/app/templates/index.html @@ -614,12 +614,22 @@ <h3>Confirm Download</h3> <p id="zip-modal-text" style="color: var(--text-main); font-size: 0.95rem;"></p> <div class="modal-actions"> - <button class="btn" id="zip-modal-cancel">Cancel</button> + <button type="button" class="btn" id="zip-modal-cancel">Cancel</button> <a href="#" class="btn btn-primary" id="zip-modal-confirm" data-turbo="false">Download (.zip)</a> </div> </div> </div> + <div id="error-modal-overlay" class="modal-overlay" style="display: none;"> + <div class="modal"> + <h3 style="color: var(--danger); margin-top: 0; margin-bottom: 1rem; font-size: 1.2rem; font-weight: 500;">Notice</h3> + <p id="error-modal-text" style="color: var(--text-main); font-size: 0.95rem; margin-bottom: 1.5rem;"></p> + <div class="modal-actions"> + <button type="button" class="btn" id="error-modal-close">Close</button> + </div> + </div> + </div> + <footer> <div> {% if page == 'listing' and items %} @@ -638,57 +648,6 @@ </div> <script> - const zipBtn = document.getElementById('download-zip-btn'); - const zipModalOverlay = document.getElementById('zip-modal-overlay'); - const zipModalCancel = document.getElementById('zip-modal-cancel'); - const zipModalConfirm = document.getElementById('zip-modal-confirm'); - const zipSpinner = document.getElementById('zip-spinner'); - const zipBtnText = document.getElementById('zip-btn-text'); - - if (zipBtn) { - zipBtn.addEventListener('click', async () => { - const path = zipBtn.getAttribute('data-path') || ''; - zipBtn.disabled = true; - zipSpinner.style.display = 'block'; - zipBtnText.textContent = 'Calculating...'; - - try { - const res = await fetch('/api/zip-stats/' + path); - if (!res.ok) { - const err = await res.json(); - throw new Error(err.detail || 'Failed to get directory stats'); - } - const data = await res.json(); - - const folderName = path ? path.split('/').pop() : 'root'; - document.getElementById('zip-modal-text').innerHTML = - `Are you sure you want to download <strong>${folderName}</strong>?<br><br>` + - `This will package <strong>${data.file_count} file(s)</strong> totaling <strong>${data.size_str}</strong> into a .zip.`; - - zipModalConfirm.href = '/api/download-zip/' + path; - zipModalOverlay.style.display = 'flex'; - } catch (err) { - alert('Could not prepare download: ' + err.message); - } finally { - zipBtn.disabled = false; - zipSpinner.style.display = 'none'; - zipBtnText.textContent = 'Download all (.zip)'; - } - }); - } - - if (zipModalCancel) { - zipModalCancel.addEventListener('click', () => { - zipModalOverlay.style.display = 'none'; - }); - } - - if (zipModalConfirm) { - zipModalConfirm.addEventListener('click', () => { - zipModalOverlay.style.display = 'none'; - }); - } - function copyToClipboard(text, btn) { navigator.clipboard.writeText(text).then(() => { if (!btn) return; @@ -715,35 +674,102 @@ }); } - if ('serviceWorker' in navigator) { - window.addEventListener('load', () => { - navigator.serviceWorker.register('/static/sw.js').catch(err => { - console.error('service worker registration failed:', err); - }); + // We wrap global listeners in this check so Turbo doesn't duplicate them on every page render + if (!window.__CDN_App_Initialized) { + window.__CDN_App_Initialized = true; + + // Event delegation guarantees UI buttons work even after Turbo replaces DOM elements + document.addEventListener('click', async (e) => { + + // --- ZIP Button Logic --- + const zipBtn = e.target.closest('#download-zip-btn'); + if (zipBtn) { + e.preventDefault(); + if (zipBtn.disabled) return; + + const path = zipBtn.getAttribute('data-path') || ''; + const zipSpinner = document.getElementById('zip-spinner'); + const zipBtnText = document.getElementById('zip-btn-text'); + const zipModalOverlay = document.getElementById('zip-modal-overlay'); + + zipBtn.disabled = true; + if (zipSpinner) zipSpinner.style.display = 'block'; + if (zipBtnText) zipBtnText.textContent = 'Calculating...'; + + try { + const res = await fetch('/api/zip-stats/' + path); + const data = await res.json(); + + if (!res.ok) { + throw new Error(data.detail || 'Failed to get directory stats'); + } + + const folderName = path ? path.split('/').pop() : 'root'; + document.getElementById('zip-modal-text').innerHTML = + `Are you sure you want to download <strong>${folderName}</strong>?<br><br>` + + `This will package <strong>${data.file_count} file(s)</strong> totaling <strong>${data.size_str}</strong> into a .zip.`; + + document.getElementById('zip-modal-confirm').href = '/api/download-zip/' + path; + zipModalOverlay.style.display = 'flex'; + } catch (err) { + const errorModalOverlay = document.getElementById('error-modal-overlay'); + if (errorModalOverlay) { + document.getElementById('error-modal-text').textContent = err.message; + errorModalOverlay.style.display = 'flex'; + } else { + alert(err.message); // Fallback + } + } finally { + zipBtn.disabled = false; + if (zipSpinner) zipSpinner.style.display = 'none'; + if (zipBtnText) zipBtnText.textContent = 'Download all (.zip)'; + } + } + + // --- Modal Close/Cancel Logic --- + if (e.target.closest('#zip-modal-cancel') || e.target.closest('#zip-modal-confirm')) { + const zipOverlay = document.getElementById('zip-modal-overlay'); + if (zipOverlay) zipOverlay.style.display = 'none'; + } + + if (e.target.closest('#error-modal-close')) { + const errOverlay = document.getElementById('error-modal-overlay'); + if (errOverlay) errOverlay.style.display = 'none'; + } }); - } - document.addEventListener('turbo:load', () => { - if (window.Turbo && window.Turbo.setProgressBarDelay) { - window.Turbo.setProgressBarDelay(0); - } - }, { once: true }); - document.addEventListener('turbo:click', () => document.body.classList.add('turbo-loading')); - document.addEventListener('turbo:submit-start', () => document.body.classList.add('turbo-loading')); - document.addEventListener('turbo:before-render', () => document.body.classList.remove('turbo-loading')); - document.addEventListener('turbo:render', () => document.body.classList.remove('turbo-loading')); - document.addEventListener('turbo:load', () => document.body.classList.remove('turbo-loading')); - - document.addEventListener('keydown', (e) => { - if (e.key !== '/') return; - const tag = (document.activeElement && document.activeElement.tagName) || ''; - if (tag === 'INPUT' || tag === 'TEXTAREA') return; - const filterInput = document.getElementById('filter-input'); - if (filterInput) { - e.preventDefault(); - filterInput.focus(); + if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/static/sw.js').catch(err => { + console.error('service worker registration failed:', err); + }); + }); } - }); + + // Turbo loading state management + document.addEventListener('turbo:load', () => { + if (window.Turbo && window.Turbo.setProgressBarDelay) { + window.Turbo.setProgressBarDelay(0); + } + }, { once: true }); + document.addEventListener('turbo:visit', () => document.body.classList.add('turbo-loading')); + document.addEventListener('turbo:submit-start', () => document.body.classList.add('turbo-loading')); + document.addEventListener('turbo:before-render', () => document.body.classList.remove('turbo-loading')); + document.addEventListener('turbo:render', () => document.body.classList.remove('turbo-loading')); + document.addEventListener('turbo:load', () => document.body.classList.remove('turbo-loading')); + + // Filter quick focus keybind + document.addEventListener('keydown', (e) => { + if (e.key !== '/') return; + const tag = (document.activeElement && document.activeElement.tagName) || ''; + if (tag === 'INPUT' || tag === 'TEXTAREA') return; + const filterInput = document.getElementById('filter-input'); + if (filterInput) { + e.preventDefault(); + filterInput.focus(); + } + }); + } </script> </body> </html>
© notamitgamer • Site Built: 2026-09-05 01:53:16 UTC • git-mirror commit: c170d72 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror