diff --git a/TODO.md b/TODO.md index 314f1ce..74baa41 100644 --- a/TODO.md +++ b/TODO.md @@ -20,3 +20,6 @@ - [x] Update admin/add.php, admin/edit.php, partage/index.php: swap sortable+file-upload-queue for filepond - [x] Remove file-upload-queue.js and sortable.min.js - [x] Clean up CSS: remove .fq-*, .tfe-file-queue styles, add filepond.css + theme overrides + - [x] Decouple format extras from main file inputs — slot-based HTMX swaps preserve FilePond instances + - [x] Fix initFilePonds → window.XamxamInitFilePonds bug + - [x] Verify backend $_FILES['queue_file'][*] data flow unchanged diff --git a/app/public/assets/js/file-upload-filepond.js b/app/public/assets/js/file-upload-filepond.js index b97194e..5f723d4 100644 --- a/app/public/assets/js/file-upload-filepond.js +++ b/app/public/assets/js/file-upload-filepond.js @@ -113,7 +113,7 @@ * Upgrade .tfe-file-picker inputs to FilePond instances. * Called on page load and after HTMX swaps. */ - function initFilePonds() { + window.XamxamInitFilePonds = function () { document.querySelectorAll(".tfe-file-picker").forEach(function (input) { // Skip already upgraded inputs if (input.dataset.filepondUpgraded) return; @@ -145,18 +145,20 @@ } /** - * Destroy all FilePond instances and restore original inputs. - * Called before HTMX swaps that replace the file block. + * Destroy FilePond instances inside a given container element. + * Used before HTMX swaps to avoid leaks. */ - function destroyFilePonds() { - Object.keys(_ponds).forEach(function (key) { - try { - _ponds[key].destroy(); - } catch (_) { /* ignore */ } - delete _ponds[key]; - }); - // Also catch any stray instances (HTMX may have replaced DOM) - document.querySelectorAll(".tfe-file-picker[data-filepond-upgraded]").forEach(function (input) { + function destroyFilePondsIn(el) { + if (!el) return; + // Find FilePond-upgraded inputs inside this element + el.querySelectorAll(".tfe-file-picker[data-filepond-upgraded]").forEach(function (input) { + // Destroy the FilePond instance if it exists + var id = input.id; + var pond = id ? _ponds[id] : null; + if (pond) { + try { pond.destroy(); } catch (_) {} + delete _ponds[id]; + } delete input.dataset.filepondUpgraded; }); } @@ -164,17 +166,16 @@ // ── HTMX integration ───────────────────────────────────────────────── /** - * Called before HTMX replaces the #format-fichiers-block. - * We must destroy FilePond instances on the soon-to-be-removed DOM nodes - * to avoid leaks and file-state conflicts. + * Before HTMX swaps a slot element that may contain FilePond instances, + * destroy them to avoid leaks and file-state conflicts. */ function onHtmxBeforeSwap(evt) { - // Only care about format-fichiers-block swaps - if (evt.detail.target && ( - evt.detail.target.id === "format-fichiers-block" || - evt.detail.target.closest && evt.detail.target.closest("#format-fichiers-block") - )) { - destroyFilePonds(); + var target = evt.detail.target; + if (!target) return; + var id = target.id || ""; + // Only care about slot elements that may contain FilePond file inputs + if (id === "slot-video" || id === "slot-audio" || id === "annexes-input-block" || id === "format-extras-block") { + destroyFilePondsIn(target); } } @@ -183,26 +184,18 @@ // Hook into HTMX events if htmx is loaded if (window.htmx) { window.htmx.on("htmx:beforeSwap", onHtmxBeforeSwap); + window.htmx.on("htmx:afterSwap", function () { + window.XamxamInitFilePonds(); + }); } // Initialise on DOM ready if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", function () { - initFilePonds(); - // Re-init handles HTMX after-swap - if (window.htmx) { - window.htmx.on("htmx:afterSwap", function () { - initFilePonds(); - }); - } + window.XamxamInitFilePonds(); }); } else { - initFilePonds(); - if (window.htmx) { - window.htmx.on("htmx:afterSwap", function () { - initFilePonds(); - }); - } + window.XamxamInitFilePonds(); } // ── Mark form dirty on FilePond changes (beforeunload guard) ───────── diff --git a/app/public/partage/fichiers-fragment.php b/app/public/partage/fichiers-fragment.php index 6b96d2b..e32552a 100644 --- a/app/public/partage/fichiers-fragment.php +++ b/app/public/partage/fichiers-fragment.php @@ -2,25 +2,17 @@ /** * fichiers-fragment.php (partage & admin) * - * HTMX fragment: returns the combined Format(s) + Fichiers block. - * Called on every format checkbox change so the Fichiers fieldset adapts. + * Returns the combined Format(s) + Fichiers block. * - * Fixed inputs (always present in #format-fichiers-block): - * 1. Image de couverture (optional) — single file, plain input - * 2. Note d'intention (PDF, required) — single file, plain input - * 3. TFE — multi-file, FilePond-powered — client-side, orderable - * 4. Annexes checkbox + FilePond-powered queue — client-side, orderable - * - * Format-specific extra inputs (#format-extras-block): - * - Site web → URL field only - * - Vidéo → PeerTube single upload OR FilePond multi-file upload - * - Audio → PeerTube single upload OR FilePond multi-file upload - * - * File uploads are managed by FilePond (file-upload-filepond.js). - * Each .tfe-file-picker input is upgraded to a FilePond instance. - * storeAsFile:true preserves native multipart form submission; - * server receives files via $_FILES indexed by name attribute - * (e.g. queue_file[tfe][], queue_file[video][], etc.). + * Architecture: + * - Formats checkboxes: static, never swapped. They trigger HTMX swaps + * on individual #slot-siteweb, #slot-video, #slot-audio elements. + * - File inputs (couverture, note d'intention, TFE, annexes): always + * static in the DOM — never destroyed by format toggling. + * - Format-specific extras: each is a standalone HTMX fragment slot. + * When unchecked → empty hidden placeholder. When checked → input + * fields rendered via HTMX. This preserves FilePond instances on + * the main file inputs across format changes. * * Expected POST: * formats[] — array of selected format_type IDs @@ -75,7 +67,7 @@ $hxPost = $adminMode ? '/admin/fichiers-fragment.php' : '/partage/fichiers-fragm $hasAnnexesChecked = !empty($_POST['has_annexes']); ?> - +