feat: initial client side searching for resources
All checks were successful
Deploy to SFTP Server / build (push) Successful in 14m30s
All checks were successful
Deploy to SFTP Server / build (push) Successful in 14m30s
This commit is contained in:
43
src/components/ClientSearch.svelte
Normal file
43
src/components/ClientSearch.svelte
Normal 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>
|
||||
@@ -70,7 +70,7 @@ const definedSizes = [
|
||||
];
|
||||
|
||||
const sizes = thumbnail
|
||||
? [definedSizes[0]]
|
||||
? [definedSizes[1]]
|
||||
: definedSizes.filter((size) => !maxWidth || size.width <= maxWidth);
|
||||
---
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user