Compare commits
14 Commits
19a703367d
...
main
Author | SHA1 | Date | |
---|---|---|---|
546b36f44f | |||
|
f84704d20b
|
||
|
666b618a73
|
||
|
b32ca7d65e | ||
|
c74b314b1e | ||
|
50ce8b3ff7 | ||
|
8e293c204d | ||
|
972c2382f3 | ||
|
e0543f2a58 | ||
|
3d78b9e56c | ||
|
84e56f2668 | ||
|
8af8db0714 | ||
59eeadd4b3 | |||
6aa6ddabb0 |
1
.github/workflows/default.yaml
vendored
1
.github/workflows/default.yaml
vendored
@@ -53,7 +53,6 @@ jobs:
|
|||||||
|
|
||||||
- name: 🏗️ Build site
|
- name: 🏗️ Build site
|
||||||
run: |
|
run: |
|
||||||
# Install dependencies, build, and generate site output
|
|
||||||
pnpm i && pnpm build
|
pnpm i && pnpm build
|
||||||
|
|
||||||
- name: 🔑 Configure rclone
|
- name: 🔑 Configure rclone
|
||||||
|
@@ -17,13 +17,12 @@ const locales = {
|
|||||||
const DEFAULT_LAYOUT = '@layouts/Post.astro';
|
const DEFAULT_LAYOUT = '@layouts/Post.astro';
|
||||||
|
|
||||||
function setDefaultLayout() {
|
function setDefaultLayout() {
|
||||||
return function (_, file) {
|
return function(_, file) {
|
||||||
const { frontmatter } = file.data.astro;
|
const { frontmatter } = file.data.astro;
|
||||||
if (!frontmatter.layout) frontmatter.layout = DEFAULT_LAYOUT;
|
if (!frontmatter.layout) frontmatter.layout = DEFAULT_LAYOUT;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://astro.build/config
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: "https://max-richter.dev",
|
site: "https://max-richter.dev",
|
||||||
trailingSlash: "never",
|
trailingSlash: "never",
|
||||||
@@ -38,6 +37,13 @@ export default defineConfig({
|
|||||||
compiler: 'svelte',
|
compiler: 'svelte',
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
server: {
|
||||||
|
watch: {
|
||||||
|
// Customize watch behavior to reduce file watchers
|
||||||
|
ignored: ['**/node_modules/**', '**/dist/**', '**/.git/**'],
|
||||||
|
usePolling: process.env.NODE_ENV === 'production',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
markdown: {
|
markdown: {
|
||||||
remarkPlugins: [setDefaultLayout]
|
remarkPlugins: [setDefaultLayout]
|
||||||
|
38
package.json
38
package.json
@@ -11,32 +11,32 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.4",
|
"@astrojs/check": "^0.9.4",
|
||||||
"@astrojs/mdx": "^4.0.8",
|
"@astrojs/mdx": "^4.3.1",
|
||||||
"@astrojs/svelte": "^7.0.4",
|
"@astrojs/svelte": "^7.1.0",
|
||||||
"@astrojs/tailwind": "^6.0.0",
|
"@astrojs/tailwind": "^6.0.2",
|
||||||
"astro": "^5.3.0",
|
"astro": "^5.12.0",
|
||||||
"astro-i18n-aut": "^0.7.3",
|
"astro-i18n-aut": "^0.7.3",
|
||||||
"exifreader": "^4.26.1",
|
"exifreader": "^4.31.1",
|
||||||
"svelte": "^5.20.1",
|
"svelte": "^5.36.10",
|
||||||
"svelte-gestures": "^5.1.3",
|
"svelte-gestures": "^5.1.4",
|
||||||
"tailwindcss": "^4.0.6",
|
"tailwindcss": "^4.1.11",
|
||||||
"thumbhash": "^0.1.1",
|
"thumbhash": "^0.1.1",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.8.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/sitemap": "^3.2.1",
|
"@astrojs/sitemap": "^3.4.1",
|
||||||
"@iconify-json/tabler": "^1.2.16",
|
"@iconify-json/tabler": "^1.2.19",
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@types/markdown-it": "^14.1.2",
|
||||||
"@unocss/preset-icons": "^65.5.0",
|
"@unocss/preset-icons": "^66.3.3",
|
||||||
"@unocss/reset": "^65.5.0",
|
"@unocss/reset": "^66.3.3",
|
||||||
"astro-font": "^1.0.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.5.1",
|
"prettier": "^3.6.2",
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.34.3",
|
||||||
"unocss": "^65.5.0",
|
"unocss": "^66.3.3",
|
||||||
"unplugin-icons": "^22.0.0",
|
"unplugin-icons": "^22.1.0",
|
||||||
"vite-plugin-glsl": "^1.3.1"
|
"vite-plugin-glsl": "^1.5.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2827
pnpm-lock.yaml
generated
2827
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
4
pnpm-workspace.yaml
Normal file
4
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- esbuild
|
||||||
|
- exifreader
|
||||||
|
- sharp
|
@@ -31,7 +31,14 @@ const link = translatePath(`/${collection}/${id.split("/")[0]}`);
|
|||||||
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 ${cover ? "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">
|
||||||
<Card.Title classes="text-4xl flex items-center gap-2">
|
<Card.Title classes="text-4xl flex items-center gap-2">
|
||||||
{icon && <img src={icon} class="h-6 w-6" />}
|
{
|
||||||
|
icon &&
|
||||||
|
(
|
||||||
|
icon?.length > 5
|
||||||
|
? <img class="h-6 w-6" src={icon} />
|
||||||
|
: <span class="p-r-4 text-md">{icon}</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
{title}
|
{title}
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
<Card.Description>
|
<Card.Description>
|
||||||
|
@@ -3,7 +3,7 @@ import type { ImageMetadata } from "astro";
|
|||||||
import { Picture as AstroImage } from "astro:assets";
|
import { Picture as AstroImage } from "astro:assets";
|
||||||
import { generateThumbHash, getExifData } from "@helpers/image";
|
import { generateThumbHash, getExifData } from "@helpers/image";
|
||||||
interface Props {
|
interface Props {
|
||||||
src: ImageMetadata;
|
src: ImageMetadata & { fsPath?: string };
|
||||||
alt: string;
|
alt: string;
|
||||||
pictureClass?: string;
|
pictureClass?: string;
|
||||||
class?: string;
|
class?: string;
|
||||||
@@ -22,7 +22,7 @@ const {
|
|||||||
maxWidth,
|
maxWidth,
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
|
||||||
let thumbhash = hash ? await generateThumbHash(image) : "";
|
let thumbhash = hash && image.fsPath ? await generateThumbHash(image) : "";
|
||||||
|
|
||||||
let exif = await getExifData(image);
|
let exif = await getExifData(image);
|
||||||
|
|
||||||
|
13
src/components/MetaTags.astro
Normal file
13
src/components/MetaTags.astro
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
cover: string;
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<meta property="og:title" content={Astro.props.title} />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content={Astro.url} />
|
||||||
|
<meta property="og:image" content={Astro.props.cover} />
|
||||||
|
<meta property="og:description" content="Max Richters personal blog" />
|
||||||
|
<meta property="og:site_name" content="Max Richter" />
|
@@ -24,8 +24,8 @@ const paths = [
|
|||||||
text: t("nav.photos"),
|
text: t("nav.photos"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: translatePath("/videos"),
|
link: translatePath("/resources"),
|
||||||
text: t("nav.videos"),
|
text: t("nav.resources"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
---
|
---
|
||||||
|
@@ -21,7 +21,13 @@ const { post } = Astro.props;
|
|||||||
<a href={tp(`/${post.collection}/${post.id.split("/")[0]}`)}>
|
<a href={tp(`/${post.collection}/${post.id.split("/")[0]}`)}>
|
||||||
<h2
|
<h2
|
||||||
class="text-2xl flex gap-2 items-center line-clamp text-ellipsis overflow-hidden">
|
class="text-2xl flex gap-2 items-center line-clamp text-ellipsis overflow-hidden">
|
||||||
{post.data.icon && <img src={post.data.icon} class="h-6" />}
|
{
|
||||||
|
post.data.icon?.length > 3 ? (
|
||||||
|
<img src={post.data.icon} class="h-6" />
|
||||||
|
) : post.data.icon?.length ? (
|
||||||
|
<span>{post.data.icon}</span>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
{post.data.title}
|
{post.data.title}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-ellipsis overflow-hidden line-clamp-2">
|
<p class="text-ellipsis overflow-hidden line-clamp-2">
|
||||||
|
@@ -2,6 +2,9 @@
|
|||||||
title: "Erasmus Valencia"
|
title: "Erasmus Valencia"
|
||||||
date: 2022-09-02
|
date: 2022-09-02
|
||||||
cover: ./images/MAX_8218 - MAX_8230.jpg
|
cover: ./images/MAX_8218 - MAX_8230.jpg
|
||||||
|
toc: true
|
||||||
|
icon: 🍊
|
||||||
|
tags: ["valencia", "erasmus"]
|
||||||
---
|
---
|
||||||
|
|
||||||
import Image from "@components/Image.astro"
|
import Image from "@components/Image.astro"
|
||||||
|
@@ -3,6 +3,8 @@ title: "Erasmus Valencia"
|
|||||||
date: 2022-09-02
|
date: 2022-09-02
|
||||||
cover: ./images/MAX_8218 - MAX_8230.jpg
|
cover: ./images/MAX_8218 - MAX_8230.jpg
|
||||||
toc: true
|
toc: true
|
||||||
|
icon: 🍊
|
||||||
|
tags: ["valencia", "erasmus"]
|
||||||
---
|
---
|
||||||
|
|
||||||
import Image from "@components/Image.astro"
|
import Image from "@components/Image.astro"
|
||||||
|
@@ -3,7 +3,9 @@ title: Madeira
|
|||||||
date: 2025-02-16
|
date: 2025-02-16
|
||||||
license: "CC-BY-SA:4.0"
|
license: "CC-BY-SA:4.0"
|
||||||
comments: true
|
comments: true
|
||||||
|
icon: 🏝️
|
||||||
cover: ./images/MAX_0603.jpg
|
cover: ./images/MAX_0603.jpg
|
||||||
|
tags: ["madeira", "travel"]
|
||||||
---
|
---
|
||||||
|
|
||||||
import Image from "@components/Image.astro";
|
import Image from "@components/Image.astro";
|
||||||
|
@@ -3,7 +3,9 @@ title: Madeira
|
|||||||
date: 2025-02-16
|
date: 2025-02-16
|
||||||
license: "CC-BY-SA:4.0"
|
license: "CC-BY-SA:4.0"
|
||||||
comments: true
|
comments: true
|
||||||
|
icon: 🏝️
|
||||||
cover: ./images/MAX_0603.jpg
|
cover: ./images/MAX_0603.jpg
|
||||||
|
tags: ["madeira", "travel"]
|
||||||
---
|
---
|
||||||
|
|
||||||
import Image from "@components/Image.astro";
|
import Image from "@components/Image.astro";
|
||||||
|
@@ -3,6 +3,8 @@ title: "Peaks of the Balkans"
|
|||||||
date: 2024-06-19
|
date: 2024-06-19
|
||||||
cover: ./images/MAX_9861.jpg
|
cover: ./images/MAX_9861.jpg
|
||||||
license: "CC-BY-SA:4.0"
|
license: "CC-BY-SA:4.0"
|
||||||
|
icon: 🏔️
|
||||||
|
tags: ["balkans", "travel"]
|
||||||
comments: true
|
comments: true
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@@ -3,6 +3,8 @@ title: "Peaks of the Balkans"
|
|||||||
date: 2024-06-19
|
date: 2024-06-19
|
||||||
cover: ./images/MAX_9861.jpg
|
cover: ./images/MAX_9861.jpg
|
||||||
license: "CC-BY-SA:4.0"
|
license: "CC-BY-SA:4.0"
|
||||||
|
icon: 🏔️
|
||||||
|
tags: ["balkans", "hiking", "travel"]
|
||||||
comments: true
|
comments: true
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
---
|
---
|
||||||
title: Videos
|
title: Resources
|
||||||
menu: nav
|
menu: nav
|
||||||
---
|
---
|
||||||
|
1036
src/helpers/exif_
1036
src/helpers/exif_
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@ async function getSharp(): Promise<typeof import("sharp") | undefined> {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateThumbHash(image: { width: number, height: number }) {
|
export async function generateThumbHash(image: ImageMetadata & { fsPath?: string }) {
|
||||||
|
|
||||||
const sharp = await getSharp();
|
const sharp = await getSharp();
|
||||||
if (!sharp) return;
|
if (!sharp) return;
|
||||||
@@ -19,16 +19,21 @@ export async function generateThumbHash(image: { width: number, height: number }
|
|||||||
const smallWidth = Math.floor(image.width * scaleFactor);
|
const smallWidth = Math.floor(image.width * scaleFactor);
|
||||||
const smallHeight = Math.floor(image.height * scaleFactor);
|
const smallHeight = Math.floor(image.height * scaleFactor);
|
||||||
|
|
||||||
//@ts-ignore
|
try {
|
||||||
const smallImg = await sharp(image.fsPath)
|
const smallImg = await sharp(image.fsPath)
|
||||||
.resize(smallWidth, smallHeight)
|
.resize(smallWidth, smallHeight)
|
||||||
.withMetadata()
|
.withMetadata()
|
||||||
.raw()
|
.raw()
|
||||||
.ensureAlpha()
|
.ensureAlpha()
|
||||||
.toBuffer();
|
.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 ""
|
||||||
|
}
|
||||||
|
|
||||||
const buffer = rgbaToThumbHash(smallWidth, smallHeight, smallImg);
|
|
||||||
return Buffer.from(buffer).toString("base64");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowedExif = [
|
const allowedExif = [
|
||||||
@@ -48,10 +53,15 @@ const allowedExif = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export async function getExifData(image: ImageMetadata) {
|
export async function getExifData(image: ImageMetadata) {
|
||||||
|
if (image.format === "svg") return undefined; // SVGs don't have EXIF data")
|
||||||
const sharp = await getSharp();
|
const sharp = await getSharp();
|
||||||
if (!sharp) return;
|
if (!sharp) return;
|
||||||
|
const imagePath = (image as ImageMetadata & { fsPath: string }).fsPath;
|
||||||
try {
|
try {
|
||||||
const tags = await ExifReader.load((image as ImageMetadata & { fsPath: string }).fsPath, { async: true });
|
|
||||||
|
const buffer = await sharp(imagePath).toBuffer();
|
||||||
|
|
||||||
|
const tags = await ExifReader.load(buffer, { async: true });
|
||||||
|
|
||||||
const out: Record<string, any> = {};
|
const out: Record<string, any> = {};
|
||||||
let hasExif = false;
|
let hasExif = false;
|
||||||
@@ -65,7 +75,7 @@ export async function getExifData(image: ImageMetadata) {
|
|||||||
return hasExif ? out : undefined;
|
return hasExif ? out : undefined;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
console.log("Error reading EXIF data", error);
|
console.log(`Error reading EXIF data from ${imagePath}`, error);
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
src/helpers/memorium.ts
Normal file
10
src/helpers/memorium.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export async function listResource(id: string): Promise<any[]> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`http://localhost:8080/resources?name=${id}`,
|
||||||
|
);
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
@@ -20,7 +20,7 @@ export const ui = {
|
|||||||
'home.subtitle': 'Trained Media Designer, Blender Nerd, Developer and Hardware Tinkerer.',
|
'home.subtitle': 'Trained Media Designer, Blender Nerd, Developer and Hardware Tinkerer.',
|
||||||
'nav.blog': 'Blog',
|
'nav.blog': 'Blog',
|
||||||
'nav.projects': 'Projects',
|
'nav.projects': 'Projects',
|
||||||
'nav.videos': 'Videos',
|
'nav.resources': 'Resources',
|
||||||
'nav.photos': 'Photos',
|
'nav.photos': 'Photos',
|
||||||
'toc.title': 'Table of Contents',
|
'toc.title': 'Table of Contents',
|
||||||
"resume": "Resume",
|
"resume": "Resume",
|
||||||
@@ -38,7 +38,7 @@ export const ui = {
|
|||||||
'home.subtitle': 'Ausgebildeter Mediengestalter, Blender Nerd, Entwickler und Hardware Bastler.',
|
'home.subtitle': 'Ausgebildeter Mediengestalter, Blender Nerd, Entwickler und Hardware Bastler.',
|
||||||
'nav.blog': 'Blog',
|
'nav.blog': 'Blog',
|
||||||
'nav.projects': 'Projekte',
|
'nav.projects': 'Projekte',
|
||||||
'nav.videos': 'Videos',
|
'nav.resources': 'Resources',
|
||||||
'nav.photos': 'Fotos',
|
'nav.photos': 'Fotos',
|
||||||
"resume": "Lebenslauf",
|
"resume": "Lebenslauf",
|
||||||
'toc.title': 'Inhaltsverzeichnis',
|
'toc.title': 'Inhaltsverzeichnis',
|
||||||
|
@@ -6,10 +6,12 @@ import { useTranslations } from "@i18n/utils";
|
|||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
width?: "full" | "compact";
|
width?: "full" | "compact";
|
||||||
|
image?: string;
|
||||||
}
|
}
|
||||||
const t = useTranslations(Astro.url);
|
const t = useTranslations(Astro.url);
|
||||||
|
|
||||||
const { title } = Astro.props;
|
const { title } = Astro.props;
|
||||||
|
|
||||||
import "./theme.css";
|
import "./theme.css";
|
||||||
import "./global.css";
|
import "./global.css";
|
||||||
---
|
---
|
||||||
@@ -21,6 +23,7 @@ import "./global.css";
|
|||||||
<meta name="description" content="Astro description" />
|
<meta name="description" content="Astro description" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="props" content={JSON.stringify(Astro.props)} />
|
<meta name="props" content={JSON.stringify(Astro.props)} />
|
||||||
|
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<script
|
<script
|
||||||
is:inline
|
is:inline
|
||||||
@@ -60,7 +63,7 @@ import "./global.css";
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
<!-- <meta http-equiv="refresh" content="0;url=/" /> -->
|
<slot name="meta-tags" />
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<script is:inline>
|
<script is:inline>
|
||||||
(function () {
|
(function () {
|
||||||
|
@@ -9,20 +9,20 @@ type Props = MarkdownLayoutProps<{
|
|||||||
date: Date;
|
date: Date;
|
||||||
links?: string[][];
|
links?: string[][];
|
||||||
toc?: boolean;
|
toc?: boolean;
|
||||||
|
cover?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const { frontmatter, headings } = Astro.props;
|
const { frontmatter, headings } = Astro.props;
|
||||||
const t = useTranslations(Astro.url);
|
const t = useTranslations(Astro.url);
|
||||||
const { title, url, date: dateString, links, toc } = frontmatter;
|
const { title, url, date: dateString, links, toc, cover } = frontmatter;
|
||||||
const collection = url?.split("/")[2];
|
const collection = url?.split("/")[2];
|
||||||
const date = new Date(dateString);
|
|
||||||
const path = useTranslatedPath(Astro.url);
|
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const backlinkContent = t(`nav.${collection}`).toLowerCase();
|
const backlinkContent = t(`nav.${collection}`).toLowerCase();
|
||||||
|
const date = new Date(dateString);
|
||||||
|
const path = useTranslatedPath(Astro.url);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={title}>
|
<Layout title={title} image={cover}>
|
||||||
<div class="top-info flex items-center place-content-between m-y-2">
|
<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)}
|
<a class="flex items-center gap-1 opacity-50" href={path("/" + collection)}
|
||||||
><span class="i-tabler-arrow-left"></span>
|
><span class="i-tabler-arrow-left"></span>
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
import { getCollection, render } from "astro:content";
|
import { getCollection, render } from "astro:content";
|
||||||
import { getLocale } from "astro-i18n-aut";
|
import { getLocale } from "astro-i18n-aut";
|
||||||
import { filterCollection, parseSlug } from "@i18n/utils";
|
import { filterCollection, parseSlug } from "@i18n/utils";
|
||||||
|
import MetaTags from "@components/MetaTags.astro";
|
||||||
|
|
||||||
const locale = getLocale(Astro.url);
|
const locale = getLocale(Astro.url);
|
||||||
|
|
||||||
@@ -31,4 +32,5 @@ if (!page) {
|
|||||||
const { Content } = await render(page);
|
const { Content } = await render(page);
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<MetaTags title={page.data.title} cover={page.data.cover?.src} />
|
||||||
<Content />
|
<Content />
|
||||||
|
@@ -33,9 +33,17 @@ const posts = filterCollection(
|
|||||||
await getCollection("blog"),
|
await getCollection("blog"),
|
||||||
getLocale(Astro.url),
|
getLocale(Astro.url),
|
||||||
);
|
);
|
||||||
|
const photos = filterCollection(
|
||||||
|
await getCollection("photos"),
|
||||||
|
getLocale(Astro.url),
|
||||||
|
);
|
||||||
|
const list = [...posts, ...photos];
|
||||||
|
list.sort((a, b) => {
|
||||||
|
return a.data.date > b.data.date ? -1 : 1;
|
||||||
|
});
|
||||||
|
|
||||||
const featuredPost = posts.find((post) => post.data?.featured);
|
const featuredPost = list.find((post) => post.data?.featured);
|
||||||
const otherPosts = posts.filter((post) => featuredPost !== post).slice(0, 3);
|
const otherPosts = list.filter((post) => featuredPost !== post).slice(0, 3);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Max Richter">
|
<Layout title="Max Richter">
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
import { getCollection, render } from "astro:content";
|
import { getCollection, render } from "astro:content";
|
||||||
import { getLocale } from "astro-i18n-aut";
|
import { getLocale } from "astro-i18n-aut";
|
||||||
import { filterCollection, parseSlug } from "@i18n/utils";
|
import { filterCollection, parseSlug } from "@i18n/utils";
|
||||||
|
import MetaTags from "@components/MetaTags.astro";
|
||||||
|
|
||||||
const locale = getLocale(Astro.url);
|
const locale = getLocale(Astro.url);
|
||||||
|
|
||||||
@@ -31,4 +32,5 @@ if (!page) {
|
|||||||
const { Content } = await render(page);
|
const { Content } = await render(page);
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<MetaTags title={page.data.title} cover={page.data.cover?.src} />
|
||||||
<Content />
|
<Content />
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
import { getCollection, render } from "astro:content";
|
import { getCollection, render } from "astro:content";
|
||||||
import { getLocale } from "astro-i18n-aut";
|
import { getLocale } from "astro-i18n-aut";
|
||||||
import { filterCollection, parseSlug } from "@i18n/utils";
|
import { filterCollection, parseSlug } from "@i18n/utils";
|
||||||
|
import MetaTags from "@components/MetaTags.astro";
|
||||||
|
|
||||||
const locale = getLocale(Astro.url);
|
const locale = getLocale(Astro.url);
|
||||||
|
|
||||||
@@ -31,4 +32,5 @@ if (!page) {
|
|||||||
const { Content } = await render(page);
|
const { Content } = await render(page);
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<MetaTags title={page.data.title} cover={page.data.cover?.src} />
|
||||||
<Content />
|
<Content />
|
||||||
|
64
src/pages/resources/index.astro
Normal file
64
src/pages/resources/index.astro
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
import Layout from "@layouts/Layout.astro";
|
||||||
|
import HeroCard from "@components/HeroCard.astro";
|
||||||
|
|
||||||
|
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">
|
||||||
|
<HeroCard post={wiki} />
|
||||||
|
<HeroCard post={recipes} />
|
||||||
|
<HeroCard post={articles} />
|
||||||
|
<HeroCard post={movies} />
|
||||||
|
<HeroCard post={series} />
|
||||||
|
</Layout>
|
38
src/pages/resources/movies/[movieName].astro
Normal file
38
src/pages/resources/movies/[movieName].astro
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
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>
|
27
src/pages/resources/movies/index.astro
Normal file
27
src/pages/resources/movies/index.astro
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
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>
|
71
src/pages/resources/recipes/[recipeName].astro
Normal file
71
src/pages/resources/recipes/[recipeName].astro
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
---
|
||||||
|
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.map((recipe: any) => {
|
||||||
|
return {
|
||||||
|
params: {
|
||||||
|
recipeName: recipe.identifier
|
||||||
|
.replace("Recipes/", "")
|
||||||
|
.replace(/\.md$/, ""),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const recipes = await memorium.listResource(
|
||||||
|
//@ts-ignore
|
||||||
|
`Recipes/${Astro.params.recipeName}.md`,
|
||||||
|
);
|
||||||
|
if (recipes.length === 0) {
|
||||||
|
return new Response(null, {
|
||||||
|
status: 404,
|
||||||
|
statusText: "Not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const recipe = recipes[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">
|
||||||
|
{
|
||||||
|
recipe.date?.toLocaleString("en-US", {
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
year: "numeric",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>{recipe.name}</h1>
|
||||||
|
|
||||||
|
<h3>Ingredients</h3>
|
||||||
|
<ol>
|
||||||
|
{
|
||||||
|
recipe.recipeIngredient?.map((ingredient: any) => (
|
||||||
|
<li>{markdownToText(ingredient)}</li>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Instructions</h3>
|
||||||
|
<p>{recipe.recipeInstructions}</p>
|
||||||
|
</Layout>
|
23
src/pages/resources/recipes/index.astro
Normal file
23
src/pages/resources/recipes/index.astro
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
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.map((recipe: any) => (
|
||||||
|
<HeroCard
|
||||||
|
post={{
|
||||||
|
collection: "resources/recipes",
|
||||||
|
id: recipe.identifier.replace("Recipes/", "").replace(/\.md$/, ""),
|
||||||
|
data: {
|
||||||
|
title: recipe.name,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Layout>
|
58
src/pages/resources/series/[seriesName].astro
Normal file
58
src/pages/resources/series/[seriesName].astro
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
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>
|
27
src/pages/resources/series/index.astro
Normal file
27
src/pages/resources/series/index.astro
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
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>
|
13
src/pages/robots.txt.ts
Normal file
13
src/pages/robots.txt.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { APIRoute } from 'astro';
|
||||||
|
|
||||||
|
const getRobotsTxt = (sitemapURL: URL) => `
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
Sitemap: ${sitemapURL.href}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GET: APIRoute = ({ site }) => {
|
||||||
|
const sitemapURL = new URL('sitemap-index.xml', site);
|
||||||
|
return new Response(getRobotsTxt(sitemapURL));
|
||||||
|
};
|
@@ -1,10 +1,10 @@
|
|||||||
// uno.config.ts
|
// uno.config.ts
|
||||||
import { defineConfig, presetUno } from 'unocss'
|
import { defineConfig, presetWind3 } from 'unocss'
|
||||||
import presetIcons from '@unocss/preset-icons'
|
import presetIcons from '@unocss/preset-icons'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
presets: [
|
presets: [
|
||||||
presetUno(),
|
presetWind3(),
|
||||||
presetIcons(),
|
presetIcons(),
|
||||||
],
|
],
|
||||||
shortcuts: {
|
shortcuts: {
|
||||||
|
Reference in New Issue
Block a user