// ── Theme + Settings ── import { $, api, getCsrf } from './util.js'; import { state } from './state.js'; import { renderSettingsPanel as renderNotifSettings } from './notifications.js'; const overlay = $('settings-overlay'); const themeList = $('settings-theme-list'); // Render theme list in settings function renderThemes() { if (!themeList) return; const current = localStorage.getItem('hyprcrd-theme') || 'slate'; themeList.innerHTML = ''; for (const [key, t] of Object.entries(window.themes)) { const btn = document.createElement('button'); const active = key === current; btn.className = `flex items-center gap-3 px-3 py-2.5 text-left text-xs border transition-colors rounded ${active ? 'border-t-accent text-t-bright' : 'border-t-border text-t-muted hover:border-t-accent hover:text-t-bright'}`; btn.innerHTML = ` ${t.label} ${active ? '' : ''}`; btn.onclick = () => { window.applyTheme(key); renderThemes(); window._renderBoard?.(); }; themeList.appendChild(btn); } } // Tab switching overlay?.querySelectorAll('.settings-tab').forEach(tab => { tab.addEventListener('click', () => { overlay.querySelectorAll('.settings-tab').forEach(t => t.classList.remove('active')); tab.classList.add('active'); const panel = tab.dataset.tab; overlay.querySelectorAll('[id^="settings-panel-"]').forEach(p => p.classList.add('hidden')); $(`settings-panel-${panel}`)?.classList.remove('hidden'); if (panel === 'sync') window._loadCaldavSettings?.(); if (panel === 'notifications') renderNotifSettings(); if (panel === 'behavior') initBehaviorSettings(); }); }); // Open/Close function updateStats() { const sc = $('stats-cards'); if (sc) sc.textContent = state.cards?.length || 0; const sb = $('stats-buckets'); if (sb) sb.textContent = state.buckets?.length || 0; const tags = new Set(); (state.cards || []).forEach(c => (c.tags || '').split(',').forEach(t => { if (t) tags.add(t); })); const st = $('stats-tags'); if (st) st.textContent = tags.size; } $('settings-btn')?.addEventListener('click', () => { renderThemes(); updateStats(); overlay?.classList.add('open'); }); $('settings-close')?.addEventListener('click', () => overlay?.classList.remove('open')); overlay?.addEventListener('click', e => { if (e.target === overlay) overlay?.classList.remove('open'); }); // Password change $('settings-pw-save')?.addEventListener('click', async () => { const current = $('settings-pw-current')?.value; const newPw = $('settings-pw-new')?.value; const confirm = $('settings-pw-confirm')?.value; const status = $('settings-pw-status'); if (!current || !newPw) { if (status) { status.textContent = 'fill all fields'; status.style.color = 'var(--t-danger)'; } return; } if (newPw !== confirm) { if (status) { status.textContent = 'passwords do not match'; status.style.color = 'var(--t-danger)'; } return; } if (newPw.length < 4) { if (status) { status.textContent = 'min 4 characters'; status.style.color = 'var(--t-danger)'; } return; } const res = await fetch('api/auth/password', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf() }, body: JSON.stringify({ current, new: newPw }), }).then(r => r.json()).catch(() => ({ error: 'network error' })); if (res.ok) { if (status) { status.textContent = res.rekey ? 'password changed — re-encrypting notes...' : 'password changed'; status.style.color = 'var(--t-accent)'; } $('settings-pw-current').value = ''; $('settings-pw-new').value = ''; $('settings-pw-confirm').value = ''; // Trigger sync to re-push notes with new encryption key if (res.rekey) { window._triggerSync?.(); setTimeout(() => { if (status) status.textContent = 'password changed, notes re-encrypted'; }, 3000); } } else { if (status) { status.textContent = res.error || 'failed'; status.style.color = 'var(--t-danger)'; } } }); function initBehaviorSettings() { const sel = $('setting-current-days'); if (sel) { sel.value = localStorage.getItem('hyprcrd-current-days') || '2'; sel.onchange = () => { localStorage.setItem('hyprcrd-current-days', sel.value); window._renderBoard?.(); }; } } // Apply saved theme on load window.applyTheme(localStorage.getItem('hyprcrd-theme') || 'slate');