229d2fca33
Replace synchronous DOM manipulation with Web Worker + Svelte reactive state for recipe search. This moves text normalization and filtering off the main thread, ensuring zero input lag while typing. Search now runs in parallel with UI rendering, improving performance significantly for 240+ recipes. - Add search.worker.js for background search processing - Update Search.svelte to use Web Worker with $state runes - Update +page.svelte with reactive filtering based on worker results - Add language-aware recipe data synchronization for proper English/German search - Migrate to Svelte 5 event handlers (onsubmit, onclick)
61 lines
1.6 KiB
JavaScript
61 lines
1.6 KiB
JavaScript
/**
|
||
* Web Worker for recipe search
|
||
* Handles text normalization and filtering off the main thread
|
||
*/
|
||
|
||
let recipes = [];
|
||
|
||
self.onmessage = (e) => {
|
||
const { type, data } = e.data;
|
||
|
||
if (type === 'init') {
|
||
// Initialize worker with recipe data
|
||
recipes = data.recipes || [];
|
||
self.postMessage({ type: 'ready' });
|
||
}
|
||
|
||
if (type === 'search') {
|
||
const query = data.query;
|
||
|
||
// Empty query = show all recipes
|
||
if (!query || query.trim().length === 0) {
|
||
self.postMessage({
|
||
type: 'results',
|
||
matchedIds: recipes.map(r => r._id),
|
||
matchedCategories: new Set(recipes.map(r => r.category))
|
||
});
|
||
return;
|
||
}
|
||
|
||
// Normalize and split search query
|
||
const searchText = query.toLowerCase().trim()
|
||
.normalize('NFD')
|
||
.replace(/\p{Diacritic}/gu, "");
|
||
const searchTerms = searchText.split(" ").filter(term => term.length > 0);
|
||
|
||
// Filter recipes
|
||
const matched = recipes.filter(recipe => {
|
||
// Build searchable string from recipe data
|
||
const searchString = [
|
||
recipe.name || '',
|
||
recipe.description || '',
|
||
...(recipe.tags || [])
|
||
].join(' ')
|
||
.toLowerCase()
|
||
.normalize('NFD')
|
||
.replace(/\p{Diacritic}/gu, "")
|
||
.replace(/­|/g, ''); // Remove soft hyphens
|
||
|
||
// All search terms must match
|
||
return searchTerms.every(term => searchString.includes(term));
|
||
});
|
||
|
||
// Return matched recipe IDs and categories with results
|
||
self.postMessage({
|
||
type: 'results',
|
||
matchedIds: matched.map(r => r._id),
|
||
matchedCategories: new Set(matched.map(r => r.category))
|
||
});
|
||
}
|
||
};
|