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:
@@ -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>
|
||||
|
||||
@@ -59,6 +59,7 @@ const row = layout === 'row';
|
||||
pubDate={pubDate}
|
||||
updatedDate={updatedDate}
|
||||
minutes={readingMinutes(post)}
|
||||
viewsSlug={post.id}
|
||||
class="mt-auto pt-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user