init
Some checks failed
Clean ESA Versions on Main / clean-esa-versions (push) Has been cancelled

This commit is contained in:
2026-01-02 00:03:49 +08:00
commit 7b7e32ddd4
348 changed files with 148701 additions and 0 deletions

857
src/layouts/Layout.astro Normal file
View 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>

View 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>