Changeset 2e62848 in Klonkt


Ignore:
Timestamp:
06/28/2026 10:07:38 AM (2 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
37a297d
Parents:
907a013
Message:

fix(fedi): post tags federate as visible hashtags (+ CamelCase for spaces)

  • services/ActivityPubService.js — buildNote now (a) normalises post.tags whether it arrives as an array, a JSON string, or a comma string (it was a JSON string at federation → the old Array.isArray check silently dropped the whole tags field), and (b) appends the tags-field hashtags to the Note content as rel="tag" links so Mastodon renders them (a Hashtag only in the tag array isn't shown inline). Multi-word tags become CamelCase (#LiveMusic) for display; the slug/href stays lowercase. Deduped against hashtags already inline in the body.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    r907a013 r2e62848  
    229229  body = body.replace(/\r?\n/g, '<br>');
    230230  body = linkHashtags(base, body); // link inline #hashtags in the post body too
     231  // Append the tags-field hashtags to the content so Mastodon renders them as clickable
     232  // hashtags (a Hashtag that's only in the `tag` array isn't shown inline). CamelCase
     233  // multi-word tags; skip any already present inline in the body.
     234  {
     235    const inlineTags = new Set(hashtagTags(base, body).map((h) => h.name.slice(1).toLowerCase()));
     236    const addSeen = new Set();
     237    const tagLinks = normalizeTags(post.tags).map(tagParts).filter(Boolean)
     238      .filter((p) => !inlineTags.has(p.slug) && !addSeen.has(p.slug) && addSeen.add(p.slug))
     239      .map((p) => `<a href="${base}/tag/${encodeURIComponent(p.slug)}" class="mention hashtag" rel="tag">#${p.label}</a>`);
     240    if (tagLinks.length) body += `<p>${tagLinks.join(' ')}</p>`;
     241  }
    231242  const seen = new Set();
    232243  const attachment = urls.filter(Boolean)
     
    915926}
    916927
     928// Normalise a post's tags field (array, JSON-string, or comma-string) to an array.
     929function normalizeTags(t) {
     930  if (Array.isArray(t)) return t;
     931  if (typeof t === 'string') {
     932    const s = t.trim(); if (!s) return [];
     933    if (s[0] === '[') { try { const a = JSON.parse(s); return Array.isArray(a) ? a : []; } catch { /* fall through */ } }
     934    return s.split(',').map((x) => x.trim()).filter(Boolean);
     935  }
     936  return [];
     937}
     938// A tag → { label, slug }. Multi-word tags become CamelCase (#LiveMusic) for the display
     939// name (Mastodon hashtags can't contain spaces; CamelCase is the accessibility norm); the
     940// slug/href stays lowercase ("livemusic").
     941function tagParts(raw) {
     942  const words = String(raw || '').trim().split(/[\s_]+/).map((w) => w.replace(/[^A-Za-z0-9]/g, '')).filter(Boolean);
     943  if (!words.length) return null;
     944  const slug = words.join('').toLowerCase();
     945  if (!slug) return null;
     946  const label = words.length > 1 ? words.map((w) => w[0].toUpperCase() + w.slice(1)).join('') : words[0];
     947  return { label, slug };
     948}
    917949// Merge a post's tags field + the #hashtags linked inline in its body into one deduped
    918950// Hashtag tag list (with hrefs to our /tag page).
    919951function buildHashtagList(base, tagsField, content) {
    920952  const out = [], seen = new Set();
    921   for (const t of (Array.isArray(tagsField) ? tagsField : [])) {
    922     const name = String(t).replace(/\s+/g, ''); if (!name) continue;
    923     const k = name.toLowerCase(); if (seen.has(k)) continue; seen.add(k);
    924     out.push({ type: 'Hashtag', href: `${base}/tag/${encodeURIComponent(k)}`, name: '#' + name });
     953  for (const t of normalizeTags(tagsField)) {
     954    const p = tagParts(t); if (!p || seen.has(p.slug)) continue; seen.add(p.slug);
     955    out.push({ type: 'Hashtag', href: `${base}/tag/${encodeURIComponent(p.slug)}`, name: '#' + p.label });
    925956  }
    926957  for (const h of hashtagTags(base, content)) {
Note: See TracChangeset for help on using the changeset viewer.