M2: content collections — case studies, blog, RSS, tags, sitemap
Projects + blog as schema-validated content collections; structured case studies (problem/design/outcome), blog with tag pages, reading time, RSS feed (drafts excluded), sitemap, and Shiki dual-theme code highlighting.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
---
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
import { getPosts, formatDate } from "../../lib/blog";
|
||||
import { readingTime } from "../../lib/reading";
|
||||
import { render } from "astro:content";
|
||||
import type { GetStaticPaths } from "astro";
|
||||
|
||||
export const getStaticPaths = (async () => {
|
||||
const posts = await getPosts();
|
||||
return posts.map((entry) => ({ params: { slug: entry.id }, props: { entry } }));
|
||||
}) satisfies GetStaticPaths;
|
||||
|
||||
const { entry } = Astro.props;
|
||||
const { Content } = await render(entry);
|
||||
---
|
||||
|
||||
<Layout title={entry.data.title} path={`/blog/${entry.id}`} description={entry.data.summary}>
|
||||
<article class="post">
|
||||
<div class="container post__inner">
|
||||
<a class="post__back mono" href="/blog">← All posts</a>
|
||||
|
||||
<header class="post__head" data-reveal>
|
||||
<div class="post__meta mono">
|
||||
<time datetime={entry.data.date.toISOString()}>{formatDate(entry.data.date)}</time>
|
||||
<span class="post__sep">·</span>
|
||||
<span>{readingTime(entry.body)}</span>
|
||||
</div>
|
||||
<h1 class="post__title">{entry.data.title}</h1>
|
||||
<ul class="post__tags">
|
||||
{entry.data.tags.map((t) => (
|
||||
<li><a class="tag" href={`/blog/tags/${t}`}>{t}</a></li>
|
||||
))}
|
||||
</ul>
|
||||
</header>
|
||||
|
||||
<div class="prose post__body" data-reveal>
|
||||
<Content />
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
.post__inner {
|
||||
max-width: 48rem;
|
||||
padding-block: var(--space-7);
|
||||
}
|
||||
.post__back {
|
||||
display: inline-block;
|
||||
font-size: var(--step--1);
|
||||
color: var(--text-dim);
|
||||
margin-bottom: var(--space-6);
|
||||
}
|
||||
.post__back:hover {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
.post__meta {
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-faint);
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.post__sep {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.post__title {
|
||||
margin-top: var(--space-3);
|
||||
font-size: var(--step-4);
|
||||
max-width: 26ch;
|
||||
}
|
||||
.post__tags {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: var(--space-4) 0 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
.post__tags a:hover {
|
||||
border-color: var(--accent-line);
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
.post__body {
|
||||
margin-top: var(--space-7);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user