| module.exports = function initUi(state, dom) { |
| const { |
| searchInput, |
| searchBox, |
| menuToggle, |
| searchToggle, |
| sidebar, |
| searchContainer, |
| overlay, |
| sidebarToggle, |
| content, |
| themeToggle, |
| themeIcon, |
| } = dom; |
|
|
| const SUBMENU_PANEL_VISIBLE_CLASS = 'submenu-panel-visible'; |
| const SIDEBAR_LAYOUT_TRANSITION_MS = 300; |
| let submenuPanelRevealTimer = null; |
|
|
| |
| document.documentElement.classList.remove('preload'); |
|
|
| let systemThemeMql = null; |
| let systemThemeListener = null; |
|
|
| function setTheme(isLight) { |
| const nextIsLight = Boolean(isLight); |
| state.isLightTheme = nextIsLight; |
| document.body.classList.toggle('light-theme', nextIsLight); |
|
|
| if (nextIsLight) { |
| themeIcon.classList.remove('fa-moon'); |
| themeIcon.classList.add('fa-sun'); |
| } else { |
| themeIcon.classList.remove('fa-sun'); |
| themeIcon.classList.add('fa-moon'); |
| } |
| } |
|
|
| function stopSystemThemeFollow() { |
| if (systemThemeMql && systemThemeListener) { |
| if (typeof systemThemeMql.removeEventListener === 'function') { |
| systemThemeMql.removeEventListener('change', systemThemeListener); |
| } else if (typeof systemThemeMql.removeListener === 'function') { |
| systemThemeMql.removeListener(systemThemeListener); |
| } |
| } |
| systemThemeMql = null; |
| systemThemeListener = null; |
| } |
|
|
| function startSystemThemeFollow() { |
| stopSystemThemeFollow(); |
|
|
| try { |
| systemThemeMql = window.matchMedia('(prefers-color-scheme: light)'); |
| } catch (e) { |
| systemThemeMql = null; |
| } |
| if (!systemThemeMql) return; |
|
|
| systemThemeListener = (event) => { |
| const savedTheme = localStorage.getItem('theme'); |
| if (savedTheme === 'light' || savedTheme === 'dark') { |
| stopSystemThemeFollow(); |
| return; |
| } |
| setTheme(Boolean(event && event.matches)); |
| }; |
|
|
| if (typeof systemThemeMql.addEventListener === 'function') { |
| systemThemeMql.addEventListener('change', systemThemeListener); |
| } else if (typeof systemThemeMql.addListener === 'function') { |
| systemThemeMql.addListener(systemThemeListener); |
| } |
| } |
|
|
| function getThemeMode() { |
| const raw = document.documentElement.getAttribute('data-theme-mode'); |
| return raw ? String(raw).trim().toLowerCase() : 'dark'; |
| } |
|
|
| |
| if (document.documentElement.classList.contains('theme-preload')) { |
| document.documentElement.classList.remove('theme-preload'); |
| setTheme(true); |
| } else { |
| setTheme(false); |
| } |
|
|
| |
| if (document.documentElement.classList.contains('sidebar-collapsed-preload')) { |
| document.documentElement.classList.remove('sidebar-collapsed-preload'); |
| sidebar.classList.add('collapsed'); |
| content.classList.add('expanded'); |
| state.isSidebarCollapsed = true; |
| } |
|
|
| |
| document.body.classList.remove('loading'); |
| document.body.classList.add('loaded'); |
|
|
| function isMobile() { |
| return window.innerWidth <= 768; |
| } |
|
|
| function clearSubmenuPanelRevealTimer() { |
| if (submenuPanelRevealTimer) { |
| window.clearTimeout(submenuPanelRevealTimer); |
| submenuPanelRevealTimer = null; |
| } |
| } |
|
|
| function hideSubmenuPanelImmediately() { |
| clearSubmenuPanelRevealTimer(); |
| sidebar.classList.remove(SUBMENU_PANEL_VISIBLE_CLASS); |
| } |
|
|
| function showSubmenuPanelImmediately() { |
| clearSubmenuPanelRevealTimer(); |
| sidebar.classList.add(SUBMENU_PANEL_VISIBLE_CLASS); |
| } |
|
|
| function revealSubmenuPanelAfterSidebarTransition() { |
| clearSubmenuPanelRevealTimer(); |
| submenuPanelRevealTimer = window.setTimeout(() => { |
| if (!state.isSidebarCollapsed && !sidebar.classList.contains('collapsed')) { |
| sidebar.classList.add(SUBMENU_PANEL_VISIBLE_CLASS); |
| } |
| }, SIDEBAR_LAYOUT_TRANSITION_MS); |
| } |
|
|
| |
| function toggleSidebarCollapse() { |
| |
| document.documentElement.classList.add('with-anim'); |
|
|
| state.isSidebarCollapsed = !state.isSidebarCollapsed; |
|
|
| if (state.isSidebarCollapsed) { |
| |
| hideSubmenuPanelImmediately(); |
| } |
|
|
| |
| requestAnimationFrame(() => { |
| sidebar.classList.toggle('collapsed', state.isSidebarCollapsed); |
| content.classList.toggle('expanded', state.isSidebarCollapsed); |
|
|
| |
| localStorage.setItem('sidebarCollapsed', state.isSidebarCollapsed ? 'true' : 'false'); |
|
|
| if (!state.isSidebarCollapsed) { |
| |
| revealSubmenuPanelAfterSidebarTransition(); |
| } |
| }); |
| } |
|
|
| |
| function initSidebarState() { |
| |
| const savedState = localStorage.getItem('sidebarCollapsed'); |
|
|
| |
| if (savedState === 'true' && !isMobile()) { |
| state.isSidebarCollapsed = true; |
| hideSubmenuPanelImmediately(); |
| } else { |
| state.isSidebarCollapsed = false; |
| showSubmenuPanelImmediately(); |
| } |
| } |
|
|
| |
| function toggleTheme() { |
| setTheme(!state.isLightTheme); |
|
|
| |
| localStorage.setItem('theme', state.isLightTheme ? 'light' : 'dark'); |
| stopSystemThemeFollow(); |
| } |
|
|
| |
| function initTheme() { |
| |
| const savedTheme = localStorage.getItem('theme'); |
| if (savedTheme === 'light') { |
| stopSystemThemeFollow(); |
| setTheme(true); |
| return; |
| } |
| if (savedTheme === 'dark') { |
| stopSystemThemeFollow(); |
| setTheme(false); |
| return; |
| } |
|
|
| |
| const mode = getThemeMode(); |
|
|
| if (mode === 'light') { |
| stopSystemThemeFollow(); |
| setTheme(true); |
| return; |
| } |
|
|
| if (mode === 'system') { |
| let shouldUseLight = false; |
| try { |
| shouldUseLight = |
| window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches; |
| } catch (e) { |
| shouldUseLight = false; |
| } |
| setTheme(shouldUseLight); |
| startSystemThemeFollow(); |
| return; |
| } |
|
|
| |
| stopSystemThemeFollow(); |
| setTheme(false); |
| } |
|
|
| |
| function toggleSidebar() { |
| state.isSidebarOpen = !state.isSidebarOpen; |
| sidebar.classList.toggle('active', state.isSidebarOpen); |
| overlay.classList.toggle('active', state.isSidebarOpen); |
| } |
|
|
| |
| function toggleSearch() { |
| searchInput && searchInput.focus(); |
| } |
|
|
| |
| function closeAllPanels() { |
| if (state.isSidebarOpen) { |
| toggleSidebar(); |
| } |
| } |
|
|
| |
| if (sidebarToggle) { |
| sidebarToggle.addEventListener('click', toggleSidebarCollapse); |
| } |
|
|
| |
| themeToggle.addEventListener('click', toggleTheme); |
|
|
| |
| menuToggle.addEventListener('click', toggleSidebar); |
| searchToggle.addEventListener('click', toggleSearch); |
| overlay.addEventListener('click', closeAllPanels); |
|
|
| |
| document.addEventListener('keydown', (e) => { |
| const key = (e.key || '').toLowerCase(); |
| if (key !== 'k') return; |
| if ((!e.ctrlKey && !e.metaKey) || e.altKey) return; |
|
|
| const target = e.target; |
| const isTypingTarget = |
| target && |
| (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable); |
|
|
| if (isTypingTarget && target !== searchInput) return; |
|
|
| e.preventDefault(); |
|
|
| searchInput && searchInput.focus(); |
| }); |
|
|
| |
| window.addEventListener('resize', () => { |
| if (!isMobile()) { |
| sidebar.classList.remove('active'); |
| searchContainer.classList.remove('active'); |
| overlay.classList.remove('active'); |
| state.isSidebarOpen = false; |
| if (state.isSidebarCollapsed) { |
| hideSubmenuPanelImmediately(); |
| } else { |
| showSubmenuPanelImmediately(); |
| } |
| } else { |
| |
| sidebar.classList.remove('collapsed'); |
| content.classList.remove('expanded'); |
| showSubmenuPanelImmediately(); |
| } |
| }); |
|
|
| |
| void searchBox; |
|
|
| return { |
| isMobile, |
| closeAllPanels, |
| initTheme, |
| initSidebarState, |
| }; |
| }; |
|
|