Changeset fc229f5 in Klonkt


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@…>

Files:
1 added
5 edited

Legend:

Unmodified
Added
Removed
  • CHANGELOG.de.md

    r2abd3d2 rfc229f5  
    4040
    4141### Behoben
     42- **Erwähnungen, Hashtags und Links in Klammern funktionieren jetzt.** Eine Erwähnung wie
     43  `(@benutzer@server)`, ein `(#hashtag)` oder eine URL in Klammern föderierte als reiner Text —
     44  und die erwähnte Person wurde nie benachrichtigt. Sie verlinken (und benachrichtigen) jetzt wie
     45  ohne Klammern.
    4246- **Nackte Webadressen werden im Fediverse zu Links.** Eine einfache URL in einem Beitrag oder einer
    4347  Antwort föderiert jetzt als klickbarer Link statt als reiner Text.
  • CHANGELOG.md

    r2abd3d2 rfc229f5  
    3535
    3636### Fixed
     37- **Mentions, hashtags and links inside brackets now work.** A mention like `(@user@server)`, a
     38  `(#hashtag)` or a bracketed URL federated as plain text — and the mentioned person was never
     39  notified. They now link (and notify) like their unbracketed forms.
    3740- **Plain web addresses become links on the fediverse.** A bare URL typed in a post or reply now
    3841  federates as a clickable link instead of plain text.
  • CHANGELOG.nl.md

    r2abd3d2 rfc229f5  
    3838
    3939### Opgelost
     40- **Vermeldingen, hashtags en links tussen haakjes werken nu.** Een vermelding als
     41  `(@gebruiker@server)`, een `(#hashtag)` of een URL tussen haakjes federeerde als platte tekst —
     42  en de genoemde persoon kreeg nooit een melding. Ze linken (en melden) nu net als zonder haakjes.
    4043- **Kale webadressen worden links op de fediverse.** Een losse URL in een post of reactie federeert
    4144  nu als klikbare link in plaats van platte tekst.
  • 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  }
  • test/url-linkify.test.js

    r2abd3d2 rfc229f5  
    4040});
    4141
     42test('a URL inside parentheses links, closing paren stays outside', () => {
     43  const n = note('<p>site (https://example.com/a) hier</p>');
     44  assert.match(n.content, /\(<a href="https:\/\/example\.com\/a"[^>]*>https:\/\/example\.com\/a<\/a>\)/);
     45});
     46
     47test('a hashtag inside parentheses still links; quoted attribute values never match', () => {
     48  const n = note('<p>leuk (#jazz) toch</p>');
     49  assert.match(n.content, /\(<a [^>]*class="mention hashtag"[^>]*>#jazz<\/a>\)/);
     50  // A quote precedes attribute values — the prefix class must NOT include quotes.
     51  const n2 = note('<p>plaatje <a href="https://x.example/#frag">link</a></p>');
     52  assert.equal((n2.content.match(/<a /g) || []).length, 1, 'href value untouched');
     53});
     54
    4255test('attribute values are untouched, standalone URLs still link (image stripped from content)', () => {
    4356  const n = note('<p><img src="https://cdn.example.com/pic.png" alt=""> and https://example.org</p>');
Note: See TracChangeset for help on using the changeset viewer.