- Timestamp:
- 08/16/2026 01:44:46 PM (3 weeks ago)
- Branches:
- main
- Children:
- d68ee3a
- Parents:
- 21257e4
- Location:
- src
- Files:
-
- 4 edited
-
assets/js/mod/playlist-editor.js (modified) (4 diffs)
-
config/database.js (modified) (1 diff)
-
services/PlaylistService.js (modified) (10 diffs)
-
views/partials/playlist-editor.ejs (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/js/mod/playlist-editor.js
r21257e4 rd8f7d9c 63 63 64 64 // Existing playlist data when editing 65 let initial = { title: '', artist: '', year: '', cover: '', kind: 'album', track_ids: [] };65 let initial = { title: '', artist: '', year: '', cover: '', kind: 'album', release_date: '', mb_release_id: '', track_ids: [] }; 66 66 if (isEdit && opts.id) { 67 67 try { … … 102 102 <option value="playlist" ${initial.kind === 'playlist' ? 'selected' : ''}>📃 Playlist (track-covers)</option> 103 103 </select> 104 </label> 105 <label class="pl-field pl-uitgave"> 106 <span>Uitgavedatum</span> 107 <input type="date" id="pli-release-date" value="${esc(initial.release_date || '')}"> 108 </label> 109 <label class="pl-field pl-uitgave"> 110 <span>MusicBrainz release-id</span> 111 <input type="text" id="pli-mb-release" maxlength="36" spellcheck="false" 112 placeholder="00000000-0000-0000-0000-000000000000" 113 value="${esc(initial.mb_release_id || '')}"> 104 114 </label> 105 115 <div class="pl-field pl-field-full"> … … 157 167 const yearEl = $('#pli-year'); 158 168 const kindEl = $('#pli-kind'); 169 const releaseEl = $('#pli-release-date'); 170 const mbReleaseEl = $('#pli-mb-release'); 159 171 const coverEl = $('#pli-cover'); 172 173 // Uitgavedatum en release-id horen bij een ALBUM, niet bij een 174 // afspeellijst -- dat is wat de keuze album/playlist betekent. Ze 175 // verdwijnen dus als je omschakelt, en de server maakt ze dan ook leeg; 176 // dit scherm is de uitleg, niet de bewaking. 177 const toonUitgave = () => { 178 const album = kindEl.value !== 'playlist'; 179 for (const el of backdrop.querySelectorAll('.pl-uitgave')) el.hidden = !album; 180 }; 181 kindEl.addEventListener('change', toonUitgave); 182 toonUitgave(); 160 183 const coverThumb = $('#pli-cover-thumb'); 161 184 const saveBtn = $('#pli-save'); … … 376 399 cover: coverEl.value.trim(), 377 400 kind: kindEl.value === 'playlist' ? 'playlist' : 'album', 401 release_date: releaseEl.value.trim(), 402 mb_release_id: mbReleaseEl.value.trim(), 378 403 tracks: selected.slice(), 379 404 }; -
src/config/database.js
r21257e4 rd8f7d9c 140 140 ensureColumn('sites', 'mb_artist_id', 'TEXT'); 141 141 ensureColumn('sites', 'mb_artist_name', 'TEXT'); 142 // Een UITGAVE heeft twee dingen die een afspeellijst niet heeft (shaer-756s). 143 // 144 // release_date en niet `year`: die kolom bestaat al en blijft, maar hun 145 // AlbumSerializer leest `released` als een DateField. Een jaartal als 146 // 2024-01-01 versturen is een dag verzinnen, en dat is precies wat we bij 147 // artiesten en albums niet doen. Volledige datum of niets. 148 // 149 // mb_release_id is de tegenhanger van sites.mb_artist_id: dezelfde soort 150 // verwijzing naar MusicBrainz, een niveau lager. 151 // 152 // Ze horen ALLEEN bij kind='album'. PlaylistService dwingt dat af bij het 153 // opslaan -- zie daar waarom dat niet alleen in het scherm mag zitten. 154 ensureColumn('playlists', 'release_date', 'TEXT'); 155 ensureColumn('playlists', 'mb_release_id', 'TEXT'); 142 156 ensureColumn('audio_tracks', 'cover_url', 'TEXT'); 143 157 ensureColumn('audio_tracks', 'album', 'TEXT'); -
src/services/PlaylistService.js
r21257e4 rd8f7d9c 15 15 import { v4 as uuid } from 'uuid'; 16 16 17 /** 18 * Een volledige datum of niets (shaer-756s). 19 * 20 * STRIKT, en dat is de hele functie. `year` bestaat al en blijft; dit veld 21 * bestaat juist omdat een jaartal geen uitgavedatum is. Zou hij "2024" 22 * doorlaten en er 2024-01-01 van maken, dan stond er straks een dag op de 23 * federatie die niemand ooit heeft ingevoerd -- en dan hadden we het veld net 24 * zo goed niet kunnen toevoegen. 25 * 26 * Ook 2024-02-31 valt af: dat is geen strengheid om de strengheid, Date rolt 27 * hem stilletjes door naar 2 maart en dan slaan we iets anders op dan er 28 * ingetypt is. 29 */ 30 function normDatum(v) { 31 const s = String(v == null ? '' : v).trim(); 32 if (!s) return null; 33 if (!/^\d{4}-\d{2}-\d{2}$/.test(s)) return null; 34 const d = new Date(`${s}T00:00:00Z`); 35 return Number.isNaN(d.getTime()) || d.toISOString().slice(0, 10) !== s ? null : s; 36 } 37 38 /** Een MusicBrainz-id of niets. Zelfde vorm als sites.mb_artist_id. */ 39 function normMbid(v) { 40 const s = String(v == null ? '' : v).trim().toLowerCase(); 41 return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(s) ? s : null; 42 } 43 17 44 class PlaylistService { 18 45 … … 52 79 const rows = db.prepare(` 53 80 SELECT p.id, p.title, p.artist, p.year, p.cover_url, p.kind, 81 p.release_date, p.mb_release_id, 54 82 p.created_at, p.updated_at, 55 83 (SELECT COUNT(*) FROM playlist_tracks WHERE playlist_id = p.id) AS track_count … … 65 93 cover: r.cover_url || '', 66 94 kind: r.kind || 'album', 95 release_date: r.release_date || '', 96 mb_release_id: r.mb_release_id || '', 67 97 track_count: r.track_count, 68 98 created_at: r.created_at, … … 86 116 if (!id) return null; 87 117 const p = db.prepare(` 88 SELECT id, title, artist, year, cover_url, kind, created_at, updated_at118 SELECT id, title, artist, year, cover_url, kind, release_date, mb_release_id, created_at, updated_at 89 119 FROM playlists WHERE site_id = ? AND id = ? 90 120 `).get(siteId, id); … … 125 155 cover: p.cover_url || fallbackCover, 126 156 kind: (p.kind === 'playlist') ? 'playlist' : 'album', 157 // Leeg als het een afspeellijst is -- de opslag houdt ze daar al leeg, 158 // maar dit is de plek waar de editor leest en die mag niet afhangen van 159 // wat er toevallig in de kolom stond. 160 release_date: p.kind === 'playlist' ? '' : (p.release_date || ''), 161 mb_release_id: p.kind === 'playlist' ? '' : (p.mb_release_id || ''), 127 162 tracks: mappedTracks, 128 163 }; … … 141 176 const kind = data.kind === 'playlist' ? 'playlist' : 'album'; 142 177 178 // Alleen een UITGAVE draagt deze twee. Een afspeellijst heeft geen 179 // uitgavedatum en geen release-id, en dat onderscheid is precies wat de 180 // keuze album/playlist betekent (shaer-cyg). Het afdwingen gebeurt HIER en 181 // niet alleen in het scherm: een scherm kun je omzeilen -- de API ligt open 182 // voor de post-editor -- en dan staat er stille rommel op een mixtape die 183 // later als Album de deur uit gaat. 184 const uitgave = kind === 'album'; 185 const releaseDate = uitgave ? normDatum(data.release_date) : null; 186 const mbRelease = uitgave ? normMbid(data.mb_release_id) : null; 187 143 188 const tx = db.transaction(() => { 144 189 db.prepare(` 145 INSERT INTO playlists (id, site_id, title, artist, year, cover_url, kind, created_at, updated_at)146 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ? )190 INSERT INTO playlists (id, site_id, title, artist, year, cover_url, kind, release_date, mb_release_id, created_at, updated_at) 191 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 147 192 `).run( 148 193 id, siteId, title, … … 150 195 Number.isFinite(+data.year) && +data.year > 0 ? +data.year : null, 151 196 String(data.cover || '').trim() || null, 152 kind, now, now,197 kind, releaseDate, mbRelease, now, now, 153 198 ); 154 199 this._writeTracks(id, siteId, data.tracks); … … 171 216 if (!id) return false; 172 217 const existing = db.prepare( 173 'SELECT id FROM playlists WHERE site_id = ? AND id = ?'218 'SELECT id, kind FROM playlists WHERE site_id = ? AND id = ?' 174 219 ).get(siteId, id); 175 220 if (!existing) return false; … … 177 222 const fields = []; 178 223 const values = []; 224 // Wat wordt het NA deze wijziging? `kind` hoeft niet in data te staan, dus 225 // val terug op wat er ligt. 226 const nieuwKind = Object.prototype.hasOwnProperty.call(data, 'kind') 227 ? (data.kind === 'playlist' ? 'playlist' : 'album') 228 : (existing.kind || 'album'); 179 229 if (Object.prototype.hasOwnProperty.call(data, 'title')) { 180 230 const v = String(data.title || '').trim(); … … 193 243 } 194 244 if (Object.prototype.hasOwnProperty.call(data, 'kind')) { 195 fields.push('kind = ?'); values.push(data.kind === 'playlist' ? 'playlist' : 'album'); 245 fields.push('kind = ?'); values.push(nieuwKind); 246 } 247 // De uitgavevelden. Wordt dit een afspeellijst, dan gaan ze ALTIJD leeg -- 248 // ook als de aanroeper er niets over zei. Anders houdt een album dat je tot 249 // mixtape ombouwt zijn uitgavedatum en zijn release-id, en die duiken dan 250 // weer op zodra iemand hem terugzet. Een veld dat niet meer mag bestaan 251 // hoort weg te zijn, niet te wachten. 252 if (nieuwKind !== 'album') { 253 fields.push('release_date = ?'); values.push(null); 254 fields.push('mb_release_id = ?'); values.push(null); 255 } else { 256 if (Object.prototype.hasOwnProperty.call(data, 'release_date')) { 257 fields.push('release_date = ?'); values.push(normDatum(data.release_date)); 258 } 259 if (Object.prototype.hasOwnProperty.call(data, 'mb_release_id')) { 260 fields.push('mb_release_id = ?'); values.push(normMbid(data.mb_release_id)); 261 } 196 262 } 197 263 fields.push('updated_at = ?'); values.push(new Date().toISOString()); -
src/views/partials/playlist-editor.ejs
r21257e4 rd8f7d9c 99 99 } 100 100 .pl-field { display: flex; flex-direction: column; gap: .25rem; } 101 /* `display: flex` wint van het hidden-attribuut, dus zonder deze regel doet 102 [hidden] op een .pl-field NIETS -- het veld blijft gewoon staan. De 103 uitgavevelden schakelen daarop (album versus afspeellijst). */ 104 .pl-field[hidden] { display: none; } 101 105 .pl-field-full { grid-column: 1 / -1; } 102 106 .pl-field > span {
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)