| ---
|
| import { Picture } from "astro:assets";
|
| import * as path from "node:path";
|
| import type { ImageMetadata } from "astro";
|
| import { Icon } from "astro-icon/components";
|
| import DisplaySettings from "@/components/controls/DisplaySettingsIntegrated.svelte";
|
| import LightDarkSwitch from "@/components/controls/LightDarkSwitch.svelte";
|
| import Search from "@/components/controls/Search.svelte";
|
| import MusicPlayer from "@/components/features/MusicPlayer.astro";
|
| import { backgroundWallpaper, navBarConfig, siteConfig } from "@/config";
|
| import { musicPlayerConfig } from "@/config/musicConfig";
|
| import { LinkPresets } from "@/constants/link-presets";
|
| import I18nKey from "@/i18n/i18nKey";
|
| import { i18n } from "@/i18n/translation";
|
| import { LinkPreset, type NavBarLink } from "@/types/config";
|
| import { getFallbackFormat, getImageFormats } from "@/utils/image-utils";
|
| 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 navbarTransparentMode =
|
| backgroundWallpaper.mode === "banner"
|
| ? backgroundWallpaper.banner?.navbar?.transparentMode || "semi"
|
| : "semi";
|
|
|
|
|
| const navbarEnableBlur =
|
| backgroundWallpaper.mode === "banner"
|
| ? (backgroundWallpaper.banner?.navbar?.enableBlur ?? true)
|
| : false;
|
|
|
|
|
| const navbarBlur =
|
| backgroundWallpaper.mode === "banner"
|
| ? (backgroundWallpaper.banner?.navbar?.blur ?? 20)
|
| : 0;
|
|
|
|
|
| const navbarTitle = siteConfig.navbar.title || siteConfig.title;
|
|
|
|
|
| const navbarWidthFull = siteConfig.navbar.widthFull ?? false;
|
|
|
|
|
| const isHomePageCheck = isHomePage(Astro.url.pathname);
|
|
|
|
|
| const showThemeColor = !siteConfig.themeColor.fixed;
|
| const isWallpaperSwitchable = backgroundWallpaper.switchable ?? true;
|
| const allowLayoutSwitch = siteConfig.postListLayout.allowSwitch;
|
| const hasDisplaySettings =
|
| showThemeColor || isWallpaperSwitchable || allowLayoutSwitch;
|
|
|
| let links: NavBarLink[] = navBarConfig.links.map(
|
| (item: NavBarLink | LinkPreset): NavBarLink => {
|
| if (typeof item === "number") {
|
| return LinkPresets[item];
|
| }
|
| return item;
|
| },
|
| );
|
|
|
|
|
| const logoValue = siteConfig.navbar.logo?.value || "";
|
| const isLocalSrcLogo =
|
| siteConfig.navbar.logo?.type === "image" &&
|
| logoValue &&
|
| !logoValue.startsWith("/") &&
|
| !logoValue.startsWith("http");
|
| let logoImg: ImageMetadata | null = null;
|
|
|
| if (isLocalSrcLogo) {
|
| const files = import.meta.glob<ImageMetadata>(
|
| "../../**/*.{png,jpg,jpeg,webp,avif,gif,svg}",
|
| { import: "default" },
|
| );
|
| const normalizedPath = path
|
| .normalize(path.join("../../", logoValue))
|
| .replace(/\\/g, "/");
|
| const file = files[normalizedPath];
|
| if (file) {
|
| logoImg = await file();
|
| }
|
| }
|
| ---
|
| <div id="navbar" class="z-50" style={`--navbar-glass-blur: ${navbarBlur}px;`} data-transparent-mode={navbarTransparentMode} data-enable-blur={String(navbarEnableBlur)} data-is-home={isHomePageCheck} data-full-width={navbarWidthFull}>
|
| <div class:list={[
|
| className,
|
| "overflow-visible! h-18 mx-auto flex items-center px-4",
|
| navbarWidthFull ? "" : "justify-between max-w-(--page-width)"
|
| ]}>
|
| <a href={url('/')} class="btn-plain scale-animation rounded-lg h-13 px-5 font-bold active:scale-95">
|
| <div class:list={[
|
| "flex flex-row items-center text-md",
|
| siteConfig.navbar.followTheme ? "text-(--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" && logoImg ? (
|
| <Picture
|
| src={logoImg}
|
| alt={siteConfig.navbar.logo.alt || siteConfig.title}
|
| class="h-7 w-7 mb-1 mr-2 object-contain"
|
| formats={getImageFormats()}
|
| fallbackFormat={getFallbackFormat()}
|
| widths={[28, 56]}
|
| loading="eager"
|
| fetchpriority="high"
|
| />
|
| ) : siteConfig.navbar.logo?.type === "image" ? (
|
| <img src={url(siteConfig.navbar.logo.value)} alt={siteConfig.navbar.logo.alt || siteConfig.title} class="h-7 w-7 mb-1 mr-2 object-contain" fetchpriority="high" />
|
| ) : siteConfig.navbar.logo?.type === "url" ? (
|
| <img src={siteConfig.navbar.logo.value} alt={siteConfig.navbar.logo.alt || siteConfig.title} class="h-7 w-7 mb-1 mr-2 object-contain" fetchpriority="high" />
|
| ) : (
|
| <Icon name="material-symbols:home-pin-outline" class="text-[1.75rem] mb-1 mr-2" />
|
| )}
|
| {navbarTitle}
|
| </div>
|
| </a>
|
| <div class="hidden lg:flex items-center space-x-1">
|
| {links.map((l) => {
|
| return <DropdownMenu link={l} />;
|
| })}
|
| </div>
|
| <div class:list={["flex", navbarWidthFull ? "ml-auto" : ""]}>
|
|
|
| <Search
|
| client:load
|
| />
|
| {musicPlayerConfig.showInNavbar && (
|
| <button aria-label={i18n(I18nKey.music)} class="btn-plain scale-animation rounded-lg h-11 w-11 active:scale-90" id="music-player-switch">
|
| <Icon name="material-symbols:music-note-rounded" class="text-[1.25rem]"></Icon>
|
| </button>
|
| )}
|
| {hasDisplaySettings && (
|
| <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 */}
|
| <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 lg:hidden!" id="nav-menu-switch">
|
| <Icon name="material-symbols:menu-rounded" class="text-[1.25rem]"></Icon>
|
| </button>
|
| </div>
|
| {musicPlayerConfig.showInNavbar && (
|
| <div id="music-nav-panel" class="float-panel float-panel-closed absolute transition-all right-16 p-2 pt-4.5 w-80 z-50">
|
| <MusicPlayer />
|
| </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 musicBtn = document.getElementById("music-player-switch");
|
| if (musicBtn) {
|
| musicBtn.onclick = function () {
|
| let musicPanel = document.getElementById("music-nav-panel");
|
| if (musicPanel) {
|
| musicPanel.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");
|
| }
|
| };
|
| }
|
|
|
| document.addEventListener("click", function(event) {
|
| const panels = ["display-setting", "nav-menu-panel", "music-nav-panel", "theme-mode-panel"];
|
| const buttons = ["display-settings-switch", "nav-menu-switch", "music-player-switch", "scheme-switch"];
|
|
|
| panels.forEach((id, index) => {
|
| const panel = document.getElementById(id);
|
| const button = document.getElementById(buttons[index]);
|
| if (panel && button && !panel.classList.contains("float-panel-closed")) {
|
| if (event.target instanceof Node && !panel.contains(event.target) && !button.contains(event.target)) {
|
| panel.classList.add("float-panel-closed");
|
| }
|
| }
|
| });
|
| });
|
| }
|
|
|
| loadButtonScript();
|
|
|
|
|
| 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;
|
| }
|
|
|
|
|
| navbar.classList.remove('scrolled');
|
|
|
| let ticking = false;
|
|
|
| function updateNavbarState() {
|
| const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
| const threshold = 50;
|
|
|
| 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>
|
|
|
| {
|
| import.meta.env.PROD && (
|
| <script is:inline define:vars={{ scriptUrl: url('/pagefind/pagefind.js') }}>
|
| {}
|
| 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>
|
| )
|
| }
|
| |