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: {
|
server: {
|
||||||
watch: {
|
watch: {
|
||||||
// Customize watch behavior to reduce file watchers
|
|
||||||
ignored: ["**/node_modules/**", "**/dist/**", "**/.git/**"],
|
ignored: ["**/node_modules/**", "**/dist/**", "**/.git/**"],
|
||||||
usePolling: process.env.NODE_ENV === "production",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
18
package.json
18
package.json
@@ -11,15 +11,15 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.5",
|
"@astrojs/check": "^0.9.5",
|
||||||
"@astrojs/mdx": "^4.3.7",
|
"@astrojs/mdx": "^4.3.9",
|
||||||
"@astrojs/svelte": "^7.2.0",
|
"@astrojs/svelte": "^7.2.1",
|
||||||
"@astrojs/tailwind": "^6.0.2",
|
"@astrojs/tailwind": "^6.0.2",
|
||||||
"astro": "^5.14.8",
|
"astro": "^5.15.2",
|
||||||
"astro-i18n-aut": "^0.7.3",
|
"astro-i18n-aut": "^0.7.3",
|
||||||
"exifreader": "^4.32.0",
|
"exifreader": "^4.32.0",
|
||||||
"svelte": "^5.39.8",
|
"svelte": "^5.42.3",
|
||||||
"svelte-gestures": "^5.2.2",
|
"svelte-gestures": "^5.2.2",
|
||||||
"tailwindcss": "^4.1.14",
|
"tailwindcss": "^4.1.16",
|
||||||
"thumbhash": "^0.1.1",
|
"thumbhash": "^0.1.1",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
@@ -27,16 +27,16 @@
|
|||||||
"@astrojs/sitemap": "^3.6.0",
|
"@astrojs/sitemap": "^3.6.0",
|
||||||
"@iconify-json/tabler": "^1.2.23",
|
"@iconify-json/tabler": "^1.2.23",
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@types/markdown-it": "^14.1.2",
|
||||||
"@unocss/preset-icons": "^66.5.2",
|
"@unocss/preset-icons": "^66.5.4",
|
||||||
"@unocss/reset": "^66.5.2",
|
"@unocss/reset": "^66.5.4",
|
||||||
"astro-font": "^1.1.0",
|
"astro-font": "^1.1.0",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"ogl": "^1.0.11",
|
"ogl": "^1.0.11",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
"sharp": "^0.34.4",
|
"sharp": "^0.34.4",
|
||||||
"unocss": "^66.5.2",
|
"unocss": "^66.5.4",
|
||||||
"unplugin-icons": "^22.4.2",
|
"unplugin-icons": "^22.5.0",
|
||||||
"vite-plugin-glsl": "^1.5.4"
|
"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 t = useTranslations(Astro.url);
|
||||||
|
|
||||||
const link = translatePath(`/${collection}/${id.split("/")[0]}`);
|
const link = translatePath(`/${collection}/${id.split("/")[0]}`);
|
||||||
|
|
||||||
|
const image = cover as unknown;
|
||||||
|
|
||||||
|
const hasCover = typeof image === "string" ? !!image?.length : !!cover?.src;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Card
|
<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">
|
<Card.Content classes="px-8 py-7 order-last xs:order-first">
|
||||||
{
|
{
|
||||||
(date || body || rating !== undefined) && (
|
(date || body || rating !== undefined) && (
|
||||||
@@ -57,14 +61,15 @@ const link = translatePath(`/${collection}/${id.split("/")[0]}`);
|
|||||||
<Card.ReadMoreButton link={link} text={t("read-more")} />
|
<Card.ReadMoreButton link={link} text={t("read-more")} />
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
{
|
{
|
||||||
cover && (
|
hasCover && (
|
||||||
<a href={link}>
|
<a href={link}>
|
||||||
<Image
|
<Image
|
||||||
hash
|
hash
|
||||||
loader={false}
|
loader={false}
|
||||||
src={cover as ImageMetadata}
|
src={cover as ImageMetadata}
|
||||||
alt={"cover for " + title}
|
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
|
thumbnail
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -15,23 +15,25 @@ interface Props {
|
|||||||
thumbnail?: boolean;
|
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;
|
const src = typeof image === "string" ? image : image.src;
|
||||||
if (!src) return false;
|
if (!src) return;
|
||||||
try {
|
try {
|
||||||
if (src.startsWith("/@fs") || src.startsWith("/_astro")) return true;
|
if (src.startsWith("/@fs") || src.startsWith("/_astro")) return image;
|
||||||
const res = await inferRemoteSize(src);
|
const res = await inferRemoteSize(src);
|
||||||
if (res.format) {
|
if (res.format) {
|
||||||
image.format = res.format;
|
image.format = res.format;
|
||||||
return true;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to load: ", src);
|
console.log("Failed to load: ", src);
|
||||||
}
|
}
|
||||||
return false;
|
return;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("\n");
|
console.log("\n");
|
||||||
console.log("Failed to fetch: ", src);
|
console.log("Failed to fetch: ", src);
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +72,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);
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -81,16 +83,19 @@ const sizes = thumbnail
|
|||||||
alt={alt}
|
alt={alt}
|
||||||
data-thumbhash={thumbhash}
|
data-thumbhash={thumbhash}
|
||||||
data-exif={JSON.stringify(exif)}
|
data-exif={JSON.stringify(exif)}
|
||||||
inferSize={true}
|
width={imageOk?.width}
|
||||||
|
height={imageOk?.height}
|
||||||
pictureAttributes={{
|
pictureAttributes={{
|
||||||
class: `${hash ? "block h-full relative" : ""} ${loader ? "thumb" : ""} ${pictureClass}`,
|
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)}
|
widths={sizes.map((size) => size.width)}
|
||||||
sizes={sizes
|
sizes={sizes
|
||||||
.map((size) => `${size.media || "100vw"} ${size.width}px`)
|
.map((size) => `${size.media || "100vw"} ${size.width}px`)
|
||||||
.join(", ")}>
|
.join(", ")}>
|
||||||
<slot />
|
<slot />
|
||||||
</AstroImage>
|
</AstroImage>
|
||||||
) : undefined
|
) : (
|
||||||
|
<div>{JSON.stringify({ "imageOk":imageOk, image })}</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,14 @@
|
|||||||
let height: number;
|
let height: number;
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
|
||||||
function hide(img: HTMLPictureElement) {
|
function hide(index: number) {
|
||||||
|
const img = images[index];
|
||||||
img.classList.remove("active");
|
img.classList.remove("active");
|
||||||
}
|
}
|
||||||
|
|
||||||
function show(img: HTMLPictureElement) {
|
const heightCache = [];
|
||||||
|
function show(index: number) {
|
||||||
|
const img = images[index];
|
||||||
img.classList.add("active");
|
img.classList.add("active");
|
||||||
const _img = img.querySelector("img") || img;
|
const _img = img.querySelector("img") || img;
|
||||||
if (!_img) return;
|
if (!_img) return;
|
||||||
@@ -21,9 +24,12 @@
|
|||||||
_img.style.opacity = "1";
|
_img.style.opacity = "1";
|
||||||
});
|
});
|
||||||
altText = _img["alt"] ?? _img.getAttribute("alt") ?? "";
|
altText = _img["alt"] ?? _img.getAttribute("alt") ?? "";
|
||||||
height = _img.getBoundingClientRect().height;
|
if (heightCache[index]) {
|
||||||
|
height = heightCache[index];
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
height = _img.getBoundingClientRect().height;
|
height = heightCache[index] ?? _img.getBoundingClientRect().height;
|
||||||
|
heightCache[index] = height;
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,16 +37,16 @@
|
|||||||
function setIndex(i: number) {
|
function setIndex(i: number) {
|
||||||
if (i < 0) i = images.length - 1;
|
if (i < 0) i = images.length - 1;
|
||||||
if (i >= images.length) i = 0;
|
if (i >= images.length) i = 0;
|
||||||
hide(images[index]);
|
hide(index);
|
||||||
index = i;
|
index = i;
|
||||||
show(images[index]);
|
show(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (slot && !images?.length) {
|
$: if (slot && !images?.length) {
|
||||||
images = Array.from(slot.querySelectorAll("picture"));
|
images = Array.from(slot.querySelectorAll("picture"));
|
||||||
if (images?.length) {
|
if (images?.length) {
|
||||||
images.forEach(hide);
|
images.forEach((_, i) => hide(i));
|
||||||
show(images[index]);
|
show(index);
|
||||||
images[index].onload = () => {
|
images[index].onload = () => {
|
||||||
loaded = true;
|
loaded = true;
|
||||||
height = images[index].getBoundingClientRect().height;
|
height = images[index].getBoundingClientRect().height;
|
||||||
@@ -54,11 +60,10 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="wrapper grid overflow-hidden rounded-xl border border-neutral"
|
class="wrapper grid overflow-hidden rounded-xl border border-neutral"
|
||||||
class:title
|
class:title={true}
|
||||||
class:not-loaded={!loaded}
|
class:not-loaded={!loaded}
|
||||||
class:loaded
|
class:loaded
|
||||||
style={`--height:${height}px`}>
|
style={`--height:${height}px`}>
|
||||||
{#if title}
|
|
||||||
<div class="flex items-center p-x-4 p-y-6 bg justify-between">
|
<div class="flex items-center p-x-4 p-y-6 bg justify-between">
|
||||||
<h3>{title}</h3>
|
<h3>{title}</h3>
|
||||||
|
|
||||||
@@ -74,9 +79,8 @@
|
|||||||
on:click={() => setIndex(index + 1)}></button>
|
on:click={() => setIndex(index + 1)}></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<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 />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 flex items-center place-content-between bg">
|
<div class="px-4 flex items-center place-content-between bg">
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,7 +34,8 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex gap-3 wrapper">
|
{#if rating || date || readDuration || author}
|
||||||
|
<div class="flex flex-wrap gap-3 wrapper">
|
||||||
{#if rating}
|
{#if rating}
|
||||||
<div class="text-sm bg-light">{formatRating(rating)}</div>
|
<div class="text-sm bg-light">{formatRating(rating)}</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -49,7 +50,7 @@
|
|||||||
<div class="text-sm bg-light">{author}</div>
|
<div class="text-sm bg-light">{author}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.wrapper > * {
|
.wrapper > * {
|
||||||
@@ -57,5 +58,6 @@
|
|||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,6 +6,6 @@
|
|||||||
<a
|
<a
|
||||||
href={link}
|
href={link}
|
||||||
data-astro-prefetch
|
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>
|
>{text}<span class="i-tabler-arrow-right inline-block w-4 h-4"></span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -13,12 +13,17 @@ 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 box-shadow"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div set:html={markdownToHtml(resource?.content?.articleBody)} />
|
<div
|
||||||
|
class="flex flex-col gap-4"
|
||||||
|
set:html={markdownToHtml(resource?.content?.articleBody)}
|
||||||
|
/>
|
||||||
|
|||||||
@@ -4,29 +4,44 @@ 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" />}
|
|
||||||
</div>
|
|
||||||
<p>{resource?.content?.description}</p>
|
|
||||||
<h2 class="text-2xl">Ingredients</h2>
|
|
||||||
<ul>
|
|
||||||
{
|
{
|
||||||
ingredients.map((ingredient: string) => (
|
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)} />
|
<li set:html={markdownToHtml(ingredient)} />
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 class="text-2xl">Steps</h2>
|
<h2 class="text-2xl px-4">Steps</h2>
|
||||||
<ol>
|
<ol class="list-decimal px-10">
|
||||||
{
|
{
|
||||||
instructions.map((ingredient: string) => (
|
instructions.filter((s:string) => !!s?.length).map((ingredient: string) => (
|
||||||
<li set:html={markdownToHtml(ingredient)} />
|
<li set:html={markdownToHtml(ingredient)} />
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function formatRating(rating: string | number) {
|
|||||||
}
|
}
|
||||||
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 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/>
|
<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"/>
|
||||||
|
|
||||||
|
|||||||
@@ -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"/>
|
||||||
|
|||||||
@@ -23,26 +23,33 @@ export type MemoriumEntry = MemoriumFile | MemoriumDir;
|
|||||||
const SERVER_URL = "https://marka.max-richter.dev";
|
const SERVER_URL = "https://marka.max-richter.dev";
|
||||||
//const SERVER_URL = "http://localhost:8080";
|
//const SERVER_URL = "http://localhost:8080";
|
||||||
|
|
||||||
|
const cache = {};
|
||||||
|
|
||||||
export async function listResource(
|
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}`;
|
||||||
|
if (cache[url]) return cache[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) {
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json.type == "dir") {
|
if (json.type == "dir") {
|
||||||
return {
|
const res = {
|
||||||
...json,
|
...json,
|
||||||
content: json.content.filter((res: MemoriumEntry) =>
|
content: json.content.filter((res: MemoriumEntry) =>
|
||||||
res.mime === "application/markdown"
|
res.mime === "application/markdown"
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
cache[url] = res;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
cache[url] = json;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
console.log("Failed to get: ", url);
|
console.log("Failed to get: ", url);
|
||||||
|
cache[url] = undefined;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ p {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
h3 {
|
h3 {
|
||||||
@@ -103,14 +107,23 @@ picture.thumb-loading::before {
|
|||||||
header::before {
|
header::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 10px;
|
width: 20px;
|
||||||
height: 10px;
|
height: 20px;
|
||||||
right: 3rem;
|
right: 3rem;
|
||||||
bottom: -10px;
|
bottom: -20px;
|
||||||
transform: rotate(90deg);
|
background: var(--background-dark);
|
||||||
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");
|
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 {
|
.box-shadow {
|
||||||
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:
|
||||||
|
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 { useTranslatedPath } from "@i18n/utils";
|
||||||
import ResourceDisplay from "@components/resources/Display.astro";
|
import ResourceDisplay from "@components/resources/Display.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 "@content/resources.ts";
|
||||||
|
|
||||||
const { resourceType, resourceName } = Astro?.params;
|
const { resourceType, resourceName } = Astro?.params;
|
||||||
|
|
||||||
@@ -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: {
|
||||||
@@ -48,7 +47,7 @@ const resource = await memorium.listResource(
|
|||||||
{
|
{
|
||||||
resource?.content?.url && (
|
resource?.content?.url && (
|
||||||
<a class="flex gap-1 items-center" href={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" />
|
<span class="inline-block w-3 h-3 i-tabler-external-link" />
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
import Layout from "@layouts/Layout.astro";
|
import Layout from "@layouts/Layout.astro";
|
||||||
import HeroCard from "@components/HeroCard.astro";
|
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 "@content/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,35 @@ 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 +80,8 @@ function isValidResource(res: any) {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -1,18 +1,53 @@
|
|||||||
---
|
---
|
||||||
import Layout from "@layouts/Layout.astro";
|
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 "@content/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);
|
||||||
|
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>
|
||||||
|
|||||||
Reference in New Issue
Block a user