Compare commits

..

2 Commits

Author SHA1 Message Date
Max Richter
712c6fe11e fix: dont try to use icons as img src
Some checks failed
Deploy to SFTP Server / build (push) Failing after 4m59s
2025-05-14 16:40:37 +02:00
Max Richter
8bd21a9a23 chore: pnpm up 2025-05-14 16:30:15 +02:00
23 changed files with 2583 additions and 1631 deletions

View File

@@ -53,6 +53,7 @@ jobs:
- name: 🏗️ Build site
run: |
# Install dependencies, build, and generate site output
pnpm i && pnpm build
- name: 🔑 Configure rclone

View File

@@ -17,12 +17,13 @@ const locales = {
const DEFAULT_LAYOUT = '@layouts/Post.astro';
function setDefaultLayout() {
return function(_, file) {
return function (_, file) {
const { frontmatter } = file.data.astro;
if (!frontmatter.layout) frontmatter.layout = DEFAULT_LAYOUT;
};
}
// https://astro.build/config
export default defineConfig({
site: "https://max-richter.dev",
trailingSlash: "never",
@@ -37,13 +38,6 @@ export default defineConfig({
compiler: 'svelte',
}),
],
server: {
watch: {
// Customize watch behavior to reduce file watchers
ignored: ['**/node_modules/**', '**/dist/**', '**/.git/**'],
usePolling: process.env.NODE_ENV === 'production',
},
},
},
markdown: {
remarkPlugins: [setDefaultLayout]

View File

@@ -11,32 +11,32 @@
},
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/mdx": "^4.3.6",
"@astrojs/svelte": "^7.2.0",
"@astrojs/mdx": "^4.2.6",
"@astrojs/svelte": "^7.0.13",
"@astrojs/tailwind": "^6.0.2",
"astro": "^5.14.1",
"astro": "^5.7.13",
"astro-i18n-aut": "^0.7.3",
"exifreader": "^4.32.0",
"svelte": "^5.39.8",
"svelte-gestures": "^5.2.2",
"tailwindcss": "^4.1.14",
"exifreader": "^4.30.1",
"svelte": "^5.28.6",
"svelte-gestures": "^5.1.4",
"tailwindcss": "^4.1.6",
"thumbhash": "^0.1.1",
"typescript": "^5.9.3"
"typescript": "^5.8.3"
},
"devDependencies": {
"@astrojs/sitemap": "^3.6.0",
"@iconify-json/tabler": "^1.2.23",
"@astrojs/sitemap": "^3.4.0",
"@iconify-json/tabler": "^1.2.17",
"@types/markdown-it": "^14.1.2",
"@unocss/preset-icons": "^66.5.2",
"@unocss/reset": "^66.5.2",
"@unocss/preset-icons": "^66.1.1",
"@unocss/reset": "^66.1.1",
"astro-font": "^1.1.0",
"markdown-it": "^14.1.0",
"ogl": "^1.0.11",
"prettier": "^3.6.2",
"prettier": "^3.5.3",
"prettier-plugin-astro": "^0.14.1",
"sharp": "^0.34.4",
"unocss": "^66.5.2",
"unplugin-icons": "^22.4.2",
"vite-plugin-glsl": "^1.5.4"
"sharp": "^0.34.1",
"unocss": "^66.1.1",
"unplugin-icons": "^22.1.0",
"vite-plugin-glsl": "^1.4.1"
}
}

2769
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +0,0 @@
onlyBuiltDependencies:
- esbuild
- exifreader
- sharp

View File

@@ -32,12 +32,12 @@ const link = translatePath(`/${collection}/${id.split("/")[0]}`);
<Card.Content classes="px-8 py-7 order-last xs:order-first">
<Card.Title classes="text-4xl flex items-center gap-2">
{
icon &&
(icon?.length > 5 ? (
<img class="h-6 w-6" src={icon} />
) : (
<span class="p-r-4 text-md">{icon}</span>
))
icon &&
(
icon?.length > 5
? <img class="h-6 w-6" src={icon} />
: <span class="p-r-4 text-md">{icon}</span>
)
}
{title}
</Card.Title>

View File

@@ -3,7 +3,7 @@ import type { ImageMetadata } from "astro";
import { Picture as AstroImage } from "astro:assets";
import { generateThumbHash, getExifData } from "@helpers/image";
interface Props {
src: ImageMetadata & { fsPath?: string };
src: ImageMetadata;
alt: string;
pictureClass?: string;
class?: string;
@@ -13,18 +13,6 @@ interface Props {
maxWidth?: number;
}
async function checkImage(src: string) {
try {
const res = await fetch(src);
if (res.ok) {
return src;
}
return undefined;
} catch (err) {
return undefined;
}
}
const {
src: image,
loader = true,
@@ -34,9 +22,7 @@ const {
maxWidth,
} = Astro.props;
let thumbhash = hash && image.fsPath ? await generateThumbHash(image) : "";
const imageSrc = await checkImage(image.src);
let thumbhash = hash ? await generateThumbHash(image) : "";
let exif = await getExifData(image);
@@ -59,23 +45,18 @@ const sizes = [
].filter((size) => !maxWidth || size.width <= maxWidth);
---
{
imageSrc ? (
<AstroImage
src={imageSrc}
alt={alt}
data-thumbhash={thumbhash}
data-exif={JSON.stringify(exif)}
inferSize={true}
pictureAttributes={{
class: `${hash ? "block h-full relative" : ""} ${loader ? "thumb" : ""} ${pictureClass}`,
}}
class={Astro.props.class}
widths={sizes.map((size) => size.width)}
sizes={sizes
.map((size) => `${size.media || "100vw"} ${size.width}px`)
.join(", ")}>
<slot />
</AstroImage>
) : undefined
}
<AstroImage
src={image}
alt={alt}
data-thumbhash={thumbhash}
data-exif={JSON.stringify(exif)}
pictureAttributes={{
class: `${hash ? "block h-full relative" : ""} ${loader ? "thumb" : ""} ${pictureClass}`,
}}
class={Astro.props.class}
widths={sizes.map((size) => size.width)}
sizes={sizes
.map((size) => `${size.media || "100vw"} ${size.width}px`)
.join(", ")}>
<slot />
</AstroImage>

View File

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

View File

@@ -24,8 +24,8 @@ const paths = [
text: t("nav.photos"),
},
{
link: translatePath("/resources"),
text: t("nav.resources"),
link: translatePath("/videos"),
text: t("nav.videos"),
},
];
---

View File

@@ -1,4 +1,4 @@
---
title: Resources
title: Videos
menu: nav
---

1036
src/helpers/exif_ Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ async function getSharp(): Promise<typeof import("sharp") | undefined> {
return s;
}
export async function generateThumbHash(image: ImageMetadata & { fsPath?: string }) {
export async function generateThumbHash(image: { width: number, height: number }) {
const sharp = await getSharp();
if (!sharp) return;
@@ -19,21 +19,16 @@ export async function generateThumbHash(image: ImageMetadata & { fsPath?: string
const smallWidth = Math.floor(image.width * scaleFactor);
const smallHeight = Math.floor(image.height * scaleFactor);
try {
const smallImg = await sharp(image.fsPath)
.resize(smallWidth, smallHeight)
.withMetadata()
.raw()
.ensureAlpha()
.toBuffer();
const buffer = rgbaToThumbHash(smallWidth, smallHeight, smallImg);
return Buffer.from(buffer).toString("base64");
} catch (error) {
console.log(`Could not generate thumbhash for ${image.fsPath}`, error)
return ""
}
//@ts-ignore
const smallImg = await sharp(image.fsPath)
.resize(smallWidth, smallHeight)
.withMetadata()
.raw()
.ensureAlpha()
.toBuffer();
const buffer = rgbaToThumbHash(smallWidth, smallHeight, smallImg);
return Buffer.from(buffer).toString("base64");
}
const allowedExif = [
@@ -53,15 +48,10 @@ const allowedExif = [
];
export async function getExifData(image: ImageMetadata) {
if (image.format === "svg") return undefined; // SVGs don't have EXIF data")
const sharp = await getSharp();
if (!sharp) return;
const imagePath = (image as ImageMetadata & { fsPath: string }).fsPath;
try {
const buffer = await sharp(imagePath).toBuffer();
const tags = await ExifReader.load(buffer, { async: true });
const tags = await ExifReader.load((image as ImageMetadata & { fsPath: string }).fsPath, { async: true });
const out: Record<string, any> = {};
let hasExif = false;
@@ -75,7 +65,7 @@ export async function getExifData(image: ImageMetadata) {
return hasExif ? out : undefined;
} catch (error) {
console.log(`Error reading EXIF data from ${imagePath}`, error);
console.log("Error reading EXIF data", error);
return undefined
}

View File

@@ -1,47 +0,0 @@
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 {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
if (json.type == "dir") {
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

@@ -20,7 +20,7 @@ export const ui = {
'home.subtitle': 'Trained Media Designer, Blender Nerd, Developer and Hardware Tinkerer.',
'nav.blog': 'Blog',
'nav.projects': 'Projects',
'nav.resources': 'Resources',
'nav.videos': 'Videos',
'nav.photos': 'Photos',
'toc.title': 'Table of Contents',
"resume": "Resume",
@@ -38,7 +38,7 @@ export const ui = {
'home.subtitle': 'Ausgebildeter Mediengestalter, Blender Nerd, Entwickler und Hardware Bastler.',
'nav.blog': 'Blog',
'nav.projects': 'Projekte',
'nav.resources': 'Resources',
'nav.videos': 'Videos',
'nav.photos': 'Fotos',
"resume": "Lebenslauf",
'toc.title': 'Inhaltsverzeichnis',

View File

@@ -16,10 +16,11 @@ const { frontmatter, headings } = Astro.props;
const t = useTranslations(Astro.url);
const { title, url, date: dateString, links, toc, cover } = frontmatter;
const collection = url?.split("/")[2];
//@ts-ignore
const backlinkContent = t(`nav.${collection}`).toLowerCase();
const date = new Date(dateString);
const path = useTranslatedPath(Astro.url);
//@ts-ignore
const backlinkContent = t(`nav.${collection}`).toLowerCase();
---
<Layout title={title} image={cover}>

View File

@@ -41,6 +41,10 @@ const list = [...posts, ...photos];
list.sort((a, b) => {
return a.data.date > b.data.date ? -1 : 1;
});
// .sort((a, b) => {
// return a.data.date.getDate() > b.data.date.getDate() ? 1 : -1;
// });
console.log(list.map((post) => [post.data.date, post.data.title]));
const featuredPost = list.find((post) => post.data?.featured);
const otherPosts = list.filter((post) => featuredPost !== post).slice(0, 3);

View File

@@ -1,65 +0,0 @@
---
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

@@ -1,37 +0,0 @@
---
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,9 +0,0 @@
---
import Layout from "@layouts/Layout.astro";
import HeroCard from "@components/HeroCard.astro";
import { resources } from "./resources.ts";
---
<Layout title="Max Richter">
{resources.map((resource) => <HeroCard post={resource} />)}
</Layout>

View File

@@ -1,63 +0,0 @@
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,10 +1,5 @@
import { vitePreprocess } from "@astrojs/svelte";
import { vitePreprocess } from '@astrojs/svelte';
export default {
preprocess: vitePreprocess(),
compilerOptions: {
experimental: {
async: true,
},
},
};
}

View File

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

View File

@@ -1,10 +1,10 @@
// uno.config.ts
import { defineConfig, presetWind3 } from 'unocss'
import { defineConfig, presetUno } from 'unocss'
import presetIcons from '@unocss/preset-icons'
export default defineConfig({
presets: [
presetWind3(),
presetUno(),
presetIcons(),
],
shortcuts: {