Files
xamxam/app/templates/admin/contenus.php
Pontoporeia dfde88eaa5 Migrate all <img>-based icons to inline SVG via PHP helper
Replace every <img src="/assets/icons/..."> with <?= icon('name') ?>
across 26 template files. The PHP helper inlines the SVG markup into the
DOM so CSS color cascades naturally through fill="currentColor".

- Add src/icon.php helper: reads SVG file, sets width/height to 1em,
  injects aria-hidden, supports optional CSS class
- Fix 12 icon SVGs that had hardcoded fill="#000000" or missing fill attr
- Replace search.svg with Phosphor fill-based magnifying glass
- Add explicit SVG sizes for admin header nav icons (16px/20px)
- Scope public search icon CSS to form[role=search]:not(.header-search-form)
  to avoid breaking admin header layout; change stroke to fill
- Remove <img> filter: brightness(0) invert(1) hacks from admin.css
2026-06-21 17:52:27 +02:00

609 lines
31 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<main id="main-content" class="admin-main--toc">
<?php include APP_ROOT . '/templates/admin/partials/admin-toc.php'; ?>
<article>
<h1>Contenus</h1>
<?php
$flash = App::consumeFlash();
?>
<?php if ($flash['success']): ?>
<div class="flash-success" role="alert"><?= htmlspecialchars($flash['success']) ?></div>
<?php endif; ?>
<?php if ($flash['error']): ?>
<div class="flash-error" role="alert"><?= htmlspecialchars($flash['error']) ?></div>
<?php endif; ?>
<!-- ═══════════════════════════════════════════════════════════════════
PAGES STATIQUES
═══════════════════════════════════════════════════════════════════ -->
<section aria-labelledby="static-pages-title">
<h2 id="static-pages-title">Pages statiques</h2>
<table>
<thead>
<tr>
<th scope="col">Slug</th>
<th scope="col">Titre</th>
<th scope="col">Mis à jour</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($pages as $p): ?>
<tr>
<td><code><?= htmlspecialchars($p['slug']) ?></code></td>
<td><?= htmlspecialchars($p['title']) ?></td>
<td><?= htmlspecialchars($p['updated_at'] ?? '—') ?></td>
<td>
<a href="/admin/contenus-edit.php?slug=<?= urlencode($p['slug']) ?>"
class="admin-icon-btn admin-icon-btn--edit" title="Éditer">
<?= icon('pencil-note') ?>
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</section>
<!-- ═══════════════════════════════════════════════════════════════════
DONNÉES SECONDAIRES
═══════════════════════════════════════════════════════════════════ -->
<section aria-labelledby="donnees-secondaires-title">
<h2 id="donnees-secondaires-title">Données Secondaires</h2>
<fieldset>
<legend>Langues</legend>
<form id="langues-search-form"
hx-get="/admin/contenus-langues-fragment.php"
hx-target="#langues-table-wrap"
hx-swap="innerHTML"
hx-trigger="input changed delay:200ms from:input[name=q], keyup[key=='Enter'] from:input[name=q]"
hx-push-url="false"
style="margin-bottom:var(--space-xs)">
<input type="text" name="q" placeholder="Rechercher une langue…" style="max-width:300px">
</form>
<div id="langues-table-wrap"
hx-get="/admin/contenus-langues-fragment.php"
hx-trigger="load"
hx-swap="innerHTML"
style="min-height:50vh;max-height:50vh;overflow-y:auto">
<div style="padding:var(--space-m); text-align:center; color:var(--text-tertiary)">
<img alt="Chargement…" class="htmx-indicator" width="24" src="/assets/img/bars.svg" style="opacity:0.5">
</div>
</div>
</fieldset>
<p style="margin-top:var(--space-s)"><a href="/admin/tags.php" class="btn btn--sm btn--primary">Gérer les mots-clés (page dédiée)</a></p>
<fieldset>
<legend>Mots-clés</legend>
<form id="motscles-search-form"
hx-get="/admin/contenus-motscles-fragment.php"
hx-target="#motscles-table-wrap"
hx-swap="innerHTML"
hx-trigger="input changed delay:200ms from:input[name=q], keyup[key=='Enter'] from:input[name=q]"
hx-push-url="false"
style="margin-bottom:var(--space-xs)">
<input type="text" name="q" placeholder="Rechercher un mot-clé…" style="max-width:300px">
</form>
<div id="motscles-table-wrap"
hx-get="/admin/contenus-motscles-fragment.php"
hx-trigger="load"
hx-swap="innerHTML"
style="min-height:50vh;max-height:50vh;overflow-y:auto">
<div style="padding:var(--space-m); text-align:center; color:var(--text-tertiary)">
<img alt="Chargement…" class="htmx-indicator" width="24" src="/assets/img/bars.svg" style="opacity:0.5">
</div>
</div>
</fieldset>
</section>
<!-- ═══════════════════════════════════════════════════════════════════
PARAMÈTRES DU FORMULAIRE
═══════════════════════════════════════════════════════════════════ -->
<section aria-labelledby="form-settings-title">
<h2 id="form-settings-title">Paramètres du Formulaire</h2>
<fieldset id="fieldset-acces">
<legend>Degré d'ouverture</legend>
<p>Options de visibilité disponibles dans le formulaire d'ajout de TFE.</p>
<div class="param-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="section" value="formulaire_acces">
<label class="param-checkbox">
<input type="checkbox" name="access_type_libre_enabled" value="1"
<?= ($siteSettings['access_type_libre_enabled'] ?? '0') === '1' ? 'checked' : '' ?>
hx-post="/admin/actions/settings.php"
hx-trigger="change"
hx-target="#acces-response"
hx-swap="innerHTML"
hx-include="#fieldset-acces"
hx-on::before-request="console.log('[acces-libre] sending checked=' + this.checked)"
hx-on::after-request="console.log('[acces-libre] response received')">
<span>
<strong>Libre</strong><br>
<small>Libre accès — TFE accessible publiquement sur la plateforme et en bibliothèque</small>
</span>
</label>
<label class="param-checkbox">
<input type="checkbox" name="access_type_interne_enabled" value="1"
<?= ($siteSettings['access_type_interne_enabled'] ?? '1') === '1' ? 'checked' : '' ?>
hx-post="/admin/actions/settings.php"
hx-trigger="change"
hx-target="#acces-response"
hx-swap="innerHTML"
hx-include="#fieldset-acces"
hx-on::before-request="console.log('[acces-interne] sending checked=' + this.checked)"
hx-on::after-request="console.log('[acces-interne] response received')">
<span>
<strong>Interne</strong><br>
<small>TFE accessible uniquement sur place en physique</small>
</span>
</label>
<label class="param-checkbox">
<input type="checkbox" name="access_type_interdit_enabled" value="1"
<?= ($siteSettings['access_type_interdit_enabled'] ?? '1') === '1' ? 'checked' : '' ?>
hx-post="/admin/actions/settings.php"
hx-trigger="change"
hx-target="#acces-response"
hx-swap="innerHTML"
hx-include="#fieldset-acces"
hx-on::before-request="console.log('[acces-interdit] sending checked=' + this.checked)"
hx-on::after-request="console.log('[acces-interdit] response received')">
<span>
<strong>Interdit</strong><br>
<small>TFE non disponible en physique ni sur le site</small>
</span>
</label>
</div>
<div id="acces-response" aria-live="polite"></div>
</fieldset>
<fieldset id="fieldset-types">
<legend>Types de travaux</legend>
<p>Active ou désactive les types de travaux dans les formulaires et la consultation. Un type désactivé ne peut plus être soumis ni affiché sur le site.</p>
<p class="param-note">Le type <strong>TFE</strong> est toujours actif et ne peut pas être désactivé.</p>
<div class="param-form">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="section" value="objet_types">
<label class="param-checkbox param-checkbox--disabled">
<input type="checkbox" disabled checked>
<span>
<strong>TFE</strong><br>
<small>Travail de fin d'études — toujours actif</small>
</span>
</label>
<label class="param-checkbox">
<input type="checkbox" name="objet_these_enabled" value="1"
<?= ($siteSettings['objet_these_enabled'] ?? '1') === '1' ? 'checked' : '' ?>
hx-post="/admin/actions/settings.php"
hx-trigger="change"
hx-target="#types-response"
hx-swap="innerHTML"
hx-include="#fieldset-types"
hx-on::before-request="console.log('[types-these] sending checked=' + this.checked)"
hx-on::after-request="console.log('[types-these] response received')">
<span>
<strong>Thèse</strong><br>
<small>Thèses doctorales</small>
</span>
</label>
<label class="param-checkbox">
<input type="checkbox" name="objet_frart_enabled" value="1"
<?= ($siteSettings['objet_frart_enabled'] ?? '1') === '1' ? 'checked' : '' ?>
hx-post="/admin/actions/settings.php"
hx-trigger="change"
hx-target="#types-response"
hx-swap="innerHTML"
hx-include="#fieldset-types"
hx-on::before-request="console.log('[types-frart] sending checked=' + this.checked)"
hx-on::after-request="console.log('[types-frart] response received')">
<span>
<strong>Frart</strong><br>
<small>Formation de recherche en art</small>
</span>
</label>
</div>
<div id="types-response" aria-live="polite"></div>
</fieldset>
<fieldset>
<legend>Structure du Formulaire</legend>
<p class="fhb-hint">
Chaque <strong>bloc d'aide</strong> s'affiche au-dessus de sa section dans le formulaire de soumission.
Le <strong>bouton rond</strong> active/désactive l'affichage.
</p>
<p>
<a href="/admin/structure-formulaire.php" class="btn btn--primary btn--sm">Gérer la structure du formulaire (page dédiée)</a>
</p>
</fieldset>
</section>
</article>
</main>
<!-- ══════════════════════════════════════════════════════════════
CONFIRM DIALOGS FOR LANGUES
══════════════════════════════════════════════════════════════ -->
<dialog id="langues-delete-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="langues-delete-title">
<div class="admin-dialog__header">
<h2 id="langues-delete-title">Supprimer la langue</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="langues-delete-name"></strong> » ? Cette action est irréversible.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="btn btn--danger" onclick="this.closest('dialog').close(); languesSubmitPending()">Supprimer</button>
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<dialog id="langues-bulk-merge-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="langues-bulk-merge-title">
<div class="admin-dialog__header">
<h2 id="langues-bulk-merge-title">Fusionner des langues</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 <strong><span id="langues-bulk-merge-count"></span> langue(s)</strong> sélectionnée(s) dans :</p>
<select id="langues-bulk-merge-target-select" class="admin-select--inline" style="margin-top:var(--space-xs); width:100%" required>
<option value="">— Choisir la destination —</option>
</select>
</div>
<div class="admin-dialog__footer">
<button type="button" class="btn btn--warning" onclick="languesExecBulkMerge()">Fusionner</button>
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<dialog id="langues-bulk-delete-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="langues-bulk-delete-title">
<div class="admin-dialog__header">
<h2 id="langues-bulk-delete-title">Supprimer des langues</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="langues-bulk-delete-count"></span> langue(s)</strong>&nbsp;? Cette action est irréversible.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="btn btn--danger" onclick="languesExecBulkDelete()">Supprimer</button>
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<script>
let _languesPendingForm = null;
function languesConfirmDelete(btn, name) {
_languesPendingForm = btn.closest('form');
document.getElementById('langues-delete-name').textContent = name;
document.getElementById('langues-delete-dialog').showModal();
}
// ── Inline rename via HTMX ──────────────────────────────────────────────
function languesStartRename(id) {
var cell = document.getElementById('lang-name-' + id);
var csrf = document.querySelector('input[name="csrf_token"]').value;
cell.innerHTML = '<form hx-post="/admin/actions/language.php" hx-target="#langues-table-wrap" hx-swap="innerHTML" class="admin-inline-form">'
+ '<input type="hidden" name="csrf_token" value="' + csrf + '">'
+ '<input type="hidden" name="action" value="rename">'
+ '<input type="hidden" name="return" value="/admin/contenus.php">'
+ '<input type="hidden" name="language_id" value="' + id + '">'
+ '<input type="text" name="new_name" value="' + cell.getAttribute('data-name') + '" required class="admin-input--inline">'
+ '<button type="submit" class="admin-icon-btn admin-icon-btn--edit" title="Valider">'
+ '<?= icon('check-circle') ?>'
+ '</button>'
+ '<button type="button" class="admin-icon-btn admin-icon-btn--delete" onclick="languesCancelRename(' + id + ')" title="Annuler">'
+ '<?= icon('x-close') ?>'
+ '</button></form>';
cell.querySelector('input').focus();
}
function languesCancelRename(id) {
var cell = document.getElementById('lang-name-' + id);
cell.innerHTML = '<span class="tag-name-cell">' + cell.getAttribute('data-name') + '</span>'
+ '<button type="button" class="admin-icon-btn admin-icon-btn--edit" title="Renommer" onclick="languesStartRename(' + id + ')">'
+ '<?= icon('pencil-note') ?>'
+ '</button>';
}
function languesSubmitPending() {
if (_languesPendingForm) _languesPendingForm.submit();
}
function languesToggleAll(src) {
document.querySelectorAll('input[name="selected_langs[]"]').forEach(cb => cb.checked = src.checked);
languesUpdateBulk();
}
function languesUpdateBulk() {
const n = document.querySelectorAll('input[name="selected_langs[]"]:checked').length;
document.getElementById('langues-selected-count').textContent = n;
const bar = document.getElementById('langues-bulk-actions');
const wrap = document.getElementById('langues-table-wrap');
const visible = n > 1;
bar.style.display = visible ? 'flex' : 'none';
// Force reflow then read bar height for sticky th offset
if (visible) {
requestAnimationFrame(() => {
wrap.style.setProperty('--sticky-top', bar.offsetHeight + 'px');
});
} else {
wrap.style.setProperty('--sticky-top', '0px');
}
}
function languesCancelSelection() {
document.querySelectorAll('input[name="selected_langs[]"]').forEach(cb => cb.checked = false);
languesUpdateBulk();
}
function languesConfirmBulkDelete() {
const checked = document.querySelectorAll('input[name="selected_langs[]"]:checked');
if (checked.length < 1) return;
document.getElementById('langues-bulk-delete-count').textContent = checked.length;
document.getElementById('langues-bulk-delete-dialog').showModal();
}
function languesExecBulkDelete() {
const container = document.getElementById('langues-bulk-checkboxes');
container.innerHTML = '';
document.querySelectorAll('input[name="selected_langs[]"]:checked').forEach(cb => {
const inp = document.createElement('input');
inp.type = 'hidden';
inp.name = 'selected_langs[]';
inp.value = cb.value;
container.appendChild(inp);
});
document.getElementById('langues-bulk-form').querySelector('input[name="action"]').value = 'delete_bulk';
document.getElementById('langues-bulk-delete-dialog').close();
document.getElementById('langues-bulk-form').submit();
}
function languesConfirmBulkMerge() {
const checked = document.querySelectorAll('input[name="selected_langs[]"]:checked');
if (checked.length < 2) return;
document.getElementById('langues-bulk-merge-count').textContent = checked.length;
const sel = document.getElementById('langues-bulk-merge-target-select');
sel.innerHTML = '<option value="">— Choisir la destination —</option>';
checked.forEach(cb => {
const tr = cb.closest('tr');
sel.innerHTML += '<option value="' + cb.value + '">' + tr.querySelector('td:nth-child(2)').textContent.trim() + '</option>';
});
document.getElementById('langues-bulk-merge-dialog').showModal();
}
function languesExecBulkMerge() {
const targetId = document.getElementById('langues-bulk-merge-target-select').value;
if (!targetId) return;
document.getElementById('langues-bulk-target').value = targetId;
const container = document.getElementById('langues-bulk-checkboxes');
container.innerHTML = '';
document.querySelectorAll('input[name="selected_langs[]"]:checked').forEach(cb => {
const inp = document.createElement('input');
inp.type = 'hidden';
inp.name = 'selected_langs[]';
inp.value = cb.value;
container.appendChild(inp);
});
document.getElementById('langues-bulk-merge-dialog').close();
document.getElementById('langues-bulk-form').submit();
}
document.addEventListener('htmx:afterSwap', function(evt) {
if (evt.target.id === 'langues-table-wrap') {
document.querySelectorAll('input[name="selected_langs[]"]').forEach(cb => cb.addEventListener('change', languesUpdateBulk));
languesUpdateBulk();
}
});
</script>
<!-- ═══════════════════════════════════════════════════════════════
CONFIRM DIALOGS FOR MOTS-CLÉS
══════════════════════════════════════════════════════════════ -->
<dialog id="motscles-delete-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="motscles-delete-title">
<div class="admin-dialog__header">
<h2 id="motscles-delete-title">Supprimer le mot-clé</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 «&nbsp;<strong id="motscles-delete-name"></strong>&nbsp;»&nbsp;? Cette action est irréversible.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="btn btn--danger" onclick="this.closest('dialog').close(); motsclesSubmitPending()">Supprimer</button>
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<dialog id="motscles-bulk-merge-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="motscles-bulk-merge-title">
<div class="admin-dialog__header">
<h2 id="motscles-bulk-merge-title">Fusionner des mots-clés</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 <strong><span id="motscles-bulk-merge-count"></span> mot(s)-clé(s)</strong> sélectionné(s) dans&nbsp;:</p>
<select id="motscles-bulk-merge-target-select" class="admin-select--inline" style="margin-top:var(--space-xs); width:100%" required>
<option value="">— Choisir la destination —</option>
</select>
</div>
<div class="admin-dialog__footer">
<button type="button" class="btn btn--warning" onclick="motsclesExecBulkMerge()">Fusionner</button>
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<dialog id="motscles-bulk-delete-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="motscles-bulk-delete-title">
<div class="admin-dialog__header">
<h2 id="motscles-bulk-delete-title">Supprimer des mots-clés</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="motscles-bulk-delete-count"></span> mot(s)-clé(s)</strong>&nbsp;? Cette action est irréversible.</p>
</div>
<div class="admin-dialog__footer">
<button type="button" class="btn btn--danger" onclick="motsclesExecBulkDelete()">Supprimer</button>
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
</div>
</dialog>
<script>
let _motsclesPendingForm = null;
function motsclesConfirmDelete(btn, name) {
_motsclesPendingForm = btn.closest('form');
document.getElementById('motscles-delete-name').textContent = name;
document.getElementById('motscles-delete-dialog').showModal();
}
// ── Inline rename via HTMX ──────────────────────────────────────────────
function motsclesStartRename(id) {
var cell = document.getElementById('motscles-name-' + id);
var csrf = document.querySelector('input[name="csrf_token"]').value;
cell.innerHTML = '<form hx-post="/admin/actions/tag.php" hx-target="#motscles-table-wrap" hx-swap="innerHTML" class="admin-inline-form">'
+ '<input type="hidden" name="csrf_token" value="' + csrf + '">'
+ '<input type="hidden" name="action" value="rename">'
+ '<input type="hidden" name="return" value="/admin/contenus.php">'
+ '<input type="hidden" name="tag_id" value="' + id + '">'
+ '<input type="text" name="new_name" value="' + cell.getAttribute('data-name') + '" required class="admin-input--inline">'
+ '<button type="submit" class="admin-icon-btn admin-icon-btn--edit" title="Valider">'
+ '<?= icon('check-circle') ?>'
+ '</button>'
+ '<button type="button" class="admin-icon-btn admin-icon-btn--delete" onclick="motsclesCancelRename(' + id + ')" title="Annuler">'
+ '<?= icon('x-close') ?>'
+ '</button></form>';
cell.querySelector('input').focus();
}
function motsclesCancelRename(id) {
var cell = document.getElementById('motscles-name-' + id);
cell.innerHTML = '<span class="tag-name-cell">' + cell.getAttribute('data-name') + '</span>'
+ '<button type="button" class="admin-icon-btn admin-icon-btn--edit" title="Renommer" onclick="motsclesStartRename(' + id + ')">'
+ '<?= icon('pencil-note') ?>'
+ '</button>';
}
function motsclesSubmitPending() {
if (_motsclesPendingForm) _motsclesPendingForm.submit();
}
function motsclesToggleAll(src) {
document.querySelectorAll('input[name="selected_tags[]"]').forEach(cb => cb.checked = src.checked);
motsclesUpdateBulk();
}
function motsclesUpdateBulk() {
const n = document.querySelectorAll('input[name="selected_tags[]"]:checked').length;
document.getElementById('motscles-selected-count').textContent = n;
const bar = document.getElementById('motscles-bulk-actions');
const wrap = document.getElementById('motscles-table-wrap');
const visible = n > 1;
bar.style.display = visible ? 'flex' : 'none';
// Force reflow then read bar height for sticky th offset
if (visible) {
requestAnimationFrame(() => {
wrap.style.setProperty('--sticky-top', bar.offsetHeight + 'px');
});
} else {
wrap.style.setProperty('--sticky-top', '0px');
}
}
function motsclesConfirmBulkMerge() {
const checked = document.querySelectorAll('input[name="selected_tags[]"]:checked');
if (checked.length < 2) return;
document.getElementById('motscles-bulk-merge-count').textContent = checked.length;
const sel = document.getElementById('motscles-bulk-merge-target-select');
sel.innerHTML = '<option value="">— Choisir la destination —</option>';
checked.forEach(cb => {
const tr = cb.closest('tr');
sel.innerHTML += '<option value="' + cb.value + '">' + tr.querySelector('td:nth-child(2)').textContent.trim() + '</option>';
});
document.getElementById('motscles-bulk-merge-dialog').showModal();
}
function motsclesExecBulkMerge() {
const targetId = document.getElementById('motscles-bulk-merge-target-select').value;
if (!targetId) return;
document.getElementById('motscles-bulk-target').value = targetId;
const container = document.getElementById('motscles-bulk-checkboxes');
container.innerHTML = '';
document.querySelectorAll('input[name="selected_tags[]"]:checked').forEach(cb => {
const inp = document.createElement('input');
inp.type = 'hidden';
inp.name = 'selected_tags[]';
inp.value = cb.value;
container.appendChild(inp);
});
document.getElementById('motscles-bulk-merge-dialog').close();
document.getElementById('motscles-bulk-form').submit();
}
function motsclesCancelSelection() {
document.querySelectorAll('input[name="selected_tags[]"]').forEach(cb => cb.checked = false);
motsclesUpdateBulk();
}
function motsclesConfirmBulkDelete() {
const checked = document.querySelectorAll('input[name="selected_tags[]"]:checked');
if (checked.length < 1) return;
document.getElementById('motscles-bulk-delete-count').textContent = checked.length;
document.getElementById('motscles-bulk-delete-dialog').showModal();
}
function motsclesExecBulkDelete() {
const container = document.getElementById('motscles-bulk-checkboxes');
container.innerHTML = '';
document.querySelectorAll('input[name="selected_tags[]"]:checked').forEach(cb => {
const inp = document.createElement('input');
inp.type = 'hidden';
inp.name = 'selected_tags[]';
inp.value = cb.value;
container.appendChild(inp);
});
document.getElementById('motscles-bulk-form').querySelector('input[name="action"]').value = 'delete_bulk';
document.getElementById('motscles-bulk-delete-dialog').close();
document.getElementById('motscles-bulk-form').submit();
}
document.addEventListener('htmx:afterSwap', function(evt) {
if (evt.target.id === 'motscles-table-wrap') {
document.querySelectorAll('input[name="selected_tags[]"]').forEach(cb => cb.addEventListener('change', motsclesUpdateBulk));
motsclesUpdateBulk();
}
});
</script>
<script>
(function () {
var otScript = document.createElement('script');
otScript.src = '<?= App::assetV('/assets/js/vendor/overtype.min.js') ?>';
document.head.appendChild(otScript);
var handlerScript = document.createElement('script');
handlerScript.src = '<?= App::assetV('/assets/js/app/autosave-handler.js') ?>';
document.head.appendChild(handlerScript);
})();
</script>