/** * GoingsOn - Settings Module * Settings page with sidebar navigation, plugin manager */ (function() { 'use strict'; const esc = GoingsOn.utils.escapeHtml; const escAttr = GoingsOn.utils.escapeAttr; let currentSection = 'appearance'; // ============ Settings Page ============ /** * Open settings as a modeless overlay above the current view. The * underlying app stays mounted so theme/notification/account changes * preview live against the real surfaces. (Phase 7 Tier 6.) */ async function openSettings() { const overlay = document.getElementById('settings-overlay'); if (!overlay) return; overlay.classList.remove('hidden'); overlay.setAttribute('aria-hidden', 'false'); document.addEventListener('keydown', handleSettingsKeydown); await showSection(currentSection); } /** * Called by navigation.loadViewData when settings view becomes active. * Renders the last-active section. Retained for back-compat with any * remaining callers; new code should call openSettings. */ async function loadSettings() { await showSection(currentSection); } /** * Close the settings overlay. Returns the user to whatever view was * underneath — no view switch needed since settings is now an overlay, * not a navigated subview. */ function goBack() { const overlay = document.getElementById('settings-overlay'); if (!overlay) return; overlay.classList.add('hidden'); overlay.setAttribute('aria-hidden', 'true'); document.removeEventListener('keydown', handleSettingsKeydown); } function handleSettingsKeydown(e) { if (e.key !== 'Escape') return; const isTyping = ['INPUT', 'TEXTAREA', 'SELECT'].includes(e.target.tagName) || e.target.isContentEditable; if (isTyping) return; goBack(); } /** * Show a specific settings section and update sidebar active state. * @param {string} section - Section name (appearance, notifications, planning, plugins, sync, data) */ async function showSection(section) { currentSection = section; // Update sidebar active state document.querySelectorAll('.settings-nav-item').forEach(btn => { btn.classList.toggle('active', btn.dataset.section === section); }); const container = document.getElementById('settings-content'); if (!container) return; switch (section) { case 'appearance': await renderAppearance(container); break; case 'notifications': renderNotifications(container); break; case 'email': await GoingsOn.emails.renderAccountsSection(container); break; case 'planning': renderPlanning(container); break; case 'plugins': await renderPlugins(container); break; case 'sync': await renderSync(container); break; case 'data': await renderData(container); break; case 'about': await renderAbout(container); break; } } // ============ Section Renderers ============ async function renderAppearance(container) { const savedTheme = localStorage.getItem('goingson-theme') || 'system'; const { light: lightThemes, dark: darkThemes, highContrast: highContrastThemes } = await GoingsOn.themes.getByType(); const highContrastGroup = highContrastThemes.length > 0 ? `` : ''; container.innerHTML = `
Choose a color theme for the interface.
How far in advance the Events tab dot turns yellow.
Controls when plan/review nudge dots appear.
Failed to load plugins: ${esc(GoingsOn.utils.getErrorMessage(err))}
`; return; } const enabledIds = new Set(enabledPlugins.map(p => p.id)); const pluginsList = allPlugins.length === 0 ? 'No plugins installed. Drop a plugin file into the GoingsOn plugins directory inside your OS app-data location.
' : allPlugins.map(plugin => renderPluginItem(plugin, enabledIds.has(plugin.id))).join(''); container.innerHTML = `Plugins extend GoingsOn with support for importing data from various formats.
Cloud sync module not loaded.
'; } } async function renderData(container) { // Build backup settings inline let backupHtml = ''; try { const settings = await GoingsOn.api.export.getBackupSettings(); const lastBackupText = settings.lastBackupAt ? `Last backup: ${new Date(settings.lastBackupAt).toLocaleString()}` : 'No backups yet'; const frequencyOptions = [ { value: 15, label: 'Every 15 minutes (Recommended)' }, { value: 30, label: 'Every 30 minutes' }, { value: 60, label: 'Every hour' }, { value: 360, label: 'Every 6 hours' }, { value: 1440, label: 'Daily' }, ]; const retentionOptions = [ { value: 1, label: 'Keep 1 backup (Recommended)' }, { value: 3, label: 'Keep 3 backups' }, { value: 7, label: 'Keep 7 backups' }, { value: 14, label: 'Keep 14 backups' }, { value: 0, label: 'Keep all backups' }, ]; const renderFormField = GoingsOn.ui.renderFormField; backupHtml = `Automatic backups protect your data by creating compressed snapshots on a schedule.
Import data from external sources or export for safekeeping.
Tasks, email, calendar, contacts.
Check for updates on launch
When a new signed release is available, a banner appears in the app. Install is always user-initiated.
© 2026 Make Creative, LLC
${esc(plugin.description)}
Files: .${extensions.join(', .')} | Types: ${entityTypes.join(', ')}