/* ── Olimpic Nastri — App JS v2 ─────────────────────────────────────────── */ // ── CSRF helper ── function getCsrf() { return document.cookie.split('; ') .find(r => r.startsWith('csrftoken=')) ?.split('=')[1] ?? ''; } // ── Toast notifications ── function showToast(message, type = 'success') { const container = document.getElementById('toast-container'); if (!container) return; const icons = { success: 'bi-check-circle-fill', danger: 'bi-exclamation-triangle-fill', info: 'bi-info-circle-fill', }; const colors = { success: '#15803d', danger: '#dc2626', info: '#4361ee', }; const id = 'toast-' + Date.now(); const html = ` `; container.insertAdjacentHTML('beforeend', html); const toastEl = document.getElementById(id); const bsToast = new bootstrap.Toast(toastEl, { delay: 3000 }); bsToast.show(); toastEl.addEventListener('hidden.bs.toast', () => toastEl.remove()); } // ── Convert Django messages to toasts on page load ── function convertMessagesToToasts() { document.querySelectorAll('[data-toast-message]').forEach(el => { showToast(el.dataset.toastMessage, el.dataset.toastType || 'success'); el.remove(); }); } // ── Progress slider AJAX ── function initSliders() { document.querySelectorAll('.progress-slider').forEach(slider => { if (slider._sliderInit) return; slider._sliderInit = true; const id = slider.dataset.id; const lbl = document.getElementById('lbl-' + id); const saving = document.getElementById('saving-' + id); let timer = null; slider.addEventListener('input', () => { const v = slider.value; if (lbl) lbl.textContent = v + '%'; slider.style.setProperty('--val', v + '%'); }); slider.addEventListener('change', () => { clearTimeout(timer); if (saving) { saving.classList.remove('d-none'); saving.textContent = 'salvataggio…'; } timer = setTimeout(() => { fetch(slider.dataset.url, { method: 'POST', headers: { 'X-CSRFToken': getCsrf(), 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'avanzamento=' + slider.value, }) .then(r => r.json()) .then(d => { if (saving) { saving.textContent = d.ok ? '✓ salvato' : '⚠ errore'; setTimeout(() => saving.classList.add('d-none'), 1500); } }) .catch(() => { if (saving) { saving.textContent = '⚠ errore'; setTimeout(() => saving.classList.add('d-none'), 1500); } }); }, 400); }); }); } // ── Live search dropdown ── function initLiveSearch() { const input = document.getElementById('search-input'); const dropdown = document.getElementById('search-dropdown'); if (!input || !dropdown) return; let timer = null; input.addEventListener('input', () => { const q = input.value.trim(); clearTimeout(timer); if (q.length < 2) { dropdown.classList.remove('show'); dropdown.innerHTML = ''; return; } timer = setTimeout(() => { fetch('/ricerca/?q=' + encodeURIComponent(q), { headers: { 'HX-Request': 'true' } }) .then(r => r.text()) .then(html => { dropdown.innerHTML = html; dropdown.classList.add('show'); }); }, 300); }); // Close dropdown on click outside document.addEventListener('click', (e) => { if (!input.contains(e.target) && !dropdown.contains(e.target)) { dropdown.classList.remove('show'); } }); // Navigate to full search on Enter input.closest('form')?.addEventListener('submit', (e) => { dropdown.classList.remove('show'); }); } // ── Init everything ── function initApp() { convertMessagesToToasts(); initSliders(); initLiveSearch(); initMentions(); } // ── @Mentions autocomplete ── function initMentions() { document.querySelectorAll('textarea.form-control').forEach(textarea => { if (textarea._mentionInit) return; textarea._mentionInit = true; let dropdown = textarea.parentElement.querySelector('.mention-dropdown'); if (!dropdown) { dropdown = document.createElement('div'); dropdown.className = 'mention-dropdown'; textarea.parentElement.style.position = 'relative'; textarea.parentElement.appendChild(dropdown); } let mentionStart = -1; let timer = null; textarea.addEventListener('input', () => { const pos = textarea.selectionStart; const text = textarea.value.substring(0, pos); const atMatch = text.match(/@(\w[\w.]*)$/); if (!atMatch) { dropdown.classList.remove('show'); mentionStart = -1; return; } mentionStart = pos - atMatch[0].length; const query = atMatch[1]; clearTimeout(timer); timer = setTimeout(() => { fetch('/api/utenti/?q=' + encodeURIComponent(query)) .then(r => r.json()) .then(users => { if (!users.length) { dropdown.classList.remove('show'); return; } dropdown.innerHTML = users.map(u => `
${u.username.substring(0,2).toUpperCase()} ${u.nome} @${u.username}
` ).join(''); dropdown.classList.add('show'); }); }, 200); }); dropdown.addEventListener('click', (e) => { const item = e.target.closest('.mention-item'); if (!item) return; const username = item.dataset.username; const before = textarea.value.substring(0, mentionStart); const after = textarea.value.substring(textarea.selectionStart); textarea.value = before + '@' + username + ' ' + after; textarea.focus(); const newPos = mentionStart + username.length + 2; textarea.setSelectionRange(newPos, newPos); dropdown.classList.remove('show'); }); textarea.addEventListener('blur', () => { setTimeout(() => dropdown.classList.remove('show'), 200); }); textarea.addEventListener('keydown', (e) => { if (!dropdown.classList.contains('show')) return; const items = dropdown.querySelectorAll('.mention-item'); const active = dropdown.querySelector('.mention-item.active'); let idx = Array.from(items).indexOf(active); if (e.key === 'ArrowDown') { e.preventDefault(); if (active) active.classList.remove('active'); idx = (idx + 1) % items.length; items[idx].classList.add('active'); } else if (e.key === 'ArrowUp') { e.preventDefault(); if (active) active.classList.remove('active'); idx = idx <= 0 ? items.length - 1 : idx - 1; items[idx].classList.add('active'); } else if (e.key === 'Enter' && active) { e.preventDefault(); active.click(); } else if (e.key === 'Escape') { dropdown.classList.remove('show'); } }); }); } // Run on initial page load document.addEventListener('DOMContentLoaded', initApp); // Re-init after HTMX swaps document.addEventListener('htmx:afterSettle', initApp);