'use strict'; // --------------------------------------------------------------------------- // Init // --------------------------------------------------------------------------- (function init() { // Migrate PBKDF2-derived key stored by login/register form if (window.USER_ID) { const pending = localStorage.getItem('diora_pending_enc_key'); if (pending) { localStorage.setItem(`diora_enc_key_${window.USER_ID}`, pending); localStorage.removeItem('diora_pending_enc_key'); } } // Populate saved stations from server-side context if available if (typeof INITIAL_SAVED !== 'undefined' && Array.isArray(INITIAL_SAVED)) { // The server already renders saved stations in the template; nothing extra needed. // But if JS-rendered saved tab were needed we'd call addSavedRow here. } // Restore persisted volume, fall back to slider default const volSlider = $('volume'); if (volSlider) { const saved = localStorage.getItem('diora_volume'); const vol = saved !== null ? parseInt(saved, 10) : parseInt(volSlider.value, 10); setVolume(vol); } // Restore persisted read-aloud language const ttsLangSelect = $('reader-tts-lang'); if (ttsLangSelect) ttsLangSelect.value = ttsLang; // Load recommendations on page load loadRecommendations(); // Initialise focus timer display renderTimer(); // Initialise mood/genre chips initMoodChips(); // Initialise curated station lists initCuratedLists(); // Show curated lists again when search input is cleared const searchInput = document.getElementById('search-input'); if (searchInput) { searchInput.addEventListener('input', function () { if (this.value === '') { const curated = document.getElementById('curated-lists'); if (curated) curated.style.display = ''; } }); } // Load focus session stats loadFocusStats(); // Apply encrypted wallpaper (if set) applyEncryptedBackground(); // Init book drop zone initBookDropZone(); const showReadToggle = $('book-show-read-toggle'); if (showReadToggle) showReadToggle.checked = _bookShowRead; // Restore active tab: a URL hash (shared/bookmarked link, or browser // back/forward) wins over the last-used tab remembered in localStorage — // except when offline, where books (usable offline) always takes over. const offlineOverride = !navigator.onLine && IS_AUTHENTICATED; const hashTab = (location.hash || '').replace(/^#/, '').split('/')[0]; if (!offlineOverride && TOP_TABS.includes(hashTab)) { _routeFromHash(); } else { const savedTab = localStorage.getItem('diora_active_tab') || 'radio'; const savedRadioTab = localStorage.getItem('diora_active_radio_tab') || 'saved'; showTab(offlineOverride ? 'books' : savedTab); showRadioTab(savedRadioTab); } // React to connectivity changes window.addEventListener('offline', () => { showOfflineBanner(true); if (IS_AUTHENTICATED) showTab('books'); }); window.addEventListener('online', async () => { showOfflineBanner(false); await _syncPendingAnnotations(); loadBookList(); // sends the positions read offline first (the progress outbox) }); // Show banner immediately if already offline at startup if (!navigator.onLine) showOfflineBanner(true); // Reading position: positions reached by the reader's own doing count (see _noteReadingPosition). { const content = $('reader-content'); if (content) { for (const type of ['wheel', 'touchstart', 'mousedown']) { content.addEventListener(type, _noteReaderInteraction, {passive: true, capture: true}); } } document.addEventListener('keydown', () => { if (currentBookId) _noteReaderInteraction(); }, true); if (IS_AUTHENTICATED) _flushProgressOutbox(); } // Footnote hover (desktop) + tap (mobile) via event delegation const _readerContentEl = $('reader-content'); if (_readerContentEl) { _readerContentEl.addEventListener('mouseover', e => { const link = e.target.closest('[data-footnote-ref]'); if (!link) return; clearTimeout(_footnotePopoverTimer); _footnotePopoverTimer = setTimeout(() => showFootnotePopover(link), 120); }); _readerContentEl.addEventListener('mouseout', e => { if (!e.target.closest('[data-footnote-ref]')) return; _footnotePopoverTimer = setTimeout(dismissFootnotePopover, 250); }); _readerContentEl.addEventListener('click', e => { const link = e.target.closest('[data-footnote-ref]'); if (!link) return; e.preventDefault(); if (currentFootnotePopover?._refLink === link) { dismissFootnotePopover(); } else { showFootnotePopover(link); } }); // Text selection → highlight color/note popover _readerContentEl.addEventListener('mouseup', handleReaderSelection); _readerContentEl.addEventListener('touchend', e => { const target = e.target; const touch = e.changedTouches && e.changedTouches[0]; const clientX = touch ? touch.clientX : undefined; const clientY = touch ? touch.clientY : undefined; setTimeout(() => handleReaderSelection({target, clientX, clientY}), 50); }); } // Margin ("page margin") panel — click a marker to view/edit, click empty // space to anchor a new freeform note at that line's height const _readerMarginEl = $('reader-margin'); if (_readerMarginEl) { _readerMarginEl.addEventListener('click', handleMarginClick); } })();