feat: initial client side searching for resources
All checks were successful
Deploy to SFTP Server / build (push) Successful in 14m30s

This commit is contained in:
Max Richter
2025-10-27 00:04:02 +01:00
parent 207d5998da
commit 3263ab9123
11 changed files with 162 additions and 60 deletions

View File

@@ -0,0 +1,43 @@
<script lang="ts">
import { onMount } from "svelte";
let searchTerm = "";
let staticContainer: HTMLElement | null;
onMount(async () => {
staticContainer = document.getElementById("resource-list-static");
});
function search() {
if (!staticContainer) return;
const lowerCaseSearchTerm = searchTerm.toLowerCase().trim();
if (!lowerCaseSearchTerm) {
[...staticContainer?.children].forEach((element: HTMLElement) => {
element.style.display = "";
});
return;
}
for (const element of staticContainer?.children) {
const el = element as HTMLElement;
const isMatch = el.dataset.searchTerm?.includes(lowerCaseSearchTerm);
if (isMatch) {
el.style.display = "";
} else {
el.style.display = "none";
}
}
}
$: search(searchTerm);
</script>
<div class="search-wrapper my-4 noise relative">
<input
type="text"
bind:value={searchTerm}
placeholder={`Search...`}
class="w-full p-2 gradient border-1 border-neutral rounded noise" />
</div>

View File

@@ -70,7 +70,7 @@ const definedSizes = [
]; ];
const sizes = thumbnail const sizes = thumbnail
? [definedSizes[0]] ? [definedSizes[1]]
: definedSizes.filter((size) => !maxWidth || size.width <= maxWidth); : definedSizes.filter((size) => !maxWidth || size.width <= maxWidth);
--- ---

View File

@@ -8,9 +8,9 @@
typeof d === "string" ? new Date(d) : d; typeof d === "string" ? new Date(d) : d;
const iso = (d: string | Date) => { const iso = (d: string | Date) => {
if(!d) return "" if (!d) return "";
const v = toDate(d); const v = toDate(d);
if(!v?.getTime) return "" if (!v?.getTime) return "";
return isNaN(v.getTime()) ? "" : v.toISOString(); return isNaN(v.getTime()) ? "" : v.toISOString();
}; };
@@ -50,12 +50,12 @@
{/if} {/if}
</div> </div>
<style> <style>
.wrapper > * { .wrapper > * {
padding: 2px 11px; padding: 2px 11px;
border-radius: 14px; border-radius: 14px;
font-size: 11px; font-size: 11px;
opacity: 0.7; opacity: 0.7;
white-space: nowrap;
} }
</style> </style>

View File

@@ -13,7 +13,9 @@ const { resource } = Astro.props;
resource?.content?.image && ( resource?.content?.image && (
<Image <Image
hash hash
src={{ src: memorium.getImageUrl(resource.content.image) } as ImageMetadata} src={
{ src: memorium.getImageUrl(resource.content.image) } as ImageMetadata
}
alt="Cover for {resource?.content?.name}" alt="Cover for {resource?.content?.name}"
class="rounded-2xl overflow-hidden" class="rounded-2xl overflow-hidden"
pictureClass="rounded-2xl" pictureClass="rounded-2xl"
@@ -21,4 +23,7 @@ const { resource } = Astro.props;
) )
} }
</div> </div>
<div set:html={markdownToHtml(resource?.content?.articleBody)} /> <div
class="flex flex-col gap-4"
set:html={markdownToHtml(resource?.content?.articleBody)}
/>

View File

@@ -4,18 +4,30 @@ import { markdownToHtml } from "@helpers/markdown";
import Image from "@components/Image.astro"; import Image from "@components/Image.astro";
import type { ImageMetadata } from "astro"; import type { ImageMetadata } from "astro";
const { resource } = Astro.props const { resource } = Astro.props;
const ingredients = resource?.content?.recipeIngredient || []; const ingredients = resource?.content?.recipeIngredient || [];
const instructions = resource?.content?.recipeInstructions || []; const instructions = resource?.content?.recipeInstructions || [];
--- ---
<h1 class="text-4xl">{resource?.content?.name}</h1> <h1 class="text-4xl">{resource?.content?.name}</h1>
<div> <div>
{resource?.content?.image && <Image hash src={{src: memorium.getImageUrl(resource.content.image)} as ImageMetadata} alt="Cover for {resource?.content?.name}" class="rounded-2xl overflow-hidden" pictureClass="rounded-2xl" />} {
resource?.content?.image && (
<Image
hash
src={
{ src: memorium.getImageUrl(resource.content.image) } as ImageMetadata
}
alt="Cover for {resource?.content?.name}"
class="rounded-2xl overflow-hidden"
pictureClass="rounded-2xl"
/>
)
}
</div> </div>
<p>{resource?.content?.description}</p> <main class="flex flex-col gap-4" set:html={markdownToHtml(resource?.content?.description ?? "")} />
<h2 class="text-2xl">Ingredients</h2> <h2 class="text-2xl">Ingredients</h2>
<ul> <ul class="list-disc">
{ {
ingredients.map((ingredient: string) => ( ingredients.map((ingredient: string) => (
<li set:html={markdownToHtml(ingredient)} /> <li set:html={markdownToHtml(ingredient)} />
@@ -24,7 +36,7 @@ const instructions = resource?.content?.recipeInstructions || [];
</ul> </ul>
<h2 class="text-2xl">Steps</h2> <h2 class="text-2xl">Steps</h2>
<ol> <ol class="list-decimal">
{ {
instructions.map((ingredient: string) => ( instructions.map((ingredient: string) => (
<li set:html={markdownToHtml(ingredient)} /> <li set:html={markdownToHtml(ingredient)} />

View File

@@ -16,16 +16,13 @@ import ImageSlider from "@components/ImageSlider.svelte"
<ImageGallery client:load/> <ImageGallery client:load/>
## May 27th ## May 27th
First day, arrival at the sea.
import image1 from "images/20250527_125228.jpg" import image1 from "images/20250527_125228.jpg"
import image16 from "images/PXL_20250527_101057540.MP.jpg" import image16 from "images/PXL_20250527_101057540.MP.jpg"
import image17 from "images/PXL_20250527_100728883.jpg" import image17 from "images/PXL_20250527_100728883.jpg"
<ImageSlider title="Hike" client:load> <ImageSlider title="First day, arrival at the sea." client:load>
<Image src={image1} alt="A cow on a meadow in front of the sea"/> <Image src={image1} alt="A cow on a meadow in front of the sea"/>
<Image src={image17} alt="A person on flysch rock in front of a cave at the sea"/> <Image src={image17} alt="A person on flysch rock in front of a cave at the sea"/>
<Image src={image16} alt="A person in a cave from which you can see the sea"/> <Image src={image16} alt="A person in a cave from which you can see the sea"/>
@@ -33,12 +30,10 @@ import image17 from "images/PXL_20250527_100728883.jpg"
## May 28th ## May 28th
First day of hiking
import image15 from "images/PXL_20250528_121633744.MP.jpg" import image15 from "images/PXL_20250528_121633744.MP.jpg"
import image19 from "images/20250528_164715.jpg" import image19 from "images/20250528_164715.jpg"
<ImageSlider title="Hike" client:load> <ImageSlider title="First day of hiking" client:load>
<Image src={image15} alt="Us after the first 300 meters"/> <Image src={image15} alt="Us after the first 300 meters"/>
<Image src={image19} alt="Many mountain goats that like to lick the salt from the skin"/> <Image src={image19} alt="Many mountain goats that like to lick the salt from the skin"/>
</ImageSlider> </ImageSlider>
@@ -127,5 +122,5 @@ Final-descent
import image27 from "images/PXL_20250605_100650266.jpg" import image27 from "images/PXL_20250605_100650266.jpg"
<Image alt="" src={image27} alt="27"/> <Image alt="" src={image27} alt="The last walk down the mountain"/>

View File

@@ -16,7 +16,6 @@ import ImageSlider from "@components/ImageSlider.svelte"
<ImageGallery client:load/> <ImageGallery client:load/>
## 27. May ## 27. May
Erster Tag, Ankunft am Meer. Erster Tag, Ankunft am Meer.
@@ -25,7 +24,7 @@ import image1 from "images/20250527_125228.jpg"
import image16 from "images/PXL_20250527_101057540.MP.jpg" import image16 from "images/PXL_20250527_101057540.MP.jpg"
import image17 from "images/PXL_20250527_100728883.jpg" import image17 from "images/PXL_20250527_100728883.jpg"
<ImageSlider title="Wanderung" client:load> <ImageSlider client:load>
<Image src={image1} alt="Bild einer Kuh auf einer Wiese vor dem Meer"/> <Image src={image1} alt="Bild einer Kuh auf einer Wiese vor dem Meer"/>
<Image src={image17} alt="Person auf flysch gestein vor einer Höhle am Meer"/> <Image src={image17} alt="Person auf flysch gestein vor einer Höhle am Meer"/>
<Image src={image16} alt="Bild von Person in einer Höhle aus der man das Meer sieht"/> <Image src={image16} alt="Bild von Person in einer Höhle aus der man das Meer sieht"/>
@@ -38,7 +37,7 @@ Erster Wandertag
import image15 from "images/PXL_20250528_121633744.MP.jpg" import image15 from "images/PXL_20250528_121633744.MP.jpg"
import image19 from "images/20250528_164715.jpg" import image19 from "images/20250528_164715.jpg"
<ImageSlider title="Wanderung" client:load> <ImageSlider client:load>
<Image src={image15} alt="Wir nach den ersten 300 Metern"/> <Image src={image15} alt="Wir nach den ersten 300 Metern"/>
<Image src={image19} alt="Viele Bergziegen die gerne das Salz von der Haut lecken"/> <Image src={image19} alt="Viele Bergziegen die gerne das Salz von der Haut lecken"/>
</ImageSlider> </ImageSlider>
@@ -56,8 +55,6 @@ import image13 from "images/PXL_20250529_201559403.jpg"
## 30-31. May ## 30-31. May
Gewitter und Restday
import image11 from "images/PXL_20250530_110041174.jpg" import image11 from "images/PXL_20250530_110041174.jpg"
import image10 from "images/PXL_20250530_135631186.MP.jpg" import image10 from "images/PXL_20250530_135631186.MP.jpg"
import image8 from "images/PXL_20250530_170114907.MP.jpg" import image8 from "images/PXL_20250530_170114907.MP.jpg"
@@ -127,4 +124,4 @@ Finaler Abstieg
import image27 from "images/PXL_20250605_100650266.jpg" import image27 from "images/PXL_20250605_100650266.jpg"
<Image alt="" src={image27} alt="27"/> <Image alt="" src={image27} alt="The last walk down the mountain"/>

View File

@@ -27,6 +27,7 @@ export async function listResource(
id: string, id: string,
): Promise<MemoriumEntry | undefined> { ): Promise<MemoriumEntry | undefined> {
const url = `${SERVER_URL}/resources/${id}`; const url = `${SERVER_URL}/resources/${id}`;
console.log(url);
try { try {
const response = await fetch(url, { signal: AbortSignal.timeout(5000) }); const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
if (response.ok) { if (response.ok) {

View File

@@ -14,7 +14,6 @@ export async function getStaticPaths() {
const paths = await Promise.all( const paths = await Promise.all(
resourceTypes.map(async (resourceType) => { resourceTypes.map(async (resourceType) => {
const resources = await memorium.listResource(resourceType.id); const resources = await memorium.listResource(resourceType.id);
console.log({resources:resources?.content[0]})
return resources?.content?.map((res: any) => { return resources?.content?.map((res: any) => {
return { return {
params: { params: {

View File

@@ -4,6 +4,7 @@ import HeroCard from "@components/HeroCard.astro";
import * as memorium from "@helpers/memorium"; import * as memorium from "@helpers/memorium";
import { resources as resourceTypes } from "../resources.ts"; import { resources as resourceTypes } from "../resources.ts";
import { useTranslations } from "@i18n/utils"; import { useTranslations } from "@i18n/utils";
import ClientSearch from "@components/ClientSearch.svelte";
const { resourceType } = Astro.params; const { resourceType } = Astro.params;
@@ -32,15 +33,31 @@ export async function getStaticPaths() {
function isValidResource(res: any) { function isValidResource(res: any) {
return !!res?.content?._type; return !!res?.content?._type;
} }
function buildSearchTerm(res:any){
if(!res) return "";
return Object.keys(res).map((key) => {
if(key.startsWith("_")) return;
const value = res[key];
if(Array.isArray(value)) return value.join(" ");
if(typeof value === "object") return buildSearchTerm(value);
return value
}).filter(s => !!s?.length).join(" ").toLowerCase();
}
--- ---
<Layout title="Max Richter"> <Layout title="Max Richter">
<h1 class="text-4xl mb-4">{t(resourceType)}</h1> <h1 class="text-4xl mb-4">{t(resourceType)}</h1>
<p>{t(`${resourceType as "articles"}.description`)}</p> <p>{t(`${resourceType as "articles"}.description`)}</p>
<ClientSearch resourceType={resourceType} client:load />
<div id="resource-list-static"class="flex flex-col gap-6">
{ {
resources.content resources?.content
.filter((res: any) => isValidResource(res)) .filter((res: any) => isValidResource(res))
.map((resource: any) => ( .map((resource: any) => (
<div data-search-term={buildSearchTerm(resource?.content)}>
<HeroCard <HeroCard
post={{ post={{
collection: "resources/" + resourceType, collection: "resources/" + resourceType,
@@ -59,6 +76,8 @@ function isValidResource(res: any) {
}, },
}} }}
/> />
</div>
)) ))
} }
</div>
</Layout> </Layout>

View File

@@ -3,16 +3,47 @@ import Layout from "@layouts/Layout.astro";
import HeroCard from "@components/HeroCard.astro"; import HeroCard from "@components/HeroCard.astro";
import { resources } from "./resources.ts"; import { resources } from "./resources.ts";
import { useTranslations } from "@i18n/utils"; import { useTranslations } from "@i18n/utils";
import * as memorium from "@helpers/memorium";
const t = useTranslations(Astro.url); const t = useTranslations(Astro.url);
async function getCoverImage(resourceName:string){
const resources = await memorium.listResource(resourceName);
if(!resources?.content) return "";
let amount = 0;
while (true){
amount++;
const randomResource = resources?.content[Math.floor(Math.random() * resources?.content.length)];
const cover = randomResource?.content?.cover || randomResource?.content?.image;
if(cover){
if(cover.startsWith("https://") || cover.startsWith("http://")){
continue;
}
return `https://marka.max-richter.dev/${cover}`;
}
if(amount > 50) {
break;
}
}
}
--- ---
<Layout title="Max Richter"> <Layout title="Max Richter">
{resources.map((resource) => {
<HeroCard post={{ await Promise.all(resources.map(async (resource) => {
const cover = await getCoverImage(resource.id);
console.log({cover})
return <HeroCard
post={{
...resource, ...resource,
body: t(`${resource.id}.description`), body: t(`${resource.id}.description`),
data: { data: {
cover:{src:resource.cover}, title: t(resource.id)}}} /> cover: { src: cover },
)} title: t(resource.id),
},
}}
/>
}))
}
</Layout> </Layout>