first commit
This commit is contained in:
261
src/components/layout/Navbar.astro
Normal file
261
src/components/layout/Navbar.astro
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user