From 5b0c8e0bf59fbd1a4f4ff31fee356f62cc9e2c72 Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Mon, 7 Sep 2026 00:03:01 +0200 Subject: [PATCH] feat: add drag-and-drop dropzone to the magazine upload form Replaces the plain with the same reader-dropzone component the Book/Source screen already uses (reused CSS, no new styles needed beyond grid/layout adjustments) -- drag a PDF onto the zone or click "Choose file", both paths route through the same hidden input magCreate() already reads via a DataTransfer handoff, so no other logic needed to change. Shows the chosen/dropped filename in place of the default hint text, and resets it after a successful add. Verified live: click-to-choose and a simulated real 'drop' DragEvent both populate the file input correctly, dragover highlighting toggles, no console errors from the app itself. Co-Authored-By: Claude Sonnet 5 --- static/js/magazine.js | 43 ++++++++++++++++++++++++++++++++- static/sections/s-magazine.html | 13 +++++++--- static/style.css | 4 ++- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/static/js/magazine.js b/static/js/magazine.js index 76e7701..2fca30f 100644 --- a/static/js/magazine.js +++ b/static/js/magazine.js @@ -106,12 +106,51 @@ function renderLibrary() { }).join(''); } +// ── Add-magazine dropzone (mirrors reader.js's setupReaderDropzone) ───────── +const MAG_DROPZONE_DEFAULT_NOTE = 'Scanned issues work too — pages without selectable text are read with OCR.'; + +function magShowDroppedFile(file) { + const noteEl = $('mag-dropzone-note'); + if (noteEl) noteEl.textContent = file ? file.name : MAG_DROPZONE_DEFAULT_NOTE; +} + +function setupMagDropzone() { + const zone = $('mag-dropzone'); + const input = $('mag-new-file'); + if (!zone || !input || 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'); + } + }); + 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]; + if (!file) return; + if (file.type !== 'application/pdf' && !/\.pdf$/i.test(file.name)) { + return say('Only PDF files are supported', 'err'); + } + // Route the dropped file through the same hidden magCreate() + // already reads, so drag-and-drop and "Choose file" share one code path. + const dt = new DataTransfer(); + dt.items.add(file); + input.files = dt.files; + magShowDroppedFile(file); + }); + input.addEventListener('change', () => magShowDroppedFile(input.files?.[0])); +} + async function magCreate() { const title = ($('mag-new-title')?.value || '').trim(); const issue = ($('mag-new-issue')?.value || '').trim(); const file = $('mag-new-file')?.files?.[0]; if (!title) return say('Give the magazine a title', 'err'); - if (!file) return say('Choose the PDF', 'err'); + if (!file) return say('Choose or drop the PDF', 'err'); try { const { id } = await api('/api/magazine/docs', { method: 'POST', @@ -123,6 +162,7 @@ async function magCreate() { if ($('mag-new-title')) $('mag-new-title').value = ''; if ($('mag-new-issue')) $('mag-new-issue').value = ''; if ($('mag-new-file')) $('mag-new-file').value = ''; + magShowDroppedFile(null); await magOpen(id); await magIngest(); } catch (e) { @@ -912,6 +952,7 @@ function magInit() { if (!$('mag-library-list')) return; // section not mounted magLoadLibrary(); magLoadVoices(); + setupMagDropzone(); magShow('library'); } diff --git a/static/sections/s-magazine.html b/static/sections/s-magazine.html index 7c03eab..d7d14c9 100644 --- a/static/sections/s-magazine.html +++ b/static/sections/s-magazine.html @@ -17,14 +17,21 @@ -