Fork of Chiri for Astro for my blog
0
fork

Configure Feed

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

refactor(theme): improve switching logic

the3ash 6af4686e c9b14e65

+160 -142
-100
src/components/layout/BaseHead.astro
··· 13 13 <!-- Global Metadata --> 14 14 <meta charset="utf-8" /> 15 15 <meta name="viewport" content="width=device-width,initial-scale=1" /> 16 - {themeConfig.general.fadeAnimation && <meta name="view-transition" content="same-origin" />} 17 16 <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 18 17 <link rel="apple-touch-icon" href="/apple-touch-icon.png" /> 19 18 <link rel="preload" href="/fonts/Inter.woff2" as="font" type="font/woff2" crossorigin="anonymous" /> ··· 56 55 <meta property="twitter:title" content={title} /> 57 56 <meta property="twitter:description" content={description} /> 58 57 <meta property="twitter:image" content={new URL('/chiri-og.png', Astro.url)} /> 59 - 60 - <!-- Transitions and Theme Initialization --> 61 - <script is:inline define:vars={{ fadeAnimation: themeConfig.general.fadeAnimation }}> 62 - function initMotionPref(doc = document) { 63 - const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches 64 - const supportsViewTransitions = 'startViewTransition' in document 65 - 66 - doc.documentElement.classList.toggle('reduce-motion', prefersReducedMotion) 67 - 68 - doc.documentElement.classList.toggle( 69 - 'disable-transitions', 70 - !fadeAnimation || !supportsViewTransitions 71 - ) 72 - 73 - // Dynamically control view transition navigation behavior 74 - let viewTransitionStyle = doc.getElementById('view-transition-style') 75 - 76 - if (fadeAnimation && supportsViewTransitions && !prefersReducedMotion) { 77 - // Enable view transition navigation 78 - if (!viewTransitionStyle) { 79 - viewTransitionStyle = doc.createElement('style') 80 - viewTransitionStyle.id = 'view-transition-style' 81 - viewTransitionStyle.textContent = '@view-transition { navigation: auto; }' 82 - doc.head.appendChild(viewTransitionStyle) 83 - } 84 - } else { 85 - // Disable view transition navigation 86 - if (viewTransitionStyle) { 87 - viewTransitionStyle.remove() 88 - } 89 - } 90 - 91 - doc.documentElement.classList.add('js') 92 - } 93 - 94 - // Theme initialization function 95 - function initTheme(doc = document) { 96 - const htmlElement = doc.documentElement 97 - 98 - // Get system theme preference 99 - function getSystemTheme() { 100 - const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches 101 - return isDark ? 'dark' : 'light' 102 - } 103 - 104 - // Apply theme 105 - function applyTheme(theme) { 106 - htmlElement.classList.remove('light', 'dark') 107 - 108 - if (theme === 'dark') { 109 - htmlElement.classList.add('dark') 110 - } else { 111 - htmlElement.classList.add('light') 112 - } 113 - } 114 - 115 - // Initialize theme - ensure explicit classes are always present 116 - function initThemeState() { 117 - const systemTheme = getSystemTheme() 118 - applyTheme(systemTheme) 119 - } 120 - 121 - // Listen for system theme changes 122 - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') 123 - mediaQuery.addEventListener('change', function (e) { 124 - const newTheme = e.matches ? 'dark' : 'light' 125 - applyTheme(newTheme) 126 - }) 127 - 128 - // Initialize theme state 129 - initThemeState() 130 - } 131 - 132 - // Initialize both motion preferences and theme 133 - function initAll(doc = document) { 134 - initMotionPref(doc) 135 - initTheme(doc) 136 - } 137 - 138 - initAll() 139 - 140 - document.addEventListener('astro:before-swap', ({ newDocument }) => { 141 - initAll(newDocument) 142 - }) 143 - 144 - document.addEventListener('astro:page-load', () => { 145 - initAll() 146 - }) 147 - 148 - document.addEventListener('visibilitychange', () => { 149 - if (document.visibilityState === 'visible') { 150 - initAll() 151 - } 152 - }) 153 - 154 - window.addEventListener('pageshow', () => { 155 - initAll() 156 - }) 157 - </script>
+84
src/components/ui/ThemeManager.astro
··· 1 + <script is:inline> 2 + // Global Theme Manager 3 + window.ThemeManager = { 4 + STORAGE_KEY: 'chiri-theme', 5 + 6 + getSystemTheme() { 7 + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' 8 + }, 9 + 10 + getStoredTheme() { 11 + try { 12 + return localStorage.getItem(this.STORAGE_KEY) 13 + } catch { 14 + return null 15 + } 16 + }, 17 + 18 + setStoredTheme(theme) { 19 + try { 20 + if (theme === 'system') { 21 + localStorage.removeItem(this.STORAGE_KEY) 22 + } else { 23 + localStorage.setItem(this.STORAGE_KEY, theme) 24 + } 25 + } catch (e) { 26 + console.warn('Failed to store theme preference:', e) 27 + } 28 + }, 29 + 30 + getEffectiveTheme() { 31 + return this.getStoredTheme() || this.getSystemTheme() 32 + }, 33 + 34 + applyTheme(theme) { 35 + document.documentElement.classList.remove('light', 'dark') 36 + document.documentElement.classList.add(theme) 37 + 38 + // Dispatch event for other components 39 + document.dispatchEvent( 40 + new CustomEvent('themechange', { 41 + detail: { theme, isUserChoice: this.getStoredTheme() !== null } 42 + }) 43 + ) 44 + }, 45 + 46 + toggle() { 47 + const currentEffective = this.getEffectiveTheme() 48 + const systemTheme = this.getSystemTheme() 49 + const storedTheme = this.getStoredTheme() 50 + 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 + } 58 + 59 + this.setStoredTheme(newTheme) 60 + this.applyTheme(this.getEffectiveTheme()) 61 + }, 62 + 63 + init() { 64 + // Set initial theme 65 + this.applyTheme(this.getEffectiveTheme()) 66 + 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' 71 + 72 + if (!stored) { 73 + this.applyTheme(systemTheme) 74 + } else if (stored === systemTheme) { 75 + this.setStoredTheme('system') 76 + this.applyTheme(systemTheme) 77 + } 78 + }) 79 + } 80 + } 81 + 82 + // Initialize theme manager 83 + window.ThemeManager.init() 84 + </script>
+2 -40
src/components/ui/ThemeToggle.astro
··· 13 13 14 14 <script is:inline> 15 15 window.addEventListener('DOMContentLoaded', function () { 16 - const htmlElement = document.documentElement 17 - 18 - // Get current actual theme state 19 - function getCurrentTheme() { 20 - const hasLight = htmlElement.classList.contains('light') 21 - const hasDark = htmlElement.classList.contains('dark') 22 - 23 - if (hasLight) { 24 - return 'light' 25 - } 26 - if (hasDark) { 27 - return 'dark' 28 - } 29 - 30 - // Fallback to system theme 31 - const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches 32 - return isDark ? 'dark' : 'light' 33 - } 34 - 35 - // Apply theme 36 - function applyTheme(theme) { 37 - htmlElement.classList.remove('light', 'dark') 38 - 39 - if (theme === 'dark') { 40 - htmlElement.classList.add('dark') 41 - } else { 42 - htmlElement.classList.add('light') 43 - } 44 - } 45 - 46 - // Get theme toggle button (only if it exists) 47 16 const themeToggle = document.getElementById('theme-toggle') 48 - if (themeToggle) { 49 - // Toggle theme function 50 - function toggleTheme() { 51 - const currentTheme = getCurrentTheme() 52 - const newTheme = currentTheme === 'dark' ? 'light' : 'dark' 53 - applyTheme(newTheme) 54 - } 55 - 17 + if (themeToggle && window.ThemeManager) { 56 18 themeToggle.addEventListener('click', function (e) { 57 19 e.preventDefault() 58 20 e.stopPropagation() 59 - toggleTheme() 21 + window.ThemeManager.toggle() 60 22 }) 61 23 } 62 24 })
+67
src/components/ui/TransitionInit.astro
··· 1 + --- 2 + import { themeConfig } from '@/config' 3 + 4 + const fadeAnimation = themeConfig.general.fadeAnimation 5 + --- 6 + 7 + {fadeAnimation && <meta name="view-transition" content="same-origin" />} 8 + 9 + <script is:inline define:vars={{ fadeAnimation }}> 10 + // Initialize page transition animation 11 + function initMotionPref(doc = document) { 12 + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches 13 + const supportsViewTransitions = 'startViewTransition' in document 14 + 15 + doc.documentElement.classList.toggle('reduce-motion', prefersReducedMotion) 16 + 17 + doc.documentElement.classList.toggle( 18 + 'disable-transitions', 19 + !fadeAnimation || !supportsViewTransitions 20 + ) 21 + 22 + // Dynamically control view transition navigation behavior 23 + let viewTransitionStyle = doc.getElementById('view-transition-style') 24 + 25 + if (fadeAnimation && supportsViewTransitions && !prefersReducedMotion) { 26 + // Enable view transition navigation 27 + if (!viewTransitionStyle) { 28 + viewTransitionStyle = doc.createElement('style') 29 + viewTransitionStyle.id = 'view-transition-style' 30 + viewTransitionStyle.textContent = '@view-transition { navigation: auto; }' 31 + doc.head.appendChild(viewTransitionStyle) 32 + } 33 + } else { 34 + // Disable view transition navigation 35 + if (viewTransitionStyle) { 36 + viewTransitionStyle.remove() 37 + } 38 + } 39 + 40 + doc.documentElement.classList.add('js') 41 + } 42 + 43 + // Initialize motion preferences 44 + function initAll(doc = document) { 45 + initMotionPref(doc) 46 + } 47 + 48 + initAll() 49 + 50 + document.addEventListener('astro:before-swap', ({ newDocument }) => { 51 + initAll(newDocument) 52 + }) 53 + 54 + document.addEventListener('astro:page-load', () => { 55 + initAll() 56 + }) 57 + 58 + document.addEventListener('visibilitychange', () => { 59 + if (document.visibilityState === 'visible') { 60 + initAll() 61 + } 62 + }) 63 + 64 + window.addEventListener('pageshow', () => { 65 + initAll() 66 + }) 67 + </script>
+7 -2
src/layouts/BaseLayout.astro
··· 1 1 --- 2 2 import { themeConfig } from '@/config' 3 - import TransitionWrapper from '@/components/layout/TransitionWrapper.astro' 3 + import ThemeManager from '@/components/ui/ThemeManager.astro' 4 + import TransitionInit from '@/components/ui/TransitionInit.astro' 4 5 import FaviconThemeSwitcher from '@/components/ui/FaviconThemeSwitcher.astro' 6 + import TransitionWrapper from '@/components/layout/TransitionWrapper.astro' 5 7 import type { LayoutProps } from '@/types' 6 8 7 9 type Props = LayoutProps ··· 22 24 {...themeConfig.general.fadeAnimation ? { 'transition:animate': 'initial' } : {}} 23 25 > 24 26 <head> 27 + <TransitionInit /> 25 28 <slot name="head" /> 26 29 </head> 27 30 <body ··· 32 35 ${shouldUseCustomWidth ? `--content-width: ${widthValue}rem;` : ''} 33 36 `} 34 37 > 38 + <ThemeManager /> 39 + <FaviconThemeSwitcher /> 40 + 35 41 <TransitionWrapper type={type} class="layout-wrapper"> 36 42 <slot /> 37 43 </TransitionWrapper> 38 - <FaviconThemeSwitcher /> 39 44 </body> 40 45 </html> 41 46