master
1---
2import {
3 buildConfig,
4 fastActionsConfig,
5 profileConfig,
6 searchConfig,
7 siteConfig,
8} from '@/config';
9import '@/styles/global.css';
10import '@/styles/transition.css';
11import Banner from '@components/Banner.astro';
12import FastActions from '@components/FastActions.astro';
13import ImageZoomLoader from '@components/misc/ImageZoomLoader.astro';
14import Navbar from '@components/Navbar.astro';
15import PageFooter from '@components/PageFooter.astro';
16import Search from '@components/Search.astro';
17import 'photoswipe/dist/photoswipe.css';
18import { pwaAssetsHead } from 'virtual:pwa-assets/head';
19import { pwaInfo } from 'virtual:pwa-info';
20
21interface Props {
22 title?: string;
23 description?: string;
24 lang?: string;
25 banner?: {
26 src: string;
27 basePath?: string;
28 };
29}
30
31let { title, lang } = Astro.props;
32const { description, banner } = Astro.props;
33
34let pageTitle: string;
35if (title) pageTitle = `${title} - ${siteConfig.title}`;
36else
37 pageTitle = `${siteConfig.title}${siteConfig.subtitle.length > 0 ? ` - ${siteConfig.subtitle}` : ''}`;
38
39if (!lang) lang = `${siteConfig.lang}`;
40const siteLang = lang.replace('_', '-');
41---
42
43<!doctype html>
44<html lang={siteLang}>
45 <head>
46 <title>{pageTitle}</title>
47
48 <meta charset="UTF-8" />
49 <meta name="author" content={profileConfig.name} />
50
51 <!-- Open Graph / Facebook -->
52 <meta property="og:site_name" content={siteConfig.title} />
53 <meta property="og:url" content={Astro.url} />
54 <meta property="og:title" content={pageTitle} />
55 <meta property="og:description" content={description || pageTitle} />
56
57 <meta name="twitter:card" content="summary_large_image" />
58 <meta property="twitter:url" content={Astro.url} />
59 <meta name="twitter:title" content={pageTitle} />
60 <meta name="twitter:description" content={description || pageTitle} />
61
62 <meta name="viewport" content="width=device-width" />
63
64 <meta name="generator" content={Astro.generator} />
65
66 {
67 pwaAssetsHead.themeColor && (
68 <meta name="theme-color" content={pwaAssetsHead.themeColor.content} />
69 )
70 }
71 {pwaAssetsHead.links.map((link) => <link {...link} />)}
72 {pwaInfo && <Fragment set:html={pwaInfo.webManifest.linkTag} />}
73 <script src="@scripts/pwa.ts"></script>
74
75 <link
76 rel="alternate"
77 type="application/rss+xml"
78 title={siteConfig.title}
79 href={new URL('rss.xml', Astro.site)}
80 />
81
82 <slot name="head" />
83 </head>
84 <body class="bg-base-100 text-base-content flex min-h-screen flex-col">
85 {fastActionsConfig.enable && <FastActions />}
86 <Navbar title={siteConfig.title}>
87 <slot name="header" />
88 {siteConfig.banner !== false && <Banner src={banner?.src} basePath={banner?.basePath} />}
89 <div id="body-wrap" class="w-full items-center md:px-4">
90 <slot />
91 </div>
92 </Navbar>
93 {searchConfig.enable && <Search />}
94 <PageFooter />
95 {siteConfig.banner !== false && <div id="page-height-extend" class="hidden" />}
96 </body>
97</html>
98
99{/* 初始化 Swup */}
100<script>
101 import SwupHeadPlugin from '@swup/head-plugin';
102 import SwupParallelPlugin from '@swup/parallel-plugin';
103 import SwupPreloadPlugin from '@swup/preload-plugin';
104 import SwupProgressPlugin from '@swup/progress-plugin';
105 import SwupScriptsPlugin from '@swup/scripts-plugin';
106 import SwupScrollPlugin from '@swup/scroll-plugin';
107 import Swup from 'swup';
108
109 import { siteConfig } from '@/config';
110
111 window.swup = new Swup({
112 containers: [
113 'main',
114 ...(siteConfig.banner === false
115 ? []
116 : [
117 ...(typeof siteConfig.banner.src === 'string'
118 ? ['#banner-img']
119 : ['#banner-img-light', '#banner-img-dark']),
120 '#banner-text',
121 ]),
122 ],
123 animationSelector: '[class*="swup-transition-"]',
124 linkSelector: 'a:not([href="#"])[href]',
125 plugins: [
126 new SwupHeadPlugin(),
127 new SwupParallelPlugin({
128 containers: [...(siteConfig.banner === false ? [] : ['#banner-img'])],
129 }),
130 new SwupPreloadPlugin(),
131 new SwupProgressPlugin({
132 delay: 2000,
133 }),
134 new SwupScriptsPlugin(),
135 new SwupScrollPlugin({
136 offset: () => (document.querySelector('#navbar') as HTMLDivElement).offsetHeight,
137 }),
138 ],
139 });
140
141 const dispatch = (name: string) => document.dispatchEvent(new Event(name));
142 window.swup.hooks.before('content:replace', () => dispatch('astro:before-swap'));
143 window.swup.hooks.on('content:replace', () => dispatch('astro:after-swap'));
144 window.swup.hooks.on('page:view', () => dispatch('astro:page-load'));
145</script>
146
147<script>
148 import { buildConfig } from '@/config';
149 import { convertTimeToRelative } from '@scripts/utils';
150
151 // 深色模式切换
152 function applyDarkMode() {
153 if (
154 localStorage.darkMode === 'true' ||
155 (!('darkMode' in localStorage) &&
156 window.matchMedia('(prefers-color-scheme: dark)').matches)
157 ) {
158 document.documentElement.setAttribute('data-theme', buildConfig.themeNames.dark);
159 } else document.documentElement.setAttribute('data-theme', buildConfig.themeNames.light);
160 }
161
162 function setup() {
163 applyDarkMode();
164 convertTimeToRelative();
165 }
166
167 document.addEventListener('astro:after-swap', setup);
168 setup();
169</script>
170
171{buildConfig.enableImageZoom && <ImageZoomLoader />}
172
173<style is:global>
174 a[data-pwsp-src] {
175 cursor: zoom-in;
176 }
177</style>