Changeset 89d6536 in Klonkt


Ignore:
Timestamp:
06/14/2026 04:20:02 AM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
c80e78b
Parents:
2b52bfb
Message:

fix: track buttons via event delegation (survives HTMX history-restore after back button)

Regression from the back-button sheet: popstate let HTMX restore #pcms-main
(hx-history-elt) with data-pcms-attached baked in but listeners gone -> the
per-button attach skipped them -> dead buttons after "back". Now: one delegated
body listener (as playlist-delete already did). data-pcms-attached + htmx:afterSettle
rebinding removed. audio-player.js ?v=8 -> ?v=9.

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

Location:
src
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/assets/js/audio-player.js

    r2b52bfb r89d6536  
    1313 *  - body.has-audio-player adds bottom padding when player visible
    1414 *  - body.audio-sheet-locked prevents body scroll when sheet open
    15  *  - Survives HTMX swaps via htmx:afterSettle re-attach
     15 *  - Survives HTMX swaps + history-restores via event delegation on document.body
    1616 *
    1717 * Singleton — guards against double-init.
     
    593593  // wrapper. For the other three the data is on the button itself. The
    594594  // handler reads from button-first, falls back to wrapper.
    595   function attachListeners() {
    596     const playBtns = document.querySelectorAll(
    597       '.post-audio-track .pat-play, ' +
    598       '.post-album-tracks .pat-row, ' +
    599       '.post-album-cover-btn, ' +
    600       '.post-album-playall'
    601     );
    602     if (playBtns.length) console.log('[pcms-audio] attaching to', playBtns.length, 'play buttons');
    603 
    604     playBtns.forEach((btn) => {
    605       if (btn.dataset.pcmsAttached) return;
    606       btn.dataset.pcmsAttached = '1';
    607 
    608       btn.addEventListener('click', (e) => {
    609         e.preventDefault();
    610         e.stopPropagation();
    611         // Resolve metadata: button-first, then closest .post-audio-track wrapper
    612         // (only inline single-track widgets put the data on the wrapper).
    613         const wrapper = btn.closest('.post-audio-track');
    614         const albumId   = btn.dataset.pcmsAlbumId  || (wrapper && wrapper.dataset.pcmsAlbumId);
    615         const trackData = btn.dataset.pcmsTrack    || (wrapper && wrapper.dataset.pcmsTrack);
    616         const trackUrl  = btn.dataset.pcmsTrackUrl || (wrapper && wrapper.dataset.pcmsTrackUrl);
    617         console.log('[pcms-audio] click', { btn: btn.className, albumId, trackUrl, hasTrackData: !!trackData });
    618 
    619         if (albumId) {
    620           const album = document.getElementById(albumId);
    621           if (!album) { console.error('[pcms-audio] album not found:', albumId); return; }
    622           try {
    623             const tracks = JSON.parse(album.dataset.pcmsAlbum);
    624             // Start at the clicked track if we know its URL, else start at 0
    625             // (cover-btn and playall both want to start from the beginning).
    626             const startIdx = trackUrl ? tracks.findIndex(t => t.url === trackUrl) : 0;
    627             setQueue(tracks, startIdx >= 0 ? startIdx : 0, { albumName: album.dataset.pcmsAlbumTitle || '' });
    628           } catch(err) { console.error('[pcms-audio] bad album JSON', err, album.dataset.pcmsAlbum); }
    629         } else if (trackData) {
    630           try {
    631             const t = JSON.parse(trackData);
    632             setQueue([t], 0);
    633           } catch(err) { console.error('[pcms-audio] bad track JSON', err, trackData); }
    634         } else if (trackUrl) {
    635           // Fallback: at minimum we have the signed URL
    636           setQueue([{ url: trackUrl, title: 'Track', artist: '', cover: '' }], 0);
    637         } else {
    638           console.error('[pcms-audio] no track data or url on button or wrapper', btn);
    639         }
    640       });
    641     });
    642   }
    643 
    644   attachListeners();
    645   document.body.addEventListener('htmx:afterSettle', attachListeners);
     595  // Event-delegation op document.body i.p.v. per-knop listeners. Dit overleeft
     596  // HTMX history-restores: de mobiele terug-knop (popstate) laat HTMX #pcms-main
     597  // terugzetten uit z'n snapshot; een per-element `data-pcms-attached`-vlag zou
     598  // dan dode knoppen geven (vlag ingebakken in de snapshot, listener weg). Eén
     599  // gedelegeerde listener werkt ongeacht hoe vaak de DOM ge(her)swapt wordt.
     600  const PLAY_SELECTOR =
     601    '.post-audio-track .pat-play, .post-album-tracks .pat-row, .post-album-cover-btn, .post-album-playall';
     602  document.body.addEventListener('click', (e) => {
     603    const btn = e.target.closest(PLAY_SELECTOR);
     604    if (!btn) return;
     605    e.preventDefault();
     606    e.stopPropagation();
     607    // Resolve metadata: button-first, then closest .post-audio-track wrapper
     608    // (only inline single-track widgets put the data on the wrapper).
     609    const wrapper = btn.closest('.post-audio-track');
     610    const albumId   = btn.dataset.pcmsAlbumId  || (wrapper && wrapper.dataset.pcmsAlbumId);
     611    const trackData = btn.dataset.pcmsTrack    || (wrapper && wrapper.dataset.pcmsTrack);
     612    const trackUrl  = btn.dataset.pcmsTrackUrl || (wrapper && wrapper.dataset.pcmsTrackUrl);
     613    console.log('[pcms-audio] click', { btn: btn.className, albumId, trackUrl, hasTrackData: !!trackData });
     614
     615    if (albumId) {
     616      const album = document.getElementById(albumId);
     617      if (!album) { console.error('[pcms-audio] album not found:', albumId); return; }
     618      try {
     619        const tracks = JSON.parse(album.dataset.pcmsAlbum);
     620        // Start at the clicked track if we know its URL, else start at 0
     621        // (cover-btn and playall both want to start from the beginning).
     622        const startIdx = trackUrl ? tracks.findIndex(t => t.url === trackUrl) : 0;
     623        setQueue(tracks, startIdx >= 0 ? startIdx : 0, { albumName: album.dataset.pcmsAlbumTitle || '' });
     624      } catch(err) { console.error('[pcms-audio] bad album JSON', err, album.dataset.pcmsAlbum); }
     625    } else if (trackData) {
     626      try {
     627        const t = JSON.parse(trackData);
     628        setQueue([t], 0);
     629      } catch(err) { console.error('[pcms-audio] bad track JSON', err, trackData); }
     630    } else if (trackUrl) {
     631      // Fallback: at minimum we have the signed URL
     632      setQueue([{ url: trackUrl, title: 'Track', artist: '', cover: '' }], 0);
     633    } else {
     634      console.error('[pcms-audio] no track data or url on button or wrapper', btn);
     635    }
     636  });
    646637
    647638  // ============================================================
  • src/views/shell.ejs

    r2b52bfb r89d6536  
    277277     ?v=N — cache-buster: bump bij elke audio-player.js wijziging zodat
    278278     Cloudflare (max-age=1y) niet de oude versie blijft serveren. -->
    279 <script src="/assets/js/audio-player.js?v=8"></script>
     279<script src="/assets/js/audio-player.js?v=9"></script>
    280280<% if (site && site.enable_audio_player && audioTracks && audioTracks.length > 0) { %>
    281281  <script>window.PCMS_SITE_TRACKS = <%- JSON.stringify(audioTracks) %>;</script>
Note: See TracChangeset for help on using the changeset viewer.