Changeset 5300652 in Klonkt
- Timestamp:
- 08/13/2026 01:08:32 PM (4 weeks ago)
- Branches:
- main
- Children:
- 6401681
- Parents:
- a117862
- git-author:
- Robin <roboburr@…> (08/13/2026 12:10:40 PM)
- git-committer:
- Robin <roboburr@…> (08/13/2026 01:08:32 PM)
- Files:
-
- 5 edited
-
src/assets/js/mod/admin-audio.js (modified) (1 diff)
-
src/routes/admin-audio.js (modified) (1 diff)
-
src/services/i18n.js (modified) (3 diffs)
-
src/views/pages/admin-audio.ejs (modified) (1 diff)
-
test/musicbrainz.test.js (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/js/mod/admin-audio.js
ra117862 r5300652 452 452 }); 453 453 })(); 454 455 // MusicBrainz-paneel (shaer-mbz). Binnen run(), niet op moduleniveau: 456 // deze modules krijgen bij elke paginawissel opnieuw init(), en een blok dat 457 // maar een keer per sessie draait is precies wat shaer-5s1 opleverde. 458 wireMusicBrainz(); 454 459 } 460 461 // ── MusicBrainz: ben jij dit? (shaer-mbz, stap 2) ───────────────── 462 // 463 // De kandidaten komen van de server, want MusicBrainz eist een verzoek per 464 // seconde per APPLICATIE en een User-Agent met contact -- allebei niet vanuit 465 // een browser af te dwingen. 466 // 467 // WIJ KIEZEN NIET. Ook niet als er precies een treffer is: een verkeerd 468 // geraden MBID zet jouw naam onder andermans werk. De knop staat er, de klik 469 // is van de artiest. 470 function wireMusicBrainz() { 471 const knop = document.getElementById('mb-zoek-btn'); 472 const veld = document.getElementById('mb-q'); 473 const uit = document.getElementById('mb-uit'); 474 if (!knop || !veld || !uit || knop.__wired) return; 475 knop.__wired = true; 476 477 const el = (tag, cls, tekst) => { 478 const e = document.createElement(tag); 479 if (cls) e.className = cls; 480 if (tekst != null) e.textContent = tekst; // textContent: nooit HTML uit een vreemd register 481 return e; 482 }; 483 484 async function zoek() { 485 const q = (veld.value || '').trim(); 486 if (!q) return; 487 uit.hidden = false; 488 uit.replaceChildren(el('p', 'ax-hint', T.aaud_mb_busy || 'Zoeken…')); 489 knop.disabled = true; 490 try { 491 const r = await fetch(`api/musicbrainz?q=${encodeURIComponent(q)}`, { credentials: 'same-origin' }); 492 const j = await r.json(); 493 toon((j && j.kandidaten) || []); 494 } catch { 495 uit.replaceChildren(el('p', 'ax-hint', T.aaud_mb_fail || 'MusicBrainz is even niet bereikbaar.')); 496 } finally { 497 knop.disabled = false; 498 } 499 } 500 501 function toon(kandidaten) { 502 if (!kandidaten.length) { 503 uit.replaceChildren(el('p', 'ax-hint', T.aaud_mb_none || 'Niets gevonden.')); 504 return; 505 } 506 const lijst = el('ul', 'mb-lijst'); 507 for (const k of kandidaten) { 508 const li = el('li', 'mb-kandidaat'); 509 li.appendChild(el('strong', null, k.naam)); 510 // De toelichting is het hele punt: er zijn drie bands die Nirvana heten, 511 // en zonder dit veld kiest iemand de verkeerde. 512 const bij = [k.toelichting, k.soort, k.land, k.jaren].filter(Boolean).join(' · '); 513 if (bij) li.appendChild(el('small', 'mb-bij', bij)); 514 const open = el('a', 'mb-open', T.aaud_mb_open || 'Bekijk op MusicBrainz'); 515 open.href = k.url; open.target = '_blank'; open.rel = 'noopener'; 516 li.appendChild(open); 517 li.appendChild(kiesForm(k)); 518 lijst.appendChild(li); 519 } 520 uit.replaceChildren(lijst); 521 } 522 523 function kiesForm(k) { 524 const f = document.createElement('form'); 525 f.method = 'POST'; 526 f.action = 'musicbrainz/link'; 527 for (const [naam, waarde] of [['_csrf', csrf()], ['mbid', k.mbid], ['naam', k.naam]]) { 528 const i = document.createElement('input'); 529 i.type = 'hidden'; i.name = naam; i.value = waarde || ''; 530 f.appendChild(i); 531 } 532 const b = el('button', 'ax-btn', T.aaud_mb_pick || 'Dit ben ik'); 533 b.type = 'submit'; 534 f.appendChild(b); 535 return f; 536 } 537 538 // Het token staat al in elk formulier op deze pagina; er is er geen apart voor. 539 const csrf = () => (document.querySelector('input[name="_csrf"]') || {}).value || ''; 540 541 knop.addEventListener('click', zoek); 542 veld.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); zoek(); } }); 543 } -
src/routes/admin-audio.js
ra117862 r5300652 414 414 }); 415 415 416 /** 417 * De keuze vastleggen. Alleen een echte MBID komt de kolom in: de naam die we 418 * ernaast bewaren is voor het scherm, de MBID is het enige dat naar buiten gaat. 419 */ 420 router.post('/musicbrainz/link', requireGod, (req, res) => { 421 const site = res.locals.site; 422 if (!site) return res.status(404).end(); 423 const mbid = String(req.body.mbid || '').trim().toLowerCase(); 424 const naam = String(req.body.naam || '').trim().slice(0, 200); 425 const terug = (res.locals.siteUrlBase || '') + '/admin/audio'; 426 if (!MusicBrainz.isMbid(mbid)) return res.redirect(`${terug}?error=` + encodeURIComponent('Geen geldige MusicBrainz-id.')); 427 db.prepare('UPDATE sites SET mb_artist_id = ?, mb_artist_name = ? WHERE id = ?').run(mbid, naam || null, site.id); 428 res.redirect(`${terug}?success=` + encodeURIComponent('Gekoppeld aan MusicBrainz.')); 429 }); 430 431 /** Terugdraaien. Een verkeerde koppeling zet jouw naam onder andermans werk. */ 432 router.post('/musicbrainz/unlink', requireGod, (req, res) => { 433 const site = res.locals.site; 434 if (!site) return res.status(404).end(); 435 db.prepare('UPDATE sites SET mb_artist_id = NULL, mb_artist_name = NULL WHERE id = ?').run(site.id); 436 res.redirect((res.locals.siteUrlBase || '') + '/admin/audio?success=' + encodeURIComponent('Ontkoppeld.')); 437 }); 438 416 439 router.get('/api/albums', requireGod, (req, res) => { 417 440 const site = res.locals.site; -
src/services/i18n.js
ra117862 r5300652 360 360 'aaud.fedi_off': 'Niet op de fediverse gedeeld (alleen webspeler, bestand verborgen) — klik om te delen', 361 361 'aaud.embed_player': 'Embedbare speler', 362 'aaud.mb_title': 'Ben jij dit in MusicBrainz?', 363 'aaud.mb_hint': 'MusicBrainz is het open register waar muziekdiensten je aan herkennen. Zoek jezelf op en kies je eigen pagina — dan weten anderen dat jouw muziek van jou is.', 364 'aaud.mb_search': 'Zoeken', 365 'aaud.mb_none': 'Niets gevonden. Sta je er nog niet in? Dan kun je jezelf aanmelden op musicbrainz.org — dat kan alleen daar, niet vanuit Klonkt.', 366 'aaud.mb_pick': 'Dit ben ik', 367 'aaud.mb_linked': 'Gekoppeld aan', 368 'aaud.mb_unlink': 'Ontkoppelen', 369 'aaud.mb_open': 'Bekijk op MusicBrainz', 370 'aaud.mb_busy': 'Zoeken…', 371 'aaud.mb_fail': 'MusicBrainz is even niet bereikbaar.', 362 372 'aaud.embed_hint': 'Plak deze code op je eigen website/blog om je muziek met deze speler in te sluiten:', 363 373 'aaud.preview_player': 'Speler-voorbeeld openen', … … 1307 1317 'aaud.fedi_off': 'Not shared on the fediverse (web player only, file hidden) — click to share', 1308 1318 'aaud.embed_player': 'Embeddable player', 1319 'aaud.mb_title': 'Is this you on MusicBrainz?', 1320 'aaud.mb_hint': 'MusicBrainz is the open register music services recognise you by. Look yourself up and pick your own page — then others know your music is yours.', 1321 'aaud.mb_search': 'Search', 1322 'aaud.mb_none': 'Nothing found. Not in there yet? You can add yourself on musicbrainz.org — that can only be done there, not from Klonkt.', 1323 'aaud.mb_pick': 'This is me', 1324 'aaud.mb_linked': 'Linked to', 1325 'aaud.mb_unlink': 'Unlink', 1326 'aaud.mb_open': 'View on MusicBrainz', 1327 'aaud.mb_busy': 'Searching…', 1328 'aaud.mb_fail': 'MusicBrainz is unreachable right now.', 1309 1329 'aaud.embed_hint': 'Paste this code on your own website/blog to embed your music with this player:', 1310 1330 'aaud.preview_player': 'Open player preview', … … 2253 2273 'aaud.fedi_off': 'Nicht im Fediverse geteilt (nur Web-Player, Datei verborgen) — zum Teilen klicken', 2254 2274 'aaud.embed_player': 'Einbettbarer Player', 2275 'aaud.mb_title': 'Bist das du bei MusicBrainz?', 2276 'aaud.mb_hint': 'MusicBrainz ist das offene Register, an dem Musikdienste dich erkennen. Suche dich und wähle deine eigene Seite — dann wissen andere, dass deine Musik von dir ist.', 2277 'aaud.mb_search': 'Suchen', 2278 'aaud.mb_none': 'Nichts gefunden. Noch nicht dabei? Du kannst dich auf musicbrainz.org eintragen — das geht nur dort, nicht aus Klonkt heraus.', 2279 'aaud.mb_pick': 'Das bin ich', 2280 'aaud.mb_linked': 'Verknüpft mit', 2281 'aaud.mb_unlink': 'Trennen', 2282 'aaud.mb_open': 'Bei MusicBrainz ansehen', 2283 'aaud.mb_busy': 'Suche läuft…', 2284 'aaud.mb_fail': 'MusicBrainz ist gerade nicht erreichbar.', 2255 2285 'aaud.embed_hint': 'Füge diesen Code auf deiner eigenen Website/deinem Blog ein, um deine Musik mit diesem Player einzubetten:', 2256 2286 'aaud.preview_player': 'Player-Vorschau öffnen', -
src/views/pages/admin-audio.ejs
ra117862 r5300652 12 12 <% if (success) { %><div class="ax-flash ax-flash-ok"><%= success %></div><% } %> 13 13 <% if (error) { %><div class="ax-flash ax-flash-err"><%= error %></div><% } %> 14 15 <%# ── MUSICBRAINZ ───────────────────────────────────────────── 16 Wie ben jij, buiten Klonkt? Een MBID is geen vocabulaire maar een 17 register: eraan koppelen is als je ISBN noemen. Andere muziekdiensten 18 -- Funkwhale voorop -- herkennen je eraan zonder dat wij hun model 19 hoeven over te nemen (shaer-mbz). %> 20 <section class="ax-card" id="mb-card"> 21 <div class="ax-card-title"><%= t('aaud.mb_title') %></div> 22 <p class="ax-hint"><%= t('aaud.mb_hint') %></p> 23 24 <% if (site.mb_artist_id) { %> 25 <p class="mb-linked"> 26 <%= t('aaud.mb_linked') %> 27 <strong><%= site.mb_artist_name || site.mb_artist_id %></strong> 28 <a href="https://musicbrainz.org/artist/<%= site.mb_artist_id %>" 29 target="_blank" rel="noopener"><%= t('aaud.mb_open') %></a> 30 </p> 31 <%# Ontkoppelen moet kunnen: een verkeerde koppeling zet jouw naam onder 32 andermans werk, en dat hoort met een klik terug te draaien zijn. %> 33 <form method="POST" action="<%= (typeof siteUrlBase !== 'undefined' ? siteUrlBase : '') %>/admin/audio/musicbrainz/unlink"> 34 <input type="hidden" name="_csrf" value="<%= csrfToken %>"> 35 <button type="submit" class="ax-btn ax-btn-quiet"><%= t('aaud.mb_unlink') %></button> 36 </form> 37 <% } %> 38 39 <div class="mb-zoek"> 40 <input type="text" id="mb-q" value="<%= site.author || site.title || '' %>" 41 placeholder="<%= t('aaud.artist') %>" autocomplete="off"> 42 <button type="button" class="ax-btn" id="mb-zoek-btn"><%= t('aaud.mb_search') %></button> 43 </div> 44 <div id="mb-uit" class="mb-uit" hidden></div> 45 </section> 14 46 15 47 <%# ── UPLOAD ──────────────────────────────────────────────── %> -
test/musicbrainz.test.js
ra117862 r5300652 97 97 98 98 test.after(() => { globalThis.fetch = echt; }); 99 100 // ── Stap 2: de keuze vastleggen ─────────────────────────────────────────── 101 102 test('alleen een echte MBID komt de kolom in', async () => { 103 // De route weigert alles wat geen UUID is. Zonder die zeef sluipt er een 104 // hele URL of een handle in het veld dat straks naar buiten gaat. 105 const db = dbMod.default; 106 db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)') 107 .run('u1', 'u1', 'u1@t', 'x', 'god'); 108 db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)').run('s1', 'band', 'Band', 'u1'); 109 for (const rommel of ['https://musicbrainz.org/artist/8be31978-1884-4773-beae-f73df35b92aa', 'nirvana', '']) { 110 assert.equal(MB.isMbid(rommel), false, `${rommel} hoort geweigerd te worden`); 111 } 112 db.prepare('UPDATE sites SET mb_artist_id = ?, mb_artist_name = ? WHERE id = ?') 113 .run('8be31978-1884-4773-beae-f73df35b92aa', 'Nirvana', 's1'); 114 const s = db.prepare("SELECT mb_artist_id, mb_artist_name FROM sites WHERE id = 's1'").get(); 115 assert.equal(s.mb_artist_id, '8be31978-1884-4773-beae-f73df35b92aa'); 116 assert.equal(s.mb_artist_name, 'Nirvana', 'de naam ernaast, zodat het scherm kan tonen WAT er hangt'); 117 }); 118 119 test('ontkoppelen maakt beide velden leeg', () => { 120 const db = dbMod.default; 121 db.prepare('UPDATE sites SET mb_artist_id = NULL, mb_artist_name = NULL WHERE id = ?').run('s1'); 122 const s = db.prepare("SELECT mb_artist_id, mb_artist_name FROM sites WHERE id = 's1'").get(); 123 assert.equal(s.mb_artist_id, null); 124 assert.equal(s.mb_artist_name, null, 'anders blijft er een naam staan zonder koppeling'); 125 });
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)