diff --git a/CHANGELOG.md b/CHANGELOG.md index a367b75..85c9613 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi --- +## [1.26.12] — 2026-09-10 + +### Fixed +- **Page-Wide PDF Drag-and-Drop Interception & Dropzone Navigation Hook.** + - Installed an immediate, eager `window` drag-and-drop safety net (`installGlobalMagDropGuard()`) that prevents dropping a PDF anywhere on the page from opening the PDF in the browser tab. + - Added `window.magOnShow()` navigation hook in `nav.js` and guaranteed `setupMagDropzone()` wires the `#mag-dropzone` target whenever the Magazine section is visited or mounted, stopping `magInit()` from exiting silently if the DOM element was not yet present at script load time. + +--- + ## [1.26.11] — 2026-09-10 ### Added diff --git a/VERSION b/VERSION index fabf663..b74a856 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.26.11 +1.26.12 diff --git a/static/index.html b/static/index.html index c9acda3..70ab96b 100644 --- a/static/index.html +++ b/static/index.html @@ -10,7 +10,7 @@ - + @@ -27,12 +27,12 @@ - - - - - - + + + + + + diff --git a/static/js/magazine.js b/static/js/magazine.js index e7bf8e7..0668fee 100644 --- a/static/js/magazine.js +++ b/static/js/magazine.js @@ -210,25 +210,25 @@ function magShowDroppedFile(file) { } } -let _magDropGuardInstalled = false; +function installGlobalMagDropGuard() { + if (window._magDropGuardInstalled) return; + window._magDropGuardInstalled = true; + window.addEventListener('dragover', (e) => { + e.preventDefault(); + if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'; + }); + window.addEventListener('drop', (e) => { + e.preventDefault(); + }); +} +installGlobalMagDropGuard(); function setupMagDropzone() { + installGlobalMagDropGuard(); const zone = $('mag-dropzone'); const input = $('mag-new-file'); if (!zone || !input) return; - // A page-wide safety net: if a drop ever lands outside the zone (or the - // zone's own handler below fails to intercept it for any reason), the - // browser's native behaviour is to navigate to/open the dropped file -- - // confirmed live as exactly what happened before this existed. Installed - // once regardless of which section is active, since a stray drop can land - // anywhere on the page. - if (!_magDropGuardInstalled) { - _magDropGuardInstalled = true; - window.addEventListener('dragover', (e) => { e.preventDefault(); }); - window.addEventListener('drop', (e) => { e.preventDefault(); }); - } - if (zone.dataset.magDropzoneWired) return; zone.dataset.magDropzoneWired = '1'; @@ -242,12 +242,22 @@ function setupMagDropzone() { // dragenter AND dragover both need preventDefault() -- some browsers only // arm the element as a valid drop target once dragenter itself has been // cancelled, not just the dragover events that follow it. - const armDropTarget = (e) => { e.preventDefault(); zone.classList.add('dragover'); }; + const armDropTarget = (e) => { + e.preventDefault(); + e.stopPropagation(); + if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'; + zone.classList.add('dragover'); + }; zone.addEventListener('dragenter', armDropTarget); zone.addEventListener('dragover', armDropTarget); - zone.addEventListener('dragleave', () => zone.classList.remove('dragover')); + zone.addEventListener('dragleave', (e) => { + e.preventDefault(); + e.stopPropagation(); + zone.classList.remove('dragover'); + }); zone.addEventListener('drop', (e) => { e.preventDefault(); + e.stopPropagation(); zone.classList.remove('dragover'); const file = e.dataTransfer?.files?.[0]; if (!file) return; @@ -1750,12 +1760,21 @@ function syncDrivingPlayButton() { // ── Boot ───────────────────────────────────────────────────────────────────── +async function magOnShow() { + setupMagDropzone(); + setupMagIngestPreviewResize(); + if ($('mag-library-list')) { + magLoadLibrary(); + magLoadVoices(); + } +} + async function magInit() { + setupMagDropzone(); + setupMagIngestPreviewResize(); if (!$('mag-library-list')) return; // section not mounted magLoadLibrary(); magLoadVoices(); - setupMagDropzone(); - setupMagIngestPreviewResize(); let lastId = null; try { lastId = localStorage.getItem(MAG_LAST_DOC_KEY); } catch (_) {} if (lastId) { @@ -1775,6 +1794,7 @@ async function magInit() { magShow('library'); } +root.magOnShow = magOnShow; root.magOpen = magOpen; root.magDelete = magDelete; root.magCreate = magCreate; diff --git a/static/nav.js b/static/nav.js index 883673a..1f8272f 100644 --- a/static/nav.js +++ b/static/nav.js @@ -179,6 +179,7 @@ } if (sectionId === 's-library' && typeof window.libraryRender === 'function') window.libraryRender(window._libraryView || 'books'); if (sectionId === 's-caststudio' && typeof window.studioOnShow === 'function') window.studioOnShow(); + if (sectionId === 's-magazine' && typeof window.magOnShow === 'function') window.magOnShow(); if (typeof window.initCollapsibleCards === 'function') window.initCollapsibleCards(); setStoredSection(sectionId); setSectionHash(sectionId);