Use pagefind for search
This commit is contained in:
parent
4ac361e66c
commit
f166aab8f7
|
@ -5,6 +5,7 @@ import remarkEmoji from 'remark-emoji';
|
|||
import remarkRuby from './src/utils/ruby';
|
||||
import mdx from '@astrojs/mdx';
|
||||
import solid from '@astrojs/solid-js';
|
||||
import pagefind from 'astro-pagefind';
|
||||
|
||||
|
||||
// https://astro.build/config
|
||||
|
@ -27,5 +28,6 @@ export default defineConfig({
|
|||
integrations: [
|
||||
mdx(),
|
||||
solid(),
|
||||
pagefind(),
|
||||
]
|
||||
});
|
||||
|
|
10
package.json
10
package.json
|
@ -10,13 +10,13 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^0.19.0",
|
||||
"@astrojs/solid-js": "^2.1.0",
|
||||
"astro": "^2.3.2",
|
||||
"@astrojs/mdx": "^0.19.1",
|
||||
"@astrojs/solid-js": "^2.1.1",
|
||||
"astro": "^2.4.0",
|
||||
"astro-pagefind": "^1.1.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"leaflet": "^1.9.3",
|
||||
"leaflet.markercluster": "^1.5.3",
|
||||
"lunr": "^2.3.9",
|
||||
"rehype-katex": "^6.0.3",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"rehype-stringify": "^9.0.3",
|
||||
|
@ -26,7 +26,7 @@
|
|||
"remark-parse": "^10.0.1",
|
||||
"remark-rehype": "^10.1.0",
|
||||
"reveal.js": "^4.5.0",
|
||||
"solid-js": "^1.7.3",
|
||||
"solid-js": "^1.7.4",
|
||||
"unified": "^10.1.2",
|
||||
"unist-util-visit": "^4.1.2"
|
||||
},
|
||||
|
|
522
pnpm-lock.yaml
522
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -1,23 +1,69 @@
|
|||
import { createSignal, onMount } from "solid-js";
|
||||
import dayjs from "dayjs";
|
||||
import lunr from "lunr";
|
||||
import { createResource, createSignal, For, onMount } from "solid-js";
|
||||
|
||||
|
||||
interface Pagefind {
|
||||
search: (query: string) => Promise<PagefindResponse>;
|
||||
}
|
||||
|
||||
interface PagefindResult {
|
||||
id: string;
|
||||
data: () => Promise<PagefindDocument>;
|
||||
}
|
||||
|
||||
interface PagefindResponse {
|
||||
results: PagefindResult[];
|
||||
}
|
||||
|
||||
interface PagefindDocument {
|
||||
url: string;
|
||||
excerpt: string;
|
||||
filters: {
|
||||
author: string;
|
||||
};
|
||||
meta: {
|
||||
title: string;
|
||||
image: string;
|
||||
};
|
||||
content: string;
|
||||
word_count: number;
|
||||
}
|
||||
|
||||
|
||||
async function loadPagefind(): Promise<Pagefind> {
|
||||
const pf = "/_pagefind/pagefind.js";
|
||||
return await import(/* @vite-ignore */ pf);
|
||||
}
|
||||
|
||||
function Result(props: { page: PagefindResult }) {
|
||||
const [data, setData] = createSignal<PagefindDocument>();
|
||||
|
||||
onMount(async () => await props.page.data().then(setData))
|
||||
|
||||
return (
|
||||
<>
|
||||
{data() && (
|
||||
<a class="c-search__result" href={data()!.url}>
|
||||
<header class="c-search__header">
|
||||
<h2 class="c-search__title">{data()!.meta.title}</h2>
|
||||
{/* <time class="c-search__date" datetime={dayjs().toISOString()}>{dayjs().format("MMM DD, YYYY")}</time> */}
|
||||
</header>
|
||||
<div class="c-search__excerpt" innerHTML={data()!.excerpt}></div>
|
||||
</a>
|
||||
) || (
|
||||
<div>Loading...</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
const KEY = 'q' as const;
|
||||
export default function Search() {
|
||||
const [query, setQuery] = createSignal('');
|
||||
|
||||
// Data loaded from server
|
||||
let index: lunr.Index;
|
||||
let metadata: any;
|
||||
async function load() {
|
||||
const data = await fetch('/search.json').then(r => r.json());
|
||||
index = lunr.Index.load(data.index);
|
||||
metadata = data.metadata;
|
||||
}
|
||||
|
||||
// Dynamically calculated from query
|
||||
const results = () => index?.search(query()) ?? [];
|
||||
// Search
|
||||
let pagefind: Pagefind;
|
||||
const [pages] = createResource(query, async (query: string) => await pagefind.search(query));
|
||||
|
||||
// Update URL query and history
|
||||
let debounce: number;
|
||||
|
@ -42,7 +88,8 @@ export default function Search() {
|
|||
}
|
||||
|
||||
onMount(async () => {
|
||||
await load().then(sync);
|
||||
pagefind = await loadPagefind();
|
||||
sync();
|
||||
window.addEventListener('popstate', sync);
|
||||
});
|
||||
|
||||
|
@ -55,17 +102,10 @@ export default function Search() {
|
|||
|
||||
{query() && (
|
||||
<section class="c-search__results">
|
||||
<div>Showing results for "{query()}" ({results().length})</div>
|
||||
{results().map(result => {
|
||||
const meta = metadata[result.ref];
|
||||
const date = dayjs(meta.date);
|
||||
return (
|
||||
<a class="c-search__result" href={result.ref}>
|
||||
<span class="c-search__name">{meta.title}</span>
|
||||
<time class="c-search__date" datetime={date.toISOString()}>{date.format("MMM DD, YYYY")}</time>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
<div>Showing results for "{query()}" ({pages()?.results.length || 0})</div>
|
||||
{pages() && <For each={pages()!.results}>{(page, i) => (
|
||||
<Result page={page} />
|
||||
)}</For>}
|
||||
</section>
|
||||
) || (
|
||||
<div>No results to show yet...</div>
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
import lunr from 'lunr';
|
||||
import { getCollection } from 'astro:content';
|
||||
import type { APIContext } from "astro";
|
||||
|
||||
|
||||
const posts = await Promise.all([
|
||||
getCollection('posts').then(a => a.map(p => ({...p, slug: `/posts/${p.slug}/`}))),
|
||||
getCollection('aoc').then(a => a.map(p => ({...p, slug: `/aoc/${p.slug}/`}))),
|
||||
getCollection('slides').then(a => a.map(p => ({...p, slug: `/slides/${p.slug}/`}))),
|
||||
])
|
||||
.then(array => array.flat());
|
||||
|
||||
const metadata = posts.reduce(
|
||||
(acc, next) => (
|
||||
acc[next.slug] = { title: next.data.title, date: next.data.date },
|
||||
acc
|
||||
),
|
||||
{} as {[key: string]: {title: string, date: Date}}
|
||||
)
|
||||
|
||||
const index = lunr(function() {
|
||||
this.ref('slug');
|
||||
this.field('body');
|
||||
|
||||
for (const post of posts)
|
||||
this.add(post);
|
||||
})
|
||||
|
||||
export async function get(_: APIContext) {
|
||||
return {body: JSON.stringify({ index, metadata })};
|
||||
}
|
|
@ -21,13 +21,12 @@
|
|||
}
|
||||
|
||||
&__result {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0.5em;
|
||||
background-color: white;
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;
|
||||
transition: box-shadow linear 100ms;
|
||||
text-decoration: none;
|
||||
color: var(--c-text);
|
||||
|
||||
&:focus-within,
|
||||
&:hover {
|
||||
|
@ -35,8 +34,25 @@
|
|||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 1.3em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__date {
|
||||
margin-left: auto;
|
||||
color: rgb(65, 65, 65);
|
||||
}
|
||||
|
||||
&__excerpt {
|
||||
mark {
|
||||
background-color: unset;
|
||||
font-weight: 800;
|
||||
text-decoration: underline;
|
||||
color: var(--c-primary-d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue