Ignore:
Timestamp:
06/27/2026 11:52:49 AM (2 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
7cea873
Parents:
30f843c
Message:

fix(fedi): hashtags in replies federate as Hashtag tags + clickable links

  • services/ActivityPubService.js — #tags typed in a fediverse reply were sent as plain text. deliverReply now links them (Mastodon-style <a class="mention hashtag" rel="tag">), and buildReplyNote adds matching {type:'Hashtag', href:/tag/x, name:'#x'} entries to the Note's tag array, so they are clickable + searchable on Mastodon and link to our /tag page.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    r30f843c r6bc2ca7e  
    896896
    897897// Build one of OUR outbound reply Notes from an ap_outbox row.
     898// Turn #hashtags in reply text into Mastodon-style hashtag links (clickable + federated).
     899function linkHashtags(base, html) {
     900  return String(html || '').replace(/(^|[\s>])#([A-Za-z0-9_]+)/g, (m, pre, tag) =>
     901    `${pre}<a href="${base}/tag/${encodeURIComponent(tag.toLowerCase())}" class="mention hashtag" rel="tag">#${tag}</a>`);
     902}
     903// Extract the AP Hashtag tag objects from already-linked reply content.
     904function hashtagTags(base, content) {
     905  const tags = [], seen = new Set();
     906  const re = /class="[^"]*\bhashtag\b[^"]*"[^>]*>#([A-Za-z0-9_]+)</gi;
     907  let m;
     908  while ((m = re.exec(content || ''))) {
     909    const k = m[1].toLowerCase();
     910    if (seen.has(k)) continue; seen.add(k);
     911    tags.push({ type: 'Hashtag', href: `${base}/tag/${encodeURIComponent(k)}`, name: '#' + m[1] });
     912  }
     913  return tags;
     914}
     915
    898916export function buildReplyNote(base, site, row) {
    899917  const me = actorId(base, site.slug);
     
    908926    to: row.to_actor ? [row.to_actor] : [PUBLIC],
    909927    cc: [PUBLIC, `${me}/followers`],
    910     tag: row.to_actor ? [{ type: 'Mention', href: row.to_actor, name: row.to_handle }] : [],
     928    tag: [
     929      ...(row.to_actor ? [{ type: 'Mention', href: row.to_actor, name: row.to_handle }] : []),
     930      ...hashtagTags(base, row.content),
     931    ],
    911932  };
    912933}
     
    931952  const mention = parent.actor_uri
    932953    ? `<a href="${escHtml(parent.actor_url || parent.actor_uri)}" class="u-url mention">${escHtml(handle)}</a> ` : '';
    933   const content = `<p>${mention}${body}</p>`;
     954  const content = `<p>${mention}${linkHashtags(base, body)}</p>`;
    934955  // Dedup: skip if the exact same reply was already sent (double-submit guard).
    935956  const dup = db.prepare('SELECT 1 FROM ap_outbox WHERE site_slug = ? AND IFNULL(in_reply_to, \'\') = ? AND content = ? LIMIT 1')
Note: See TracChangeset for help on using the changeset viewer.