blog/src/layouts/Head.astro
2025-03-15 01:02:45 +00:00

168 lines
6 KiB
Text

---
import { allLocales, defaultLocale, themeConfig } from '@/config'
import { ClientRouter } from 'astro:transitions'
interface Props {
postTitle?: string
postDescription?: string
postSlug?: string
}
const { postTitle, postDescription, postSlug } = Astro.props
const { title, subtitle, description, author, url, favicon } = themeConfig.site
const { mode, light: { background: lightMode }, dark: { background: darkMode } } = themeConfig.color
// const { locale, moreLocales } = themeConfig.global
const { verification = {}, twitterID = '', googleAnalyticsID = '', umamiAnalyticsID = '' } = themeConfig.seo ?? {}
const { google = '', bing = '', yandex = '', baidu = '' } = verification
const { commentURL = '', imageHostURL = '', customGoogleAnalyticsJS = '', customUmamiAnalyticsJS = '' } = themeConfig.preload
const initMetaTheme = mode === 'dark' ? darkMode : lightMode
const pageTitle = postTitle ? `${postTitle} | ${title}` : `${title} - ${subtitle}`
const pageDescription = postDescription || description
// TODO: Change openGraph image fallback url
const pageImage = postSlug ? `${url}/opengraph/${postSlug}.png` : 'https://placehold.co/1200x630'
---
<head>
<!-- Basic info -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
{favicon.toLowerCase().endsWith('.svg') && <link rel="icon" type="image/svg+xml" href={favicon} />}
{favicon.toLowerCase().endsWith('.png') && <link rel="icon" type="image/png" href={favicon} />}
{favicon.toLowerCase().endsWith('.ico') && <link rel="icon" type="image/x-icon" href={favicon} />}
<title>{pageTitle}</title>
<meta name="description" content={pageDescription} />
<meta name="author" content={author} />
<meta name="generator" content={Astro.generator} />
<meta name="theme-color" content={initMetaTheme} />
<!-- Preload -->
<link rel="preload" href="/font/Snell-Black.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/font/EarlySummer-Subset.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/font/Snell-Bold.woff2" as="font" type="font/woff2" crossorigin />
{commentURL && <link rel="preconnect" href={commentURL} crossorigin />}
{commentURL && <link rel="dns-prefetch" href={commentURL} />}
{imageHostURL && <link rel="preconnect" href={imageHostURL} crossorigin />}
{imageHostURL && <link rel="dns-prefetch" href={imageHostURL} />}
<link rel="alternate" href="/rss.xml" type="application/rss+xml" title="RSS" />
<link rel="canonical" href={Astro.url} />
<!-- i18n hreflang generate -->
{allLocales.map(lang => (
<link
rel="alternate"
href={`${url}${lang === defaultLocale ? '' : `/${lang}/`}`}
hreflang={lang === 'zh-tw' ? 'zh-TW' : lang}
/>
))}
<!-- Facebook Open Graph -->
<meta property="og:title" content={pageTitle} />
<meta property="og:type" content={postTitle ? 'article' : 'website'} />
<meta property="og:image" content={pageImage} />
<meta property="og:url" content={Astro.url} />
<meta property="og:description" content={pageDescription} />
<meta property="og:site_name" content={title} />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={pageTitle} />
<meta name="twitter:description" content={pageDescription} />
<meta name="twitter:image" content={pageImage} />
{twitterID && (
<>
<meta name="twitter:site" content={twitterID} />
<meta name="twitter:creator" content={twitterID} />
</>
)}
<!-- Site Verification -->
{google && <meta name="google-site-verification" content={google} />}
{bing && <meta name="msvalidate.01" content={bing} />}
{yandex && <meta name="yandex-verification" content={yandex} />}
{baidu && <meta name="baidu-site-verification" content={baidu} />}
<!-- Global View Transition -->
<ClientRouter fallback="none" />
<!-- Theme Toggle -->
<script
is:inline
define:vars={{ defaultMode: themeConfig.color.mode, lightMode, darkMode }}
>
// Check if current theme is dark mode
// Priority: Local storage theme > Default theme > System preference
function isCurrentDark() {
const currentTheme = localStorage.getItem('theme')
if (currentTheme)
return currentTheme === 'dark'
if (defaultMode)
return defaultMode === 'dark'
return window.matchMedia('(prefers-color-scheme: dark)').matches
}
// Initialize theme
function initTheme(doc = document) {
const isDark = isCurrentDark()
doc.documentElement.classList.toggle('dark', isDark)
const metaTheme = doc.querySelector('meta[name="theme-color"]')
if (metaTheme) {
metaTheme.setAttribute('content', isDark ? darkMode : lightMode)
}
}
// Function 1: Initialize theme on first load
initTheme()
// Function 2: Update theme before page transition to prevent flashing
document.addEventListener('astro:before-swap', ({ newDocument }) => {
initTheme(newDocument)
})
// Function 3: listen to system theme changes in real-time
window
.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', ({ matches: isDark }) => {
localStorage.setItem('theme', isDark ? 'dark' : 'light')
initTheme()
document.dispatchEvent(new Event('theme-changed'))
})
</script>
<!-- Google Analytics -->
{googleAnalyticsID && (
<>
<script
type="text/partytown"
src={customGoogleAnalyticsJS || `https://www.googletagmanager.com/gtag/js?id=${googleAnalyticsID}`}
/>
<script type="text/partytown" define:vars={{ googleAnalyticsID, customGoogleAnalyticsJS }}>
window.dataLayer = window.dataLayer || []
function gtag(...args) {
dataLayer.push(args)
}
gtag('js', new Date())
if (customGoogleAnalyticsJS) {
gtag('config', googleAnalyticsID, {
transport_url: new URL(customGoogleAnalyticsJS).origin,
})
}
else {
gtag('config', googleAnalyticsID)
}
</script>
</>
)}
<!-- Umami Analytics -->
{umamiAnalyticsID && (
<script
type="text/partytown"
crossorigin="anonymous"
data-website-id={umamiAnalyticsID}
src={customUmamiAnalyticsJS || 'https://analytics.umami.is/script.js'}
data-cache="true"
/>
)}
</head>