fix: magazine dropzone opened the file in the browser instead of accepting the drop

Two real bugs, both confirmed live:

1. dragover's preventDefault() was gated on dataTransfer.types including
   'Files' -- during a real OS file drag, Chromium does not reliably
   populate that list synchronously on every dragover event, so the
   condition failed often enough that preventDefault was silently
   skipped. Per the HTML5 DnD spec, an element only becomes a valid drop
   target once dragover (and, in some browsers, dragenter) has been
   cancelled -- without that, the browser falls through to its native
   "open the dropped file" behaviour, which is exactly what was
   reported. Removed the gate entirely; dragenter is now also handled,
   not just dragover.

2. No page-wide safety net existed, so a drop landing even slightly
   outside the dropzone element bubbled to the browser's default action
   uncontested. Added a window-level dragover/drop preventDefault
   installed once regardless of which section is active.

This also explains the "no hover highlight" symptom: the same
gated-preventDefault bug meant dragover never reliably reached the
class-toggle code either.

Verified live: dragenter/dragover/drop each report defaultPrevented,
the highlight class appears on dragenter and clears on drop, the
dropped file populates the hidden input, and a drop dispatched directly
on <body> (simulating one that misses the zone) is also intercepted by
the new window-level guard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-09-07 00:11:54 +02:00
parent fbb0de8e2c
commit 3c210df8a4

View File

@ -114,23 +114,44 @@ function magShowDroppedFile(file) {
if (noteEl) noteEl.textContent = file ? file.name : MAG_DROPZONE_DEFAULT_NOTE;
}
let _magDropGuardInstalled = false;
function setupMagDropzone() {
const zone = $('mag-dropzone');
const input = $('mag-new-file');
if (!zone || !input || zone.dataset.magDropzoneWired) return;
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';
zone.addEventListener('dragover', (e) => {
if (e.dataTransfer && Array.from(e.dataTransfer.types || []).includes('Files')) {
e.preventDefault();
zone.classList.add('dragover');
}
});
// 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. Not gated on
// dataTransfer.types: during a real OS file drag, Chromium does not
// always populate that list synchronously on every one of these events,
// and gating on it here (confirmed live) silently skipped preventDefault
// often enough that neither the hover highlight nor the drop interception
// ever fired -- the browser fell through to opening the file itself.
const armDropTarget = (e) => { e.preventDefault(); zone.classList.add('dragover'); };
zone.addEventListener('dragenter', armDropTarget);
zone.addEventListener('dragover', armDropTarget);
zone.addEventListener('dragleave', () => zone.classList.remove('dragover'));
zone.addEventListener('drop', (e) => {
if (!(e.dataTransfer && Array.from(e.dataTransfer.types || []).includes('Files'))) return;
e.preventDefault();
zone.classList.remove('dragover');
const file = e.dataTransfer.files?.[0];
const file = e.dataTransfer?.files?.[0];
if (!file) return;
if (file.type !== 'application/pdf' && !/\.pdf$/i.test(file.name)) {
return say('Only PDF files are supported', 'err');