feat: add search

This commit is contained in:
Maciej Jur 2024-07-02 00:34:13 +02:00
parent b86201b927
commit 57e247b573
Signed by: kamov
GPG key ID: 191CBFF5F72ECAFD
20 changed files with 1507 additions and 9 deletions

24
js/search/.gitignore vendored Normal file
View 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
View 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

File diff suppressed because it is too large Load diff

97
js/search/src/App.svelte Executable file
View 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
View 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
View 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
View file

@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />

View 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
View 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" }]
}

View 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
View 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
View 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: {}

View file

@ -5,6 +5,7 @@ pkgs.mkShell {
cargo
clippy
esbuild
nodejs
nodePackages.pnpm
pagefind
python3

View file

@ -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"
}
}
"#;

View file

@ -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)
}

View file

@ -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();