mirror of
https://github.com/saicaca/fuwari.git
synced 2026-01-12 07:12:52 +01:00
* fix: Remove useless calls during initialization * fix: refactor search component and enhance search functionality * fix: clean up code formatting and improve readability in Search component and global types --------- Co-authored-by: jump-and-jump <984292420@qq.com> Co-authored-by: L4Ph <4ranci0ne@gmail.com>
153 lines
4.8 KiB
Svelte
153 lines
4.8 KiB
Svelte
<script lang="ts">
|
|
import type { SearchResult } from "@/global";
|
|
import I18nKey from "@i18n/i18nKey";
|
|
import { i18n } from "@i18n/translation";
|
|
import Icon from "@iconify/svelte";
|
|
import { url } from "@utils/url-utils.ts";
|
|
import { onMount } from "svelte";
|
|
|
|
let keywordDesktop = "";
|
|
let keywordMobile = "";
|
|
let result: SearchResult[] = [];
|
|
let isSearching = false;
|
|
let pagefindLoaded = false;
|
|
|
|
const fakeResult: SearchResult[] = [
|
|
{
|
|
url: url("/"),
|
|
meta: {
|
|
title: "This Is a Fake Search Result",
|
|
},
|
|
excerpt:
|
|
"Because the search cannot work in the <mark>dev</mark> environment.",
|
|
},
|
|
{
|
|
url: url("/"),
|
|
meta: {
|
|
title: "If You Want to Test the Search",
|
|
},
|
|
excerpt: "Try running <mark>npm build && npm preview</mark> instead.",
|
|
},
|
|
];
|
|
|
|
const togglePanel = () => {
|
|
const panel = document.getElementById("search-panel");
|
|
panel?.classList.toggle("float-panel-closed");
|
|
};
|
|
|
|
const setPanelVisibility = (show: boolean, isDesktop: boolean): void => {
|
|
const panel = document.getElementById("search-panel");
|
|
if (!panel || !isDesktop) return;
|
|
|
|
if (show) {
|
|
panel.classList.remove("float-panel-closed");
|
|
} else {
|
|
panel.classList.add("float-panel-closed");
|
|
}
|
|
};
|
|
|
|
const search = async (keyword: string, isDesktop: boolean): Promise<void> => {
|
|
if (!keyword) {
|
|
setPanelVisibility(false, isDesktop);
|
|
result = [];
|
|
return;
|
|
}
|
|
|
|
isSearching = true;
|
|
|
|
try {
|
|
let searchResults: SearchResult[] = [];
|
|
|
|
if (import.meta.env.PROD && pagefindLoaded) {
|
|
const response = await window.pagefind.search(keyword);
|
|
searchResults = await Promise.all(
|
|
response.results.map((item) => item.data()),
|
|
);
|
|
} else {
|
|
searchResults = fakeResult;
|
|
}
|
|
|
|
result = searchResults;
|
|
setPanelVisibility(result.length > 0, isDesktop);
|
|
} catch (error) {
|
|
console.error("Search error:", error);
|
|
result = [];
|
|
setPanelVisibility(false, isDesktop);
|
|
} finally {
|
|
isSearching = false;
|
|
}
|
|
};
|
|
|
|
onMount(async () => {
|
|
pagefindLoaded = typeof window !== "undefined" && "pagefind" in window;
|
|
|
|
if (import.meta.env.DEV) {
|
|
console.log(
|
|
"Pagefind is not available in development mode. Using mock data.",
|
|
);
|
|
}
|
|
});
|
|
|
|
$: search(keywordDesktop, true);
|
|
$: search(keywordMobile, false);
|
|
</script>
|
|
|
|
<!-- search bar for desktop view -->
|
|
<div id="search-bar" class="hidden lg:flex transition-all items-center h-11 mr-2 rounded-lg
|
|
bg-black/[0.04] hover:bg-black/[0.06] focus-within:bg-black/[0.06]
|
|
dark:bg-white/5 dark:hover:bg-white/10 dark:focus-within:bg-white/10
|
|
">
|
|
<Icon icon="material-symbols:search" class="absolute text-[1.25rem] pointer-events-none ml-3 transition my-auto text-black/30 dark:text-white/30"></Icon>
|
|
<input placeholder="{i18n(I18nKey.search)}" bind:value={keywordDesktop} on:focus={() => search(keywordDesktop, true)}
|
|
class="transition-all pl-10 text-sm bg-transparent outline-0
|
|
h-full w-40 active:w-60 focus:w-60 text-black/50 dark:text-white/50"
|
|
>
|
|
</div>
|
|
|
|
<!-- toggle btn for phone/tablet view -->
|
|
<button on:click={togglePanel} aria-label="Search Panel" id="search-switch"
|
|
class="btn-plain scale-animation lg:!hidden rounded-lg w-11 h-11 active:scale-90">
|
|
<Icon icon="material-symbols:search" class="text-[1.25rem]"></Icon>
|
|
</button>
|
|
|
|
<!-- search panel -->
|
|
<div id="search-panel" class="float-panel float-panel-closed search-panel absolute md:w-[30rem]
|
|
top-20 left-4 md:left-[unset] right-4 shadow-2xl rounded-2xl p-2">
|
|
|
|
<!-- search bar inside panel for phone/tablet -->
|
|
<div id="search-bar-inside" class="flex relative lg:hidden transition-all items-center h-11 rounded-xl
|
|
bg-black/[0.04] hover:bg-black/[0.06] focus-within:bg-black/[0.06]
|
|
dark:bg-white/5 dark:hover:bg-white/10 dark:focus-within:bg-white/10
|
|
">
|
|
<Icon icon="material-symbols:search" class="absolute text-[1.25rem] pointer-events-none ml-3 transition my-auto text-black/30 dark:text-white/30"></Icon>
|
|
<input placeholder="Search" bind:value={keywordMobile}
|
|
class="pl-10 absolute inset-0 text-sm bg-transparent outline-0
|
|
focus:w-60 text-black/50 dark:text-white/50"
|
|
>
|
|
</div>
|
|
|
|
<!-- search results -->
|
|
{#each result as item}
|
|
<a href={item.url}
|
|
class="transition first-of-type:mt-2 lg:first-of-type:mt-0 group block
|
|
rounded-xl text-lg px-3 py-2 hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)]">
|
|
<div class="transition text-90 inline-flex font-bold group-hover:text-[var(--primary)]">
|
|
{item.meta.title}<Icon icon="fa6-solid:chevron-right" class="transition text-[0.75rem] translate-x-1 my-auto text-[var(--primary)]"></Icon>
|
|
</div>
|
|
<div class="transition text-sm text-50">
|
|
{@html item.excerpt}
|
|
</div>
|
|
</a>
|
|
{/each}
|
|
</div>
|
|
|
|
<style>
|
|
input:focus {
|
|
outline: 0;
|
|
}
|
|
.search-panel {
|
|
max-height: calc(100vh - 100px);
|
|
overflow-y: auto;
|
|
}
|
|
</style>
|