feat: show view count badge next to reading time

- GET /api/views/counts/ returns { slug: count } for all published posts
- Eye-icon badge on post cards and post header, filled client-side;
  space is reserved so there is no layout shift, hidden if the API fails

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 09:21:21 +03:00
parent a32c0d0911
commit b1f2c4c875
13 changed files with 252 additions and 38 deletions
+35 -1
View File
@@ -8,10 +8,12 @@ export interface Props {
updatedDate?: Date;
/** Reading time in minutes, shown after the dates. */
minutes?: number;
/** Post slug: shows a view-count badge filled from /api/views/counts/ after load. */
viewsSlug?: string;
class?: string;
}
const { pubDate, updatedDate, minutes, class: className } = Astro.props;
const { pubDate, updatedDate, minutes, viewsSlug, class: className } = Astro.props;
const { primary, secondary } = dateBadges(pubDate, updatedDate);
const meta = (b: DateBadge) =>
@@ -38,4 +40,36 @@ const s = secondary && meta(secondary);
{fmt(blog.card.readingTime, { n: minutes })}
</Badge>
)}
{viewsSlug && (
// Space is reserved (invisible) so filling it in causes no layout shift; stays hidden if the API is down.
<Badge
variant="outline"
icon="tabler:eye"
class="invisible"
data-views-slug={viewsSlug}
data-views-template={blog.card.views}
title={blog.card.viewsLabel}
>
<span data-views-text>{fmt(blog.card.views, { n: '–' })}</span>
</Badge>
)}
</div>
<script>
const badges = document.querySelectorAll<HTMLElement>('[data-views-slug]');
if (badges.length) {
fetch('/api/views/counts/')
.then((r) => (r.ok ? r.json() : Promise.reject()))
.then((counts: Record<string, number>) => {
const nf = new Intl.NumberFormat('tr-TR');
for (const el of badges) {
const n = counts[el.dataset.viewsSlug ?? ''] ?? 0;
const text = el.querySelector('[data-views-text]');
if (text)
text.textContent = (el.dataset.viewsTemplate ?? '{n}').replace('{n}', nf.format(n));
el.classList.remove('invisible');
}
})
.catch(() => {});
}
</script>
+1
View File
@@ -59,6 +59,7 @@ const row = layout === 'row';
pubDate={pubDate}
updatedDate={updatedDate}
minutes={readingMinutes(post)}
viewsSlug={post.id}
class="mt-auto pt-2"
/>
</div>