mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-08-10 07:11:18 +02:00
fix: merge corbeille lists + bulk restore button in cleanup
- Single unified 'Corbeille' table replaces the two separate lists (stale/orphelin + restorable). Each row has a Statut column showing '↺ Restaurable' or 'Orphelin', plus a checkbox for bulk ops. - Checkboxes on restorable rows carry data-restorable='1' attribute so the bulk JS can distinguish them. - Bulk actions bar now has both 'Supprimer la sélection' and '↺ Restaurer la sélection' buttons. The restore button only appears when at least one restorable item is checked. - New cleanupBulkRestore() JS function populates a dedicated hidden form and confirms before submitting. Only restorable items are sent. - restore-trash.php refactored to handle both single (trash_file) and bulk (trash_files[]) inputs via a loop, accumulating restored/skipped counts and re-rendering the fragment on HTMX requests.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -13,3 +13,4 @@
|
|||||||
- [x] Add restore-from-corbeille functionality in cleanup page
|
- [x] Add restore-from-corbeille functionality in cleanup page
|
||||||
- [x] Fix cleanup stats: use sidecar JSON for restorability classification (not DB row existence)
|
- [x] Fix cleanup stats: use sidecar JSON for restorability classification (not DB row existence)
|
||||||
- [x] Remove destructive HTMX fragment refresh from relink flow (close modal only, pond.addFile in-place)
|
- [x] Remove destructive HTMX fragment refresh from relink flow (close modal only, pond.addFile in-place)
|
||||||
|
- [x] Merge corbeille lists into single table with status column + bulk restore button
|
||||||
|
|||||||
@@ -32,9 +32,11 @@ $fpMeta = $fpStale . ' dossier' . ($fpStale > 1 ? 's' : '');
|
|||||||
if ($fpStale > 0) {
|
if ($fpStale > 0) {
|
||||||
$fpMeta .= ' · ' . ($d['filepond_stale_human'] ?? '');
|
$fpMeta .= ' · ' . ($d['filepond_stale_human'] ?? '');
|
||||||
}
|
}
|
||||||
$trMeta = $trStale . ' fichier' . ($trStale > 1 ? 's' : '');
|
$trMeta = ($trStale + $trActive) . ' fichier' . (($trStale + $trActive) > 1 ? 's' : '');
|
||||||
if ($trStale > 0) {
|
$trTotalSize = ($d['trash_stale_size'] ?? 0) + ($d['trash_active_size'] ?? 0);
|
||||||
$trMeta .= ' · ' . ($d['trash_stale_human'] ?? '');
|
$trTotalHuman = humanBytes($trTotalSize);
|
||||||
|
if (($trStale + $trActive) > 0) {
|
||||||
|
$trMeta .= ' · ' . $trTotalHuman;
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<?php if ($totalStale === 0 && $totalFiles === 0): ?>
|
<?php if ($totalStale === 0 && $totalFiles === 0): ?>
|
||||||
@@ -45,6 +47,9 @@ if ($trStale > 0) {
|
|||||||
<div id="cleanup-bulk-actions" class="admin-bulk-actions" style="display:none">
|
<div id="cleanup-bulk-actions" class="admin-bulk-actions" style="display:none">
|
||||||
<strong><span id="cleanup-selected-count">0</span> fichier(s) sélectionné(s)</strong>
|
<strong><span id="cleanup-selected-count">0</span> fichier(s) sélectionné(s)</strong>
|
||||||
<div class="admin-bulk-btns">
|
<div class="admin-bulk-btns">
|
||||||
|
<button type="button" class="btn btn--sm" onclick="cleanupBulkRestore()" id="cleanup-bulk-restore-btn" style="display:none">
|
||||||
|
↺ Restaurer la sélection
|
||||||
|
</button>
|
||||||
<button type="button" class="btn btn--sm btn--red" onclick="cleanupBulkDelete()">
|
<button type="button" class="btn btn--sm btn--red" onclick="cleanupBulkDelete()">
|
||||||
<?= icon('trash') ?> Supprimer la sélection
|
<?= icon('trash') ?> Supprimer la sélection
|
||||||
</button>
|
</button>
|
||||||
@@ -62,6 +67,17 @@ if ($trStale > 0) {
|
|||||||
<div id="cleanup-bulk-checkboxes"></div>
|
<div id="cleanup-bulk-checkboxes"></div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<!-- Hidden bulk restore form -->
|
||||||
|
<form id="cleanup-bulk-restore-form" method="post" action="/admin/actions/restore-trash.php"
|
||||||
|
hx-post="/admin/actions/restore-trash.php"
|
||||||
|
hx-target="#tmp-cleanup-stats-wrapper"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
hx-indicator="#tmp-cleanup-stats-wrapper"
|
||||||
|
style="display:none">
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||||
|
<div id="cleanup-bulk-restore-checkboxes"></div>
|
||||||
|
</form>
|
||||||
|
|
||||||
<?php if ($fpStale > 0): ?>
|
<?php if ($fpStale > 0): ?>
|
||||||
<h3 id="tmp-filepond-heading">Téléversements abandonnés <span class="n-meta"><?= htmlspecialchars($fpMeta) ?></span></h3>
|
<h3 id="tmp-filepond-heading">Téléversements abandonnés <span class="n-meta"><?= htmlspecialchars($fpMeta) ?></span></h3>
|
||||||
<table class="n-table" aria-labelledby="tmp-filepond-heading">
|
<table class="n-table" aria-labelledby="tmp-filepond-heading">
|
||||||
@@ -91,60 +107,44 @@ if ($trStale > 0) {
|
|||||||
</table>
|
</table>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($trStale > 0): ?>
|
|
||||||
<h3 id="tmp-trash-heading">Corbeille (à nettoyer) <span class="n-meta"><?= htmlspecialchars($trMeta) ?></span></h3>
|
|
||||||
<table class="n-table" aria-labelledby="tmp-trash-heading">
|
|
||||||
<thead><tr><th width="1%"><input type="checkbox" onchange="cleanupToggleAll(this, 'trash')" title="Tout sélectionner"></th><th>Nom</th><th>Taille</th><th>Âge</th><th width="1%"></th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach ($d['trash_stale_files'] as $f): ?>
|
|
||||||
<tr>
|
|
||||||
<td><input type="checkbox" name="trash_files[]" value="<?= htmlspecialchars($f['name']) ?>" data-cleanup-group="trash" onchange="cleanupUpdateBulk()"></td>
|
|
||||||
<td><strong><?= htmlspecialchars($f['name']) ?></strong></td>
|
|
||||||
<td style="white-space:nowrap"><?= htmlspecialchars($f['human']) ?></td>
|
|
||||||
<td style="white-space:nowrap">~<?= (int)$f['age_days'] ?> j</td>
|
|
||||||
<td style="white-space:nowrap">
|
|
||||||
<button type="button" class="btn btn--sm btn--danger" style="font-size:0.85em;padding:2px var(--space-xs)"
|
|
||||||
hx-post="/admin/actions/cleanup-tmp.php"
|
|
||||||
hx-confirm="Supprimer définitivement ce fichier de la corbeille ?"
|
|
||||||
hx-vals='{"csrf_token":"<?= htmlspecialchars($_SESSION['csrf_token']) ?>","trash_file":"<?= htmlspecialchars($f['name']) ?>"}'
|
|
||||||
hx-target="#tmp-cleanup-stats-wrapper"
|
|
||||||
hx-swap="innerHTML"
|
|
||||||
hx-indicator="#tmp-cleanup-stats-wrapper">
|
|
||||||
<?= icon('trash') ?>
|
|
||||||
Supprimer
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
$trActiveFiles = $d['trash_active_files'] ?? [];
|
// ── Merge stale + active trash files into a single list for display ─────
|
||||||
if (!empty($trActiveFiles)):
|
// stale = orphaned (no sidecar, or too old); active = restorable (has sidecar)
|
||||||
$trActiveMeta = $trActive . ' fichier' . ($trActive > 1 ? 's' : '');
|
$allTrashFiles = [];
|
||||||
if ($trActive > 0) {
|
foreach (($d['trash_active_files'] ?? []) as $f) {
|
||||||
$trActiveMeta .= ' · ' . ($d['trash_active_human'] ?? '');
|
$f['_restorable'] = true;
|
||||||
|
$f['_stale'] = false;
|
||||||
|
$allTrashFiles[] = $f;
|
||||||
}
|
}
|
||||||
?>
|
foreach (($d['trash_stale_files'] ?? []) as $f) {
|
||||||
<h3 id="tmp-trash-restore-heading">Corbeille (restaurable) <span class="n-meta"><?= htmlspecialchars($trActiveMeta) ?></span></h3>
|
$f['_restorable'] = false;
|
||||||
<p style="font-size:0.85em;color:var(--text-secondary);margin:0 0 var(--space-sm)">
|
$f['_stale'] = true;
|
||||||
Fichiers récemment supprimés pour lesquels le TFE associé existe encore. Vous pouvez les restaurer ou les supprimer définitivement.
|
$f['has_sidecar'] = false;
|
||||||
</p>
|
$f['original_name'] = '';
|
||||||
<table class="n-table" aria-labelledby="tmp-trash-restore-heading">
|
$f['file_type'] = '';
|
||||||
<thead><tr><th>Nom</th><th>Taille</th><th>Âge</th><th>Origine</th><th width="1%"></th></tr></thead>
|
$allTrashFiles[] = $f;
|
||||||
|
}
|
||||||
|
$hasTrash = !empty($allTrashFiles);
|
||||||
|
if ($hasTrash): ?>
|
||||||
|
<h3 id="tmp-trash-heading">Corbeille <span class="n-meta"><?= htmlspecialchars($trMeta) ?></span></h3>
|
||||||
|
<table class="n-table" aria-labelledby="tmp-trash-heading">
|
||||||
|
<thead><tr><th width="1%"><input type="checkbox" onchange="cleanupToggleAll(this, 'trash')" title="Tout sélectionner"></th><th>Nom</th><th>Taille</th><th>Âge</th><th>Statut</th><th width="1%"></th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach ($trActiveFiles as $f): ?>
|
<?php foreach ($allTrashFiles as $f): ?>
|
||||||
<tr>
|
<tr<?= $f['_restorable'] ? ' class="trash-restorable-row"' : '' ?>>
|
||||||
|
<td><input type="checkbox" name="trash_files[]" value="<?= htmlspecialchars($f['name']) ?>" data-cleanup-group="trash"<?= $f['_restorable'] ? ' data-restorable="1"' : '' ?> onchange="cleanupUpdateBulk()"></td>
|
||||||
<td><strong><?= htmlspecialchars($f['name']) ?></strong></td>
|
<td><strong><?= htmlspecialchars($f['name']) ?></strong></td>
|
||||||
<td style="white-space:nowrap"><?= htmlspecialchars($f['human']) ?></td>
|
<td style="white-space:nowrap"><?= htmlspecialchars($f['human']) ?></td>
|
||||||
<td style="white-space:nowrap">~<?= (int)$f['age_days'] ?> j</td>
|
<td style="white-space:nowrap">~<?= (int)$f['age_days'] ?> j</td>
|
||||||
<td style="font-size:0.85em;color:var(--text-secondary)">
|
<td>
|
||||||
<?= $f['has_sidecar'] ? htmlspecialchars($f['original_name'] ?? '?') . ' (' . htmlspecialchars($f['file_type'] ?? '?') . ')' : 'métadonnées indisponibles' ?>
|
<?php if ($f['_restorable']): ?>
|
||||||
|
<span style="font-size:0.85em;color:var(--accent-green)" title="<?= htmlspecialchars($f['original_name'] ?? '') ?>">↺ Restaurable</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span style="font-size:0.85em;color:var(--text-secondary)">Orphelin</span>
|
||||||
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
<td style="white-space:nowrap">
|
<td style="white-space:nowrap">
|
||||||
<?php if ($f['has_sidecar']): ?>
|
<?php if ($f['_restorable']): ?>
|
||||||
<button type="button" class="btn btn--sm"
|
<button type="button" class="btn btn--sm"
|
||||||
style="font-size:0.85em;padding:2px var(--space-xs);margin-right:4px"
|
style="font-size:0.85em;padding:2px var(--space-xs);margin-right:4px"
|
||||||
hx-post="/admin/actions/restore-trash.php"
|
hx-post="/admin/actions/restore-trash.php"
|
||||||
|
|||||||
@@ -31,43 +31,52 @@ if (!isset($_POST['csrf_token'], $_SESSION['csrf_token'])
|
|||||||
}
|
}
|
||||||
|
|
||||||
$trashFile = trim($_POST['trash_file'] ?? '');
|
$trashFile = trim($_POST['trash_file'] ?? '');
|
||||||
if ($trashFile === '') {
|
$trashFiles = $_POST['trash_files'] ?? [];
|
||||||
|
if (!is_array($trashFiles)) $trashFiles = [];
|
||||||
|
|
||||||
|
// Normalise: accept both single (trash_file) and bulk (trash_files[])
|
||||||
|
$allTrashFiles = $trashFile !== '' ? [$trashFile] : $trashFiles;
|
||||||
|
$allTrashFiles = array_values(array_filter(array_map('trim', $allTrashFiles), fn($n) => $n !== ''));
|
||||||
|
|
||||||
|
if (empty($allTrashFiles)) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
echo json_encode(['ok' => false, 'error' => 'Nom de fichier invalide.']);
|
echo json_encode(['ok' => false, 'error' => 'Aucun fichier spécifié.']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../../src/Database.php';
|
||||||
|
$db = new Database();
|
||||||
|
|
||||||
$storageRoot = STORAGE_ROOT;
|
$storageRoot = STORAGE_ROOT;
|
||||||
$trashDir = $storageRoot . '/tmp/_trash';
|
$trashDir = $storageRoot . '/tmp/_trash';
|
||||||
$trashPath = $trashDir . '/' . basename($trashFile);
|
|
||||||
|
$restored = 0;
|
||||||
|
$skipped = 0;
|
||||||
|
$errors = [];
|
||||||
|
|
||||||
|
foreach ($allTrashFiles as $trashFile) {
|
||||||
|
$trashFile = basename($trashFile);
|
||||||
|
$trashPath = $trashDir . '/' . $trashFile;
|
||||||
$sidecarPath = $trashPath . '.json';
|
$sidecarPath = $trashPath . '.json';
|
||||||
|
|
||||||
// Validate the trash file is inside the trash directory
|
|
||||||
if (!is_file($trashPath) || !str_starts_with(realpath($trashPath), realpath($trashDir))) {
|
if (!is_file($trashPath) || !str_starts_with(realpath($trashPath), realpath($trashDir))) {
|
||||||
http_response_code(404);
|
$skipped++;
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
$errors[] = "$trashFile : introuvable dans la corbeille.";
|
||||||
echo json_encode(['ok' => false, 'error' => 'Fichier introuvable dans la corbeille.']);
|
continue;
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read sidecar metadata
|
|
||||||
if (!file_exists($sidecarPath)) {
|
if (!file_exists($sidecarPath)) {
|
||||||
http_response_code(400);
|
$skipped++;
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
$errors[] = "$trashFile : métadonnées absentes (trop ancien ?).";
|
||||||
echo json_encode([
|
continue;
|
||||||
'ok' => false,
|
|
||||||
'error' => 'Métadonnées de restauration absentes (fichier trop ancien ?). Utilisez "Relier un fichier existant".',
|
|
||||||
]);
|
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$sidecar = json_decode(file_get_contents($sidecarPath), true);
|
$sidecar = json_decode(file_get_contents($sidecarPath), true);
|
||||||
if (!is_array($sidecar) || empty($sidecar['file_path']) || empty($sidecar['thesis_id'])) {
|
if (!is_array($sidecar) || empty($sidecar['file_path']) || empty($sidecar['thesis_id'])) {
|
||||||
http_response_code(400);
|
$skipped++;
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
$errors[] = "$trashFile : métadonnées corrompues.";
|
||||||
echo json_encode(['ok' => false, 'error' => 'Métadonnées corrompues.']);
|
continue;
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$originalPath = $sidecar['file_path'];
|
$originalPath = $sidecar['file_path'];
|
||||||
@@ -78,69 +87,48 @@ $mimeType = $sidecar['mime_type'] ?? 'application/octet-stream';
|
|||||||
$fileSize = (int)($sidecar['file_size'] ?? 0);
|
$fileSize = (int)($sidecar['file_size'] ?? 0);
|
||||||
$displayLabel = $sidecar['display_label'] ?? null;
|
$displayLabel = $sidecar['display_label'] ?? null;
|
||||||
|
|
||||||
// Verify the thesis still exists
|
|
||||||
require_once __DIR__ . '/../../../src/Database.php';
|
|
||||||
$db = new Database();
|
|
||||||
$thesis = $db->getThesis($thesisId);
|
$thesis = $db->getThesis($thesisId);
|
||||||
if (!$thesis) {
|
if (!$thesis) {
|
||||||
http_response_code(404);
|
$skipped++;
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
$errors[] = "$trashFile : le TFE #$thesisId n'existe plus.";
|
||||||
echo json_encode(['ok' => false, 'error' => 'Le TFE associé n\'existe plus.']);
|
continue;
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check no file already exists at the original path
|
|
||||||
$absOriginal = $storageRoot . '/' . $originalPath;
|
$absOriginal = $storageRoot . '/' . $originalPath;
|
||||||
if (file_exists($absOriginal)) {
|
if (file_exists($absOriginal)) {
|
||||||
// File already restored or a new file replaced it — just clean up the trash
|
|
||||||
@unlink($trashPath);
|
@unlink($trashPath);
|
||||||
@unlink($sidecarPath);
|
@unlink($sidecarPath);
|
||||||
http_response_code(409);
|
$skipped++;
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
$errors[] = "$trashFile : un fichier existe déjà à l'emplacement d'origine.";
|
||||||
echo json_encode([
|
continue;
|
||||||
'ok' => false,
|
|
||||||
'error' => 'Un fichier existe déjà à l\'emplacement d\'origine. Fichier corbeille nettoyé.',
|
|
||||||
]);
|
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure parent directory exists
|
|
||||||
$parentDir = dirname($absOriginal);
|
$parentDir = dirname($absOriginal);
|
||||||
if (!is_dir($parentDir)) {
|
if (!is_dir($parentDir)) {
|
||||||
mkdir($parentDir, 0755, true);
|
mkdir($parentDir, 0755, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move file back from trash to original location
|
|
||||||
if (!rename($trashPath, $absOriginal)) {
|
if (!rename($trashPath, $absOriginal)) {
|
||||||
http_response_code(500);
|
$skipped++;
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
$errors[] = "$trashFile : échec du déplacement.";
|
||||||
echo json_encode(['ok' => false, 'error' => 'Échec du déplacement du fichier.']);
|
continue;
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
chmod($absOriginal, 0644);
|
chmod($absOriginal, 0644);
|
||||||
|
|
||||||
// Delete the sidecar file
|
|
||||||
@unlink($sidecarPath);
|
@unlink($sidecarPath);
|
||||||
|
|
||||||
// Re-insert the thesis_files DB row
|
|
||||||
$db->insertThesisFile(
|
$db->insertThesisFile(
|
||||||
$thesisId,
|
$thesisId, $fileType, $originalPath, $fileName,
|
||||||
$fileType,
|
$fileSize, $mimeType, $displayLabel, null
|
||||||
$originalPath,
|
|
||||||
$fileName,
|
|
||||||
$fileSize,
|
|
||||||
$mimeType,
|
|
||||||
$displayLabel,
|
|
||||||
null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$newId = $db->getConnection()->lastInsertId();
|
$newId = $db->getConnection()->lastInsertId();
|
||||||
|
error_log("[restore-trash] thesis_id=$thesisId file_id=$newId restored: $trashFile → $originalPath");
|
||||||
error_log("[restore-trash] thesis_id=$thesisId file_id=$newId restored from trash: $trashFile → $originalPath");
|
$restored++;
|
||||||
|
}
|
||||||
|
|
||||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||||
|
|
||||||
// HTMX request: re-render the fragment
|
|
||||||
if (isset($_SERVER['HTTP_HX_REQUEST'])) {
|
if (isset($_SERVER['HTTP_HX_REQUEST'])) {
|
||||||
header('HX-Trigger: refreshStats');
|
header('HX-Trigger: refreshStats');
|
||||||
require __DIR__ . '/cleanup-stats-fragment.php';
|
require __DIR__ . '/cleanup-stats-fragment.php';
|
||||||
@@ -149,9 +137,10 @@ if (isset($_SERVER['HTTP_HX_REQUEST'])) {
|
|||||||
|
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'ok' => true,
|
'ok' => $restored > 0,
|
||||||
'id' => $newId,
|
'restored' => $restored,
|
||||||
'thesis_id' => $thesisId,
|
'skipped' => $skipped,
|
||||||
'message' => 'Fichier restauré avec succès.',
|
'errors' => $errors,
|
||||||
|
'message' => $restored . ' fichier(s) restauré(s).' . ($skipped > 0 ? ' ' . $skipped . ' ignoré(s).' : ''),
|
||||||
]);
|
]);
|
||||||
exit;
|
exit;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* admin-cleanup-bulk.js — Bulk selection and deletion for the temp files cleanup dialog.
|
* admin-cleanup-bulk.js — Bulk selection, deletion, and restore for the temp files cleanup dialog.
|
||||||
*
|
*
|
||||||
* Provides: cleanupToggleAll, cleanupUpdateBulk, cleanupBulkDelete.
|
* Provides: cleanupToggleAll, cleanupUpdateBulk, cleanupBulkDelete, cleanupBulkRestore.
|
||||||
* All functions are attached to `window` for onclick handlers in PHP templates.
|
* All functions are attached to `window` for onclick handlers in PHP templates.
|
||||||
*/
|
*/
|
||||||
(() => {
|
(() => {
|
||||||
@@ -19,13 +19,25 @@
|
|||||||
function cleanupUpdateBulk() {
|
function cleanupUpdateBulk() {
|
||||||
const bar = document.getElementById("cleanup-bulk-actions");
|
const bar = document.getElementById("cleanup-bulk-actions");
|
||||||
const countEl = document.getElementById("cleanup-selected-count");
|
const countEl = document.getElementById("cleanup-selected-count");
|
||||||
|
const restoreBtn = document.getElementById("cleanup-bulk-restore-btn");
|
||||||
if (!bar || !countEl) return;
|
if (!bar || !countEl) return;
|
||||||
|
|
||||||
const n = document.querySelectorAll(
|
const allChecked = document.querySelectorAll(
|
||||||
'input[name="filepond_dirs[]"]:checked, input[name="trash_files[]"]:checked',
|
'input[name="filepond_dirs[]"]:checked, input[name="trash_files[]"]:checked',
|
||||||
).length;
|
);
|
||||||
|
const n = allChecked.length;
|
||||||
|
|
||||||
|
// Count how many checked items are restorable
|
||||||
|
let nRestorable = 0;
|
||||||
|
allChecked.forEach((cb) => {
|
||||||
|
if (cb.dataset.restorable === "1") nRestorable++;
|
||||||
|
});
|
||||||
|
|
||||||
countEl.textContent = n;
|
countEl.textContent = n;
|
||||||
bar.style.display = n > 0 ? "flex" : "none";
|
bar.style.display = n > 0 ? "flex" : "none";
|
||||||
|
if (restoreBtn) {
|
||||||
|
restoreBtn.style.display = nRestorable > 0 ? "" : "none";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupBulkDelete() {
|
function cleanupBulkDelete() {
|
||||||
@@ -51,6 +63,31 @@
|
|||||||
htmx.trigger(form, "submit");
|
htmx.trigger(form, "submit");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cleanupBulkRestore() {
|
||||||
|
const form = document.getElementById("cleanup-bulk-restore-form");
|
||||||
|
const container = document.getElementById("cleanup-bulk-restore-checkboxes");
|
||||||
|
if (!form || !container) return;
|
||||||
|
|
||||||
|
container.innerHTML = "";
|
||||||
|
let count = 0;
|
||||||
|
document
|
||||||
|
.querySelectorAll('input[name="trash_files[]"]:checked[data-restorable="1"]')
|
||||||
|
.forEach((cb) => {
|
||||||
|
const inp = document.createElement("input");
|
||||||
|
inp.type = "hidden";
|
||||||
|
inp.name = "trash_files[]";
|
||||||
|
inp.value = cb.value;
|
||||||
|
container.appendChild(inp);
|
||||||
|
count++;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (count === 0) return;
|
||||||
|
|
||||||
|
if (!confirm(`Restaurer ${count} fichier(s) vers leur(s) TFE associé(s) ?`)) return;
|
||||||
|
|
||||||
|
htmx.trigger(form, "submit");
|
||||||
|
}
|
||||||
|
|
||||||
function reattachListeners() {
|
function reattachListeners() {
|
||||||
document
|
document
|
||||||
.querySelectorAll(
|
.querySelectorAll(
|
||||||
@@ -73,4 +110,5 @@
|
|||||||
window.cleanupToggleAll = cleanupToggleAll;
|
window.cleanupToggleAll = cleanupToggleAll;
|
||||||
window.cleanupUpdateBulk = cleanupUpdateBulk;
|
window.cleanupUpdateBulk = cleanupUpdateBulk;
|
||||||
window.cleanupBulkDelete = cleanupBulkDelete;
|
window.cleanupBulkDelete = cleanupBulkDelete;
|
||||||
|
window.cleanupBulkRestore = cleanupBulkRestore;
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user