+ = icon('paint-brush-household') ?> + Fichiers temporaires +
+Chargement…
+Chargement…
+diff --git a/TODO.md b/TODO.md index 8d39800..34371d1 100644 --- a/TODO.md +++ b/TODO.md @@ -15,3 +15,5 @@ - [x] Responsive paginator: hide first/last buttons on mobile, smaller touch targets - [x] Compact range counter on mobile: "1–15 / 112" instead of full "1–15 sur 112 résultats" - [x] Remove orientation display from search result card meta +- [x] Add bulk select/delete to tmp cleanup dialog (like admin/index.php pattern) +- [x] Move cleanup UI from modal to dedicated page (like add.php/edit.php) diff --git a/app/public/admin/actions/cleanup-stats-fragment.php b/app/public/admin/actions/cleanup-stats-fragment.php index 0484506..eeedad7 100644 --- a/app/public/admin/actions/cleanup-stats-fragment.php +++ b/app/public/admin/actions/cleanup-stats-fragment.php @@ -41,7 +41,7 @@ elseif ($staleSize > 0) { $staleHuman = $staleSize . ' B'; } $summaryMeta = $staleMeta; if ($staleHuman) $summaryMeta .= ' · ' . $staleHuman; -if ($totalStale === 0 && $totalFiles === 0): ?> +
| Nom | Taille | Âge | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Nom | Taille | Âge | ||||||||||||||
| = htmlspecialchars($f['name']) ?> | = htmlspecialchars($f['human']) ?> | ~= (int)$f['age_minutes'] ?> min | @@ -93,10 +115,11 @@ if ($totalStale === 0 && $totalFiles === 0): ?> Corbeille
| Nom | Taille | Âge | ||
|---|---|---|---|---|
| Nom | Taille | Âge | ||
| = htmlspecialchars($f['name']) ?> | = htmlspecialchars($f['human']) ?> | ~= (int)$f['age_days'] ?> j | diff --git a/app/public/admin/actions/cleanup-tmp.php b/app/public/admin/actions/cleanup-tmp.php index eaae11d..a07611d 100644 --- a/app/public/admin/actions/cleanup-tmp.php +++ b/app/public/admin/actions/cleanup-tmp.php @@ -43,32 +43,42 @@ $details = []; $db = new Database(); $pdo = $db->getPDO(); -// ── Individual deletion mode ──────────────────────────────────────────── +// ── Individual / bulk deletion mode ──────────────────────────────────── $individualFilepond = trim($_POST['filepond_dir'] ?? ''); $individualTrash = trim($_POST['trash_file'] ?? ''); +$bulkFilepond = $_POST['filepond_dirs'] ?? []; +$bulkTrash = $_POST['trash_files'] ?? []; -if ($individualFilepond !== '' || $individualTrash !== '') { - if ($individualFilepond !== '') { - $dirPath = $filepondDir . '/' . basename($individualFilepond); +// Normalize: single values also become arrays for unified handling +$filepondItems = $individualFilepond !== '' ? [$individualFilepond] : (is_array($bulkFilepond) ? $bulkFilepond : []); +$trashItems = $individualTrash !== '' ? [$individualTrash] : (is_array($bulkTrash) ? $bulkTrash : []); + +if (!empty($filepondItems) || !empty($trashItems)) { + foreach ($filepondItems as $item) { + $item = trim($item); + if ($item === '') continue; + $dirPath = $filepondDir . '/' . basename($item); if (is_dir($dirPath) && str_starts_with(realpath($dirPath), realpath($filepondDir))) { rmdirRecursive($dirPath); - $filepondRemoved = 1; - $details[] = "filepond/$individualFilepond: suppression manuelle"; + $filepondRemoved++; + $details[] = "filepond/$item: suppression manuelle"; } else { - $errors[] = 'Dossier introuvable : ' . htmlspecialchars($individualFilepond); + $errors[] = 'Dossier introuvable : ' . htmlspecialchars($item); } } - if ($individualTrash !== '') { - $filePath = $trashDir . '/' . basename($individualTrash); + foreach ($trashItems as $item) { + $item = trim($item); + if ($item === '') continue; + $filePath = $trashDir . '/' . basename($item); if (is_file($filePath) && str_starts_with(realpath($filePath), realpath($trashDir))) { if (@unlink($filePath)) { - $trashRemoved = 1; - $details[] = "_trash/$individualTrash: suppression manuelle"; + $trashRemoved++; + $details[] = "_trash/$item: suppression manuelle"; } else { - $errors[] = 'Impossible de supprimer : ' . htmlspecialchars($individualTrash); + $errors[] = 'Impossible de supprimer : ' . htmlspecialchars($item); } } else { - $errors[] = 'Fichier introuvable : ' . htmlspecialchars($individualTrash); + $errors[] = 'Fichier introuvable : ' . htmlspecialchars($item); } } diff --git a/app/public/admin/cleanup.php b/app/public/admin/cleanup.php new file mode 100644 index 0000000..01a2e05 --- /dev/null +++ b/app/public/admin/cleanup.php @@ -0,0 +1,20 @@ + { + function cleanupToggleAll(src, group) { + const selector = group === 'filepond' + ? 'input[name="filepond_dirs[]"]' + : 'input[name="trash_files[]"]'; + document.querySelectorAll(selector).forEach((cb) => { + cb.checked = src.checked; + }); + cleanupUpdateBulk(); + } + + function cleanupUpdateBulk() { + const bar = document.getElementById('cleanup-bulk-actions'); + const countEl = document.getElementById('cleanup-selected-count'); + if (!bar || !countEl) return; + + const n = document.querySelectorAll( + 'input[name="filepond_dirs[]"]:checked, input[name="trash_files[]"]:checked' + ).length; + countEl.textContent = n; + bar.style.display = n > 0 ? 'flex' : 'none'; + } + + function cleanupBulkDelete() { + const form = document.getElementById('cleanup-bulk-form'); + const container = document.getElementById('cleanup-bulk-checkboxes'); + if (!form || !container) return; + + // Populate hidden inputs from checked checkboxes + container.innerHTML = ''; + document.querySelectorAll( + 'input[name="filepond_dirs[]"]:checked, input[name="trash_files[]"]:checked' + ).forEach((cb) => { + const inp = document.createElement('input'); + inp.type = 'hidden'; + inp.name = cb.name; // "filepond_dirs[]" or "trash_files[]" + inp.value = cb.value; + container.appendChild(inp); + }); + + // Submit via HTMX + htmx.trigger(form, 'submit'); + } + + function reattachListeners() { + document.querySelectorAll( + 'input[name="filepond_dirs[]"], input[name="trash_files[]"]' + ).forEach((cb) => { + cb.addEventListener('change', cleanupUpdateBulk); + }); + cleanupUpdateBulk(); + } + + document.addEventListener('htmx:afterSwap', (evt) => { + // Only reattach if the swap target is our cleanup wrapper + if (evt.detail.target.id === 'tmp-cleanup-stats-wrapper') { + reattachListeners(); + } + }); + + // Export to global scope for onclick handlers in HTML + window.cleanupToggleAll = cleanupToggleAll; + window.cleanupUpdateBulk = cleanupUpdateBulk; + window.cleanupBulkDelete = cleanupBulkDelete; +})(); diff --git a/app/public/assets/js/app/admin-entry.js b/app/public/assets/js/app/admin-entry.js index ccadb85..6519409 100644 --- a/app/public/assets/js/app/admin-entry.js +++ b/app/public/assets/js/app/admin-entry.js @@ -16,6 +16,7 @@ import "./htmx-global-setup.js"; // Admin features import "./admin-index-bulk.js"; +import "./admin-cleanup-bulk.js"; import "./admin-tags.js"; import "./admin-contenus-langues.js"; import "./admin-contenus-motscles.js"; diff --git a/app/templates/admin/cleanup.php b/app/templates/admin/cleanup.php new file mode 100644 index 0000000..0fd8742 --- /dev/null +++ b/app/templates/admin/cleanup.php @@ -0,0 +1,38 @@ +