135 lines
3.9 KiB
TypeScript
135 lines
3.9 KiB
TypeScript
import type { Metadata } from "next";
|
||
import { Geist, Geist_Mono } from "next/font/google";
|
||
import { Toaster } from "sonner";
|
||
import { ThemeProvider } from "@/components/theme-provider";
|
||
|
||
import "./globals.css";
|
||
import { SessionProvider } from "next-auth/react";
|
||
|
||
import { getSiteSettings } from "@/lib/db/queries";
|
||
|
||
export async function generateMetadata(): Promise<Metadata> {
|
||
const settings = await getSiteSettings();
|
||
|
||
return {
|
||
metadataBase: settings.siteUrl ? new URL(settings.siteUrl) : undefined,
|
||
title: {
|
||
template: `%s | ${settings.siteName}`,
|
||
default: settings.siteName,
|
||
},
|
||
description: settings.siteDescription || settings.siteName,
|
||
icons: {
|
||
icon: settings.siteFavicon || "/favicon.ico",
|
||
apple: settings.siteFavicon || "/favicon.ico",
|
||
},
|
||
};
|
||
}
|
||
|
||
export const viewport = {
|
||
maximumScale: 1, // Disable auto-zoom on mobile Safari
|
||
};
|
||
|
||
const geist = Geist({
|
||
subsets: ["latin"],
|
||
display: "swap",
|
||
variable: "--font-geist",
|
||
});
|
||
|
||
const geistMono = Geist_Mono({
|
||
subsets: ["latin"],
|
||
display: "swap",
|
||
variable: "--font-geist-mono",
|
||
});
|
||
|
||
const LIGHT_THEME_COLOR = "hsl(0 0% 100%)";
|
||
const DARK_THEME_COLOR = "hsl(240deg 10% 3.92%)";
|
||
const THEME_COLOR_SCRIPT = `\
|
||
(function() {
|
||
var html = document.documentElement;
|
||
var meta = document.querySelector('meta[name="theme-color"]');
|
||
if (!meta) {
|
||
meta = document.createElement('meta');
|
||
meta.setAttribute('name', 'theme-color');
|
||
document.head.appendChild(meta);
|
||
}
|
||
function updateThemeColor() {
|
||
var isDark = html.classList.contains('dark');
|
||
meta.setAttribute('content', isDark ? '${DARK_THEME_COLOR}' : '${LIGHT_THEME_COLOR}');
|
||
}
|
||
var observer = new MutationObserver(updateThemeColor);
|
||
observer.observe(html, { attributes: true, attributeFilter: ['class'] });
|
||
updateThemeColor();
|
||
})();`;
|
||
|
||
const CHUNK_ERROR_RECOVERY_SCRIPT = `\
|
||
(function() {
|
||
var hasReloaded = sessionStorage.getItem('chunk-reload-attempted');
|
||
|
||
window.addEventListener('error', function(event) {
|
||
var isChunkError = event.message && (
|
||
event.message.includes('Failed to load chunk') ||
|
||
event.message.includes('Loading chunk') ||
|
||
event.message.includes('/_next/static/chunks/')
|
||
);
|
||
|
||
if (isChunkError && !hasReloaded) {
|
||
console.warn('[ChunkError] 检测到chunk加载失败,自动刷新页面...');
|
||
sessionStorage.setItem('chunk-reload-attempted', 'true');
|
||
setTimeout(function() {
|
||
window.location.reload();
|
||
}, 1000);
|
||
}
|
||
}, true);
|
||
|
||
// 页面加载成功后清除标记
|
||
window.addEventListener('load', function() {
|
||
setTimeout(function() {
|
||
sessionStorage.removeItem('chunk-reload-attempted');
|
||
}, 2000);
|
||
});
|
||
})();`;
|
||
|
||
export default function RootLayout({
|
||
children,
|
||
}: Readonly<{
|
||
children: React.ReactNode;
|
||
}>) {
|
||
return (
|
||
<html
|
||
className={`${geist.variable} ${geistMono.variable}`}
|
||
// `next-themes` injects an extra classname to the body element to avoid
|
||
// visual flicker before hydration. Hence the `suppressHydrationWarning`
|
||
// prop is necessary to avoid the React hydration mismatch warning.
|
||
// https://github.com/pacocoursey/next-themes?tab=readme-ov-file#with-app
|
||
lang="zh-CN"
|
||
suppressHydrationWarning
|
||
>
|
||
<head>
|
||
<script
|
||
// biome-ignore lint/security/noDangerouslySetInnerHtml: "Required"
|
||
dangerouslySetInnerHTML={{
|
||
__html: THEME_COLOR_SCRIPT,
|
||
}}
|
||
/>
|
||
<script
|
||
// biome-ignore lint/security/noDangerouslySetInnerHtml: "Required"
|
||
dangerouslySetInnerHTML={{
|
||
__html: CHUNK_ERROR_RECOVERY_SCRIPT,
|
||
}}
|
||
/>
|
||
</head>
|
||
<body className="antialiased">
|
||
<ThemeProvider
|
||
attribute="class"
|
||
defaultTheme="system"
|
||
disableTransitionOnChange
|
||
enableSystem
|
||
>
|
||
<Toaster position="top-center" />
|
||
<SessionProvider>{children}</SessionProvider>
|
||
</ThemeProvider>
|
||
</body>
|
||
</html>
|
||
);
|
||
}
|