Changeset c832064 in Klonkt


Ignore:
Timestamp:
08/09/2026 07:37:31 AM (4 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
d7e72b8
Parents:
e667d26
Message:

De soort van een playlist wordt opgezocht, niet afgeleid (shaer-cyg)

Robins afbakening maakt twee dingen tegelijk simpeler. Album of playlist wordt
gekozen wanneer de PLAYLIST wordt gemaakt -- die keuze staat al in
playlists.kind -- en de post neemt bij een enkele insluiting die soort over. Een
post om een album is dus een album, ook al heet de shortcode [[playlist:...]].

En de afleiding is er voor BESTAANDE posts; nieuwe krijgen hun type uit de
keuze. Daarmee is "wie wint, de keuze of de afleiding?" geen vraag meer: ze
komen elkaar niet tegen. De afleiding draait eenmalig, de keuze daarna.

Onbekende situatie -> gewone post met insluitingen die de post als context
hebben. Een onvindbare playlist, of een van een andere site, verzint dus geen
type meer.

De patronen zijn nu die van AudioEmbedService: wat daar niet insluit, telt hier
niet mee. Mijn playlist-patroon accepteerde underscores die de renderer weigert,
en zo'n shortcode had wel het type van de post kunnen bepalen.

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

Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/services/music/index.js

    re667d26 rc832064  
    336336
    337337/**
    338  * Het type van een post afleiden uit de muziek die erin staat.
     338 * Het type van een BESTAANDE post afleiden uit de muziek die erin staat.
     339 *
     340 * WAARVOOR DIT WEL EN NIET IS (Robins afbakening, 9-8). Nieuwe posts krijgen
     341 * hun type uit de keuze: album of playlist wordt gekozen als de playlist wordt
     342 * gemaakt, en de post neemt dat over. Deze functie is er voor wat er al staat --
     343 * de posts met type=audio uit de tijd voor die keuze bestond. Daarmee is de
     344 * vraag "wie wint, de keuze of de afleiding?" geen vraag meer: ze komen elkaar
     345 * niet tegen. De afleiding draait eenmalig, de keuze draait daarna.
    339346 *
    340347 * DE REGEL GAAT OVER IDENTITEIT, NIET OVER TELLEN (Robins herformulering, 9-8):
     
    357364 * letterlijk een album; hem als losse tracks tellen zou een albumpost tot
    358365 * playlist maken. Telt hij straks anders, dan is dat hier een regel.
    359  */
    360 export function postMusicType(content) {
     366 *
     367 * WIE KIEST ALBUM OF PLAYLIST: dat gebeurt wanneer de PLAYLIST wordt gemaakt,
     368 * en die keuze staat al in playlists.kind. Bij een enkele insluiting neemt de
     369 * post dus die soort over -- een post om een album is een album, ook al heet de
     370 * shortcode [[playlist:...]]. De soort van een playlist wordt hier dus
     371 * OPGEZOCHT en niet afgeleid.
     372 *
     373 * EN ALS WE HET NIET WETEN: een gewone post met insluitingen die de post als
     374 * context hebben. Dat is geen noodgreep maar de rustende toestand -- tracks
     375 * wijzen met `context` toch al terug naar hun post, dus er gaat niets verloren
     376 * als het label 'post' wordt.
     377 *
     378 * @param {string} content   de HTML/tekst van de post
     379 * @param {string} siteId    nodig om playlists.kind te kunnen opzoeken
     380 */
     381export function postMusicType(content, siteId) {
    361382  const c = String(content || '');
    362383  const uniek = (re) => [...new Set([...c.matchAll(re)].map((m) => m[1].trim()))];
    363384
    364   const playlists = uniek(/\[\[playlist:([A-Za-z0-9_-]+)\]\]/g);
     385  // Dezelfde patronen als de renderer in AudioEmbedService: wat daar niet
     386  // insluit, telt hier niet mee. Anders zou een shortcode die niets oplevert
     387  // wel het type van de post kunnen bepalen.
     388  const playlists = uniek(/\[\[playlist:([a-z0-9][a-z0-9-]*)\]\]/gi);
    365389  const albums    = uniek(/\[\[album:([^\]]+)\]\]/g);
    366390  const tracks    = uniek(/\[\[track:([A-Za-z0-9_-]+)\]\]/g);
    367391
     392  if (!playlists.length && !albums.length && !tracks.length) return null;
     393
     394  // De soort van een playlist is een gegeven, geen afleiding.
     395  let onbekend = [];
     396  const uitPlaylists = playlists.map((id) => {
     397    const kind = playlistKind(id, siteId);
     398    if (!kind) { onbekend.push(id); return null; }
     399    return { soort: kind, id };
     400  }).filter(Boolean);
     401
    368402  const collecties = [
    369     ...playlists.map((id) => ({ soort: 'playlist', id })),
     403    ...uitPlaylists,
    370404    ...albums.map((naam) => ({ soort: 'album', naam })),
    371405  ];
    372406
    373   if (!collecties.length && !tracks.length) return null;          // geen muziek
     407  // Onbekende situatie -> gewone post. De insluitingen blijven staan en houden
     408  // de post als context; alleen het label wordt niet verzonnen.
     409  if (onbekend.length) {
     410    return { type: 'post', collecties, tracks, bonus: [], onbekend, leentMetadata: false };
     411  }
    374412
    375413  if (!collecties.length) {
     
    389427  return { type: 'post', collecties, tracks, bonus: [], leentMetadata: false };
    390428}
     429
     430/**
     431 * De gekozen soort van een playlist: 'album' | 'playlist', of null als hij niet
     432 * (op deze site) bestaat. Zelfde normalisatie als PlaylistService: alles wat
     433 * geen 'playlist' zegt is een album.
     434 */
     435function playlistKind(id, siteId) {
     436  if (!siteId) return null;
     437  try {
     438    const r = db.prepare('SELECT kind FROM playlists WHERE id = ? AND site_id = ?').get(id, siteId);
     439    if (!r) return null;
     440    return r.kind === 'playlist' ? 'playlist' : 'album';
     441  } catch { return null; }
     442}
  • test/music-type.test.js

    re667d26 rc832064  
    1 // Welk soort uitgave is een post? (shaer-cyg)
     1// Welk soort uitgave is een BESTAANDE post? (shaer-cyg)
    22//
    3 // De regel gaat over IDENTITEIT en niet over tellen: een post neemt het type
    4 // van zijn muziek over als hij precies EEN muzikale eenheid bevat. Zijn het er
    5 // meer, dan is de post een post die naar muziek verwijst en houden de
    6 // collecties hun eigen identiteit -- en vervalt de reden om de posttitel en
    7 // -cover te lenen.
     3// Nieuwe posts krijgen hun type uit de keuze -- album of playlist wordt gekozen
     4// als de playlist wordt gemaakt. Deze afleiding is er voor wat er al staat: de
     5// type=audio posts van voor die keuze. De regel gaat over IDENTITEIT en niet
     6// over tellen: een post neemt het type van zijn muziek over als hij precies EEN
     7// muzikale eenheid bevat. Zijn het er meer, dan is de post een post die naar
     8// muziek verwijst en houden de collecties hun eigen identiteit -- en vervalt de
     9// reden om de posttitel en -cover te lenen.
    810import { test } from 'node:test';
    911import assert from 'node:assert/strict';
     
    1416const dbMod = await import('../src/config/database.js');
    1517dbMod.initializeDatabase();
     18const db = dbMod.default;
    1619const { postMusicType } = await import('../src/services/music/index.js');
    1720
     21const SITE = 'site-muziek';
     22const ANDERE = 'site-anders';
     23db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
     24  .run('u1', 'robin', 'r@test', 'x', 'god');
     25db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)').run(SITE, 'muziek', 'Muziek', 'u1');
     26db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)').run(ANDERE, 'anders', 'Anders', 'u1');
     27
     28const maakPlaylist = (id, kind, siteId = SITE) =>
     29  db.prepare('INSERT INTO playlists (id, site_id, title, kind) VALUES (?, ?, ?, ?)')
     30    .run(id, siteId, id, kind);
     31
     32maakPlaylist('nachtlicht', 'playlist');
     33maakPlaylist('de-plaat', 'album');
     34maakPlaylist('tweede', 'playlist');
     35maakPlaylist('elders', 'playlist', ANDERE);
     36
     37const type = (c) => postMusicType(c, SITE);
     38
    1839test('geen muziek: geen type', () => {
    19   assert.equal(postMusicType('<p>gewoon een bericht</p>'), null);
    20   assert.equal(postMusicType(''), null);
    21   assert.equal(postMusicType(null), null);
     40  assert.equal(type('<p>gewoon een bericht</p>'), null);
     41  assert.equal(type(''), null);
     42  assert.equal(type(null), null);
    2243});
    2344
    2445test('losse tracks worden een playlist, en lenen de metadata van de post', () => {
    25   const r = postMusicType('Twee nieuwe: [[track:a]] en [[track:b]]');
     46  const r = type('Twee nieuwe: [[track:a]] en [[track:b]]');
    2647  assert.equal(r.type, 'playlist');
    2748  assert.deepEqual(r.tracks, ['a', 'b']);
     
    3253  // Naar buiten toe is er dan altijd een collectie om naar te wijzen. Dat
    3354  // "Playlist" boven een enkel nummer gek leest is een weergavekwestie.
    34   assert.equal(postMusicType('[[track:a]]').type, 'playlist');
     55  assert.equal(type('[[track:a]]').type, 'playlist');
    3556});
    3657
    37 test('een playlist blijft een playlist', () => {
    38   const r = postMusicType('Nieuw: [[playlist:nachtlicht]]');
    39   assert.equal(r.type, 'playlist');
     58test('de post neemt de GEKOZEN soort van de playlist over', () => {
     59  // De kern van Robins besluit: album of playlist is bij het maken van de
     60  // playlist al gekozen. Hier wordt hij opgezocht, niet afgeleid.
     61  assert.equal(type('Nieuw: [[playlist:nachtlicht]]').type, 'playlist');
     62  assert.equal(type('Nieuw: [[playlist:de-plaat]]').type, 'album',
     63    'een post om een album is een album, ook al heet de shortcode playlist');
     64});
     65
     66test('de collectie draagt haar eigen soort mee', () => {
     67  const r = type('Nieuw: [[playlist:nachtlicht]]');
    4068  assert.deepEqual(r.collectie, { soort: 'playlist', id: 'nachtlicht' });
    4169  assert.equal(r.leentMetadata, true);
     
    4371
    4472test('een album-insluiting is ook een collectie, en telt als album', () => {
    45   // Robins regel noemde alleen playlists; [[album:naam]] groepeert net zo goed
    46   // tracks. Hem als losse tracks tellen zou een albumpost tot playlist maken.
    47   const r = postMusicType('Luister [[album:De Plaat]]');
     73  // [[album:naam]] groepeert tracks op hun albumveld -- geen playlist-rij, dus
     74  // geen gekozen soort. Hem als losse tracks tellen zou een albumpost tot
     75  // playlist maken.
     76  const r = type('Luister [[album:De Plaat]]');
    4877  assert.equal(r.type, 'album');
    4978  assert.deepEqual(r.collectie, { soort: 'album', naam: 'De Plaat' });
     
    5180
    5281test('een collectie MET losse tracks is een album, en die losse zijn bonus', () => {
    53   const r = postMusicType('[[playlist:nachtlicht]] plus [[track:x]] en [[track:y]]');
    54   assert.equal(r.type, 'album');
     82  const r = type('[[playlist:nachtlicht]] plus [[track:x]] en [[track:y]]');
     83  assert.equal(r.type, 'album', 'ook al is de playlist zelf een playlist');
    5584  assert.equal(r.collectie.id, 'nachtlicht');
    5685  assert.deepEqual(r.bonus, ['x', 'y'], 'geen rommelrestje maar bonus-tracks');
     
    6089
    6190test('TWEE collecties: de post blijft een post en leent niets', () => {
    62   const r = postMusicType('[[playlist:een]] en ook [[playlist:twee]]');
     91  const r = type('[[playlist:nachtlicht]] en ook [[playlist:tweede]]');
    6392  assert.equal(r.type, 'post');
    6493  assert.equal(r.collecties.length, 2);
     
    6897
    6998test('twee collecties van verschillende soort tellen ook als twee', () => {
    70   const r = postMusicType('[[playlist:een]] naast [[album:De Plaat]]');
     99  const r = type('[[playlist:nachtlicht]] naast [[album:De Plaat]]');
    71100  assert.equal(r.type, 'post');
    72101  assert.equal(r.collecties.length, 2);
     
    76105  // Anders maakt een herhaalde shortcode van een albumpost stilletjes een
    77106  // gewone post.
    78   const r = postMusicType('[[playlist:een]] ... en nog eens [[playlist:een]]');
     107  const r = type('[[playlist:nachtlicht]] ... en nog eens [[playlist:nachtlicht]]');
    79108  assert.equal(r.type, 'playlist');
    80   assert.equal(r.collectie.id, 'een');
     109  assert.equal(r.collectie.id, 'nachtlicht');
    81110});
    82111
    83112test('dezelfde track twee keer telt ook maar een keer', () => {
    84   assert.deepEqual(postMusicType('[[track:a]] [[track:a]] [[track:b]]').tracks, ['a', 'b']);
     113  assert.deepEqual(type('[[track:a]] [[track:a]] [[track:b]]').tracks, ['a', 'b']);
    85114});
     115
     116test('onbekende playlist: gewone post, niets verzonnen', () => {
     117  // Robins laatste optie voor een onbekende situatie. De insluiting blijft
     118  // staan en houdt de post als context; alleen het label wordt niet geraden.
     119  const r = type('[[playlist:bestaat-niet]]');
     120  assert.equal(r.type, 'post');
     121  assert.deepEqual(r.onbekend, ['bestaat-niet']);
     122  assert.equal(r.leentMetadata, false);
     123});
     124
     125test('een playlist van een ANDERE site telt niet als de onze', () => {
     126  const r = type('[[playlist:elders]]');
     127  assert.equal(r.type, 'post');
     128  assert.deepEqual(r.onbekend, ['elders']);
     129});
     130
     131test('zonder site is er niets op te zoeken, dus geen type verzinnen', () => {
     132  assert.equal(postMusicType('[[playlist:nachtlicht]]').type, 'post');
     133});
Note: See TracChangeset for help on using the changeset viewer.