147 lines
4.8 KiB
TypeScript
147 lines
4.8 KiB
TypeScript
import { metadata } from '@/app/metadata'
|
|
import { ImageWrapper } from '@/components/common/ImageWrapper'
|
|
import { ShareLinks } from '@/components/common/ShareLinks'
|
|
import { TableOfContents } from '@/components/common/TableOfContents'
|
|
import { getAllPostSlugs, getNextAndPreviousPosts, getPostBySlug } from '@/lib/posts'
|
|
import { slugify } from '@/utils/slug'
|
|
import type { Metadata } from 'next'
|
|
import Link from 'next/link'
|
|
|
|
export async function generateStaticParams() {
|
|
return getAllPostSlugs().map((slug) => ({ slug }))
|
|
}
|
|
|
|
export const dynamicParams = false
|
|
|
|
export async function generateMetadata({
|
|
params,
|
|
}: {
|
|
params: Promise<{ slug: string }>
|
|
}): Promise<Metadata> {
|
|
const { slug } = await params
|
|
const { meta } = await getPostBySlug(slug)
|
|
return {
|
|
title: `${meta.title} | ${metadata.title!.toString()}`,
|
|
description: meta.description,
|
|
openGraph: {
|
|
type: 'article',
|
|
title: `${meta.title} | ${metadata.title!.toString()}`,
|
|
description: meta.description,
|
|
url: process.env.SITE_URL + `/posts/${slug}`,
|
|
images: meta.coverImage ? [meta.coverImage] : undefined,
|
|
tags: meta.tags,
|
|
...(meta.updated && { modifiedTime: new Date(meta.updated).toISOString() }),
|
|
siteName: metadata.title!.toString(),
|
|
},
|
|
}
|
|
}
|
|
|
|
export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
|
|
const { slug } = await params
|
|
const { meta, content, tableOfContents } = await getPostBySlug(slug)
|
|
const { next, previous } = getNextAndPreviousPosts(slug)
|
|
|
|
return (
|
|
<main className="flex w-full">
|
|
<article className="max-w-3xl mx-auto px-4 py-8 lg:py-16">
|
|
{meta.category && (
|
|
<Link
|
|
href={`/categories/${slugify(meta.category)}`}
|
|
className="text-accent hover:underline font-sm mb-2 block"
|
|
>
|
|
{meta.category}
|
|
</Link>
|
|
)}
|
|
<h1 className="text-4xl font-bold mb-4">{meta.title}</h1>
|
|
<div className="flex items-center gap-2 text-fg-dim mb-4 border-y border-border py-2 px-4 text-sm">
|
|
<div className="flex items-center gap-2">
|
|
<span>Posted</span>
|
|
<time
|
|
dateTime={meta.date}
|
|
title={new Date(meta.date).toLocaleString()}
|
|
className="font-semibold"
|
|
>
|
|
{new Date(meta.date).toLocaleDateString(undefined, {
|
|
day: 'numeric',
|
|
month: 'long',
|
|
year: 'numeric',
|
|
})}
|
|
</time>
|
|
</div>
|
|
<span>·</span>
|
|
{meta.updated && (
|
|
<>
|
|
<div className="flex items-center gap-2">
|
|
<span>Updated</span>
|
|
<time
|
|
dateTime={meta.updated}
|
|
title={new Date(meta.updated).toLocaleString()}
|
|
className="font-semibold"
|
|
>
|
|
{new Date(meta.updated).toLocaleDateString(undefined, {
|
|
day: 'numeric',
|
|
month: 'long',
|
|
year: 'numeric',
|
|
})}
|
|
</time>
|
|
</div>
|
|
<span>·</span>
|
|
</>
|
|
)}
|
|
<span>{meta.readingTimeText}</span>
|
|
</div>
|
|
<p className="text-fg text-lg mb-4">{meta.description}</p>
|
|
{meta.coverImage && <ImageWrapper src={meta.coverImage} alt={meta.coverImageAlt} />}
|
|
<div className="prose">{content}</div>
|
|
|
|
{meta.tags && (
|
|
<div className="mt-6 border-t border-border pt-4 flex gap-2 items-center flex-wrap">
|
|
<h2 className="text-lg font-bold">Tags</h2>
|
|
<div className="flex flex-wrap gap-1">
|
|
{meta.tags.map((tag) => (
|
|
<span key={tag} className="bg-accent/20 text-accent px-3 py-1 rounded-full text-xs">
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="mt-8 mx-auto flex">
|
|
<ShareLinks
|
|
url={process.env.SITE_URL + `/posts/${slug}`}
|
|
title={meta.title}
|
|
description={meta.description}
|
|
/>
|
|
</div>
|
|
|
|
<div className="mt-8 flex justify-between">
|
|
{previous ? (
|
|
<Link
|
|
href={`/posts/${previous.slug}`}
|
|
className="text-accent hover:underline font-sm mb-2 block"
|
|
>
|
|
← {previous.title}
|
|
</Link>
|
|
) : (
|
|
<div />
|
|
)}
|
|
{next ? (
|
|
<Link
|
|
href={`/posts/${next.slug}`}
|
|
className="text-accent hover:underline font-sm mb-2 block"
|
|
>
|
|
{next.title} →
|
|
</Link>
|
|
) : (
|
|
<div />
|
|
)}
|
|
</div>
|
|
</article>
|
|
|
|
<aside className="hidden lg:block w-80">
|
|
<TableOfContents items={tableOfContents} />
|
|
</aside>
|
|
</main>
|
|
)
|
|
}
|