feat: add search
This commit is contained in:
parent
b86201b927
commit
57e247b573
24
js/search/.gitignore
vendored
Normal file
24
js/search/.gitignore
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
21
js/search/package.json
Normal file
21
js/search/package.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "search",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"svelte": "5.0.0-next.169",
|
||||
"svelte-check": "^3.8.1",
|
||||
"tslib": "^2.6.3",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.3.1"
|
||||
}
|
||||
}
|
1108
js/search/pnpm-lock.yaml
Normal file
1108
js/search/pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
97
js/search/src/App.svelte
Executable file
97
js/search/src/App.svelte
Executable file
|
@ -0,0 +1,97 @@
|
|||
<script lang="ts">
|
||||
import type { ChangeEventHandler, UIEventHandler } from 'svelte/elements';
|
||||
|
||||
let client = $state<Pagefind>();
|
||||
let query = $state<string>('');
|
||||
let limit = $state<number>(10);
|
||||
let result = $derived(client?.search(query));
|
||||
|
||||
const require = (path: string) => import(/* @vite-ignore */path);
|
||||
|
||||
|
||||
function sync(): void {
|
||||
query = new URLSearchParams(window.location.search).get('q') || '';
|
||||
}
|
||||
|
||||
function onInput(): ChangeEventHandler<HTMLInputElement> {
|
||||
let debounce: number | undefined;
|
||||
|
||||
return event => {
|
||||
clearTimeout(debounce);
|
||||
const value = event.currentTarget.value;
|
||||
const url = new URL(window.location.href);
|
||||
(value)
|
||||
? url.searchParams.set('q', value)
|
||||
: url.searchParams.delete('q');
|
||||
debounce = setTimeout(() => window.history.pushState({}, '', url), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function onScroll(): UIEventHandler<Window> {
|
||||
let throttle = Date.now();
|
||||
|
||||
return event => {
|
||||
const now = Date.now();
|
||||
if (throttle + 200 > now) return;
|
||||
|
||||
const { scrollHeight } = document.documentElement;
|
||||
const { innerHeight, scrollY } = event.currentTarget;
|
||||
|
||||
const distance = scrollHeight - (innerHeight + scrollY);
|
||||
if (distance < 100) {
|
||||
limit += 5;
|
||||
throttle = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
sync();
|
||||
require('/pagefind/pagefind.js').then(pf => client = pf);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<svelte:window
|
||||
on:popstate={sync}
|
||||
on:scroll={onScroll()}/>
|
||||
|
||||
{#snippet tile(data: PagefindDocument)}
|
||||
<a class="c-search__result" href={data.url}>
|
||||
<header class="c-search__header">
|
||||
<h2 class="c-search__title">
|
||||
{data.meta.title}
|
||||
</h2>
|
||||
</header>
|
||||
<div class="c-search__excerpt">
|
||||
{@html data.excerpt}
|
||||
</div>
|
||||
</a>
|
||||
{/snippet}
|
||||
|
||||
<article class="c-search">
|
||||
<h1>Search</h1>
|
||||
<input class="c-search__input" placeholder="Start typing here!"
|
||||
bind:value={query}
|
||||
on:input={onInput()}
|
||||
on:input={() => limit = 10}/>
|
||||
|
||||
{#if query && result}
|
||||
{#await result}
|
||||
Loading...
|
||||
{:then {results}}
|
||||
<section class="c-search__results">
|
||||
<div>Showing results for "{query}" ({results.length})</div>
|
||||
{#each results.slice(0, limit) as page (page.id)}
|
||||
{#await page.data()}
|
||||
Loading...
|
||||
{:then page}
|
||||
{@render tile(page)}
|
||||
{/await}
|
||||
{/each}
|
||||
</section>
|
||||
{/await}
|
||||
{:else}
|
||||
<div>No results to show yet...</div>
|
||||
{/if}
|
||||
</article>
|
8
js/search/src/main.ts
Normal file
8
js/search/src/main.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { mount } from 'svelte';
|
||||
import App from './App.svelte'
|
||||
|
||||
|
||||
const app = mount(App, { target: document.getElementById('app')! });
|
||||
|
||||
|
||||
export default app
|
26
js/search/src/pagefind.d.ts
vendored
Executable file
26
js/search/src/pagefind.d.ts
vendored
Executable file
|
@ -0,0 +1,26 @@
|
|||
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;
|
||||
}
|
2
js/search/src/vite-env.d.ts
vendored
Normal file
2
js/search/src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
7
js/search/svelte.config.js
Normal file
7
js/search/svelte.config.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
export default {
|
||||
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
}
|
21
js/search/tsconfig.json
Normal file
21
js/search/tsconfig.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
* Note that setting allowJs false does not prevent the use
|
||||
* of JS in `.svelte` files.
|
||||
*/
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force"
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
12
js/search/tsconfig.node.json
Normal file
12
js/search/tsconfig.node.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
13
js/search/vite.config.ts
Normal file
13
js/search/vite.config.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [svelte()],
|
||||
build: {
|
||||
lib: {
|
||||
entry: './src/main.ts',
|
||||
formats: ['es'],
|
||||
}
|
||||
}
|
||||
})
|
159
pnpm-lock.yaml
Normal file
159
pnpm-lock.yaml
Normal file
|
@ -0,0 +1,159 @@
|
|||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
svelte:
|
||||
specifier: 5.0.0-next.130
|
||||
version: 5.0.0-next.130
|
||||
|
||||
packages:
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.5':
|
||||
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2':
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/set-array@1.2.1':
|
||||
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.4.15':
|
||||
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
'@types/estree@1.0.5':
|
||||
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
||||
|
||||
acorn-typescript@1.4.13:
|
||||
resolution: {integrity: sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==}
|
||||
peerDependencies:
|
||||
acorn: '>=8.9.0'
|
||||
|
||||
acorn@8.12.0:
|
||||
resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
aria-query@5.3.0:
|
||||
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
|
||||
|
||||
axobject-query@4.0.0:
|
||||
resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==}
|
||||
|
||||
dequal@2.0.3:
|
||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
esm-env@1.0.0:
|
||||
resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==}
|
||||
|
||||
esrap@1.2.2:
|
||||
resolution: {integrity: sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==}
|
||||
|
||||
is-reference@3.0.2:
|
||||
resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==}
|
||||
|
||||
locate-character@3.0.0:
|
||||
resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
|
||||
|
||||
magic-string@0.30.10:
|
||||
resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
|
||||
|
||||
svelte@5.0.0-next.130:
|
||||
resolution: {integrity: sha512-gowsYNL5N9t9loV17TLlNKY1fbmliyz5SlfP1Fxyf4kZDIX+hx4TV9EtZMOgjgXs0NI/bVwR942EPl3g+gaqGQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
zimmerframe@1.1.2:
|
||||
resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.5
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.5':
|
||||
dependencies:
|
||||
'@jridgewell/set-array': 1.2.1
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/set-array@1.2.1': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.4.15': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
|
||||
'@types/estree@1.0.5': {}
|
||||
|
||||
acorn-typescript@1.4.13(acorn@8.12.0):
|
||||
dependencies:
|
||||
acorn: 8.12.0
|
||||
|
||||
acorn@8.12.0: {}
|
||||
|
||||
aria-query@5.3.0:
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
|
||||
axobject-query@4.0.0:
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
|
||||
dequal@2.0.3: {}
|
||||
|
||||
esm-env@1.0.0: {}
|
||||
|
||||
esrap@1.2.2:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
'@types/estree': 1.0.5
|
||||
|
||||
is-reference@3.0.2:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
|
||||
locate-character@3.0.0: {}
|
||||
|
||||
magic-string@0.30.10:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
|
||||
svelte@5.0.0-next.130:
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
'@types/estree': 1.0.5
|
||||
acorn: 8.12.0
|
||||
acorn-typescript: 1.4.13(acorn@8.12.0)
|
||||
aria-query: 5.3.0
|
||||
axobject-query: 4.0.0
|
||||
esm-env: 1.0.0
|
||||
esrap: 1.2.2
|
||||
is-reference: 3.0.2
|
||||
locate-character: 3.0.0
|
||||
magic-string: 0.30.10
|
||||
zimmerframe: 1.1.2
|
||||
|
||||
zimmerframe@1.1.2: {}
|
|
@ -5,6 +5,7 @@ pkgs.mkShell {
|
|||
cargo
|
||||
clippy
|
||||
esbuild
|
||||
nodejs
|
||||
nodePackages.pnpm
|
||||
pagefind
|
||||
python3
|
||||
|
|
|
@ -15,9 +15,8 @@ socket.addEventListener("message", (event) => {
|
|||
const JS_IMPORTS: &str = r#"
|
||||
{
|
||||
"imports": {
|
||||
"splash": "/js/splash.js",
|
||||
"reveal": "/js/reveal.js",
|
||||
"photos": "/js/photos.js"
|
||||
"reveal": "/js/vanilla/reveal.js",
|
||||
"photos": "/js/vanilla/photos.js"
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
|
|
@ -17,8 +17,7 @@ pub fn map() -> impl Renderable {
|
|||
|
||||
pub fn search() -> impl Renderable {
|
||||
page("Search", maud!(
|
||||
main {
|
||||
|
||||
}
|
||||
main #app {}
|
||||
script type="module" src="/js/search/dist/search.js" {}
|
||||
), None)
|
||||
}
|
||||
|
|
|
@ -366,12 +366,13 @@ fn build() {
|
|||
println!("{}", String::from_utf8(res.stdout).unwrap());
|
||||
|
||||
let res = Command::new("esbuild")
|
||||
.arg("js/reveal.js")
|
||||
.arg("js/photos.ts")
|
||||
.arg("js/vanilla/reveal.js")
|
||||
.arg("js/vanilla/photos.ts")
|
||||
.arg("js/search/dist/search.js")
|
||||
.arg("--format=esm")
|
||||
.arg("--bundle")
|
||||
.arg("--splitting")
|
||||
.arg("--minify")
|
||||
//.arg("--minify")
|
||||
.arg("--outdir=dist/js/")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
|
Loading…
Reference in a new issue