first commit
This commit is contained in:
268
demos/preview/src/pages/posts/index.astro
Normal file
268
demos/preview/src/pages/posts/index.astro
Normal file
@@ -0,0 +1,268 @@
|
||||
---
|
||||
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>
|
||||
Reference in New Issue
Block a user