source: Klonkt/src/assets/js/shared/post-music-type.js

main
Last change on this file was b0ccd0d, checked in by Robin <roboburr@…>, 3 weeks ago

De mixtape als eigen soort: data en draad

Een bandje is geen album en geen afspeellijst. Een album is een uitgave -- de
nummers bestaan er los van. Bij een mixtape is de VOLGORDE het werk en zijn de
nummers onderdelen. Vandaar een eigen soort, en op de draad een enkel object
met de nummers erin.

De valkuil zat in de vorm die er al stond. De keuze album/playlist stond vijf
keer als x === 'playlist' ? 'playlist' : 'album' verspreid over drie
bestanden, en zo'n vorm valt bij een derde soort niet om -- hij slikt hem. Een
mixtape zou stilzwijgend een album zijn geworden en als Album de deur uit gaan.
De lijst staat nu een keer, in de pure gedeelde module, want die kent de
browser ook.

Op de draad:

  • type is de STRING 'Mixtape'. Geen array, en dat is geleerd: bij de playlist-collectie stond ooit ['OrderedCollection','Album'] -- geldig AS2 en werkelijk allebei -- en een lezer die type als tekst uitpakt (Shaer) verloor daarmee stil het hele object.
  • orderedItems op het object zelf, niet een collectie ernaast.
  • Geen released en geen musicbrainzId: die zouden beweren dat dit een uitgave is.
  • Geen eigen url. Robins keuze: het bandje is een omhulling, geen gerenderd bestand. Prijs ervan is opgeschreven bij de functie -- wie het type niet kent heeft geen stream, al staan de nummers er stuk voor stuk in.

Een mixtape met losse tracks erbij blijft een mixtape. De oude regel maakte
daar een album van met bonustracks; dat klopt voor een plaat en niet voor een
bandje. Album en playlist houden bewust hun oude gedrag, want dat zijn
bestaande posts.

Acht tests, waaronder de weg door de database heen in plaats van langs een
handgemaakte opzoeker.

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

  • Property mode set to 100644
File size: 6.1 KB
Line 
1/**
2 * De regel: welk soort uitgave is een post? (shaer-cyg)
3 *
4 * WAAROM DIT ONDER assets/ STAAT. De regel wordt op twee plaatsen gebruikt: de
5 * server leidt er het type van bestaande posts mee af, en de editor laat het
6 * type er live door volgen als je muziek invoegt. Dat MOET dezelfde regel zijn.
7 * Twee kopieën zouden niet luidruchtig kapotgaan maar stil uit elkaar lopen --
8 * de editor zegt album, de server zegt playlist, en niemand die het merkt. De
9 * browser kan alleen bij /assets, de server kan overal bij; dus staat hij hier,
10 * en importeert de server hem vanuit services/music.
11 *
12 * Deze module is bewust PUUR: geen database, geen DOM, geen fetch. Wie hem
13 * gebruikt levert zelf een opzoeker voor de soort van een playlist.
14 *
15 * DE REGEL GAAT OVER IDENTITEIT, NIET OVER TELLEN (Robins herformulering, 9-8):
16 * een post neemt het type van zijn muziek over als hij precies EEN muzikale
17 * eenheid bevat. Zijn het er meer, dan is de post een post die naar muziek
18 * verwijst, en houden de collecties hun eigen identiteit.
19 *
20 * losse track(s) -> playlist metadata van de post geleend
21 * een collectie -> die soort metadata van de post geleend
22 * een collectie + losse tracks -> album de losse zijn BONUS-TRACKS
23 * een MIXTAPE + losse tracks -> mixtape de losse zijn BONUS-TRACKS
24 * twee of meer collecties -> post NIETS geleend
25 *
26 * Die vierde regel is de uitzondering op de derde en staat er met opzet naast:
27 * bonustracks maken van een uitgave een album, maar een bandje is geen uitgave.
28 *
29 * Waarom de lening bij de laatste vervalt: die bestaat omdat een collectie soms
30 * dun is -- geen eigen hoes, geen eigen titel. Bij twee is de post niet meer de
31 * drager van EEN identiteit, en vervalt de reden vanzelf.
32 *
33 * WIE KIEST ALBUM OF PLAYLIST: dat gebeurt wanneer de PLAYLIST wordt gemaakt.
34 * Die keuze staat in playlists.kind en wordt hier OPGEZOCHT, niet afgeleid --
35 * een post om een album is een album, ook al heet de shortcode [[playlist:...]].
36 *
37 * EN ALS WE HET NIET WETEN: een gewone post met insluitingen die de post als
38 * context hebben. Geen noodgreep maar de rustende toestand; tracks wijzen met
39 * `context` toch al terug naar hun post, dus er gaat niets verloren.
40 */
41
42// Dezelfde patronen als de renderer in AudioEmbedService: wat daar niet
43// insluit, telt hier niet mee. Anders zou een shortcode die niets oplevert wel
44// het type van de post kunnen bepalen.
45// De soorten die een playlist kan zijn, en dit is de ENIGE lijst. PlaylistService
46// en music/index.js halen hem hier op, precies omdat het andersom niet kan:
47// deze module draait ook in de browser en mag niets van de database weten.
48//
49// Waarom dat uitmaakt: bij twee soorten stond de keuze vijf keer als
50// `x === 'playlist' ? 'playlist' : 'album'` verspreid over drie bestanden. Zo'n
51// vorm valt niet om bij een derde soort, hij slikt hem -- een mixtape werd
52// stilzwijgend een album en ging als Album de deur uit.
53export const SOORTEN = ['album', 'playlist', 'mixtape'];
54
55const RE_PLAYLIST = /\[\[playlist:([a-z0-9][a-z0-9-]*)\]\]/gi;
56const RE_ALBUM = /\[\[album:([^\]]+)\]\]/g;
57const RE_TRACK = /\[\[track:([A-Za-z0-9_-]+)\]\]/g;
58
59/**
60 * @param {string} content de HTML/tekst van de post
61 * @param {(id: string) => ('album'|'playlist'|null)} kindVan
62 * de gekozen soort van een playlist, of null als hij onbekend is
63 * @returns {null|{type, collectie?, collecties?, tracks, bonus, onbekend?, leentMetadata}}
64 */
65export function afleidenUitInsluitingen(content, kindVan) {
66 const c = String(content || '');
67 const zoek = typeof kindVan === 'function' ? kindVan : () => null;
68 const uniek = (re) => [...new Set([...c.matchAll(re)].map((m) => m[1].trim()))];
69
70 const playlists = uniek(RE_PLAYLIST);
71 const albums = uniek(RE_ALBUM);
72 const tracks = uniek(RE_TRACK);
73
74 if (!playlists.length && !albums.length && !tracks.length) return null;
75
76 // De soort van een playlist is een gegeven, geen afleiding.
77 const onbekend = [];
78 const uitPlaylists = playlists.map((id) => {
79 const kind = zoek(id);
80 if (!SOORTEN.includes(kind)) { onbekend.push(id); return null; }
81 return { soort: kind, id };
82 }).filter(Boolean);
83
84 const collecties = [
85 ...uitPlaylists,
86 ...albums.map((naam) => ({ soort: 'album', naam })),
87 ];
88
89 // Onbekende situatie -> gewone post. De insluitingen blijven staan en houden
90 // de post als context; alleen het label wordt niet verzonnen.
91 if (onbekend.length) {
92 return { type: 'post', collecties, tracks, bonus: [], onbekend, leentMetadata: false };
93 }
94
95 if (!collecties.length) {
96 // Ook EEN losse track wordt een playlist: naar buiten toe is er dan altijd
97 // een collectie om naar te wijzen. Hoe Klonkt dat toont is een aparte vraag.
98 return { type: 'playlist', collectie: null, tracks, bonus: [], leentMetadata: true };
99 }
100
101 if (collecties.length === 1) {
102 const c0 = collecties[0];
103 // EEN MIXTAPE BLIJFT EEN MIXTAPE, ook met losse tracks erbij. De regel
104 // hieronder maakt van collectie + losse tracks een album met bonustracks, en
105 // dat klopt voor een uitgave: extra nummers bij een plaat zijn bonus. Een
106 // bandje is geen uitgave. Er een album van maken omdat er een los nummer
107 // naast staat zou het ding hernoemen op grond van iets wat er niet bij
108 // hoort. Album en playlist houden bewust hun oude gedrag: dat zijn
109 // bestaande posts en die mogen hier niet stilletjes van soort wisselen.
110 if (c0.soort === 'mixtape') {
111 return { type: 'mixtape', collectie: c0, tracks: [], bonus: tracks, leentMetadata: true };
112 }
113 // Losse tracks naast een collectie zijn geen rommelrestje maar bonus-tracks,
114 // en dat maakt het geheel een album.
115 if (tracks.length) return { type: 'album', collectie: c0, tracks: [], bonus: tracks, leentMetadata: true };
116 return { type: c0.soort, collectie: c0, tracks: [], bonus: [], leentMetadata: true };
117 }
118
119 return { type: 'post', collecties, tracks, bonus: [], leentMetadata: false };
120}
121
122/** De playlist-ids die in een tekst worden ingesloten. */
123export function ingeslotenPlaylists(content) {
124 return [...new Set([...String(content || '').matchAll(RE_PLAYLIST)].map((m) => m[1].trim()))];
125}
Note: See TracBrowser for help on using the repository browser.