Compare commits
9 Commits
207d5998da
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24d01e44b0
|
||
|
|
6545a25741
|
||
|
|
c527a13c54
|
||
|
|
c3299868c0
|
||
|
|
71074a8b49
|
||
|
|
ea5c35ee85
|
||
| 6b8b032832 | |||
|
|
e314d6edcb
|
||
|
|
3263ab9123
|
@@ -42,9 +42,7 @@ export default defineConfig({
|
||||
],
|
||||
server: {
|
||||
watch: {
|
||||
// Customize watch behavior to reduce file watchers
|
||||
ignored: ["**/node_modules/**", "**/dist/**", "**/.git/**"],
|
||||
usePolling: process.env.NODE_ENV === "production",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
18
package.json
18
package.json
@@ -11,15 +11,15 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.5",
|
||||
"@astrojs/mdx": "^4.3.7",
|
||||
"@astrojs/svelte": "^7.2.0",
|
||||
"@astrojs/mdx": "^4.3.9",
|
||||
"@astrojs/svelte": "^7.2.1",
|
||||
"@astrojs/tailwind": "^6.0.2",
|
||||
"astro": "^5.14.8",
|
||||
"astro": "^5.15.2",
|
||||
"astro-i18n-aut": "^0.7.3",
|
||||
"exifreader": "^4.32.0",
|
||||
"svelte": "^5.39.8",
|
||||
"svelte": "^5.42.3",
|
||||
"svelte-gestures": "^5.2.2",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"tailwindcss": "^4.1.16",
|
||||
"thumbhash": "^0.1.1",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
@@ -27,16 +27,16 @@
|
||||
"@astrojs/sitemap": "^3.6.0",
|
||||
"@iconify-json/tabler": "^1.2.23",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@unocss/preset-icons": "^66.5.2",
|
||||
"@unocss/reset": "^66.5.2",
|
||||
"@unocss/preset-icons": "^66.5.4",
|
||||
"@unocss/reset": "^66.5.4",
|
||||
"astro-font": "^1.1.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"ogl": "^1.0.11",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"sharp": "^0.34.4",
|
||||
"unocss": "^66.5.2",
|
||||
"unplugin-icons": "^22.4.2",
|
||||
"unocss": "^66.5.4",
|
||||
"unplugin-icons": "^22.5.0",
|
||||
"vite-plugin-glsl": "^1.5.4"
|
||||
}
|
||||
}
|
||||
|
||||
1273
pnpm-lock.yaml
generated
1273
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
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>
|
||||
@@ -25,10 +25,14 @@ const translatePath = useTranslatedPath(Astro.url);
|
||||
const t = useTranslations(Astro.url);
|
||||
|
||||
const link = translatePath(`/${collection}/${id.split("/")[0]}`);
|
||||
|
||||
const image = cover as unknown;
|
||||
|
||||
const hasCover = typeof image === "string" ? !!image?.length : !!cover?.src;
|
||||
---
|
||||
|
||||
<Card
|
||||
classes={`grid gradient border-1 border-neutral overflow-hidden ${cover ? "grid-rows-[200px_1fr] xs:grid-rows-none xs:grid-cols-[1fr_200px]" : ""}`}>
|
||||
classes={`grid gradient border-1 border-neutral overflow-hidden ${hasCover ? "grid-rows-[200px_1fr] xs:grid-rows-none xs:grid-cols-[1fr_200px]" : ""}`}>
|
||||
<Card.Content classes="px-8 py-7 order-last xs:order-first">
|
||||
{
|
||||
(date || body || rating !== undefined) && (
|
||||
@@ -57,14 +61,15 @@ const link = translatePath(`/${collection}/${id.split("/")[0]}`);
|
||||
<Card.ReadMoreButton link={link} text={t("read-more")} />
|
||||
</Card.Content>
|
||||
{
|
||||
cover && (
|
||||
hasCover && (
|
||||
<a href={link}>
|
||||
<Image
|
||||
hash
|
||||
loader={false}
|
||||
src={cover as ImageMetadata}
|
||||
alt={"cover for " + title}
|
||||
class="right-0 h-full object-cover object-center rounded-none border-l border-neutral"
|
||||
class="h-full right-0 object-cover object-center rounded-none border-l border-neutral"
|
||||
pictureClass="h-full"
|
||||
thumbnail
|
||||
/>
|
||||
</a>
|
||||
|
||||
@@ -15,23 +15,25 @@ interface Props {
|
||||
thumbnail?: boolean;
|
||||
}
|
||||
|
||||
async function checkImage(image: ImageMetadata) {
|
||||
async function checkImage(
|
||||
image: ImageMetadata,
|
||||
): Promise<{ height: number; width: number } | undefined> {
|
||||
const src = typeof image === "string" ? image : image.src;
|
||||
if (!src) return false;
|
||||
if (!src) return;
|
||||
try {
|
||||
if (src.startsWith("/@fs") || src.startsWith("/_astro")) return true;
|
||||
if (src.startsWith("/@fs") || src.startsWith("/_astro")) return image;
|
||||
const res = await inferRemoteSize(src);
|
||||
if (res.format) {
|
||||
image.format = res.format;
|
||||
return true;
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to load: ", src);
|
||||
}
|
||||
return false;
|
||||
return;
|
||||
} catch (err) {
|
||||
console.log("\n");
|
||||
console.log("Failed to fetch: ", src);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +72,7 @@ const definedSizes = [
|
||||
];
|
||||
|
||||
const sizes = thumbnail
|
||||
? [definedSizes[0]]
|
||||
? [definedSizes[1]]
|
||||
: definedSizes.filter((size) => !maxWidth || size.width <= maxWidth);
|
||||
---
|
||||
|
||||
@@ -81,16 +83,19 @@ const sizes = thumbnail
|
||||
alt={alt}
|
||||
data-thumbhash={thumbhash}
|
||||
data-exif={JSON.stringify(exif)}
|
||||
inferSize={true}
|
||||
width={imageOk?.width}
|
||||
height={imageOk?.height}
|
||||
pictureAttributes={{
|
||||
class: `${hash ? "block h-full relative" : ""} ${loader ? "thumb" : ""} ${pictureClass}`,
|
||||
}}
|
||||
class={`${Astro.props.class} h-full w-full`}
|
||||
class={`${Astro.props.class} w-full`}
|
||||
widths={sizes.map((size) => size.width)}
|
||||
sizes={sizes
|
||||
.map((size) => `${size.media || "100vw"} ${size.width}px`)
|
||||
.join(", ")}>
|
||||
<slot />
|
||||
</AstroImage>
|
||||
) : undefined
|
||||
) : (
|
||||
<div>{JSON.stringify({ "imageOk":imageOk, image })}</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,11 +8,14 @@
|
||||
let height: number;
|
||||
let loaded = false;
|
||||
|
||||
function hide(img: HTMLPictureElement) {
|
||||
function hide(index: number) {
|
||||
const img = images[index];
|
||||
img.classList.remove("active");
|
||||
}
|
||||
|
||||
function show(img: HTMLPictureElement) {
|
||||
const heightCache = [];
|
||||
function show(index: number) {
|
||||
const img = images[index];
|
||||
img.classList.add("active");
|
||||
const _img = img.querySelector("img") || img;
|
||||
if (!_img) return;
|
||||
@@ -21,9 +24,12 @@
|
||||
_img.style.opacity = "1";
|
||||
});
|
||||
altText = _img["alt"] ?? _img.getAttribute("alt") ?? "";
|
||||
height = _img.getBoundingClientRect().height;
|
||||
if (heightCache[index]) {
|
||||
height = heightCache[index];
|
||||
}
|
||||
setTimeout(() => {
|
||||
height = _img.getBoundingClientRect().height;
|
||||
height = heightCache[index] ?? _img.getBoundingClientRect().height;
|
||||
heightCache[index] = height;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
@@ -31,16 +37,16 @@
|
||||
function setIndex(i: number) {
|
||||
if (i < 0) i = images.length - 1;
|
||||
if (i >= images.length) i = 0;
|
||||
hide(images[index]);
|
||||
hide(index);
|
||||
index = i;
|
||||
show(images[index]);
|
||||
show(index);
|
||||
}
|
||||
|
||||
$: if (slot && !images?.length) {
|
||||
images = Array.from(slot.querySelectorAll("picture"));
|
||||
if (images?.length) {
|
||||
images.forEach(hide);
|
||||
show(images[index]);
|
||||
images.forEach((_, i) => hide(i));
|
||||
show(index);
|
||||
images[index].onload = () => {
|
||||
loaded = true;
|
||||
height = images[index].getBoundingClientRect().height;
|
||||
@@ -54,29 +60,27 @@
|
||||
|
||||
<div
|
||||
class="wrapper grid overflow-hidden rounded-xl border border-neutral"
|
||||
class:title
|
||||
class:title={true}
|
||||
class:not-loaded={!loaded}
|
||||
class:loaded
|
||||
style={`--height:${height}px`}>
|
||||
{#if title}
|
||||
<div class="flex items-center p-x-4 p-y-6 bg justify-between">
|
||||
<h3>{title}</h3>
|
||||
<div class="flex items-center p-x-4 p-y-6 bg justify-between">
|
||||
<h3>{title}</h3>
|
||||
|
||||
<div
|
||||
class="overflow-hidden rounded-md bg-light gap-2 flex p-2 border border-light">
|
||||
<button
|
||||
class="flex-1 i-tabler-arrow-left"
|
||||
aria-label="previous image"
|
||||
on:click={() => setIndex(index - 1)}></button>
|
||||
<button
|
||||
class="flex-1 i-tabler-arrow-right"
|
||||
aria-label="next image"
|
||||
on:click={() => setIndex(index + 1)}></button>
|
||||
</div>
|
||||
<div
|
||||
class="overflow-hidden rounded-md bg-light gap-2 flex p-2 border border-light">
|
||||
<button
|
||||
class="flex-1 i-tabler-arrow-left"
|
||||
aria-label="previous image"
|
||||
on:click={() => setIndex(index - 1)}></button>
|
||||
<button
|
||||
class="flex-1 i-tabler-arrow-right"
|
||||
aria-label="next image"
|
||||
on:click={() => setIndex(index + 1)}></button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="images border-t-1 border-b-1 border-neutral" bind:this={slot}>
|
||||
<div class="images border-block border-neutral" bind:this={slot}>
|
||||
<slot />
|
||||
</div>
|
||||
<div class="px-4 flex items-center place-content-between bg">
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -34,22 +34,23 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="flex gap-3 wrapper">
|
||||
{#if rating}
|
||||
<div class="text-sm bg-light">{formatRating(rating)}</div>
|
||||
{/if}
|
||||
{#if date}
|
||||
<time class="text-sm bg-light" datetime={iso(date)}
|
||||
>{formatDate(date)}</time>
|
||||
{/if}
|
||||
{#if readDuration > 1}
|
||||
<div class="text-sm bg-light">{readDuration} mins read</div>
|
||||
{/if}
|
||||
{#if author}
|
||||
<div class="text-sm bg-light">{author}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if rating || date || readDuration || author}
|
||||
<div class="flex flex-wrap gap-3 wrapper">
|
||||
{#if rating}
|
||||
<div class="text-sm bg-light">{formatRating(rating)}</div>
|
||||
{/if}
|
||||
{#if date}
|
||||
<time class="text-sm bg-light" datetime={iso(date)}
|
||||
>{formatDate(date)}</time>
|
||||
{/if}
|
||||
{#if readDuration > 1}
|
||||
<div class="text-sm bg-light">{readDuration} mins read</div>
|
||||
{/if}
|
||||
{#if author}
|
||||
<div class="text-sm bg-light">{author}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.wrapper > * {
|
||||
@@ -57,5 +58,6 @@
|
||||
border-radius: 14px;
|
||||
font-size: 11px;
|
||||
opacity: 0.7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
<a
|
||||
href={link}
|
||||
data-astro-prefetch
|
||||
class="bg-light p-2 text-s rounded-md px-4 flex flex-0 items-center gap-2 w-fit"
|
||||
class="mt-auto bg-light p-2 text-s rounded-md px-4 flex flex-0 items-center gap-2 w-fit"
|
||||
>{text}<span class="i-tabler-arrow-right inline-block w-4 h-4"></span>
|
||||
</a>
|
||||
|
||||
@@ -13,12 +13,17 @@ 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"
|
||||
pictureClass="rounded-2xl box-shadow"
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div set:html={markdownToHtml(resource?.content?.articleBody)} />
|
||||
<div
|
||||
class="flex flex-col gap-4"
|
||||
set:html={markdownToHtml(resource?.content?.articleBody)}
|
||||
/>
|
||||
|
||||
@@ -4,30 +4,45 @@ 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" />}
|
||||
</div>
|
||||
<p>{resource?.content?.description}</p>
|
||||
<h2 class="text-2xl">Ingredients</h2>
|
||||
<ul>
|
||||
{
|
||||
ingredients.map((ingredient: string) => (
|
||||
<li set:html={markdownToHtml(ingredient)}/>
|
||||
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 box-shadow"
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col gap-4 px-4"
|
||||
set:html={markdownToHtml(resource?.content?.description ?? "")}
|
||||
/>
|
||||
<h2 class="text-2xl px-4">Ingredients</h2>
|
||||
<ul class="list-disc px-10">
|
||||
{
|
||||
ingredients.filter((s:string) => !!s?.length).map((ingredient: string) => (
|
||||
<li set:html={markdownToHtml(ingredient)} />
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
|
||||
<h2 class="text-2xl">Steps</h2>
|
||||
<ol>
|
||||
<h2 class="text-2xl px-4">Steps</h2>
|
||||
<ol class="list-decimal px-10">
|
||||
{
|
||||
instructions.map((ingredient: string) => (
|
||||
<li set:html={markdownToHtml(ingredient)}/>
|
||||
instructions.filter((s:string) => !!s?.length).map((ingredient: string) => (
|
||||
<li set:html={markdownToHtml(ingredient)} />
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
|
||||
@@ -24,7 +24,7 @@ function formatRating(rating: string | number) {
|
||||
}
|
||||
alt="Cover for {resource?.content?.name}"
|
||||
class="rounded-2xl overflow-hidden"
|
||||
pictureClass="rounded-2xl w-1/2 mr-4 mb-4 float-left"
|
||||
pictureClass="rounded-2xl w-1/2 mr-4 mb-4 float-left box-shadow"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -23,26 +23,33 @@ export type MemoriumEntry = MemoriumFile | MemoriumDir;
|
||||
const SERVER_URL = "https://marka.max-richter.dev";
|
||||
//const SERVER_URL = "http://localhost:8080";
|
||||
|
||||
const cache = {};
|
||||
|
||||
export async function listResource(
|
||||
id: string,
|
||||
): Promise<MemoriumEntry | undefined> {
|
||||
const url = `${SERVER_URL}/resources/${id}`;
|
||||
if (cache[url]) return cache[url];
|
||||
try {
|
||||
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
|
||||
if (response.ok) {
|
||||
const json = await response.json();
|
||||
if (json.type == "dir") {
|
||||
return {
|
||||
const res = {
|
||||
...json,
|
||||
content: json.content.filter((res: MemoriumEntry) =>
|
||||
res.mime === "application/markdown"
|
||||
),
|
||||
};
|
||||
cache[url] = res;
|
||||
return res;
|
||||
}
|
||||
cache[url] = json;
|
||||
return json;
|
||||
}
|
||||
} catch (_e) {
|
||||
console.log("Failed to get: ", url);
|
||||
cache[url] = undefined;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@ p {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
@@ -103,14 +107,23 @@ picture.thumb-loading::before {
|
||||
header::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
right: 3rem;
|
||||
bottom: -10px;
|
||||
transform: rotate(90deg);
|
||||
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 249 249' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M248.5 0H0V249C0.268799 111.435 111.423 0 248.5 0Z' fill='white'/%3E%3C/svg%3E");
|
||||
bottom: -20px;
|
||||
background: var(--background-dark);
|
||||
mask-image: radial-gradient(circle at bottom left,
|
||||
transparent 19.5px,
|
||||
rgba(0, 0, 0, .5) 20px,
|
||||
#000 20.5px);
|
||||
box-shadow: 0px 0px 10px red;
|
||||
}
|
||||
|
||||
.dark header::before {
|
||||
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 249 249' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M248.5 0H0V249C0.268799 111.435 111.423 0 248.5 0Z' fill='%2316161e'/%3E%3C/svg%3E");
|
||||
.box-shadow {
|
||||
box-shadow:
|
||||
0px 1px 1px rgba(3, 7, 18, 0.02),
|
||||
0px 5px 4px rgba(3, 7, 18, 0.03),
|
||||
0px 12px 9px rgba(3, 7, 18, 0.05),
|
||||
0px 20px 15px rgba(3, 7, 18, 0.06),
|
||||
0px 32px 24px rgba(3, 7, 18, 0.08);
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
---
|
||||
|
||||
<Layout title="Home"> Sup people :) </Layout>
|
||||
@@ -3,7 +3,7 @@ import Layout from "@layouts/Layout.astro";
|
||||
import { useTranslatedPath } from "@i18n/utils";
|
||||
import ResourceDisplay from "@components/resources/Display.astro";
|
||||
import * as memorium from "@helpers/memorium";
|
||||
import { resources as resourceTypes } from "../resources.ts";
|
||||
import { resources as resourceTypes } from "@content/resources.ts";
|
||||
|
||||
const { resourceType, resourceName } = Astro?.params;
|
||||
|
||||
@@ -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: {
|
||||
@@ -48,7 +47,7 @@ const resource = await memorium.listResource(
|
||||
{
|
||||
resource?.content?.url && (
|
||||
<a class="flex gap-1 items-center" href={resource?.content?.url}>
|
||||
link
|
||||
source link
|
||||
<span class="inline-block w-3 h-3 i-tabler-external-link" />
|
||||
</a>
|
||||
)
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import HeroCard from "@components/HeroCard.astro";
|
||||
import * as memorium from "@helpers/memorium";
|
||||
import { resources as resourceTypes } from "../resources.ts";
|
||||
import { resources as resourceTypes } from "@content/resources.ts";
|
||||
import { useTranslations } from "@i18n/utils";
|
||||
import ClientSearch from "@components/ClientSearch.svelte";
|
||||
|
||||
const { resourceType } = Astro.params;
|
||||
|
||||
@@ -32,33 +33,55 @@ 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>
|
||||
{
|
||||
resources.content
|
||||
.filter((res: any) => isValidResource(res))
|
||||
.map((resource: any) => (
|
||||
<HeroCard
|
||||
post={{
|
||||
collection: "resources/" + resourceType,
|
||||
id: resource.name.replace(/\.md$/, ""),
|
||||
data: {
|
||||
title:
|
||||
resource?.content?.name ??
|
||||
resource?.content?.headline ??
|
||||
resource.content?.itemReviewed?.name,
|
||||
date: resource?.content?.datePublished,
|
||||
author: resource?.content?.author?.name,
|
||||
rating: resource?.content?.reviewRating?.ratingValue,
|
||||
cover: {
|
||||
src: memorium.getImageUrl(resource.content.image),
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
||||
<ClientSearch resourceType={resourceType} client:load />
|
||||
|
||||
<div id="resource-list-static" class="flex flex-col gap-6">
|
||||
{
|
||||
resources?.content
|
||||
.filter((res: any) => isValidResource(res))
|
||||
.map((resource: any) => (
|
||||
<div data-search-term={buildSearchTerm(resource?.content)}>
|
||||
<HeroCard
|
||||
post={{
|
||||
collection: "resources/" + resourceType,
|
||||
id: resource.name.replace(/\.md$/, ""),
|
||||
data: {
|
||||
title:
|
||||
resource?.content?.name ??
|
||||
resource?.content?.headline ??
|
||||
resource.content?.itemReviewed?.name,
|
||||
date: resource?.content?.datePublished,
|
||||
author: resource?.content?.author?.name,
|
||||
rating: resource?.content?.reviewRating?.ratingValue,
|
||||
cover: {
|
||||
src: memorium.getImageUrl(resource.content.image),
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
@@ -1,18 +1,53 @@
|
||||
---
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import HeroCard from "@components/HeroCard.astro";
|
||||
import { resources } from "./resources.ts";
|
||||
import {useTranslations} from "@i18n/utils";
|
||||
import { resources } from "@content/resources.ts";
|
||||
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={{
|
||||
...resource,
|
||||
body: t(`${resource.id}.description`),
|
||||
data: {
|
||||
cover:{src:resource.cover}, title: t(resource.id)}}} />
|
||||
)}
|
||||
{
|
||||
await Promise.all(
|
||||
resources.map(async (resource) => {
|
||||
const cover = await getCoverImage(resource.id);
|
||||
return (
|
||||
<HeroCard
|
||||
post={{
|
||||
...resource,
|
||||
body: t(`${resource.id}.description`),
|
||||
data: {
|
||||
cover: { src: cover },
|
||||
title: t(resource.id),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
)
|
||||
}
|
||||
</Layout>
|
||||
|
||||
Reference in New Issue
Block a user