first commit

This commit is contained in:
2026-01-07 16:24:34 +00:00
commit 5c1399bb96
362 changed files with 59794 additions and 0 deletions

View File

@@ -0,0 +1,261 @@
---
import { Icon } from "astro-icon/components";
import DisplaySettings from "@/components/interactive/DisplaySettings.svelte";
import LayoutSwitchButton from "@/components/interactive/LayoutSwitchButton.svelte";
import LightDarkSwitch from "@/components/interactive/LightDarkSwitch.svelte";
import Search from "@/components/interactive/Search.svelte";
import WallpaperSwitch from "@/components/interactive/WallpaperSwitch.svelte";
import {
backgroundWallpaper,
navBarConfig,
navBarSearchConfig,
siteConfig,
} from "@/config";
import { LinkPresets } from "@/constants/link-presets";
import {
LinkPreset,
type NavBarLink,
NavBarSearchMethod,
} from "@/types/config";
import { isHomePage } from "@/utils/layout-utils";
import { url } from "@/utils/url-utils";
import DropdownMenu from "./DropdownMenu.astro";
import NavMenuPanel from "./NavMenuPanel.astro";
const className = Astro.props.class;
// 检查是否允许切换壁纸模式
const isWallpaperSwitchable = backgroundWallpaper.switchable ?? true;
// 检查是否允许切换布局(双侧栏模式下也允许切换,但切换到网格时会自动隐藏右侧边栏)
const allowLayoutSwitch = siteConfig.postListLayout.allowSwitch;
// 获取导航栏透明模式配置
const navbarTransparentMode =
backgroundWallpaper.banner?.navbar?.transparentMode || "semi";
// 获取导航栏标题,如果没有设置则使用 siteConfig.title
const navbarTitle = siteConfig.navbar.title || siteConfig.title;
// 获取导航栏宽度配置
const navbarWidthFull = siteConfig.navbar.widthFull ?? false;
// 检查是否为首页
const isHomePageCheck = isHomePage(Astro.url.pathname);
let links: NavBarLink[] = navBarConfig.links.map(
(item: NavBarLink | LinkPreset): NavBarLink => {
if (typeof item === "number") {
return LinkPresets[item];
}
return item;
},
);
---
<div id="navbar" class="z-50 onload-animation" data-transparent-mode={navbarTransparentMode} data-is-home={isHomePageCheck} data-full-width={navbarWidthFull}>
<div class="absolute h-8 left-0 right-0 -top-8 bg-[var(--card-bg)] transition"></div> <!-- used for onload animation -->
<div class:list={[
className,
"!overflow-visible h-[4.5rem] mx-auto flex items-center px-4",
navbarWidthFull ? "" : "justify-between max-w-[var(--page-width)]"
]}>
<a href={url('/')} class="btn-plain scale-animation rounded-lg h-[3.25rem] px-5 font-bold active:scale-95">
<div class:list={[
"flex flex-row items-center text-md",
siteConfig.navbar.followTheme ? "text-[var(--primary)]" : "dark:text-white text-black"
]}>
{siteConfig.navbar.logo?.type === "icon" ? (
<Icon name={siteConfig.navbar.logo.value || "material-symbols:home-pin-outline"} class="text-[1.75rem] mb-1 mr-2" />
) : siteConfig.navbar.logo?.type === "image" ? (
<img src={url(siteConfig.navbar.logo.value)} alt={siteConfig.navbar.logo.alt || siteConfig.title} class="h-[1.75rem] w-[1.75rem] mb-1 mr-2 object-contain" loading="lazy" />
) : (
<Icon name="material-symbols:home-pin-outline" class="text-[1.75rem] mb-1 mr-2" />
)}
{navbarTitle}
</div>
</a>
<div class="hidden md:flex items-center space-x-1">
{links.map((l) => {
return <DropdownMenu link={l} />;
})}
</div>
<div class:list={["flex", navbarWidthFull ? "ml-auto" : ""]}>
<!--<SearchPanel client:load>-->
<Search
client:load
searchMethod={navBarSearchConfig.method}
meiliSearchConfig={navBarSearchConfig.meiliSearchConfig}
/>
{!siteConfig.themeColor.fixed && (
<button aria-label="Display Settings" class="btn-plain scale-animation rounded-lg h-11 w-11 active:scale-90" id="display-settings-switch">
<Icon name="material-symbols:palette-outline" class="text-[1.25rem]"></Icon>
</button>
)}
{/* @ts-ignore */}
{isWallpaperSwitchable && <WallpaperSwitch client:load></WallpaperSwitch>}
{allowLayoutSwitch && <LayoutSwitchButton client:load currentLayout={siteConfig.postListLayout.defaultMode}></LayoutSwitchButton>}
{/* @ts-ignore */}
<LightDarkSwitch client:load></LightDarkSwitch>
<button aria-label="Menu" name="Nav Menu" class="btn-plain scale-animation rounded-lg w-11 h-11 active:scale-90 md:!hidden" id="nav-menu-switch">
<Icon name="material-symbols:menu-rounded" class="text-[1.25rem]"></Icon>
</button>
</div>
<NavMenuPanel links={links}></NavMenuPanel>
<DisplaySettings client:load></DisplaySettings>
</div>
</div>
<script>
function loadButtonScript() {
let settingBtn = document.getElementById("display-settings-switch");
if (settingBtn) {
settingBtn.onclick = function () {
let settingPanel = document.getElementById("display-setting");
if (settingPanel) {
settingPanel.classList.toggle("float-panel-closed");
}
};
}
let menuBtn = document.getElementById("nav-menu-switch");
if (menuBtn) {
menuBtn.onclick = function () {
let menuPanel = document.getElementById("nav-menu-panel");
if (menuPanel) {
menuPanel.classList.toggle("float-panel-closed");
}
};
}
let wallpaperSwitchBtn = document.getElementById("wallpaper-mode-switch");
if (wallpaperSwitchBtn) {
wallpaperSwitchBtn.onclick = function () {
let wallpaperPanel = document.getElementById("wallpaper-mode-panel");
if (wallpaperPanel) {
wallpaperPanel.classList.toggle("float-panel-closed");
}
};
}
let themeSwitchBtn = document.getElementById("scheme-switch");
if (themeSwitchBtn) {
themeSwitchBtn.onclick = function () {
let themePanel = document.getElementById("theme-mode-panel");
if (themePanel) {
themePanel.classList.toggle("float-panel-closed");
}
};
}
}
loadButtonScript();
// 为semifull模式添加滚动检测逻辑
function initSemifullScrollDetection() {
const navbar = document.getElementById('navbar');
if (!navbar) return;
const transparentMode = navbar.getAttribute('data-transparent-mode');
if (transparentMode !== 'semifull') return;
const isHomePage = navbar.getAttribute('data-is-home') === 'true';
// 如果不是首页,移除滚动事件监听器并设置为半透明状态
if (!isHomePage) {
// 移除之前的滚动事件监听器(如果存在)
if (window.semifullScrollHandler) {
window.removeEventListener('scroll', window.semifullScrollHandler as () => void);
window.semifullScrollHandler = undefined;
}
// 设置为半透明状态
navbar.classList.add('scrolled');
return;
}
// 移除现有的scrolled类重置状态
navbar.classList.remove('scrolled');
let ticking = false;
function updateNavbarState() {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const threshold = 50; // 滚动阈值,可以根据需要调整
// 使用批量DOM操作优化性能
if (scrollTop > threshold) {
navbar!.classList.add('scrolled');
} else {
navbar!.classList.remove('scrolled');
}
ticking = false;
}
function requestTick() {
if (!ticking) {
requestAnimationFrame(updateNavbarState);
ticking = true;
}
}
// 移除之前的滚动事件监听器(如果存在)
if (window.semifullScrollHandler) {
window.removeEventListener('scroll', window.semifullScrollHandler as () => void);
}
// 保存新的事件处理器引用
window.semifullScrollHandler = requestTick as unknown as (() => void);
// 监听滚动事件
window.addEventListener('scroll', requestTick, { passive: true });
// 初始化状态
updateNavbarState();
}
// 将函数暴露到全局对象,供页面切换时调用
window.initSemifullScrollDetection = initSemifullScrollDetection;
// 页面加载完成后初始化滚动检测
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initSemifullScrollDetection);
} else {
initSemifullScrollDetection();
}
</script>
{
navBarSearchConfig.method === NavBarSearchMethod.PageFind && import.meta.env.PROD && (
<script is:inline define:vars={{ scriptUrl: url('/pagefind/pagefind.js') }}>
{/* 你的 loadPagefind 函数的完整内容放在这里 */}
async function loadPagefind() {
try {
const response = await fetch(scriptUrl, { method: 'HEAD' });
if (!response.ok) {
throw new Error(`Pagefind script not found: ${response.status}`);
}
const pagefind = await import(scriptUrl);
await pagefind.options({
excerptLength: 20
});
window.pagefind = pagefind;
document.dispatchEvent(new CustomEvent('pagefindready'));
console.log('Pagefind loaded and initialized successfully, event dispatched.');
} catch (error) {
console.error('Failed to load Pagefind:', error);
window.pagefind = {
search: () => Promise.resolve({ results: [] }),
options: () => Promise.resolve(),
};
document.dispatchEvent(new CustomEvent('pagefindloaderror'));
console.log('Pagefind load error, event dispatched.');
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadPagefind);
} else {
loadPagefind();
}
</script>
)
}