Implement progressive enhancement for universal search with context-aware filtering
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				CI / update (push) Failing after 5s
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	CI / update (push) Failing after 5s
				
			Add comprehensive search solution that works across all recipe pages with proper fallbacks. Features include universal API endpoint, context-aware filtering (category/tag/icon/season/favorites), and progressive enhancement with form submission fallback for no-JS users. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
		@@ -72,7 +72,7 @@
 | 
			
		||||
{/each}
 | 
			
		||||
</div>
 | 
			
		||||
<section>
 | 
			
		||||
<Search></Search>
 | 
			
		||||
<Search icon={active_icon}></Search>
 | 
			
		||||
</section>
 | 
			
		||||
<section>
 | 
			
		||||
<slot name=recipes></slot>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,90 @@
 | 
			
		||||
<script>
 | 
			
		||||
    import {onMount} from "svelte";
 | 
			
		||||
    import { browser } from '$app/environment';
 | 
			
		||||
    import "$lib/css/nordtheme.css";
 | 
			
		||||
    
 | 
			
		||||
    // Filter props for different contexts
 | 
			
		||||
    export let category = null;
 | 
			
		||||
    export let tag = null;
 | 
			
		||||
    export let icon = null;
 | 
			
		||||
    export let season = null;
 | 
			
		||||
    export let favoritesOnly = false;
 | 
			
		||||
    export let searchResultsUrl = '/rezepte/search';
 | 
			
		||||
    
 | 
			
		||||
    let searchQuery = '';
 | 
			
		||||
    
 | 
			
		||||
    // Build search URL with current filters
 | 
			
		||||
    function buildSearchUrl(query) {
 | 
			
		||||
        if (browser) {
 | 
			
		||||
            const url = new URL(searchResultsUrl, window.location.origin);
 | 
			
		||||
            if (query) url.searchParams.set('q', query);
 | 
			
		||||
            if (category) url.searchParams.set('category', category);
 | 
			
		||||
            if (tag) url.searchParams.set('tag', tag);
 | 
			
		||||
            if (icon) url.searchParams.set('icon', icon);
 | 
			
		||||
            if (season) url.searchParams.set('season', season);
 | 
			
		||||
            if (favoritesOnly) url.searchParams.set('favorites', 'true');
 | 
			
		||||
            return url.toString();
 | 
			
		||||
        } else {
 | 
			
		||||
            // Server-side fallback - return just the base path
 | 
			
		||||
            return searchResultsUrl;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    function handleSubmit(event) {
 | 
			
		||||
        if (browser) {
 | 
			
		||||
            // For JS-enabled browsers, prevent default and navigate programmatically
 | 
			
		||||
            // This allows for future enhancements like instant search
 | 
			
		||||
            const url = buildSearchUrl(searchQuery);
 | 
			
		||||
            window.location.href = url;
 | 
			
		||||
        }
 | 
			
		||||
        // If no JS, form will submit normally
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    function clearSearch() {
 | 
			
		||||
        searchQuery = '';
 | 
			
		||||
        if (browser) {
 | 
			
		||||
            // Reset any client-side filtering if present
 | 
			
		||||
            const recipes = document.querySelectorAll(".search_me");
 | 
			
		||||
            recipes.forEach(recipe => {
 | 
			
		||||
                recipe.style.display = 'flex';
 | 
			
		||||
                recipe.classList.remove("matched-recipe");
 | 
			
		||||
            });
 | 
			
		||||
            document.querySelectorAll(".media_scroller_wrapper").forEach( scroller => {
 | 
			
		||||
                scroller.style.display= 'block'
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onMount(() => {
 | 
			
		||||
        // Swap buttons for JS-enabled experience
 | 
			
		||||
        const submitButton = document.getElementById('submit-search');
 | 
			
		||||
        const clearButton = document.getElementById('clear-search');
 | 
			
		||||
        
 | 
			
		||||
        if (submitButton && clearButton) {
 | 
			
		||||
            submitButton.style.display = 'none';
 | 
			
		||||
            clearButton.style.display = 'flex';
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Get initial search value from URL if present
 | 
			
		||||
        const urlParams = new URLSearchParams(window.location.search);
 | 
			
		||||
        const urlQuery = urlParams.get('q');
 | 
			
		||||
        if (urlQuery) {
 | 
			
		||||
            searchQuery = urlQuery;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Enhanced client-side filtering (existing functionality)
 | 
			
		||||
        const recipes = document.querySelectorAll(".search_me");
 | 
			
		||||
        const search = document.getElementById("search");
 | 
			
		||||
  	const clearSearch = document.getElementById("clear-search");
 | 
			
		||||
        
 | 
			
		||||
        if (recipes.length > 0 && search) {
 | 
			
		||||
            function do_search(click_only_result=false){
 | 
			
		||||
   		// grab search input value
 | 
			
		||||
                const searchText = search.value.toLowerCase().trim().normalize('NFD').replace(/\p{Diacritic}/gu, "");
 | 
			
		||||
                const searchTerms = searchText.split(" ");
 | 
			
		||||
                const hasFilter = searchText.length > 0;
 | 
			
		||||
 | 
			
		||||
                let scrollers_with_results = [];
 | 
			
		||||
                let scrollers = [];
 | 
			
		||||
    		// for each recipe hide all but matched
 | 
			
		||||
                
 | 
			
		||||
                recipes.forEach(recipe => {
 | 
			
		||||
                    const searchString = `${recipe.textContent} ${recipe.dataset.tags}`.toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, "").replace(/­|/g, '');
 | 
			
		||||
                    const isMatch = searchTerms.every(term => searchString.includes(term));
 | 
			
		||||
@@ -35,43 +104,21 @@ onMount(() => {
 | 
			
		||||
                scrollers.filter(item => !scrollers_with_results.includes(item)).forEach( scroller => {
 | 
			
		||||
                    scroller.parentNode.style.display= 'none'
 | 
			
		||||
                })
 | 
			
		||||
		scroll
 | 
			
		||||
                
 | 
			
		||||
                let items = document.querySelectorAll(".matched-recipe");
 | 
			
		||||
		items = [...new Set(items)] // make unique as seasonal mediascroller can lead to duplicates
 | 
			
		||||
		// if only one result and click_only_result is true, click it
 | 
			
		||||
                items = [...new Set(items)]
 | 
			
		||||
                if(click_only_result && scrollers_with_results.length == 1 && items.length == 1){
 | 
			
		||||
			// add '/rezepte' to history to not force-redirect back to recipe if going back
 | 
			
		||||
                    items[0].click();
 | 
			
		||||
                }
 | 
			
		||||
		// if scrollers with results are presenet scroll first result into view
 | 
			
		||||
		/*if(scrollers_with_results.length > 0){
 | 
			
		||||
			scrollers_with_results[0].scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
 | 
			
		||||
		}*/ // For now disabled because it is annoying on mobile
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            search.addEventListener("input", () => {
 | 
			
		||||
                searchQuery = search.value;
 | 
			
		||||
                do_search();
 | 
			
		||||
            })
 | 
			
		||||
            
 | 
			
		||||
  	clearSearch.addEventListener("click", () => {
 | 
			
		||||
    		search.value = "";
 | 
			
		||||
    		recipes.forEach(recipe => {
 | 
			
		||||
      			recipe.style.display = 'flex';
 | 
			
		||||
      			recipe.classList.remove("matched-recipe");
 | 
			
		||||
		})
 | 
			
		||||
		document.querySelectorAll(".media_scroller_wrapper").forEach( scroller => {
 | 
			
		||||
			scroller.style.display= 'block'
 | 
			
		||||
		})
 | 
			
		||||
  	})
 | 
			
		||||
 | 
			
		||||
	let paramString = window.location.href.split('?')[1];
 | 
			
		||||
	let queryString = new URLSearchParams(paramString);
 | 
			
		||||
 | 
			
		||||
	for (let pair of queryString.entries()) {
 | 
			
		||||
		if(pair[0] == 'q'){
 | 
			
		||||
			const search = document.getElementById("search");
 | 
			
		||||
			search.value=pair[1];
 | 
			
		||||
            // Initial search if URL had query
 | 
			
		||||
            if (urlQuery) {
 | 
			
		||||
                do_search(true);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -110,7 +157,7 @@ input::placeholder{
 | 
			
		||||
	scale: 1.02 1.02;
 | 
			
		||||
  	filter: drop-shadow(0.4em  0.5em 1em rgba(0,0,0,0.6))
 | 
			
		||||
}
 | 
			
		||||
button#clear-search {
 | 
			
		||||
.search-button {
 | 
			
		||||
  all: unset;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
@@ -123,17 +170,35 @@ button#clear-search {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  transition: color 180ms ease-in-out;
 | 
			
		||||
}
 | 
			
		||||
button#clear-search:hover {
 | 
			
		||||
.search-button:hover {
 | 
			
		||||
  color: white;
 | 
			
		||||
  scale: 1.1 1.1;
 | 
			
		||||
}
 | 
			
		||||
button#clear-search:active{
 | 
			
		||||
.search-button:active{
 | 
			
		||||
transition: 50ms;
 | 
			
		||||
scale: 0.8 0.8;
 | 
			
		||||
}
 | 
			
		||||
.search-button svg {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
<div class="search js-only">
 | 
			
		||||
  <input type="text" id="search" placeholder="Suche...">
 | 
			
		||||
  <button id="clear-search">
 | 
			
		||||
	  <svg  xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Sucheintrag löschen</title><path d="M135.19 390.14a28.79 28.79 0 0021.68 9.86h246.26A29 29 0 00432 371.13V140.87A29 29 0 00403.13 112H156.87a28.84 28.84 0 00-21.67 9.84v0L46.33 256l88.86 134.11z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"></path><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M336.67 192.33L206.66 322.34M336.67 322.34L206.66 192.33M336.67 192.33L206.66 322.34M336.67 322.34L206.66 192.33"></path></svg></button>
 | 
			
		||||
</div>
 | 
			
		||||
<form class="search" method="get" action={buildSearchUrl('')} on:submit|preventDefault={handleSubmit}>
 | 
			
		||||
  {#if category}<input type="hidden" name="category" value={category} />{/if}
 | 
			
		||||
  {#if tag}<input type="hidden" name="tag" value={tag} />{/if}
 | 
			
		||||
  {#if icon}<input type="hidden" name="icon" value={icon} />{/if}
 | 
			
		||||
  {#if season}<input type="hidden" name="season" value={season} />{/if}
 | 
			
		||||
  {#if favoritesOnly}<input type="hidden" name="favorites" value="true" />{/if}
 | 
			
		||||
  
 | 
			
		||||
  <input type="text" id="search" name="q" placeholder="Suche..." bind:value={searchQuery}>
 | 
			
		||||
  
 | 
			
		||||
  <!-- Submit button (visible by default, hidden when JS loads) -->
 | 
			
		||||
  <button type="submit" id="submit-search" class="search-button" style="display: flex;">
 | 
			
		||||
	  <svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512" style="width: 100%; height: 100%;"><title>Suchen</title><path d="M221.09 64a157.09 157.09 0 10157.09 157.09A157.1 157.1 0 00221.09 64z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"></path><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="m338.29 338.29 105.25 105.25"></path></svg>
 | 
			
		||||
  </button>
 | 
			
		||||
  
 | 
			
		||||
  <!-- Clear button (hidden by default, shown when JS loads) -->
 | 
			
		||||
  <button type="button" id="clear-search" class="search-button js-only" style="display: none;" on:click={clearSearch}>
 | 
			
		||||
	  <svg  xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Sucheintrag löschen</title><path d="M135.19 390.14a28.79 28.79 0 0021.68 9.86h246.26A29 29 0 00432 371.13V140.87A29 29 0 00403.13 112H156.87a28.84 28.84 0 00-21.67 9.84v0L46.33 256l88.86 134.11z" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"></path><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M336.67 192.33L206.66 322.34M336.67 322.34L206.66 192.33M336.67 192.33L206.66 322.34M336.67 322.34L206.66 192.33"></path></svg>
 | 
			
		||||
  </button>
 | 
			
		||||
</form>
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ a.month:hover,
 | 
			
		||||
{/each}
 | 
			
		||||
</div>
 | 
			
		||||
<section>
 | 
			
		||||
<Search></Search>
 | 
			
		||||
<Search season={active_index + 1}></Search>
 | 
			
		||||
</section>
 | 
			
		||||
<section>
 | 
			
		||||
<slot name=recipes></slot>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										74
									
								
								src/routes/api/rezepte/search/+server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/routes/api/rezepte/search/+server.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
import { json, type RequestHandler } from '@sveltejs/kit';
 | 
			
		||||
import type { BriefRecipeType } from '../../../../types/types';
 | 
			
		||||
import { Recipe } from '../../../../models/Recipe';
 | 
			
		||||
import { dbConnect, dbDisconnect } from '../../../../utils/db';
 | 
			
		||||
 | 
			
		||||
export const GET: RequestHandler = async ({ url, locals }) => {
 | 
			
		||||
  await dbConnect();
 | 
			
		||||
  
 | 
			
		||||
  const query = url.searchParams.get('q')?.toLowerCase().trim() || '';
 | 
			
		||||
  const category = url.searchParams.get('category');
 | 
			
		||||
  const tag = url.searchParams.get('tag');
 | 
			
		||||
  const icon = url.searchParams.get('icon');
 | 
			
		||||
  const season = url.searchParams.get('season');
 | 
			
		||||
  const favoritesOnly = url.searchParams.get('favorites') === 'true';
 | 
			
		||||
  
 | 
			
		||||
  try {
 | 
			
		||||
    // Build base query
 | 
			
		||||
    let dbQuery: any = {};
 | 
			
		||||
    
 | 
			
		||||
    // Apply filters based on context
 | 
			
		||||
    if (category) {
 | 
			
		||||
      dbQuery.category = category;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (tag) {
 | 
			
		||||
      dbQuery.tags = { $in: [tag] };
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (icon) {
 | 
			
		||||
      dbQuery.icon = icon;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (season) {
 | 
			
		||||
      const seasonNum = parseInt(season);
 | 
			
		||||
      if (!isNaN(seasonNum)) {
 | 
			
		||||
        dbQuery.season = { $in: [seasonNum] };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Get all recipes matching base filters
 | 
			
		||||
    let recipes = await Recipe.find(dbQuery, 'name short_name tags category icon description season dateModified').lean() as BriefRecipeType[];
 | 
			
		||||
    
 | 
			
		||||
    // Handle favorites filter
 | 
			
		||||
    if (favoritesOnly && locals.session?.user) {
 | 
			
		||||
      const User = (await import('../../../../models/User')).User;
 | 
			
		||||
      const user = await User.findById(locals.session.user.id);
 | 
			
		||||
      if (user && user.favoriteRecipes) {
 | 
			
		||||
        const favoriteShortNames = user.favoriteRecipes;
 | 
			
		||||
        recipes = recipes.filter(recipe => favoriteShortNames.includes(recipe.short_name));
 | 
			
		||||
      } else {
 | 
			
		||||
        recipes = [];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Apply text search if query provided
 | 
			
		||||
    if (query) {
 | 
			
		||||
      const searchTerms = query.normalize('NFD').replace(/\p{Diacritic}/gu, "").split(" ");
 | 
			
		||||
      
 | 
			
		||||
      recipes = recipes.filter(recipe => {
 | 
			
		||||
        const searchString = `${recipe.name} ${recipe.description || ''} ${recipe.tags?.join(' ') || ''}`.toLowerCase()
 | 
			
		||||
          .normalize('NFD').replace(/\p{Diacritic}/gu, "").replace(/­|/g, '');
 | 
			
		||||
        
 | 
			
		||||
        return searchTerms.every(term => searchString.includes(term));
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    await dbDisconnect();
 | 
			
		||||
    return json(JSON.parse(JSON.stringify(recipes)));
 | 
			
		||||
    
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    await dbDisconnect();
 | 
			
		||||
    return json({ error: 'Search failed' }, { status: 500 });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
	}
 | 
			
		||||
</style>
 | 
			
		||||
<h1>Rezepte in Kategorie <q>{data.category}</q>:</h1>
 | 
			
		||||
<Search></Search>
 | 
			
		||||
<Search category={data.category}></Search>
 | 
			
		||||
<section>
 | 
			
		||||
<Recipes>
 | 
			
		||||
	{#each rand_array(data.recipes) as recipe}
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ h1{
 | 
			
		||||
    {/if}
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
<Search></Search>
 | 
			
		||||
<Search favoritesOnly={true}></Search>
 | 
			
		||||
 | 
			
		||||
{#if data.error}
 | 
			
		||||
    <p class="empty-state">Fehler beim Laden der Favoriten: {data.error}</p>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										50
									
								
								src/routes/rezepte/search/+page.server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/routes/rezepte/search/+page.server.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
import type { PageServerLoad } from './$types';
 | 
			
		||||
 | 
			
		||||
export const load: PageServerLoad = async ({ url, fetch }) => {
 | 
			
		||||
    const query = url.searchParams.get('q') || '';
 | 
			
		||||
    const category = url.searchParams.get('category');
 | 
			
		||||
    const tag = url.searchParams.get('tag');
 | 
			
		||||
    const icon = url.searchParams.get('icon');
 | 
			
		||||
    const season = url.searchParams.get('season');
 | 
			
		||||
    const favoritesOnly = url.searchParams.get('favorites') === 'true';
 | 
			
		||||
    
 | 
			
		||||
    // Build API URL with filters
 | 
			
		||||
    const apiUrl = new URL('/api/rezepte/search', url.origin);
 | 
			
		||||
    if (query) apiUrl.searchParams.set('q', query);
 | 
			
		||||
    if (category) apiUrl.searchParams.set('category', category);
 | 
			
		||||
    if (tag) apiUrl.searchParams.set('tag', tag);
 | 
			
		||||
    if (icon) apiUrl.searchParams.set('icon', icon);
 | 
			
		||||
    if (season) apiUrl.searchParams.set('season', season);
 | 
			
		||||
    if (favoritesOnly) apiUrl.searchParams.set('favorites', 'true');
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
        const response = await fetch(apiUrl.toString());
 | 
			
		||||
        const results = await response.json();
 | 
			
		||||
        
 | 
			
		||||
        return {
 | 
			
		||||
            query,
 | 
			
		||||
            results: response.ok ? results : [],
 | 
			
		||||
            error: response.ok ? null : results.error || 'Search failed',
 | 
			
		||||
            filters: {
 | 
			
		||||
                category,
 | 
			
		||||
                tag,
 | 
			
		||||
                icon,
 | 
			
		||||
                season,
 | 
			
		||||
                favoritesOnly
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        return {
 | 
			
		||||
            query,
 | 
			
		||||
            results: [],
 | 
			
		||||
            error: 'Search failed',
 | 
			
		||||
            filters: {
 | 
			
		||||
                category,
 | 
			
		||||
                tag,
 | 
			
		||||
                icon,
 | 
			
		||||
                season,
 | 
			
		||||
                favoritesOnly
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										75
									
								
								src/routes/rezepte/search/+page.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/routes/rezepte/search/+page.svelte
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
    import type { PageData } from './$types';
 | 
			
		||||
    import Recipes from '$lib/components/Recipes.svelte';
 | 
			
		||||
    import Search from '$lib/components/Search.svelte';
 | 
			
		||||
    import Card from '$lib/components/Card.svelte';
 | 
			
		||||
    export let data: PageData;
 | 
			
		||||
    export let current_month = new Date().getMonth() + 1;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
    h1 {
 | 
			
		||||
        text-align: center;
 | 
			
		||||
        font-size: 3em;
 | 
			
		||||
    }
 | 
			
		||||
    .search-info {
 | 
			
		||||
        text-align: center;
 | 
			
		||||
        margin-bottom: 2rem;
 | 
			
		||||
        color: var(--nord3);
 | 
			
		||||
    }
 | 
			
		||||
    .filter-info {
 | 
			
		||||
        text-align: center;
 | 
			
		||||
        margin-bottom: 1rem;
 | 
			
		||||
        font-size: 0.9em;
 | 
			
		||||
        color: var(--nord2);
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
    <title>Suchergebnisse{data.query ? ` für "${data.query}"` : ''} - Bocken Rezepte</title>
 | 
			
		||||
    <meta name="description" content="Suchergebnisse in den Bockenschen Rezepten." />
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<h1>Suchergebnisse</h1>
 | 
			
		||||
 | 
			
		||||
{#if data.filters.category || data.filters.tag || data.filters.icon || data.filters.season || data.filters.favoritesOnly}
 | 
			
		||||
    <div class="filter-info">
 | 
			
		||||
        Gefiltert nach:
 | 
			
		||||
        {#if data.filters.category}Kategorie "{data.filters.category}"{/if}
 | 
			
		||||
        {#if data.filters.tag}Stichwort "{data.filters.tag}"{/if}
 | 
			
		||||
        {#if data.filters.icon}Icon "{data.filters.icon}"{/if}
 | 
			
		||||
        {#if data.filters.season}Saison "{data.filters.season}"{/if}
 | 
			
		||||
        {#if data.filters.favoritesOnly}Nur Favoriten{/if}
 | 
			
		||||
    </div>
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
<Search 
 | 
			
		||||
    category={data.filters.category}
 | 
			
		||||
    tag={data.filters.tag}
 | 
			
		||||
    icon={data.filters.icon}
 | 
			
		||||
    season={data.filters.season}
 | 
			
		||||
    favoritesOnly={data.filters.favoritesOnly}
 | 
			
		||||
/>
 | 
			
		||||
 | 
			
		||||
{#if data.error}
 | 
			
		||||
    <div class="search-info">
 | 
			
		||||
        <p>Fehler bei der Suche: {data.error}</p>
 | 
			
		||||
    </div>
 | 
			
		||||
{:else if data.query}
 | 
			
		||||
    <div class="search-info">
 | 
			
		||||
        <p>{data.results.length} Ergebnisse für "{data.query}"</p>
 | 
			
		||||
    </div>
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
{#if data.results.length > 0}
 | 
			
		||||
    <Recipes>
 | 
			
		||||
        {#each data.results as recipe}
 | 
			
		||||
            <Card {recipe} {current_month} isFavorite={recipe.isFavorite} showFavoriteIndicator={true}></Card>
 | 
			
		||||
        {/each}
 | 
			
		||||
    </Recipes>
 | 
			
		||||
{:else if data.query && !data.error}
 | 
			
		||||
    <div class="search-info">
 | 
			
		||||
        <p>Keine Rezepte gefunden.</p>
 | 
			
		||||
        <p>Versuche es mit anderen Suchbegriffen.</p>
 | 
			
		||||
    </div>
 | 
			
		||||
{/if}
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
	}
 | 
			
		||||
</style>
 | 
			
		||||
<h1>Rezepte mit Stichwort <q>{data.tag}</q>:</h1>
 | 
			
		||||
<Search></Search>
 | 
			
		||||
<Search tag={data.tag}></Search>
 | 
			
		||||
<section>
 | 
			
		||||
<Recipes>
 | 
			
		||||
	{#each rand_array(data.recipes) as recipe}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user