summaryrefslogtreecommitdiff
path: root/web/js/podcasts.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/js/podcasts.js')
-rw-r--r--web/js/podcasts.js74
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>
`;
}