From 64564a6fee02708d375a349d75ce49d515e66f8d Mon Sep 17 00:00:00 2001 From: schererleander Date: Wed, 25 Jun 2025 01:00:49 +0200 Subject: add markdown-based blog --- src/pages/Blog.tsx | 50 ++++++++++++++++++++++++++++++++++++ src/pages/Post.tsx | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 src/pages/Blog.tsx create mode 100644 src/pages/Post.tsx (limited to 'src/pages') diff --git a/src/pages/Blog.tsx b/src/pages/Blog.tsx new file mode 100644 index 0000000..5db87c8 --- /dev/null +++ b/src/pages/Blog.tsx @@ -0,0 +1,50 @@ +import { useMemo } from "react"; +import matter from "gray-matter"; +import { Link } from "react-router-dom"; +import CardLink from "../components/CardLink"; + +interface PostMeta { + slug: string; + title: string; + date: string; + excerpt: string; + cover?: string; +} + +export default function Blog() { + const posts = useMemo(() => { + const files = import.meta.glob("../blog/*.md", { + eager: true, + query: "?raw", + import: "default", + }) as Record; + + return Object.entries(files) + .map(([path, raw]) => { + const { data } = matter(raw); + const slug = path.split("/").pop()!.replace(".md", ""); + return { slug, ...data } as PostMeta; + }) + .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + }, []); + + return ( +
+

Blog

+ +
+ {posts.map((post) => ( + /* 1) Link für internes Routing */ + /* 2) CardLink bekommt genau deine Prop-Namen */ + + + + ))} +
+
+ ); +} diff --git a/src/pages/Post.tsx b/src/pages/Post.tsx new file mode 100644 index 0000000..17022d2 --- /dev/null +++ b/src/pages/Post.tsx @@ -0,0 +1,75 @@ +import { useEffect, useState } from "react"; +import { useParams, Link } from "react-router-dom"; +import matter from "gray-matter"; +import ReactMarkdown from "react-markdown"; +import CodeSnippet from "../components/CodeSnippet"; +import LinkWithIcon from "../components/LinkWithIcon"; +import NotFoundPage from "./404Page"; + +interface PostMeta { + title: string; + date: string; + cover?: string; +} + +export default function Post() { + const { slug } = useParams<{ slug: string }>(); + const [meta, setMeta] = useState(null); + const [content, setContent] = useState(""); + const [notFound, setNotFound] = useState(false); + + useEffect(() => { + import(`../blog/${slug}.md?raw`) + .then((m) => { + const { data, content } = matter(m.default); + setMeta(data as PostMeta); + setContent(content); + }) + .catch(() => setNotFound(true)); + }, [slug]); + + if (!meta) return null; + if (notFound) return ; + + return ( +
+ + ← Zurück zum Blog + + + {meta.cover && ( + {meta.title} + )} + +

{meta.title}

+

+ {new Date(meta.date).toLocaleDateString("de-DE")} +

+ +
+ ; + }, + + a({ href = "", children, ...props }) { + return ( + + {children} + + ); + }, + }} + > + {content} + +
+
+ ); +} \ No newline at end of file -- cgit v1.3.1