Add Mots-clés and Langues management to contenus page

- Add searchLanguages, getAllLanguagesWithCount, renameLanguage, mergeLanguage, deleteLanguage to Database
- Create actions/language.php handler with rename/merge/merge_bulk/delete actions
- Add merge_bulk action to actions/tag.php
- Add Mots-clés section to contenus template with HTMX search, select checkboxes, rename/delete/merge buttons, and multi-select merge toolbar
- Add Langues section to contenus template with same pattern
- Create contenus-tags-fragment.php and contenus-languages-fragment.php HTMX fragments
- Remove form-settings- from flat-fieldset CSS selector so fieldsets in contenus retain border/padding
- contenus.php: add 'Gérer les mots-clés' link to /admin/tags.php
- contenus.php: add Langues fieldset with HTMX search + table (rename/merge/delete/bulk)
- tags.php: add HTMX search bar, checkbox column, bulk merge toolbar
- Create tags-fragment.php and contenus-langues-fragment.php for HTMX
- Remove tab component and associated CSS
- Simplify JS: separate tags/langues-prefixed functions
- Fix redirects: tag.php defaults to /admin/tags.php, supports return override
- Keep tags.php standalone page and Mots-clés button unchanged
This commit is contained in:
Pontoporeia
2026-05-10 12:13:26 +02:00
parent 494675d78c
commit 396cf19e9f
13 changed files with 814 additions and 195 deletions

View File

@@ -0,0 +1,78 @@
<?php
/**
* admin-toc.php — sidebar table-of-contents for long admin pages.
*
* Scans <section aria-labelledby="..."> elements in #main-content and builds a
* slim vertical nav. Uses IntersectionObserver to highlight the active section.
*
* Usage: include APP_ROOT . '/templates/admin/partials/admin-toc.php';
*/
?>
<nav id="admin-toc" class="admin-toc" aria-label="Sur cette page">
<ul class="admin-toc-list" id="admin-toc-list">
<!-- populated by JS -->
</ul>
</nav>
<script>
(function() {
var main = document.getElementById('main-content');
if (!main) return;
var tocList = document.getElementById('admin-toc-list');
if (!tocList) return;
// Find all labelled sections
var sections = main.querySelectorAll('section[aria-labelledby]');
if (sections.length < 2) {
document.getElementById('admin-toc').style.display = 'none';
return;
}
var items = [];
sections.forEach(function(sec) {
var headingId = sec.getAttribute('aria-labelledby');
var heading = document.getElementById(headingId);
if (!heading) return;
var li = document.createElement('li');
var a = document.createElement('a');
a.href = '#' + sec.id;
a.textContent = heading.textContent;
a.setAttribute('data-toc-target', sec.id);
li.appendChild(a);
tocList.appendChild(li);
// Ensure section has an id for anchoring
if (!sec.id) {
sec.id = headingId;
}
items.push({ section: sec, link: a });
});
// IntersectionObserver: highlight the link whose section is most visible
var observer = new IntersectionObserver(function(entries) {
var best = null;
var bestRatio = 0;
entries.forEach(function(e) {
if (e.intersectionRatio > bestRatio) {
bestRatio = e.intersectionRatio;
best = e.target;
}
});
if (best) {
items.forEach(function(item) {
var isActive = item.section === best;
item.link.classList.toggle('admin-toc-active', isActive);
});
}
}, {
rootMargin: '-10% 0px -70% 0px',
threshold: [0, 0.25, 0.5, 0.75, 1]
});
items.forEach(function(item) { observer.observe(item.section); });
})();
</script>