mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
refactor: extract inline JS into app/ modules, remove dead overtype-webcomponent
- Remove overtype-webcomponent.min.js (zero references) - Extract copyLogContent + fallbackCopy + HTMX tab-updater → app/admin-logs.js (removes duplicate from both system.php and parametres.php) - Extract copyUrl → app/clipboard.js (shared by acces.php) - Extract tag/language pill-search logic → app/pill-search.js Generalized with data-pill-search attributes, auto-inits via DOMContentLoaded + htmx:afterSwap - Extract access-request form handler → app/access-request.js (was inline in templates/public/tfe.php) Files created: admin-logs.js, clipboard.js, pill-search.js, access-request.js Files modified: 9 templates/controllers to drop inline scripts and reference external JS files
This commit is contained in:
@@ -31,7 +31,7 @@ $maxLanguages = $maxLanguages ?? 10;
|
||||
$required = $required ?? false;
|
||||
$langCount = count($selectedLanguages);
|
||||
?>
|
||||
<div id="<?= htmlspecialchars($id) ?>-search-container">
|
||||
<div id="<?= htmlspecialchars($id) ?>-search-container" data-pill-search data-pill-name="<?= htmlspecialchars($name) ?>" data-pill-max="<?= (int)$maxLanguages ?>" data-pill-min="0" data-pill-required="0" data-pill-role="lang">
|
||||
<span class="admin-row-label"><?= htmlspecialchars($label) ?><span id="language-autre-required"><?= $required ? ' <span class="asterisk">*</span>' : '' ?></span></span>
|
||||
<div class="tag-search-wrapper">
|
||||
<?php if ($hint): ?>
|
||||
@@ -79,183 +79,24 @@ $langCount = count($selectedLanguages);
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inline script for the interactive behaviour (no external JS required) -->
|
||||
<script>
|
||||
(function() {
|
||||
const container = document.getElementById(<?= json_encode($id . '-search-container') ?>);
|
||||
if (!container || container._langSearchInit) return;
|
||||
container._langSearchInit = true;
|
||||
|
||||
const pills = document.getElementById(<?= json_encode($id . '-pills') ?>);
|
||||
const search = document.getElementById(<?= json_encode($id . '-search') ?>);
|
||||
const dropdown = document.getElementById(<?= json_encode($id . '-suggestions') ?>);
|
||||
const countEl = document.getElementById(<?= json_encode($id . '-count') ?>);
|
||||
const counter = document.getElementById(<?= json_encode($id . '-counter') ?>);
|
||||
const maxLanguages = <?= (int)$maxLanguages ?>;
|
||||
const inputName = <?= json_encode($name) ?>;
|
||||
let selectedIdx = -1;
|
||||
|
||||
function updateCount() {
|
||||
const n = pills.querySelectorAll('.tag-pill').length;
|
||||
if (countEl) countEl.textContent = n + '/' + maxLanguages;
|
||||
if (counter) counter.style.display = (n > 0) ? '' : 'none';
|
||||
|
||||
// Toggle the checkbox-list asterisk: if any "autre" language pill
|
||||
// is present, the checkbox list is no longer required.
|
||||
const asteriskEl = document.getElementById('languages-required-asterisk');
|
||||
if (asteriskEl) {
|
||||
const checkboxes = document.querySelectorAll('#languages-fieldset input[type="checkbox"]:checked');
|
||||
asteriskEl.innerHTML = (n === 0 && checkboxes.length === 0) ? ' <span class="asterisk">*</span>' : '';
|
||||
}
|
||||
|
||||
// Show/hide search input based on max
|
||||
const wrap = container.querySelector('.tag-search-input-wrap');
|
||||
const maxMsg = container.querySelector('.tag-search-max-msg');
|
||||
if (n >= maxLanguages) {
|
||||
if (wrap) wrap.style.display = 'none';
|
||||
if (maxMsg) maxMsg.style.display = '';
|
||||
} else {
|
||||
if (wrap) {
|
||||
wrap.style.display = '';
|
||||
if (search) search.style.display = '';
|
||||
}
|
||||
if (maxMsg) maxMsg.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Lowercase, collapse spaces, trim, ucfirst for display
|
||||
function normalizeLang(name) {
|
||||
return name.trim().replace(/\s+/g, ' ').toLowerCase();
|
||||
}
|
||||
|
||||
function ucfirst(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
// Check if language already exists in pills (case-insensitive)
|
||||
function langAlreadyAdded(name) {
|
||||
const norm = normalizeLang(name);
|
||||
const existing = pills.querySelectorAll('.tag-pill-name');
|
||||
for (const el of existing) {
|
||||
if (normalizeLang(el.textContent) === norm) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove a pill
|
||||
pills.addEventListener('click', function(e) {
|
||||
const btn = e.target.closest('.tag-pill-remove');
|
||||
if (!btn) return;
|
||||
const pill = btn.closest('.tag-pill');
|
||||
pill.remove();
|
||||
updateCount();
|
||||
// Re-enable search field visibility
|
||||
const wrap = container.querySelector('.tag-search-input-wrap');
|
||||
const searchInput = container.querySelector('.tag-search-input');
|
||||
if (wrap && searchInput) {
|
||||
wrap.style.display = '';
|
||||
searchInput.style.display = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Highlight a suggestion by index
|
||||
function highlight(idx) {
|
||||
const items = dropdown.querySelectorAll('.tag-search-item');
|
||||
items.forEach(function(item, i) {
|
||||
if (i === idx) {
|
||||
item.classList.add('tag-search-item--highlight');
|
||||
} else {
|
||||
item.classList.remove('tag-search-item--highlight');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Select a suggestion by button element
|
||||
function selectLang(btn) {
|
||||
const langName = normalizeLang(btn.getAttribute('data-tag-name') || '');
|
||||
if (!langName) return;
|
||||
|
||||
if (langAlreadyAdded(langName)) return;
|
||||
if (pills.querySelectorAll('.tag-pill').length >= maxLanguages) return;
|
||||
|
||||
const escapedName = htmlEscape(langName);
|
||||
const pill = document.createElement('span');
|
||||
pill.className = 'tag-pill';
|
||||
pill.innerHTML = '<input type="hidden" name="' + inputName + '[]" value="' + escapedName + '">'
|
||||
+ '<span class="tag-pill-name">' + escapedName + '</span>'
|
||||
+ '<button type="button" class="tag-pill-remove" title="Retirer \u00AB\u00A0' + escapedName + '\u00A0\u00BB" aria-label="Retirer ' + escapedName + '">'
|
||||
+ '<svg width="16" height="16" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM112,168V104a8,8,0,0,1,16,0v64a8,8,0,0,1-16,0Zm48,0V104a8,8,0,0,1,16,0v64a8,8,0,0,1-16,0Z"></path></svg>'
|
||||
+ '</button>';
|
||||
pills.appendChild(pill);
|
||||
updateCount();
|
||||
search.value = '';
|
||||
dropdown.innerHTML = '';
|
||||
selectedIdx = -1;
|
||||
search.focus();
|
||||
}
|
||||
|
||||
// Click on suggestion
|
||||
dropdown.addEventListener('click', function(e) {
|
||||
console.log('[lang-search] dropdown click, target:', e.target.tagName, e.target.className);
|
||||
const btn = e.target.closest('.tag-search-item');
|
||||
if (!btn) { console.log('[lang-search] no .tag-search-item found in click path'); return; }
|
||||
console.log('[lang-search] found btn:', btn.getAttribute('data-tag-name'), btn.className);
|
||||
selectLang(btn);
|
||||
});
|
||||
|
||||
// Keyboard navigation
|
||||
search.addEventListener('keydown', function(e) {
|
||||
const items = dropdown.querySelectorAll('.tag-search-item');
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
if (items.length === 0) return;
|
||||
if (e.key === 'ArrowDown') {
|
||||
selectedIdx = (selectedIdx + 1) % items.length;
|
||||
} else {
|
||||
selectedIdx = selectedIdx <= 0 ? items.length - 1 : selectedIdx - 1;
|
||||
}
|
||||
highlight(selectedIdx);
|
||||
} else if (e.key === 'Enter') {
|
||||
if (items.length > 0) {
|
||||
e.preventDefault();
|
||||
if (selectedIdx >= 0 && selectedIdx < items.length) {
|
||||
selectLang(items[selectedIdx]);
|
||||
} else {
|
||||
selectLang(items[0]);
|
||||
}
|
||||
}
|
||||
} else if (e.key === 'Escape') {
|
||||
dropdown.innerHTML = '';
|
||||
selectedIdx = -1;
|
||||
}
|
||||
});
|
||||
|
||||
// Hide dropdown on blur (after a tiny delay so click events fire)
|
||||
search.addEventListener('blur', function() {
|
||||
setTimeout(function() {
|
||||
if (!dropdown.contains(document.activeElement)) {
|
||||
console.log('[lang-search] blur: hiding dropdown');
|
||||
dropdown.innerHTML = '';
|
||||
selectedIdx = -1;
|
||||
}
|
||||
}, 150);
|
||||
});
|
||||
|
||||
// Log HTMX responses
|
||||
document.body.addEventListener('htmx:afterSwap', function(e) {
|
||||
if (e.detail.target && e.detail.target.id === '<?= htmlspecialchars($id) ?>-suggestions') {
|
||||
console.log('[lang-search] htmx:afterSwap, target:', e.detail.target.id, 'html length:', e.detail.target.innerHTML.length);
|
||||
console.log('[lang-search] innerHTML:', e.detail.target.innerHTML);
|
||||
}
|
||||
});
|
||||
|
||||
function htmlEscape(str) {
|
||||
const el = document.createElement('span');
|
||||
el.textContent = str;
|
||||
return el.innerHTML;
|
||||
// Language-specific: toggle checkbox-list asterisk based on pills presence
|
||||
(function () {
|
||||
var container = document.getElementById(<?= json_encode($id . '-search-container') ?>);
|
||||
if (!container) return;
|
||||
var pills = container.querySelector('.tag-search-pills');
|
||||
if (!pills) return;
|
||||
function check() {
|
||||
var asteriskEl = document.getElementById('languages-required-asterisk');
|
||||
if (!asteriskEl) return;
|
||||
var n = pills.querySelectorAll('.tag-pill').length;
|
||||
var checkboxes = document.querySelectorAll('#languages-fieldset input[type="checkbox"]:checked');
|
||||
asteriskEl.innerHTML = (n === 0 && checkboxes.length === 0) ? ' <span class="asterisk">*</span>' : '';
|
||||
}
|
||||
var observer = new MutationObserver(check);
|
||||
observer.observe(pills, { childList: true });
|
||||
check();
|
||||
})();
|
||||
</script>
|
||||
|
||||
<?php
|
||||
unset($name, $label, $placeholder, $hint, $hxPost, $selectedLanguages, $id, $maxLanguages, $langCount, $required);
|
||||
|
||||
Reference in New Issue
Block a user