Ignore:
Timestamp:
07/01/2026 10:20:25 PM (2 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
e499b71
Parents:
2abd3d2
Message:

fix(fediverse): bracketed mentions/hashtags/URLs link and notify

The mention/hashtag/URL prefix classes required whitespace or '>' before the
token, so "(@user@server)", "(#tag)" and "(https://…)" federated as plain text —
and a bracketed mention's target was never notified (real-world miss on a reply).
Opening brackets are now valid prefixes. Quote characters are deliberately NOT in
the class: a quote precedes attribute values, which must never match.

  • src/services/ActivityPubService.js — prefix class [\s>] → [\s>([{] in linkHashtags, linkUrls, and both mention regexes (scan + per-handle replace).
  • test/url-linkify.test.js — bracketed URL + hashtag link; attribute values stay untouched.
  • CHANGELOG(.nl/.de).md — under Fixed.

Closes prutfolio-src-ovj.

Co-Authored-By: Claude <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    r2abd3d2 rfc229f5  
    14021402// Turn #hashtags in reply text into Mastodon-style hashtag links (clickable + federated).
    14031403function linkHashtags(base, html) {
    1404   return String(html || '').replace(/(^|[\s>])#([\p{L}\p{M}\p{N}_]+)/gu, (m, pre, tag) =>
     1404  // Prefix: start / whitespace / '>' / opening bracket — "(#tag" is a tag too. NO quote
     1405  // chars in this class: a quote precedes attribute values (alt="#…"), which must not match.
     1406  return String(html || '').replace(/(^|[\s>([{])#([\p{L}\p{M}\p{N}_]+)/gu, (m, pre, tag) =>
    14051407    `${pre}<a href="${base}/tag/${encodeURIComponent(tag.toLowerCase())}" class="mention hashtag" rel="tag">#${tag}</a>`);
    14061408}
     
    14131415  for (let i = 0; i < parts.length; i++) {
    14141416    if (/^<a\b/i.test(parts[i])) continue; // already a link → leave as-is
    1415     parts[i] = parts[i].replace(/(^|[\s>])(https?:\/\/[^\s<]+?)([.,;:!?)\]»]*)(?=$|[\s<])/g,
     1417    parts[i] = parts[i].replace(/(^|[\s>([{])(https?:\/\/[^\s<]+?)([.,;:!?)\]»]*)(?=$|[\s<])/g,
    14161418      (m, pre, url, trail) => `${pre}<a href="${url.replace(/"/g, '%22')}" rel="nofollow noopener" target="_blank">${url}</a>${trail}`);
    14171419  }
     
    14851487  const inboxes = [];
    14861488  const handles = new Set();
    1487   const re = /(^|[\s>])@([\p{L}\p{M}\p{N}_.-]+@[\p{L}\p{M}\p{N}.-]+)/gu;
     1489  // Prefix also allows opening brackets — "(@user@host + me)" is a mention too (real-world
     1490  // miss: a bracketed mention federated as plain text and its target was never notified).
     1491  const re = /(^|[\s>([{])@([\p{L}\p{M}\p{N}_.-]+@[\p{L}\p{M}\p{N}.-]+)/gu;
    14881492  let m;
    14891493  while ((m = re.exec(html || ''))) handles.add(m[2]);
     
    14981502    const profileUrl = actorInfo(actor, actorUri).url || actorUri; // human profile page → the link href
    14991503    const esc = h.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    1500     out = out.replace(new RegExp('(^|[\\s>])@' + esc + '(?![\\p{L}\\p{M}\\p{N}_.-])', 'gu'),
     1504    out = out.replace(new RegExp('(^|[\\s>([{])@' + esc + '(?![\\p{L}\\p{M}\\p{N}_.-])', 'gu'),
    15011505      (full, pre) => `${pre}<a href="${profileUrl}" class="u-url mention" data-actor="${actorUri}">@${h}</a>`);
    15021506  }
Note: See TracChangeset for help on using the changeset viewer.