Fork of Chiri for Astro for my blog
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: theme toggle

the3ash 39d37366 8190ec73

+124 -69
+7
src/components/ui/TableOfContents.astro
··· 234 234 init() 235 235 }) 236 236 237 + // Fallback for when Astro transitions are disabled 238 + document.addEventListener('DOMContentLoaded', () => { 239 + if (!state.container || !state.hasContent) { 240 + init() 241 + } 242 + }) 243 + 237 244 window.addEventListener('resize', handleResize) 238 245 window.addEventListener('scroll', handleScroll) 239 246 })()
+104 -66
src/components/ui/ThemeManager.astro
··· 1 1 <script is:inline> 2 2 // Global Theme Manager 3 - window.ThemeManager = { 4 - STORAGE_KEY: 'chiri-theme', 3 + ;(function () { 4 + // Prevent duplicate initialization 5 + if (window.ThemeManager && window.ThemeManager.initialized) { 6 + return 7 + } 5 8 6 - getSystemTheme() { 7 - return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' 8 - }, 9 + window.ThemeManager = { 10 + STORAGE_KEY: 'chiri-theme', 11 + initialized: false, 9 12 10 - getStoredTheme() { 11 - try { 12 - return localStorage.getItem(this.STORAGE_KEY) 13 - } catch { 14 - return null 15 - } 16 - }, 13 + getSystemTheme() { 14 + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' 15 + }, 17 16 18 - setStoredTheme(theme) { 19 - try { 20 - if (theme === 'system') { 21 - localStorage.removeItem(this.STORAGE_KEY) 22 - } else { 23 - localStorage.setItem(this.STORAGE_KEY, theme) 17 + getStoredTheme() { 18 + try { 19 + return localStorage.getItem(this.STORAGE_KEY) 20 + } catch { 21 + return null 24 22 } 25 - } catch (e) { 26 - console.warn('Failed to store theme preference:', e) 27 - } 28 - }, 23 + }, 29 24 30 - getEffectiveTheme() { 31 - return this.getStoredTheme() || this.getSystemTheme() 32 - }, 25 + setStoredTheme(theme) { 26 + try { 27 + if (theme === 'system') { 28 + localStorage.removeItem(this.STORAGE_KEY) 29 + } else { 30 + localStorage.setItem(this.STORAGE_KEY, theme) 31 + } 32 + } catch (e) { 33 + console.warn('Failed to store theme preference:', e) 34 + } 35 + }, 33 36 34 - applyTheme(theme) { 35 - document.documentElement.classList.remove('light', 'dark') 36 - document.documentElement.classList.add(theme) 37 + getEffectiveTheme() { 38 + const stored = this.getStoredTheme() 39 + return stored || this.getSystemTheme() 40 + }, 37 41 38 - // Dispatch event for other components 39 - document.dispatchEvent( 40 - new CustomEvent('themechange', { 41 - detail: { theme, isUserChoice: this.getStoredTheme() !== null } 42 - }) 43 - ) 44 - }, 42 + isUsingSystemTheme() { 43 + return this.getStoredTheme() === null 44 + }, 45 45 46 - toggle() { 47 - const currentEffective = this.getEffectiveTheme() 48 - const systemTheme = this.getSystemTheme() 49 - const storedTheme = this.getStoredTheme() 46 + applyTheme(theme) { 47 + document.documentElement.classList.remove('light', 'dark') 48 + document.documentElement.classList.add(theme) 50 49 51 - let newTheme 52 - if (!storedTheme) { 53 - newTheme = currentEffective === 'dark' ? 'light' : 'dark' 54 - } else { 55 - newTheme = 56 - storedTheme === systemTheme ? (currentEffective === 'dark' ? 'light' : 'dark') : 'system' 57 - } 50 + // Dispatch event for other components 51 + document.dispatchEvent( 52 + new CustomEvent('themechange', { 53 + detail: { 54 + theme, 55 + isUserChoice: !this.isUsingSystemTheme(), 56 + isSystemTheme: this.isUsingSystemTheme() 57 + } 58 + }) 59 + ) 60 + }, 61 + 62 + toggle() { 63 + const currentTheme = this.getEffectiveTheme() 64 + // Simply toggle between light and dark 65 + const newTheme = currentTheme === 'dark' ? 'light' : 'dark' 66 + 67 + this.setStoredTheme(newTheme) 68 + this.applyTheme(newTheme) 69 + }, 70 + 71 + init() { 72 + if (this.initialized) return 58 73 59 - this.setStoredTheme(newTheme) 60 - this.applyTheme(this.getEffectiveTheme()) 61 - }, 74 + // Set initial theme (maintain current theme when refreshing page) 75 + this.applyTheme(this.getEffectiveTheme()) 62 76 63 - init() { 64 - // Set initial theme 65 - this.applyTheme(this.getEffectiveTheme()) 77 + // Listen for system theme changes 78 + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { 79 + const newSystemTheme = e.matches ? 'dark' : 'light' 66 80 67 - // Listen for system theme changes 68 - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { 69 - const stored = this.getStoredTheme() 70 - const systemTheme = e.matches ? 'dark' : 'light' 81 + // Always follow system theme changes and update stored theme preference 82 + this.setStoredTheme(newSystemTheme) 83 + this.applyTheme(newSystemTheme) 84 + }) 71 85 72 - if (!stored) { 73 - this.applyTheme(systemTheme) 74 - } else if (stored === systemTheme) { 75 - this.setStoredTheme('system') 76 - this.applyTheme(systemTheme) 77 - } 78 - }) 86 + this.initialized = true 87 + } 79 88 } 80 - } 89 + 90 + // Initialize theme manager 91 + window.ThemeManager.init() 81 92 82 - // Initialize theme manager 83 - window.ThemeManager.init() 93 + // Listen for Astro page transition events, but delay execution to avoid conflicts with transition animations 94 + document.addEventListener('astro:page-load', () => { 95 + if (window.ThemeManager) { 96 + // Use requestAnimationFrame to ensure execution in the next frame, avoiding conflicts with transition animations 97 + requestAnimationFrame(() => { 98 + const currentTheme = window.ThemeManager.getEffectiveTheme() 99 + const appliedTheme = document.documentElement.classList.contains('dark') 100 + ? 'dark' 101 + : 'light' 102 + 103 + // Only reapply theme when there's a mismatch to avoid unnecessary flickering 104 + if (currentTheme !== appliedTheme) { 105 + window.ThemeManager.applyTheme(currentTheme) 106 + } 107 + }) 108 + } 109 + }) 110 + 111 + // Listen for page transition start event to ensure theme is ready before transition 112 + document.addEventListener('astro:before-preparation', () => { 113 + if (window.ThemeManager) { 114 + const theme = window.ThemeManager.getEffectiveTheme() 115 + // Ensure theme class is applied before transition starts 116 + if (!document.documentElement.classList.contains(theme)) { 117 + document.documentElement.classList.add(theme) 118 + } 119 + } 120 + }) 121 + })() 84 122 </script>
+13 -3
src/components/ui/ThemeToggle.astro
··· 12 12 } 13 13 14 14 <script is:inline> 15 - window.addEventListener('DOMContentLoaded', function () { 15 + function bindThemeToggle() { 16 16 const themeToggle = document.getElementById('theme-toggle') 17 17 if (themeToggle && window.ThemeManager) { 18 - themeToggle.addEventListener('click', function (e) { 18 + // Remove existing event listeners to prevent duplicates 19 + const newToggle = themeToggle.cloneNode(true) 20 + themeToggle.parentNode.replaceChild(newToggle, themeToggle) 21 + 22 + newToggle.addEventListener('click', function (e) { 19 23 e.preventDefault() 20 24 e.stopPropagation() 21 25 window.ThemeManager.toggle() 22 26 }) 23 27 } 24 - }) 28 + } 29 + 30 + // Bind on initial load 31 + window.addEventListener('DOMContentLoaded', bindThemeToggle) 32 + 33 + // Bind on Astro page transitions 34 + document.addEventListener('astro:page-load', bindThemeToggle) 25 35 </script> 26 36 27 37 <style>