Fixes: 1. media.ts: wrap placeholder generation in try-catch 2. toolbar.ts: check r.ok, display error message in popover
130 lines
2.9 KiB
Plaintext
130 lines
2.9 KiB
Plaintext
---
|
|
import {
|
|
getTerm,
|
|
getEmDashCollection,
|
|
getTermsForEntries,
|
|
decodeSlug,
|
|
} from "emdash";
|
|
import Base from "../../layouts/Base.astro";
|
|
import PostCard from "../../components/PostCard.astro";
|
|
import { getReadingTime } from "../../utils/reading-time";
|
|
|
|
const slug = decodeSlug(Astro.params.slug);
|
|
const term = slug ? await getTerm("category", slug) : null;
|
|
|
|
if (!term) {
|
|
return Astro.redirect("/404");
|
|
}
|
|
|
|
const { entries: posts, cacheHint } = await getEmDashCollection("posts", {
|
|
where: { category: term.slug },
|
|
orderBy: { published_at: "desc" },
|
|
});
|
|
|
|
Astro.cache.set(cacheHint);
|
|
|
|
// Single batched query for tags on every post in this category, rather
|
|
// than calling getEntryTerms() per post (which would be one round-trip
|
|
// per post).
|
|
const tagsByEntry = await getTermsForEntries(
|
|
"posts",
|
|
posts.map((p) => p.data.id),
|
|
"tag",
|
|
);
|
|
const filteredPosts = posts.map((post) => ({
|
|
post,
|
|
tags: tagsByEntry.get(post.data.id) ?? [],
|
|
}));
|
|
---
|
|
|
|
<Base title={`${term.label} posts`} description={`All posts in ${term.label}`}>
|
|
<section class="archive-section">
|
|
<header class="archive-header">
|
|
<span class="archive-label">Category</span>
|
|
<h1 class="archive-title">{term.label}</h1>
|
|
<p class="archive-count">
|
|
{filteredPosts.length}
|
|
{filteredPosts.length === 1 ? "post" : "posts"}
|
|
</p>
|
|
</header>
|
|
|
|
{
|
|
filteredPosts.length === 0 ? (
|
|
<p class="no-posts">No posts in this category yet.</p>
|
|
) : (
|
|
<div class="posts-grid">
|
|
{filteredPosts.map(({ post, tags }) => (
|
|
<PostCard
|
|
title={post.data.title}
|
|
excerpt={post.data.excerpt}
|
|
featuredImage={post.data.featured_image}
|
|
href={`/posts/${post.id}`}
|
|
date={post.data.publishedAt ?? undefined}
|
|
readingTime={getReadingTime(post.data.content)}
|
|
tags={tags.map((t) => ({ slug: t.slug, label: t.label }))}
|
|
/>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
</section>
|
|
</Base>
|
|
|
|
<style>
|
|
.archive-section {
|
|
max-width: var(--wide-width);
|
|
margin: 0 auto;
|
|
padding: var(--spacing-12) var(--spacing-6);
|
|
}
|
|
|
|
.archive-header {
|
|
margin-bottom: var(--spacing-12);
|
|
padding-bottom: var(--spacing-8);
|
|
border-bottom: 1px solid var(--color-border-subtle);
|
|
}
|
|
|
|
.archive-label {
|
|
display: block;
|
|
font-size: var(--font-size-xs);
|
|
font-weight: 500;
|
|
color: var(--color-accent);
|
|
text-transform: uppercase;
|
|
letter-spacing: var(--tracking-wider);
|
|
margin-bottom: var(--spacing-2);
|
|
}
|
|
|
|
.archive-title {
|
|
font-size: var(--font-size-4xl);
|
|
font-weight: 700;
|
|
letter-spacing: var(--tracking-tight);
|
|
margin-bottom: var(--spacing-2);
|
|
}
|
|
|
|
.archive-count {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-muted);
|
|
}
|
|
|
|
.posts-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: var(--spacing-12) var(--spacing-8);
|
|
}
|
|
|
|
.no-posts {
|
|
color: var(--color-muted);
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.posts-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.posts-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|