feat(blog): comment count badge on post overview
Deploy / verify (push) Successful in 38s
Deploy / deploy (push) Successful in 59s

PostCard now shows a small comment-count pill in the top-left of the
image when count > 0. BlogOverview bulk-fetches counts for the visible
posts via the new `/api/comments/counts?ids=…` endpoint and passes
them down. One request per page render, regardless of card count.

- New helper `getCommentCounts()` in `cms.ts`
- `BlogOverview.svelte` derives slugs from posts, fires bulk fetch
  in `$effect`, hands the result map to `PostCard`
- `PostCard.svelte` accepts `commentCount?: number | null`, renders
  pill only when value is a positive number

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-04-28 13:26:09 +02:00
parent 8f45094f64
commit 8f791ebc54
3 changed files with 89 additions and 0 deletions
+41
View File
@@ -8,6 +8,7 @@
import { postHref } from "$lib/blog-utils";
import { t as tStatic, T } from "$lib/translations";
import type { Translations } from "$lib/translations";
import { env } from "$env/dynamic/public";
interface TagEntry {
_slug?: string;
@@ -113,6 +114,45 @@
}
const totalCount = $derived(totalPosts);
// Bulk-fetch comment counts for the visible posts. Uses a single
// `/api/comments/counts` request keyed on the post slug. Failures keep the
// map empty so cards just don't render the badge.
let commentCounts = $state<Record<string, number>>({});
function postSlug(p: PostEntry): string {
const raw = (p as { slug?: string; _slug?: string }).slug ?? p._slug ?? "";
return raw.replace(/^\//, "").trim();
}
$effect(() => {
const slugs = posts.map(postSlug).filter(Boolean);
if (slugs.length === 0) {
commentCounts = {};
return;
}
const cmsBase = (
env.PUBLIC_CMS_URL ||
import.meta.env.PUBLIC_CMS_URL ||
""
).replace(/\/$/, "");
if (!cmsBase) return;
const url = new URL(`${cmsBase}/api/comments/counts`);
url.searchParams.set("ids", slugs.join(","));
let cancelled = false;
fetch(url.toString())
.then((r) => (r.ok ? r.json() : { counts: {} }))
.then((d) => {
if (cancelled) return;
commentCounts = (d?.counts ?? {}) as Record<string, number>;
})
.catch(() => {
if (!cancelled) commentCounts = {};
});
return () => {
cancelled = true;
};
});
</script>
<div class="blog-overview">
@@ -258,6 +298,7 @@
post={post}
href={postHref(post)}
tagBase={`${basePath}/tag`}
commentCount={commentCounts[postSlug(post)] ?? null}
/>
{/each}
</div>