226 lines
7.0 KiB
TypeScript
226 lines
7.0 KiB
TypeScript
import Link from "next/link";
|
|
import { notFound } from "next/navigation";
|
|
import type { Metadata } from "next";
|
|
import { MDXRemote } from "next-mdx-remote/rsc";
|
|
import remarkGfm from "remark-gfm";
|
|
import { getAdjacentPosts, getAllPosts, getPost } from "@/lib/devblog";
|
|
|
|
type Props = { params: Promise<{ slug: string }> };
|
|
|
|
export function generateStaticParams() {
|
|
return getAllPosts().map((p) => ({ slug: p.slug }));
|
|
}
|
|
|
|
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|
const { slug } = await params;
|
|
const post = getPost(slug);
|
|
if (!post) return {};
|
|
const url = `https://highlandgamesstudio.com/devblog/${post.slug}`;
|
|
return {
|
|
title: post.title,
|
|
description: post.excerpt,
|
|
alternates: { canonical: url },
|
|
openGraph: {
|
|
type: "article",
|
|
title: post.title,
|
|
description: post.excerpt,
|
|
url,
|
|
publishedTime: post.date,
|
|
authors: post.author ? [post.author] : undefined,
|
|
tags: post.tags,
|
|
},
|
|
twitter: {
|
|
card: "summary_large_image",
|
|
title: post.title,
|
|
description: post.excerpt,
|
|
},
|
|
};
|
|
}
|
|
|
|
function formatDate(date: string) {
|
|
return new Date(date).toLocaleDateString("en-US", {
|
|
year: "numeric",
|
|
month: "long",
|
|
day: "numeric",
|
|
});
|
|
}
|
|
|
|
const mdxComponents = {
|
|
h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
|
<h1
|
|
{...props}
|
|
className="font-display text-4xl sm:text-5xl tracking-wider mt-12 mb-6 first:mt-0"
|
|
/>
|
|
),
|
|
h2: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
|
<h2
|
|
{...props}
|
|
className="font-display text-3xl tracking-wider mt-12 mb-4"
|
|
/>
|
|
),
|
|
h3: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
|
<h3
|
|
{...props}
|
|
className="font-display text-2xl tracking-wider mt-8 mb-3"
|
|
/>
|
|
),
|
|
p: (props: React.HTMLAttributes<HTMLParagraphElement>) => (
|
|
<p {...props} className="text-foreground-muted leading-relaxed mb-5" />
|
|
),
|
|
ul: (props: React.HTMLAttributes<HTMLUListElement>) => (
|
|
<ul
|
|
{...props}
|
|
className="text-foreground-muted leading-relaxed mb-5 ml-6 list-disc space-y-2"
|
|
/>
|
|
),
|
|
ol: (props: React.HTMLAttributes<HTMLOListElement>) => (
|
|
<ol
|
|
{...props}
|
|
className="text-foreground-muted leading-relaxed mb-5 ml-6 list-decimal space-y-2"
|
|
/>
|
|
),
|
|
a: (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) => (
|
|
<a
|
|
{...props}
|
|
className="text-accent hover:text-foreground underline underline-offset-4 transition-colors"
|
|
/>
|
|
),
|
|
strong: (props: React.HTMLAttributes<HTMLElement>) => (
|
|
<strong {...props} className="text-foreground font-semibold" />
|
|
),
|
|
blockquote: (props: React.HTMLAttributes<HTMLQuoteElement>) => (
|
|
<blockquote
|
|
{...props}
|
|
className="border-l-2 border-accent pl-6 my-6 italic text-foreground"
|
|
/>
|
|
),
|
|
code: (props: React.HTMLAttributes<HTMLElement>) => (
|
|
<code
|
|
{...props}
|
|
className="bg-surface-elevated border border-border px-1.5 py-0.5 rounded text-sm text-accent"
|
|
/>
|
|
),
|
|
hr: () => <hr className="my-12 border-border" />,
|
|
};
|
|
|
|
export default async function DevblogPost({ params }: Props) {
|
|
const { slug } = await params;
|
|
const post = getPost(slug);
|
|
if (!post) notFound();
|
|
|
|
const { newer, older } = getAdjacentPosts(slug);
|
|
const url = `https://highlandgamesstudio.com/devblog/${post.slug}`;
|
|
const jsonLd = {
|
|
"@context": "https://schema.org",
|
|
"@type": "BlogPosting",
|
|
headline: post.title,
|
|
description: post.excerpt,
|
|
datePublished: post.date,
|
|
dateModified: post.date,
|
|
author: {
|
|
"@type": "Organization",
|
|
name: post.author ?? "Highland Games Studio",
|
|
},
|
|
publisher: {
|
|
"@type": "Organization",
|
|
name: "Highland Games Studio",
|
|
logo: {
|
|
"@type": "ImageObject",
|
|
url: "https://highlandgamesstudio.com/image/Studio/Logo Flat.png",
|
|
},
|
|
},
|
|
mainEntityOfPage: { "@type": "WebPage", "@id": url },
|
|
url,
|
|
keywords: post.tags?.join(", "),
|
|
};
|
|
|
|
return (
|
|
<article className="pt-32 pb-24 px-6 lg:px-10">
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
|
/>
|
|
<div className="max-w-3xl mx-auto">
|
|
<Link
|
|
href="/devblog"
|
|
className="inline-block font-display text-xs tracking-widest text-foreground-muted hover:text-accent transition-colors mb-12"
|
|
>
|
|
← BACK TO DEVBLOG
|
|
</Link>
|
|
|
|
<header className="mb-16 pb-8 border-b border-border">
|
|
<time
|
|
dateTime={post.date}
|
|
className="font-display text-xs tracking-[0.3em] text-accent"
|
|
>
|
|
{formatDate(post.date).toUpperCase()}
|
|
</time>
|
|
<h1 className="font-display text-4xl sm:text-5xl lg:text-6xl tracking-wider leading-[0.95] mt-4 mb-6">
|
|
{post.title.toUpperCase()}
|
|
</h1>
|
|
{post.excerpt && (
|
|
<p className="text-lg text-foreground-muted leading-relaxed">
|
|
{post.excerpt}
|
|
</p>
|
|
)}
|
|
{post.author && (
|
|
<p className="text-xs tracking-widest text-foreground-muted mt-6 uppercase">
|
|
By {post.author}
|
|
</p>
|
|
)}
|
|
</header>
|
|
|
|
<div className="prose-custom">
|
|
<MDXRemote
|
|
source={post.content}
|
|
components={mdxComponents}
|
|
options={{
|
|
mdxOptions: {
|
|
remarkPlugins: [remarkGfm],
|
|
},
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{(newer || older) && (
|
|
<nav
|
|
aria-label="Post navigation"
|
|
className="mt-20 pt-10 border-t border-border grid sm:grid-cols-2 gap-6"
|
|
>
|
|
{newer ? (
|
|
<Link
|
|
href={`/devblog/${newer.slug}`}
|
|
className="group block border border-border p-6 hover:border-accent transition-colors"
|
|
>
|
|
<p className="font-display text-xs tracking-[0.3em] text-foreground-muted group-hover:text-accent transition-colors mb-2">
|
|
← NEWER
|
|
</p>
|
|
<p className="font-display text-lg tracking-wider group-hover:text-accent transition-colors">
|
|
{newer.title.toUpperCase()}
|
|
</p>
|
|
</Link>
|
|
) : (
|
|
<span aria-hidden />
|
|
)}
|
|
{older ? (
|
|
<Link
|
|
href={`/devblog/${older.slug}`}
|
|
className="group block border border-border p-6 hover:border-accent transition-colors sm:text-right"
|
|
>
|
|
<p className="font-display text-xs tracking-[0.3em] text-foreground-muted group-hover:text-accent transition-colors mb-2">
|
|
OLDER →
|
|
</p>
|
|
<p className="font-display text-lg tracking-wider group-hover:text-accent transition-colors">
|
|
{older.title.toUpperCase()}
|
|
</p>
|
|
</Link>
|
|
) : (
|
|
<span aria-hidden />
|
|
)}
|
|
</nav>
|
|
)}
|
|
</div>
|
|
</article>
|
|
);
|
|
}
|