Commit 87c5e24
Changed files (4)
src
components
src/components/search/Pagefind.astro
@@ -1,89 +0,0 @@
----
-import { Icon } from 'astro-icon/components';
-import SearchBaseUI from './SearchBaseUI.astro';
-
-const bundlePath = `${import.meta.env.BASE_URL}pagefind/`;
----
-
-<SearchBaseUI
- data-pagefind-ui
- data-bundle-path={bundlePath}
- data-base-url={import.meta.env.BASE_URL}
-/>
-
-<template id="pagefind-result-template">
- <a class="group hover:bg-primary/30 w-full rounded-md p-2 duration-150" href="#">
- <div class="flex flex-row items-center gap-1 text-center">
- <span class="group-hover:text-primary text-lg duration-150">Fake Result</span>
- <Icon
- name="material-symbols:chevron-right"
- class="text-primary"
- height="1.125rem"
- width="1.125rem"
- />
- </div>
- <div id="pagefind-result-template-excerpt" class="text-sm opacity-60">
- This is a fake result.
- </div>
- </a>
-</template>
-
-<script is:inline>
- async function setup() {
- for (const el of document.querySelectorAll('[data-pagefind-ui]')) {
- const bundlePath = el.getAttribute('data-bundle-path');
- const baseUrl = el.getAttribute('data-base-url');
- const pagefind = await import(`${bundlePath}pagefind.js`);
- await pagefind.options({
- baseUrl: baseUrl,
- basePath: bundlePath,
- });
- pagefind.init();
-
- const searchInput = el.querySelector('input');
- const searchResultsWrapper = el.querySelector('.search-result');
- const searchResultTemplate = document.getElementById('pagefind-result-template');
- if (!searchInput || !searchResultsWrapper || !searchResultTemplate) {
- console.error('Pagefind: Required elements not found');
- return;
- }
-
- const search = async (text) => {
- const results = (await pagefind.debouncedSearch(text, 300)).results;
- searchResultsWrapper.innerHTML = '';
- if (results.length === 0) {
- searchResultsWrapper.textContent = 'No results found';
- return;
- }
- results.forEach(async (result) => {
- const data = await result.data();
- const resultNode = searchResultTemplate.content.cloneNode(true);
- const resultLink = resultNode.querySelector('a');
- const resultTitle = resultNode.querySelector('span');
- const resultExcerpt = resultNode.querySelector('#pagefind-result-template-excerpt');
-
- resultLink.setAttribute('href', data.url);
- resultTitle.textContent = data.meta.title;
- resultExcerpt.innerHTML = data.excerpt;
-
- resultExcerpt.removeAttribute('id');
- searchResultsWrapper.appendChild(resultNode);
- });
- };
-
- searchInput.addEventListener('input', async () => {
- await search(searchInput.value);
- });
- }
- }
-
- document.addEventListener('astro:page-load', setup);
- setup();
-</script>
-
-<style is:global>
- [data-pagefind-ui] mark {
- background-color: transparent;
- color: var(--color-secondary);
- }
-</style>
src/components/search/Pagefind.vue
@@ -0,0 +1,106 @@
+<script setup>
+import { onMounted, ref } from 'vue';
+
+const props = defineProps({
+ inputId: {
+ type: String,
+ required: true,
+ },
+});
+
+const bundlePath = `${import.meta.env.BASE_URL}pagefind/`;
+const baseUrl = import.meta.env.BASE_URL;
+
+const searchResults = ref([]);
+const noResults = ref(false);
+let pagefind = null;
+
+const search = async (text) => {
+ if (!pagefind) return;
+
+ const searchResponse = await pagefind.debouncedSearch(text, 300);
+ if (!searchResponse) return;
+ const results = searchResponse.results;
+ if (results.length === 0) {
+ searchResults.value = [];
+ noResults.value = true;
+ return;
+ }
+
+ noResults.value = false;
+ const processedResults = await Promise.all(
+ results.map(async (result) => {
+ const data = await result.data();
+ return {
+ url: data.url,
+ title: data.meta.title,
+ excerpt: data.excerpt,
+ };
+ })
+ );
+ searchResults.value = processedResults;
+};
+
+const setupSearch = () => {
+ const searchInput = document.getElementById(props.inputId);
+ if (!searchInput) {
+ console.error(`Pagefind: Input element with id "${props.inputId}" not found`);
+ return;
+ }
+
+ searchInput.addEventListener('input', (e) => search(e.target.value));
+};
+
+const setup = async () => {
+ pagefind = await import(/* @vite-ignore */ `${bundlePath}pagefind.js`);
+ await pagefind.options({
+ baseUrl: baseUrl,
+ basePath: bundlePath,
+ });
+ pagefind.init();
+ setupSearch();
+};
+
+onMounted(() => {
+ setup();
+ document.addEventListener('astro:page-load', setup);
+});
+
+defineExpose({
+ searchResults,
+ noResults,
+});
+</script>
+
+<template>
+ <div class="w-full" data-pagefind-ui>
+ <slot></slot>
+ <div
+ class="search-result mt-4 flex h-fit max-h-[calc(60vh-8rem)] flex-col items-center gap-2 overflow-y-auto text-center"
+ >
+ <template v-if="noResults"> No results found </template>
+ <template v-else>
+ <a
+ v-for="result in searchResults"
+ :key="result.url"
+ :href="result.url"
+ class="group hover:bg-primary/30 w-full rounded-md p-2 duration-150"
+ >
+ <div class="flex flex-row items-center gap-1 text-center">
+ <span class="group-hover:text-primary text-lg duration-150"
+ >{{ result.title }}<slot name="icon"></slot
+ ></span>
+ </div>
+ <div class="text-sm opacity-60" v-html="result.excerpt"></div>
+ </a>
+ </template>
+ </div>
+ </div>
+</template>
+
+<style>
+[data-pagefind-ui] mark {
+ background-color: transparent;
+ color: var(--color-secondary);
+}
+</style>
src/components/search/SearchBaseUI.astro
@@ -1,21 +0,0 @@
----
-import I18nKey from '@i18n/I18nKey';
-import { i18n } from '@i18n/translation';
-import { Icon } from 'astro-icon/components';
-import type { HTMLAttributes } from 'astro/types';
-
-type Props = HTMLAttributes<'div'>;
-
-const { class: className, ...rest } = Astro.props;
----
-
-<div class:list={['w-full', className]} {...rest}>
- <label class="input input-bordered flex w-full items-center gap-2">
- <input type="text" class="grow" placeholder={i18n(I18nKey.search)} />
- <Icon name="material-symbols:search-rounded" height="1.875rem" width="1.875rem" />
- </label>
- <div
- class="search-result mt-4 flex h-fit max-h-[calc(60vh-8rem)] flex-col items-center gap-2 overflow-y-auto text-center"
- >
- </div>
-</div>
src/components/Search.astro
@@ -1,6 +1,9 @@
---
import { searchConfig } from '@/config';
-import Pagefind from './search/Pagefind.astro';
+import I18nKey from '@i18n/I18nKey';
+import { i18n } from '@i18n/translation';
+import { Icon } from 'astro-icon/components';
+import Pagefind from './search/Pagefind.vue';
---
<dialog id="search_modal" class="modal">
@@ -13,22 +16,46 @@ import Pagefind from './search/Pagefind.astro';
(() => {
switch (searchConfig.provider) {
case 'pagefind':
- return <Pagefind />;
+ return (
+ <Pagefind client:visible inputId="search-input">
+ <label class="input input-bordered flex w-full items-center gap-2">
+ <input
+ id="search-input"
+ type="text"
+ class="grow"
+ placeholder={i18n(I18nKey.search)}
+ />
+ <Icon
+ name="material-symbols:search-rounded"
+ height="1.875rem"
+ width="1.875rem"
+ />
+ </label>
+ <Icon
+ slot="icon"
+ name="material-symbols:chevron-right"
+ class="text-primary inline-block"
+ height="1.125rem"
+ width="1.125rem"
+ />
+ </Pagefind>
+ );
}
})()
}
</div>
<div class="relative mt-auto w-full shrink-0 pt-4 text-center">
Powered by {
- searchConfig.provider === 'pagefind' ? (
- <a href="https://pagefind.app" target="_blank" class="text-primary">
- Pagefind
- </a>
- ) : (
- <a href="https://www.algolia.com" target="_blank" class="text-primary">
- Algolia
- </a>
- )
+ (() => {
+ switch (searchConfig.provider) {
+ case 'pagefind':
+ return (
+ <a href="https://pagefind.app" target="_blank" class="text-primary">
+ Pagefind
+ </a>
+ );
+ }
+ })()
}
</div>
</div>