// ======================================================================= // Notes Application - UI & Utilities Module (Native ES Module) // ======================================================================= export function escapeHtml(str) { if (!str) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } export function escapeRegex(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } export function slugify(text) { if (!text) return 'book'; return text.toString().toLowerCase().trim() .replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); } export function shuffleArray(array) { const arr = [...array]; for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; } return arr; } // ----------------------------------------------------------------------- // Toast Notifications // ----------------------------------------------------------------------- export function showToast(message, type = 'info') { let toastContainer = document.getElementById('notes-toast-container'); if (!toastContainer) { toastContainer = document.createElement('div'); toastContainer.id = 'notes-toast-container'; toastContainer.style.cssText = ` position: fixed; bottom: 24px; right: 24px; z-index: 10000; display: flex; flex-direction: column; gap: 8px; pointer-events: none; `; document.body.appendChild(toastContainer); } const toast = document.createElement('div'); const colors = { success: '#10b981', error: '#ef4444', warning: '#f59e0b', info: '#3b82f6' }; const bg = colors[type] || colors.info; toast.style.cssText = ` background: ${bg}; color: #ffffff; padding: 10px 18px; border-radius: 8px; font-size: 14px; font-weight: 500; box-shadow: 0 4px 14px rgba(0,0,0,0.25); opacity: 0; transform: translateY(12px); transition: all 0.25s ease; pointer-events: auto; max-width: 340px; `; toast.textContent = message; toastContainer.appendChild(toast); requestAnimationFrame(() => { toast.style.opacity = '1'; toast.style.transform = 'translateY(0)'; }); setTimeout(() => { toast.style.opacity = '0'; toast.style.transform = 'translateY(12px)'; setTimeout(() => toast.remove(), 250); }, 3500); } // ----------------------------------------------------------------------- // Theme Management // ----------------------------------------------------------------------- export function initTheme() { const themeToggle = document.getElementById('theme-toggle'); const mobileThemeBtn = document.getElementById('mobile-theme-btn'); const savedTheme = localStorage.getItem('notes_theme') || 'dark'; document.documentElement.setAttribute('data-theme', savedTheme); function toggle() { const current = document.documentElement.getAttribute('data-theme'); const next = current === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', next); localStorage.setItem('notes_theme', next); } if (themeToggle) { themeToggle.addEventListener('click', toggle); } if (mobileThemeBtn) { mobileThemeBtn.addEventListener('click', () => { toggle(); showToast(`Тема переключена`, 'info'); }); } } // ----------------------------------------------------------------------- // Modal Open / Close Helpers // ----------------------------------------------------------------------- export function openModal(modalElem) { if (!modalElem) return; modalElem.classList.remove('hidden'); } export function closeModal(modalElem) { if (!modalElem) return; modalElem.classList.add('hidden'); } // ----------------------------------------------------------------------- // Header Stat Counters // ----------------------------------------------------------------------- export function updateHeaderStats(total = 0, today = 0, srsCount = 0) { const statTotal = document.getElementById('stat-total'); const statToday = document.getElementById('stat-today'); const srsBadge = document.getElementById('srs-badge'); const tabSrsBadge = document.getElementById('tab-srs-badge'); const drawerSrsBadge = document.getElementById('drawer-srs-badge'); const drawerStatTotal = document.getElementById('drawer-stat-total'); const drawerStatToday = document.getElementById('drawer-stat-today'); const drawerStatDue = document.getElementById('drawer-stat-due'); const formattedToday = today > 0 ? `+${today}` : today; if (statTotal) statTotal.textContent = total; if (statToday) statToday.textContent = formattedToday; if (drawerStatTotal) drawerStatTotal.textContent = total; if (drawerStatToday) drawerStatToday.textContent = formattedToday; if (drawerStatDue) drawerStatDue.textContent = srsCount; if (srsBadge) { srsBadge.textContent = srsCount; srsBadge.style.display = srsCount > 0 ? 'inline-block' : 'none'; } if (tabSrsBadge) { tabSrsBadge.textContent = srsCount; tabSrsBadge.classList.toggle('hidden', srsCount === 0); } if (drawerSrsBadge) { drawerSrsBadge.textContent = srsCount; drawerSrsBadge.classList.toggle('hidden', srsCount === 0); } } // ----------------------------------------------------------------------- // Audio Player Helper (Safe stub / audio stream) // ----------------------------------------------------------------------- export function playAudio(url) { if (!url) return; try { const audio = new Audio(url); audio.play().catch(e => console.warn('Audio playback not supported or blocked:', e)); } catch (e) { console.warn('Audio play error:', e); } }