/** * @fileoverview Mobile tab bar navigation and panel switching. * * On mobile (<= 768px), replaces the three-column layout with a bottom tab bar. * Tabs: Feed (items list), Saved (starred items), Sources (sidebar). * The + button opens the add-feed modal; More opens a popover with settings/sync/OPML. * * All functions are no-ops on desktop (>768px) — class additions/removals are harmless * when the tab bar is hidden and panels are laid out by CSS grid/flex. */ (function() { 'use strict'; let currentTab = 'feed'; /** * Check if we're in mobile layout by testing tab bar visibility. * @returns {boolean} True if the mobile tab bar is visible. */ function isMobile() { const tabBar = document.getElementById('mobile-tab-bar'); return tabBar && getComputedStyle(tabBar).display !== 'none'; } /** * Get the current active tab name. * @returns {string} Current tab: 'feed', 'saved', or 'sources'. */ function getCurrentTab() { return currentTab; } /** Initialize tab bar event listeners. */ function init() { const tabBar = document.getElementById('mobile-tab-bar'); if (!tabBar) return; // Tab buttons tabBar.addEventListener('click', (e) => { const tab = e.target.closest('.mobile-tab'); if (!tab) return; // Create button if (tab.id === 'mobile-create-btn') { BB.feeds.openAddFeed(); return; } // More button if (tab.id === 'mobile-more-btn') { toggleMorePopover(); return; } // Regular tab const tabName = tab.dataset.tab; if (tabName) switchTab(tabName); }); // More popover actions const popover = document.getElementById('mobile-more-popover'); if (popover) { popover.addEventListener('click', (e) => { const btn = e.target.closest('[data-action]'); if (btn) { handleMoreAction(btn.dataset.action); closeMorePopover(); } }); } // Mobile search input — debounced, mirrors desktop search behavior const mobileSearch = document.getElementById('mobile-search-input'); if (mobileSearch) { mobileSearch.addEventListener('input', BB.utils.debounce(() => { BB.state.set('currentSearch', mobileSearch.value); BB.state.resetPagination(); BB.items.load(); // Sync desktop search const desktopSearch = document.getElementById('search-input'); if (desktopSearch) desktopSearch.value = mobileSearch.value; }, 300)); } // Mobile sort select const mobileSort = document.getElementById('mobile-sort-select'); if (mobileSort) { mobileSort.addEventListener('change', (e) => { BB.state.set('currentOrder', e.target.value); BB.state.resetPagination(); BB.items.load(); // Sync desktop sort const desktopSort = document.getElementById('sort-select'); if (desktopSort) desktopSort.value = e.target.value; }); } // Close more popover on outside click document.addEventListener('click', (e) => { const pop = document.getElementById('mobile-more-popover'); if (!pop || !pop.classList.contains('visible')) return; if (!pop.contains(e.target) && e.target.id !== 'mobile-more-btn') { closeMorePopover(); } }); } /** * Switch the active tab and manage panel visibility. * @param {string} tab - Tab name: 'feed', 'saved', or 'sources'. */ function switchTab(tab) { currentTab = tab; const sidebar = document.querySelector('.sidebar'); const itemsPanel = document.querySelector('.items-panel'); // Close detail panel when switching tabs closeMobileDetail(); // Reset panel visibility classes sidebar.classList.remove('mobile-visible'); itemsPanel.classList.remove('mobile-hidden'); switch (tab) { case 'feed': // Show items, hide sidebar // If currently viewing saved, switch back to all if (BB.state.currentSource === '__saved__') { BB.sources.select(''); } break; case 'saved': // Show items (filtered to saved), hide sidebar BB.sources.select('__saved__'); break; case 'sources': // Show sidebar full-width, hide items sidebar.classList.add('mobile-visible'); itemsPanel.classList.add('mobile-hidden'); break; } updateTabBar(); } /** Update tab bar active states to reflect current tab. */ function updateTabBar() { const tabs = document.querySelectorAll('.mobile-tab[data-tab]'); tabs.forEach(t => { const isActive = t.dataset.tab === currentTab; t.classList.toggle('active', isActive); t.setAttribute('aria-selected', isActive ? 'true' : 'false'); }); } /** Show mobile detail overlay: hide tab bar, add back button. */ function openMobileDetail() { if (!isMobile()) return; document.body.classList.add('mobile-detail-open'); // Add back button to detail header if not present const header = document.querySelector('.detail-header'); if (header && !header.querySelector('.mobile-back-btn')) { const backBtn = document.createElement('button'); backBtn.className = 'btn btn-small mobile-back-btn'; backBtn.textContent = '\u2190 Back'; backBtn.addEventListener('click', () => BB.detail.close()); header.prepend(backBtn); } } /** Close mobile detail overlay: show tab bar, remove back button. */ function closeMobileDetail() { document.body.classList.remove('mobile-detail-open'); const backBtn = document.querySelector('.mobile-back-btn'); if (backBtn) backBtn.remove(); } /** * Called from sources.js after a source is selected. * On mobile, switches to Feed tab to show filtered items. * @param {string} sourceId - The selected source ID. */ function onSourceSelected(sourceId) { if (!isMobile()) return; if (sourceId === '__saved__') return; // Saved tab handles this switchTab('feed'); } /** Toggle the more popover visibility. */ function toggleMorePopover() { const pop = document.getElementById('mobile-more-popover'); if (pop) pop.classList.toggle('visible'); } /** Close the more popover. */ function closeMorePopover() { const pop = document.getElementById('mobile-more-popover'); if (pop) pop.classList.remove('visible'); } /** * Dispatch a more popover action to the appropriate handler. * @param {string} action - Action name from data-action attribute. */ function handleMoreAction(action) { switch (action) { case 'settings': BB.app.showSettings(); break; case 'sync': BB.sync.openSettings(); break; case 'import': BB.feeds.importOpml(); break; case 'export': BB.feeds.exportOpml(); break; case 'shortcuts': BB.app.showHelp(); break; } } BB.navigation = { init, isMobile, switchTab, updateTabBar, openMobileDetail, closeMobileDetail, onSourceSelected, closeMorePopover, getCurrentTab, }; })();