// ── CalDAV Settings + Calendar Config ── // // Genuinely CalDAV-scoped concerns only: // * Settings panel (connection URL / user / password, Test + Save) // * Per-calendar color/icon configuration (persisted in localStorage) // // Sync orchestration (triggerSync, background poll, autosync toggle, // sync-interval, relay settings, sync.tick hook) lives in sync.js. import { $, esc, api, getCsrf } from './util.js'; const CAL_ICONS = [ { id: 'calendar-dots', label: 'calendar' }, { id: 'briefcase', label: 'work' }, { id: 'house', label: 'home' }, { id: 'heart', label: 'personal' }, { id: 'cake', label: 'birthday' }, { id: 'graduation-cap', label: 'education' }, { id: 'airplane', label: 'travel' }, { id: 'basketball', label: 'sport' }, { id: 'first-aid', label: 'health' }, { id: 'star', label: 'important' }, { id: 'users', label: 'team' }, { id: 'phone', label: 'calls' }, ]; const CAL_COLORS = ['#38bdf8', '#8b5cf6', '#fb7185', '#fbbf24', '#4ade80', '#fb923c', '#22d3ee', '#f472b6', '#a3e635', '#94a3b8']; let calendarConfigs = {}; // slug → { color, icon } const CAL_CONFIG_KEY = 'hyprcrd-cal-configs'; const settingsOverlay = $('settings-overlay'); const urlInput = $('caldav-url'); const userInput = $('caldav-user'); const passInput = $('caldav-pass'); const testStatus = $('caldav-test-status'); const syncStatus = $('caldav-sync-status'); const logEl = $('caldav-log'); export function openCaldavSettings() { // Open settings overlay on sync tab settingsOverlay?.classList.add('open'); settingsOverlay?.querySelectorAll('.settings-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === 'caldav')); settingsOverlay?.querySelectorAll('[id^="settings-panel-"]').forEach(p => p.classList.toggle('hidden', p.id !== 'settings-panel-caldav')); loadSettings(); } export function closeCaldavSettings() { settingsOverlay?.classList.remove('open'); } function loadCalConfigs() { try { calendarConfigs = JSON.parse(localStorage.getItem(CAL_CONFIG_KEY) || '{}'); } catch { calendarConfigs = {}; } } function saveCalConfigs() { localStorage.setItem(CAL_CONFIG_KEY, JSON.stringify(calendarConfigs)); } export function getCalConfig(slug) { loadCalConfigs(); return calendarConfigs[slug] || null; } export function getAllCalConfigs() { loadCalConfigs(); return calendarConfigs; } function renderCalList(calendars) { const container = $('caldav-cal-list'); const wrap = $('caldav-calendars'); if (!container || !wrap || !calendars?.length) return; loadCalConfigs(); wrap.classList.remove('hidden'); container.innerHTML = calendars.map(cal => { const slug = cal.name.toLowerCase().replace(/[^a-z0-9_-]/g, ''); const cfg = calendarConfigs[slug] || {}; const activeColor = cfg.color || '#94a3b8'; const activeIcon = cfg.icon || 'calendar-dots'; return `
${esc(cal.name)}
${CAL_COLORS.map(c => ``).join('')}
${CAL_ICONS.slice(0, 6).map(ic => ``).join('')}
`; }).join(''); container.querySelectorAll('.cal-cfg-color').forEach(btn => { btn.addEventListener('click', () => { const slug = btn.closest('[data-cal-slug]').dataset.calSlug; if (!calendarConfigs[slug]) calendarConfigs[slug] = {}; calendarConfigs[slug].color = btn.dataset.color; saveCalConfigs(); renderCalList(calendars); }); }); container.querySelectorAll('.cal-cfg-icon').forEach(btn => { btn.addEventListener('click', () => { const slug = btn.closest('[data-cal-slug]').dataset.calSlug; if (!calendarConfigs[slug]) calendarConfigs[slug] = {}; calendarConfigs[slug].icon = btn.dataset.icon; saveCalConfigs(); renderCalList(calendars); }); }); } export async function loadSettings() { const s = await api('api/caldav/settings'); if (!s) return; if (urlInput) urlInput.value = s.url || ''; if (userInput) userInput.value = s.username || ''; if (passInput) passInput.value = ''; if (passInput && s.has_password) passInput.placeholder = '(saved)'; if (syncStatus) syncStatus.textContent = s.last_sync ? `last sync: ${s.last_sync}` : ''; // Load calendar list if configured if (s.url && s.has_password) { const test = await fetch('api/caldav/test', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf() }, body: '{}', }).then(r => r.json()).catch(() => null); if (test?.calendars) renderCalList(test.calendars); } } function setCaldavTestStatus(text, kind = 'info') { if (!testStatus) return; if (!text) { testStatus.classList.add('hidden'); testStatus.textContent = ''; testStatus.style.borderColor = ''; testStatus.style.color = ''; return; } testStatus.classList.remove('hidden'); testStatus.textContent = text; if (kind === 'error') { testStatus.style.borderColor = 'var(--t-danger)'; testStatus.style.color = 'var(--t-danger)'; } else if (kind === 'success') { testStatus.style.borderColor = 'var(--t-accent)'; testStatus.style.color = 'var(--t-accent)'; } else { testStatus.style.borderColor = 'var(--t-border)'; testStatus.style.color = 'var(--t-muted)'; } } $('caldav-test')?.addEventListener('click', async () => { setCaldavTestStatus('testing...'); const body = { url: urlInput.value, username: userInput.value }; if (passInput.value) body.password = passInput.value; const res = await fetch('api/caldav/test', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf() }, body: JSON.stringify(body), }).then(r => r.json()).catch(() => ({ ok: false, error: 'network error' })); if (res.ok) { const names = (res.calendars || []).map(c => c.name).join(', '); setCaldavTestStatus(`connected (${names || 'no calendars'})`, 'success'); if (res.calendars) renderCalList(res.calendars); } else { setCaldavTestStatus(res.error || 'failed', 'error'); } showConnectionLog(res.log); }); $('caldav-save')?.addEventListener('click', async () => { const body = { url: urlInput.value, username: userInput.value, enabled: true }; if (passInput.value) body.password = passInput.value; await api('api/caldav/settings', { method: 'POST', body }); testStatus.textContent = 'saved'; testStatus.style.color = 'var(--t-accent)'; }); function showConnectionLog(lines) { if (!logEl || !lines?.length) return; logEl.classList.remove('hidden'); logEl.innerHTML = lines.map(l => `
${l.replace(/` ).join(''); } $('cal-caldav-settings')?.addEventListener('click', openCaldavSettings);