mirror of
https://github.com/afoim/fuwari.git
synced 2026-01-31 00:53:19 +08:00
在页面切换后,滚动控制相关的DOM元素引用未及时更新,导致滚动功能失效。添加refreshControlRefs函数在每次滚动检查前刷新引用,并确保页面加载完成后立即执行一次滚动检查。
766 lines
25 KiB
Plaintext
766 lines
25 KiB
Plaintext
---
|
||
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 NewPostNotification from "@components/widget/NewPostNotification.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] dark"
|
||
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} />
|
||
<link rel="preconnect" href="https://pic1.acofork.com" />
|
||
<link rel="preconnect" href="https://umami.acofork.com" />
|
||
<link rel="preconnect" href="https://support.nodeget.com" />
|
||
|
||
<script src="/js/umami-share.js" defer></script>
|
||
<script data-swup-ignore-script src="https://pic1.acofork.com/random.js" defer></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.2x.nz 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.2x.nz 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') || DARK_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`);
|
||
</script>
|
||
|
||
<style define:vars={{ configHue }}>
|
||
/* Fallback for no-js: Apply default theme hue */
|
||
:root {
|
||
--hue: var(--configHue);
|
||
}
|
||
</style>
|
||
<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);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
z-index: -1;
|
||
transition: opacity 0.3s ease-in-out;
|
||
/* Prevent background jump on mobile browsers with dynamic URL bars */
|
||
will-change: transform;
|
||
transform: translateZ(0);
|
||
}
|
||
|
||
#bg-box.loaded {
|
||
opacity: calc(var(--bg-opacity) * var(--bg-enable));
|
||
}
|
||
</style>
|
||
|
||
<slot name="head"></slot>
|
||
|
||
|
||
<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>
|
||
|
||
</head>
|
||
<body class=" min-h-screen transition " class:list={[{"lg:is-home": isHomePage, "enable-banner": enableBanner}]}
|
||
data-overlayscrollbars-initialize
|
||
>
|
||
<noscript>
|
||
<style>
|
||
#bg-box {
|
||
opacity: 1 !important;
|
||
}
|
||
</style>
|
||
</noscript>
|
||
<div id="bg-box"></div>
|
||
<ConfigCarrier></ConfigCarrier>
|
||
<NewPostNotification></NewPostNotification>
|
||
<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>
|
||
|
||
|
||
</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')
|
||
}
|
||
scrollFunction()
|
||
});
|
||
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 goToCommentsBtn = document.getElementById('go-to-comments-btn');
|
||
let toc = document.getElementById('toc-wrapper');
|
||
let navbar = document.getElementById('navbar-wrapper')
|
||
function refreshControlRefs() {
|
||
backToTopBtn = document.getElementById('back-to-top-btn');
|
||
goToCommentsBtn = document.getElementById('go-to-comments-btn');
|
||
toc = document.getElementById('toc-wrapper');
|
||
navbar = document.getElementById('navbar-wrapper')
|
||
}
|
||
function scrollFunction() {
|
||
refreshControlRefs()
|
||
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 (goToCommentsBtn) {
|
||
// Only show if comments exist and scrolled down
|
||
const commentsExist = !!document.getElementById('giscus-container');
|
||
if (commentsExist) {
|
||
goToCommentsBtn.classList.remove('hide')
|
||
} else {
|
||
goToCommentsBtn.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
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', scrollFunction)
|
||
} else {
|
||
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", {
|
||
wheel: 'zoom',
|
||
clickContent: 'close',
|
||
dblclickContent: 'zoom',
|
||
click: 'close',
|
||
dblclick: 'zoom',
|
||
Panels: {
|
||
display: ['counter', 'zoom']
|
||
},
|
||
Images: {
|
||
panning: true,
|
||
zoom: true,
|
||
protect: false
|
||
}
|
||
})
|
||
|
||
window.swup.hooks.on("page:view", () => {
|
||
Fancybox.bind(".custom-md img, #post-cover img", {
|
||
wheel: 'zoom',
|
||
clickContent: 'close',
|
||
dblclickContent: 'zoom',
|
||
click: 'close',
|
||
dblclick: 'zoom',
|
||
Panels: {
|
||
display: ['counter', 'zoom']
|
||
},
|
||
Images: {
|
||
panning: true,
|
||
zoom: true,
|
||
protect: false
|
||
}
|
||
})
|
||
})
|
||
|
||
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>
|
||
|
||
<script is:inline>
|
||
// Giscus Loader
|
||
function loadGiscus() {
|
||
console.log('[Giscus] Attempting to load Giscus...');
|
||
const container = document.getElementById('giscus-container');
|
||
if (!container) {
|
||
console.log('[Giscus] No container found, skipping.');
|
||
return;
|
||
}
|
||
|
||
// Prevent double loading if script is already there
|
||
if (container.querySelector('script[src*="giscus"]')) {
|
||
console.log('[Giscus] Script already exists, skipping.');
|
||
return;
|
||
}
|
||
|
||
console.log('[Giscus] Container found, creating script...');
|
||
const script = document.createElement('script');
|
||
script.src = "https://giscus.app/client.js";
|
||
script.async = true;
|
||
script.crossOrigin = "anonymous";
|
||
|
||
// Required attributes
|
||
const requiredAttrs = [
|
||
'repo', 'repoId', 'category', 'categoryId',
|
||
'mapping', 'strict', 'reactionsEnabled',
|
||
'emitMetadata', 'inputPosition', 'lang', 'loading'
|
||
];
|
||
|
||
requiredAttrs.forEach(attr => {
|
||
if (container.dataset[attr]) {
|
||
const kebab = attr.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
||
script.setAttribute(`data-${kebab}`, container.dataset[attr]);
|
||
}
|
||
});
|
||
|
||
// Theme handling
|
||
const theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
|
||
console.log(`[Giscus] Setting initial theme to: ${theme}`);
|
||
script.setAttribute('data-theme', theme);
|
||
|
||
container.appendChild(script);
|
||
console.log('[Giscus] Script appended to container.');
|
||
}
|
||
|
||
function initGiscus() {
|
||
console.log('[Giscus] initGiscus called.');
|
||
loadGiscus();
|
||
|
||
// Listen for Swup navigation
|
||
// Use a polling mechanism to wait for swup if it's not immediately available
|
||
const checkSwup = setInterval(() => {
|
||
if (window.swup) {
|
||
console.log('[Giscus] Swup detected via polling, registering hooks.');
|
||
clearInterval(checkSwup);
|
||
|
||
window.swup.hooks.on('page:view', () => {
|
||
console.log('[Giscus] Swup page:view triggered.');
|
||
loadGiscus();
|
||
});
|
||
|
||
// Also listen for content:replace to be safe, as page:view might sometimes fire before content is fully swapped in edge cases
|
||
window.swup.hooks.on('content:replace', () => {
|
||
console.log('[Giscus] Swup content:replace triggered.');
|
||
// Small delay to ensure DOM is ready
|
||
setTimeout(loadGiscus, 0);
|
||
});
|
||
|
||
} else {
|
||
console.log('[Giscus] Waiting for Swup...');
|
||
}
|
||
}, 200);
|
||
|
||
// Stop polling after 5 seconds to prevent infinite loop
|
||
setTimeout(() => clearInterval(checkSwup), 5000);
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', initGiscus);
|
||
} else {
|
||
initGiscus();
|
||
}
|
||
|
||
// Global theme observer
|
||
const observer = new MutationObserver((mutations) => {
|
||
mutations.forEach((mutation) => {
|
||
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
||
const iframe = document.querySelector('iframe.giscus-frame');
|
||
if (iframe) {
|
||
const theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
|
||
console.log(`[Giscus] Theme changed to: ${theme}, updating iframe.`);
|
||
iframe.contentWindow.postMessage({
|
||
giscus: { setConfig: { theme: theme } }
|
||
}, 'https://giscus.app');
|
||
}
|
||
}
|
||
});
|
||
});
|
||
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
||
</script>
|
||
|
||
<svg style="position: absolute; width: 0; height: 0; overflow: hidden;" aria-hidden="true">
|
||
<defs>
|
||
<filter id="liquid-glass">
|
||
<feTurbulence type="fractalNoise" baseFrequency="0.05" numOctaves="2" result="noise" />
|
||
<feDisplacementMap in="SourceGraphic" in2="noise" scale="20" xChannelSelector="R" yChannelSelector="G" />
|
||
</filter>
|
||
</defs>
|
||
</svg>
|