Changeset ea3e722 in Klonkt


Ignore:
Timestamp:
08/09/2026 08:34:57 AM (4 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
5ae4d61
Parents:
d60c498
Message:

Een hashtag hoort te staan zoals hij geschreven is (Robins punt, 9-8)

Stond een tag zowel in het tagveld als in het lijf van de post, dan won de vorm
uit het veld. Die gaat door tagParts, want een hashtag mag geen spaties
bevatten en "Doen we Niet" wordt daar #DoenWeNiet van. Nodig voor een tag die
alleen in het veld staat -- maar als iemand in zijn tekst #DoenweNiet heeft
getypt is dat geen benadering meer maar de tag zelf, en die verdween als
duplicaat van de genormaliseerde vorm.

Nu gaat het lijf voor: de geschreven vorm wint, en het tagveld vult aan wat er
niet staat. De slug in de href blijft kleingeschreven -- dat is een adres, geen
naam.

En de uitgave-collectie gebruikt nu diezelfde lijst in plaats van een eigen: ze
miste de tags uit de tekst helemaal, en spelde de rest anders dan dezelfde post
elders deed. buildHashtagList en hashtagTags zijn daarvoor van
ActivityPubService naar ap-core verhuisd, want music/ mag daar niet uit
importeren.

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

Files:
4 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    rd60c498 rea3e722  
    2929import Blocklist from './BlocklistService.js';
    3030import * as Guardianship from './guardianship/index.js';
    31 import { PUBLIC, AP_CONTEXT, safeUrl, actorId, noteId, guessMediaType, normalizeTags, tagParts } from './ap-core.js';
     31import { PUBLIC, AP_CONTEXT, safeUrl, actorId, noteId, guessMediaType, normalizeTags, tagParts, hashtagTags, buildHashtagList } from './ap-core.js';
    3232// Doorgeven wat hier altijd vandaan kwam, zodat elke bestaande aanroep blijft werken.
    3333export { AP_CONTEXT, actorId, noteId, guessMediaType };
     
    29602960
    29612961// Extract the AP Hashtag tag objects from already-linked reply content.
    2962 function hashtagTags(base, content) {
    2963   const tags = [], seen = new Set();
    2964   const re = /class="[^"]*\bhashtag\b[^"]*"[^>]*>#([\p{L}\p{M}\p{N}_]+)</giu;
    2965   let m;
    2966   while ((m = re.exec(content || ''))) {
    2967     const k = m[1].toLowerCase();
    2968     if (seen.has(k)) continue; seen.add(k);
    2969     tags.push({ type: 'Hashtag', href: `${base}/tag/${encodeURIComponent(k)}`, name: '#' + m[1] });
    2970   }
    2971   return tags;
    2972 }
    29732962
    29742963// Normalise a post's tags field (array, JSON-string, or comma-string) to an array.
     
    29802969// Merge a post's tags field + the #hashtags linked inline in its body into one deduped
    29812970// Hashtag tag list (with hrefs to our /tag page).
    2982 function buildHashtagList(base, tagsField, content) {
    2983   const out = [], seen = new Set();
    2984   for (const t of normalizeTags(tagsField)) {
    2985     const p = tagParts(t); if (!p || seen.has(p.slug)) continue; seen.add(p.slug);
    2986     out.push({ type: 'Hashtag', href: `${base}/tag/${encodeURIComponent(p.slug)}`, name: '#' + p.label });
    2987   }
    2988   for (const h of hashtagTags(base, content)) {
    2989     const k = h.name.slice(1).toLowerCase(); if (seen.has(k)) continue; seen.add(k);
    2990     out.push(h);
    2991   }
    2992   return out;
    2993 }
     2971// hashtagTags en buildHashtagList staan sinds shaer-38y in ap-core: music/
     2972// heeft dezelfde lijst nodig en mag hier niet uit importeren.
    29942973
    29952974// Extract Mention tag objects from already-linked content (class="u-url mention").
  • src/services/ap-core.js

    rd60c498 rea3e722  
    124124  return { label, slug };
    125125}
     126
     127/**
     128 * De #hashtags die in het LIJF van een post gelinkt staan, zoals ze GESCHREVEN
     129 * zijn. De slug in de href is kleingeschreven -- dat is een adres -- maar de
     130 * naam niet: #DoenweNiet blijft #DoenweNiet.
     131 */
     132export function hashtagTags(base, content) {
     133  const tags = [], seen = new Set();
     134  const re = /class="[^"]*\bhashtag\b[^"]*"[^>]*>#([\p{L}\p{M}\p{N}_]+)</giu;
     135  let m;
     136  while ((m = re.exec(content || ''))) {
     137    const k = m[1].toLowerCase();
     138    if (seen.has(k)) continue; seen.add(k);
     139    tags.push({ type: 'Hashtag', href: `${base}/tag/${encodeURIComponent(k)}`, name: '#' + m[1] });
     140  }
     141  return tags;
     142}
     143
     144/**
     145 * Het tagveld van een post en de #hashtags uit het lijf, samen en ontdubbeld.
     146 *
     147 * HET LIJF GAAT VOOR (Robin, 9-8): staat een tag allebei, dan wint de vorm
     148 * zoals hij GESCHREVEN is. Het tagveld gaat door tagParts, en die maakt van
     149 * "Doen we Niet" het CamelCase #DoenWeNiet -- nodig, want een hashtag mag geen
     150 * spaties bevatten. Maar als iemand in zijn tekst #DoenweNiet heeft getypt is
     151 * dat geen benadering meer maar de tag zelf, en dan hoort die te staan zoals
     152 * hij er staat. Eerder won het veld, en verdween de geschreven vorm.
     153 */
     154export function buildHashtagList(base, tagsField, content) {
     155  const out = [], seen = new Set();
     156  for (const h of hashtagTags(base, content)) {
     157    const k = h.name.slice(1).toLowerCase(); if (seen.has(k)) continue; seen.add(k);
     158    out.push(h);
     159  }
     160  for (const t of normalizeTags(tagsField)) {
     161    const p = tagParts(t); if (!p || seen.has(p.slug)) continue; seen.add(p.slug);
     162    out.push({ type: 'Hashtag', href: `${base}/tag/${encodeURIComponent(p.slug)}`, name: '#' + p.label });
     163  }
     164  return out;
     165}
  • src/services/music/index.js

    rd60c498 rea3e722  
    1414
    1515import db from '../../config/database.js';
    16 import { AP_CONTEXT, PUBLIC, actorId, noteId, safeUrl, guessMediaType, normalizeTags, tagParts } from '../ap-core.js';
     16import { AP_CONTEXT, PUBLIC, actorId, noteId, safeUrl, guessMediaType, buildHashtagList } from '../ap-core.js';
    1717import { afleidenUitInsluitingen, ingeslotenPlaylists } from '../../assets/js/shared/post-music-type.js';
    1818
     
    377377  }
    378378
    379   const tags = hashtagsVanPost(base, post.tags);
     379  // Dezelfde lijst als de Note: het tagveld EN de hashtags uit het lijf, waarbij
     380  // de geschreven vorm voorgaat. Een eigen lijst hier zou de tags uit de tekst
     381  // missen en de rest anders spellen dan dezelfde post elders doet.
     382  const tags = buildHashtagList(base, post.tags, post.content);
    380383  if (tags.length) obj.tag = tags;
    381384
     
    386389  obj.context = noteId(base, post.id);
    387390  return obj;
    388 }
    389 
    390 /**
    391  * De tags van een post als AS2 Hashtags -- zelfde vorm als buildHashtagList.
    392  *
    393  * Met normalizeTags en niet met een eigen split: het veld staat als JSON-array
    394  * in de database, en op komma's splitsen leverde live `#["Doen we Niet"` op.
    395  * Dat is het soort fout dat niet omvalt maar onzin uitlevert.
    396  */
    397 function hashtagsVanPost(base, tagsField) {
    398   const uit = [], gezien = new Set();
    399   for (const t of normalizeTags(tagsField)) {
    400     const p = tagParts(t);
    401     if (!p || gezien.has(p.slug)) continue;
    402     gezien.add(p.slug);
    403     uit.push({ type: 'Hashtag', href: `${base}/tag/${encodeURIComponent(p.slug)}`, name: '#' + p.label });
    404   }
    405   return uit;
    406391}
    407392
  • test/music-uitgave.test.js

    rd60c498 rea3e722  
    7474  assert.equal(col.image.type, 'Image');
    7575  assert.equal(col.image.url, `${BASE}/media/hoes.jpg`);
    76   // Het tagveld is JSON in de database, en een tag van twee woorden wordt
    77   // CamelCase -- zelfde regel als elders, want het is nu dezelfde parser.
     76  // Het tagveld is JSON in de database. Een tag van twee woorden wordt
     77  // CamelCase, want een hashtag mag geen spaties bevatten.
    7878  assert.deepEqual(col.tag.map((t) => t.name), ['#lofi', '#ZolderTapes']);
    7979  assert.equal(col.tag[1].href, `${BASE}/tag/zoldertapes`);
     
    196196  assert.equal(M.buildPostTrackCollection(BASE, site, p).content, 'Opgenomen in de schuur.');
    197197});
     198
     199test('een hashtag uit het LIJF staat zoals hij geschreven is', () => {
     200  // Robins punt (9-8): #DoenweNiet in de tekst hoort #DoenweNiet te blijven.
     201  // Eerder won de vorm uit het tagveld -- tagParts maakt daar #DoenWeNiet van --
     202  // en verdween de geschreven vorm als duplicaat.
     203  const p = maakPost({
     204    id: 'p-schrijf', slug: 'zoals-geschreven', titel: 'Zoals geschreven',
     205    content: '<p>Zie <a class="hashtag" href="/tag/doenweniet">#DoenweNiet</a></p>[[track:t-een]]',
     206    tags: '["Doen we Niet","lofi"]',
     207  });
     208  const col = M.buildPostTrackCollection(BASE, site, p);
     209  const namen = col.tag.map((t) => t.name);
     210  assert.ok(namen.includes('#DoenweNiet'), 'de geschreven vorm wint');
     211  assert.ok(!namen.includes('#DoenWeNiet'), 'en de genormaliseerde staat er niet naast');
     212  assert.ok(namen.includes('#lofi'), 'tags die alleen in het veld staan blijven');
     213  assert.equal(col.tag[0].href, `${BASE}/tag/doenweniet`, 'de slug blijft wel klein: dat is een adres');
     214});
Note: See TracChangeset for help on using the changeset viewer.