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>