Replace browser alert/confirm dialogs with <dialog> modals

- admin/index.php: alert() → no-selection dialog; confirm() bulk actions → bulk-confirm/bulk-delete dialogs; confirm() single delete → delete-thesis dialog; removed redundant confirm on Dépublier (reversible action)
- admin/tags.php: confirm() merge/delete → merge-tag/delete-tag dialogs
- admin/acces-etudiante.php: confirm() delete link → delete-link dialog
- admin/acces.php: confirm() archive link → archive-link dialog
- admin/parametres.php: confirm() maintenance/delete-all → enable-maintenance/delete-all-tfe dialogs; admin password confirm() kept with TODO comment
- admin/account.php: admin password confirm() kept with TODO comment
- admin.css: add .admin-dialog--sm, .admin-dialog__alert, .admin-dialog__footer styles
This commit is contained in:
Pontoporeia
2026-05-04 17:52:30 +02:00
parent ca5983075d
commit ae6d9b86b3
10 changed files with 313 additions and 31 deletions

View File

@@ -36,6 +36,15 @@
- [x] `templates/admin/acces.php` — archive button, archived links collapsible section - [x] `templates/admin/acces.php` — archive button, archived links collapsible section
- [x] `scripts/setup-server.sh` — provision `/var/log/xamxam.log` with correct ownership - [x] `scripts/setup-server.sh` — provision `/var/log/xamxam.log` with correct ownership
## Replace browser dialogs with `<dialog>` modals
- [x] `admin/index.php``alert()` (no selection) → `<dialog id="no-selection-dialog">`; `confirm()` bulk publish/unpublish → `<dialog id="bulk-confirm-dialog">`; `confirm()` bulk delete → `<dialog id="bulk-delete-dialog">`; `confirm()` single delete → `<dialog id="delete-thesis-dialog">`; inline `confirm()` on Dépublier button removed (no confirmation needed for reversible action)
- [x] `admin/tags.php``confirm()` merge → `<dialog id="merge-tag-dialog">`; `confirm()` delete → `<dialog id="delete-tag-dialog">`
- [x] `admin/acces-etudiante.php``confirm()` delete link → `<dialog id="delete-link-dialog">`
- [x] `admin/acces.php``confirm()` archive link → `<dialog id="archive-link-dialog">`
- [x] `admin/parametres.php``confirm()` enable maintenance → `<dialog id="enable-maintenance-dialog">`; `confirm()` delete all TFE → `<dialog id="delete-all-tfe-dialog">`; admin password `confirm()` kept with `TODO` comment
- [x] `admin/account.php` — admin password `confirm()` kept with `TODO` comment
- [x] `admin.css` — added `.admin-dialog--sm`, `.admin-dialog__alert`, `.admin-dialog__footer` styles
## Duplicate warning display fixes ## Duplicate warning display fixes
- [x] `toast-fragment.php` — 204 guard now also checks `warning`; warning was silently discarded before - [x] `toast-fragment.php` — 204 guard now also checks `warning`; warning was silently discarded before
- [x] `partage/index.php` — warning stored as plain text (no pre-escaping); `htmlspecialchars()` applied once at render; was double-encoded before - [x] `partage/index.php` — warning stored as plain text (no pre-escaping); `htmlspecialchars()` applied once at render; was double-encoded before

View File

@@ -915,6 +915,27 @@
padding-bottom: var(--space-m); padding-bottom: var(--space-m);
} }
/* Small/confirmation dialog variant */
.admin-dialog--sm {
max-width: 420px;
}
.admin-dialog__alert {
padding: var(--space-m) var(--space-l);
font-size: var(--step--1);
line-height: 1.6;
}
.admin-dialog__alert p {
margin: 0;
}
.admin-dialog__footer {
display: flex;
gap: var(--space-xs);
padding: 0 var(--space-l) var(--space-m);
}
/* ── Import results log ─────────────────────────────────────────────── */ /* ── Import results log ─────────────────────────────────────────────── */
.admin-import-log { .admin-import-log {
list-style: none; list-style: none;

View File

@@ -0,0 +1 @@
{"timestamp":"2026-05-04T15:36:30+00:00","ip":"127.0.0.1","user_agent":"Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0","resource":"system","action":"maintenance_on","status":"success"}

View File

@@ -0,0 +1 @@
2026-05-04T15:36:30+00:00

View File

@@ -95,11 +95,12 @@
🔑 🔑
</button> </button>
<form method="post" action="actions/acces-etudiante.php" class="publish-form" <form method="post" action="actions/acces-etudiante.php" class="publish-form"
onsubmit="return confirm('Supprimer ce lien ? Les soumissions via ce lien seront bloquées.')"> id="delete-link-form-<?= $link['id'] ?>">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>"> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="delete"> <input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= $link['id'] ?>"> <input type="hidden" name="id" value="<?= $link['id'] ?>">
<button type="submit" class="admin-btn-sm admin-btn-delete" title="Supprimer"> <button type="button" class="admin-btn-sm admin-btn-delete" title="Supprimer"
onclick="openDeleteLinkDialog(<?= $link['id'] ?>)">
🗑 🗑
</button> </button>
</form> </form>
@@ -180,6 +181,16 @@ document.getElementById('open-create-dialog').addEventListener('click', () => {
document.getElementById('create-dialog').showModal(); document.getElementById('create-dialog').showModal();
}); });
let _pendingDeleteLinkId = null;
function openDeleteLinkDialog(id) {
_pendingDeleteLinkId = id;
document.getElementById('delete-link-dialog').showModal();
}
function _executeDeleteLink() {
const form = document.getElementById('delete-link-form-' + _pendingDeleteLinkId);
if (form) form.submit();
}
function copyUrl(id) { function copyUrl(id) {
const input = document.getElementById('url-' + id); const input = document.getElementById('url-' + id);
navigator.clipboard.writeText(input.value).then(() => { navigator.clipboard.writeText(input.value).then(() => {
@@ -199,3 +210,19 @@ function openPasswordDialog(id, hasPassword) {
document.getElementById('password-dialog').showModal(); document.getElementById('password-dialog').showModal();
} }
</script> </script>
<!-- Delete link confirm -->
<dialog id="delete-link-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="delete-link-title">
<div class="admin-dialog__header">
<h2 id="delete-link-title">Supprimer ce lien</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>Supprimer ce lien ? Les soumissions via ce lien seront bloquées.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="admin-btn admin-btn--danger" onclick="this.closest('dialog').close(); _executeDeleteLink()">Supprimer</button>
<button type="button" class="admin-btn-secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>

View File

@@ -15,7 +15,7 @@
</div> </div>
<?php if (empty($links)): ?> <?php if (empty($links)): ?>
<p class="admin-empty">Aucun lien d'accès créé. Cliquez sur «Créer un lien» pour générer un lien partageable.</p> <p class="admin-empty">Aucun lien d'accès créé. Cliquez sur « Créer un lien » pour générer un lien partageable.</p>
<?php else: ?> <?php else: ?>
<table> <table>
<thead> <thead>
@@ -38,7 +38,7 @@
$statusLabel = $isExpired ? 'Expiré' : ($link['is_active'] ? 'Actif' : 'Désactivé'); $statusLabel = $isExpired ? 'Expiré' : ($link['is_active'] ? 'Actif' : 'Désactivé');
$fullUrl = $baseUrl . '/partage/' . htmlspecialchars($link['slug']); $fullUrl = $baseUrl . '/partage/' . htmlspecialchars($link['slug']);
$created = date('d/m/Y H:i', strtotime($link['created_at'])); $created = date('d/m/Y H:i', strtotime($link['created_at']));
$expires = $link['expires_at'] ? date('d/m/Y', strtotime($link['expires_at'])) : ''; $expires = $link['expires_at'] ? date('d/m/Y', strtotime($link['expires_at'])) : '-';
$hasLinkPassword = !empty($link['password_hash']); $hasLinkPassword = !empty($link['password_hash']);
?> ?>
<tr> <tr>
@@ -92,11 +92,12 @@
🔑 🔑
</button> </button>
<form method="post" action="actions/acces-etudiante.php" class="publish-form" <form method="post" action="actions/acces-etudiante.php" class="publish-form"
onsubmit="return confirm('Archiver ce lien? Il ne sera plus accessible, mais les statistiques seront conservées.')"> id="archive-link-form-<?= $link['id'] ?>">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>"> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="archive"> <input type="hidden" name="action" value="archive">
<input type="hidden" name="id" value="<?= $link['id'] ?>"> <input type="hidden" name="id" value="<?= $link['id'] ?>">
<button type="submit" class="admin-btn-sm admin-btn-delete" title="Archiver"> <button type="button" class="admin-btn-sm admin-btn-delete" title="Archiver"
onclick="openArchiveLinkDialog(<?= $link['id'] ?>)">
🗄 🗄
</button> </button>
</form> </form>
@@ -127,7 +128,7 @@
<?php foreach ($archivedLinks as $link): ?> <?php foreach ($archivedLinks as $link): ?>
<?php <?php
$created = date('d/m/Y H:i', strtotime($link['created_at'])); $created = date('d/m/Y H:i', strtotime($link['created_at']));
$expires = $link['expires_at'] ? date('d/m/Y', strtotime($link['expires_at'])) : ''; $expires = $link['expires_at'] ? date('d/m/Y', strtotime($link['expires_at'])) : '-';
?> ?>
<tr> <tr>
<td> <td>
@@ -433,4 +434,30 @@ function openRejectDialog(requestId) {
document.getElementById('reject-request-id').value = requestId; document.getElementById('reject-request-id').value = requestId;
document.getElementById('reject-dialog').showModal(); document.getElementById('reject-dialog').showModal();
} }
let _pendingArchiveLinkId = null;
function openArchiveLinkDialog(id) {
_pendingArchiveLinkId = id;
document.getElementById('archive-link-dialog').showModal();
}
function _executeArchiveLink() {
const form = document.getElementById('archive-link-form-' + _pendingArchiveLinkId);
if (form) form.submit();
}
</script> </script>
<!-- Archive link confirm -->
<dialog id="archive-link-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="archive-link-title">
<div class="admin-dialog__header">
<h2 id="archive-link-title">Archiver ce lien</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>Archiver ce lien ? Il ne sera plus accessible, mais les statistiques seront conservées.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="admin-btn admin-btn--warning" onclick="this.closest('dialog').close(); _executeArchiveLink()">Archiver</button>
<button type="button" class="admin-btn-secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>

View File

@@ -74,6 +74,7 @@
dépendra uniquement de l'authentification nginx Basic Auth si elle est configurée. dépendra uniquement de l'authentification nginx Basic Auth si elle est configurée.
</small> </small>
</p> </p>
<?php /* TODO: replace this browser confirm() with a proper <dialog> modal like the other confirmations */ ?>
<form method="post" action="/admin/actions/account.php" <form method="post" action="/admin/actions/account.php"
onsubmit="return confirm('Supprimer le mot de passe PHP ? L\'accès admin ne sera protégé que par nginx Basic Auth.')"> onsubmit="return confirm('Supprimer le mot de passe PHP ? L\'accès admin ne sera protégé que par nginx Basic Auth.')">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>"> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">

View File

@@ -9,19 +9,40 @@ function updateBulk() {
document.getElementById('selected-count').textContent = checked.length; document.getElementById('selected-count').textContent = checked.length;
bulk.style.display = checked.length > 0 ? 'flex' : 'none'; bulk.style.display = checked.length > 0 ? 'flex' : 'none';
} }
// Pending bulk action state
let _pendingBulkAction = null;
function bulkAction(action) { function bulkAction(action) {
const checked = document.querySelectorAll('input[name="selected_theses[]"]:checked'); const checked = document.querySelectorAll('input[name="selected_theses[]"]:checked');
if (!checked.length) { alert('Sélectionnez au moins un TFE.'); return; } if (!checked.length) {
document.getElementById('no-selection-dialog').showModal();
return;
}
_pendingBulkAction = action;
let word, endpoint; let word, endpoint;
if (action === 'publish') { word = 'publier'; endpoint = 'actions/publish.php'; } if (action === 'publish') { word = 'publier'; endpoint = 'actions/publish.php'; }
else if (action === 'unpublish') { word = 'dépublier'; endpoint = 'actions/publish.php'; } else if (action === 'unpublish') { word = 'dépublier'; endpoint = 'actions/publish.php'; }
else if (action === 'delete') { word = 'supprimer'; endpoint = 'actions/delete.php'; } else if (action === 'delete') { word = 'supprimer'; endpoint = 'actions/delete.php'; }
else return; else return;
if (action === 'delete') { if (action === 'delete') {
if (!confirm(`Supprimer définitivement ${checked.length} TFE(s) ? Cette action est irréversible.`)) return; document.getElementById('bulk-delete-count').textContent = checked.length;
document.getElementById('bulk-delete-dialog').showModal();
} else { } else {
if (!confirm(`${word.charAt(0).toUpperCase()+word.slice(1)} ${checked.length} TFE(s) ?`)) return; document.getElementById('bulk-confirm-word').textContent = word.charAt(0).toUpperCase() + word.slice(1);
document.getElementById('bulk-confirm-count').textContent = checked.length;
document.getElementById('bulk-confirm-dialog').showModal();
} }
}
function _executeBulkAction() {
const action = _pendingBulkAction;
if (!action) return;
let endpoint;
if (action === 'publish' || action === 'unpublish') { endpoint = 'actions/publish.php'; }
else if (action === 'delete') { endpoint = 'actions/delete.php'; }
else return;
const checked = document.querySelectorAll('input[name="selected_theses[]"]:checked');
document.getElementById('bulk-action-input').value = action; document.getElementById('bulk-action-input').value = action;
document.getElementById('bulk-form').action = endpoint; document.getElementById('bulk-form').action = endpoint;
const container = document.getElementById('bulk-checkboxes'); const container = document.getElementById('bulk-checkboxes');
@@ -33,11 +54,21 @@ function bulkAction(action) {
}); });
document.getElementById('bulk-form').submit(); document.getElementById('bulk-form').submit();
} }
// Pending single-delete state
let _pendingDeleteId = null;
function deleteThesis(id, title) { function deleteThesis(id, title) {
if (!confirm(`Supprimer « ${title} » ?\nCette action est irréversible.`)) return; _pendingDeleteId = id;
const form = document.getElementById('delete-form-' + id); document.getElementById('delete-thesis-title').textContent = title;
document.getElementById('delete-thesis-dialog').showModal();
}
function _executeDeleteThesis() {
const form = document.getElementById('delete-form-' + _pendingDeleteId);
if (form) form.submit(); if (form) form.submit();
} }
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('input[name="selected_theses[]"]').forEach(cb => cb.addEventListener('change', updateBulk)); document.querySelectorAll('input[name="selected_theses[]"]').forEach(cb => cb.addEventListener('change', updateBulk));
}); });
@@ -202,8 +233,7 @@ document.addEventListener('DOMContentLoaded', () => {
<input type="hidden" name="thesis_id" value="<?= $thesis['id'] ?>"> <input type="hidden" name="thesis_id" value="<?= $thesis['id'] ?>">
<?php if ($thesis['is_published']): ?> <?php if ($thesis['is_published']): ?>
<input type="hidden" name="action" value="unpublish"> <input type="hidden" name="action" value="unpublish">
<button type="submit" class="admin-btn-sm admin-btn-unpublish" <button type="submit" class="admin-btn-sm admin-btn-unpublish">Dépublier</button>
onclick="return confirm('Retirer de la publication ?')">Dépublier</button>
<?php else: ?> <?php else: ?>
<input type="hidden" name="action" value="publish"> <input type="hidden" name="action" value="publish">
<button type="submit" class="admin-btn-sm admin-btn-publish">Publier</button> <button type="submit" class="admin-btn-sm admin-btn-publish">Publier</button>
@@ -236,6 +266,73 @@ document.addEventListener('DOMContentLoaded', () => {
?> ?>
</main> </main>
<!-- ══════════════════════════════════════════════════════════════
CONFIRM DIALOGS (replacing browser alert/confirm)
══════════════════════════════════════════════════════════════ -->
<!-- No-selection alert -->
<dialog id="no-selection-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="no-sel-title">
<div class="admin-dialog__header">
<h2 id="no-sel-title">Aucune sélection</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>Sélectionnez au moins un TFE avant d'effectuer une action groupée.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="admin-btn" onclick="this.closest('dialog').close()">OK</button>
</div>
</dialog>
<!-- Bulk publish/unpublish confirm -->
<dialog id="bulk-confirm-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="bulk-confirm-title">
<div class="admin-dialog__header">
<h2 id="bulk-confirm-title">Confirmation</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p><span id="bulk-confirm-word"></span> <span id="bulk-confirm-count"></span> TFE(s) ?</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="admin-btn" onclick="this.closest('dialog').close(); _executeBulkAction()">Confirmer</button>
<button type="button" class="admin-btn-secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<!-- Bulk delete confirm -->
<dialog id="bulk-delete-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="bulk-delete-title">
<div class="admin-dialog__header">
<h2 id="bulk-delete-title">Supprimer des TFE</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>Supprimer définitivement <strong><span id="bulk-delete-count"></span> TFE(s)</strong> ? Cette action est irréversible.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="admin-btn admin-btn--danger" onclick="this.closest('dialog').close(); _executeBulkAction()">Supprimer</button>
<button type="button" class="admin-btn-secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<!-- Single thesis delete confirm -->
<dialog id="delete-thesis-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="delete-thesis-title-label">
<div class="admin-dialog__header">
<h2 id="delete-thesis-title-label">Supprimer ce TFE</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>Supprimer « <strong id="delete-thesis-title"></strong> » ? Cette action est irréversible.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="admin-btn admin-btn--danger" onclick="this.closest('dialog').close(); _executeDeleteThesis()">Supprimer</button>
<button type="button" class="admin-btn-secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<!-- ══════════════════════════════════════════════════════════════ <!-- ══════════════════════════════════════════════════════════════
IMPORT DIALOG IMPORT DIALOG
══════════════════════════════════════════════════════════════ --> ══════════════════════════════════════════════════════════════ -->

View File

@@ -20,12 +20,12 @@
</form> </form>
<?php else: ?> <?php else: ?>
<p>Site public : <strong>en ligne</strong></p> <p>Site public : <strong>en ligne</strong></p>
<form method="post" action="actions/maintenance.php"> <form method="post" action="actions/maintenance.php" id="enable-maintenance-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>"> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="enable_maintenance"> <input type="hidden" name="action" value="enable_maintenance">
<input type="hidden" name="redirect" value="/admin/parametres.php"> <input type="hidden" name="redirect" value="/admin/parametres.php">
<button type="submit" class="param-btn-warning" <button type="button" class="param-btn-warning"
onclick="return confirm('Mettre le site en maintenance ? Les visiteurs verront une page 503.')"> onclick="document.getElementById('enable-maintenance-dialog').showModal()">
Activer la maintenance Activer la maintenance
</button> </button>
</form> </form>
@@ -50,12 +50,14 @@
Supprime définitivement tous les TFE de la base de données, y compris auteurs, Supprime définitivement tous les TFE de la base de données, y compris auteurs,
promoteurs, tags, fichiers associés. Cette action est <strong>irréversible</strong>. promoteurs, tags, fichiers associés. Cette action est <strong>irréversible</strong>.
</p> </p>
<form method="post" action="actions/delete.php" <form method="post" action="actions/delete.php" id="delete-all-tfe-form">
onsubmit="return confirm('⚠ Supprimer définitivement TOUS les TFE ? Cette action est IRRÉVERSIBLE.')"> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>"> <input type="hidden" name="delete_all" value="1">
<input type="hidden" name="delete_all" value="1"> <button type="button" class="param-btn-danger"
<button type="submit" class="param-btn-danger">Supprimer tous les TFE (<?= $stats['total'] ?? '?' ?>)</button> onclick="document.getElementById('delete-all-tfe-dialog').showModal()">
</form> Supprimer tous les TFE (<?= $stats['total'] ?? '?' ?>)
</button>
</form>
</fieldset> </fieldset>
</section> </section>
@@ -377,8 +379,9 @@
Supprime le hash de la base de données. L'accès admin Supprime le hash de la base de données. L'accès admin
dépendra uniquement de l'authentification nginx Basic Auth si elle est configurée. dépendra uniquement de l'authentification nginx Basic Auth si elle est configurée.
</p> </p>
<?php /* TODO: replace this browser confirm() with a proper <dialog> modal like the other confirmations */ ?>
<form method="post" action="/admin/actions/account.php" <form method="post" action="/admin/actions/account.php"
onsubmit="return confirm('Supprimer le mot de passe PHP ? L\'accès admin ne sera protégé que par nginx Basic Auth.')">> onsubmit="return confirm('Supprimer le mot de passe PHP ? L\'accès admin ne sera protégé que par nginx Basic Auth.')">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>"> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="remove_credentials"> <input type="hidden" name="action" value="remove_credentials">
<input type="hidden" name="redirect" value="/admin/parametres.php"> <input type="hidden" name="redirect" value="/admin/parametres.php">
@@ -584,3 +587,41 @@ document.body.addEventListener('htmx:afterSwap', function(evt) {
} }
}); });
</script> </script>
<!-- Enable maintenance confirm -->
<dialog id="enable-maintenance-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="enable-maint-title">
<div class="admin-dialog__header">
<h2 id="enable-maint-title">Activer la maintenance</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>Mettre le site en maintenance ? Les visiteurs verront une page 503.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="admin-btn admin-btn--warning"
onclick="this.closest('dialog').close(); document.getElementById('enable-maintenance-form').submit()">
Activer
</button>
<button type="button" class="admin-btn-secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<!-- Delete all TFE confirm -->
<dialog id="delete-all-tfe-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="delete-all-title">
<div class="admin-dialog__header">
<h2 id="delete-all-title">Supprimer tous les TFE</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>⚠️ Supprimer définitivement <strong>TOUS les TFE</strong> ? Cette action est <strong>IRRÉVERSIBLE</strong>.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="admin-btn admin-btn--danger"
onclick="this.closest('dialog').close(); document.getElementById('delete-all-tfe-form').submit()">
Supprimer tout
</button>
<button type="button" class="admin-btn-secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>

View File

@@ -1,3 +1,28 @@
<script>
let _pendingTagForm = null;
function confirmMergeTag(btn) {
const form = btn.closest('form');
const select = form.querySelector('select[name="target_id"]');
const targetName = select.options[select.selectedIndex]?.text ?? '';
if (!select.value) { return true; } // let HTML validation handle empty
_pendingTagForm = form;
document.getElementById('merge-target-name').textContent = targetName;
document.getElementById('merge-tag-dialog').showModal();
return false;
}
function confirmDeleteTag(btn, name) {
_pendingTagForm = btn.closest('form');
document.getElementById('delete-tag-name').textContent = name;
document.getElementById('delete-tag-dialog').showModal();
}
function _submitPendingTagForm() {
if (_pendingTagForm) _pendingTagForm.submit();
}
</script>
<main id="main-content"> <main id="main-content">
<h1>Mots-clés (<?= count($tags) ?>)</h1> <h1>Mots-clés (<?= count($tags) ?>)</h1>
@@ -38,8 +63,8 @@
<?php endif; ?> <?php endif; ?>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<button type="submit" class="admin-btn admin-btn--sm admin-btn--warning" <button type="button" class="admin-btn admin-btn--sm admin-btn--warning"
onclick="return confirm('Fusionner ce tag dans la cible ? Le tag source sera supprimé.')"> onclick="return confirmMergeTag(this)">
Fusionner Fusionner
</button> </button>
</form> </form>
@@ -49,8 +74,8 @@
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>"> <input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="action" value="delete"> <input type="hidden" name="action" value="delete">
<input type="hidden" name="tag_id" value="<?= (int)$tag['id'] ?>"> <input type="hidden" name="tag_id" value="<?= (int)$tag['id'] ?>">
<button type="submit" class="admin-btn admin-btn--sm admin-btn--danger" <button type="button" class="admin-btn admin-btn--sm admin-btn--danger"
onclick="return confirm('Supprimer le tag « <?= htmlspecialchars(addslashes($tag['name'])) ?> » ? Cette action est irréversible.')"> onclick="confirmDeleteTag(this, <?= htmlspecialchars(json_encode($tag['name']), ENT_QUOTES) ?>)">
Supprimer Supprimer
</button> </button>
</form> </form>
@@ -60,3 +85,35 @@
</tbody> </tbody>
</table> </table>
</main> </main>
<!-- Merge tag confirm -->
<dialog id="merge-tag-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="merge-tag-title">
<div class="admin-dialog__header">
<h2 id="merge-tag-title">Fusionner le tag</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>Fusionner ce tag dans « <strong id="merge-target-name"></strong> » ? Le tag source sera supprimé.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="admin-btn admin-btn--warning" onclick="this.closest('dialog').close(); _submitPendingTagForm()">Fusionner</button>
<button type="button" class="admin-btn-secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<!-- Delete tag confirm -->
<dialog id="delete-tag-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="delete-tag-title">
<div class="admin-dialog__header">
<h2 id="delete-tag-title">Supprimer le tag</h2>
<button type="button" class="admin-dialog__close" aria-label="Fermer"
onclick="this.closest('dialog').close()">&#x2715;</button>
</div>
<div class="admin-dialog__alert">
<p>Supprimer « <strong id="delete-tag-name"></strong> » ? Cette action est irréversible.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="admin-btn admin-btn--danger" onclick="this.closest('dialog').close(); _submitPendingTagForm()">Supprimer</button>
<button type="button" class="admin-btn-secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>