Changeset d727e92 in Klonkt
- Timestamp:
- 06/20/2026 05:37:42 AM (3 months ago)
- Branches:
- main
- Children:
- a66f691
- Parents:
- 183875b
- Location:
- src
- Files:
-
- 7 edited
-
assets/css/audio.css (modified) (1 diff)
-
routes/admin-audio.js (modified) (1 diff)
-
routes/posts.js (modified) (3 diffs)
-
services/AudioEmbedService.js (modified) (6 diffs)
-
services/PlaylistService.js (modified) (2 diffs)
-
views/pages/admin-audio.ejs (modified) (2 diffs)
-
views/shell.ejs (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/css/audio.css
r183875b rd727e92 105 105 .pat-link--youtube { color: #FF0000; } 106 106 .pat-link--soundcloud { color: #FF5500; } 107 108 /* Link-only tracks (geen audiobestand): plek van de afspeelknop = stil muziek-icoon. */ 109 .pat-noplay { 110 display: inline-flex; align-items: center; justify-content: center; 111 flex: 0 0 38px; width: 38px; align-self: center; 112 color: var(--ink-faint, #a8a29e); 113 } 114 .pat-noplay svg { width: 18px; height: 18px; } 115 .pat-row.pat-static { cursor: default; } 107 116 108 117 /* ============================================================ -
src/routes/admin-audio.js
r183875b rd727e92 394 394 395 395 /** GET /admin/audio/api/:id — single track with all metadata */ 396 // Maak een track ZONDER audiobestand (alleen titel + open-in links). Verschijnt 397 // in albums/playlists in de lijst, met open-in-iconen maar zonder afspeelknop. 398 router.post('/create-link', requireGod, express.json(), (req, res) => { 399 const site = res.locals.site; 400 if (!site) return res.status(404).json({ error: 'Site required' }); 401 const trackId = uuid(); 402 const title = ((req.body && req.body.title) || 'Nieuwe track').toString().trim().slice(0, 200) || 'Nieuwe track'; 403 try { 404 db.prepare(` 405 INSERT INTO audio_tracks (id, site_id, title, media_id, position) 406 VALUES (?, ?, ?, NULL, COALESCE((SELECT MAX(position) + 1 FROM audio_tracks WHERE site_id = ?), 0)) 407 `).run(trackId, site.id, title, site.id); 408 } catch (e) { 409 return res.status(500).json({ error: e.message }); 410 } 411 res.json({ ok: true, id: trackId }); 412 }); 413 396 414 router.get('/api/:id', requireGod, (req, res) => { 397 415 const site = res.locals.site; -
src/routes/posts.js
r183875b rd727e92 537 537 html = AudioEmbedService.embedTrackShortcodes(html, (id) => { 538 538 const r = byId.get(id); 539 if (!r || !r.filename) return null;539 if (!r) return null; 540 540 return { 541 541 id: r.id, … … 548 548 link_youtube: r.link_youtube || '', 549 549 link_soundcloud: r.link_soundcloud || '', 550 url: audioUrl(r.filename),550 url: r.filename ? audioUrl(r.filename) : '', // '' = link-only track 551 551 }; 552 552 }); … … 566 566 const byAlbum = new Map(); 567 567 for (const r of albumRows) { 568 if (!r.filename) continue;568 // Link-only tracks (geen bestand) blijven in het album-overzicht (url ''). 569 569 if (!byAlbum.has(r.album)) byAlbum.set(r.album, []); 570 570 byAlbum.get(r.album).push({ 571 571 id: r.id, 572 url: audioUrl(r.filename),572 url: r.filename ? audioUrl(r.filename) : '', 573 573 title: r.title || 'Untitled', 574 574 artist: r.artist || '', -
src/services/AudioEmbedService.js
r183875b rd727e92 270 270 return html.replace(/\[\[track:([A-Za-z0-9_-]+)\]\]/g, (match, id) => { 271 271 const t = trackLookup(id); 272 if (!t || !t.url) return match; 272 if (!t) return match; 273 const titleH0 = this.escape(t.title || 'Untitled'); 274 const artistH0 = this.escape(t.artist || ''); 275 const creditBits0 = [this.escape(t.credit || ''), this.escape(t.license || '')].filter(Boolean).join(' · '); 276 // Link-only track (geen audiobestand): geen afspeelknop, wel info + open-in. 277 if (!t.url) { 278 return `<div class="post-audio-track post-audio-track--static" id="track-${id}"> 279 <span class="pat-noplay" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg></span> 280 <div class="pat-info"> 281 <div class="pat-title">${titleH0}</div> 282 ${artistH0 ? `<div class="pat-artist">${artistH0}</div>` : ''} 283 ${creditBits0 ? `<div class="pat-credit">${creditBits0}</div>` : ''} 284 </div> 285 ${this.openInLinks(t)} 286 </div>`; 287 } 273 288 const trackJson = JSON.stringify({ 274 289 id, … … 318 333 // Stable DOM id for this rendering — used as data-pcms-album-id on tracks 319 334 const albumDomId = 'album-' + Math.random().toString(36).slice(2, 10); 320 const albumJson = JSON.stringify(album.tracks) 335 // Alleen afspeelbare tracks (met url) in de queue; link-only tracks staan 336 // wel in de lijst maar niet in de afspeel-JSON. 337 const albumJson = JSON.stringify(album.tracks.filter((t) => t.url)) 321 338 .replace(/&/g, '&').replace(/'/g, ''').replace(/</g, '<'); 322 339 const titleH = this.escape(album.title || name); … … 327 344 const tTitle = this.escape(t.title || ('Track ' + (i + 1))); 328 345 const tArtist = this.escape(t.artist || ''); 346 // Link-only track: geen afspeelknop, wel nummer + info + open-in. 347 if (!t.url) { 348 return ` <li class="post-audio-track post-audio-track--static"${t.id ? ` id="track-${t.id}"` : ''}> 349 <span class="pat-track-num">${i + 1}.</span> 350 <div class="pat-info"> 351 <div class="pat-title">${tTitle}</div> 352 ${tArtist && tArtist !== artistH ? `<div class="pat-artist">${tArtist}</div>` : ''} 353 </div> 354 ${this.openInLinks(t)} 355 </li>`; 356 } 329 357 const tUrl = this.escape(t.url); 330 358 return ` <li class="post-audio-track"${t.id ? ` id="track-${t.id}" data-pcms-track-id="${t.id}"` : ''} data-pcms-track-url="${tUrl}" data-pcms-album-id="${albumDomId}"> … … 404 432 // Audio-player.js reads data-pcms-album for queue. Same shape as 405 433 // embedAlbumShortcodes — keep both in sync. 406 const tracksData = pl.tracks.map(t => ({ 434 // Alleen afspeelbare tracks in de queue; link-only tracks staan wel in de 435 // lijst maar niet in de afspeel-JSON. 436 const tracksData = pl.tracks.filter(t => t.url).map(t => ({ 407 437 id: t.id, 408 438 url: t.url, … … 426 456 } 427 457 const metaLine = this.escape(metaParts.join(' · ')); 428 const firstUrl = this.escape( pl.tracks[0].url);458 const firstUrl = this.escape((pl.tracks.find(t => t.url) || {}).url || ''); 429 459 430 460 // Track items — playlist-kind shows per-track cover thumbs, album-kind shows numbers … … 445 475 : `<span class="pat-num">${i + 1}</span>`; 446 476 477 // Link-only track: geen klikbare afspeel-rij (statische div), wel open-in. 478 if (!t.url) { 479 return ` <li class="post-album-track-compact post-album-track-compact--static"${t.id ? ` id="track-${t.id}"` : ''}> 480 <div class="pat-row pat-static"> 481 ${leader} 482 <span class="pat-meta"> 483 <span class="pat-title">${tTitleH}</span> 484 ${showArtist ? `<span class="pat-artist">${tArtistH}</span>` : ''} 485 </span> 486 ${durHtml} 487 </div> 488 ${this.openInLinks(t)} 489 </li>`; 490 } 447 491 const trackBase = String(t.url).split('?')[0]; 448 492 return ` <li class="post-album-track-compact"${t.id ? ` id="track-${t.id}" data-pcms-track-id="${t.id}"` : ''}> -
src/services/PlaylistService.js
r183875b rd727e92 104 104 105 105 const mappedTracks = tracks 106 .filter(t => t.filename) // skip orphaned references106 // Link-only tracks (geen media-bestand) blijven in de lijst staan met url ''. 107 107 .map(t => ({ 108 108 id: t.id, … … 114 114 link_youtube: t.link_youtube || '', 115 115 link_soundcloud: t.link_soundcloud || '', 116 url: urlFor ? urlFor(t.filename) : null,116 url: (t.filename && urlFor) ? urlFor(t.filename) : '', 117 117 })); 118 118 // Geen eigen cover? Val terug op de eerste track-cover, zodat de kaart niet leeg is. -
src/views/pages/admin-audio.ejs
r183875b rd727e92 62 62 <%# ── TRACK LIST ─────────────────────────────────────────── %> 63 63 <section class="ax-card"> 64 <div class="ax-card-title" >64 <div class="ax-card-title" style="display:flex;align-items:center;gap:.5rem"> 65 65 Tracks <span class="ax-count"><%= tracks.length %></span> 66 <button type="button" class="ax-btn ax-btn-secondary" id="add-link-track-btn" 67 style="margin-left:auto;font-size:.85rem" title="Een track zonder audiobestand — alleen titel + open-in links"> 68 + Track zonder audio 69 </button> 66 70 </div> 67 71 … … 889 893 }); 890 894 }); 895 896 // ── "+ Track zonder audio": maak een link-only stub + open de editor ── 897 const addLinkBtn = document.getElementById('add-link-track-btn'); 898 if (addLinkBtn) { 899 addLinkBtn.addEventListener('click', async () => { 900 addLinkBtn.disabled = true; 901 try { 902 const r = await fetch('/admin/audio/create-link', { 903 method: 'POST', credentials: 'same-origin', 904 headers: { 'Content-Type': 'application/json' }, 905 body: JSON.stringify({ title: 'Nieuwe track' }), 906 }); 907 const j = await r.json(); 908 if (!r.ok || !j.ok) throw new Error(j.error || 'Aanmaken mislukt'); 909 if (typeof window.openTrackEditor !== 'function') { location.reload(); return; } 910 window.openTrackEditor({ id: j.id, onSaved: () => location.reload() }); 911 } catch (err) { 912 alert('Track aanmaken mislukt: ' + err.message); 913 } finally { 914 addLinkBtn.disabled = false; 915 } 916 }); 917 } 891 918 892 919 // ── Wire all "Edit" buttons to the track-editor modal ───────── -
src/views/shell.ejs
r183875b rd727e92 168 168 anywhere (admin previews, post embeds, etc). The player itself is 169 169 a singleton — see the script tag near </body>. --> 170 <link rel="stylesheet" href="/assets/css/audio.css?v= 8">170 <link rel="stylesheet" href="/assets/css/audio.css?v=9"> 171 171 <!-- Eigen custom media-embeds (YouTube/SoundCloud/Spotify) in huisstijl. --> 172 172 <link rel="stylesheet" href="/assets/css/embed.css?v=8">
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)