fix: include interaction target URL in timeline content

Interaction types with comment text (e.g. repost with a comment) were
showing only the comment, losing the repost-of/bookmark-of/like-of URL.
Now always includes the target URL for interaction types, combining it
with any body text.
This commit is contained in:
Ricardo
2026-03-27 16:26:13 +01:00
parent f1ad18d92d
commit 6436763dab
2 changed files with 40 additions and 19 deletions

View File

@@ -290,49 +290,70 @@ export function createSyndicator(plugin) {
// ─── Timeline helpers ───────────────────────────────────────────────────────
/**
* Build content from JF2 properties. Synthesizes content for interaction
* types (likes, bookmarks, reposts) that have no body text.
* Build content from JF2 properties for the ap_timeline entry.
* For interaction types (likes, bookmarks, reposts), always includes
* the target URL — even when there's comment text alongside it.
*/
function buildTimelineContent(properties) {
const content = properties.content;
if (content) {
if (typeof content === "string") return { text: content, html: `<p>${content}</p>` };
if (content.text || content.html) {
return {
text: content.text || content.value || "",
html: content.html || content.text || content.value || "",
};
const esc = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
// Extract any existing body content
const raw = properties.content;
let bodyText = "";
let bodyHtml = "";
if (raw) {
if (typeof raw === "string") {
bodyText = raw;
bodyHtml = `<p>${raw}</p>`;
} else {
bodyText = raw.text || raw.value || "";
bodyHtml = raw.html || raw.text || raw.value || "";
}
}
// Synthesize for interaction types
const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
// Interaction types: prepend label + target URL, append any comment
const likeOf = properties["like-of"];
if (likeOf) {
const prefix = `Liked: ${likeOf}`;
const prefixHtml = `<p>Liked: <a href="${esc(likeOf)}">${esc(likeOf)}</a></p>`;
return {
text: `Liked: ${likeOf}`,
html: `<p>Liked: <a href="${esc(likeOf)}">${esc(likeOf)}</a></p>`,
text: bodyText ? `${prefix}\n\n${bodyText}` : prefix,
html: bodyText ? `${prefixHtml}\n${bodyHtml}` : prefixHtml,
};
}
const bookmarkOf = properties["bookmark-of"];
if (bookmarkOf) {
const label = properties.name || bookmarkOf;
const prefix = `Bookmarked: ${label}`;
const prefixHtml = `<p>Bookmarked: <a href="${esc(bookmarkOf)}">${esc(label)}</a></p>`;
return {
text: `Bookmarked: ${label}`,
html: `<p>Bookmarked: <a href="${esc(bookmarkOf)}">${esc(label)}</a></p>`,
text: bodyText ? `${prefix}\n\n${bodyText}` : prefix,
html: bodyText ? `${prefixHtml}\n${bodyHtml}` : prefixHtml,
};
}
const repostOf = properties["repost-of"];
if (repostOf) {
const label = properties.name || repostOf;
const prefix = `Reposted: ${label}`;
const prefixHtml = `<p>Reposted: <a href="${esc(repostOf)}">${esc(label)}</a></p>`;
return {
text: `Reposted: ${label}`,
html: `<p>Reposted: <a href="${esc(repostOf)}">${esc(label)}</a></p>`,
text: bodyText ? `${prefix}\n\n${bodyText}` : prefix,
html: bodyText ? `${prefixHtml}\n${bodyHtml}` : prefixHtml,
};
}
// Regular post — return body content as-is
if (bodyText || bodyHtml) {
return { text: bodyText, html: bodyHtml };
}
// Article with title but no body
if (properties.name) {
return { text: properties.name, html: `<p>${esc(properties.name)}</p>` };
}
return { text: "", html: "" };
}

View File

@@ -1,6 +1,6 @@
{
"name": "@rmdes/indiekit-endpoint-activitypub",
"version": "3.10.2",
"version": "3.10.3",
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
"keywords": [
"indiekit",