#gzip #extract-inline-js enable gzip in nginx + move ~730 lines of inline JS to 15 external files

This commit is contained in:
Pontoporeia
2026-06-24 12:56:09 +02:00
parent 0ff6ee78d9
commit e74f9210c5
35 changed files with 1198 additions and 843 deletions

View File

@@ -179,40 +179,7 @@
</form>
</dialog>
<script>
document.getElementById('open-create-dialog').addEventListener('click', () => {
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) {
const input = document.getElementById('url-' + id);
navigator.clipboard.writeText(input.value).then(() => {
const btn = event.target.closest('button');
const orig = btn.textContent;
btn.textContent = '✓ Copié';
setTimeout(() => { btn.textContent = orig; }, 1200);
});
}
function openPasswordDialog(id, hasPassword) {
document.getElementById('password-link-id').value = id;
const info = document.getElementById('password-current-info');
info.textContent = hasPassword
? 'Un mot de passe est actuellement configuré. Entrez-en un nouveau ou laissez vide pour le supprimer.'
: 'Aucun mot de passe configuré.';
document.getElementById('password-dialog').showModal();
}
</script>
<script src="<?= App::assetV('/assets/js/app/admin-acces-sharelink.js') ?>"></script>
<!-- Delete link confirm -->
<dialog id="delete-link-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="delete-link-title">

View File

@@ -583,76 +583,10 @@
</form>
</dialog>
<script>
// ── Forward PHP flash data to JS globals ──────────────────────────────────
const BASE_URL = <?= json_encode($baseUrl) ?>;
const _newLinkPassword = <?= json_encode($newLinkPassword ?? '') ?>;
const _newLinkSlug = <?= json_encode($newLinkSlug ?? '') ?>;
// ── Show result dialogs after redirect ────────────────────────────────────
if (_newLinkSlug && _newLinkPassword) {
document.getElementById('create-result-password').value = _newLinkPassword;
document.getElementById('create-result-url').value = BASE_URL + '/partage/' + _newLinkSlug;
document.getElementById('create-result-dialog').showModal();
}
document.getElementById('open-create-dialog').addEventListener('click', () => {
document.getElementById('create-dialog').showModal();
});
function copyUrl(id) {
const input = document.getElementById('url-' + id);
navigator.clipboard.writeText(input.value).then(() => {
const btn = event.target.closest('button');
if (btn) { const orig = btn.getAttribute('title') || ''; btn.setAttribute('title', '✓ Copié'); setTimeout(() => btn.setAttribute('title', orig), 1200); }
});
}
function copyUrlFrom(el) {
navigator.clipboard.writeText(el.value).then(() => {
const btn = el.nextElementSibling;
if (btn) { const orig = btn.textContent; btn.textContent = '✓ Copié'; setTimeout(() => { btn.textContent = orig; }, 1200); }
});
}
function copyTextToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
const btn = event?.target?.closest('button');
if (btn) { const orig = btn.getAttribute('title') || ''; btn.setAttribute('title', '✓ Copié'); setTimeout(() => btn.setAttribute('title', orig), 1200); }
}).catch(() => {});
}
function openEditDialog(id, name, hasPassword, expiresVal) {
document.getElementById('edit-link-id').value = id;
document.getElementById('edit-name').value = name || '';
document.getElementById('edit-expires').value = expiresVal || '';
document.getElementById('edit-dialog').showModal();
}
function openApproveDialog(requestId) {
document.getElementById('approve-request-id').value = requestId;
document.getElementById('approve-dialog').showModal();
}
function openRejectDialog(requestId) {
document.getElementById('reject-request-id').value = requestId;
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();
}
function openDeleteArchivedLinkDialog(id) {
document.getElementById('delete-archived-link-id').value = id;
document.getElementById('delete-archived-link-dialog').showModal();
}
</script>
<meta name="acces-base-url" content="<?= htmlspecialchars($baseUrl) ?>">
<meta name="acces-new-link-password" content="<?= htmlspecialchars($newLinkPassword ?? '') ?>">
<meta name="acces-new-link-slug" content="<?= htmlspecialchars($newLinkSlug ?? '') ?>">
<script src="<?= App::assetV('/assets/js/app/admin-acces.js') ?>"></script>
<!-- Archive link confirm -->
<dialog id="archive-link-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="archive-link-title">

View File

@@ -10,7 +10,9 @@
hx-post="/admin/actions/apropos.php"
hx-trigger="change delay:1500ms, input delay:1500ms"
hx-swap="none"
hx-on::after-request="handleAutosaveResponse(event)">
hx-on::after-request="handleAutosaveResponse(event)"
data-apropos-key="<?= $aproposKey ?>"
data-apropos-group-count="<?= count($groups) ?>">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
<input type="hidden" name="apropos_key" value="<?= htmlspecialchars($aproposKey) ?>">
@@ -93,83 +95,4 @@
</fieldset>
</template>
</form>
<script>
(function() {
var key = '<?= $aproposKey ?>';
var form = document.getElementById('apropos-form-' + key);
var groupCount = <?= count($groups) ?>;
var entryTpl = document.getElementById('entry-template-f-' + key).innerHTML;
var groupTpl = document.getElementById('group-template-f-' + key).innerHTML;
function reindexGroups() {
var fieldsets = form.querySelectorAll('fieldset.apropos-group');
groupCount = fieldsets.length;
fieldsets.forEach(function(fs, i) {
var newIdx = i;
var legend = fs.querySelector('legend');
if (legend) legend.textContent = 'Contact ' + (newIdx + 1);
// Update name attributes on all inputs
fs.querySelectorAll('input').forEach(function(inp) {
if (inp.name) {
inp.name = inp.name.replace(/groups\[\d+\]/, 'groups[' + newIdx + ']');
}
if (inp.id) {
inp.id = inp.id.replace(/(group_f_contacts_|entry_f_contacts_)\d+/, '$1' + newIdx);
}
});
// Update for attributes on labels
fs.querySelectorAll('label[for]').forEach(function(lbl) {
lbl.setAttribute('for', lbl.getAttribute('for').replace(/(group_f_contacts_|entry_f_contacts_)\d+/, '$1' + newIdx));
});
// Update data-group on add-entry button
var addBtn = fs.querySelector('.add-entry-btn-f');
if (addBtn) addBtn.dataset.group = newIdx;
});
}
function bindAddEntry(btn) {
btn.addEventListener('click', function() {
var gi = parseInt(this.dataset.group);
var fieldset = this.closest('fieldset');
var entryCount = fieldset.querySelectorAll('.apropos-entry').length;
var html = entryTpl.replaceAll('{{gi}}', gi).replaceAll('{{ei}}', entryCount);
this.insertAdjacentHTML('beforebegin', html);
});
}
function bindDeleteGroup(btn) {
btn.addEventListener('click', function() {
var fieldset = this.closest('fieldset');
if (fieldset) {
fieldset.remove();
reindexGroups();
}
});
}
// Bind existing buttons
form.querySelectorAll('.add-entry-btn-f').forEach(bindAddEntry);
form.querySelectorAll('.delete-group-btn-f').forEach(bindDeleteGroup);
form.querySelector('.add-group-btn-f').addEventListener('click', function() {
groupCount++;
var html = groupTpl.replaceAll('{{gi}}', groupCount);
this.insertAdjacentHTML('beforebegin', html);
var newGroup = this.previousElementSibling;
if (newGroup && newGroup.classList.contains('apropos-group')) {
var addBtn = newGroup.querySelector('.add-entry-btn-f');
if (addBtn) {
addBtn.dataset.group = groupCount;
bindAddEntry(addBtn);
}
var delBtn = newGroup.querySelector('.delete-group-btn-f');
if (delBtn) bindDeleteGroup(delBtn);
}
});
})();
</script>
<script src="<?= App::assetV('/assets/js/app/admin-contacts-form.js') ?>"></script>

View File

@@ -92,47 +92,7 @@
</div>
</template>
<script>
(function() {
var form = document.getElementById('sidebar-links-form');
var tpl = document.getElementById('sidebar-link-tpl');
var tplHtml = tpl.innerHTML;
function reindexLinks() {
var rows = form.querySelectorAll('.sidebar-link-row');
rows.forEach(function(row, i) {
row.querySelectorAll('input').forEach(function(inp) {
if (inp.name) {
inp.name = inp.name.replace(/links\[\d+\]/, 'links[' + i + ']');
}
if (inp.id) {
inp.id = inp.id.replace(/sl_\d+/, 'sl_' + i);
}
});
row.querySelectorAll('label[for]').forEach(function(lbl) {
lbl.setAttribute('for', lbl.getAttribute('for').replace(/sl_\d+/, 'sl_' + i));
});
});
}
// Event delegation for remove buttons (including dynamically added)
form.addEventListener('click', function(e) {
if (!e.target.closest('.remove-sidebar-link-btn')) return;
e.preventDefault();
e.target.closest('.sidebar-link-row').remove();
reindexLinks();
});
// Add button
var addBtn = document.getElementById('add-sidebar-link-btn');
addBtn.addEventListener('click', function() {
var rows = form.querySelectorAll('.sidebar-link-row');
var idx = rows.length;
var html = tplHtml.split('{{li}}').join(idx);
this.insertAdjacentHTML('beforebegin', html);
});
})();
</script>
<script src="<?= App::assetV('/assets/js/app/sidebar-links-editor.js') ?>"></script>
<?php elseif ($editType === 'page' && $pageSlug !== 'about'): ?>
<form action="/admin/actions/page.php" method="post" class="admin-form admin-form--full-editor">

View File

@@ -289,16 +289,9 @@
</div>
</dialog>
<script src="<?= App::assetV('/assets/js/app/admin-contenus-langues.js') ?>"></script>
<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 ──────────────────────────────────────────────
// ── Inline rename via HTMX (needs PHP-generated icon SVGs) ───────────────
function languesStartRename(id) {
var cell = document.getElementById('lang-name-' + id);
var csrf = document.querySelector('input[name="csrf_token"]').value;
@@ -324,96 +317,6 @@ function languesCancelRename(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>
<!-- ═══════════════════════════════════════════════════════════════
@@ -468,16 +371,9 @@ document.addEventListener('htmx:afterSwap', function(evt) {
</div>
</dialog>
<script src="<?= App::assetV('/assets/js/app/admin-contenus-motscles.js') ?>"></script>
<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 ──────────────────────────────────────────────
// ── Inline rename via HTMX (needs PHP-generated icon SVGs) ───────────────
function motsclesStartRename(id) {
var cell = document.getElementById('motscles-name-' + id);
var csrf = document.querySelector('input[name="csrf_token"]').value;
@@ -503,96 +399,6 @@ function motsclesCancelRename(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>

View File

@@ -182,16 +182,6 @@
</form>
</dialog>
<script>
function openApproveDialog(requestId) {
document.getElementById('approve-request-id').value = requestId;
document.getElementById('approve-dialog').showModal();
}
function openRejectDialog(requestId) {
document.getElementById('reject-request-id').value = requestId;
document.getElementById('reject-dialog').showModal();
}
</script>
<script src="<?= App::assetV('/assets/js/app/admin-file-access.js') ?>"></script>

View File

@@ -17,35 +17,6 @@
<script><?= $extraJsInline ?></script>
<?php endif; ?>
<script src="/assets/js/vendor/htmx.min.js"></script>
<script>
// Global HTMX debugging for settings checkboxes
document.body.addEventListener('htmx:sendError', function (e) {
console.error('[htmx:sendError] target=', e.target.id, 'detail=', e.detail);
});
document.body.addEventListener('htmx:beforeSend', function (e) {
if (e.target.id && (e.target.id.includes('fieldset-') || e.target.name)) {
console.log('[htmx:beforeSend] name=' + e.target.name + ' checked=' + e.target.checked);
}
});
document.body.addEventListener('htmx:afterSettle', function (e) {
if (e.target && e.target.id === 'toast-region') {
var warn = e.target.querySelector('.toast--warning');
if (warn) { warn.setAttribute('tabindex', '-1'); warn.focus(); }
}
});
// Markdown cheatsheet: close on backdrop click, remove stale dialogs before new one arrives
document.body.addEventListener('htmx:beforeRequest', function(e) {
if (e.detail.requestConfig && e.detail.requestConfig.path === '/admin/markdown-cheatsheet-fragment.php') {
var old = document.getElementById('md-cheatsheet-dialog');
if (old) old.remove();
}
});
document.body.addEventListener('click', function(e) {
if (e.target.tagName === 'DIALOG' && e.target.id === 'md-cheatsheet-dialog') {
e.target.close();
}
});
</script>
<script src="<?= App::assetV('/assets/js/app/htmx-global-setup.js') ?>"></script>
</body>
</html>

View File

@@ -120,7 +120,4 @@ $sortArrow = function(string $col) use ($sortCol, $sortDir): string {
</tbody>
</table>
<script>
document.querySelectorAll('input[name="selected_theses[]"]').forEach(cb => cb.addEventListener('change', updateBulk));
</script>
</div>

View File

@@ -1,15 +1,4 @@
<script>
function toggleAll(src){document.querySelectorAll('input[name="selected_theses[]"]').forEach(cb=>cb.checked=src.checked);updateBulk();}
function updateBulk(){const n=document.querySelectorAll('input[name="selected_theses[]"]:checked').length;const b=document.getElementById('bulk-actions');document.getElementById('selected-count').textContent=n;b.style.display=n>0?'flex':'none';document.getElementById('admin-table-wrap').style.setProperty('--sticky-top',n>0?b.offsetHeight+'px':'0px');}
function getSelectedIds(){return Array.from(document.querySelectorAll('input[name="selected_theses[]"]:checked')).map(cb=>cb.value);}
function confirmBulk(act){const ids=getSelectedIds();if(!ids.length){document.getElementById('no-selection-dialog').showModal();return;}const n=ids.length;document.getElementById('bulk-action-input').value=act;if(act==='delete'){document.getElementById('bulk-delete-count').textContent=n;document.getElementById('bulk-delete-dialog').showModal();}else{document.getElementById('bulk-confirm-word').textContent=act=='publish'?'Publier':'Dépublier';document.getElementById('bulk-confirm-count').textContent=n;document.getElementById('bulk-confirm-dialog').showModal();}}
function execBulk(){const a=document.getElementById('bulk-action-input').value;const f=document.getElementById('bulk-form');f.action = a=='delete' ? 'actions/delete.php' : 'actions/publish.php';const c=document.getElementById('bulk-checkboxes');c.innerHTML='';getSelectedIds().forEach(id=>{const inp=document.createElement('input');inp.type='hidden';inp.name='selected_theses[]';inp.value=id;c.appendChild(inp);});f.submit();}
function confirmExport(){const ids=getSelectedIds();if(!ids.length){document.getElementById('no-selection-dialog').showModal();return;}window.location.href='/admin/actions/export.php?csv=1&ids='+ids.join(',');}
function confirmExportFiles(){const ids=getSelectedIds();if(!ids.length){document.getElementById('no-selection-dialog').showModal();return;}window.location.href='/admin/actions/export.php?files=1&ids='+ids.join(',');}
function confirmDelete(id,title){document.getElementById('delete-thesis-title').textContent=title;document.getElementById('delete-thesis-dialog').showModal();document.getElementById('delete-dialog-confirm').onclick=function(){document.getElementById('delete-form-'+id).submit();};}
document.addEventListener('DOMContentLoaded',()=>{document.querySelectorAll('input[name="selected_theses[]"]').forEach(cb=>cb.addEventListener('change',updateBulk));});
document.addEventListener('htmx:afterSwap',()=>{document.querySelectorAll('input[name="selected_theses[]"]').forEach(cb=>cb.addEventListener('change',updateBulk));updateBulk();});
</script>
<script src="<?= App::assetV('/assets/js/app/admin-index-bulk.js') ?>"></script>
<main id="main-content" class="admin-main--list">
<!-- Title + filters + stats + import all in one toolbar row -->

View File

@@ -481,18 +481,7 @@
</article>
</main>
<script>
// Focus the SMTP field that caused the probe error
(function () {
var form = document.querySelector('form[data-smtp-error-field]');
if (!form) return;
var fieldId = form.getAttribute('data-smtp-error-field');
var el = fieldId ? document.getElementById(fieldId) : null;
if (!el) return;
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
el.focus();
}());
</script>
<script src="<?= App::assetV('/assets/js/app/smtp-error-focus.js') ?>"></script>
<script src="/assets/js/app/admin-logs.js"></script>
<!-- Enable maintenance confirm -->

View File

@@ -12,46 +12,4 @@
</nav>
</aside>
<script>
(function() {
function build() {
var main = document.getElementById('main-content');
var nav = document.getElementById('admin-toc-list');
var aside = document.getElementById('admin-toc');
if (!main || !nav || !aside) return;
var sections = main.querySelectorAll('section[aria-labelledby]');
if (sections.length < 2) { aside.hidden = true; return; }
var items = [];
sections.forEach(function(sec) {
var headingId = sec.getAttribute('aria-labelledby');
var heading = document.getElementById(headingId);
if (!heading) return;
if (!sec.id) sec.id = headingId;
var a = document.createElement('a');
a.href = '#' + sec.id;
a.textContent = heading.textContent.trim();
a.style.display = 'block';
nav.appendChild(a);
items.push({ section: sec, link: a });
});
var observer = new IntersectionObserver(function(entries) {
var best = null, bestRatio = 0;
entries.forEach(function(e) {
if (e.intersectionRatio > bestRatio) { bestRatio = e.intersectionRatio; best = e.target; }
});
items.forEach(function(item) {
item.link.classList.toggle('admin-toc-active', item.section === best);
});
}, { rootMargin: '-10% 0px -70% 0px', threshold: [0, 0.25, 0.5, 0.75, 1] });
items.forEach(function(item) { observer.observe(item.section); });
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', build);
else build();
})();
</script>
<script src="<?= App::assetV('/assets/js/app/admin-toc.js') ?>"></script>

View File

@@ -1,89 +1,30 @@
<script src="<?= App::assetV('/assets/js/app/admin-tags.js') ?>"></script>
<script>
let _pendingTagForm = null;
function tagsToggleAll(src) {
document.querySelectorAll('input[name="selected_tags[]"]').forEach(cb => cb.checked = src.checked);
tagsUpdateBulk();
}
function tagsUpdateBulk() {
const n = document.querySelectorAll('input[name="selected_tags[]"]:checked').length;
document.getElementById('tags-selected-count').textContent = n;
document.getElementById('tags-bulk-actions').style.display = n > 1 ? 'flex' : 'none';
}
function tagsConfirmBulkMerge() {
const checked = document.querySelectorAll('input[name="selected_tags[]"]:checked');
if (checked.length < 2) return;
document.getElementById('bulk-merge-count').textContent = checked.length;
const sel = document.getElementById('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('.tag-name-cell').textContent.trim() + '</option>';
});
document.getElementById('bulk-merge-dialog').showModal();
}
function tagsExecBulkMerge() {
const targetId = document.getElementById('bulk-merge-target-select').value;
if (!targetId) return;
document.getElementById('tags-bulk-target').value = targetId;
const container = document.getElementById('tags-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('bulk-merge-dialog').close();
document.getElementById('tags-bulk-form').submit();
}
// ── Inline rename via HTMX ──────────────────────────────────────────────────
// ── Inline rename via HTMX (needs PHP-generated icon SVGs) ───────────────
function tagsStartRename(id) {
var cell = document.getElementById('tag-name-' + id);
var csrf = document.querySelector('input[name="csrf_token"]').value;
cell.innerHTML = '<form hx-post=\"/admin/actions/tag.php\" hx-target=\"#tags-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=\"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") ?>'
cell.innerHTML = '<form hx-post="/admin/actions/tag.php" hx-target="#tags-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="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=\"tagsCancelRename(' + id + ')\" title=\"Annuler\">'
+ '<?= icon("x-close") ?>'
+ '<button type="button" class="admin-icon-btn admin-icon-btn--delete" onclick="tagsCancelRename(' + id + ')" title="Annuler">'
+ '<?= icon('x-close') ?>'
+ '</button></form>';
cell.querySelector('input').focus();
}
function tagsCancelRename(id) {
var cell = document.getElementById('tag-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=\"tagsStartRename(' + id + ')\">'
+ '<?= icon("pencil-note") ?>'
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="tagsStartRename(' + id + ')">'
+ '<?= icon('pencil-note') ?>'
+ '</button>';
}
function tagsConfirmDelete(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();
}
document.addEventListener('htmx:afterSwap', function(evt) {
if (evt.target.id === 'tags-table-wrap') {
document.querySelectorAll('input[name=\"selected_tags[]\"]').forEach(cb => cb.addEventListener('change', tagsUpdateBulk));
tagsUpdateBulk();
}
});
</script>
<main id="main-content" class="admin-main--list">