From 2d2dcaec7de01d5015cb7ce7409e0c8da7646b5c Mon Sep 17 00:00:00 2001 From: Ricardo Date: Tue, 3 Mar 2026 14:30:40 +0100 Subject: [PATCH] feat: interaction counts on timeline cards (Release 5) Extract reply/boost/like counts from AP Collections (getReplies, getLikes, getShares) and Mastodon API (replies_count, reblogs_count, favourites_count). Display counts next to interaction buttons with optimistic updates on like/boost actions. Confab-Link: http://localhost:8080/sessions/e9d666ac-3c90-4298-9e92-9ac9d142bc06 --- assets/reader.css | 8 ++++++++ lib/controllers/explore-utils.js | 5 +++++ lib/timeline-store.js | 16 ++++++++++++++++ package.json | 2 +- views/partials/ap-item-card.njk | 25 +++++++++++++++++-------- 5 files changed, 47 insertions(+), 9 deletions(-) diff --git a/assets/reader.css b/assets/reader.css index 432882c..8fcbd66 100644 --- a/assets/reader.css +++ b/assets/reader.css @@ -763,6 +763,14 @@ opacity: 0.6; } +/* Interaction counts */ +.ap-card__count { + font-size: var(--font-size-xs); + color: var(--color-on-offset); + margin-left: 0.25rem; + font-variant-numeric: tabular-nums; +} + /* Error message */ .ap-card__action-error { color: var(--color-error); diff --git a/lib/controllers/explore-utils.js b/lib/controllers/explore-utils.js index 197ee94..e8b11bc 100644 --- a/lib/controllers/explore-utils.js +++ b/lib/controllers/explore-utils.js @@ -133,6 +133,11 @@ export function mapMastodonStatusToItem(status, instance) { video, audio, inReplyTo: status.in_reply_to_id ? `https://${instance}/web/statuses/${status.in_reply_to_id}` : "", + counts: { + replies: status.replies_count ?? null, + boosts: status.reblogs_count ?? null, + likes: status.favourites_count ?? null, + }, createdAt: new Date().toISOString(), _explore: true, }; diff --git a/lib/timeline-store.js b/lib/timeline-store.js index e843ccb..34d6bd9 100644 --- a/lib/timeline-store.js +++ b/lib/timeline-store.js @@ -279,6 +279,21 @@ export async function extractObjectData(object, options = {}) { // Quote URL — Fedify reads quoteUrl / _misskey_quote / quoteUri const quoteUrl = object.quoteUrl?.href || ""; + // Interaction counts — extract from AP Collection objects + const counts = { replies: null, boosts: null, likes: null }; + try { + const replies = await object.getReplies?.(loaderOpts); + if (replies?.totalItems != null) counts.replies = replies.totalItems; + } catch { /* ignore — collection may not exist */ } + try { + const likes = await object.getLikes?.(loaderOpts); + if (likes?.totalItems != null) counts.likes = likes.totalItems; + } catch { /* ignore */ } + try { + const shares = await object.getShares?.(loaderOpts); + if (shares?.totalItems != null) counts.boosts = shares.totalItems; + } catch { /* ignore */ } + // Build base timeline item const item = { uid, @@ -298,6 +313,7 @@ export async function extractObjectData(object, options = {}) { audio, inReplyTo, quoteUrl, + counts, createdAt: new Date().toISOString() }; diff --git a/package.json b/package.json index e84f176..0b26c5f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rmdes/indiekit-endpoint-activitypub", - "version": "2.5.3", + "version": "2.5.4", "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.", "keywords": [ "indiekit", diff --git a/views/partials/ap-item-card.njk b/views/partials/ap-item-card.njk index b31b578..7cc7065 100644 --- a/views/partials/ap-item-card.njk +++ b/views/partials/ap-item-card.njk @@ -149,6 +149,9 @@ {% set itemUid = item.uid or item.url or item.originalUrl %} {% set isLiked = interactionMap[itemUid].like if interactionMap[itemUid] else false %} {% set isBoosted = interactionMap[itemUid].boost if interactionMap[itemUid] else false %} + {% set replyCount = item.counts.replies if item.counts and item.counts.replies != null else null %} + {% set boostCount = item.counts.boosts if item.counts and item.counts.boosts != null else null %} + {% set likeCount = item.counts.likes if item.counts and item.counts.likes != null else null %}