cleanup markdown

This commit is contained in:
2026-06-18 01:32:05 +02:00
parent c1fe73dbd0
commit 73ba2b573d
11 changed files with 137 additions and 26 deletions

View File

@@ -1,9 +1,10 @@
import { notFound } from "next/navigation";
import { MDXRemote } from "next-mdx-remote/rsc";
import { TRPCError } from "@trpc/server";
import matter from "gray-matter";
import { servTrpc } from "~/app/_trpc/ServerClient";
import { Badge } from "~/components/ui/badge";
import { mdxComponents } from "../_components/mdx-components";
import { mdxComponents } from "~/components/mdx-components";
type Props = {
params: Promise<{ slug: string }>;
@@ -12,37 +13,47 @@ type Props = {
export default async function BlogPostPage({ params }: Props) {
const { slug } = await params;
let post: Awaited<ReturnType<typeof servTrpc.blog.bySlug>>;
let post: Awaited<ReturnType<typeof servTrpc.blog.metadataBySlug>>;
try {
post = await servTrpc.blog.bySlug(slug);
post = await servTrpc.blog.metadataBySlug(slug);
} catch (e) {
if (e instanceof TRPCError && e.code === "NOT_FOUND") notFound();
throw e;
}
const response = await fetch(post.fileUrl, { next: { revalidate: 3600 } });
if (!response.ok) notFound();
const parsed = matter(await response.text());
const tags = Array.isArray(parsed.data.tags)
? parsed.data.tags.map((tag) => String(tag).trim()).filter(Boolean)
: post.tags;
const title = typeof parsed.data.title === "string" ? parsed.data.title : post.title;
const date = typeof parsed.data.date === "string" ? parsed.data.date : post.date;
return (
<main className="mx-auto max-w-2xl px-4 py-12">
<header className="mb-8">
<h1 className="text-3xl font-bold">{post.title}</h1>
{post.date && (
<h1 className="text-3xl font-bold">{title}</h1>
{date && (
<time className="text-muted-foreground text-sm">
{new Date(post.date).toLocaleDateString("en-US", {
{new Date(date).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</time>
)}
{post.tags.length > 0 && (
{tags.length > 0 && (
<div className="mt-3 flex flex-wrap gap-1.5">
{post.tags.map((tag) => (
{tags.map((tag) => (
<Badge key={tag} variant="outline">{tag}</Badge>
))}
</div>
)}
</header>
<article className="prose dark:prose-invert max-w-none">
<MDXRemote source={post.content} components={mdxComponents} />
<MDXRemote source={parsed.content} components={mdxComponents} />
</article>
</main>
);

View File

@@ -1,128 +0,0 @@
import Link from "next/link";
import { Children, isValidElement, type ComponentPropsWithoutRef, type ReactNode } from "react";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { cn } from "~/lib/utils";
type CalloutVariant = "note" | "tip" | "warning";
const calloutStyles: Record<CalloutVariant, string> = {
note: "border-sky-500/40 bg-sky-500/10 text-sky-950 dark:text-sky-100",
tip: "border-emerald-500/40 bg-emerald-500/10 text-emerald-950 dark:text-emerald-100",
warning: "border-amber-500/40 bg-amber-500/10 text-amber-950 dark:text-amber-100",
};
function Callout({
title,
variant = "note",
children,
}: {
title?: string;
variant?: CalloutVariant;
children: ReactNode;
}) {
return (
<aside className={cn("my-6 rounded-md border px-4 py-3", calloutStyles[variant])}>
{title && <p className="mb-2 font-semibold">{title}</p>}
<div className="[&>*:first-child]:mt-0 [&>*:last-child]:mb-0">{children}</div>
</aside>
);
}
function Lead({ children }: { children: ReactNode }) {
return <span className="text-muted-foreground my-6 block text-lg leading-8">{children}</span>;
}
function TagList({ tags }: { tags: string[] }) {
return (
<div className="my-4 flex flex-wrap gap-1.5">
{tags.map((tag) => (
<Badge key={tag} variant="outline">
{tag}
</Badge>
))}
</div>
);
}
function ButtonLink({
href,
children,
variant = "default",
}: {
href: string;
children: ReactNode;
variant?: ComponentPropsWithoutRef<typeof Button>["variant"];
}) {
const isExternal = /^https?:\/\//.test(href);
return (
<Button asChild variant={variant}>
{isExternal ? (
<a href={href} target="_blank" rel="noreferrer">
{children}
</a>
) : (
<Link href={href}>{children}</Link>
)}
</Button>
);
}
function Figure({
src,
alt,
caption,
}: {
src: string;
alt: string;
caption?: string;
}) {
return (
<figure className="my-8">
<img src={src} alt={alt} className="w-full rounded-md border object-cover" />
{caption && <figcaption className="text-muted-foreground mt-2 text-center text-sm">{caption}</figcaption>}
</figure>
);
}
function PullQuote({ children }: { children: ReactNode }) {
return (
<blockquote className="border-primary my-8 border-l-4 pl-5 text-xl leading-8 font-medium">
{children}
</blockquote>
);
}
function ExternalLink(props: ComponentPropsWithoutRef<"a">) {
const href = props.href ?? "";
const isExternal = /^https?:\/\//.test(href);
if (!isExternal) return <a {...props} />;
return <a {...props} target="_blank" rel="noreferrer" />;
}
const blockComponents = new Set<unknown>([Callout, Figure, PullQuote, TagList]);
function Paragraph({ children }: { children: ReactNode }) {
const containsBlockComponent = Children.toArray(children).some(
(child) => isValidElement(child) && blockComponents.has(child.type),
);
if (containsBlockComponent) return <>{children}</>;
return <p>{children}</p>;
}
export const mdxComponents = {
a: ExternalLink,
p: Paragraph,
Badge,
ButtonLink,
Callout,
Figure,
Lead,
PullQuote,
TagList,
};