2
0
forked from jmug/cactoide

Merge pull request #7 from polaroi8d/feat/fuse-search

feat: add fuse.js search module
This commit is contained in:
Levente Orban
2025-08-31 18:58:05 +02:00
committed by GitHub
3 changed files with 111 additions and 62 deletions

33
package-lock.json generated
View File

@@ -10,13 +10,13 @@
"dependencies": {
"@sveltejs/adapter-node": "^5.3.1",
"drizzle-orm": "^0.44.5",
"fuse.js": "^7.1.0",
"postgres": "^3.4.7"
},
"devDependencies": {
"@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0",
"@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/adapter-netlify": "^5.2.2",
"@sveltejs/kit": "^2.22.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@tailwindcss/forms": "^0.5.9",
@@ -1135,13 +1135,6 @@
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@iarna/toml": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==",
"dev": true,
"license": "ISC"
},
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
@@ -1630,21 +1623,6 @@
"@sveltejs/kit": "^2.0.0"
}
},
"node_modules/@sveltejs/adapter-netlify": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-netlify/-/adapter-netlify-5.2.2.tgz",
"integrity": "sha512-pFWB/lxG8NuJLEYOcPxD059v5QQDFX1vxpBbVobHjgJDCpSDLySGMi4ipDKNPfysqIA9TEG+rwdexz0iaIAocg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@iarna/toml": "^2.2.5",
"esbuild": "^0.25.4",
"set-cookie-parser": "^2.6.0"
},
"peerDependencies": {
"@sveltejs/kit": "^2.4.0"
}
},
"node_modules/@sveltejs/adapter-node": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.3.1.tgz",
@@ -3242,6 +3220,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/fuse.js": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz",
"integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=10"
}
},
"node_modules/get-tsconfig": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz",

View File

@@ -40,6 +40,7 @@
"dependencies": {
"@sveltejs/adapter-node": "^5.3.1",
"drizzle-orm": "^0.44.5",
"fuse.js": "^7.1.0",
"postgres": "^3.4.7"
}
}

View File

@@ -1,24 +1,51 @@
<script lang="ts">
import type { Event } from '$lib/types';
import type { Event, EventType } from '$lib/types';
import { goto } from '$app/navigation';
import type { PageData } from '../$types';
import { formatTime, formatDate } from '$lib/dateFormatter';
import Fuse from 'fuse.js';
let publicEvents: Event[] = [];
let error = '';
let searchQuery = '';
let selectedEventType: EventType | 'all' = 'all';
let fuse: Fuse<Event>;
export let data: PageData;
// Use the server-side data
$: publicEvents = data.events;
// Filter events based on search query
$: filteredEvents =
searchQuery.trim() === ''
? publicEvents
: publicEvents.filter((event) =>
event.name.toLowerCase().includes(searchQuery.toLowerCase())
);
// Initialize Fuse.js with search options
$: fuse = new Fuse(publicEvents, {
keys: [
{ name: 'name', weight: 0.7 },
{ name: 'location', weight: 0.3 }
],
threshold: 0.3, // Lower threshold = more strict matching
includeScore: true,
includeMatches: true
});
// Filter events based on search query and event type using Fuse.js
$: filteredEvents = (() => {
let events = publicEvents;
// First filter by event type
if (selectedEventType !== 'all') {
events = events.filter((event) => event.type === selectedEventType);
}
// Then apply search query
if (searchQuery.trim() !== '') {
events = fuse.search(searchQuery).map((result) => result.item);
// Re-apply type filter after search
if (selectedEventType !== 'all') {
events = events.filter((event) => event.type === selectedEventType);
}
}
return events;
})();
</script>
<svelte:head>
@@ -61,45 +88,79 @@
<p class="text-slate-500">Discover events created by the community</p>
</div>
<!-- Search Bar -->
<!-- Search and Filter Section -->
<div class="mb-8">
<div class="relative mx-auto max-w-md">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<svg
class="h-5 w-5 text-slate-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
></path>
</svg>
</div>
<input
type="text"
bind:value={searchQuery}
placeholder="Search events by title..."
class="w-full rounded-lg border border-slate-600 bg-slate-800 px-4 py-3 pl-10 text-white placeholder-slate-400 focus:border-violet-500 focus:ring-2 focus:ring-violet-500/20 focus:outline-none"
/>
{#if searchQuery}
<button
on:click={() => (searchQuery = '')}
class="absolute inset-y-0 right-0 flex items-center pr-3 text-slate-400 hover:text-slate-300"
>
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-center">
<!-- Search Bar -->
<div class="relative mx-auto max-w-md sm:mx-0">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<svg
class="h-5 w-5 text-slate-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
></path>
</svg>
</div>
<input
type="text"
bind:value={searchQuery}
placeholder="Search events by name, location..."
class="w-full rounded-lg border border-slate-600 bg-slate-800 px-4 py-3 pl-10 text-white placeholder-slate-400 focus:border-violet-500 focus:ring-2 focus:ring-violet-500/20 focus:outline-none"
/>
{#if searchQuery}
<button
on:click={() => (searchQuery = '')}
class="absolute inset-y-0 right-0 flex items-center pr-3 text-slate-400 hover:text-slate-300"
>
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
></path>
</svg>
</button>
{/if}
</div>
<!-- Event Type Filter -->
<div class="flex items-center justify-center gap-2">
<button
on:click={() => (selectedEventType = 'all')}
class="rounded-sm border px-3 py-2 text-sm font-medium transition-colors {selectedEventType ===
'all'
? 'border-violet-500 bg-violet-500/20 text-violet-400'
: 'border-slate-600 text-slate-400 hover:border-slate-500 hover:text-slate-300'}"
>
All
</button>
{/if}
<button
on:click={() => (selectedEventType = 'limited')}
class="rounded-sm border px-3 py-2 text-sm font-medium transition-colors {selectedEventType ===
'limited'
? 'border-amber-600 bg-amber-600/20 text-amber-600'
: 'border-slate-600 text-slate-400 hover:border-slate-500 hover:text-slate-300'}"
>
Limited
</button>
<button
on:click={() => (selectedEventType = 'unlimited')}
class="rounded-sm border px-3 py-2 text-sm font-medium transition-colors {selectedEventType ===
'unlimited'
? 'border-teal-500 bg-teal-500/20 text-teal-500'
: 'border-slate-600 text-slate-400 hover:border-slate-500 hover:text-slate-300'}"
>
Unlimited
</button>
</div>
</div>
</div>