269 lines
5.9 KiB
Plaintext
269 lines
5.9 KiB
Plaintext
---
|
|
import { getEmDashCollection, getEntryTerms } from "emdash";
|
|
import Base from "../../layouts/Base.astro";
|
|
import { getReadingTime } from "../../utils/reading-time";
|
|
|
|
const { entries: posts, cacheHint } = await getEmDashCollection("posts");
|
|
|
|
Astro.cache.set(cacheHint);
|
|
|
|
const sortedPosts = posts.toSorted((a, b) => {
|
|
const dateA = a.data.publishedAt?.getTime() ?? 0;
|
|
const dateB = b.data.publishedAt?.getTime() ?? 0;
|
|
return dateB - dateA;
|
|
});
|
|
|
|
// Fetch tags for each post (bylines are already hydrated by getEmDashCollection)
|
|
const postsWithTags = await Promise.all(
|
|
sortedPosts.map(async (post) => {
|
|
const tags = await getEntryTerms("posts", post.data.id, "tag");
|
|
const bylines = post.data.bylines ?? [];
|
|
return { post, tags, bylines };
|
|
})
|
|
);
|
|
|
|
const formatDate = (date: Date) =>
|
|
date.toLocaleDateString("en-US", {
|
|
year: "numeric",
|
|
month: "long",
|
|
day: "numeric",
|
|
});
|
|
---
|
|
|
|
<Base title="All Posts" description="Browse all blog posts">
|
|
<div class="posts-page">
|
|
<header class="page-header">
|
|
<h1 class="page-title">All Posts</h1>
|
|
<p class="page-description">
|
|
{posts.length}
|
|
{posts.length === 1 ? "article" : "articles"}
|
|
</p>
|
|
</header>
|
|
|
|
{
|
|
sortedPosts.length === 0 ? (
|
|
<p class="empty">No posts yet.</p>
|
|
) : (
|
|
<div class="posts-list">
|
|
{postsWithTags.map(({ post, tags, bylines }) => (
|
|
<article class="post-item">
|
|
<a href={`/posts/${post.id}`} class="post-link">
|
|
<div class="post-meta">
|
|
{bylines.length > 0 && (
|
|
<>
|
|
<div class="post-bylines">
|
|
{bylines.slice(0, 2).map((credit, index) => (
|
|
<>
|
|
{index > 0 && <span class="byline-sep">,</span>}
|
|
<span class="post-byline">
|
|
{credit.byline.avatarMediaId && (
|
|
<img
|
|
src={`/_emdash/api/media/file/${credit.byline.avatarMediaId}`}
|
|
alt={credit.byline.displayName}
|
|
class="post-byline-avatar"
|
|
/>
|
|
)}
|
|
<span class="post-byline-name">
|
|
{credit.byline.displayName}
|
|
</span>
|
|
</span>
|
|
</>
|
|
))}
|
|
{bylines.length > 2 && (
|
|
<span class="byline-more">+{bylines.length - 2}</span>
|
|
)}
|
|
</div>
|
|
<span class="meta-dot" />
|
|
</>
|
|
)}
|
|
{post.data.publishedAt && (
|
|
<time>{formatDate(post.data.publishedAt)}</time>
|
|
)}
|
|
{post.data.publishedAt && <span class="meta-dot" />}
|
|
<span>{getReadingTime(post.data.content)} min read</span>
|
|
</div>
|
|
<h2 class="post-title">{post.data.title}</h2>
|
|
{post.data.excerpt && (
|
|
<p class="post-excerpt">{post.data.excerpt}</p>
|
|
)}
|
|
</a>
|
|
{tags.length > 0 && (
|
|
<div class="post-tags">
|
|
{tags.slice(0, 3).map((t) => (
|
|
<a href={`/tag/${t.slug}`} class="post-tag">
|
|
{t.label}
|
|
</a>
|
|
))}
|
|
</div>
|
|
)}
|
|
</article>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
</div>
|
|
</Base>
|
|
|
|
<style>
|
|
.posts-page {
|
|
max-width: var(--content-width);
|
|
margin: 0 auto;
|
|
padding: var(--spacing-8) var(--spacing-6) var(--spacing-16);
|
|
}
|
|
|
|
.page-header {
|
|
margin-bottom: var(--spacing-12);
|
|
}
|
|
|
|
.page-title {
|
|
font-size: var(--font-size-4xl);
|
|
font-weight: 700;
|
|
letter-spacing: var(--tracking-tight);
|
|
margin-bottom: var(--spacing-2);
|
|
}
|
|
|
|
.page-description {
|
|
font-size: var(--font-size-lg);
|
|
color: var(--color-muted);
|
|
}
|
|
|
|
.empty {
|
|
color: var(--color-muted);
|
|
font-size: var(--font-size-lg);
|
|
}
|
|
|
|
.posts-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.post-item {
|
|
padding: var(--spacing-8) 0;
|
|
border-bottom: 1px solid var(--color-border-subtle);
|
|
}
|
|
|
|
.post-item:first-child {
|
|
padding-top: 0;
|
|
}
|
|
|
|
.post-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.post-link {
|
|
display: block;
|
|
text-decoration: none;
|
|
color: inherit;
|
|
}
|
|
|
|
.post-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-3);
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-muted);
|
|
margin-bottom: var(--spacing-2);
|
|
}
|
|
|
|
.meta-dot {
|
|
width: 3px;
|
|
height: 3px;
|
|
border-radius: 50%;
|
|
background: var(--color-muted);
|
|
}
|
|
|
|
/* Post bylines */
|
|
.post-bylines {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
}
|
|
|
|
.post-byline {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--spacing-1);
|
|
}
|
|
|
|
.post-byline-avatar {
|
|
width: var(--avatar-size-sm);
|
|
height: var(--avatar-size-sm);
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.post-byline-name {
|
|
font-weight: 500;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.byline-sep {
|
|
color: var(--color-muted);
|
|
margin-right: 2px;
|
|
}
|
|
|
|
.byline-more {
|
|
font-size: var(--font-size-xs);
|
|
color: var(--color-muted);
|
|
margin-left: 2px;
|
|
}
|
|
|
|
.post-title {
|
|
font-size: var(--font-size-2xl);
|
|
font-weight: 600;
|
|
line-height: var(--leading-snug);
|
|
letter-spacing: var(--tracking-snug);
|
|
margin-bottom: var(--spacing-2);
|
|
transition: color var(--transition-fast);
|
|
}
|
|
|
|
.post-link:hover .post-title {
|
|
color: var(--color-accent);
|
|
}
|
|
|
|
.post-excerpt {
|
|
font-size: var(--font-size-lg);
|
|
line-height: var(--leading-relaxed);
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.post-tags {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--spacing-2);
|
|
margin-top: var(--spacing-4);
|
|
}
|
|
|
|
.post-tag {
|
|
display: inline-block;
|
|
padding: var(--tag-padding-y) var(--spacing-3);
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-text-secondary);
|
|
background: var(--color-surface);
|
|
border-radius: var(--radius);
|
|
text-decoration: none;
|
|
transition:
|
|
color var(--transition-fast),
|
|
background var(--transition-fast);
|
|
}
|
|
|
|
.post-tag:hover {
|
|
color: var(--color-text);
|
|
background: var(--color-border);
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.posts-page {
|
|
padding: var(--spacing-6) var(--spacing-4) var(--spacing-12);
|
|
}
|
|
|
|
.page-title {
|
|
font-size: var(--font-size-3xl);
|
|
}
|
|
|
|
.post-title {
|
|
font-size: var(--font-size-xl);
|
|
}
|
|
}
|
|
</style>
|