This commit is contained in:
857
src/layouts/Layout.astro
Normal file
857
src/layouts/Layout.astro
Normal file
@@ -0,0 +1,857 @@
|
||||
---
|
||||
import Analytics from "@vercel/analytics/astro";
|
||||
import "@fontsource/roboto/400.css";
|
||||
import "@fontsource/roboto/500.css";
|
||||
import "@fontsource/roboto/700.css";
|
||||
|
||||
import { profileConfig, siteConfig } from "@/config";
|
||||
import ConfigCarrier from "@components/ConfigCarrier.astro";
|
||||
import {
|
||||
AUTO_MODE,
|
||||
BANNER_HEIGHT,
|
||||
BANNER_HEIGHT_EXTEND,
|
||||
BANNER_HEIGHT_HOME,
|
||||
DARK_MODE,
|
||||
DEFAULT_THEME,
|
||||
LIGHT_MODE,
|
||||
PAGE_WIDTH,
|
||||
} from "../constants/constants";
|
||||
import { defaultFavicons } from "../constants/icon";
|
||||
import type { Favicon } from "../types/config";
|
||||
import { url, pathsEqual } from "../utils/url-utils";
|
||||
import "katex/dist/katex.css";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
banner?: string;
|
||||
description?: string;
|
||||
lang?: string;
|
||||
setOGTypeArticle?: boolean;
|
||||
}
|
||||
|
||||
let { title, banner, description, lang, setOGTypeArticle } = Astro.props;
|
||||
|
||||
// apply a class to the body element to decide the height of the banner, only used for initial page load
|
||||
// Swup can update the body for each page visit, but it's after the page transition, causing a delay for banner height change
|
||||
// so use Swup hooks instead to change the height immediately when a link is clicked
|
||||
const isHomePage = pathsEqual(Astro.url.pathname, url("/"));
|
||||
|
||||
// defines global css variables
|
||||
// why doing this in Layout instead of GlobalStyles: https://github.com/withastro/astro/issues/6728#issuecomment-1502203757
|
||||
const configHue = siteConfig.themeColor.hue;
|
||||
if (!banner || typeof banner !== "string" || banner.trim() === "") {
|
||||
banner = siteConfig.banner.src;
|
||||
}
|
||||
|
||||
// TODO don't use post cover as banner for now
|
||||
banner = siteConfig.banner.src;
|
||||
|
||||
const enableBanner = siteConfig.banner.enable;
|
||||
|
||||
let pageTitle: string;
|
||||
if (title) {
|
||||
pageTitle = `${title} - ${siteConfig.title}`;
|
||||
} else {
|
||||
if (siteConfig.subtitle && siteConfig.subtitle.trim() !== "") {
|
||||
pageTitle = `${siteConfig.title} - ${siteConfig.subtitle}`;
|
||||
} else {
|
||||
pageTitle = siteConfig.title;
|
||||
}
|
||||
}
|
||||
|
||||
// Use siteConfig.description as fallback for meta description when no description is provided
|
||||
if (!description) {
|
||||
description = siteConfig.description || pageTitle;
|
||||
}
|
||||
|
||||
const favicons: Favicon[] =
|
||||
siteConfig.favicon.length > 0 ? siteConfig.favicon : defaultFavicons;
|
||||
|
||||
// const siteLang = siteConfig.lang.replace('_', '-')
|
||||
if (!lang) {
|
||||
lang = `${siteConfig.lang}`;
|
||||
}
|
||||
const siteLang = lang.replace("_", "-");
|
||||
|
||||
const bannerOffsetByPosition = {
|
||||
top: `${BANNER_HEIGHT_EXTEND}vh`,
|
||||
center: `${BANNER_HEIGHT_EXTEND / 2}vh`,
|
||||
bottom: "0",
|
||||
};
|
||||
const bannerOffset =
|
||||
bannerOffsetByPosition[siteConfig.banner.position || "center"];
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html
|
||||
lang={siteLang}
|
||||
class="bg-[var(--page-bg)] transition text-[12px] md:text-[16px]"
|
||||
data-overlayscrollbars-initialize
|
||||
>
|
||||
<!-- 手机端适配 -->
|
||||
|
||||
<head>
|
||||
<title>{pageTitle}</title>
|
||||
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content={description} />
|
||||
<meta name="author" content={profileConfig.name} />
|
||||
{
|
||||
siteConfig.keywords && (
|
||||
<meta name="keywords" content={siteConfig.keywords.join(", ")} />
|
||||
)
|
||||
}
|
||||
|
||||
<meta property="og:site_name" content={siteConfig.title} />
|
||||
<meta property="og:url" content={Astro.url} />
|
||||
<meta property="og:title" content={pageTitle} />
|
||||
<meta property="og:description" content={description} />
|
||||
{
|
||||
setOGTypeArticle ? (
|
||||
<meta property="og:type" content="article" />
|
||||
) : (
|
||||
<meta property="og:type" content="website" />
|
||||
)
|
||||
}
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={Astro.url} />
|
||||
<meta name="twitter:title" content={pageTitle} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<script src="/js/umami-share.js" defer></script>
|
||||
<script
|
||||
data-swup-ignore-script
|
||||
is:inline
|
||||
src="https://pic.acofork.com/random.js"></script>
|
||||
|
||||
{
|
||||
/* <!-- Content Security Policy -->
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' https://giscus.app https://hpic.072103.xyz https://umami.acofork.com https://hm.baidu.com https://www.googletagmanager.com https://www.google-analytics.com https://pagead2.googlesyndication.com https://googleads.g.doubleclick.net https://static.cloudflareinsights.com https://*.adtrafficquality.google; style-src 'self' 'unsafe-inline' https://giscus.app https://fonts.googleapis.com https://api.iconify.design; font-src 'self' https://fonts.gstatic.com https://api.iconify.design; img-src 'self' data: https: http:; connect-src 'self' https://umami.acofork.com https://hm.baidu.com https://www.google-analytics.com https://analytics.google.com https://api.iconify.design https://static.cloudflareinsights.com https://pic.2x.nz https://q2.qlogo.cn https://ep1.adtrafficquality.google https://googleads.g.doubleclick.net; frame-src 'self' https://giscus.app *.bilibili.com https://www.google.com https://googleads.g.doubleclick.net https://support.nodeget.com https://*.adtrafficquality.google; object-src 'none'; base-uri 'self'; form-action 'self';"> */
|
||||
}
|
||||
{
|
||||
favicons.map((favicon) => (
|
||||
<link
|
||||
rel="icon"
|
||||
href={favicon.src.startsWith("/") ? url(favicon.src) : favicon.src}
|
||||
sizes={favicon.sizes}
|
||||
media={favicon.theme && `(prefers-color-scheme: ${favicon.theme})`}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
||||
<!-- Set the theme before the page is rendered to avoid a flash -->
|
||||
<script
|
||||
is:inline
|
||||
define:vars={{
|
||||
DEFAULT_THEME,
|
||||
LIGHT_MODE,
|
||||
DARK_MODE,
|
||||
AUTO_MODE,
|
||||
BANNER_HEIGHT_EXTEND,
|
||||
PAGE_WIDTH,
|
||||
configHue,
|
||||
forceDarkMode: siteConfig.themeColor.forceDarkMode,
|
||||
}}
|
||||
>
|
||||
// Force dark mode if configured
|
||||
if (forceDarkMode) {
|
||||
document.documentElement.classList.add("dark");
|
||||
localStorage.setItem("theme", DARK_MODE);
|
||||
} else {
|
||||
// Load the theme from local storage
|
||||
const theme = localStorage.getItem("theme") || AUTO_MODE;
|
||||
switch (theme) {
|
||||
case LIGHT_MODE:
|
||||
document.documentElement.classList.remove("dark");
|
||||
break;
|
||||
case DARK_MODE:
|
||||
document.documentElement.classList.add("dark");
|
||||
break;
|
||||
case AUTO_MODE:
|
||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load the hue from local storage
|
||||
const hue = localStorage.getItem("hue") || configHue;
|
||||
document.documentElement.style.setProperty("--hue", hue);
|
||||
|
||||
// calculate the --banner-height-extend, which needs to be a multiple of 4 to avoid blurry text
|
||||
let offset = Math.floor(
|
||||
window.innerHeight * (BANNER_HEIGHT_EXTEND / 100)
|
||||
);
|
||||
offset = offset - (offset % 4);
|
||||
document.documentElement.style.setProperty(
|
||||
"--banner-height-extend",
|
||||
`${offset}px`
|
||||
);
|
||||
|
||||
// // Background image loading detection
|
||||
// const bgUrl = getComputedStyle(document.documentElement).getPropertyValue('--bg-url').trim();
|
||||
// const bgEnable = getComputedStyle(document.documentElement).getPropertyValue('--bg-enable').trim();
|
||||
|
||||
// if (bgUrl && bgUrl !== 'none' && bgEnable === '1') {
|
||||
// const img = new Image();
|
||||
// const urlMatch = bgUrl.match(/url\(["']?([^"')]+)["']?\)/);
|
||||
// if (urlMatch) {
|
||||
// img.onload = function() {
|
||||
// // 背景图片完全加载后,显示背景并启用卡片透明效果
|
||||
// document.body.classList.add('bg-loaded');
|
||||
// document.documentElement.style.setProperty('--card-bg', 'var(--card-bg-transparent)');
|
||||
// document.documentElement.style.setProperty('--float-panel-bg', 'var(--float-panel-bg-transparent)');
|
||||
// };
|
||||
// img.onerror = function() {
|
||||
// // Keep cards opaque if background image fails to load
|
||||
// console.warn('Background image failed to load, keeping cards opaque');
|
||||
// };
|
||||
// img.src = urlMatch[1];
|
||||
// }
|
||||
// }
|
||||
</script>
|
||||
<style
|
||||
define:vars={{
|
||||
configHue,
|
||||
"page-width": `${PAGE_WIDTH}rem`,
|
||||
"bg-url": siteConfig.background.src
|
||||
? `url(${siteConfig.background.src})`
|
||||
: "none",
|
||||
"bg-enable": siteConfig.background.enable ? "1" : "0",
|
||||
"bg-position": siteConfig.background.position || "center",
|
||||
"bg-size": siteConfig.background.size || "cover",
|
||||
"bg-repeat": siteConfig.background.repeat || "no-repeat",
|
||||
"bg-attachment": siteConfig.background.attachment || "fixed",
|
||||
"bg-opacity": (siteConfig.background.opacity || 0.3).toString(),
|
||||
}}
|
||||
>
|
||||
:root {
|
||||
--bg-url: var(--bg-url);
|
||||
--bg-enable: var(--bg-enable);
|
||||
--bg-position: var(--bg-position);
|
||||
--bg-size: var(--bg-size);
|
||||
--bg-repeat: var(--bg-repeat);
|
||||
--bg-attachment: var(--bg-attachment);
|
||||
--bg-opacity: var(--bg-opacity);
|
||||
}
|
||||
|
||||
/* Background image configuration */
|
||||
body {
|
||||
--bg-url: var(--bg-url);
|
||||
--bg-enable: var(--bg-enable);
|
||||
--bg-position: var(--bg-position);
|
||||
--bg-size: var(--bg-size);
|
||||
--bg-repeat: var(--bg-repeat);
|
||||
--bg-attachment: var(--bg-attachment);
|
||||
--bg-opacity: var(--bg-opacity);
|
||||
}
|
||||
|
||||
#bg-box {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-position: var(--bg-position);
|
||||
background-size: var(--bg-size);
|
||||
background-repeat: var(--bg-repeat);
|
||||
background-attachment: var(--bg-attachment);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
#bg-box.loaded {
|
||||
opacity: calc(var(--bg-opacity) * var(--bg-enable));
|
||||
}
|
||||
</style>
|
||||
|
||||
<slot name="head" />
|
||||
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title={profileConfig.name}
|
||||
href={`${Astro.site}rss.xml`}
|
||||
/>
|
||||
<!-- Umami分析(自建) -->
|
||||
<script
|
||||
defer
|
||||
src="https://umami.acofork.com/script.js"
|
||||
data-website-id="5d710dbd-3a2e-43e3-a553-97b415090c63"
|
||||
data-swup-ignore-script></script>
|
||||
{
|
||||
/* - Umami分析(云-备用)
|
||||
<script defer src="https://cloud.umami.is/script.js" data-website-id="5d710dbd-3a2e-43e3-a553-97b415090c63" data-swup-ignore-script></script> */
|
||||
}
|
||||
<!-- 超级吊的Umami eop -->
|
||||
<script
|
||||
defer
|
||||
src="https://eo-umami.acofork.com/script.js"
|
||||
data-website-id="3f525363-5e81-4653-9491-1bd205c2a571"></script>
|
||||
<!-- 百度统计 -->
|
||||
<script data-swup-ignore-script>
|
||||
var _hmt = _hmt || [];
|
||||
(function () {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?b219eaad631b87d273cfe72148b2138b";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- clarity 分析 -->
|
||||
<script type="text/javascript" data-swup-ignore-script>
|
||||
(function (c, l, a, r, i, t, y) {
|
||||
c[a] =
|
||||
c[a] ||
|
||||
function () {
|
||||
(c[a].q = c[a].q || []).push(arguments);
|
||||
};
|
||||
t = l.createElement(r);
|
||||
t.async = 1;
|
||||
t.src = "https://www.clarity.ms/tag/" + i;
|
||||
y = l.getElementsByTagName(r)[0];
|
||||
y.parentNode.insertBefore(t, y);
|
||||
})(window, document, "clarity", "script", "t8f0gmcwtx");
|
||||
</script>
|
||||
<!-- gad -->
|
||||
<script
|
||||
async
|
||||
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-1683686345039700"
|
||||
crossorigin="anonymous"
|
||||
data-swup-ignore-script></script>
|
||||
<meta name="google-adsense-account" content="ca-pub-1683686345039700" />
|
||||
|
||||
<!-- 谷歌分析 -->
|
||||
<script
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-9Z4LT4H8KH"
|
||||
data-swup-ignore-script></script>
|
||||
<script is:inline>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag("js", new Date());
|
||||
|
||||
gtag("config", "G-9Z4LT4H8KH");
|
||||
</script>
|
||||
|
||||
<!-- Cloudflare Web Analytics -->
|
||||
<script
|
||||
defer
|
||||
src="https://static.cloudflareinsights.com/beacon.min.js"
|
||||
data-cf-beacon='{"token": "15fe148e91b34f10a15652e1a74ab26c"}'
|
||||
data-swup-ignore-script></script><!-- End Cloudflare Web Analytics -->
|
||||
</head>
|
||||
<body
|
||||
class="min-h-screen transition"
|
||||
class:list={[{ "lg:is-home": isHomePage, "enable-banner": enableBanner }]}
|
||||
data-overlayscrollbars-initialize
|
||||
>
|
||||
<div id="bg-box"></div>
|
||||
<ConfigCarrier />
|
||||
<slot />
|
||||
|
||||
<!-- increase the page height during page transition to prevent the scrolling animation from jumping -->
|
||||
<div id="page-height-extend" class="hidden h-[300vh]"></div>
|
||||
|
||||
<!-- 域名检测脚本 -->
|
||||
<script
|
||||
is:inline
|
||||
define:vars={{ officialSites: siteConfig.officialSites || [] }}
|
||||
>
|
||||
// 域名检测功能
|
||||
function checkDomain() {
|
||||
try {
|
||||
// 获取当前访问的完整URL
|
||||
const currentUrl = window.location.href;
|
||||
// 获取当前域名
|
||||
const currentDomain = window.location.hostname;
|
||||
|
||||
// 获取所有官方域名
|
||||
const officialDomains = officialSites
|
||||
.map((site) => {
|
||||
try {
|
||||
const url = typeof site === "string" ? site : site.url;
|
||||
return new URL(url).hostname;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((domain) => domain !== null);
|
||||
|
||||
// 检查当前域名是否为官方域名或本地开发环境
|
||||
const isOfficialDomain = officialDomains.some(
|
||||
(officialDomain) =>
|
||||
currentDomain === officialDomain ||
|
||||
currentDomain.endsWith("." + officialDomain)
|
||||
);
|
||||
const isLocalDev =
|
||||
currentDomain === "localhost" || currentDomain === "127.0.0.1";
|
||||
|
||||
// 如果当前域名不是官方域名且不是本地开发环境
|
||||
if (!isOfficialDomain && !isLocalDev) {
|
||||
// 创建警告弹窗
|
||||
const shouldRedirect = confirm(
|
||||
`⚠️ 域名安全警告\n\n` +
|
||||
`您当前访问的域名:${currentDomain}\n` +
|
||||
`官方域名:${officialDomains.join(", ")}\n\n` +
|
||||
`您可能正在访问非官方网站,存在安全风险!\n\n` +
|
||||
`点击"确定"跳转到官方网站\n` +
|
||||
`点击"取消"继续访问当前网站(不推荐)`
|
||||
);
|
||||
|
||||
// 如果用户选择跳转到官方网站
|
||||
if (shouldRedirect) {
|
||||
// 构建官方网站的对应页面URL(使用第一个官方网站)
|
||||
const currentPath =
|
||||
window.location.pathname +
|
||||
window.location.search +
|
||||
window.location.hash;
|
||||
const firstSite = officialSites[0];
|
||||
const firstUrl =
|
||||
typeof firstSite === "string" ? firstSite : firstSite.url;
|
||||
const officialPageUrl = firstUrl + currentPath;
|
||||
// 跳转到官方网站
|
||||
window.location.href = officialPageUrl;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果检测过程中出现错误,静默处理,不影响正常访问
|
||||
console.warn("域名检测失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后执行域名检测
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", checkDomain);
|
||||
} else {
|
||||
checkDomain();
|
||||
}
|
||||
</script>
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style
|
||||
is:global
|
||||
define:vars={{
|
||||
bannerOffset,
|
||||
"banner-height-home": `${BANNER_HEIGHT_HOME}vh`,
|
||||
"banner-height": `${BANNER_HEIGHT}vh`,
|
||||
}}
|
||||
>
|
||||
@tailwind components;
|
||||
@layer components {
|
||||
.enable-banner.is-home #banner-wrapper {
|
||||
@apply h-[var(--banner-height-home)] translate-y-[var(--banner-height-extend)];
|
||||
}
|
||||
.enable-banner #banner-wrapper {
|
||||
@apply h-[var(--banner-height-home)];
|
||||
}
|
||||
|
||||
.enable-banner.is-home #banner {
|
||||
@apply h-[var(--banner-height-home)] translate-y-0;
|
||||
}
|
||||
.enable-banner #banner {
|
||||
@apply h-[var(--banner-height-home)] translate-y-[var(--bannerOffset)];
|
||||
}
|
||||
.enable-banner.is-home #main-grid {
|
||||
@apply translate-y-[var(--banner-height-extend)];
|
||||
}
|
||||
.enable-banner #top-row {
|
||||
@apply h-[calc(var(--banner-height-home)_-_4.5rem)] transition-all duration-300;
|
||||
}
|
||||
.enable-banner.is-home #sidebar-sticky {
|
||||
@apply top-[calc(1rem_-_var(--banner-height-extend))];
|
||||
}
|
||||
.navbar-hidden {
|
||||
@apply opacity-0 -translate-y-16;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import "overlayscrollbars/overlayscrollbars.css";
|
||||
import {
|
||||
OverlayScrollbars,
|
||||
// ScrollbarsHidingPlugin,
|
||||
// SizeObserverPlugin,
|
||||
// ClickScrollPlugin
|
||||
} from "overlayscrollbars";
|
||||
import {
|
||||
getHue,
|
||||
getStoredTheme,
|
||||
setHue,
|
||||
setTheme,
|
||||
getBgBlur,
|
||||
setBgBlur,
|
||||
getHideBg,
|
||||
setHideBg,
|
||||
} from "../utils/setting-utils";
|
||||
import { pathsEqual, url } from "../utils/url-utils";
|
||||
import {
|
||||
BANNER_HEIGHT,
|
||||
BANNER_HEIGHT_HOME,
|
||||
BANNER_HEIGHT_EXTEND,
|
||||
MAIN_PANEL_OVERLAPS_BANNER_HEIGHT,
|
||||
} from "../constants/constants";
|
||||
import { siteConfig } from "../config";
|
||||
|
||||
/* Preload fonts */
|
||||
// (async function() {
|
||||
// try {
|
||||
// await Promise.all([
|
||||
// document.fonts.load("400 1em Roboto"),
|
||||
// document.fonts.load("700 1em Roboto"),
|
||||
// ]);
|
||||
// document.body.classList.remove("hidden");
|
||||
// } catch (error) {
|
||||
// console.log("Failed to load fonts:", error);
|
||||
// }
|
||||
// })();
|
||||
|
||||
/* TODO This is a temporary solution for style flicker issue when the transition is activated */
|
||||
/* issue link: https://github.com/withastro/astro/issues/8711, the solution get from here too */
|
||||
/* update: fixed in Astro 3.2.4 */
|
||||
/*
|
||||
function disableAnimation() {
|
||||
const css = document.createElement('style')
|
||||
css.appendChild(
|
||||
document.createTextNode(
|
||||
`*{
|
||||
-webkit-transition:none!important;
|
||||
-moz-transition:none!important;
|
||||
-o-transition:none!important;
|
||||
-ms-transition:none!important;
|
||||
transition:none!important
|
||||
}`
|
||||
)
|
||||
)
|
||||
document.head.appendChild(css)
|
||||
|
||||
return () => {
|
||||
// Force restyle
|
||||
;(() => window.getComputedStyle(document.body))()
|
||||
|
||||
// Wait for next tick before removing
|
||||
setTimeout(() => {
|
||||
document.head.removeChild(css)
|
||||
}, 1)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
const bannerEnabled = !!document.getElementById("banner-wrapper");
|
||||
|
||||
function setClickOutsideToClose(panel: string, ignores: string[]) {
|
||||
document.addEventListener("click", (event) => {
|
||||
let panelDom = document.getElementById(panel);
|
||||
let tDom = event.target;
|
||||
if (!(tDom instanceof Node)) return; // Ensure the event target is an HTML Node
|
||||
for (let ig of ignores) {
|
||||
let ie = document.getElementById(ig);
|
||||
if (ie == tDom || ie?.contains(tDom)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
panelDom!.classList.add("float-panel-closed");
|
||||
});
|
||||
}
|
||||
setClickOutsideToClose("display-setting", [
|
||||
"display-setting",
|
||||
"display-settings-switch",
|
||||
]);
|
||||
setClickOutsideToClose("nav-menu-panel", [
|
||||
"nav-menu-panel",
|
||||
"nav-menu-switch",
|
||||
]);
|
||||
setClickOutsideToClose("search-panel", [
|
||||
"search-panel",
|
||||
"search-bar",
|
||||
"search-switch",
|
||||
]);
|
||||
|
||||
function loadTheme() {
|
||||
const theme = getStoredTheme();
|
||||
setTheme(theme);
|
||||
}
|
||||
|
||||
function loadHue() {
|
||||
setHue(getHue());
|
||||
}
|
||||
|
||||
function loadBgBlur() {
|
||||
setBgBlur(getBgBlur());
|
||||
setHideBg(getHideBg());
|
||||
}
|
||||
|
||||
function initCustomScrollbar() {
|
||||
const bodyElement = document.querySelector("body");
|
||||
if (!bodyElement) return;
|
||||
OverlayScrollbars(
|
||||
// docs say that a initialization to the body element would affect native functionality like window.scrollTo
|
||||
// but just leave it here for now
|
||||
{
|
||||
target: bodyElement,
|
||||
cancel: {
|
||||
nativeScrollbarsOverlaid: true, // don't initialize the overlay scrollbar if there is a native one
|
||||
},
|
||||
},
|
||||
{
|
||||
scrollbars: {
|
||||
theme: "scrollbar-base scrollbar-auto py-1",
|
||||
autoHide: "move",
|
||||
autoHideDelay: 500,
|
||||
autoHideSuspend: false,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const katexElements = document.querySelectorAll(
|
||||
".katex-display"
|
||||
) as NodeListOf<HTMLElement>;
|
||||
katexElements.forEach((ele) => {
|
||||
OverlayScrollbars(ele, {
|
||||
scrollbars: {
|
||||
theme: "scrollbar-base scrollbar-auto py-1",
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showBanner() {
|
||||
if (!siteConfig.banner.enable) return;
|
||||
|
||||
const banner = document.getElementById("banner");
|
||||
if (!banner) {
|
||||
console.error("Banner element not found");
|
||||
return;
|
||||
}
|
||||
|
||||
banner.classList.remove("opacity-0", "scale-105");
|
||||
}
|
||||
|
||||
function init() {
|
||||
// disableAnimation()() // TODO
|
||||
loadTheme();
|
||||
loadHue();
|
||||
loadBgBlur();
|
||||
initCustomScrollbar();
|
||||
showBanner();
|
||||
}
|
||||
|
||||
/* Load settings when entering the site */
|
||||
init();
|
||||
|
||||
const setup = () => {
|
||||
// TODO: temp solution to change the height of the banner
|
||||
/*
|
||||
window.swup.hooks.on('animation:out:start', () => {
|
||||
const path = window.location.pathname
|
||||
const body = document.querySelector('body')
|
||||
if (path[path.length - 1] === '/' && !body.classList.contains('is-home')) {
|
||||
body.classList.add('is-home')
|
||||
} else if (path[path.length - 1] !== '/' && body.classList.contains('is-home')) {
|
||||
body.classList.remove('is-home')
|
||||
}
|
||||
})
|
||||
*/
|
||||
window.swup.hooks.on("link:click", () => {
|
||||
// Remove the delay for the first time page load
|
||||
document.documentElement.style.setProperty("--content-delay", "0ms");
|
||||
|
||||
// prevent elements from overlapping the navbar
|
||||
if (!bannerEnabled) {
|
||||
return;
|
||||
}
|
||||
let threshold = window.innerHeight * (BANNER_HEIGHT / 100) - 72 - 16;
|
||||
let navbar = document.getElementById("navbar-wrapper");
|
||||
if (!navbar || !document.body.classList.contains("lg:is-home")) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
document.body.scrollTop >= threshold ||
|
||||
document.documentElement.scrollTop >= threshold
|
||||
) {
|
||||
navbar.classList.add("navbar-hidden");
|
||||
}
|
||||
});
|
||||
window.swup.hooks.on("content:replace", initCustomScrollbar);
|
||||
window.swup.hooks.on("visit:start", (visit: { to: { url: string } }) => {
|
||||
// change banner height immediately when a link is clicked
|
||||
const bodyElement = document.querySelector("body");
|
||||
if (pathsEqual(visit.to.url, url("/"))) {
|
||||
bodyElement!.classList.add("lg:is-home");
|
||||
} else {
|
||||
bodyElement!.classList.remove("lg:is-home");
|
||||
}
|
||||
|
||||
// increase the page height during page transition to prevent the scrolling animation from jumping
|
||||
const heightExtend = document.getElementById("page-height-extend");
|
||||
if (heightExtend) {
|
||||
heightExtend.classList.remove("hidden");
|
||||
}
|
||||
|
||||
// Hide the TOC while scrolling back to top
|
||||
let toc = document.getElementById("toc-wrapper");
|
||||
if (toc) {
|
||||
toc.classList.add("toc-not-ready");
|
||||
}
|
||||
});
|
||||
window.swup.hooks.on("page:view", () => {
|
||||
// hide the temp high element when the transition is done
|
||||
const heightExtend = document.getElementById("page-height-extend");
|
||||
if (heightExtend) {
|
||||
heightExtend.classList.remove("hidden");
|
||||
}
|
||||
});
|
||||
window.swup.hooks.on("visit:end", (_visit: { to: { url: string } }) => {
|
||||
setTimeout(() => {
|
||||
const heightExtend = document.getElementById("page-height-extend");
|
||||
if (heightExtend) {
|
||||
heightExtend.classList.add("hidden");
|
||||
}
|
||||
|
||||
// Just make the transition looks better
|
||||
const toc = document.getElementById("toc-wrapper");
|
||||
if (toc) {
|
||||
toc.classList.remove("toc-not-ready");
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
};
|
||||
if (window?.swup?.hooks) {
|
||||
setup();
|
||||
} else {
|
||||
document.addEventListener("swup:enable", setup);
|
||||
}
|
||||
|
||||
let backToTopBtn = document.getElementById("back-to-top-btn");
|
||||
let toc = document.getElementById("toc-wrapper");
|
||||
let navbar = document.getElementById("navbar-wrapper");
|
||||
function scrollFunction() {
|
||||
let bannerHeight = window.innerHeight * (BANNER_HEIGHT / 100);
|
||||
|
||||
if (backToTopBtn) {
|
||||
if (
|
||||
document.body.scrollTop > bannerHeight ||
|
||||
document.documentElement.scrollTop > bannerHeight
|
||||
) {
|
||||
backToTopBtn.classList.remove("hide");
|
||||
} else {
|
||||
backToTopBtn.classList.add("hide");
|
||||
}
|
||||
}
|
||||
|
||||
if (bannerEnabled && toc) {
|
||||
if (
|
||||
document.body.scrollTop > bannerHeight ||
|
||||
document.documentElement.scrollTop > bannerHeight
|
||||
) {
|
||||
toc.classList.remove("toc-hide");
|
||||
} else {
|
||||
toc.classList.add("toc-hide");
|
||||
}
|
||||
}
|
||||
|
||||
if (!bannerEnabled) return;
|
||||
if (navbar) {
|
||||
const NAVBAR_HEIGHT = 72;
|
||||
const MAIN_PANEL_EXCESS_HEIGHT = MAIN_PANEL_OVERLAPS_BANNER_HEIGHT * 16; // The height the main panel overlaps the banner
|
||||
|
||||
let bannerHeight = BANNER_HEIGHT;
|
||||
if (
|
||||
document.body.classList.contains("lg:is-home") &&
|
||||
window.innerWidth >= 1024
|
||||
) {
|
||||
bannerHeight = BANNER_HEIGHT_HOME;
|
||||
}
|
||||
let threshold =
|
||||
window.innerHeight * (bannerHeight / 100) -
|
||||
NAVBAR_HEIGHT -
|
||||
MAIN_PANEL_EXCESS_HEIGHT -
|
||||
16;
|
||||
if (
|
||||
document.body.scrollTop >= threshold ||
|
||||
document.documentElement.scrollTop >= threshold
|
||||
) {
|
||||
navbar.classList.add("navbar-hidden");
|
||||
} else {
|
||||
navbar.classList.remove("navbar-hidden");
|
||||
}
|
||||
}
|
||||
}
|
||||
window.onscroll = scrollFunction;
|
||||
|
||||
window.onresize = () => {
|
||||
// calculate the --banner-height-extend, which needs to be a multiple of 4 to avoid blurry text
|
||||
let offset = Math.floor(window.innerHeight * (BANNER_HEIGHT_EXTEND / 100));
|
||||
offset = offset - (offset % 4);
|
||||
document.documentElement.style.setProperty(
|
||||
"--banner-height-extend",
|
||||
`${offset}px`
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { Fancybox } from "@fancyapps/ui";
|
||||
import "@fancyapps/ui/dist/fancybox/fancybox.css";
|
||||
|
||||
const setup = () => {
|
||||
Fancybox.bind(".custom-md img, #post-cover img", {
|
||||
clickContent: "close",
|
||||
dblclickContent: "zoom",
|
||||
click: "close",
|
||||
dblclick: "zoom",
|
||||
Panels: {
|
||||
display: ["counter", "zoom"],
|
||||
},
|
||||
Images: {
|
||||
panning: true,
|
||||
zoom: true,
|
||||
protect: false,
|
||||
},
|
||||
} as any);
|
||||
|
||||
window.swup.hooks.on("page:view", () => {
|
||||
Fancybox.bind(".custom-md img, #post-cover img", {
|
||||
clickContent: "close",
|
||||
dblclickContent: "zoom",
|
||||
click: "close",
|
||||
dblclick: "zoom",
|
||||
Panels: {
|
||||
display: ["counter", "zoom"],
|
||||
},
|
||||
Images: {
|
||||
panning: true,
|
||||
zoom: true,
|
||||
protect: false,
|
||||
},
|
||||
} as any);
|
||||
});
|
||||
|
||||
window.swup.hooks.on(
|
||||
"content:replace",
|
||||
() => {
|
||||
Fancybox.unbind(".custom-md img, #post-cover img");
|
||||
},
|
||||
{ before: true }
|
||||
);
|
||||
};
|
||||
|
||||
if (window.swup) {
|
||||
setup();
|
||||
} else {
|
||||
document.addEventListener("swup:enable", setup);
|
||||
}
|
||||
</script>
|
||||
125
src/layouts/MainGridLayout.astro
Normal file
125
src/layouts/MainGridLayout.astro
Normal file
@@ -0,0 +1,125 @@
|
||||
---
|
||||
import Footer from "@components/Footer.astro";
|
||||
import Navbar from "@components/Navbar.astro";
|
||||
import BackToTop from "@components/control/BackToTop.astro";
|
||||
import SideBar from "@components/widget/SideBar.astro";
|
||||
import type { MarkdownHeading } from "astro";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import ImageWrapper from "../components/misc/ImageWrapper.astro";
|
||||
import TOC from "../components/widget/TOC.astro";
|
||||
import { siteConfig } from "../config";
|
||||
import {
|
||||
BANNER_HEIGHT,
|
||||
BANNER_HEIGHT_EXTEND,
|
||||
MAIN_PANEL_OVERLAPS_BANNER_HEIGHT,
|
||||
} from "../constants/constants";
|
||||
import Layout from "./Layout.astro";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
banner?: string;
|
||||
description?: string;
|
||||
lang?: string;
|
||||
setOGTypeArticle?: boolean;
|
||||
headings?: MarkdownHeading[];
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
banner,
|
||||
description,
|
||||
lang,
|
||||
setOGTypeArticle,
|
||||
headings = [],
|
||||
} = Astro.props;
|
||||
const hasBannerCredit =
|
||||
siteConfig.banner.enable && siteConfig.banner.credit.enable;
|
||||
const hasBannerLink = !!siteConfig.banner.credit.url;
|
||||
|
||||
const mainPanelTop = siteConfig.banner.enable
|
||||
? `calc(${BANNER_HEIGHT}vh - ${MAIN_PANEL_OVERLAPS_BANNER_HEIGHT}rem)`
|
||||
: "5.5rem";
|
||||
---
|
||||
|
||||
<Layout title={title} banner={banner} description={description} lang={lang} setOGTypeArticle={setOGTypeArticle}>
|
||||
|
||||
<!-- Navbar -->
|
||||
<slot slot="head" name="head"></slot>
|
||||
<div id="top-row" class="z-50 pointer-events-none relative transition-all duration-700 max-w-[var(--page-width)] px-4 md:px-4 mx-auto" class:list={[""]}>
|
||||
<div id="navbar-wrapper" class="pointer-events-auto sticky top-0 transition-all">
|
||||
<Navbar></Navbar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Banner -->
|
||||
{siteConfig.banner.enable && <div id="banner-wrapper" class=`absolute z-10 w-full transition duration-700 overflow-hidden` style=`top: -${BANNER_HEIGHT_EXTEND}vh`>
|
||||
<ImageWrapper id="banner" alt="Banner image of the blog" class:list={["object-cover h-full transition duration-700 opacity-0 scale-105"]}
|
||||
src={siteConfig.banner.src} position={siteConfig.banner.position}
|
||||
>
|
||||
</ImageWrapper>
|
||||
</div>}
|
||||
|
||||
<!-- Main content -->
|
||||
<div class="absolute w-full z-30 pointer-events-none" style=`top: ${mainPanelTop}`>
|
||||
<!-- The pointer-events-none here prevent blocking the click event of the TOC -->
|
||||
<div class="relative max-w-[var(--page-width)] mx-auto pointer-events-auto">
|
||||
<div id="main-grid" class="transition duration-700 w-full left-0 right-0 grid grid-cols-[17.5rem_auto] grid-rows-[auto_1fr_auto] lg:grid-rows-[auto]
|
||||
mx-auto gap-4 px-4 md:px-4"
|
||||
>
|
||||
<!-- Banner image credit -->
|
||||
{hasBannerCredit && <a href={siteConfig.banner.credit.url} id="banner-credit" target="_blank" rel="noopener" aria-label="Visit image source"
|
||||
class:list={["group onload-animation transition-all absolute flex justify-center items-center rounded-full " +
|
||||
"px-3 right-4 -top-[3.25rem] bg-black/60 hover:bg-black/70 h-9", {"hover:pr-9 active:bg-black/80": hasBannerLink}]}
|
||||
>
|
||||
<Icon class="text-white/75 text-[1.25rem] mr-1" name="material-symbols:copyright-outline-rounded" ></Icon>
|
||||
<div class="text-white/75 text-xs">{siteConfig.banner.credit.text}</div>
|
||||
<Icon class:list={["transition absolute text-[oklch(0.75_0.14_var(--hue))] right-4 text-[0.75rem] opacity-0",
|
||||
{"group-hover:opacity-100": hasBannerLink}]}
|
||||
name="fa6-solid:arrow-up-right-from-square">
|
||||
</Icon>
|
||||
</a>}
|
||||
|
||||
|
||||
<SideBar class="mb-4 row-start-2 row-end-3 col-span-2 lg:row-start-1 lg:row-end-2 lg:col-span-1 lg:max-w-[17.5rem] onload-animation" headings={headings}></SideBar>
|
||||
|
||||
<main id="swup-container" class="transition-swup-fade col-span-2 lg:col-span-1 overflow-hidden">
|
||||
<div id="content-wrapper" class="onload-animation">
|
||||
<!-- the overflow-hidden here prevent long text break the layout-->
|
||||
<!-- make id different from windows.swup global property -->
|
||||
<slot></slot>
|
||||
<div class="footer col-span-2 onload-animation hidden lg:block">
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="footer col-span-2 onload-animation block lg:hidden">
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BackToTop></BackToTop>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- The things that should be under the banner, only the TOC for now -->
|
||||
<div class="absolute w-full z-0 hidden 2xl:block">
|
||||
<div class="relative max-w-[var(--page-width)] mx-auto">
|
||||
<!-- TOC component -->
|
||||
{siteConfig.toc.enable && <div id="toc-wrapper" class:list={["hidden lg:block transition absolute top-0 -right-[var(--toc-width)] w-[var(--toc-width)] flex items-center",
|
||||
{"toc-hide": siteConfig.banner.enable}]}
|
||||
>
|
||||
<div id="toc-inner-wrapper" class="fixed top-14 w-[var(--toc-width)] h-[calc(100vh_-_20rem)] overflow-y-scroll overflow-x-hidden hide-scrollbar">
|
||||
<div id="toc" class="w-full h-full transition-swup-fade ">
|
||||
<div class="h-8 w-full"></div>
|
||||
<TOC headings={headings}></TOC>
|
||||
<div class="h-8 w-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
<!-- #toc needs to exist for Swup to work normally -->
|
||||
{!siteConfig.toc.enable && <div id="toc"></div>}
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
Reference in New Issue
Block a user