diff options
Diffstat (limited to 'web/js/podcasts.js')
| -rw-r--r-- | web/js/podcasts.js | 74 |
1 files changed, 60 insertions, 14 deletions
diff --git a/web/js/podcasts.js b/web/js/podcasts.js index 1883e63..4692f7b 100644 --- a/web/js/podcasts.js +++ b/web/js/podcasts.js @@ -1,6 +1,8 @@ // podcastUI.js — Podcast feed manager + episode rendering. import { API } from './api.js'; +import { state } from './state.js'; import { escapeHtml, fmtDur, toast } from './utils.js'; +import { renderSets } from './views/sets.js'; export function initPodcasts() { const modal = document.getElementById('podcast-modal'); @@ -32,6 +34,7 @@ export function initPodcasts() { urlInput.value = ''; nameInput.value = ''; refreshPodcasts(); + refreshSets(); } catch (err) { toast(err.message || 'Subscribe failed', 'error'); } @@ -46,7 +49,8 @@ export function initPodcasts() { return; } listEl.innerHTML = podcasts.map(p => - `<div class="flex gap-2 align-center py-1 border-b"> + `<div class="podcast-feed-row py-1 border-b"> + <img class="podcast-feed-cover" src="/api/sets/${p.id}/cover" alt="" loading="lazy"> <span class="flex-1">${escapeHtml(p.name)}</span> <span class="text-xs text-muted">${escapeHtml(p.root_path)}</span> </div>` @@ -55,6 +59,15 @@ export function initPodcasts() { listEl.innerHTML = `<p class="error-message">${escapeHtml(err.message)}</p>`; } } + + async function refreshSets() { + try { + state.sets = await API.sets() || []; + renderSets(); + } catch { + // The subscription succeeded; leave the existing sidebar in place if refresh fails. + } + } } export function renderPodcastEpisodes(grid, episodes) { @@ -68,21 +81,50 @@ export function renderPodcastEpisodes(grid, episodes) { const card = document.createElement('div'); card.className = 'media-card episode-card'; card.dataset.id = ep.id; - card.innerHTML = renderEpisodeHtml(ep); + renderEpisodeCard(card, ep); grid.appendChild(card); + }); +} + +function renderEpisodeCard(card, ep) { + card.innerHTML = renderEpisodeHtml(ep); + + const playBtn = card.querySelector('[data-action="play"]'); + const downloadBtn = card.querySelector('[data-action="download-episode"]'); + const completeBtn = card.querySelector('.btn-complete'); + + playBtn?.addEventListener('click', (e) => { + e.stopPropagation(); + toast(ep.is_downloaded ? 'Play from downloads' : 'Download first to play', 'info'); + }); - const playBtn = card.querySelector('[data-action="play"]'); - const completeBtn = card.querySelector('.btn-complete'); - playBtn?.addEventListener('click', () => { - toast(ep.is_downloaded ? 'Play from downloads' : 'Download first to play', 'info'); - }); - completeBtn?.addEventListener('click', async () => { - try { - const res = await API.toggleEpisodeComplete(ep.id); - completeBtn.classList.toggle('active'); - toast(res.is_completed ? 'Marked listened' : 'Marked unlistened'); - } catch (err) { toast(err.message || 'Toggle failed', 'error'); } - }); + downloadBtn?.addEventListener('click', async (e) => { + e.stopPropagation(); + downloadBtn.disabled = true; + try { + const media = await API.downloadEpisode(ep.id); + ep.is_downloaded = true; + ep.media_id = media.id; + ep.file_name = media.file_name; + toast('Episode downloaded'); + card.dispatchEvent(new CustomEvent('podcast:episode-downloaded', { bubbles: true, detail: { media } })); + renderEpisodeCard(card, ep); + } catch (err) { + downloadBtn.disabled = false; + toast(err.message || 'Download failed', 'error'); + } + }); + + completeBtn?.addEventListener('click', async (e) => { + e.stopPropagation(); + try { + await API.toggleEpisodeComplete(ep.id); + ep.is_completed = !ep.is_completed; + completeBtn.classList.toggle('active', ep.is_completed); + toast(ep.is_completed ? 'Marked listened' : 'Marked unlistened'); + } catch (err) { + toast(err.message || 'Toggle failed', 'error'); + } }); } @@ -90,6 +132,9 @@ function renderEpisodeHtml(ep) { const completed = ep.is_completed; const dateStr = ep.published_at ? new Date(ep.published_at).toLocaleDateString() : ''; const duration = ep.duration_seconds ? fmtDur(ep.duration_seconds) : ''; + const downloadButton = ep.is_downloaded + ? '<span class="text-xs text-muted">Downloaded</span>' + : '<button class="btn btn-primary btn-sm btn-download-episode" data-action="download-episode">Download</button>'; return ` <div class="thumb-wrap"> <span class="placeholder">🎙️</span> @@ -102,6 +147,7 @@ function renderEpisodeHtml(ep) { <div class="meta"> <div class="title">${escapeHtml(ep.title || 'Untitled')}</div> <div class="subtitle">${escapeHtml(ep.description || 'Podcast episode')}</div> + ${downloadButton} </div> `; } |
