const audio = new Audio(); let loaded = false; // Read the preview URL from the data- attribute rather than interpolating it // into this JS string. Askama autoescapes for HTML, which is the correct // escaper for an attribute value but NOT for a JS string literal — keeping the // URL in the attribute keeps escaping correct even once preview URLs derive // from user-influenced filenames. const previewUrl = document.querySelector('.player').dataset.previewUrl; function togglePlay() { if (!loaded) { audio.src = previewUrl; loaded = true; } if (audio.paused) { audio.play(); document.getElementById('play').innerHTML = '▮▮'; } else { audio.pause(); document.getElementById('play').innerHTML = '▶'; } } audio.ontimeupdate = () => { const pct = (audio.currentTime / audio.duration) * 100; document.getElementById('progress').style.width = pct + '%'; const m = Math.floor(audio.currentTime / 60); const s = Math.floor(audio.currentTime % 60); document.getElementById('time').textContent = m + ':' + (s < 10 ? '0' : '') + s; }; audio.onended = () => { document.getElementById('play').innerHTML = '▶'; }; function seek(e) { if (!audio.duration) return; const rect = e.currentTarget.getBoundingClientRect(); audio.currentTime = ((e.clientX - rect.left) / rect.width) * audio.duration; } // Wire the play/seek controls without inline on* handlers (the embed page runs // under the same CSP that forbids script-src 'unsafe-inline'). This page loads // only this file, not the core module's data-action dispatcher, so bind directly. document.addEventListener('DOMContentLoaded', function () { var play = document.getElementById('play'); if (play) play.addEventListener('click', togglePlay); var bar = document.getElementById('progress-bar'); if (bar) bar.addEventListener('click', seek); });