/** * file-upload-filepond.js * * Thin FilePond wrapper — replaces the old custom file-upload-queue.js. * * Architecture: * 1. Each is upgraded to a FilePond instance. * 2. FilePond handles drag-to-reorder, thumbnails, remove, validation — zero custom DOM. * 3. storeAsFile: true preserves native multipart form submission. * Server receives files via $_FILES indexed by each input's name attribute * (e.g. queue_file[tfe][], queue_file[video][], etc.). * 4. Validation rules are derived from ALLOWED_BY_TYPE (same as before). */ (function () { "use strict"; // ── Per-queue-type configuration ──────────────────────────────────── var QUEUE_CONFIG = { tfe: { exts: ["jpg","jpeg","png","gif","webp","pdf","mp4","webm","ogv","mov","mp3","ogg","oga","wav","flac","aac","m4a","vtt","zip","tar","gz","tgz"], maxSize: function (f) { return (/\.pdf$/i.test(f.name) ? 100 : 500) * 1024 * 1024; }, multiple: true, }, video: { exts: ["mp4","webm","ogv","mov"], maxSize: function () { return 500 * 1024 * 1024; }, multiple: true, }, audio: { exts: ["mp3","ogg","oga","wav","flac","aac","m4a"], maxSize: function () { return 500 * 1024 * 1024; }, multiple: true, }, annexe: { exts: ["pdf","zip","tar","gz","tgz"], maxSize: function () { return 500 * 1024 * 1024; }, multiple: true, }, cover: { exts: ["jpg","jpeg","png","webp"], maxSize: function () { return 20 * 1024 * 1024; }, multiple: false, }, note_intention: { exts: ["pdf"], maxSize: function () { return 100 * 1024 * 1024; }, multiple: false, }, }; // Map input id → queue type var INPUT_ID_TO_TYPE = { "tfe-files-input": "tfe", "tfe-files-input-2": "tfe", "video-files-input": "video", "audio-files-input": "audio", "annexe-files-input": "annexe", "couverture": "cover", "note_intention": "note_intention", }; function ext(fn) { var m = fn.match(/\.([^./]+)$/); return m ? m[1].toLowerCase() : ""; } // ── FilePond configuration per queue type ───────────────────────────── function buildFilePondOptions(queueType, input) { var cfg = QUEUE_CONFIG[queueType]; if (!cfg) return null; var mimeMap = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp", pdf: "application/pdf", mp4: "video/mp4", webm: "video/webm", ogv: "video/ogg", mov: "video/quicktime", mp3: "audio/mpeg", ogg: "audio/ogg", oga: "audio/ogg", wav: "audio/wav", flac: "audio/flac", aac: "audio/aac", m4a: "audio/mp4", vtt: "text/vtt", zip: "application/zip", tar: "application/x-tar", gz: "application/gzip", tgz: "application/gzip", }; var accepted = cfg.exts.map(function(e) { return mimeMap[e] || ("." + e); }); return { allowMultiple: cfg.multiple, allowReorder: true, storeAsFile: true, labelIdle: "Glissez-déposez vos fichiers ou Parcourir", acceptedFileTypes: accepted, labelFileTypeNotAllowed: "Type de fichier non accepté", fileValidateTypeLabelExpectedTypes: "Types acceptés : " + cfg.exts.map(function(e) { return "." + e; }).join(", "), maxFileSize: function () { return "500MB"; }, beforeAddFile: function (item) { var f = item.file; var max = cfg.maxSize(f); if (f.size > max) { var maxMb = Math.round(max / 1024 / 1024); return { status: "error", main: "Fichier trop volumineux (" + (f.size / 1024 / 1024).toFixed(1) + " MB)", sub: "Maximum : " + maxMb + " MB." }; } return true; }, }; } // ── Instance tracking ──────────────────────────────────────────────── var _ponds = {}; // ── Public API ──────────────────────────────────────────────────────── /** * Upgrade .tfe-file-picker inputs to FilePond instances. * Called on page load and after HTMX swaps. */ window.XamxamInitFilePonds = function () { document.querySelectorAll(".tfe-file-picker").forEach(function (input) { // Skip already upgraded inputs if (input.dataset.filepondUpgraded) return; // Skip if input is inside an existing FilePond root if (input.closest(".filepond--root")) return; var id = input.id; var queueType = INPUT_ID_TO_TYPE[id]; if (!queueType) { // Try data-queue-type on the input itself queueType = input.dataset.queueType || null; } if (!queueType) return; var options = buildFilePondOptions(queueType, input); if (!options) return; // Preserve the input's original name for form submission options.name = input.getAttribute("name") || input.name || ""; var pond = FilePond.create(input, options); input.dataset.filepondUpgraded = "1"; // Track by id for cleanup var key = id || queueType; _ponds[key] = pond; }); } /** * Destroy FilePond instances inside a given container element. * Used before HTMX swaps to avoid leaks. */ 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; }); } // ── HTMX integration ───────────────────────────────────────────────── /** * Before HTMX swaps a slot element that may contain FilePond instances, * destroy them to avoid leaks and file-state conflicts. */ function onHtmxBeforeSwap(evt) { 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); } } // ── Bootstrap ───────────────────────────────────────────────────────── // 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 () { window.XamxamInitFilePonds(); }); } else { window.XamxamInitFilePonds(); } // ── Mark form dirty on FilePond changes (beforeunload guard) ───────── document.addEventListener("FilePond:addfile", function () { window.__xamxamDirty = true; }); // Clean dirty flag on form submit (matches beforeunload-guard.js) document.addEventListener("submit", function (e) { var form = e.target; if (form && form.hasAttribute && form.hasAttribute("data-beforeunload-guard")) { window.__xamxamDirty = false; } }); })();