Compare commits

..

3 Commits

Author SHA1 Message Date
Max Richter
06e5126fe0 refactor: use more generic resource system
Some checks failed
Deploy to SFTP Server / build (push) Failing after 7m2s
2025-10-06 00:30:44 +02:00
Max Richter
61251e2c85 update
Some checks failed
Deploy to SFTP Server / build (push) Has been cancelled
2025-10-04 14:07:46 +02:00
Max Richter
a1b8eb22e5 fix: some type errors
Some checks failed
Deploy to SFTP Server / build (push) Failing after 6m48s
2025-10-04 13:39:06 +02:00
17 changed files with 1401 additions and 1705 deletions

View File

@@ -11,32 +11,32 @@
}, },
"dependencies": { "dependencies": {
"@astrojs/check": "^0.9.4", "@astrojs/check": "^0.9.4",
"@astrojs/mdx": "^4.3.1", "@astrojs/mdx": "^4.3.6",
"@astrojs/svelte": "^7.1.0", "@astrojs/svelte": "^7.2.0",
"@astrojs/tailwind": "^6.0.2", "@astrojs/tailwind": "^6.0.2",
"astro": "^5.12.0", "astro": "^5.14.1",
"astro-i18n-aut": "^0.7.3", "astro-i18n-aut": "^0.7.3",
"exifreader": "^4.31.1", "exifreader": "^4.32.0",
"svelte": "^5.36.10", "svelte": "^5.39.8",
"svelte-gestures": "^5.1.4", "svelte-gestures": "^5.2.2",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.14",
"thumbhash": "^0.1.1", "thumbhash": "^0.1.1",
"typescript": "^5.8.3" "typescript": "^5.9.3"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/sitemap": "^3.4.1", "@astrojs/sitemap": "^3.6.0",
"@iconify-json/tabler": "^1.2.19", "@iconify-json/tabler": "^1.2.23",
"@types/markdown-it": "^14.1.2", "@types/markdown-it": "^14.1.2",
"@unocss/preset-icons": "^66.3.3", "@unocss/preset-icons": "^66.5.2",
"@unocss/reset": "^66.3.3", "@unocss/reset": "^66.5.2",
"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.3", "sharp": "^0.34.4",
"unocss": "^66.3.3", "unocss": "^66.5.2",
"unplugin-icons": "^22.1.0", "unplugin-icons": "^22.4.2",
"vite-plugin-glsl": "^1.5.1" "vite-plugin-glsl": "^1.5.4"
} }
} }

2476
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,16 +13,15 @@ interface Props {
maxWidth?: number; maxWidth?: number;
} }
async function checkImage(src:string){ async function checkImage(src: string) {
try { try {
const res = await fetch(src); const res = await fetch(src);
if (res.ok) { if (res.ok) {
return src return src;
} }
return undefined return undefined;
}catch(err){ } catch (err) {
console.log({src}); return undefined;
return undefined
} }
} }
@@ -37,7 +36,7 @@ const {
let thumbhash = hash && image.fsPath ? await generateThumbHash(image) : ""; let thumbhash = hash && image.fsPath ? await generateThumbHash(image) : "";
const imageSrc = await checkImage(image); const imageSrc = await checkImage(image.src);
let exif = await getExifData(image); let exif = await getExifData(image);
@@ -60,8 +59,9 @@ const sizes = [
].filter((size) => !maxWidth || size.width <= maxWidth); ].filter((size) => !maxWidth || size.width <= maxWidth);
--- ---
{imageSrc? {
<AstroImage imageSrc ? (
<AstroImage
src={imageSrc} src={imageSrc}
alt={alt} alt={alt}
data-thumbhash={thumbhash} data-thumbhash={thumbhash}
@@ -76,5 +76,6 @@ const sizes = [
.map((size) => `${size.media || "100vw"} ${size.width}px`) .map((size) => `${size.media || "100vw"} ${size.width}px`)
.join(", ")}> .join(", ")}>
<slot /> <slot />
</AstroImage> </AstroImage>
:undefined } ) : undefined
}

View File

@@ -16,7 +16,7 @@
let progress: number[] = []; let progress: number[] = [];
let currentIndex = -1; let currentIndex = -1;
const maxZoom = 5; const maxZoom = 5;
import { swipe } from "svelte-gestures"; import { useSwipe } from "svelte-gestures";
const mod = (a: number, b: number) => ((a % b) + b) % b; const mod = (a: number, b: number) => ((a % b) + b) % b;
@@ -232,10 +232,9 @@
{#if currentIndex > -1} {#if currentIndex > -1}
<div <div
{...useSwipe(handleSwipe)}
class="image" class="image"
use:swipe
role="dialog" role="dialog"
on:swipe={handleSwipe}
on:wheel|passive={handleScroll} on:wheel|passive={handleScroll}
on:mousemove={handleMouseMove} on:mousemove={handleMouseMove}
on:pointermove={handlePointerMove}> on:pointermove={handlePointerMove}>

View File

@@ -1,10 +1,47 @@
export async function listResource(id: string): Promise<any[]> { export type MemoriumFile = {
type: "file";
name: string;
path: string;
modTime: string;
mime: string;
size: string;
content: unknown;
};
export type MemoriumDir = {
type: "dir";
name: string;
path: string;
modTime: string;
mime: string;
size: string;
content: MemoriumEntry[];
};
export type MemoriumEntry = MemoriumFile | MemoriumDir;
export async function listResource(
id: string,
): Promise<MemoriumEntry | undefined> {
const url = `https://marka.max-richter.dev/${id}`;
console.log("Fetching: ", url);
try { try {
const response = await fetch( const response = await fetch(url);
`https://marka.max-richter.dev/${id}`, if (response.ok) {
); const json = await response.json();
return await response.json(); if (json.type == "dir") {
} catch (error) { return {
return []; ...json,
content: json.content.filter((res) =>
res.mime === "application/markdown"
),
};
}
return json;
}
} catch (_e) {
console.log("Failed to get: ", url);
console.log(_e);
return;
} }
} }

View File

@@ -1,38 +0,0 @@
---
import Layout from "@layouts/Layout.astro";
import * as memorium from "@helpers/memorium";
export async function getStaticPaths() {
const movieReviews = await memorium.listResource("Media/movies/*");
const paths = movieReviews.map((review: any) => {
return {
params: {
movieName: review.identifier
.replace("Media/movies/", "")
.replace(/\.md$/, ""),
},
};
});
return paths;
}
const reviews = await memorium.listResource(
//@ts-ignore
`Media/movies/${Astro.params.movieName}.md`,
);
if (reviews.length === 0) {
return new Response(null, {
status: 404,
statusText: "Not found",
});
}
const review = reviews[0];
---
<Layout title="Max Richter">
<h1>{review.itemReviewed?.name}</h1>
<p>{review.reviewBody}</p>
<!-- <pre><code>{JSON.stringify(review, null, 2)}</code></pre> -->
</Layout>

View File

@@ -1,27 +0,0 @@
---
import Layout from "@layouts/Layout.astro";
import HeroCard from "@components/HeroCard.astro";
import * as memorium from "@helpers/memorium";
const movieReviews = await memorium.listResource("Media/movies/*");
---
<Layout title="Max Richter">
{
movieReviews.map((review: any) => (
<HeroCard
post={{
collection: "resources/movies",
id: review.identifier
.replace("Media/movies/", "")
.replace(/\.md$/, ""),
data: {
title: review.itemReviewed.name,
description: review.reviewBody,
},
body: review.reviewBody,
}}
/>
))
}
</Layout>

View File

@@ -1,73 +0,0 @@
---
import Layout from "@layouts/Layout.astro";
import { useTranslatedPath } from "@i18n/utils";
import markdownToText from "@helpers/markdownToText";
import * as memorium from "@helpers/memorium";
const path = useTranslatedPath(Astro.url);
const collection = "resources/Recipes";
export async function getStaticPaths() {
const recipes = await memorium.listResource("Recipes");
const paths = recipes.content.filter(res =>res.content).map((recipe: any) => {
return {
params: {
recipeName: recipe.name.replace(/\.md$/, ""),
},
};
});
return paths;
}
const recipeResponse = await memorium.listResource(
//@ts-ignore
`Recipes/${Astro.params.recipeName}.md`,
);
if (!recipeResponse?.content) {
return new Response(null, {
status: 404,
statusText: "Not found",
});
}
const recipe = recipeResponse.content;
---
<Layout title="Max Richter">
<div class="top-info flex items-center place-content-between m-y-2">
<a class="flex items-center gap-1 opacity-50" href={path("/" + collection)}>
<span class="i-tabler-arrow-left"></span> back
</a>
<div class="date opacity-50">
{
recipe.date?.toLocaleString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
})
}
</div>
</div>
<h1>{recipe.name}</h1>
<h3>Ingredients</h3>
<ul>
{
recipe.recipeIngredient?.map((ingredient: any) => (
<li>{markdownToText(ingredient??"")}</li>
))
}
</ul>
<h3>Instructions</h3>
<ol>
{
recipe.recipeIngredient?.map((ingredient: any) => (
<li>{markdownToText(ingredient??"")}</li>
))
}
</ol>
</Layout>

View File

@@ -1,32 +0,0 @@
---
import Layout from "@layouts/Layout.astro";
import HeroCard from "@components/HeroCard.astro";
import * as memorium from "@helpers/memorium";
const recipes = await memorium.listResource("Recipes");
---
<Layout title="Max Richter">
{
recipes.content
.filter((res) => res.content)
.map((recipe: any) => (
<HeroCard
post={{
collection: "resources/Recipes",
id: recipe.name.replace(/\.md$/, ""),
data: {
cover: recipe.content?.image
? `http://localhost:8080/${recipe.content.image}`
: undefined,
title: recipe.name.replace(/\.md$/, ""),
},
}}
/>
))
}
</Layout>
/>
))
}
</Layout>

View File

@@ -1,58 +0,0 @@
---
import Layout from "@layouts/Layout.astro";
import { useTranslatedPath } from "@i18n/utils";
import * as memorium from "@helpers/memorium";
const collection = "resources/series";
const path = useTranslatedPath(Astro.url);
export async function getStaticPaths() {
const seriesReviews = await memorium.listResource("Media/series/*");
const paths = seriesReviews.map((review: any) => {
return {
params: {
seriesName: review.identifier
.replace("Media/series/", "")
.replace(/\.md$/, ""),
},
};
});
return paths;
}
const reviews = await memorium.listResource(
//@ts-ignore
`Media/series/${Astro.params.seriesName}.md`,
);
if (reviews.length === 0) {
return new Response(null, {
status: 404,
statusText: "Not found",
});
}
const review = reviews[0];
---
<Layout title="Max Richter">
<div class="top-info flex items-center place-content-between m-y-2">
<a class="flex items-center gap-1 opacity-50" href={path("/" + collection)}>
<span class="i-tabler-arrow-left"></span> back
</a>
<div class="date opacity-50">
{
review.date?.toLocaleString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
})
}
</div>
</div>
<h1>{review.itemReviewed?.name}</h1>
<p>{review.reviewBody}</p>
</Layout>

View File

@@ -1,27 +0,0 @@
---
import Layout from "@layouts/Layout.astro";
import HeroCard from "@components/HeroCard.astro";
import * as memorium from "@helpers/memorium";
const seriesReviewes = await memorium.listResource("Media/series/*");
---
<Layout title="Max Richter">
{
seriesReviewes.map((review: any) => (
<HeroCard
post={{
collection: "resources/series",
id: review.identifier
.replace("Media/series/", "")
.replace(/\.md$/, ""),
data: {
title: review.itemReviewed.name,
description: review.reviewBody,
},
body: review.reviewBody,
}}
/>
))
}
</Layout>

View File

@@ -0,0 +1,65 @@
---
import Layout from "@layouts/Layout.astro";
import { useTranslatedPath } from "@i18n/utils";
import * as memorium from "@helpers/memorium";
import { resources as resourceTypes } from "../resources.ts";
import markdownToText from "@helpers/markdownToText";
const { resourceType, resourceName } = Astro.params;
const path = useTranslatedPath(Astro.url);
export async function getStaticPaths(props) {
const paths = await Promise.all(resourceTypes.map(async (resourceType: string) => {
const resources = await memorium.listResource(resourceType.id);
return resources.content.map((res: any) => {
return {
params: {
resourceType: resourceType.id,
resourceName: res.name.replace(/\.md$/,"")
},
};
});
}));
const flat = paths.flat();
console.log(flat.map(p => `/resources/${p.params.resourceType}/${p.params.resourceName}`))
return flat;
}
const resource = await memorium.listResource(
`/${resourceType}/${resourceName}.md`
);
---
<Layout title="Max Richter">
<div class="top-info flex items-center place-content-between m-y-2">
<a class="flex items-center gap-1 opacity-50" href={path("/resources/"+resourceType)}>
<span class="i-tabler-arrow-left"></span> back
</a>
<div class="date opacity-50">
{
resource?.content.date?.toLocaleString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
})
}
</div>
</div>
<h1>{resource?.content?.name}</h1>
<p>{resource?.content?.description}</p>
<h2>Ingredients</h2>
<ul>
{resource.content.recipeIngredient.map(ingredient => <li>{markdownToText(ingredient)}</li>)}
</ul>
<h2>Steps</h2>
<ol>
{resource.content.recipeInstructions.map(ingredient => <li>{markdownToText(ingredient)}</li>)}
</ol>
</Layout>

View File

@@ -0,0 +1,37 @@
---
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";
const { resourceType } = Astro.params;
const resources = await memorium.listResource(resourceType);
export async function getStaticPaths(): string[] {
return resourceTypes.map((type: any) => {
return {
params: {
resourceType: type.id,
resourceName: "Recipe"
},
};
});
}
---
<Layout title="Max Richter">
{ resources.content.filter(res => res && res?.content && res?.content?.name).map((resource: any) => (
<HeroCard
post={{
collection: "resources/"+resourceType,
id: resource.name.replace(/\.md$/,""),
data: {
title: resource.content.name,
cover: {src:`https://marka.max-richter.dev/${resource.content.image}`}
},
}}
/>
))
}
</Layout>

View File

@@ -1,64 +1,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 { resources } from "./resources.ts";
const collection = "resources";
const wiki = {
id: "wiki",
collection,
body: "My knowledge base",
data: {
title: "Wiki",
icon: "🧠",
},
};
const articles = {
id: "Articles",
collection,
body: "Articles saved",
data: {
title: "Articles",
icon: "📰",
},
};
const recipes = {
id: "Recipes",
collection,
body: "Recipes",
data: {
title: "Recipes",
icon: "🍲",
},
};
const movies = {
id: "Movies",
collection,
body: "Movies",
data: {
title: "Movies",
icon: "🎥",
},
};
const series = {
id: "Series",
collection,
body: "Series",
data: {
title: "Series",
icon: "📺",
},
};
--- ---
<Layout title="Max Richter"> <Layout title="Max Richter">
<HeroCard post={wiki} /> {resources.map((resource) => <HeroCard post={resource} />)}
<HeroCard post={recipes} />
<HeroCard post={articles} />
<HeroCard post={movies} />
<HeroCard post={series} />
</Layout> </Layout>

View File

@@ -0,0 +1,63 @@
const collection = "resources";
type Resource = {
id: string;
collection: string;
body: string;
data: {
title: string;
icon: string;
};
};
// const wiki = {
// id: "wiki",
// collection,
// body: "My knowledge base",
// data: {
// title: "Wiki",
// icon: "🧠",
// },
// };
// const articles = {
// id: "Articles",
// collection,
// body: "Articles saved",
// data: {
// title: "Articles",
// icon: "📰",
// },
// };
const recipes = {
id: "Recipes",
collection,
body: "Recipes",
data: {
title: "Recipes",
icon: "🍲",
},
};
// const movies = {
// id: "Movies",
// collection,
// body: "Movies",
// data: {
// title: "Movies",
// icon: "🎥",
// },
// };
// const series = {
// id: "Series",
// collection,
// body: "Series",
// data: {
// title: "Series",
// icon: "📺",
// },
// };
export const resources: Resource[] = [recipes];

View File

@@ -1,5 +1,10 @@
import { vitePreprocess } from '@astrojs/svelte'; import { vitePreprocess } from "@astrojs/svelte";
export default { export default {
preprocess: vitePreprocess(), preprocess: vitePreprocess(),
} compilerOptions: {
experimental: {
async: true,
},
},
};

View File

@@ -11,7 +11,8 @@
"baseUrl": ".", "baseUrl": ".",
"types": [ "types": [
"vite-plugin-glsl/ext", "vite-plugin-glsl/ext",
"unplugin-icons/types" "unplugin-icons/types",
"svelte-gestures/globals"
], ],
"paths": { "paths": {
"@components/*": [ "@components/*": [