Changeset e6ad948 in Klonkt


Ignore:
Timestamp:
06/20/2026 06:47:41 AM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
08cfdaa
Parents:
57b1843
Message:

feat(epk): choose up to 5 tracks for the press kit (instead of auto top-5)

Press kit editor now has a track picker (checkboxes, max 5). /pers shows the
chosen tracks in that order; no selection → automatically the top 5 most
played. Stored as epk_tracks_<siteId> (validated on site + max 5).

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

Location:
src
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • src/routes/admin-epk.js

    r57b1843 re6ad948  
    1010
    1111import express from 'express';
     12import db from '../config/database.js';
    1213import { renderPage } from '../middleware/render.js';
    1314import { requireGod } from '../middleware/auth.js';
     
    1718const router = express.Router();
    1819
     20const MAX_EPK_TRACKS = 5;
     21
    1922router.get('/', requireGod, (req, res) => {
    2023  const site = res.locals.site;
     
    2326    return res.status(403).send('Perskit is een premium-functie.');
    2427  }
     28  const allTracks = db.prepare(
     29    `SELECT id, title, artist FROM audio_tracks WHERE site_id = ? ORDER BY position ASC, created_at ASC`
     30  ).all(site.id);
     31  let chosen = [];
     32  try { const r = JSON.parse(getSetting('epk_tracks_' + site.id, '') || '[]'); if (Array.isArray(r)) chosen = r; } catch (e) {}
    2533  renderPage(req, res, 'pages/admin-epk', {
    2634    pageTitle: 'Perskit bewerken',
     
    2937    epkBio: getSetting('epk_bio_' + site.id, '') || '',
    3038    epkContact: getSetting('epk_contact_' + site.id, '') || '',
     39    allTracks,
     40    chosenTracks: chosen,
     41    maxEpkTracks: MAX_EPK_TRACKS,
    3142    success: req.query.success || null,
    3243  });
     
    3849  setSetting('epk_bio_' + site.id, (req.body.epk_bio || '').toString().slice(0, 1000).trim());
    3950  setSetting('epk_contact_' + site.id, (req.body.epk_contact || '').toString().slice(0, 300).trim());
     51  // Gekozen nummers: alleen ids van DEZE site, max 5, in de aangeleverde volgorde.
     52  let ids = req.body.epk_tracks;
     53  if (!Array.isArray(ids)) ids = ids ? [ids] : [];
     54  const valid = new Set(db.prepare('SELECT id FROM audio_tracks WHERE site_id = ?').all(site.id).map((r) => r.id));
     55  ids = ids.map(String).filter((id) => valid.has(id)).slice(0, MAX_EPK_TRACKS);
     56  setSetting('epk_tracks_' + site.id, JSON.stringify(ids));
    4057  res.redirect('/admin/epk?success=' + encodeURIComponent('Perskit opgeslagen'));
    4158});
  • src/routes/epk.js

    r57b1843 re6ad948  
    2525  if (!site) return next();
    2626
    27   // Top 10 meest beluisterde nummers (op play_count), niet zomaar de eerste paar.
    28   const tracks = db.prepare(
    29     `SELECT title, artist, duration, cover_url, COALESCE(play_count, 0) AS plays
    30        FROM audio_tracks
    31       WHERE site_id = ?
    32       ORDER BY plays DESC, position ASC, created_at ASC
    33       LIMIT 10`
    34   ).all(site.id);
     27  // Nummers op de perskit: een door de admin GEKOZEN selectie (max 5, in eigen
     28  // volgorde) als die is ingesteld; anders automatisch de top 5 meest beluisterde.
     29  let chosenIds = [];
     30  try {
     31    const raw = JSON.parse(getSetting('epk_tracks_' + site.id, '') || '[]');
     32    if (Array.isArray(raw)) chosenIds = raw.filter((x) => typeof x === 'string').slice(0, 5);
     33  } catch (e) { /* ongeldige JSON → val terug op top */ }
     34
     35  let tracks;
     36  if (chosenIds.length) {
     37    const ph = chosenIds.map(() => '?').join(',');
     38    const rows = db.prepare(
     39      `SELECT id, title, artist, duration, cover_url, COALESCE(play_count, 0) AS plays
     40         FROM audio_tracks WHERE site_id = ? AND id IN (${ph})`
     41    ).all(site.id, ...chosenIds);
     42    const byId = new Map(rows.map((r) => [r.id, r]));
     43    tracks = chosenIds.map((id) => byId.get(id)).filter(Boolean);  // behoud gekozen volgorde
     44  } else {
     45    tracks = db.prepare(
     46      `SELECT title, artist, duration, cover_url, COALESCE(play_count, 0) AS plays
     47         FROM audio_tracks
     48        WHERE site_id = ?
     49        ORDER BY plays DESC, position ASC, created_at ASC
     50        LIMIT 5`
     51    ).all(site.id);
     52  }
    3553
    3654  const posts = db.prepare(
  • src/views/pages/admin-epk.ejs

    r57b1843 re6ad948  
    2626               value="<%= epkContact %>">
    2727      </label>
     28
     29      <div class="set-field">
     30        <span>Nummers op de perskit <small>(kies er max <%= maxEpkTracks %>; laat leeg voor automatisch de top <%= maxEpkTracks %> meest beluisterd)</small></span>
     31        <% if (!allTracks.length) { %>
     32          <p class="set-help" style="margin:0">Nog geen nummers — voeg eerst audio toe in Beheer → Audio.</p>
     33        <% } else { %>
     34          <div class="epk-track-pick" data-max="<%= maxEpkTracks %>">
     35            <% allTracks.forEach(function(t){ %>
     36              <label class="epk-pick-item">
     37                <input type="checkbox" name="epk_tracks" value="<%= t.id %>" <%= chosenTracks.indexOf(t.id) >= 0 ? 'checked' : '' %>>
     38                <span class="epk-pick-title"><%= t.title || '(zonder titel)' %></span>
     39                <% if (t.artist) { %><span class="epk-pick-artist"><%= t.artist %></span><% } %>
     40              </label>
     41            <% }); %>
     42          </div>
     43        <% } %>
     44      </div>
     45
    2846      <button type="submit" class="btn btn-primary">Opslaan</button>
    2947    </form>
     
    5169  .admin-page .set-field textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent); }
    5270  .admin-page .set-form .btn { align-self: flex-start; }
     71  .admin-page .epk-track-pick { display: flex; flex-direction: column; gap: 2px; max-height: 300px; overflow-y: auto; border: 1px solid var(--rule); border-radius: 8px; padding: 4px; }
     72  .admin-page .epk-pick-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.45rem 0.5rem; border-radius: 6px; cursor: pointer; }
     73  .admin-page .epk-pick-item:hover { background: var(--paper); }
     74  .admin-page .epk-pick-item input { width: auto; flex: 0 0 auto; }
     75  .admin-page .epk-pick-title { font-weight: 600; }
     76  .admin-page .epk-pick-artist { color: var(--ink-muted, var(--ink-soft)); font-size: 0.85rem; }
     77  .admin-page .epk-pick-item input:disabled ~ .epk-pick-title { opacity: 0.45; }
    5378</style>
     79<script>
     80(function () {
     81  var box = document.querySelector('.epk-track-pick');
     82  if (!box) return;
     83  var max = parseInt(box.dataset.max, 10) || 5;
     84  function sync() {
     85    var checks = box.querySelectorAll('input[type=checkbox]');
     86    var n = box.querySelectorAll('input[type=checkbox]:checked').length;
     87    checks.forEach(function (c) { c.disabled = (!c.checked && n >= max); });
     88  }
     89  box.addEventListener('change', sync);
     90  sync();
     91})();
     92</script>
Note: See TracChangeset for help on using the changeset viewer.