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
? [definedSizes[0]]
? [definedSizes[1]]
: definedSizes.filter((size) => !maxWidth || size.width <= maxWidth);
---

View File

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

View File

@@ -13,7 +13,9 @@ const { resource } = Astro.props;
resource?.content?.image && (
<Image
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}"
class="rounded-2xl overflow-hidden"
pictureClass="rounded-2xl"
@@ -21,4 +23,7 @@ const { resource } = Astro.props;
)
}
</div>
<div set:html={markdownToHtml(resource?.content?.articleBody)} />
<div
class="flex flex-col gap-4"
set:html={markdownToHtml(resource?.content?.articleBody)}
/>

View File

@@ -4,30 +4,42 @@ import { markdownToHtml } from "@helpers/markdown";
import Image from "@components/Image.astro";
import type { ImageMetadata } from "astro";
const { resource } = Astro.props
const { resource } = Astro.props;
const ingredients = resource?.content?.recipeIngredient || [];
const instructions = resource?.content?.recipeInstructions || [];
---
<h1 class="text-4xl">{resource?.content?.name}</h1>
<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>
<p>{resource?.content?.description}</p>
<main class="flex flex-col gap-4" set:html={markdownToHtml(resource?.content?.description ?? "")} />
<h2 class="text-2xl">Ingredients</h2>
<ul>
<ul class="list-disc">
{
ingredients.map((ingredient: string) => (
<li set:html={markdownToHtml(ingredient)}/>
<li set:html={markdownToHtml(ingredient)} />
))
}
</ul>
<h2 class="text-2xl">Steps</h2>
<ol>
<ol class="list-decimal">
{
instructions.map((ingredient: string) => (
<li set:html={markdownToHtml(ingredient)}/>
<li set:html={markdownToHtml(ingredient)} />
))
}
</ol>

View File

@@ -16,16 +16,13 @@ import ImageSlider from "@components/ImageSlider.svelte"
<ImageGallery client:load/>
## May 27th
First day, arrival at the sea.
import image1 from "images/20250527_125228.jpg"
import image16 from "images/PXL_20250527_101057540.MP.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={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"/>
@@ -33,12 +30,10 @@ import image17 from "images/PXL_20250527_100728883.jpg"
## May 28th
First day of hiking
import image15 from "images/PXL_20250528_121633744.MP.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={image19} alt="Many mountain goats that like to lick the salt from the skin"/>
</ImageSlider>
@@ -127,5 +122,5 @@ Final-descent
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/>
## 27. May
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 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={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"/>
@@ -38,7 +37,7 @@ Erster Wandertag
import image15 from "images/PXL_20250528_121633744.MP.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={image19} alt="Viele Bergziegen die gerne das Salz von der Haut lecken"/>
</ImageSlider>
@@ -56,8 +55,6 @@ import image13 from "images/PXL_20250529_201559403.jpg"
## 30-31. May
Gewitter und Restday
import image11 from "images/PXL_20250530_110041174.jpg"
import image10 from "images/PXL_20250530_135631186.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"
<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,
): Promise<MemoriumEntry | undefined> {
const url = `${SERVER_URL}/resources/${id}`;
console.log(url);
try {
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
if (response.ok) {

View File

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

View File

@@ -4,6 +4,7 @@ import HeroCard from "@components/HeroCard.astro";
import * as memorium from "@helpers/memorium";
import { resources as resourceTypes } from "../resources.ts";
import { useTranslations } from "@i18n/utils";
import ClientSearch from "@components/ClientSearch.svelte";
const { resourceType } = Astro.params;
@@ -32,15 +33,31 @@ export async function getStaticPaths() {
function isValidResource(res: any) {
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">
<h1 class="text-4xl mb-4">{t(resourceType)}</h1>
<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))
.map((resource: any) => (
<div data-search-term={buildSearchTerm(resource?.content)}>
<HeroCard
post={{
collection: "resources/" + resourceType,
@@ -59,6 +76,8 @@ function isValidResource(res: any) {
},
}}
/>
</div>
))
}
</div>
</Layout>

View File

@@ -2,17 +2,48 @@
import Layout from "@layouts/Layout.astro";
import HeroCard from "@components/HeroCard.astro";
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);
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">
{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,
body: t(`${resource.id}.description`),
data: {
cover:{src:resource.cover}, title: t(resource.id)}}} />
)}
cover: { src: cover },
title: t(resource.id),
},
}}
/>
}))
}
</Layout>