diora-web/static/podcasts/native-podcast.js

4598 lines
210 KiB
JavaScript
Raw Normal View History

// Copied from diora-android 50c6f9c 2026-10-16: web-native/static/native-podcast.js
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
// by tools/sync_podcast_ui.py. Do not edit here: change it in diora-android
// and copy again. The browser host is static/js/podcasts-web.js.
// Podcasts for the phone, the AntennaPod way: the queue comes first.
//
// The app keeps the podcast state (Podcasts.kt: feeds, queue, inbox and
// settings, fetched from the server in one request and kept for offline use)
// and plays it (RadioService: the queue is the playlist, it keeps the place
// and moves on by itself). This page only shows that state and hands what
// the user does to DioraNative.podcast*. The web app's own podcast tab stays
// hidden; its endpoints are the server side of all this.
//
// The browser runs this same file (diora-web copies it, tools/sync_podcast_ui.py):
// there window.DioraPodcastHost (diora-web static/js/podcasts-web.js) plays the
// part of the app, with the same calls.
(function () {
'use strict';
const N = window.DioraPodcastHost || window.DioraNative;
if (!N || !N.podcastState) return;
// E-readers have no speaker; they stay a pure book app.
if (N.isEinkDevice && N.isEinkDevice()) return;
document.documentElement.classList.add('nui-podcasts');
const $id = id => document.getElementById(id);
// The tabs; the search is a view of its own, opened from the top bar.
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const VIEWS = [
{id: 'queue', label: 'Warteschlange'},
{id: 'inbox', label: 'Neu'},
{id: 'episodes', label: 'Folgen'},
{id: 'feeds', label: 'Abos'},
];
const VIEW_IDS = VIEWS.map(v => v.id).concat('search');
// The app's start page (this device; native.js opens it, the browser host too).
const IN_APP = !window.DioraPodcastHost;
// A function: the app loads this file in <head>, before #tab-home exists.
const startPages = () => [
document.getElementById('tab-home') && {id: 'home', label: 'Home', hint: 'Standard'},
!IN_APP && {id: '', label: 'Zuletzt benutzt'},
{id: 'books', label: 'Bücher', hint: IN_APP && !document.getElementById('tab-home') ? 'Standard' : null},
{id: 'queue', label: 'Podcasts: Warteschlange'},
{id: 'inbox', label: 'Podcasts: Neu'},
{id: 'radio', label: 'Radio'},
// Sections of the page's navigation (nav.js), where the page has them.
window.DioraNav && DioraNav.sections().some(s => s.id === 'audiobooks') && {id: 'audiobooks', label: 'Hörbücher'},
window.DioraNav && DioraNav.sections().some(s => s.id === 'news') && {id: 'news', label: 'Nachrichten'},
].filter(Boolean);
function startPage() {
let v = null;
try { v = localStorage.getItem('nui_start_page'); } catch (e) {}
const pages = startPages();
return pages.some(p => p.id === v) ? v : pages[0].id;
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const POSITIONS = [
{value: 'back', label: 'Ans Ende'},
{value: 'front', label: 'An den Anfang'},
{value: 'after_current', label: 'Nach der laufenden Folge'},
];
// The speed menu's choices while the account has no presets of its own (settings().speed_presets).
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const SPEEDS = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.5, 1.75, 2];
const MIN_SPEED = 0.5;
const MAX_SPEED = 3.5;
const MAX_PRESETS = 12;
// Skip lengths back and forward (seconds), as AntennaPod offers them.
const SKIPS = [5, 10, 15, 20, 30, 45, 60];
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function parse(s, fallback) {
try { return JSON.parse(s); } catch (e) { return fallback; }
}
let state = parse(N.podcastState(), {});
let player = {kind: null};
let view = 'queue';
let feedView = null; // {feed, episodes} while one podcast is open
let searchResults = null;
let preview = null; // {id, url, data | choices | error} while a feed is looked at before subscribing
let previewSeq = 0;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
let refreshing = false;
let swipingSince = 0; // a row is being swiped (since then): render waits (rowGestures)
let renderAfterSwipe = false;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
let selected = null; // Set of episode ids while choosing several (queue or inbox)
let locked = false; // queue locked: no dragging
// Downloads (Downloads.kt; see "downloads" further down): where the host has them.
const DL = !!N.podcastDownloads;
let downloads = DL ? parse(N.podcastDownloads(), {}) : {};
let dlSettings = DL ? parse(N.podcastDownloadSettings(), {}) : {};
// All at a glance (Downloads.infoJson): paused, how many run and wait, done of all in this round.
let dlInfo = DL && N.podcastDownloadsInfo ? parse(N.podcastDownloadsInfo(), {}) : {};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
try { locked = localStorage.getItem('nui_pod_locked') === '1'; } catch (e) {}
try { view = localStorage.getItem('nui_pod_view') || 'queue'; } catch (e) {}
// Opening into the podcasts (start page): that list.
if (startPage() === 'queue' || startPage() === 'inbox') view = startPage();
if (!VIEW_IDS.includes(view)) view = 'queue';
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const queue = () => state.queue || [];
const inbox = () => state.inbox || [];
// All new episodes: the server sends the first ones (inbox_limit) and how many there are
// (inbox_total); what left „Neu“ here since then counts off.
function inboxTotal() {
const n = inbox().length;
const total = state.inbox_total;
if (typeof total !== 'number') return n;
const sent = typeof state.inbox_limit === 'number' ? Math.min(total, state.inbox_limit) : n;
return Math.max(n, total - Math.max(0, sent - n));
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const feeds = () => state.feeds || [];
// "Abos & Quellen" (diora-web docs/sources.md, sources.js): where subscriptions are managed, if the page has it.
const hasSources = () => !!window.DioraNav && DioraNav.sections().some(s => s.id === 'sources');
const toSources = () => DioraNav.go('sources');
// Audiobooks (diora-web docs/audiobooks.md) are listened to in the library ("Bücher"), not here.
const isBook = f => !!f && f.kind === 'audiobook';
const podcastFeeds = () => feeds().filter(f => !isBook(f));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const settings = () => state.settings || {};
const currentId = () => (player.kind === 'podcast' ? player.episodeId : 0);
// The account's speed presets (null there: the default list).
const presets = () => {
const p = settings().speed_presets;
return Array.isArray(p) && p.length ? p.slice().sort((a, b) => a - b) : SPEEDS;
};
// Skipping to the next episode keeps the skipped one (AntennaPod's "keep skipped episodes").
const keepSkipped = () => settings().keep_skipped !== false;
// "Zeiten an das Tempo anpassen": lengths and remaining times as long as they take at the speed.
const bySpeed = () => !!settings().times_by_speed;
let prefsSeen = null;
let prefsAt = 0;
function speedOf(feedId) {
if (!prefsSeen || Date.now() - prefsAt > 2000) { prefsSeen = devicePrefs(); prefsAt = Date.now(); }
const f = feedId ? feeds().find(x => x.id === feedId) : null;
return f && f.playback_speed > 0 ? f.playback_speed : prefsSeen.speed > 0 ? prefsSeen.speed : 1;
}
// Seconds as shown: at the podcast's speed (or `speed`), when so set.
function shown(seconds, feedId, speed) {
if (!bySpeed()) return seconds;
const s = speed > 0 ? speed : speedOf(feedId);
return s > 0 ? seconds / s : seconds;
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
// ---- small helpers ---------------------------------------------------------------
function el(tag, cls, text) {
const e = document.createElement(tag);
if (cls) e.className = cls;
if (text != null) e.textContent = text;
return e;
}
function button(label, title, onClick, cls) {
const b = el('button', 'btn-icon nui-icon' + (cls ? ' ' + cls : ''), label);
b.type = 'button';
b.title = title;
b.setAttribute('aria-label', title);
b.addEventListener('click', e => { e.stopPropagation(); onClick(e); });
return b;
}
// Line icons in the text's colour (black on e-ink): deleting is a bin, taking out of a
// list a list with an x — the same in menus, swipes, the selection bar and the undo bar.
const SVG_PATHS = {
trash: '<path d="M4 7h16M9.5 7V4.5h5V7M6.5 7l1 12.5h9l1-12.5M10 11v5.5M14 11v5.5"/>',
listx: '<path d="M4 6h11M4 11h7M4 16h6M14.5 13.5l5.5 5.5M20 13.5l-5.5 5.5"/>',
};
function svgIcon(name, size) {
const span = el('span', 'nui-svg');
const px = size || 20;
span.innerHTML = `<svg viewBox="0 0 24 24" width="${px}" height="${px}" aria-hidden="true" fill="none" ` +
`stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${SVG_PATHS[name]}</svg>`;
return span;
}
// The sheets (native-ui.js, or the browser host's copy) show text icons; an item's `svg`
// replaces its icon afterwards (the rows come in the items' order).
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function openSheet(title, items) {
if (!(window.DioraUI && DioraUI.openSheet)) return;
DioraUI.openSheet(title, items);
const shown = items.filter(it => it && !it.custom);
if (!shown.some(it => it.svg)) return;
const rows = document.querySelectorAll('#nui-sheet .nui-panel > .nui-row');
if (rows.length !== shown.length) return;
shown.forEach((it, i) => {
const icon = it.svg && rows[i].querySelector('.nui-row-icon');
if (icon) { icon.textContent = ''; icon.append(svgIcon(it.svg)); }
});
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
function clock(seconds) {
seconds = Math.max(0, Math.round(seconds || 0));
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
return h ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}` : `${m}:${String(s).padStart(2, '0')}`;
}
function minutes(seconds) {
const m = Math.round((seconds || 0) / 60);
if (m < 60) return `${m} min`;
return `${Math.floor(m / 60)} h ${String(m % 60).padStart(2, '0')} min`;
}
// Short dates: heute, gestern, vor 3 Tagen (up to 6), then 8.7. this year, 8.7.22 before.
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function day(iso) {
if (!iso) return '';
const d = new Date(iso);
if (isNaN(d)) return '';
const now = new Date();
const midnight = x => new Date(x.getFullYear(), x.getMonth(), x.getDate()).getTime();
const diff = Math.round((midnight(now) - midnight(d)) / 86400000);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (diff <= 0) return 'heute';
if (diff === 1) return 'gestern';
if (diff < 7) return `vor ${diff} Tagen`;
const dm = `${d.getDate()}.${d.getMonth() + 1}.`;
return d.getFullYear() === now.getFullYear() ? dm : dm + String(d.getFullYear() % 100).padStart(2, '0');
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
function toast(text) {
let t = $id('nui-radio-toast');
if (!t) {
t = el('div');
t.id = 'nui-radio-toast';
document.body.appendChild(t);
}
t.textContent = text;
t.classList.add('nui-show');
// The element is shared (radio and podcasts): so is its timer, or one hides the other's text early.
clearTimeout(t.nuiTimer);
t.nuiTimer = setTimeout(() => t.classList.remove('nui-show'), 2600);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
// GET from our server; a status other than 2xx throws (as a network error does).
async function getJson(url) {
const r = await fetch(url);
if (!r.ok) throw new Error(r.status);
return r.json();
}
// A full sheet (the player, shownotes, a podcast's info, statistics, downloads): the
// overlay — one id, the CSS and the TV's focus know it — its panel, the ⌄ at the top,
// and a tap beside the panel closes it. The caller fills the panel (top first) and
// puts the sheet on the page.
function podSheet(cls, closeLabel, onClose, dialogLabel) {
const sheet = el('div');
sheet.id = 'nui-pod-player';
if (cls) sheet.className = cls;
const panel = el('div', 'nui-pod-player-panel');
if (dialogLabel) {
panel.setAttribute('role', 'dialog');
panel.setAttribute('aria-label', dialogLabel);
}
const top = el('div', 'nui-pod-player-top');
top.append(button('⌄', closeLabel, onClose));
sheet.append(panel);
sheet.addEventListener('click', ev => { if (ev.target === sheet) onClose(); });
return {sheet, panel, top};
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
// Feed titles sometimes arrive with HTML entities still in them (&amp;).
const decoder = document.createElement('textarea');
function plain(s) {
if (!s || s.indexOf('&') < 0) return s || '';
decoder.innerHTML = s;
return decoder.value;
}
// Episodes come in two shapes: queue entries (episode__*) and inbox/feed
// episodes. One view of both:
function ep(o) {
const e = shape(o);
e.title = plain(e.title);
e.feed = plain(e.feed);
return e;
}
function shape(o) {
if (o.episode__id != null) {
return {
id: o.episode__id, title: o.episode__title, feed: o.episode__feed__title, feedId: o.episode__feed__id,
art: o.episode__artwork_url, feedArt: o.episode__feed__artwork_url || feedArtOf(o.episode__feed__id),
duration: o.episode__duration_seconds, pos: o.position_seconds, date: o.episode__pub_date, raw: o,
yt: isYouTube(o.episode__audio_url),
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
};
}
return {
id: o.id, title: o.title, feed: o.feed__title || (feedView && feedView.feed.title), feedId: o.feed__id,
art: o.artwork_url, feedArt: o.feed__artwork_url || feedArtOf(o.feed__id) || (feedView && feedView.feed.artwork_url),
duration: o.duration_seconds, pos: o.position_seconds, date: o.pub_date, played: o.played, inQueue: o.in_queue, raw: o,
yt: isYouTube(o.audio_url),
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
};
}
// "Kapitel 7/24 · <book>" while a chapter of an audiobook plays (playerState().book).
function bookLine(p) {
const b = p && p.book;
if (!b || !b.chapters) return '';
return `Kapitel ${b.index + 1}/${b.chapters}` + (b.title || p.feed ? ' · ' + plain(b.title || p.feed) : '');
}
// A video of a subscribed YouTube channel (diora-web docs/youtube.md): its sound
// is found on this device; the feed knows no length until it has played.
function isYouTube(url) {
return /^https:\/\/www\.youtube\.com\/watch\?v=/.test(url || '');
}
function feedArtOf(id) {
const f = id ? feeds().find(x => x.id === id) : null;
return f ? f.artwork_url : '';
}
// A cover: the episode's own, else its podcast's, else a tile with the podcast's initial.
function art(src, size, fallback, name) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const box = el('div', 'nui-pod-art' + (size ? ' nui-pod-art-' + size : ''));
const srcs = [src, fallback].filter(Boolean).map(s => s.replace(/^http:/, 'https:'))
.filter((s, i, all) => all.indexOf(s) === i);
const initial = () => {
if (box.querySelector('img') || box.firstChild) return;
const letter = (plain(name || '').replace(/^(the|der|die|das)\s+/i, '').match(/[0-9A-Za-zÀ-ÿ]/) || [''])[0];
if (letter) box.append(el('span', 'nui-pod-initial', letter.toUpperCase()));
};
const next = () => {
const s = srcs.shift();
if (!s) { initial(); return; }
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const img = el('img');
img.loading = 'lazy';
img.alt = '';
img.src = s;
img.addEventListener('error', () => { img.remove(); next(); });
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
box.append(img);
};
next();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
return box;
}
function inQueue(id) {
return queue().some(q => q.episode__id === id);
}
// ---- actions ---------------------------------------------------------------------------
function play(e) {
const r = remote();
if (r ? r.kind === 'podcast' && r.episodeId === e.id : currentId() === e.id) { openPlayer(); return; }
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
N.podcastPlayEpisode(JSON.stringify(e.raw), currentId());
}
function enqueue(e, where) {
N.podcastEnqueue(JSON.stringify([e.raw]), where || '', currentId());
const label = POSITIONS.find(p => p.value === (where || settings().enqueue_position));
toast('In der Warteschlange' + (label ? ': ' + label.label.toLowerCase() : ''));
}
// Move within the queue: "next" is right after the episode that plays.
function moveTo(e, where) {
const ids = queue().map(q => q.episode__id).filter(id => id !== e.id);
let index = where === 'top' ? 0 : ids.length;
if (where === 'next') {
const at = ids.indexOf(currentId());
index = at >= 0 ? at + 1 : 0;
}
N.podcastMove(e.id, index);
}
function markPlayed(e) {
N.podcastMarkPlayed(e.id, true);
toast('Als gehört markiert');
}
// Choosing several, from a row's menu (long press or ⋯).
const selectItem = (e, opts) => opts && opts.select &&
{icon: '☐', label: 'Mehrere auswählen', hint: 'diese und weitere Folgen auf einmal', action: () => startSelecting(e.id)};
// „Folge löschen“: out of the queue and „Neu“, its download goes here and on every other
// device (the server tells them), but not played: it keeps its place and stays in „Folgen“.
const DISCARD_HINT = 'aus Warteschlange und Neu, Datei auf allen Geräten löschen – nicht als gehört';
const discardItem = (e, opts) => N.podcastDiscard &&
{icon: '⊠', svg: 'trash', label: 'Folge löschen', hint: DISCARD_HINT, action: () => discardOne(e, (opts && opts.list) || '', (opts && opts.filters) || [])};
// One episode: no question, 4 s „Rückgängig“ like a swipe; sent only then. False when its row leaves the list.
function discardOne(e, list, filters) {
const hide = list === 'queue' || list === 'inbox' || list === 'downloads' ||
filters.includes('downloaded') || filters.includes('queued');
offerUndo(e, 'Folge gelöscht', {hide, svg: 'trash', run: () => {
discardNow([e.id]);
if (!hide) render();
}});
return !hide;
}
function discardNow(ids) {
N.podcastDiscard(JSON.stringify(ids));
// The lists fetched from the server (Folgen, a podcast) show it at once.
const lists = [epList && epList.items, feedView && feedView.episodes];
for (const l of lists) for (const o of l || []) if (ids.includes(o.id)) o.in_queue = false;
}
// Several: asked first.
function discardMany(ids) {
openSheet(`${nEpisodes(ids.length)} löschen? Nicht als gehört markiert.`, [
{icon: '⊠', svg: 'trash', label: 'Löschen', hint: DISCARD_HINT, danger: true, action: () => {
discardNow(ids);
toast(`${nEpisodes(ids.length)} gelöscht`);
stopSelecting();
}},
{icon: '‹', label: 'Abbrechen', action: () => {}},
]);
}
const discardAction = () => N.podcastDiscard && {label: 'Löschen', svg: 'trash', danger: true, run: discardMany};
function queueMenu(e, opts) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const playing = currentId() === e.id;
openSheet(e.title, [
{icon: '▶', label: playing ? 'Player öffnen' : 'Abspielen', action: () => play(e)},
!playing && {icon: '☰', label: 'Folge ansehen', hint: 'Shownotes, Kapitel, alles zu dieser Folge', action: () => openEpisode(e, opts)},
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
favoriteItem(e),
!playing && {icon: '⤴', label: 'Als Nächstes', hint: 'direkt nach der laufenden Folge', action: () => moveTo(e, 'next')},
{icon: '⇈', label: 'Ganz nach oben', action: () => moveTo(e, 'top')},
{icon: '⇊', label: 'Ganz nach unten', action: () => moveTo(e, 'bottom')},
{icon: '✓', label: 'Als gehört markieren', hint: 'verlässt die Warteschlange', action: () => markPlayed(e)},
...dlMenuItems(e),
{icon: '✕', svg: 'listx', label: 'Aus der Warteschlange nehmen', hint: dlDone(e.id) ? 'Datei bleibt' : null,
action: () => N.podcastDequeue(e.id)},
discardItem(e, opts),
selectItem(e, opts),
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
]);
}
function episodeMenu(e, opts) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const queued = inQueue(e.id);
openSheet(e.title, [
{icon: '▶', label: 'Abspielen', hint: queued ? null : 'kommt dafür in die Warteschlange', action: () => play(e)},
{icon: '☰', label: 'Folge ansehen', hint: 'Shownotes, Kapitel, alles zu dieser Folge', action: () => openEpisode(e, opts)},
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
favoriteItem(e),
!queued && {icon: '⤴', label: 'Als Nächstes', action: () => enqueue(e, 'after_current')},
!queued && {icon: '⇈', label: 'An den Anfang der Warteschlange', action: () => enqueue(e, 'front')},
!queued && {icon: '⇊', label: 'Ans Ende der Warteschlange', action: () => enqueue(e, 'back')},
queued && {icon: '✕', svg: 'listx', label: 'Aus der Warteschlange nehmen', hint: dlDone(e.id) ? 'Datei bleibt' : null,
action: () => N.podcastDequeue(e.id)},
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
!e.played && {icon: '✓', label: 'Als gehört markieren', action: () => markPlayed(e)},
e.played && {icon: '↺', label: 'Als ungehört markieren', action: () => N.podcastMarkPlayed(e.id, false)},
...dlMenuItems(e),
{icon: '⊘', svg: 'listx', label: 'Ausblenden', hint: 'aus „Neu“ entfernen, ohne es als gehört zu zählen',
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
action: () => N.podcastDismiss(JSON.stringify([e.id]))},
discardItem(e, opts),
selectItem(e, opts),
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
]);
}
function settingsMenu() {
const s = settings();
const choose = (name, title) => openSheet(title, POSITIONS.map(p => ({
icon: s[name] === p.value ? '●' : '○',
label: p.label,
active: s[name] === p.value,
action: () => N.podcastSettings(JSON.stringify({[name]: p.value})),
})));
const label = v => (POSITIONS.find(p => p.value === v) || POSITIONS[0]).label;
const prefs = devicePrefs();
// Grouped like AntennaPod's settings; each group opens its own list.
const group = (icon, title, hint, items) => ({icon, label: title, hint, action: () => openSheet(title, items())});
openSheet('Podcasts', [
{icon: '⟳', label: 'Alle Feeds jetzt abrufen', hint: 'der Server holt sie, hier kommt nur das Ergebnis an',
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
action: () => refresh(true)},
group('≡', 'Warteschlange', 'wohin neue Folgen kommen, gehörte entfernen', () => [
{icon: '+', label: 'Neue Folgen von Hand einreihen', hint: label(s.enqueue_position),
action: () => choose('enqueue_position', 'Von Hand eingereihte Folgen kommen …')},
{icon: '↻', label: 'Automatisch eingereihte Folgen', hint: label(s.auto_queue_position),
action: () => choose('auto_queue_position', 'Neue Folgen von Podcasts mit „Automatisch einreihen“ kommen …')},
removePlayedItem(),
keepSkippedItem(),
deleteDequeueItem(),
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
]),
group('▶', 'Abspielen', ['Sprungweiten', 'speed_presets' in s && 'Tempo', 'skip_silence' in prefs && 'Stille', '„gehört“',
'times_by_speed' in s && 'Zeiten', 'headset_next' in prefs && 'Kopfhörer',
'notify_buttons' in prefs && 'Benachrichtigung'].filter(Boolean).join(', '), () => [
skipItem('skip_back', 'Zurückspringen', SKIPS),
skipItem('skip_forward', 'Vorspringen', SKIPS),
'speed_presets' in s && {icon: '»', label: 'Tempo-Voreinstellungen', hint: presets().map(speedLabel).join(' · '),
action: () => presetsEditor()},
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
silenceItem(),
playedItem(),
timesItem(),
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
headsetItem(),
notifyItem(),
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
]),
mobileGroup(),
DL && {icon: '↓', label: 'Downloads', hint: dlSummary(), action: openDownloads},
DL && group('⚙︎', 'Download-Einstellungen', 'automatisch, gleichzeitig, Limit, aufräumen', () => [
// Account-wide (server): new subscriptions start with auto-download on or off.
'auto_download_default' in s && {icon: s.auto_download_default ? '☑' : '☐', label: 'Neue Abos laden von selbst',
hint: s.auto_download_default ? 'an: neue Folgen neuer Abos werden geladen' : 'aus: pro Podcast einschaltbar',
action: () => N.podcastSettings(JSON.stringify({auto_download_default: !s.auto_download_default}))},
...dlSettingsItems(),
]),
backgroundSyncItem(),
{icon: '⇆', label: 'Wischgesten', hint: 'was Wischen nach links und rechts in jeder Liste tut', action: swipeSettings},
startPageItem(),
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
group('⇅', 'Abos umziehen', 'OPML exportieren und importieren', () => [
{icon: '⇩', label: 'Abos exportieren (OPML)', hint: 'zum Umziehen in eine andere App', action: exportOpml},
{icon: '⇧', label: 'Abos importieren (OPML)', action: importOpml},
hasSources() && {icon: '⚙', label: 'Abos & Quellen', hint: 'alle Abos an einem Ort: Podcasts, YouTube, Nachrichten, Blogs', action: toSources},
].filter(Boolean)),
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
{icon: '▤', label: 'Hörstatistik', action: openStats},
]);
}
// The app's background job (BackgroundSync.kt): new episodes, their notifications and downloads with the app closed.
const BG_SYNC = [
{value: 0, label: 'Aus', hint: 'nur wenn die App offen ist'},
{value: 15, label: 'Alle 15 Minuten'},
{value: 60, label: 'Jede Stunde', hint: 'Standard'},
{value: 240, label: 'Alle 4 Stunden'},
];
function backgroundSyncItem() {
if (!N.podcastBackgroundSync || !N.podcastSetBackgroundSync) return null;
const now = N.podcastBackgroundSync();
const label = (BG_SYNC.find(c => c.value === now) || BG_SYNC[2]).label;
return {icon: '⟲', label: 'Im Hintergrund abgleichen', hint: label + ' · neue Folgen, Mitteilungen, Downloads',
action: () => openSheet('Im Hintergrund abgleichen', BG_SYNC.map(c => ({
icon: c.value === now ? '●' : '○', label: c.label, hint: c.hint, active: c.value === now,
action: () => { N.podcastSetBackgroundSync(c.value); settingsMenu(); },
})))};
}
// Skip lengths and "counts as played" belong to the account where the server knows them
// (the host sends them there, PlaybackPrefs.set); else to this device.
const where = name => (name in settings() ? 'für alle Geräte' : 'auf diesem Gerät');
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function skipItem(name, label, choices) {
if (!N.podcastSetPrefValue) return null;
const now = devicePrefs()[name];
return {icon: name === 'skip_back' ? '↺' : '↻', label, hint: `${now} Sekunden`,
action: () => openSheet(`${label} (${where(name)})`, choices.map(c => ({
icon: c === now ? '●' : '○', label: `${c} Sekunden`, active: c === now,
action: () => { N.podcastSetPrefValue(name, c); toast(`${label}: ${c} Sekunden`); },
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
})))};
}
function playedItem() {
if (!N.podcastSetPrefValue) return null;
const now = devicePrefs().smart_played;
if (now == null) return null;
const label = v => (v ? `${v >= 60 ? v / 60 + ' min' : v + ' s'} vor dem Ende` : 'erst ganz am Ende');
return {icon: '✓', label: 'Als gehört zählen', hint: label(now),
action: () => openSheet(`Eine Folge zählt als gehört … (${where('smart_played')})`,
[0, 15, 30, 45, 60, 120, 300, 600].map(v => ({
icon: v === now ? '●' : '○', label: label(v), active: v === now,
action: () => N.podcastSetPrefValue('smart_played', v),
})))};
}
// An account setting (server: POST /podcasts/settings/), through the host.
function setAccount(name, value, text) {
if (!N.podcastSettings) return;
N.podcastSettings(JSON.stringify({[name]: value}));
if (text) toast(text);
}
function accountSwitch(name, label, onHint, offHint, def) {
const s = settings();
if (!(name in s)) return null;
const on = s[name] == null ? def : !!s[name];
return {icon: on ? '☑' : '☐', label, hint: on ? onHint : offHint,
action: () => setAccount(name, !on, `${label}: ${on ? 'aus' : 'an'}`)};
}
// AntennaPod's "keep skipped episodes": ⏭, "Folge überspringen", headset and car go on without marking.
function keepSkippedItem() {
return accountSwitch('keep_skipped', 'Übersprungene Folgen behalten',
'an: wer zur nächsten springt, lässt die Folge ungehört in der Warteschlange',
'aus: übersprungene Folgen zählen als gehört', true);
}
// AntennaPod's "delete removes from queue".
function deleteDequeueItem() {
if (!DL) return null;
return accountSwitch('delete_dequeue', '„Datei löschen“ nimmt aus der Warteschlange',
'an: wer die Datei einer Folge löscht, nimmt sie auch aus der Warteschlange', 'aus: die Folge bleibt eingereiht', true);
}
// AntennaPod's "adjust media info to playback speed".
function timesItem() {
return accountSwitch('times_by_speed', 'Zeiten an das Tempo anpassen',
'an: Längen und Restzeiten so, wie lange sie beim eingestellten Tempo dauern', 'aus: Längen wie bei 1×', false);
}
// What may use mobile data: downloads by hand (account), automatic ones (this device), streaming (account).
const STREAMING = [
{value: 'allow', label: 'Erlauben'},
{value: 'ask', label: 'Nachfragen', hint: 'einmal pro Folge'},
{value: 'never', label: 'Nie', hint: 'nur WLAN oder heruntergeladene Folgen'},
];
function mobileGroup() {
const s = settings();
const items = () => {
const now = settings();
const dls = DL ? parse(N.podcastDownloadSettings(), dlSettings) : {};
const streaming = STREAMING.find(c => c.value === now.mobile_streaming) || STREAMING[0];
return [
DL && accountSwitch('mobile_downloads', 'Downloads per Hand über mobile Daten',
'an: was du von Hand lädst, lädt auch unterwegs', 'aus: wartet aufs WLAN, wie automatische Downloads', true),
DL && {icon: dls.mobile ? '☑' : '☐', label: 'Automatische Downloads über mobile Daten',
hint: (dls.mobile ? 'an' : 'aus: nur im WLAN') + ' · dieses Gerät', action: () => dlSet({mobile: !dls.mobile})},
N.podcastAllowStream && 'mobile_streaming' in now && {icon: '⇵', label: 'Streamen über mobile Daten',
hint: streaming.label + ' · Radio ist davon nicht betroffen',
action: () => openSheet('Folgen über mobile Daten streamen', STREAMING.map(c => ({
icon: c.value === streaming.value ? '●' : '○', label: c.label, hint: c.hint || null, active: c.value === streaming.value,
action: () => setAccount('mobile_streaming', c.value, `Streamen über mobile Daten: ${c.label}`),
})))},
].filter(Boolean);
};
if (!('mobile_downloads' in s || 'mobile_streaming' in s) || !items().length) return null;
return {icon: '◢', label: 'Mobile Daten', hint: 'Downloads und Streamen unterwegs', action: () => openSheet('Mobile Daten', items())};
}
// Which page the app opens with (this device).
function startPageItem() {
const now = startPage();
const label = id => (startPages().find(p => p.id === id) || startPages()[0]).label;
return {icon: '⌂', label: 'Startseite', hint: label(now) + (IN_APP ? ' · beim Öffnen der App' : ' · beim Öffnen von diora'),
action: () => openSheet('Beim Öffnen zeigen …', startPages().filter(p => p.id !== 'radio' || !IN_APP ||
document.documentElement.classList.contains('nui-radio')).map(p => ({
icon: p.id === now ? '●' : '○', label: p.label, hint: p.hint || null, active: p.id === now,
action: () => {
try { localStorage.setItem('nui_start_page', p.id); } catch (e) {}
toast('Startseite: ' + p.label);
},
})))};
}
// ---- speed presets (the account's; AntennaPod's speed presets, up to 3.5×) ----
const roundSpeed = v => Math.round(v * 100) / 100;
function savePresets(list) {
setAccount('speed_presets', list);
setTimeout(() => presetsEditor(list || SPEEDS), 60);
}
function presetsEditor(list) {
const now = (list || presets()).slice().sort((a, b) => a - b);
const own = Array.isArray(settings().speed_presets) || !!list;
openSheet('Tempo-Voreinstellungen', now.map(v => ({
icon: '✕', label: speedLabel(v), hint: now.length > 1 ? 'antippen zum Entfernen' : 'eine bleibt immer',
action: () => { if (now.length > 1) savePresets(now.filter(x => x !== v)); else toast('Eine Voreinstellung bleibt immer'); },
})).concat([
{icon: '+', label: 'Tempo hinzufügen', hint: `${speedLabel(MIN_SPEED)} bis ${speedLabel(MAX_SPEED)}`,
action: () => addPreset(now)},
own && {icon: '↺', label: 'Standardliste', hint: SPEEDS.map(speedLabel).join(' · '), action: () => savePresets(null)},
].filter(Boolean)));
}
function addPreset(now) {
if (now.length >= MAX_PRESETS) { toast(`Höchstens ${MAX_PRESETS} Voreinstellungen`); return; }
const add = v => savePresets(now.concat(roundSpeed(v)).sort((a, b) => a - b));
const choices = [];
for (let v = MIN_SPEED; v <= MAX_SPEED + 0.001; v += 0.25) {
if (!now.some(x => Math.abs(x - v) < 0.001)) choices.push(roundSpeed(v));
}
openSheet('Tempo hinzufügen', choices.map(v => ({icon: '+', label: speedLabel(v), action: () => add(v)}))
.concat(typeof customPrompt === 'function' ? [{icon: '✎', label: 'Eigener Wert …', hint: 'z. B. 1,65', action: async () => {
const t = await customPrompt(`Tempo (${speedLabel(MIN_SPEED)} bis ${speedLabel(MAX_SPEED)}):`, '');
if (t == null) return;
const v = roundSpeed(parseFloat(String(t).replace(',', '.').replace(/[×x]/g, '')));
if (!(v >= MIN_SPEED && v <= MAX_SPEED)) { toast(`Bitte eine Zahl von ${speedLabel(MIN_SPEED)} bis ${speedLabel(MAX_SPEED)}`); return; }
if (now.some(x => Math.abs(x - v) < 0.001)) { toast(`${speedLabel(v)} ist schon dabei`); return; }
add(v);
}}] : []));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
// A host without headset buttons or silence skipping (the browser) leaves them out of its prefs.
function headsetItem() {
if (!N.podcastSetPref || !('headset_next' in devicePrefs())) return null;
const next = !!devicePrefs().headset_next;
return {icon: '🎧', label: 'Kopfhörer: zweimal tippen', hint: next ? 'nächste Folge' : 'vorspulen (dreimal: zurück)',
action: () => openSheet('Kopfhörer zweimal tippen …', [
{icon: !next ? '●' : '○', label: 'Vorspulen', hint: 'dreimal tippen springt zurück', action: () => N.podcastSetPref('headset_next', false)},
{icon: next ? '●' : '○', label: 'Nächste Folge', action: () => N.podcastSetPref('headset_next', true)},
])};
}
// The notification's two extra buttons (app only: NotifyButtons.kt), like AntennaPod's.
const NOTIFY = [
{key: 'next', label: 'Folge überspringen',
hint: () => (keepSkipped() ? 'die nächste spielt, diese bleibt ungehört eingereiht' : 'zählt als gehört, die nächste spielt')},
{key: 'chapter', label: 'Nächstes Kapitel', hint: () => 'wenn die Folge Kapitel hat'},
// The account's presets; without any, the app's own list.
{key: 'speed', label: 'Tempo', hint: () => (Array.isArray(settings().speed_presets) && settings().speed_presets.length
? presets() : [1, 1.2, 1.5, 1.75, 2]).map(speedLabel).join(' · ')},
{key: 'sleep', label: 'Sleep-Timer', hint: () => `${sleepMinutesLabel((player.sleep || {}).last || 15)} an / aus, auch beim Radio`},
];
function notifyItem() {
if (!N.podcastSetNotifyButtons || !Array.isArray(devicePrefs().notify_buttons)) return null;
const now = devicePrefs().notify_buttons;
const names = keys => keys.map(k => (NOTIFY.find(n => n.key === k) || {}).label).filter(Boolean).join(', ') || 'keine';
const sheet = chosen => openSheet('Zwei Knöpfe in der Benachrichtigung', NOTIFY.map(n => ({
icon: chosen.includes(n.key) ? '☑' : '☐', label: n.label, hint: n.hint(),
// A third one replaces the one chosen first.
action: () => {
const next = chosen.includes(n.key) ? chosen.filter(k => k !== n.key) : chosen.concat(n.key).slice(-2);
N.podcastSetNotifyButtons(next.join(','));
toast('Benachrichtigung: ' + names(next));
},
})));
return {icon: '⊞', label: 'Knöpfe in der Benachrichtigung', hint: names(now), action: () => sheet(now)};
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function silenceItem() {
if (!N.podcastSetPref || !('skip_silence' in devicePrefs())) return null;
const on = !!devicePrefs().skip_silence;
return {icon: on ? '☑' : '☐', label: 'Stille überspringen',
hint: on ? 'an: Pausen in Folgen werden übersprungen' : 'aus',
action: () => N.podcastSetPref('skip_silence', !on)};
}
function devicePrefs() {
return parse(N.podcastPrefs ? N.podcastPrefs() : '{}', {});
}
function removePlayedItem() {
if (!N.podcastSetPref) return null;
const on = devicePrefs().remove_played !== false;
return {icon: on ? '☑' : '☐', label: 'Gehörte Folgen aus der Warteschlange nehmen',
hint: on ? 'an: fertige Folgen verlassen die Warteschlange' : 'aus: fertige Folgen bleiben stehen',
action: () => { N.podcastSetPref('remove_played', !on); toast(on ? 'Gehörte Folgen bleiben jetzt stehen' : 'Gehörte Folgen verlassen jetzt die Warteschlange'); }};
}
// ---- sorting, locking, choosing several ----------------------------------------------
const SORTS = [
{id: 'new', label: 'Neueste zuerst', key: e => -(Date.parse(e.date) || 0)},
{id: 'old', label: 'Älteste zuerst', key: e => Date.parse(e.date) || 0},
{id: 'short', label: 'Kürzeste zuerst', key: e => Math.max(0, (e.duration || 0) - (e.pos || 0))},
{id: 'long', label: 'Längste zuerst', key: e => -Math.max(0, (e.duration || 0) - (e.pos || 0))},
{id: 'feed', label: 'Nach Podcast', key: e => (e.feed || '').toLowerCase() + ' ' + String(9e15 - (Date.parse(e.date) || 0))},
];
// The episode that plays stays on top; everything else is sorted.
function sortQueue(sort) {
const items = queue().map(ep);
const now = items.filter(e => e.id === currentId());
const rest = items.filter(e => e.id !== currentId());
if (sort === 'shuffle') {
for (let i = rest.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[rest[i], rest[j]] = [rest[j], rest[i]];
}
} else {
const s = SORTS.find(x => x.id === sort);
rest.sort((a, b) => {
const ka = s.key(a), kb = s.key(b);
return ka < kb ? -1 : ka > kb ? 1 : 0;
});
}
N.podcastReorder(JSON.stringify(now.concat(rest).map(e => e.id)));
}
const keepSorted = () => settings().keep_sorted || '';
function sortMenu() {
const kept = keepSorted();
const keptLabel = kept ? (SORTS.find(x => x.id === kept) || {}).label : '';
openSheet('Warteschlange sortieren', SORTS.map(x => ({icon: '⇅', label: x.label, action: () => sortQueue(x.id)}))
.concat([{icon: '⤨', label: 'Mischen', action: () => sortQueue('shuffle')}])
.concat('keep_sorted' in settings() ? [{icon: kept ? '☑' : '☐', label: 'Immer sortiert halten',
hint: kept ? `an: ${keptLabel}` : 'aus: Reihenfolge bestimmst du',
action: () => openSheet('Immer sortiert halten', [{id: '', label: 'Aus'}].concat(SORTS).map(x => ({
icon: kept === x.id ? '●' : '○', label: x.label, hint: x.id ? 'auch neu eingereihte Folgen' : null,
action: () => N.podcastSettings(JSON.stringify({keep_sorted: x.id})),
})))}] : []));
}
function toggleLock() {
locked = !locked;
try { localStorage.setItem('nui_pod_locked', locked ? '1' : '0'); } catch (e) {}
toast(locked ? 'Warteschlange gesperrt' : 'Warteschlange entsperrt');
render();
}
function startSelecting(id) {
commitSwipe();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
selected = new Set(id ? [id] : []);
render();
}
function stopSelecting() {
if (!selected) return false;
selected = null;
render();
return true;
}
function toggleSelected(id) {
if (selected.has(id)) selected.delete(id); else selected.add(id);
render();
}
// The bar while choosing: count, all/none, and what to do with the chosen ones.
function selectionBar(items, actions) {
const bar = el('div', 'nui-pod-selbar');
const all = items.length > 0 && items.every(e => selected.has(e.id));
bar.append(
button('✕', 'Auswahl beenden', stopSelecting),
el('span', 'nui-pod-selcount', selected.size ? `${selected.size} ausgewählt` : 'Folgen antippen'),
button(all ? '☑' : '☐', all ? 'Keine auswählen' : 'Alle auswählen', () => {
selected = new Set(all ? [] : items.map(e => e.id));
render();
}),
);
const acts = el('div', 'nui-pod-selactions');
for (const a of actions.filter(Boolean)) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const b = el('button', 'btn btn-sm' + (a.danger ? ' btn-danger' : ''), a.label);
if (a.svg) b.prepend(svgIcon(a.svg, 16));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
b.type = 'button';
b.disabled = !selected.size;
b.addEventListener('click', () => {
const ids = items.filter(e => selected.has(e.id)).map(e => e.id);
if (ids.length) a.run(ids);
});
acts.append(b);
}
bar.append(acts);
return bar;
}
function listTools(buttons) {
const t = el('div', 'nui-pod-tools');
for (const b of buttons) if (b) t.append(b);
return t;
}
const nEpisodes = n => `${n} ${n === 1 ? 'Folge' : 'Folgen'}`;
// Choosing several in a list of episodes (Folgen, one podcast): what to do with them.
function episodeSelection(items) {
const pick = ids => items.filter(e => ids.includes(e.id));
const fresh = new Set(inbox().map(o => o.id));
return selectionBar(items, [
{label: 'Einreihen', run: ids => enqueueMany(pick(ids))},
{label: 'Gehört', run: ids => markMany(pick(ids), true)},
{label: 'Ungehört', run: ids => markMany(pick(ids), false)},
DL && {label: 'Laden', run: ids => downloadMany(pick(ids))},
{label: 'Favorit', run: ids => openSheet(nEpisodes(ids.length), [
{icon: '★', label: 'Als Favorit markieren', action: () => favoriteMany(pick(ids), true)},
{icon: '☆', label: 'Kein Favorit mehr', action: () => favoriteMany(pick(ids), false)},
])},
// Only where some of them are new.
items.some(e => fresh.has(e.id)) && {label: 'Aus „Neu“', svg: 'listx', run: ids => {
const gone = ids.filter(id => fresh.has(id));
if (gone.length) N.podcastDismiss(JSON.stringify(gone));
toast(gone.length ? `${nEpisodes(gone.length)} aus „Neu“ entfernt` : 'Keine davon ist in „Neu“');
stopSelecting();
}},
discardAction(),
].filter(Boolean));
}
function enqueueMany(list) {
const todo = list.filter(e => !inQueue(e.id));
if (!todo.length) { toast('Schon alle in der Warteschlange'); stopSelecting(); return; }
const add = where => {
N.podcastEnqueue(JSON.stringify(todo.map(e => e.raw)), where, currentId());
for (const e of todo) e.raw.in_queue = true;
toast(`${nEpisodes(todo.length)} eingereiht`);
stopSelecting();
};
openSheet(`${nEpisodes(todo.length)} einreihen`, [
{icon: '+', label: 'Wie eingestellt', hint: (POSITIONS.find(p => p.value === settings().enqueue_position) || POSITIONS[0]).label,
action: () => add('')},
{icon: '⤴', label: 'Als Nächstes', action: () => add('after_current')},
{icon: '⇈', label: 'An den Anfang', action: () => add('front')},
{icon: '⇊', label: 'Ans Ende', action: () => add('back')},
]);
}
function markMany(list, played) {
const ids = list.map(e => e.id);
if (played) {
N.podcastMarkPlayedAll(JSON.stringify(ids), true);
} else {
// "Ungehört" one by one fetches the state again each time: all but the
// last in one go; downloaded ones singly, the app has to know of them.
const single = ids.filter(id => DL && downloads[id]);
const rest = ids.filter(id => !(DL && downloads[id]));
if (!single.length) single.push(rest.pop());
if (rest.length) N.podcastMarkPlayedAll(JSON.stringify(rest), false);
for (const id of single) N.podcastMarkPlayed(id, false);
}
for (const e of list) {
e.raw.played = played;
if (played) e.raw.position_seconds = 0;
}
toast(`${nEpisodes(ids.length)} als ${played ? 'gehört' : 'ungehört'} markiert`);
stopSelecting();
}
function downloadMany(list) {
const todo = list.filter(e => (e.raw.audio_url || e.raw.episode__audio_url) &&
!(downloads[e.id] && downloads[e.id].state !== 'failed'));
const waiting = {};
for (const e of todo) {
N.podcastDownload(JSON.stringify(e.raw));
waiting[e.id] = {state: 'queued', progress: 0, bytes: 0, total: 0, title: e.title, reason: 'queue'};
}
toast(todo.length ? `${nEpisodes(todo.length)} ${todo.length === 1 ? 'wird' : 'werden'} geladen` : 'Schon alle geladen');
selected = null;
window.__dioraDownloads(Object.assign({}, downloads, waiting));
render();
}
async function favoriteMany(list, star) {
stopSelecting();
try {
const r = await fetch('/podcasts/episodes/favorite/', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({episode_ids: list.map(e => e.id), favorite: star}),
});
if (!r.ok) throw new Error(r.status);
for (const e of list) {
e.raw.favorite = star;
if (N.podcastFavoriteChanged) N.podcastFavoriteChanged(e.id, star);
const cached = storedDetails(e.id);
if (cached) keepDetails(e.id, Object.assign({}, cached, {favorite: star}));
}
toast(star ? `★ ${nEpisodes(list.length)} als Favorit markiert` : `${nEpisodes(list.length)}: kein Favorit mehr`);
} catch (err) {
toast(navigator.onLine ? 'Favoriten ließen sich nicht setzen' : 'Favoriten brauchen eine Verbindung');
}
render();
}
// ---- filters and order of episode lists (Folgen, one podcast) -------------------------
const FILTERS = [
{id: 'unplayed', label: 'Ungehört', test: o => !o.played, none: 'Keine ungehörten Folgen.'},
{id: 'in_progress', label: 'Angefangen', test: o => !o.played && (o.position_seconds || 0) > 0,
none: 'Keine angefangenen Folgen.'},
{id: 'favorites', label: 'Favoriten', test: o => !!o.favorite,
none: 'Noch keine Favoriten. Im ⋯-Menü einer Folge markierst du sie mit ★.'},
{id: 'queued', label: 'Warteschlange', test: o => inQueue(o.id), none: 'Keine davon steht in der Warteschlange.'},
{id: 'downloaded', label: 'Geladen', app: true, test: o => dlDone(o.id), none: 'Keine Folgen auf dem Gerät.'},
{id: 'history', label: 'Verlauf', all: true, test: o => !!o.played || (o.position_seconds || 0) > 0,
none: 'Noch nichts gehört.'},
{id: 'played', label: 'Gehört', test: o => !!o.played, none: 'Noch keine gehörten Folgen.'},
];
// Filters that can't both be on: choosing one takes the others off.
const EXCLUDES = {unplayed: ['played'], in_progress: ['played'], played: ['unplayed', 'in_progress']};
const EP_SORTS = [
{id: 'new', label: 'Neueste zuerst', key: o => -(Date.parse(o.pub_date) || -Infinity)},
{id: 'old', label: 'Älteste zuerst', key: o => Date.parse(o.pub_date) || Infinity},
{id: 'long', label: 'Längste zuerst', key: o => (o.duration_seconds ? -o.duration_seconds : Infinity)},
{id: 'short', label: 'Kürzeste zuerst', key: o => o.duration_seconds || Infinity},
{id: 'title', label: 'Nach Titel', key: o => plain(o.title).toLocaleLowerCase('de')},
{id: 'played', label: 'Zuletzt gehört', all: true},
];
const sortLabel = id => (EP_SORTS.find(s => s.id === id) || EP_SORTS[0]).label;
function dlDone(id) {
return DL && !!downloads[id] && downloads[id].state === 'done';
}
function matchesAll(o, filters) {
return filters.every(id => { const f = FILTERS.find(x => x.id === id); return !f || f.test(o); });
}
function sortEpisodes(list, sort) {
const s = EP_SORTS.find(x => x.id === sort);
if (!s || !s.key) return list;
const keyed = list.map((o, i) => [s.key(o), i, o]);
keyed.sort((a, b) => (typeof a[0] === 'string' ? a[0].localeCompare(b[0], 'de') : a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0) ||
a[1] - b[1]);
return keyed.map(k => k[2]);
}
// One row of chips: "Alle" and the filters, combinable.
function filterRow(active, onChange, allEpisodes) {
const chips = el('div', 'nui-pod-filters');
const chip = (label, on, onClick) => {
const b = el('button', 'nui-pod-chip' + (on ? ' on' : ''), label);
b.type = 'button';
b.setAttribute('aria-pressed', on ? 'true' : 'false');
b.addEventListener('click', onClick);
chips.append(b);
};
chip('Alle', !active.length, () => onChange([]));
for (const f of FILTERS) {
if ((f.app && !DL) || (f.all && !allEpisodes)) continue;
const on = active.includes(f.id);
chip(f.label, on, () => onChange(on ? active.filter(x => x !== f.id)
: active.filter(x => !(EXCLUDES[f.id] || []).includes(x)).concat(f.id)));
}
// An active filter further right shows (the row scrolls sideways).
requestAnimationFrame(() => {
const on = chips.querySelector('.nui-pod-chip.on');
if (on && on.offsetLeft + on.offsetWidth > chips.scrollLeft + chips.clientWidth) chips.scrollLeft = on.offsetLeft - 8;
});
return chips;
}
function sortMenuFor(current, allEpisodes, onPick) {
openSheet('Folgen sortieren', EP_SORTS.filter(s => allEpisodes || !s.all).map(s => ({
icon: s.id === current ? '●' : '○', label: s.label, active: s.id === current, action: () => onPick(s.id),
})));
}
function noneText(filters) {
const f = filters.length === 1 && FILTERS.find(x => x.id === filters[0]);
return f ? f.none : filters.length ? 'Keine Folgen mit diesen Filtern.' : 'Keine Folgen.';
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function refresh(fetchFeeds) {
refreshing = true;
render();
N.podcastRefresh(!!fetchFeeds);
}
// ---- rendering ---------------------------------------------------------------------------
let root = null;
// The page scrolls inside .main-content (app.css), not the window.
function scroller() {
for (let e = root && root.parentElement; e && e !== document.body; e = e.parentElement) {
const o = getComputedStyle(e).overflowY;
if (o === 'auto' || o === 'scroll') return e;
}
return document.scrollingElement || document.documentElement;
}
function scrollTop(y) {
const s = scroller();
if (y == null) return s.scrollTop;
s.scrollTop = y;
return y;
}
function build() {
const tab = $id('tab-podcasts');
if (!tab || $id('nui-pod')) return false;
root = el('div');
root.id = 'nui-pod';
tab.prepend(root);
return true;
}
function render() {
// A finger is swiping a row: redrawing now would pull the row away under it (and end
// the swipe). It redraws when the swipe ends — unless that never came (a lost pointer).
if (swipingSince && Date.now() - swipingSince < 8000) { renderAfterSwipe = true; return; }
episodeScreenParts();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (!root) return;
const scroll = scrollTop();
// Search results arriving while typing: the field keeps its text and the cursor.
const q = $id('nui-pod-q');
const typing = q && document.activeElement === q ? [q.selectionStart, q.selectionEnd] : null;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
root.textContent = '';
const bar = el('div', 'nui-tab-bar');
bar.id = 'nui-pod-bar';
const text = el('div', 'nui-bar-text');
text.append(el('span', 'native-brand', 'Podcasts'));
if (refreshing) text.append(el('span', 'muted nui-pod-status', 'Wird abgerufen …'));
else if (state.server_time) text.append(el('span', 'muted nui-pod-status', 'Stand ' + day(state.server_time) + ', ' +
new Date(state.server_time).toLocaleTimeString('de-DE', {hour: '2-digit', minute: '2-digit'})));
bar.append(...[text, dlIndicator(), searchButton(), button('⟳', 'Alle Feeds abrufen', () => refresh(true)),
button('⋮', 'Einstellungen', settingsMenu)].filter(Boolean));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
root.append(bar);
if (preview) {
renderPreview();
} else if (feedView) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
renderFeed();
} else {
const tabs = el('div', 'nui-pod-tabs');
for (const v of VIEWS) {
const b = el('button', 'nui-pod-tab' + (v.id === view ? ' active' : ''));
b.type = 'button';
b.append(el('span', null, v.label));
const count = v.id === 'queue' ? queue().length : v.id === 'inbox' ? inboxTotal() : 0;
if (count) b.append(el('span', 'nui-pod-count' + (v.id === 'queue' ? ' nui-pod-count-queue' : ''), count > 999 ? '999+' : String(count)));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
b.addEventListener('click', () => {
selected = null;
view = v.id;
try { localStorage.setItem('nui_pod_view', view); } catch (e) {}
render();
scrollTop(0);
});
tabs.append(b);
}
root.append(tabs);
const offer = importOffer();
if (offer) root.append(offer);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (view === 'queue') renderQueue();
else if (view === 'inbox') renderInbox();
else if (view === 'episodes') renderEpisodes();
else if (view === 'feeds') renderFeeds();
else renderSearch();
}
scrollTop(scroll);
const q2 = typing && $id('nui-pod-q');
if (q2) {
q2.focus();
try { q2.setSelectionRange(typing[0], typing[1]); } catch (e) {}
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
// The search: a magnifier in the top bar (an SVG: the text glyphs for it differ from font to font).
function searchButton() {
const b = button('', 'Suchen', () => {
selected = null;
feedView = null;
preview = null;
view = 'search';
render();
scrollTop(0);
const q = $id('nui-pod-q');
if (q && !(searchResults && searchResults.q)) q.focus();
}, 'nui-pod-searchbtn' + (view === 'search' && !feedView && !preview ? ' on' : ''));
b.innerHTML = '<svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" fill="none" stroke="currentColor" ' +
'stroke-width="2" stroke-linecap="round"><circle cx="10.5" cy="10.5" r="6.5"/><path d="M15.5 15.5 21 21"/></svg>';
return b;
}
// AntennaPod had episodes on the phone: offered once to fetch them again (Wi-Fi only).
function importOffer() {
const o = state.import_downloads;
if (!o || !o.count || !N.podcastImportDownloads || !N.podcastDownloadSettings) return null;
const box = el('div', 'nui-pod-offer');
const s = parse(N.podcastDownloadSettings(), {});
const needed = (Object.keys(downloads).length || 0) + o.count + (s.auto ? (s.count === 0 ? queue().length : s.count || 0) : 0);
const raise = s.limit > 0 && needed > s.limit
? ` „Höchstens behalten“ geht dafür von ${s.limit} auf ${[5, 10, 25, 50, 100, 500].find(n => n >= needed) || 'unbegrenzt'}.` : '';
box.append(el('div', null, `${o.count} ${o.count === 1 ? 'Folge war' : 'Folgen waren'} in AntennaPod heruntergeladen` +
(o.bytes ? ` (${size(o.bytes)})` : '') + '. Hier noch einmal laden? Nur im WLAN; sie kommen nicht in die Warteschlange.' + raise));
const row = el('div', 'nui-pod-offer-row');
row.append(button('Laden', 'Im WLAN herunterladen', () => { N.podcastImportDownloads(true); box.remove(); }, 'btn'),
button('Nein danke', 'Nicht laden', () => { N.podcastImportDownloads(false); box.remove(); }, 'btn'));
box.append(row);
return box;
}
window.__dioraImportDownloads = function (r) {
if (!r || !r.ok) toast('Die Liste aus AntennaPod kam nicht an — später noch einmal');
else if (r.count) toast(`${r.count} ${r.count === 1 ? 'Folge wird' : 'Folgen werden'} im WLAN geladen` +
(r.limit != null ? ` · Höchstens behalten: ${r.limit || 'unbegrenzt'}` : ''));
render();
};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function empty(text) {
root.append(el('div', 'nui-pod-empty', text));
}
function progressBar(pos, duration) {
if (!pos || !duration) return null;
const b = el('div', 'nui-pod-progress');
const f = el('span');
f.style.width = Math.min(100, Math.round(pos / duration * 100)) + '%';
b.append(f);
return b;
}
// One episode in a list. opts.list names the list (queue, inbox, episodes, feed, downloads):
// it decides the one action at hand and the swipes; everything else is in the row's menu
// (⋯ or a long press). opts.filters: the list's filters (what a swipe takes out of it).
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function episodeRow(e, opts) {
const list = opts.list || '';
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const row = el('div', 'nui-pod-row' + (currentId() === e.id ? ' nui-pod-current' : '') + (e.played ? ' nui-pod-played' : ''));
row.dataset.id = e.id;
if (swipeGone(e.id)) row.classList.add('nui-pod-gone');
row.append(art(e.art, null, e.feedArt, e.feed));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const main = el('div', 'nui-pod-main');
main.append(el('div', 'nui-pod-title', e.title));
// Length first: podcast names are long and get cut off.
const meta = [];
// A played episode, or a place past the feed's (often wrong) length: just the length.
const started = e.pos > 30 && !e.played && e.pos < e.duration - 30;
if (e.duration) meta.push(started ? minutes(shown(e.duration - e.pos, e.feedId)) + ' übrig' : minutes(shown(e.duration, e.feedId)));
if (e.yt) meta.push('YouTube');
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (opts.showDate && e.date) meta.push(day(e.date));
if (opts.showFeed && e.feed) meta.push(e.feed);
const metaEl = el('div', 'nui-pod-meta', meta.join(' · '));
// Where the row's button isn't the download: its state as a small mark.
const mark = selected || dlPrimary(e, list) ? null : dlMark(e);
if (mark) metaEl.prepend(mark);
main.append(metaEl);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const p = e.played ? null : progressBar(e.pos, e.duration);
if (p) main.append(p);
row.append(main);
if (selected) {
row.classList.add('nui-pod-selecting');
const box = el('span', 'nui-pod-check' + (selected.has(e.id) ? ' on' : ''), selected.has(e.id) ? '☑' : '☐');
row.prepend(box);
row.addEventListener('click', () => toggleSelected(e.id));
return row;
}
const how = {select: !opts.noSelect, list, filters: opts.filters || []};
const menu = () => (list === 'queue' ? queueMenu(e, how) : episodeMenu(e, how));
row.append(primaryButton(e, list), button('⋯', 'Mehr', menu, 'nui-pod-menu'));
row.addEventListener('click', () => openEpisode(e, how));
// Swipes: touch only, not while the queue is locked.
const swipes = list && SWIPE_LISTS[list] && !(list === 'queue' && locked) ? list : '';
rowGestures(row, menu, swipes && (act => swipeDone(e, swipes, act, opts.filters || [])), swipes);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
return row;
}
// The one action at hand: the queue and the downloads play, „Neu“ queues,
// the episode lists download (or queue, where nothing downloads).
function dlPrimary(e, list) {
return (list === 'episodes' || list === 'feed') && DL && !!(e.raw.audio_url || e.raw.episode__audio_url);
}
function primaryButton(e, list) {
if (list === 'queue' || list === 'downloads') {
const now = currentId() === e.id;
const b = button(now && player.playing ? '❚❚' : '▶', now && player.playing ? 'Pause' : 'Abspielen',
() => (currentId() === e.id ? N.podcastToggle() : play(e)), 'nui-pod-playbtn');
b.dataset.play = e.id;
return b;
}
if (dlPrimary(e, list)) return dlButton(e);
const queued = inQueue(e.id) || (list === 'inbox' && e.inQueue);
return queued ? button('✓', 'In der Warteschlange', () => toast('Schon in der Warteschlange'), 'nui-pod-queued')
: button('+', 'Einreihen', () => { enqueue(e); e.raw.in_queue = true; render(); });
}
// ▶ / ❚❚ in the rows follow the player without drawing the lists again.
function playButtons() {
document.querySelectorAll('#nui-pod [data-play]').forEach(b => {
const now = currentId() === Number(b.dataset.play) && player.playing;
b.textContent = now ? '❚❚' : '▶';
b.title = now ? 'Pause' : 'Abspielen';
b.setAttribute('aria-label', b.title);
});
}
// ---- long press and swipes on a row ---------------------------------------------------
// Long press opens the row's menu. Swiping (a finger, not a mouse) left or right does what
// the list's swipe settings say, with 4 s to undo. Vertical moves stay the page's scrolling;
// the queue's drag handle and the sideways chip rows aren't rows, so nothing competes.
function rowGestures(row, onLong, onSwipe, list) {
let start = null;
let holding = false; // this row counts in swipingSince
let timer = 0;
let mode = '';
let dx = 0;
let vx = 0;
let last = null;
let bg = null;
let swallow = false;
const stopTimer = () => { if (timer) clearTimeout(timer); timer = 0; };
const actionFor = d => swipeAction(list, d);
const limit = () => Math.min(140, Math.max(72, row.offsetWidth * 0.3));
const armed = () => {
const act = actionFor(dx < 0 ? 'left' : 'right');
if (act === 'none') return false;
const fling = Math.abs(dx) > 40 && Math.abs(vx) > 0.6 && Math.sign(vx) === Math.sign(dx);
return Math.abs(dx) >= limit() || fling;
};
const paint = () => {
const d = dx < 0 ? 'left' : 'right';
const act = actionFor(d);
const shownDx = act === 'none' ? dx * 0.25 : dx;
row.style.transform = `translateX(${shownDx}px)`;
const a = SWIPE_ACTIONS.find(x => x.id === act) || SWIPE_ACTIONS[0];
bg.dataset.dir = d;
bg.classList.toggle('armed', armed());
const label = bg.querySelector(d === 'right' ? '.nui-pod-swipe-l' : '.nui-pod-swipe-r');
const other = bg.querySelector(d === 'right' ? '.nui-pod-swipe-r' : '.nui-pod-swipe-l');
if (label.dataset.act !== act) {
label.dataset.act = act;
label.textContent = '';
if (act !== 'none') {
const t = swipeText(a, row);
const text = el('span', 'nui-pod-swipe-text');
text.append(el('b', null, t.title));
if (t.hint) text.append(el('small', null, t.hint));
const icon = a.svg ? svgIcon(a.svg, 22) : el('span', null, a.icon);
icon.classList.add('nui-pod-swipe-icon');
label.append(icon, text);
}
}
other.textContent = '';
delete other.dataset.act;
};
const finish = done => {
if (holding) {
holding = false;
swipingSince = 0;
if (renderAfterSwipe) { renderAfterSwipe = false; setTimeout(render, done ? 200 : 220); }
}
row.classList.remove('nui-pod-swiping');
const gone = bg;
bg = null;
if (!done) {
row.style.transition = 'transform 0.18s ease';
row.style.transform = '';
setTimeout(() => { row.style.transition = ''; if (gone) gone.remove(); }, 200);
} else if (gone) {
setTimeout(() => gone.remove(), 220);
}
};
row.addEventListener('pointerdown', ev => {
if (ev.pointerType === 'mouse' && ev.button !== 0) return;
// On the queue's drag handle: a vertical move sorts (dragSort), a sideways one is the swipe.
const onHandle = !!ev.target.closest('.nui-pod-handle');
if (onHandle && !onSwipe) return;
start = {x: ev.clientX, y: ev.clientY, id: ev.pointerId, touch: ev.pointerType === 'touch'};
mode = '';
dx = vx = 0;
last = {x: ev.clientX, t: ev.timeStamp};
swallow = false;
stopTimer();
if (!onHandle && !ev.target.closest('button')) {
timer = setTimeout(() => {
timer = 0;
if (mode || !start) return;
mode = 'long';
swallow = true;
onLong();
}, 550);
}
});
row.addEventListener('pointermove', ev => {
if (!start || ev.pointerId !== start.id) return;
const mx = ev.clientX - start.x;
const my = ev.clientY - start.y;
if (!mode) {
if (Math.abs(mx) > 8 || Math.abs(my) > 8) { stopTimer(); start.moved = true; }
if (onSwipe && start.touch && Math.abs(mx) > 12 && Math.abs(mx) > Math.abs(my) * 1.5) {
mode = 'swipe';
// The swipe before stays undoable (its bar too) until this one is done (offerUndo).
holding = true;
swipingSince = Date.now();
try { row.setPointerCapture(ev.pointerId); } catch (e) {}
bg = el('div', 'nui-pod-swipebg');
bg.append(el('span', 'nui-pod-swipe-l'), el('span', 'nui-pod-swipe-r'));
bg.style.top = row.offsetTop + 'px';
bg.style.height = row.offsetHeight + 'px';
row.parentElement.insertBefore(bg, row);
row.classList.add('nui-pod-swiping');
} else if (Math.abs(my) > 12) {
start = null;
return;
}
}
if (mode !== 'swipe') return;
ev.preventDefault();
const dt = ev.timeStamp - last.t;
if (dt > 0) vx = 0.7 * vx + 0.3 * ((ev.clientX - last.x) / dt);
last = {x: ev.clientX, t: ev.timeStamp};
dx = mx;
paint();
});
const end = ev => {
if (!start || (ev && ev.pointerId !== start.id)) return;
stopTimer();
const moved = start.moved;
start = null;
// A drag (a mouse's too) isn't a tap: no playing by accident.
if (mode !== 'swipe') { if (moved && mode !== 'long') swallow = true; return; }
swallow = true;
const d = dx < 0 ? 'left' : 'right';
const act = actionFor(d);
if (ev.type === 'pointerup' && armed() && act !== 'none') {
row.style.transition = 'transform 0.16s ease-out';
row.style.transform = `translateX(${d === 'left' ? -row.offsetWidth : row.offsetWidth}px)`;
finish(true);
setTimeout(() => {
const kept = onSwipe(act, d);
// The row stays in the list: back in its place.
if (kept !== false && row.isConnected) {
row.style.transition = 'transform 0.18s ease';
row.style.transform = '';
setTimeout(() => { row.style.transition = ''; }, 200);
}
}, 160);
} else {
finish(false);
}
};
row.addEventListener('pointerup', end);
row.addEventListener('pointercancel', end);
row.addEventListener('pointerleave', ev => { if (mode !== 'swipe') { stopTimer(); } else if (ev.pointerType !== 'touch') end(ev); });
// The tap that ends a long press or a swipe isn't a tap on the row.
row.addEventListener('click', ev => {
if (!swallow) return;
swallow = false;
ev.stopPropagation();
ev.preventDefault();
}, true);
row.addEventListener('contextmenu', ev => ev.preventDefault());
}
// What swiping does, per list (this device). The defaults are an AntennaPod setup, except the
// queue's left: „Folge löschen“ where the host can (it was „Datei löschen“).
const SWIPE_LISTS = {
queue: {label: 'Warteschlange', left: N.podcastDiscard ? 'discard' : 'delete', right: 'dequeue'},
inbox: {label: 'Neu', left: 'dismiss', right: 'queue'},
episodes: {label: 'Folgen', left: 'download', right: 'favorite'},
feed: {label: 'Podcast-Seite', hint: 'die Folgen eines Abos', left: 'delete', right: 'download'},
downloads: {label: 'Downloads', hint: 'Folgen mit dem Filter „Geladen“', left: 'delete', right: 'delete'},
};
const SWIPE_ACTIONS = [
{id: 'none', label: 'Nichts', icon: ''},
{id: 'queue', label: 'Einreihen', icon: '+'},
// Where there are files.
{id: 'dequeue', label: DL ? 'Aus Warteschlange · Datei bleibt' : 'Aus der Warteschlange',
icon: '✕', svg: 'listx'},
{id: 'download', label: 'Herunterladen', icon: '↓', dl: true},
{id: 'delete', label: 'Datei löschen', hint: 'nur die Datei auf diesem Gerät', icon: '✕', svg: 'trash', dl: true},
{id: 'played', label: 'Gehört / ungehört', hint: 'als gehört markieren', icon: '✓'},
{id: 'favorite', label: 'Favorit an / aus', icon: '★'},
{id: 'dismiss', label: 'Aus „Neu“ entfernen', icon: '⊘', svg: 'listx'},
{id: 'discard', label: 'Folge löschen', hint: 'aus Warteschlange und Neu, Datei auf allen Geräten weg – nicht gehört',
icon: '⊠', svg: 'trash', host: 'podcastDiscard'},
];
// An action the host can do (the browser has no downloads; an older host no „Folge löschen“).
const canSwipe = a => (!a.dl || DL) && (!a.host || !!N[a.host]);
function swipePrefs() {
let all = {};
try { all = JSON.parse(localStorage.getItem('nui_pod_swipes') || '{}') || {}; } catch (e) {}
return all;
}
// The queue's left swipe was „Download löschen“ by default: kept as such (stored when chosen),
// it becomes „Folge löschen“ once; any other choice stays.
(function newQueueDefault() {
if (!N.podcastDiscard) return;
const all = swipePrefs();
if (all.v >= 2) return;
if (all.queue && all.queue.left === 'delete') all.queue.left = 'discard';
all.v = 2;
try { localStorage.setItem('nui_pod_swipes', JSON.stringify(all)); } catch (e) {}
})();
function swipeAction(list, dir) {
const l = SWIPE_LISTS[list];
if (!l) return 'none';
const own = (swipePrefs()[list] || {})[dir];
const id = own || l[dir];
const a = SWIPE_ACTIONS.find(x => x.id === id);
return a && canSwipe(a) ? id : 'none';
}
// What the swipe says behind the moving row: the action by its full name (bold) and what
// it means for this episode (small) — never a bare „Löschen“ or „Entfernen“.
function swipeText(a, row) {
const id = row && Number(row.dataset.id);
switch (a.id) {
case 'discard': return {title: 'Folge löschen', hint: 'nicht gehört'};
case 'delete': return {title: 'Datei löschen', hint: id && deleteDequeues(id) ? 'auch aus Warteschlange' : 'nur auf diesem Gerät'};
case 'dequeue': return {title: 'Aus Warteschlange', hint: DL ? 'Datei bleibt' : null};
case 'dismiss': return {title: 'Aus Neu entfernen', hint: 'nicht gehört'};
case 'queue': return {title: 'Einreihen'};
case 'download': return {title: 'Laden'};
case 'favorite': return {title: 'Favorit'};
case 'played': return {title: row && row.classList.contains('nui-pod-played') ? 'Ungehört' : 'Gehört'};
default: return {title: a.label};
}
}
// „Datei löschen“ takes the episode out of the queue too (the account's delete_dequeue, on by default).
function deleteDequeues(id) {
return 'delete_dequeue' in settings() && settings().delete_dequeue !== false && inQueue(id);
}
function swipeSettings() {
openSheet('Wischgesten', Object.keys(SWIPE_LISTS).filter(id => id !== 'downloads' || DL).map(id => {
const l = SWIPE_LISTS[id];
const name = d => (SWIPE_ACTIONS.find(a => a.id === swipeAction(id, d)) || SWIPE_ACTIONS[0]).label;
return {icon: '⇆', label: l.label, hint: `← ${name('left')} · → ${name('right')}`, action: () => swipeListSheet(id)};
}).concat([{custom: el('div', 'nui-pod-meta nui-pod-sheetnote',
'Nur mit dem Finger. In der gesperrten Warteschlange ist Wischen aus. Nach jedem Wischen 4 Sekunden „Rückgängig“.')}]));
}
function swipeListSheet(list) {
const l = SWIPE_LISTS[list];
const pick = dir => openSheet(`${l.label}: nach ${dir === 'left' ? 'links' : 'rechts'} wischen`,
SWIPE_ACTIONS.filter(canSwipe).map(a => ({
icon: swipeAction(list, dir) === a.id ? '●' : '○', label: a.label, active: swipeAction(list, dir) === a.id,
hint: [a.hint, a.id === l[dir] ? 'Standard' : null].filter(Boolean).join(' · ') || null,
action: () => {
const all = swipePrefs();
all[list] = Object.assign({}, all[list], {[dir]: a.id});
try { localStorage.setItem('nui_pod_swipes', JSON.stringify(all)); } catch (e) {}
swipeListSheet(list);
},
})));
const name = d => (SWIPE_ACTIONS.find(a => a.id === swipeAction(list, d)) || SWIPE_ACTIONS[0]).label;
openSheet(`Wischen in „${l.label}“` + (l.hint ? ` (${l.hint})` : ''), [
{icon: '←', label: 'Nach links', hint: name('left'), action: () => pick('left')},
{icon: '→', label: 'Nach rechts', hint: name('right'), action: () => pick('right')},
{icon: '‹', label: 'Alle Listen', action: swipeSettings},
]);
}
// A swipe's change waits 4 s for „Rückgängig“ where it can't simply be undone
// (queued or dismissed: out of „Neu“ for good; out of the queue; played; a download
// deleted): a row that leaves its list is gone at once, the change is sent then.
// Downloading and starring happen at once; undoing them is their opposite.
let pendingSwipe = null; // {id, hide, run, undo, timer}
const recentlyGone = new Map();
function swipeGone(id) {
if (pendingSwipe && pendingSwipe.hide && pendingSwipe.id === id) return true;
const t = recentlyGone.get(id);
return !!t && Date.now() - t < 4000;
}
function commitSwipe() {
const p = pendingSwipe;
if (!p) return;
pendingSwipe = null;
clearTimeout(p.timer);
undoBar(null);
if (p.hide) recentlyGone.set(p.id, Date.now());
if (p.run) p.run();
}
function undoSwipe() {
const p = pendingSwipe;
if (!p) return;
pendingSwipe = null;
clearTimeout(p.timer);
undoBar(null);
if (p.undo) p.undo();
render();
}
// The bar with „Rückgängig“ (the toast can't be tapped).
function undoBar(text, svg) {
let bar = $id('nui-pod-undo');
if (!text) { if (bar) bar.classList.remove('nui-show'); return; }
if (!bar) {
bar = el('div');
bar.id = 'nui-pod-undo';
bar.append(el('span', 'nui-pod-undo-icon'), el('span', 'nui-pod-undo-text'));
const b = el('button', 'nui-pod-undo-btn', 'Rückgängig');
b.type = 'button';
b.addEventListener('click', ev => { ev.stopPropagation(); undoSwipe(); });
bar.append(b);
document.body.append(bar);
}
const t = $id('nui-radio-toast');
if (t) t.classList.remove('nui-show');
bar.querySelector('.nui-pod-undo-text').textContent = text;
const icon = bar.querySelector('.nui-pod-undo-icon');
icon.textContent = '';
if (svg) icon.append(svgIcon(svg, 18));
icon.hidden = !svg;
bar.classList.add('nui-show');
}
function offerUndo(e, text, change) {
commitSwipe();
pendingSwipe = Object.assign({id: e.id}, change);
pendingSwipe.timer = setTimeout(commitSwipe, 4000);
undoBar(text, change.svg);
if (change.hide) render();
}
// A swipe on an episode of `list` asks for `act`. Returns false when the row leaves the list.
function swipeDone(e, list, act, filters) {
const o = e.raw || {};
const has = d => DL && downloads[e.id] && downloads[e.id].state === d;
if (act === 'queue') {
if (inQueue(e.id)) { toast('Schon in der Warteschlange'); return true; }
// Queued episodes leave „Neu“ for good (AntennaPod's rule): sent only after the 4 s.
const hide = list === 'inbox';
offerUndo(e, 'Eingereiht', {hide, run: () => {
N.podcastEnqueue(JSON.stringify([o]), '', currentId());
o.in_queue = true;
if (!hide) render();
}});
return !hide;
}
if (act === 'dequeue') {
if (!inQueue(e.id)) { toast('Nicht in der Warteschlange'); return true; }
const hide = list === 'queue';
offerUndo(e, dlDone(e.id) ? 'Aus Warteschlange · Datei bleibt' : 'Aus Warteschlange',
{hide, svg: 'listx', run: () => { N.podcastDequeue(e.id); o.in_queue = false; }});
return !hide;
}
if (act === 'download') {
if (!(o.audio_url || o.episode__audio_url)) { toast('Diese Folge lässt sich nicht laden'); return true; }
if (downloads[e.id] && downloads[e.id].state !== 'failed') {
toast(has('done') ? 'Schon heruntergeladen' : 'Wird schon geladen');
return true;
}
dlStart(e);
offerUndo(e, 'Wird heruntergeladen', {undo: () => N.podcastDeleteDownload(e.id)});
return true;
}
if (act === 'delete') {
if (!downloads[e.id]) { toast('Nicht heruntergeladen'); return true; }
const running = !has('done') && !has('failed');
// "Löschen nimmt aus der Warteschlange" (account): then it leaves the queue too.
const out = !running && deleteDequeues(e.id);
const hide = list === 'downloads' || filters.includes('downloaded') || (list === 'queue' && out);
offerUndo(e, running ? 'Download abgebrochen' : out ? 'Datei gelöscht · auch aus Warteschlange' : 'Datei gelöscht',
{hide, svg: running ? null : 'trash', run: () => dlDelete(e, running, true)});
return !hide;
}
if (act === 'played') {
const played = !(o.played || e.played);
const hide = played
? list === 'inbox' || (list === 'queue' && devicePrefs().remove_played !== false) ||
filters.includes('unplayed') || filters.includes('in_progress')
: filters.includes('played');
offerUndo(e, played ? 'Als gehört markiert' : 'Als ungehört markiert', {hide, run: () => {
N.podcastMarkPlayed(e.id, played);
o.played = played;
if (played) o.position_seconds = 0;
if (!hide) render();
}});
return !hide;
}
if (act === 'favorite') {
toggleFavorite(e, star => {
o.favorite = star;
offerUndo(e, star ? '★ Favorit' : 'Kein Favorit mehr', {undo: () => toggleFavorite(e, s => { o.favorite = s; }, true)});
}, true);
return true;
}
if (act === 'dismiss') {
// „Neu“ is every unplayed episode not taken out (the page knows only the first ones).
if (o.played || e.played) { toast('Gehört – nicht in „Neu“'); return true; }
const hide = list === 'inbox';
offerUndo(e, 'Aus Neu entfernt', {hide, svg: 'listx', run: () => N.podcastDismiss(JSON.stringify([e.id]))});
return !hide;
}
if (act === 'discard' && N.podcastDiscard) return discardOne(e, list, filters);
return true;
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function renderQueue() {
const items = queue().map(ep);
if (!items.length) {
empty('Die Warteschlange ist leer. Unter „Neu“ oder bei einem Abo tippst du auf „+“, um Folgen einzureihen.');
return;
}
const total = items.reduce((n, e) => n + shown(Math.max(0, (e.duration || 0) - (e.pos || 0)), e.feedId), 0);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (selected) {
root.append(selectionBar(items, [
{label: 'Nach oben', run: ids => N.podcastReorder(JSON.stringify(ids.concat(queue().map(q => q.episode__id).filter(id => !ids.includes(id)))))},
{label: 'Nach unten', run: ids => N.podcastReorder(JSON.stringify(queue().map(q => q.episode__id).filter(id => !ids.includes(id)).concat(ids)))},
{label: 'Gehört', run: ids => { N.podcastMarkPlayedAll(JSON.stringify(ids), true); stopSelecting(); }},
{label: 'Aus Warteschlange', svg: 'listx', run: ids => { N.podcastDequeueAll(JSON.stringify(ids)); stopSelecting(); }},
discardAction(),
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
]));
} else {
const head = el('div', 'nui-pod-head');
const kept = keepSorted() ? ' · ' + ((SORTS.find(x => x.id === keepSorted()) || {}).label || '').toLowerCase() : '';
head.append(el('div', 'nui-pod-summary', `${items.length} ${items.length === 1 ? 'Folge' : 'Folgen'} · ${minutes(total)}${kept}`),
listTools([
button('⇅', 'Sortieren oder mischen', sortMenu),
button('☐', 'Mehrere auswählen', () => startSelecting()),
button(locked ? '🔒' : '🔓', locked ? 'Warteschlange entsperren' : 'Warteschlange sperren', toggleLock,
locked ? 'nui-pod-lock on' : 'nui-pod-lock'),
]));
root.append(head);
}
const list = el('div', 'nui-pod-list nui-pod-queue');
for (const e of items) {
const row = episodeRow(e, {showFeed: true, list: 'queue'});
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (!selected) {
if (!locked && !keepSorted()) {
const handle = el('div', 'nui-pod-handle', '≡');
handle.title = 'Ziehen zum Umsortieren';
handle.addEventListener('click', ev => ev.stopPropagation());
dragSort(handle, row, list);
row.append(handle);
}
}
list.append(row);
}
root.append(list);
}
function renderInbox() {
const items = inbox().map(ep);
if (!items.length) {
empty(feeds().length
? 'Keine neuen Folgen. Neue Folgen deiner Abos erscheinen hier, sobald der Server sie gefunden hat.'
: 'Noch keine Abos. Unter „Suche“ findest du Podcasts.');
return;
}
if (selected) {
const chosen = ids => items.filter(e => ids.includes(e.id));
const add = (ids, where) => {
N.podcastEnqueue(JSON.stringify(chosen(ids).map(e => e.raw)), where, currentId());
toast(`${ids.length} ${ids.length === 1 ? 'Folge' : 'Folgen'} eingereiht`);
stopSelecting();
};
root.append(selectionBar(items, [
{label: 'Einreihen', run: ids => openSheet(`${ids.length} ${ids.length === 1 ? 'Folge' : 'Folgen'} einreihen`, [
{icon: '+', label: 'Wie eingestellt', hint: (POSITIONS.find(p => p.value === settings().enqueue_position) || POSITIONS[0]).label,
action: () => add(ids, '')},
{icon: '⤴', label: 'Als Nächstes', action: () => add(ids, 'after_current')},
{icon: '⇈', label: 'An den Anfang', action: () => add(ids, 'front')},
{icon: '⇊', label: 'Ans Ende', action: () => add(ids, 'back')},
])},
{label: 'Gehört', run: ids => { N.podcastMarkPlayedAll(JSON.stringify(ids), true); stopSelecting(); }},
{label: 'Ausblenden', svg: 'listx', run: ids => { N.podcastDismiss(JSON.stringify(ids)); stopSelecting(); }},
DL && {label: 'Laden', run: ids => downloadMany(chosen(ids))},
discardAction(),
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
]));
} else {
const head = el('div', 'nui-pod-head');
const total = inboxTotal();
head.append(el('div', 'nui-pod-summary', `${total} neue ${total === 1 ? 'Folge' : 'Folgen'}` +
(total > items.length ? ` · die ${items.length} neuesten` : '')),
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
listTools([button('☐', 'Mehrere auswählen', () => startSelecting())]));
root.append(head);
}
const list = el('div', 'nui-pod-list');
for (const e of items) list.append(episodeRow(e, {showFeed: true, showDate: true, list: 'inbox'}));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
root.append(list);
}
// The subscriptions: sorted, each with a number (new, unplayed or downloaded
// episodes), both remembered on this device.
const byDate = key => (a, b) => String(b[key] || '').localeCompare(String(a[key] || ''));
const byTitle = (a, b) => plain(a.title).localeCompare(plain(b.title), 'de', {sensitivity: 'base'});
const FEED_SORTS = [
{id: 'latest', label: 'Neueste Folge zuerst', cmp: byDate('latest_episode_at')},
{id: 'title', label: 'Nach Titel', cmp: byTitle},
{id: 'count', label: 'Nach Zähler', hint: 'die meisten zuerst',
cmp: (a, b) => (feedCount(b) || 0) - (feedCount(a) || 0) || byTitle(a, b)},
{id: 'played', label: 'Zuletzt gehört', cmp: (a, b) => byDate('last_played_at')(a, b) || byDate('latest_episode_at')(a, b)},
{id: 'added', label: 'Zuletzt abonniert', cmp: byDate('added_at')},
];
const COUNTERS = [
{id: 'new', label: 'Neue Folgen', hint: 'wie in „Neu“', value: f => f.new_count},
{id: 'unplayed', label: 'Ungehörte Folgen', value: f => f.unplayed_count},
{id: 'downloaded', label: 'Geladene Folgen', app: true,
value: f => Object.values(downloads).filter(d => d.state === 'done' && d.feedId === f.id).length},
{id: 'none', label: 'Keine Zahl', value: () => null},
];
let feedSort = 'latest';
let feedCounter = 'new';
try {
feedSort = localStorage.getItem('nui_pod_feedsort') || feedSort;
feedCounter = localStorage.getItem('nui_pod_counter') || feedCounter;
} catch (e) {}
function counter() {
return COUNTERS.find(c => c.id === feedCounter && (!c.app || DL)) || COUNTERS[0];
}
function feedCount(f) {
const c = counter();
return c.id === 'none' ? COUNTERS[1].value(f) : c.value(f);
}
function feedSortMenu() {
const pick = (key, value) => {
if (key === 'sort') feedSort = value; else feedCounter = value;
try { localStorage.setItem(key === 'sort' ? 'nui_pod_feedsort' : 'nui_pod_counter', value); } catch (e) {}
render();
};
openSheet('Abos sortieren', FEED_SORTS.map(s => ({
icon: s.id === feedSort ? '●' : '○', label: s.label, hint: s.hint || null, active: s.id === feedSort,
action: () => pick('sort', s.id),
})).concat([{icon: '#', label: 'Zahl am Podcast', hint: counter().label, action: () => openSheet('Zahl am Podcast',
COUNTERS.filter(c => !c.app || DL).map(c => ({
icon: c.id === counter().id ? '●' : '○', label: c.label, hint: c.hint || null, active: c.id === counter().id,
action: () => pick('counter', c.id),
})))}]));
}
function feedRow(f) {
const row = el('div', 'nui-pod-row');
row.append(art(f.artwork_url, null, null, f.title));
const main = el('div', 'nui-pod-main');
main.append(el('div', 'nui-pod-title', plain(f.title)));
const meta = [];
if (f.author) meta.push(plain(f.author));
if (feedSort === 'played' && f.last_played_at) meta.push('gehört ' + day(f.last_played_at));
else if (f.latest_episode_at) meta.push('neueste ' + day(f.latest_episode_at));
main.append(el('div', 'nui-pod-meta', meta.join(' · ')));
if (f.last_error) main.append(el('div', 'nui-pod-feederr', '⚠ lädt nicht: ' + f.last_error));
row.append(main);
if (f.new_episodes === 'ignore') row.append(el('span', 'nui-pod-badge', 'still'));
else if (f.auto_queue || f.new_episodes === 'queue') row.append(el('span', 'nui-pod-badge', 'auto'));
const n = counter().value(f);
if (n) {
const c = el('span', 'nui-pod-count nui-pod-feedcount', String(n));
c.title = `${n} ${counter().label.replace(' Folgen', '').toLowerCase()}`;
row.append(c);
}
row.addEventListener('click', () => openFeed(f));
return row;
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function renderFeeds() {
const sort = FEED_SORTS.find(s => s.id === feedSort) || FEED_SORTS[0];
const items = podcastFeeds().slice().sort((a, b) => sort.cmp(a, b) || byTitle(a, b));
const books = feeds().length - items.length;
const booksHint = () => el('div', 'nui-pod-hint',
`${books === 1 ? 'Ein Hörbuch' : books + ' Hörbücher'}: in Bücher → Hörbücher`);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (!items.length) {
empty('Noch keine Abos. Unter „Suche“ findest du Podcasts.');
if (books) root.append(booksHint());
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
return;
}
const head = el('div', 'nui-pod-head');
head.append(el('div', 'nui-pod-summary', `${items.length} ${items.length === 1 ? 'Abo' : 'Abos'} · ${sort.label.toLowerCase()}`),
listTools([
hasSources() && button('⚙', 'Verwalten: Abos & Quellen', toSources),
button('⇅', 'Sortieren und Zahl wählen', feedSortMenu),
].filter(Boolean)));
root.append(head);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const list = el('div', 'nui-pod-list');
for (const f of items) list.append(feedRow(f));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
root.append(list);
if (books) root.append(booksHint());
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
// For Abos & Quellen (sources.js): a podcast's settings sheet, its episode list.
window.__dioraPodcastFeedSettings = function (feedId) {
const f = feeds().find(x => x.id === Number(feedId));
if (!f) return false;
feedSettingsSheet(f);
return true;
};
window.__dioraPodcastOpenFeed = function (feedId) {
const f = feeds().find(x => x.id === Number(feedId));
if (!f) return false;
if (window.DioraNav) DioraNav.go('podcasts');
else if (typeof showTab === 'function') showTab('podcasts');
openFeed(f);
return true;
};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
async function openFeed(f) {
feedView = {feed: f, episodes: null};
selected = null;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
render();
scrollTop(0);
try {
const data = await getJson(`/podcasts/feeds/${f.id}/episodes/`);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (feedView && feedView.feed.id === f.id) {
feedView = {feed: Object.assign({}, f, data.feed), episodes: data.episodes};
render();
}
} catch (e) {
if (feedView && feedView.feed.id === f.id) {
feedView.episodes = [];
feedView.error = true;
render();
}
}
}
// One podcast: cover, title, author, ⓘ and ⚙, one quiet line with what is set
// differently from the defaults, then its episodes. All its settings are in the ⚙ sheet.
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function renderFeed() {
const f = feedView.feed;
const live = feeds().find(x => x.id === f.id) || f;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const head = el('div', 'nui-pod-feedhead');
head.append(button('‹', 'Zurück zu den Abos', () => { feedView = null; selected = null; render(); }, 'nui-pod-back-btn'),
art(f.artwork_url, 'big', null, f.title));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const info = el('div', 'nui-pod-main');
info.append(el('div', 'nui-pod-title', plain(f.title)));
if (f.author) info.append(el('div', 'nui-pod-meta', plain(f.author)));
head.append(info, button('ⓘ', 'Über diesen Podcast', () => openFeedInfo(f), 'nui-pod-infobtn'));
if (feedHasSettings()) head.append(button('⚙︎', 'Einstellungen für diesen Podcast', () => feedSettingsSheet(f), 'nui-pod-infobtn nui-pod-gear'));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
root.append(head);
const line = feedHasSettings() ? feedSummary(f, live) : '';
if (line) {
const s = el('button', 'nui-pod-feedsum', line);
s.type = 'button';
s.title = 'Einstellungen für diesen Podcast';
s.addEventListener('click', () => feedSettingsSheet(f));
root.append(s);
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (live.last_error) {
root.append(el('div', 'nui-pod-feederr nui-pod-feederr-box',
`⚠ Der Feed ließ sich zuletzt nicht laden (${live.last_error}${live.last_error_at ? ', ' + day(live.last_error_at) : ''}). ` +
'Der Server versucht es weiter, bei wiederholten Fehlern seltener.'));
}
renderFeedEpisodes(f);
}
// The host keeps per-podcast settings (the browser and the app do; an old host doesn't).
function feedHasSettings() {
return !!N.podcastFeedSettings || !!N.podcastAutoQueue || !!N.podcastFeedPrefs;
}
function volumeLabel(v) {
if (!v || v === 1) return '';
return {0.2: 'viel leiser', 0.5: 'leiser', 1.5: '+5 dB', 2: '+10 dB', 2.5: '+15 dB'}[v] || '';
}
// This podcast's own playback (speed, intro, outro, volume): the server's where it keeps them, else this device's.
function feedOwn(f) {
const live = feeds().find(x => x.id === f.id) || f;
return 'skip_intro' in live
? {speed: live.playback_speed || 0, intro: live.skip_intro || 0, outro: live.skip_outro || 0, volume: live.volume || 1}
: (devicePrefs().feeds || {})[String(f.id)] || {};
}
const NEW_WHERE = {inbox: 'in „Neu“', queue: 'in die Warteschlange', ignore: 'nirgends'};
function filterWords(live) {
return [live.include_words && `nur „${live.include_words}“`, live.exclude_words && `ohne „${live.exclude_words}“`,
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
live.min_duration && `ab ${live.min_duration} min`].filter(Boolean).join(', ');
}
// "1,3× · Intro 20 s · ohne „Trailer“ · neue Folgen → Warteschlange": only what differs from the defaults.
function feedSummary(f, live) {
const own = N.podcastFeedPrefs ? feedOwn(f) : {};
const parts = [];
if (own.speed) parts.push(speedLabel(own.speed));
if (own.intro) parts.push(`Intro ${own.intro} s`);
if (own.outro) parts.push(`Outro ${own.outro} s`);
if (volumeLabel(own.volume)) parts.push('Lautstärke ' + volumeLabel(own.volume));
if ('skip_silence' in live && live.skip_silence != null) parts.push(live.skip_silence ? 'Stille überspringen' : 'Stille nie überspringen');
const words = filterWords(live);
if (words) parts.push(words);
if (live.new_episodes === 'queue' || (!('new_episodes' in live) && live.auto_queue)) parts.push('neue Folgen → Warteschlange');
else if (live.new_episodes === 'ignore') parts.push('neue Folgen: nirgends');
if (live.auto_download && N.podcastDownloads) parts.push('Auto-Download');
if (live.notify) parts.push('Benachrichtigen');
return parts.join(' · ');
}
// All of a podcast's settings in one sheet; after a change it opens again with the new values.
function feedSettingsSheet(f) {
const live = feeds().find(x => x.id === f.id) || f;
const title = plain(live.title || f.title);
const again = () => setTimeout(() => { if (feedView && feedView.feed.id === f.id && !preview) feedSettingsSheet(f); }, 180);
const save = changes => { N.podcastFeedSettings(live.id, JSON.stringify(changes)); setTimeout(render, 50); again(); };
const choose = (head, choices) => () => openSheet(head, choices);
const items = [];
const pos = (POSITIONS.find(p => p.value === settings().auto_queue_position) || POSITIONS[1]).label.toLowerCase();
if ('new_episodes' in live && N.podcastFeedSettings) {
const ne = live.new_episodes || 'inbox';
items.push({icon: '◇', label: 'Neue Folgen', hint: NEW_WHERE[ne] || NEW_WHERE.inbox, active: ne !== 'inbox',
action: choose(`Neue Folgen von „${title}“ …`, [
{icon: ne === 'inbox' ? '●' : '○', label: 'In „Neu“', hint: 'du entscheidest selbst', active: ne === 'inbox', action: () => save({new_episodes: 'inbox'})},
{icon: ne === 'queue' ? '●' : '○', label: 'Gleich in die Warteschlange', hint: pos, active: ne === 'queue', action: () => save({new_episodes: 'queue'})},
{icon: ne === 'ignore' ? '●' : '○', label: 'Nirgends anzeigen', hint: 'nur in der Folgenliste des Podcasts', active: ne === 'ignore',
action: () => save({new_episodes: 'ignore'})},
])});
// AntennaPod's per-podcast auto-download: new episodes land on the phone by themselves.
if ('auto_download' in live && N.podcastDownloads) {
items.push({icon: '↓', label: 'Automatisch herunterladen', hint: live.auto_download ? 'an: neue Folgen im WLAN, im Download-Limit' : 'aus',
active: !!live.auto_download, action: () => save({auto_download: !live.auto_download})});
}
// AntennaPod's episode notifications: the phone tells when a new episode is in „Neu“.
if ('notify' in live) {
items.push({icon: '◔', label: 'Benachrichtigen', hint: live.notify ? 'an: eine Mitteilung bei jeder neuen Folge' : 'aus',
active: !!live.notify, action: () => save({notify: !live.notify})});
}
const words = filterWords(live);
const ask = async (key, head, now) => {
const v = typeof customPrompt === 'function' ? await customPrompt(head, now || '') : null;
if (v != null) save({[key]: v.trim()}); else again();
};
items.push({icon: '⊻', label: 'Filter für neue Folgen', hint: words || 'keiner', active: !!words,
action: choose('Welche neuen Folgen zählen?', [
{icon: '+', label: 'Nur mit diesen Wörtern im Titel', hint: live.include_words || 'alle',
action: () => ask('include_words', 'Nur Folgen, deren Titel eines dieser Wörter enthält (mit Komma getrennt):', live.include_words)},
{icon: '−', label: 'Ohne diese Wörter im Titel', hint: live.exclude_words || 'keine',
action: () => ask('exclude_words', 'Folgen mit einem dieser Wörter im Titel weglassen (mit Komma getrennt):', live.exclude_words)},
{icon: '⏱︎', label: 'Mindestlänge', hint: live.min_duration ? `${live.min_duration} Minuten` : 'keine',
action: choose('Kürzere Folgen weglassen', [0, 5, 10, 15, 20, 30, 45, 60].map(m => ({
icon: (live.min_duration || 0) === m ? '●' : '○', label: m ? `unter ${m} Minuten weglassen` : 'Keine Mindestlänge',
active: (live.min_duration || 0) === m, action: () => save({min_duration: m}),
})))},
])});
} else if (N.podcastAutoQueue) {
items.push({icon: live.auto_queue ? '☑' : '☐', label: 'Neue Folgen automatisch einreihen', hint: pos, active: !!live.auto_queue,
action: () => { N.podcastAutoQueue(f.id, !live.auto_queue); again(); }});
}
if (N.podcastFeedPrefs) {
const own = feedOwn(f);
const set = (key, v) => { N.podcastFeedPrefs(f.id, JSON.stringify({[key]: v})); setTimeout(render, 50); again(); };
const seconds = (key, head) => choose(head, [0, 10, 15, 20, 30, 45, 60, 90, 120].map(v => ({
icon: (own[key] || 0) === v ? '●' : '○', label: v ? `${v} Sekunden` : 'Nichts überspringen', active: (own[key] || 0) === v,
action: () => set(key, v),
})));
items.push(
{icon: '»', label: 'Tempo', hint: own.speed ? speedLabel(own.speed) : 'wie alle Podcasts', active: !!own.speed,
action: choose(`Geschwindigkeit für „${title}“`, speedChoices(own.speed).map(v => ({
icon: Math.abs((own.speed || 0) - v) < 0.01 ? '●' : '○', label: speedLabel(v), active: Math.abs((own.speed || 0) - v) < 0.01,
action: () => set('speed', v)}))
.concat([{icon: '↺', label: 'Wie alle anderen Podcasts', action: () => set('speed', 0)}])
.concat(presetsEditItem()))},
{icon: '⇥', label: 'Intro überspringen', hint: own.intro ? `${own.intro} Sekunden` : 'aus', active: !!own.intro,
action: seconds('intro', 'Am Anfang jeder Folge überspringen')},
{icon: '⇤', label: 'Outro überspringen', hint: own.outro ? `${own.outro} Sekunden` : 'aus', active: !!own.outro,
action: seconds('outro', 'Am Ende jeder Folge überspringen')},
{icon: '◖', label: 'Lautstärke', hint: volumeLabel(own.volume) || 'normal', active: !!volumeLabel(own.volume),
action: choose(`Lautstärke für „${title}“`,
[[0.2, 'Viel leiser'], [0.5, 'Leiser'], [1, 'Normal'], [1.5, 'Lauter (+5 dB)'], [2, 'Viel lauter (+10 dB)'], [2.5, 'Sehr viel lauter (+15 dB)']]
.map(([v, label]) => ({icon: (own.volume || 1) === v ? '●' : '○', label, active: (own.volume || 1) === v, action: () => set('volume', v)})))},
);
}
// An audiobook: its episodes are chapters, listened to in the library (diora-web docs/audiobooks.md).
if ('kind' in live && N.podcastFeedSettings) {
const book = isBook(live);
items.push({icon: '▤', label: book ? 'Hörbuch (in Bücher)' : 'Als Hörbuch',
hint: book ? 'tippen: wieder als Podcast' : 'Folgen als Kapitel in der Bibliothek, nicht in „Neu“',
active: book, action: () => save({kind: book ? 'podcast' : 'audiobook'})});
}
// Skip silence for this podcast (server), where this player can skip silence at all.
if ('skip_silence' in live && 'skip_silence' in devicePrefs() && N.podcastFeedSettings) {
const mine = live.skip_silence;
const device = !!devicePrefs().skip_silence;
items.push({icon: '∿', label: 'Stille', hint: mine === true ? 'überspringen' : mine === false ? 'nie überspringen' : 'wie eingestellt',
active: mine != null, action: choose(`Stille in „${title}“ …`, [
{icon: mine == null ? '●' : '○', label: 'Wie eingestellt', hint: device ? 'überspringen (alle Podcasts)' : 'nicht überspringen (alle Podcasts)',
active: mine == null, action: () => save({skip_silence: null})},
{icon: mine === true ? '●' : '○', label: 'Überspringen', hint: 'Pausen werden ausgelassen', active: mine === true, action: () => save({skip_silence: true})},
{icon: mine === false ? '●' : '○', label: 'Nie überspringen', active: mine === false, action: () => save({skip_silence: false})},
])});
}
// The list's order (its filters are the chips above the episodes).
const prefs = feedListPrefs(f.id);
const sort = EP_SORTS.some(s => s.id === prefs.sort && !s.all) ? prefs.sort : 'new';
items.push({icon: '⇅', label: 'Folgen sortieren', hint: sortLabel(sort), active: sort !== 'new',
action: () => sortMenuFor(sort, false, id => {
saveFeedListPrefs(f.id, {filters: (prefs.filters || []).slice(), sort: id});
render();
again();
})});
items.push({custom: el('div', 'nui-pod-sheetsep')});
items.push({icon: '✕', label: 'Abo beenden', hint: 'fragt noch einmal nach', action: () => confirmUnsubscribe(f)});
openSheet(`Einstellungen für „${title}“`, items);
}
// Unsubscribing (server: POST /podcasts/feeds/<id>/remove/). Asked once more; then the
// server forgets the podcast with its episodes, and the app deletes their downloads
// (not those of favourites).
function confirmUnsubscribe(f) {
if (!navigator.onLine) { toast('Abo beenden braucht eine Verbindung.'); return; }
openSheet(`„${plain(f.title)}“ nicht mehr abonnieren?`, [
{custom: el('div', 'nui-pod-meta nui-pod-sheetnote', 'Die Folgen verschwinden aus Warteschlange und Listen, mit Fortschritt ' +
'und Favoriten, auch auf dem Server.' + (DL ? ' Die Downloads dieses Podcasts werden gelöscht.' : ''))},
{icon: '✕', label: 'Abo beenden', danger: true, action: () => unsubscribe(f)},
{icon: '‹', label: 'Abbrechen', action: () => feedSettingsSheet(f)},
]);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
function renderFeedEpisodes(f) {
if (feedView.episodes == null) { empty('Folgen werden geladen …'); return; }
if (feedView.error) { empty('Die Folgen lassen sich gerade nicht laden. Das braucht eine Verbindung.'); return; }
for (const o of feedView.episodes) {
// Feed episodes don't name their podcast; queue entries need it.
o.feed__id = f.id;
o.feed__title = f.title;
o.feed__artwork_url = f.artwork_url;
}
const prefs = feedListPrefs(f.id);
const filters = (prefs.filters || []).filter(id => FILTERS.some(x => x.id === id && !x.all && (!x.app || DL)));
const sort = EP_SORTS.some(s => s.id === prefs.sort && !s.all) ? prefs.sort : 'new';
const all = feedView.episodes;
const items = sortEpisodes(all.filter(o => matchesAll(o, filters)), sort).map(ep);
if (selected) {
root.append(episodeSelection(items));
} else {
const save = changes => { saveFeedListPrefs(f.id, Object.assign({filters, sort}, changes)); render(); };
root.append(filterRow(filters, next => save({filters: next}), false));
const head = el('div', 'nui-pod-head');
head.append(el('div', 'nui-pod-summary', (items.length === all.length ? nEpisodes(all.length)
: `${items.length} von ${nEpisodes(all.length)}`) + ' · ' + sortLabel(sort).toLowerCase()),
listTools([
button('⇅', 'Sortieren', () => sortMenuFor(sort, false, id => save({sort: id}))),
items.length > 0 && button('☐', 'Mehrere auswählen', () => startSelecting()),
]));
root.append(head);
}
if (!items.length) { empty(all.length ? noneText(filters) : 'Dieser Podcast hat noch keine Folgen.'); return; }
const list = el('div', 'nui-pod-list');
for (const e of items) {
const o = e.raw;
const row = episodeRow(e, {showDate: true, list: downloadsOnly(filters) ? 'downloads' : 'feed', filters});
if (o.favorite) row.querySelector('.nui-pod-title').prepend(el('span', 'nui-pod-star', '★ '));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
list.append(row);
}
root.append(list);
}
// A podcast's list: its filters and order, per podcast. On the server (episode_sort,
// episode_filter: the same on every device) where it knows them; else on this device.
const serverLists = live => !!live && 'episode_sort' in live && !!N.podcastFeedSettings;
function feedListPrefs(id) {
const live = feeds().find(x => x.id === id);
if (serverLists(live)) {
return {sort: live.episode_sort || 'new', filters: Array.isArray(live.episode_filter) ? live.episode_filter : []};
}
try { return JSON.parse(localStorage.getItem('nui_pod_feedlists') || '{}')[id] || {}; } catch (e) { return {}; }
}
function saveFeedListPrefs(id, prefs) {
const live = feeds().find(x => x.id === id);
if (serverLists(live)) {
const sort = prefs.sort === 'new' ? '' : prefs.sort;
// Shows at once; the host sends it on.
live.episode_sort = sort;
live.episode_filter = prefs.filters.slice();
N.podcastFeedSettings(id, JSON.stringify({episode_sort: sort, episode_filter: prefs.filters}));
return;
}
try {
const all = JSON.parse(localStorage.getItem('nui_pod_feedlists') || '{}');
if (!prefs.filters.length && prefs.sort === 'new') delete all[id]; else all[id] = prefs;
localStorage.setItem('nui_pod_feedlists', JSON.stringify(all));
} catch (e) {}
}
// Once the server keeps the lists: what this device remembered goes there (where the
// server has nothing set for that podcast yet), then the device forgets it.
function moveFeedListsToServer() {
if (!N.podcastFeedSettings) return;
let all = null;
try { all = JSON.parse(localStorage.getItem('nui_pod_feedlists') || 'null'); } catch (e) { all = null; }
if (!all) return;
const list = feeds();
if (!list.length || !list.some(f => 'episode_sort' in f)) return;
try { localStorage.removeItem('nui_pod_feedlists'); } catch (e) { return; }
for (const id of Object.keys(all)) {
const live = list.find(f => String(f.id) === id);
const p = all[id] || {};
if (!serverLists(live) || live.episode_sort || (live.episode_filter || []).length) continue;
const filters = (Array.isArray(p.filters) ? p.filters : []).filter(x => FILTERS.some(y => y.id === x && !y.all));
const sort = EP_SORTS.some(s => s.id === p.sort && !s.all) ? p.sort : 'new';
if (filters.length || sort !== 'new') saveFeedListPrefs(live.id, {filters, sort});
}
}
// ---- a podcast's info page: description, website, feed address, numbers ----------------
// In the shownotes' sheet (closeNotes closes it, also with the back button).
function openFeedInfo(f) {
const live = Object.assign({}, f, feedView && feedView.feed.id === f.id ? feedView.feed : {},
feeds().find(x => x.id === f.id) || {});
closeNotes();
const {sheet: made, panel, top} = podSheet('nui-pod-notes-sheet nui-pod-info', 'Schließen', closeNotes);
notesSheet = made;
const head = el('div', 'nui-pod-feedhead');
const title = el('div', 'nui-pod-main');
title.append(el('div', 'nui-pod-title', plain(live.title)));
if (live.author) title.append(el('div', 'nui-pod-meta', plain(live.author)));
head.append(art(live.artwork_url, 'big', null, live.title), title);
panel.append(top, head);
// Feed descriptions may carry HTML: only its text.
const text = live.description ? (new DOMParser().parseFromString(live.description, 'text/html').body.textContent || '').trim() : '';
if (text) panel.append(el('div', 'nui-pod-info-desc', text));
else if (feedView && feedView.feed.id === f.id && feedView.episodes == null) {
panel.append(el('div', 'nui-pod-meta', 'Beschreibung wird geladen …'));
}
const facts = el('dl', 'nui-pod-facts');
const fact = (label, ...value) => {
if (!value.length || value[0] == null || value[0] === '') return;
const dd = el('dd');
dd.append(...value.map(v => (typeof v === 'string' ? document.createTextNode(v) : v)));
facts.append(el('dt', null, label), dd);
};
const when = iso => (iso ? day(iso) + ', ' + new Date(iso).toLocaleTimeString('de-DE', {hour: '2-digit', minute: '2-digit'}) : null);
if (live.link && /^https?:\/\//i.test(live.link)) {
const a = el('a', null, live.link.replace(/^https?:\/\/(www\.)?/i, '').replace(/\/$/, ''));
a.href = live.link;
a.target = '_blank';
a.rel = 'noopener';
fact('Website', a);
}
if (live.rss_url) {
const row = el('div', 'nui-pod-info-actions');
const action = (label, onClick) => {
const b = el('button', 'btn', label);
b.type = 'button';
b.addEventListener('click', onClick);
row.append(b);
};
action('Kopieren', () => copyText(live.rss_url));
if (navigator.share) action('Teilen', () => navigator.share({title: plain(live.title), url: live.rss_url}).catch(() => {}));
fact('Feed', el('span', 'nui-pod-info-url', live.rss_url), row);
}
const count = live.episode_count != null ? live.episode_count : feedView && feedView.feed.id === f.id && feedView.episodes
? feedView.episodes.length : null;
if (count != null) {
fact('Folgen', [String(count), live.unplayed_count != null && `${live.unplayed_count} ungehört`,
live.new_count && `${live.new_count} neu`].filter(Boolean).join(' · '));
}
if (live.latest_episode_at) fact('Neueste Folge', day(live.latest_episode_at));
if (live.last_played_at) fact('Zuletzt gehört', day(live.last_played_at));
if (live.added_at) fact('Abonniert', new Date(live.added_at).toLocaleDateString('de-DE', {day: 'numeric', month: 'long', year: 'numeric'}));
fact('Abgerufen', when(live.last_refreshed_at) || 'noch nie');
if (live.last_error) {
fact('Fehler', el('span', 'nui-pod-feederr', `⚠ ${live.last_error}${live.last_error_at ? ' (' + day(live.last_error_at) + ')' : ''}` +
(live.error_count > 1 ? ` · ${live.error_count}× nacheinander` : '')));
}
panel.append(facts);
document.body.append(notesSheet);
}
async function copyText(text) {
try {
await navigator.clipboard.writeText(text);
toast('Kopiert');
return;
} catch (e) {}
const t = el('textarea');
t.value = text;
t.style.cssText = 'position:fixed;top:0;left:0;opacity:0';
document.body.append(t);
t.select();
let ok = false;
try { ok = document.execCommand('copy'); } catch (e) {}
t.remove();
toast(ok ? 'Kopiert' : 'Kopieren ging nicht');
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
async function unsubscribe(f) {
if (!navigator.onLine) { toast('Abo beenden braucht eine Verbindung.'); return; }
toast('Abo wird beendet …');
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
try {
const r = await fetch(`/podcasts/feeds/${f.id}/remove/`, {
method: 'POST', headers: {'Content-Type': 'application/json'}, body: '{}',
});
// 404: gone already (removed elsewhere) — the same result.
if (!r.ok && r.status !== 404) throw new Error(r.status);
} catch (e) {
toast(navigator.onLine ? 'Abo beenden hat nicht geklappt.' : 'Abo beenden braucht eine Verbindung.');
return;
}
// The episodes the page knows of it: downloads from before the app kept their podcast match by id.
const ids = ((feedView && feedView.feed.id === f.id && feedView.episodes) || []).map(o => o.id)
.concat(queue().filter(q => q.episode__feed__id === f.id).map(q => q.episode__id))
.concat(inbox().filter(o => o.feed__id === f.id).map(o => o.id));
state.feeds = feeds().filter(x => x.id !== f.id);
if (feedView && feedView.feed.id === f.id) feedView = null;
refreshing = true;
render();
toast(`„${plain(f.title)}“ abbestellt`);
// The app deletes the downloads and fetches the state again.
if (N.podcastFeedRemoved) N.podcastFeedRemoved(f.id, JSON.stringify(ids));
else N.podcastRefresh(false);
}
// The downloads list: an episode list with just the filter „Geladen“ (its button plays, its swipes delete).
const downloadsOnly = filters => DL && filters.length === 1 && filters[0] === 'downloaded';
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
// ---- all episodes, filtered (server: GET /podcasts/episodes/) ----------------------
// Filters combine (all have to match); "Geladen" asks for the episodes downloaded here.
let epFilters = [];
let epSort = 'new';
try {
const kept = localStorage.getItem('nui_pod_filters');
if (kept != null) epFilters = JSON.parse(kept);
else {
// Before filters combined: one of them.
const old = localStorage.getItem('nui_pod_filter');
epFilters = old == null ? ['unplayed'] : old === 'all' ? [] : [old];
if (old === 'history') epSort = 'played';
}
epSort = localStorage.getItem('nui_pod_epsort') || epSort;
} catch (e) {}
if (!Array.isArray(epFilters)) epFilters = [];
epFilters = epFilters.filter(id => FILTERS.some(f => f.id === id && (!f.app || DL)));
if (!EP_SORTS.some(s => s.id === epSort)) epSort = 'new';
let epList = null; // {key, items, total, loading, error}
const epKey = () => epFilters.slice().sort().join(',') + '|' + epSort;
function setEpisodeList(filters, sort) {
// The history goes by the last listening; leaving it, by date again.
if (sort == null) {
sort = epSort;
if (filters.includes('history') && !epFilters.includes('history') && sort === 'new') sort = 'played';
if (!filters.includes('history') && epFilters.includes('history') && sort === 'played') sort = 'new';
}
epFilters = filters;
epSort = sort;
try {
localStorage.setItem('nui_pod_filters', JSON.stringify(epFilters));
localStorage.setItem('nui_pod_epsort', epSort);
} catch (e) {}
loadEpisodes(false);
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
async function loadEpisodes(append, quiet) {
const key = epKey();
const offset = append && epList && epList.key === key ? epList.items.length : 0;
if (!append) epList = {key, items: [], total: 0, loading: true};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
else epList.loading = true;
if (!quiet) render();
const params = new URLSearchParams({
filter: epFilters.filter(id => id !== 'downloaded').join(','), sort: epSort, limit: '100', offset: String(offset),
});
if (epFilters.includes('downloaded')) {
const ids = Object.keys(downloads).filter(id => downloads[id].state === 'done');
if (!ids.length) { epList = {key, items: [], total: 0}; render(); return; }
params.set('ids', ids.join(','));
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
try {
const r = await fetch(`/podcasts/episodes/?${params}`);
if (r.status === 400) throw new Error('old');
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (!r.ok) throw new Error(r.status);
const d = await r.json();
if (!epList || epList.key !== key) return;
epList = {key, items: (append ? epList.items : []).concat(d.episodes || []), total: d.total || 0};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
} catch (e) {
if (!epList || epList.key !== key) return;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
epList.loading = false;
epList.error = e.message === 'old' ? 'Der Server kennt diese Filter noch nicht.' : 'Die Folgenliste braucht eine Verbindung.';
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
render();
}
function renderEpisodes() {
if (!selected) root.append(filterRow(epFilters, next => setEpisodeList(next), true));
if (!epList || epList.key !== epKey()) { loadEpisodes(false, true); empty('Wird geladen …'); return; }
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (epList.error && !epList.items.length) { empty(epList.error); return; }
// What changed here since (marked, starred, queued) shows at once.
const items = epList.items.filter(o => matchesAll(o, epFilters)).map(ep);
const total = Math.max(items.length, epList.total - (epList.items.length - items.length));
if (!items.length) {
empty(epList.loading ? 'Wird geladen …' : noneText(epFilters));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
return;
}
if (selected) {
root.append(episodeSelection(items));
} else {
const head = el('div', 'nui-pod-head');
head.append(el('div', 'nui-pod-summary', `${nEpisodes(total)} · ${sortLabel(epSort).toLowerCase()}`),
listTools([
button('⇅', 'Sortieren', () => sortMenuFor(epSort, true, id => setEpisodeList(epFilters, id))),
button('☐', 'Mehrere auswählen', () => startSelecting()),
]));
root.append(head);
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const list = el('div', 'nui-pod-list');
for (const e of items) {
const o = e.raw;
const row = episodeRow(e, {showFeed: true, showDate: epSort !== 'played', list: downloadsOnly(epFilters) ? 'downloads' : 'episodes',
filters: epFilters});
if (epSort === 'played' && o.last_played_at) {
const m = row.querySelector('.nui-pod-meta');
const mark = m.querySelector('.nui-pod-dlmark');
m.textContent = 'gehört ' + day(o.last_played_at) + ' · ' + (e.feed || '');
if (mark) m.prepend(mark);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
if (o.favorite) row.querySelector('.nui-pod-title').prepend(el('span', 'nui-pod-star', '★ '));
list.append(row);
}
root.append(list);
if (epList.items.length < epList.total) {
const more = el('button', 'btn nui-pod-more', epList.loading ? 'Wird geladen …' : 'Weitere laden');
more.type = 'button';
more.disabled = !!epList.loading;
more.addEventListener('click', () => loadEpisodes(true));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
root.append(more);
}
}
// ---- favourites -------------------------------------------------------------------------
function favoriteItem(e) {
const o = e.raw || {};
const known = typeof o.favorite === 'boolean';
return {icon: o.favorite ? '★' : '☆', label: known ? (o.favorite ? 'Kein Favorit mehr' : 'Als Favorit markieren') : 'Favorit an/aus',
action: () => toggleFavorite(e, star => { if (e.raw) e.raw.favorite = star; })};
}
async function toggleFavorite(e, done, quiet) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const o = e.raw || {};
const body = typeof o.favorite === 'boolean' ? {favorite: !o.favorite} : {};
try {
const r = await fetch(`/podcasts/episodes/${e.id}/favorite/`, {
method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(body),
});
if (!r.ok) throw new Error(r.status);
const d = await r.json();
if (!quiet) toast(d.favorite ? '★ Als Favorit markiert' : 'Kein Favorit mehr');
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
// Favourites keep their download (Downloads.kt).
if (N.podcastFavoriteChanged) N.podcastFavoriteChanged(e.id, !!d.favorite);
const cached = storedDetails(e.id);
if (cached) keepDetails(e.id, Object.assign({}, cached, {favorite: d.favorite}));
if (done) done(d.favorite);
if (epList) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const item = epList.items.find(x => x.id === e.id);
if (item) item.favorite = d.favorite;
}
// The ★ in the lists (Folgen, one podcast, search) and their filters.
render();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
} catch (err) {
toast(navigator.onLine ? 'Favorit ließ sich nicht setzen' : 'Favoriten brauchen eine Verbindung');
}
}
// ---- listening statistics (server: GET /podcasts/stats/) -----------------------------
let statsSheet = null;
function closeStats() {
if (!statsSheet) return false;
statsSheet.remove();
statsSheet = null;
return true;
}
function hours(seconds) {
const h = Math.floor(seconds / 3600);
const m = Math.round((seconds % 3600) / 60);
return h ? `${h} h ${String(m).padStart(2, '0')} min` : `${m} min`;
}
function openStats() {
closeStats();
const {sheet: made, panel, top} = podSheet('nui-pod-notes-sheet', 'Schließen', closeStats);
statsSheet = made;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const tabs = el('div', 'nui-pod-subtabs');
const body = el('div', 'nui-pod-stats');
panel.append(top, el('div', 'nui-pod-player-title', 'Hörstatistik'), tabs, body);
document.body.append(statsSheet);
const ranges = [{days: 30, label: '30 Tage'}, {days: 365, label: 'Jahr'}, {days: 0, label: 'Gesamt'}];
const show = async range => {
tabs.querySelectorAll('button').forEach(b => b.classList.toggle('active', b.dataset.days === String(range.days)));
body.textContent = '';
body.append(el('div', 'nui-pod-meta', 'Wird geladen …'));
let d = null;
try {
d = await getJson('/podcasts/stats/' + (range.days ? `?days=${range.days}` : ''));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
} catch (e) {
body.textContent = '';
body.append(el('div', 'nui-pod-meta', 'Die Statistik braucht eine Verbindung.'));
return;
}
body.textContent = '';
const big = el('div', 'nui-pod-stat-total');
big.append(el('strong', null, hours(d.total_seconds || 0)), el('span', null,
`${d.episodes_played || 0} Folgen gehört · ${d.episodes_started || 0} angefangen`));
body.append(big);
const feedsList = d.feeds || [];
if (!feedsList.length) { body.append(el('div', 'nui-pod-meta', 'In diesem Zeitraum nichts gehört.')); return; }
const max = Math.max(...feedsList.map(f => f.seconds || 0), 1);
for (const f of feedsList) {
const row = el('div', 'nui-pod-stat-row');
row.append(art(f.artwork_url, null, null, f.title));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const main = el('div', 'nui-pod-main');
main.append(el('div', 'nui-pod-title', plain(f.title)),
el('div', 'nui-pod-meta', `${hours(f.seconds || 0)} · ${f.episodes_played || 0} Folgen`));
const bar = el('div', 'nui-pod-progress nui-pod-stat-bar');
const fill = el('span');
fill.style.width = Math.max(2, Math.round((f.seconds || 0) / max * 100)) + '%';
bar.append(fill);
main.append(bar);
row.append(main);
body.append(row);
}
};
for (const r of ranges) {
const b = el('button', 'nui-pod-subtab', r.label);
b.type = 'button';
b.dataset.days = String(r.days);
b.addEventListener('click', () => show(r));
tabs.append(b);
}
show(ranges[0]);
}
// ---- OPML: take the subscriptions to another app, or bring them in -------------------
async function exportOpml() {
try {
const r = await fetch('/podcasts/feeds/export.opml');
if (!r.ok) throw new Error(r.status);
const blob = new Blob([await r.text()], {type: 'text/x-opml'});
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'diora-podcasts.opml';
a.click(); // the app offers to share or save it (native.js)
setTimeout(() => URL.revokeObjectURL(a.href), 60000);
} catch (e) {
toast(navigator.onLine ? 'Export hat nicht geklappt' : 'Der Export braucht eine Verbindung');
}
}
function importOpml() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.opml,.xml,text/x-opml,text/xml,application/xml';
input.addEventListener('change', async () => {
const file = input.files && input.files[0];
if (!file) return;
const form = new FormData();
form.append('file', file);
toast('Abos werden importiert …');
try {
const r = await fetch('/podcasts/feeds/import/', {method: 'POST', body: form});
const d = await r.json();
if (!r.ok || !d.ok) throw new Error(d.error || r.status);
toast(`${d.added} neue Abos${d.skipped ? `, ${d.skipped} schon vorhanden` : ''}`);
refresh(true);
} catch (e) {
toast('Diese Datei ließ sich nicht importieren');
}
});
input.click();
}
// ---- search: own podcasts, their episodes (also in the shownotes), the directory ----
// searchResults: {q, feeds, eps: {items, total, loading, local}, dir: {items, loading, error}}.
// Without a connection the episodes come from what this device knows (queue,
// "Neu", lists seen), the podcasts from the kept state.
let searchDraft = null;
function fold(s) {
return plain(s || '').toLocaleLowerCase('de').normalize('NFD').replace(/[̀-ͯ]/g, '');
}
// All words of the query, anywhere in the text.
function matcher(q) {
const words = fold(q).split(/\s+/).filter(Boolean);
return text => words.every(w => text.includes(w));
}
function knownEpisodes() {
const seen = new Map();
const add = o => { if (o && !seen.has(shape(o).id)) seen.set(shape(o).id, o); };
queue().forEach(add);
inbox().forEach(add);
if (epList) epList.items.forEach(add);
if (feedView && feedView.episodes) feedView.episodes.forEach(add);
return [...seen.values()];
}
function searchText(o) {
const e = shape(o);
const notes = o.description ? o.description.replace(/<[^>]*>/g, ' ') : '';
return fold(e.title + ' ' + notes);
}
function searchNote(text) {
root.append(el('div', 'nui-pod-sec-note', text));
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function renderSearch() {
const form = el('form', 'nui-pod-search');
const input = el('input', 'search-input');
input.type = 'search';
input.id = 'nui-pod-q';
input.placeholder = 'Abos, Folgen, Verzeichnis – oder Feed-Adresse …';
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
input.setAttribute('enterkeyhint', 'search');
input.value = searchDraft != null ? searchDraft : searchResults ? searchResults.q : '';
input.addEventListener('input', () => { searchDraft = input.value; });
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const go = el('button', 'btn', 'Suchen');
go.type = 'submit';
form.append(input, go);
form.addEventListener('submit', ev => {
ev.preventDefault();
search(input.value.trim());
});
root.append(form);
if (hasSources()) {
const more = el('button', 'nui-pod-hint nui-pod-link', 'Alles abonnieren und verwalten – auch YouTube, Nachrichten, Blogs: Abos & Quellen ›');
more.type = 'button';
more.addEventListener('click', toSources);
root.append(more);
}
const s = searchResults;
if (!s) {
empty('Sucht in deinen Abos und ihren Folgen (auch in den Shownotes) und im Podcast-Verzeichnis von Apple. ' +
'Eine Feed-Adresse (https://…) abonnierst du direkt.');
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
return;
}
if (s.feeds.length) {
root.append(el('div', 'nui-pod-sec', `Deine Podcasts (${s.feeds.length})`));
const list = el('div', 'nui-pod-list');
for (const f of s.feeds) list.append(feedRow(f));
root.append(list);
}
const eps = s.eps;
root.append(el('div', 'nui-pod-sec', 'Folgen' + (eps.items.length ? ` (${eps.total})` : '')));
if (eps.local) searchNote('Ohne Verbindung: nur Folgen, die dieses Gerät kennt.');
if (!eps.items.length) {
searchNote(eps.loading ? 'Wird gesucht …' : 'Keine Folgen gefunden.');
} else {
const list = el('div', 'nui-pod-list');
for (const o of eps.items) {
const e = ep(o);
const row = episodeRow(e, {showFeed: true, showDate: true, noSelect: true});
if (o.favorite) row.querySelector('.nui-pod-title').prepend(el('span', 'nui-pod-star', '★ '));
list.append(row);
}
root.append(list);
if (!eps.local && eps.items.length < eps.total) {
const more = el('button', 'btn nui-pod-more', eps.loading ? 'Wird geladen …' : 'Weitere Folgen');
more.type = 'button';
more.disabled = !!eps.loading;
more.addEventListener('click', () => searchEpisodes(s, true));
root.append(more);
}
}
const dir = s.dir;
root.append(el('div', 'nui-pod-sec', 'Im Podcast-Verzeichnis'));
if (dir.loading) { searchNote('Wird gesucht …'); return; }
if (dir.error) { searchNote(dir.error); return; }
if (!dir.items.length) { searchNote('Nichts gefunden.'); return; }
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const subscribed = new Set(feeds().map(f => f.rss_url));
const list = el('div', 'nui-pod-list');
for (const r of dir.items) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const row = el('div', 'nui-pod-row');
row.append(art(r.artwork_url, null, null, r.title));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const main = el('div', 'nui-pod-main');
main.append(el('div', 'nui-pod-title', r.title), el('div', 'nui-pod-meta', [r.author, r.genre].filter(Boolean).join(' · ')));
row.append(main);
if (!r.rss_url) row.append(el('span', 'nui-pod-meta', 'kein Feed'));
else if (subscribed.has(r.rss_url)) row.append(el('span', 'nui-pod-badge', 'abonniert'));
else row.append(button('+', 'Abonnieren', () => subscribe(r.rss_url, r.title)));
list.append(row);
}
root.append(list);
}
function search(q) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (!q) return;
if (/^https?:\/\//i.test(q)) { if (N.podcastPreview) openPreview(q); else subscribe(q, q); return; }
searchDraft = null;
const test = matcher(q);
const s = searchResults = {
q,
feeds: podcastFeeds().filter(f => test(fold(f.title) + ' ' + fold(f.author))),
eps: {items: [], total: 0, loading: true},
dir: {items: [], loading: true},
};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
render();
searchEpisodes(s, false);
searchDirectory(s);
}
async function searchEpisodes(s, more) {
const offset = more ? s.eps.items.length : 0;
s.eps.loading = true;
if (more) render();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
try {
const d = await getJson(`/podcasts/episodes/?q=${encodeURIComponent(s.q)}&notes=1&limit=30&offset=${offset}`);
s.eps = {items: (more ? s.eps.items : []).concat(d.episodes || []), total: d.total || 0};
} catch (e) {
if (more) {
s.eps.loading = false;
toast('Weitere Folgen brauchen eine Verbindung.');
} else {
const test = matcher(s.q);
const items = knownEpisodes().filter(o => test(searchText(o)));
s.eps = {items, total: items.length, local: true};
}
}
if (searchResults === s) render();
}
async function searchDirectory(s) {
try {
const r = await fetch('/podcasts/search/?q=' + encodeURIComponent(s.q));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const data = await r.json();
s.dir = {items: data.results || [], error: data.error ? 'Das Verzeichnis ist gerade nicht erreichbar.' : null};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
} catch (e) {
s.dir = {items: [], error: 'Das Verzeichnis braucht eine Verbindung.'};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
if (searchResults === s) render();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
async function subscribe(rssUrl, title) {
toast('Wird abonniert …');
try {
const r = await fetch('/podcasts/feeds/add/', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({rss_url: rssUrl, title}),
});
const data = await r.json();
if (!r.ok || !data.ok) throw new Error(data.error || r.status);
toast(`„${data.title || title}“ abonniert`);
refresh(false);
return true;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
} catch (e) {
toast(navigator.onLine ? 'Dieser Feed lässt sich nicht abonnieren.' : 'Abonnieren braucht eine Verbindung.');
return false;
}
}
// A feed from another app (shared link, pcast:// …) or a pasted address:
// the app fetches it (N.podcastPreview, FeedPreview.kt) and the page shows
// title, cover, description and the newest episodes before subscribing.
function openPreview(url) {
if (!N.podcastPreview) { subscribe(url, url); return; }
closePlayer();
feedView = null;
selected = null;
preview = {id: ++previewSeq, url};
if (typeof showTab === 'function') showTab('podcasts');
render();
scrollTop(0);
N.podcastPreview(preview.id, url);
}
function closePreview() {
if (!preview) return false;
preview = null;
render();
return true;
}
function renderPreview() {
const p = preview;
const d = p.data || {};
const head = el('div', 'nui-pod-feedhead');
head.append(button('‹', 'Zurück', closePreview), art(d.artwork, 'big', null, d.title));
const info = el('div', 'nui-pod-main');
info.append(el('div', 'nui-pod-title', plain(d.title) || 'Podcast'),
el('div', 'nui-pod-meta', plain(d.author) || p.url));
head.append(info);
root.append(head);
if (p.choices) {
root.append(el('div', 'nui-pod-empty', 'Diese Seite verlinkt mehrere Feeds. Welcher ist es?'));
const list = el('div', 'nui-pod-list');
for (const c of p.choices) {
const row = el('div', 'nui-pod-row');
const main = el('div', 'nui-pod-main');
main.append(el('div', 'nui-pod-title', plain(c.title) || c.url), el('div', 'nui-pod-meta', c.url));
row.append(main);
row.addEventListener('click', () => openPreview(c.url));
list.append(row);
}
root.append(list);
return;
}
if (p.error) {
// The server may still manage (it fetches the feed itself).
const box = el('div', 'nui-pod-offer', p.error);
const actions = el('div', 'nui-pod-offer-row');
const anyway = el('button', 'btn', 'Trotzdem abonnieren');
anyway.type = 'button';
anyway.addEventListener('click', () => subscribeFromPreview(p.url, p.url, anyway));
actions.append(anyway);
box.append(actions);
root.append(box);
return;
}
if (!p.data) { empty('Feed wird geladen …'); return; }
const known = feeds().find(f => f.rss_url === d.url || f.rss_url === p.url);
const actions = el('div', 'nui-pod-offer-row');
const go = el('button', 'btn', known ? 'Abonniert – öffnen' : 'Abonnieren');
go.type = 'button';
go.addEventListener('click', () => {
if (known) { preview = null; openFeed(known); return; }
subscribeFromPreview(d.url, d.title, go);
});
actions.append(go);
root.append(actions);
if (d.description) {
const desc = el('div', 'nui-pod-offer', plain(d.description));
desc.style.whiteSpace = 'pre-line';
root.append(desc);
}
const eps = d.episodes || [];
if (eps.length) {
const list = el('div', 'nui-pod-list');
for (const e of eps) {
const row = el('div', 'nui-pod-row');
row.append(art(e.artwork, null, d.artwork, d.title));
const main = el('div', 'nui-pod-main');
main.append(el('div', 'nui-pod-title', plain(e.title)),
el('div', 'nui-pod-meta', [e.duration && minutes(e.duration), day(e.date)].filter(Boolean).join(' · ')));
row.append(main);
list.append(row);
}
root.append(list);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
}
async function subscribeFromPreview(url, title, btn) {
btn.disabled = true;
const ok = await subscribe(url, title);
btn.disabled = false;
if (!ok) return;
preview = null;
view = 'feeds';
try { localStorage.setItem('nui_pod_view', view); } catch (e) {}
render();
scrollTop(0);
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
// ---- drag to reorder (touch) -----------------------------------------------------------
// Dragging the handle (≡) sorts the queue — once the move is mostly vertical (after the same
// 12 px the swipe waits for); a mostly sideways one is left to the row's swipe (rowGestures).
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function dragSort(handle, row, list) {
handle.addEventListener('pointerdown', ev => {
if (ev.pointerType === 'mouse' && ev.button !== 0) return;
if (ev.pointerType === 'mouse') ev.preventDefault(); // no text selection while dragging
const id = ev.pointerId;
const startX = ev.clientX;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const startY = ev.clientY;
let rows = null;
let from = -1;
let rects = null;
let to = -1;
const begin = () => {
rows = [...list.children];
from = rows.indexOf(row);
rects = rows.map(r => r.getBoundingClientRect());
to = from;
row.classList.add('nui-pod-dragging');
try { handle.setPointerCapture(id); } catch (e) {}
};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const move = e => {
if (e.pointerId !== id) return;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const dy = e.clientY - startY;
if (!rows) {
const dx = e.clientX - startX;
if (Math.abs(dx) <= 12 && Math.abs(dy) <= 12) return;
if (Math.abs(dy) <= Math.abs(dx)) { stop(); return; }
begin();
}
e.preventDefault();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
row.style.transform = `translateY(${dy}px)`;
const mid = rects[from].top + rects[from].height / 2 + dy;
to = rects.findIndex(r => mid < r.top + r.height / 2);
if (to < 0) to = rects.length - 1;
rows.forEach((r, i) => {
if (r === row) return;
let shift = 0;
if (from < to && i > from && i <= to) shift = -rects[from].height;
if (from > to && i >= to && i < from) shift = rects[from].height;
r.style.transform = shift ? `translateY(${shift}px)` : '';
});
};
// Listened to on the window: before the drag starts the finger may leave the narrow handle.
const stop = () => {
window.removeEventListener('pointermove', move);
window.removeEventListener('pointerup', end);
window.removeEventListener('pointercancel', end);
};
const end = e => {
if (e.pointerId !== id) return;
stop();
if (!rows) return;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
rows.forEach(r => { r.style.transform = ''; });
row.classList.remove('nui-pod-dragging');
if (to !== from) {
// Show the new order at once; the app's state follows right after.
list.insertBefore(row, to > from ? rows[to].nextSibling : rows[to]);
N.podcastMove(parseInt(row.dataset.id, 10), to);
}
};
window.addEventListener('pointermove', move);
window.addEventListener('pointerup', end);
window.addEventListener('pointercancel', end);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
});
}
// ---- mini player and full player -----------------------------------------------------------
let mini = null;
function buildMini() {
const dock = $id('nui-dock');
if (!dock || $id('nui-pod-mini')) return;
mini = el('div');
mini.id = 'nui-pod-mini';
mini.hidden = true;
mini.innerHTML = '<div class="nui-pod-mini-bar"><span></span></div>';
const text = el('button', 'nui-mini-text');
text.type = 'button';
text.append(el('span', 'nui-mini-station'), el('span', 'nui-mini-track'));
text.addEventListener('click', () => (miniKind === 'tts' ? ttsShowBook()
: miniKind === 'ambient' ? showTab('ambient')
: miniKind === 'radio' ? openRadioPlayer() : openPlayer()));
const devices = button('⇄', 'Wiedergabe auf …', deviceMenu, 'nui-mini-devices');
const here = button('⇩', 'Hier abspielen', takeOver, 'nui-mini-here');
devices.hidden = here.hidden = true;
const tts = (sign, label, fn, cls) => {
const b = button(sign, label, () => { const t = window.__dioraTts; if (t) fn(t); }, 'nui-tts-btn ' + cls);
b.hidden = true;
return b;
};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
mini.append(art('', 'mini'), text,
tts('⏮︎', 'Voriger Satz', t => t.prev(), 'nui-tts-prev'),
button('↺', '15 s zurück', () => (miniKind === 'remote'
? remoteCmd('skip', -(devicePrefs().skip_back || 15)) : N.podcastSkip(false)), 'nui-pod-skip'),
button('☆', 'Sender speichern', () => window.__dioraRadioSave && window.__dioraRadioSave(), 'nui-mini-save'),
devices, here,
button('▶', 'Abspielen', () => (miniKind === 'remote' ? remoteToggle()
: miniKind === 'tts' ? window.__dioraTts && window.__dioraTts.toggle()
: miniKind === 'ambient' ? ambientToggle()
: miniKind === 'radio' ? window.__dioraRadioToggle && window.__dioraRadioToggle() : N.podcastToggle()), 'nui-mini-toggle'),
tts('⏭︎', 'Nächster Satz', t => t.next(), 'nui-tts-next'),
tts('■', 'Vorlesen beenden', t => t.stop(), 'nui-tts-stop'));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
dock.prepend(mini);
// Read-aloud uses this bar; app.js leaves out its own (.tts-bar).
window.__dioraTtsDock = true;
// One player for podcasts and radio: the radio's own bar steps aside.
document.documentElement.classList.add('nui-one-player');
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
// A "Podcasts" button in the bottom bar, between books and radio (with the page's
// navigation, nav.js, the bar has the podcasts as a section already).
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const tabbar = $id('nui-tabbar');
if (tabbar && !window.DioraNav && !tabbar.querySelector('[data-tab="podcasts"]')) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const b = el('button');
b.type = 'button';
b.dataset.tab = 'podcasts';
b.append(el('span', 'nui-tab-icon', '◎'), document.createTextNode('Podcasts'));
b.addEventListener('click', () => { showTab('podcasts'); scrollTop(0); });
tabbar.insertBefore(b, tabbar.querySelector('[data-tab="radio"]'));
}
}
// Ambient (app.js's #tab-ambient): its sleep timer, a ☾ at the frame's top right corner.
function ambientSleepButton() {
const tab = $id('tab-ambient');
if (!tab || !N.podcastSleep || $id('nui-amb-sleep')) return;
const b = button('☾', 'Sleep-Timer', () => sleepMenu(true, true), 'nui-pod-sleep');
b.id = 'nui-amb-sleep';
tab.append(b);
}
function renderAmbientSleep() {
const b = $id('nui-amb-sleep');
if (!b) return;
b.textContent = sleepLabel(true) || '☾';
b.classList.toggle('on', !!sleepLabel(true));
}
// Ambient (app.js) started last: the bar is its — the sound world's name, ▶/■, a tap opens
// its tab — until the radio or an episode starts.
function ambientInfo() {
return window.__dioraAmbientInfo ? window.__dioraAmbientInfo() : null;
}
function ambientToggle() {
const a = ambientInfo();
if (a && window.__dioraAmbientCmd) window.__dioraAmbientCmd(a.playing ? 'stop' : 'start');
}
window.__dioraAmbientChanged = () => renderMini();
function renderAmbientMini() {
const a = ambientInfo();
if (!mini || !a || !a.last) return false;
// An episode or the radio plays here: the bar is theirs, whatever came before.
const r = radioNow();
if (!a.playing && ((player.playing && !player.remote) || (r && r.playing))) return false;
if (player.kind === 'podcast' && player.playing && !player.remote) return false;
miniKind = 'ambient';
document.documentElement.classList.remove('nui-pod-playing');
document.documentElement.classList.add('nui-pod-mini-open');
mini.hidden = false;
mini.classList.add('nui-mini-radio');
mini.classList.remove('nui-mini-remote');
for (const c of ['.nui-pod-skip', '.nui-mini-save', '.nui-mini-devices', '.nui-mini-here']) mini.querySelector(c).hidden = true;
mini.querySelector('.nui-mini-station').textContent = 'Ambient';
const z = sleepLabel(true);
mini.querySelector('.nui-mini-track').textContent = (a.mode || 'Drift') + (a.playing ? '' : ' · angehalten') + (z ? ' · ' + z : '');
const t = mini.querySelector('.nui-mini-toggle');
t.textContent = a.playing ? '■' : '▶';
t.title = a.playing ? 'Ambient stoppen' : 'Ambient abspielen';
const box = mini.querySelector('.nui-pod-art');
setArt(box, '');
const ttsArt = box.querySelector('.nui-tts-art');
if (ttsArt) ttsArt.remove();
if (!box.querySelector('.nui-amb-art')) { box.textContent = ''; box.append(el('span', 'nui-pod-initial nui-amb-art', '∿')); }
mini.querySelector('.nui-pod-mini-bar span').style.width = a.playing ? '100%' : '0';
return true;
}
// Read-aloud (app.js, window.__dioraTts; on Android with the device's voice) has the bar
// while it runs: the book, the chapter, sentence back / pause / sentence on / stop.
function ttsState() {
try { return window.__dioraTts ? window.__dioraTts.state() : null; } catch (e) { return null; }
}
function ttsButtons(on) {
if (!mini) return;
mini.querySelectorAll('.nui-tts-btn').forEach(b => { b.hidden = !on; });
}
// A tap on the text: back to the book that's being read, if it isn't open.
function ttsShowBook() {
const s = ttsState();
if (s && s.bookId && typeof openBook === 'function' && (typeof currentBookId === 'undefined' || currentBookId !== s.bookId)) openBook(s.bookId);
}
function renderTtsMini() {
const s = ttsState();
document.documentElement.classList.toggle('nui-tts-on', !!(s && s.active));
if (!mini || !s || !s.active) return false;
miniKind = 'tts';
document.documentElement.classList.remove('nui-pod-playing');
document.documentElement.classList.add('nui-pod-mini-open');
mini.hidden = false;
mini.classList.add('nui-mini-radio');
mini.classList.remove('nui-mini-remote');
for (const c of ['.nui-pod-skip', '.nui-mini-save', '.nui-mini-devices', '.nui-mini-here']) mini.querySelector(c).hidden = true;
ttsButtons(true);
mini.querySelector('.nui-mini-station').textContent = s.title || 'Vorlesen';
mini.querySelector('.nui-mini-track').textContent = (s.paused ? 'Angehalten' : 'Vorlesen') + (s.chapter ? ' · ' + s.chapter : '');
const t = mini.querySelector('.nui-mini-toggle');
t.textContent = s.paused ? '▶' : '❚❚';
t.title = s.paused ? 'Weiterlesen' : 'Pause';
const box = mini.querySelector('.nui-pod-art');
setArt(box, '');
const amb = box.querySelector('.nui-amb-art');
if (amb) amb.remove();
if (!box.querySelector('.nui-tts-art')) { box.textContent = ''; box.append(el('span', 'nui-pod-initial nui-tts-art', '❝')); }
mini.querySelector('.nui-pod-mini-bar span').style.width = typeof s.progress === 'number' ? Math.min(100, s.progress * 100) + '%' : '0';
return true;
}
window.addEventListener('diora-tts', () => renderMini());
// The bar names a running sleep timer: "… · ☾ 12 min".
function sleepSuffix() {
const l = sleepLabel();
return l ? ' · ' + l : '';
}
// What the bar shows: 'podcast', 'radio' (a station is chosen), 'remote'
// (another device is the active one) or ''.
let miniKind = '';
function radioNow() {
return typeof window.__dioraRadioNow === 'function' ? window.__dioraRadioNow() : null;
}
function setArt(box, src) {
const img = box.querySelector('img');
src = (src || '').replace(/^http:/, 'https:');
if (!img && src) {
const i = el('img');
i.alt = '';
i.src = src;
i.addEventListener('error', () => i.remove());
box.append(i);
} else if (img && img.getAttribute('src') !== src) {
if (src) img.src = src; else img.remove();
}
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function renderMini() {
renderAmbientSleep();
if (renderTtsMini()) return;
ttsButtons(false);
if (renderAmbientMini()) return;
const r = remote();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const isPod = player.kind === 'podcast';
document.documentElement.classList.toggle('nui-pod-playing', isPod || !!r);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (!mini) return;
const ambArt = mini.querySelector('.nui-amb-art, .nui-tts-art');
if (ambArt) ambArt.remove();
const radio = isPod || r ? null : radioNow();
miniKind = r ? 'remote' : isPod ? 'podcast' : radio ? 'radio' : '';
mini.hidden = !miniKind;
mini.classList.toggle('nui-mini-radio', miniKind === 'radio' || (!!r && r.kind === 'radio'));
mini.classList.toggle('nui-mini-remote', !!r);
document.documentElement.classList.toggle('nui-pod-mini-open', !!miniKind);
mini.querySelector('.nui-pod-skip').hidden = !(isPod || r) || (!!r && r.kind !== 'podcast');
mini.querySelector('.nui-mini-save').hidden = miniKind !== 'radio' || radio.saved;
mini.querySelector('.nui-mini-devices').hidden = !r || !N.playerTransfer;
mini.querySelector('.nui-mini-here').hidden = !r || !N.playerTakeOver;
if (r) {
const onRadio = r.kind === 'radio';
mini.querySelector('.nui-mini-station').textContent = (onRadio ? r.station && r.station.name : r.title) || '';
mini.querySelector('.nui-mini-track').textContent = remoteLine(r) + sleepSuffix();
const t = mini.querySelector('.nui-mini-toggle');
t.textContent = r.playing ? (onRadio ? '■' : '❚❚') : '▶';
t.title = r.playing ? (onRadio ? 'Stopp' : 'Pause') + ' auf ' + remoteName(r) : 'Abspielen auf ' + remoteName(r);
setArt(mini.querySelector('.nui-pod-art'), onRadio ? r.station && r.station.logo : r.artwork);
mini.querySelector('.nui-pod-mini-bar span').style.width = onRadio ? (r.playing ? '100%' : '0')
: r.dur ? Math.min(100, r.pos / r.dur * 100) + '%' : '0';
return;
}
if (miniKind === 'radio') {
mini.querySelector('.nui-mini-station').textContent = radio.name;
mini.querySelector('.nui-mini-track').textContent = (radio.buffering ? 'Verbinde …'
: radio.playing ? (radio.track || '● Live') : 'Angehalten') + sleepSuffix();
const t = mini.querySelector('.nui-mini-toggle');
t.textContent = radio.playing ? '■' : '▶';
t.title = radio.playing ? 'Stopp' : 'Abspielen';
setArt(mini.querySelector('.nui-pod-art'), radio.logo);
mini.querySelector('.nui-pod-mini-bar span').style.width = radio.playing ? '100%' : '0';
return;
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (!isPod) return;
mini.querySelector('.nui-mini-station').textContent = player.title || '';
// The back button names the set skip length (checked now and then, it rarely changes).
if (!mini.skipChecked || Date.now() - mini.skipChecked > 5000) {
mini.skipChecked = Date.now();
const sk = mini.querySelector('.nui-pod-skip');
sk.title = `${devicePrefs().skip_back || 15} s zurück`;
sk.setAttribute('aria-label', sk.title);
}
mini.querySelector('.nui-mini-track').textContent = (player.buffering && player.playing ? 'Lädt …'
: (bookLine(player) || player.feed || '') + (player.dur ? ' · ' + clock(shown((player.dur - player.pos) / 1000, 0, player.speed || 1)) + ' übrig' : ''))
+ sleepSuffix();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const t = mini.querySelector('.nui-mini-toggle');
t.textContent = player.playing ? '❚❚' : '▶';
t.title = player.playing ? 'Pause' : 'Abspielen';
setArt(mini.querySelector('.nui-pod-art'), player.artwork);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
mini.querySelector('.nui-pod-mini-bar span').style.width = player.dur ? Math.min(100, player.pos / player.dur * 100) + '%' : '0';
}
let sheet = null;
let sheetEpisode = 0;
let seeking = false;
// ---- chapters and shownotes (server: /podcasts/episodes/<id>/) ------------------------
// Fetched per episode when wanted and kept for offline use (the last 40).
const details = new Map();
function storedDetails(id) {
if (details.has(id)) return details.get(id);
try {
const d = JSON.parse(localStorage.getItem('nui_pod_ep_' + id) || 'null');
if (d) details.set(id, d);
return d;
} catch (e) {
return null;
}
}
function keepDetails(id, d) {
details.set(id, d);
try {
const ids = JSON.parse(localStorage.getItem('nui_pod_ep_ids') || '[]').filter(x => x !== id);
ids.push(id);
while (ids.length > 40) localStorage.removeItem('nui_pod_ep_' + ids.shift());
localStorage.setItem('nui_pod_ep_' + id, JSON.stringify(d));
localStorage.setItem('nui_pod_ep_ids', JSON.stringify(ids));
} catch (e) {}
}
async function loadDetails(id) {
try {
const d = await getJson(`/podcasts/episodes/${id}/`);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
keepDetails(id, d);
return d;
} catch (e) {
return storedDetails(id);
}
}
// Timestamps in shownotes ("12:34", "1:02:03") become links that jump there.
const STAMP = /\b(?:(\d{1,2}):)?(\d{1,2}):(\d{2})\b/g;
function linkStamps(root) {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
const nodes = [];
while (walker.nextNode()) {
const n = walker.currentNode;
if (!n.parentElement.closest('a') && STAMP.test(n.nodeValue)) nodes.push(n);
STAMP.lastIndex = 0;
}
for (const n of nodes) {
const frag = document.createDocumentFragment();
let last = 0;
n.nodeValue.replace(STAMP, (m, h, mi, se, at) => {
const secs = (parseInt(h || '0', 10) * 3600) + parseInt(mi, 10) * 60 + parseInt(se, 10);
if (parseInt(se, 10) > 59) return m;
frag.append(document.createTextNode(n.nodeValue.slice(last, at)));
const a = el('a', 'nui-pod-stamp', m);
a.href = '#';
a.dataset.t = String(secs);
frag.append(a);
last = at + m.length;
return m;
});
frag.append(document.createTextNode(n.nodeValue.slice(last)));
n.replaceWith(frag);
}
}
// Chapters and shownotes into a box; with `live`, taps jump in the playing episode,
// else with `jump(seconds)` (the episode screen) they start it there.
async function showDetails(box, id, live, jump) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
box.textContent = '';
box.dataset.episode = String(id);
let d = storedDetails(id);
if (!d) box.append(el('div', 'nui-pod-meta', 'Shownotes werden geladen …'));
const fresh = await loadDetails(id);
if (box.dataset.episode !== String(id)) return;
d = fresh || d;
box.textContent = '';
if (!d) {
box.append(el('div', 'nui-pod-meta', 'Shownotes brauchen beim ersten Mal eine Verbindung.'));
return;
}
if (live && sheet) {
const fav = sheet.querySelector('.nui-pod-fav');
if (fav) { fav.textContent = d.favorite ? '★' : '☆'; fav.classList.toggle('on', !!d.favorite); }
}
const chapters = d.chapters || [];
// The full player lists the chapters right under the title (chapterBox); here only the notes.
const chapBox = live && sheet ? sheet.querySelector('.nui-pod-chapbox') : null;
if (chapBox) chapterBox(chapBox, chapters, id);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const tabs = el('div', 'nui-pod-subtabs');
const body = el('div', 'nui-pod-notesbody');
const show = which => {
tabs.querySelectorAll('button').forEach(b => b.classList.toggle('active', b.dataset.which === which));
body.textContent = '';
if (which === 'chapters') {
const ol = el('ol', 'nui-pod-chapters');
chapters.forEach((c, i) => {
const li = el('li', 'nui-pod-chapter');
li.dataset.start = String(c.start || 0);
li.dataset.index = String(i);
li.append(el('span', 'nui-pod-chapter-t', clock(c.start || 0)), el('span', 'nui-pod-chapter-title', plain(c.title || `Kapitel ${i + 1}`)));
if (live) li.addEventListener('click', () => N.podcastSeek(Math.round((c.start || 0) * 1000)));
else if (jump) li.addEventListener('click', () => jump(c.start || 0));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
ol.append(li);
});
body.append(ol);
markChapter(box, (player.pos || 0) / 1000);
} else if (which === 'transcript') {
showTranscript(body, id, live);
} else {
const notes = el('div', 'nui-pod-notes');
notes.innerHTML = d.description || '<p>Keine Shownotes.</p>';
notes.querySelectorAll('a[href]').forEach(a => a.setAttribute('target', '_blank'));
if (live || jump) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
linkStamps(notes);
notes.addEventListener('click', ev => {
const a = ev.target.closest('a.nui-pod-stamp');
if (!a) return;
ev.preventDefault();
if (live) N.podcastSeek(parseInt(a.dataset.t, 10) * 1000);
else jump(parseInt(a.dataset.t, 10));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
});
}
body.append(notes);
}
};
const tab = (which, label) => {
const b = el('button', 'nui-pod-subtab', label);
b.type = 'button';
b.dataset.which = which;
b.addEventListener('click', () => show(which));
tabs.append(b);
};
if (chapters.length && !chapBox) tab('chapters', `Kapitel (${chapters.length})`);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
tab('notes', 'Shownotes');
if (d.has_transcript) tab('transcript', 'Transkript');
box.append(tabs, body);
show(chapters.length && !chapBox ? 'chapters' : 'notes');
}
// The chapters in the full player: which one plays, previous / next (like AntennaPod's
// chapter buttons), and the list to tap; it keeps the playing chapter in view.
function chapterBox(box, chapters, id) {
box.textContent = '';
box.dataset.episode = String(id);
box.hidden = !chapters.length;
// A smaller cover makes room for them.
if (box.parentElement) box.parentElement.classList.toggle('nui-pod-withchapters', !!chapters.length);
if (!chapters.length) return;
const jump = dir => {
const t = (player.pos || 0) / 1000;
let i = chapters.length - 1;
while (i > 0 && chapters[i].start > t + 0.5) i--;
// "Back" within the first seconds of a chapter goes to the one before.
const target = dir > 0 ? i + 1 : (t - chapters[i].start < 3 ? i - 1 : i);
if (target < 0 || target >= chapters.length) return;
N.podcastSeek(Math.round(chapters[target].start * 1000));
};
const head = el('div', 'nui-pod-chaphead');
head.append(el('span', 'nui-pod-chapcount', `${chapters.length} Kapitel`));
if (chapters.length > 1) {
head.append(button('⏮︎', 'Voriges Kapitel', () => jump(-1), 'nui-pod-chapbtn'),
button('⏭︎', 'Nächstes Kapitel', () => jump(1), 'nui-pod-chapbtn'));
}
const ol = el('ol', 'nui-pod-chapters nui-pod-chaplist');
chapters.forEach((c, i) => {
const li = el('li', 'nui-pod-chapter');
li.dataset.start = String(c.start || 0);
li.dataset.index = String(i);
li.append(el('span', 'nui-pod-chapter-t', clock(c.start || 0)), el('span', 'nui-pod-chapter-title', plain(c.title || `Kapitel ${i + 1}`)));
li.addEventListener('click', () => N.podcastSeek(Math.round((c.start || 0) * 1000)));
ol.append(li);
});
box.append(head, ol);
markChapter(box, (player.pos || 0) / 1000);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
function markChapter(scope, seconds) {
const items = scope.querySelectorAll('.nui-pod-chapter');
let current = -1;
items.forEach((li, i) => { if (parseFloat(li.dataset.start) <= seconds + 0.5) current = i; });
items.forEach((li, i) => li.classList.toggle('current', i === current));
const count = scope.querySelector('.nui-pod-chapcount');
const list = scope.querySelector('.nui-pod-chaplist');
if (count && list) {
const n = list.children.length;
count.textContent = current >= 0 ? `Kapitel ${current + 1} von ${n}` : `${n} Kapitel`;
// The playing chapter stays in view in the short list (unless it was just scrolled by hand).
if (current >= 0 && list.dataset.shown !== String(current) && Date.now() - (list.touched || 0) > 6000) {
list.dataset.shown = String(current);
const li = list.children[current];
list.scrollTop = Math.max(0, li.offsetTop - list.offsetTop - li.offsetHeight);
}
if (!list.watched) {
list.watched = true;
list.addEventListener('touchstart', () => { list.touched = Date.now(); }, {passive: true});
list.addEventListener('wheel', () => { list.touched = Date.now(); }, {passive: true});
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
markTranscript(scope, seconds);
}
// ---- transcript: follows the playback, a tap jumps there --------------------------------
const transcripts = new Map();
let transcriptTouched = 0; // the user scrolled the transcript: don't pull it along for a while
async function showTranscript(body, id, live) {
body.append(el('div', 'nui-pod-meta', 'Transkript wird geladen …'));
let t = transcripts.get(id);
if (!t) {
try {
t = await getJson(`/podcasts/episodes/${id}/transcript/`);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
transcripts.set(id, t);
} catch (e) {
body.textContent = '';
body.append(el('div', 'nui-pod-meta', navigator.onLine ? 'Für diese Folge gibt es kein lesbares Transkript.' : 'Das Transkript braucht eine Verbindung.'));
return;
}
}
body.textContent = '';
const list = el('div', 'nui-pod-transcript');
for (const seg of t.segments || []) {
const p = el('p', 'nui-pod-seg');
p.dataset.start = String(seg.start || 0);
if (seg.speaker) p.append(el('strong', 'nui-pod-speaker', seg.speaker + ': '));
p.append(document.createTextNode(seg.text || ''));
if (live) p.addEventListener('click', () => N.podcastSeek(Math.round((seg.start || 0) * 1000)));
list.append(p);
}
list.addEventListener('touchstart', () => { transcriptTouched = Date.now(); }, {passive: true});
body.append(list);
markTranscript(body, (player.pos || 0) / 1000);
}
function markTranscript(scope, seconds) {
const segs = scope.querySelectorAll('.nui-pod-seg');
if (!segs.length) return;
let current = null;
segs.forEach(p => { if (parseFloat(p.dataset.start) <= seconds + 0.3) current = p; });
segs.forEach(p => p.classList.toggle('current', p === current));
if (current && current.dataset.shown !== '1' && Date.now() - transcriptTouched > 8000) {
segs.forEach(p => { p.dataset.shown = ''; });
current.dataset.shown = '1';
current.scrollIntoView({block: 'center', behavior: 'smooth'});
}
}
// ---- the episode screen: a tap on a row opens it ----------------------------------------
// Cover, title, podcast (→ its page), date, length, where it stands; one big button to play;
// the actions with the swipes' names and icons; chapters (a tap plays from there) and notes.
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
let notesSheet = null;
let epScreen = null; // {e, ctx, actions, playBtn} while the episode screen is open
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function openEpisode(e, ctx) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
closeNotes();
ctx = ctx || {};
const {sheet: made, panel, top} = podSheet('nui-pod-notes-sheet nui-pod-episode', 'Schließen', closeNotes);
notesSheet = made;
const head = el('div', 'nui-pod-ephead');
head.append(art(e.art, 'big', e.feedArt, e.feed));
const info = el('div', 'nui-pod-main');
info.append(el('div', 'nui-pod-player-title', e.title));
const f = e.feedId ? feeds().find(x => x.id === e.feedId) : null;
if (f) {
const link = el('button', 'nui-pod-eplink', plain(f.title) + ' ›');
link.type = 'button';
link.title = 'Zur Podcast-Seite';
link.addEventListener('click', () => {
closeNotes();
closePlayer();
preview = null;
if (typeof showTab === 'function') showTab('podcasts');
openFeed(f);
});
info.append(link);
} else if (e.feed) {
info.append(el('div', 'nui-pod-player-feed', e.feed));
}
info.append(el('div', 'nui-pod-meta nui-pod-epfacts'));
head.append(info);
const playBtn = el('button', 'btn nui-pod-notes-play nui-pod-epplay');
playBtn.type = 'button';
playBtn.addEventListener('click', () => {
if (currentId() === e.id) { N.podcastToggle(); return; }
closeNotes();
play(e);
});
const actions = el('div', 'nui-pod-epactions');
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const box = el('div', 'nui-pod-notesbox');
panel.append(top, head, playBtn, actions, box);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
document.body.append(notesSheet);
epScreen = {e, ctx, actions, playBtn, facts: info.querySelector('.nui-pod-epfacts')};
episodeScreenParts();
showDetails(box, e.id, false, seconds => playAt(e, seconds));
}
// Start the episode at `seconds` (a chapter, a time in the notes).
function playAt(e, seconds) {
const ms = Math.round(seconds * 1000);
if (currentId() === e.id) {
N.podcastSeek(ms);
if (!player.playing) N.podcastToggle();
} else if (N.podcastPlayEpisodeAt) {
N.podcastPlayEpisodeAt(JSON.stringify(e.raw), currentId(), ms);
} else {
play(e);
}
closeNotes();
}
// The parts of the screen that follow the state: facts, the play button, the actions.
function episodeScreenParts() {
if (!epScreen || !notesSheet) return;
const {e, ctx, actions, playBtn, facts} = epScreen;
// The freshest place and queue state of the episode.
const q = queue().find(x => x.episode__id === e.id);
const pos = q ? q.position_seconds || 0 : e.raw.position_seconds || 0;
const played = !!e.raw.played;
const dur = e.duration || 0;
const parts = [];
if (e.date) parts.push(day(e.date));
if (dur) parts.push(minutes(shown(dur, e.feedId)));
if (played) parts.push('gehört');
else if (pos > 30 && dur) parts.push('noch ' + minutes(shown(Math.max(0, dur - pos), e.feedId)));
facts.textContent = parts.join(' · ');
const now = currentId() === e.id;
playBtn.textContent = now && player.playing ? '❚❚ Pause'
: !played && pos > 5 ? `▶ Weiter ab ${clock(pos)}` : '▶ Abspielen';
actions.textContent = '';
const act = (svg, icon, label, run, cls) => {
const b = el('button', 'nui-pod-epact' + (cls ? ' ' + cls : ''));
b.type = 'button';
b.append(svg ? svgIcon(svg, 22) : el('span', 'nui-pod-epact-icon', icon), el('span', null, label));
b.addEventListener('click', ev => { ev.stopPropagation(); run(); setTimeout(episodeScreenParts, 150); });
actions.append(b);
};
if (inQueue(e.id)) act('listx', '', 'Aus Warteschlange', () => { N.podcastDequeue(e.id); e.raw.in_queue = false; });
else act(null, '+', 'Einreihen', () => { enqueue(e); e.raw.in_queue = true; });
if (DL && (e.raw.audio_url || e.raw.episode__audio_url)) {
const d = downloads[e.id];
if (!d || d.state === 'failed') act(null, '↓', 'Laden', () => dlStart(e));
else if (d.state === 'done') act('trash', '', 'Datei löschen', () => dlDelete(e));
else act('trash', '', 'Download abbrechen', () => dlDelete(e, true));
}
if (N.podcastDiscard) {
act('trash', '', 'Folge löschen', () => { closeNotes(); discardOne(e, ctx.list || '', ctx.filters || []); }, 'nui-pod-epact-danger');
}
const fav = e.raw.favorite;
act(null, fav ? '★' : '☆', 'Favorit', () => toggleFavorite(e, star => { e.raw.favorite = star; episodeScreenParts(); }),
fav ? 'on' : '');
if (played) act(null, '↺', 'Ungehört', () => { N.podcastMarkPlayed(e.id, false); e.raw.played = false; });
else act(null, '✓', 'Gehört', () => { markPlayed(e); e.raw.played = true; e.raw.position_seconds = 0; });
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
function closeNotes() {
if (!notesSheet) return false;
notesSheet.remove();
notesSheet = null;
epScreen = null;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
return true;
}
// The full player: one for both. Which one it shows now.
let sheetKind = '';
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function openPlayer() {
if (remote()) { openRemotePlayer(); return; }
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (player.kind !== 'podcast') return;
closePlayer();
sheetKind = 'podcast';
const {sheet: made, panel, top} = podSheet('', 'Player schließen', closePlayer, 'Player');
sheet = made;
panel.append(top, art(player.artwork, 'player', null, player.feed));
const chapBox = el('div', 'nui-pod-chapbox');
chapBox.hidden = true;
panel.append(el('div', 'nui-pod-player-title'), el('div', 'nui-pod-player-feed'), chapBox);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const range = el('input', 'nui-pod-seek');
range.type = 'range';
range.min = 0;
range.step = 1;
range.id = 'nui-pod-seek';
range.addEventListener('input', () => {
seeking = true;
sheet.querySelector('.nui-pod-time-pos').textContent = clock(shown(range.value, 0, player.speed || 1));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
});
range.addEventListener('change', () => {
N.podcastSeek(range.value * 1000);
seeking = false;
});
const times = el('div', 'nui-pod-times');
times.append(el('span', 'nui-pod-time-pos'), el('span', 'nui-pod-time-left'));
panel.append(range, times);
const prefs = devicePrefs();
const back = prefs.skip_back || 15;
const fwd = prefs.skip_forward || 30;
const controls = el('div', 'nui-pod-controls');
controls.append(
button(String(back), `${back} Sekunden zurück`, () => N.podcastSkip(false), 'nui-pod-back'),
button('▶', 'Abspielen', () => N.podcastToggle(), 'nui-pod-play'),
button(String(fwd), `${fwd} Sekunden vor`, () => N.podcastSkip(true), 'nui-pod-fwd'),
);
panel.append(controls);
const extra = el('div', 'nui-pod-extra');
extra.append(...[
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
button('1×', 'Geschwindigkeit', speedMenu, 'nui-pod-speed'),
button('☾', 'Sleep-Timer', sleepMenu, 'nui-pod-sleep'),
button('☆', 'Favorit', () => toggleFavorite({id: player.episodeId}, star => {
const b = sheet && sheet.querySelector('.nui-pod-fav');
if (b) { b.textContent = star ? '★' : '☆'; b.classList.toggle('on', star); }
}), 'nui-pod-fav'),
button('⏭', keepSkipped() ? 'Nächste Folge (diese bleibt in der Warteschlange)' : 'Folge abschließen, nächste spielen',
finishAndNext, 'nui-pod-next'),
N.playerTransfer && button('⇄', 'Wiedergabe auf …', deviceMenu, 'nui-pod-devices'),
button('≡', 'Warteschlange', showQueue),
].filter(Boolean));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
panel.append(extra);
const notes = el('div', 'nui-pod-notesbox');
panel.append(notes);
document.body.append(sheet);
sheetEpisode = player.episodeId;
showDetails(notes, player.episodeId, true);
renderPlayer();
}
function closePlayer() {
if (!sheet) return false;
sheet.remove();
sheet = null;
return true;
}
// The radio in the same player: the station instead of the episode, "Live"
// instead of the seek bar, save / stop / sleep timer as the buttons.
function openRadioPlayer() {
const r = radioNow();
if (!r) return;
closePlayer();
sheetKind = 'radio';
const {sheet: made, panel, top} = podSheet('', 'Player schließen', closePlayer, 'Radio');
sheet = made;
const cover = art(r.logo, 'player');
cover.classList.add('nui-radio-art');
panel.append(top, cover, el('div', 'nui-pod-player-title'), el('div', 'nui-pod-player-feed'),
el('div', 'nui-radio-live'));
const controls = el('div', 'nui-pod-controls');
controls.append(
button('☆', 'Sender speichern', () => window.__dioraRadioSave && window.__dioraRadioSave(), 'nui-pod-fav nui-radio-save'),
button('▶', 'Abspielen', () => window.__dioraRadioToggle && window.__dioraRadioToggle(), 'nui-pod-play'),
button('☾', 'Sleep-Timer', () => sleepMenu(true), 'nui-pod-sleep'),
);
const extra = el('div', 'nui-pod-extra');
const stations = el('button', 'btn nui-radio-stations', 'Sender wechseln');
stations.type = 'button';
stations.addEventListener('click', () => { closePlayer(); showTab('radio'); });
extra.append(stations);
if (N.playerTransfer) extra.append(devicesButton());
panel.append(controls, extra);
document.body.append(sheet);
renderRadioPlayer();
}
function renderRadioPlayer() {
const r = radioNow();
if (!r) { closePlayer(); return; }
sheet.querySelector('.nui-pod-player-title').textContent = r.name;
sheet.querySelector('.nui-pod-player-feed').textContent = r.track || '';
const live = sheet.querySelector('.nui-radio-live');
live.textContent = r.buffering ? 'Verbinde …' : r.playing ? '● Live' : 'Angehalten';
live.classList.toggle('on', r.playing && !r.buffering);
const p = sheet.querySelector('.nui-pod-play');
p.textContent = r.playing ? '■' : '▶';
p.title = r.playing ? 'Stopp' : 'Abspielen';
const s = sheet.querySelector('.nui-radio-save');
s.textContent = r.saved ? '★' : '☆';
s.classList.toggle('on', r.saved);
s.disabled = r.saved;
s.title = r.saved ? 'Gespeichert' : 'Sender speichern';
const sl = sheet.querySelector('.nui-pod-sleep');
sl.textContent = sleepLabel() || '☾';
sl.classList.toggle('on', !!sleepLabel());
setArt(sheet.querySelector('.nui-pod-art-player'), r.logo);
}
// native-radio.js: the station, its track or its state changed.
window.__dioraRadioChanged = function () {
renderMini();
if (sheet && sheetKind === 'radio') renderRadioPlayer();
};
window.__dioraOpenPlayer = kind => (kind === 'radio' && !remote() ? openRadioPlayer() : openPlayer());
// Home (diora-web static/js/home.js): one of the lists, e.g. "inbox" for the new episodes.
window.__dioraPodcastView = function (id) {
if (VIEW_IDS.indexOf(id) < 0) return;
selected = null;
feedView = null;
preview = null;
view = id;
try { localStorage.setItem('nui_pod_view', view); } catch (e) {}
render();
scrollTop(0);
};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function renderPlayer() {
if (!sheet) return;
const r = remote();
if (sheetKind === 'remote') {
// Back here (taken over) or the other device went away: this device's player.
if (!r) {
closePlayer();
if (player.kind === 'podcast') openPlayer(); else if (radioNow()) openRadioPlayer();
} else if (sheet.dataset.remote !== remoteKey(r)) {
openRemotePlayer();
} else {
renderRemotePlayer();
}
return;
}
if (r) { openRemotePlayer(); return; }
if (sheetKind === 'radio') {
if (player.kind === 'podcast') closePlayer(); else renderRadioPlayer();
return;
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (player.kind !== 'podcast') { closePlayer(); return; }
if (sheetEpisode !== player.episodeId) {
// The next episode started while the player was open.
sheetEpisode = player.episodeId;
showDetails(sheet.querySelector('.nui-pod-notesbox'), player.episodeId, true);
}
markChapter(sheet, (player.pos || 0) / 1000);
sheet.querySelector('.nui-pod-player-title').textContent = player.title || '';
sheet.querySelector('.nui-pod-player-feed').textContent = bookLine(player) || player.feed || '';
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const range = sheet.querySelector('.nui-pod-seek');
const dur = Math.round((player.dur || 0) / 1000);
const pos = Math.round((player.pos || 0) / 1000);
range.max = dur || 1;
// At the speed, when so set: the place and what's left as long as they take.
const at = s => shown(s, 0, player.speed || 1);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (!seeking) {
range.value = pos;
sheet.querySelector('.nui-pod-time-pos').textContent = clock(at(pos));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
sheet.querySelector('.nui-pod-time-left').textContent = dur ? '−' + clock(at(dur - pos)) : '';
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const p = sheet.querySelector('.nui-pod-play');
p.textContent = player.playing ? '❚❚' : '▶';
p.title = player.playing ? 'Pause' : 'Abspielen';
const sp = sheet.querySelector('.nui-pod-speed');
sp.textContent = speedLabel(player.speed || 1) + (player.podcastSpeed ? '•' : '');
sp.title = player.podcastSpeed ? 'Geschwindigkeit (eigene für diesen Podcast)' : 'Geschwindigkeit';
const sl = sheet.querySelector('.nui-pod-sleep');
sl.textContent = sleepLabel() || '☾';
sl.classList.toggle('on', !!sleepLabel());
const img = sheet.querySelector('.nui-pod-art-player img');
const src = (player.artwork || '').replace(/^http:/, 'https:');
if (img && src && img.getAttribute('src') !== src) img.src = src;
}
function speedLabel(s) {
return String(Math.round(s * 100) / 100).replace('.', ',') + '×';
}
// Speed: for all podcasts, or (AntennaPod-like) remembered for this podcast.
// The presets, and the speed now if it isn't one of them (a podcast's own 3,5× …).
function speedChoices(now) {
const list = presets().slice();
if (now > 0 && !list.some(s => Math.abs(s - now) < 0.01)) list.push(now);
return list.sort((a, b) => a - b);
}
function presetsEditItem() {
return 'speed_presets' in settings() ? [{icon: '✎', label: 'Voreinstellungen bearbeiten', hint: 'hinzufügen, entfernen',
action: () => presetsEditor()}] : [];
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function speedMenu() {
const own = !!player.podcastSpeed;
const pick = forPodcast => openSheet(forPodcast ? `Geschwindigkeit für „${plain(player.feed)}“` : 'Geschwindigkeit für alle Podcasts',
speedChoices(own === forPodcast ? player.speed || 1 : 0).map(s => ({
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
icon: Math.abs((player.speed || 1) - s) < 0.01 && own === forPodcast ? '●' : '○',
label: speedLabel(s),
action: () => (forPodcast ? N.podcastSpeedForPodcast(s) : N.podcastSpeed(s)),
})).concat(forPodcast && own ? [{icon: '↺', label: 'Wie alle anderen Podcasts', action: () => N.podcastSpeedForPodcast(0)}] : [])
.concat(presetsEditItem(), silenceSpeedItems()));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (!N.podcastSpeedForPodcast || !player.feedId) { pick(false); return; }
openSheet('Geschwindigkeit', [
{icon: '◎', label: 'Für alle Podcasts', hint: own ? null : speedLabel(player.speed || 1), action: () => pick(false)},
{icon: '◉', label: `Nur für „${plain(player.feed)}“`, hint: own ? speedLabel(player.speed || 1) : 'noch keine eigene',
action: () => pick(true)},
].concat(presetsEditItem(), silenceSpeedItems()));
}
// „Stille überspringen“ where AntennaPod has it: in the speed menu. For all podcasts (this
// device), and for the playing one its own choice (server) — where the player can skip silence.
function silenceSpeedItems() {
const prefs = devicePrefs();
if (!N.podcastSetPref || !('skip_silence' in prefs)) return [];
const device = !!prefs.skip_silence;
const live = player.feedId ? feeds().find(x => x.id === player.feedId) : null;
const mine = live && 'skip_silence' in live ? live.skip_silence : undefined;
const items = [{icon: device ? '☑' : '☐', label: 'Stille überspringen',
hint: (device ? 'an' : 'aus') + ' · alle Podcasts' + (mine === true ? ' · hier: immer' : mine === false ? ' · hier: nie' : ''),
action: () => { N.podcastSetPref('skip_silence', !device); toast(device ? 'Stille wird nicht mehr übersprungen' : 'Stille wird übersprungen'); }}];
if (mine !== undefined && N.podcastFeedSettings) {
const title = plain(player.feed);
const save = v => N.podcastFeedSettings(player.feedId, JSON.stringify({skip_silence: v}));
items.push({icon: '∿', label: `Stille bei „${title}“`, hint: mine === true ? 'immer überspringen' : mine === false ? 'nie überspringen' : 'wie alle Podcasts',
action: () => openSheet(`Stille bei „${title}“ …`, [
{icon: mine == null ? '●' : '○', label: 'Wie alle Podcasts', hint: device ? 'überspringen' : 'nicht überspringen', action: () => save(null)},
{icon: mine === true ? '●' : '○', label: 'Immer überspringen', action: () => save(true)},
{icon: mine === false ? '●' : '○', label: 'Nie überspringen', action: () => save(false)},
])});
}
return items;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
// ---- sleep timer (runs in the app, also with the screen off; episodes and the radio) ----
// The timer that counts: the other device's while that one is the active one, else ours
// (`local`: ours anyway — Ambient's button, Ambient plays here).
function sleepState(local) {
const r = local ? null : remote();
return (r ? r.sleep : player.sleep) || {};
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function sleepLabel(local) {
const t = sleepState(local);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (t.episodes > 0) return t.episodes === 1 ? '☾ Folgenende' : `☾ ${t.episodes} Folgen`;
if (t.leftMs > 0) return `☾ ${Math.max(1, Math.ceil(t.leftMs / 60000))} min`;
return '';
}
const SLEEP_MINUTES = [5, 10, 15, 20, 30, 45, 60, 90];
const SLEEP_MAX = 600;
// A typed-in time: "25", "25 min", "1:30", "1,5 h" → minutes (1 to 600), else null.
function parseSleepMinutes(text) {
const t = String(text || '').trim().toLowerCase().replace(',', '.').replace(/\s*(minuten|min|m)$/, '');
let m;
const hm = /^(\d+):(\d{1,2})$/.exec(t);
const h = /^(\d+(?:\.\d+)?)\s*(h|std|stunden?)$/.exec(t);
if (hm) m = Number(hm[2]) < 60 ? Number(hm[1]) * 60 + Number(hm[2]) : NaN;
else if (h) m = Math.round(Number(h[1]) * 60);
else m = /^\d+(\.\d+)?$/.test(t) ? Math.round(Number(t)) : NaN;
return m >= 1 && m <= SLEEP_MAX ? m : null;
}
function sleepMinutesLabel(m) {
if (m === 1) return '1 Minute';
return m < 60 ? `${m} Minuten` : `${Math.floor(m / 60)} h` + (m % 60 ? ` ${m % 60} min` : '');
}
function hourOf(v, fallback) {
return Number.isInteger(v) && v >= 0 && v <= 23 ? v : fallback;
}
function sleepWindow() {
const p = devicePrefs();
const from = hourOf(p.sleep_from, 22);
const to = hourOf(p.sleep_to, 6);
return from === to ? 'ganztags' : `${from}–${to} Uhr`;
}
// Minutes count only while playing; shaking the phone starts them over. With another
// device playing, the timer is that device's (set there by the player sync).
function sleepMenu(radio, local) {
const r = local ? null : remote();
const set = r ? (m => remoteCmd('sleep', m)) : N.podcastSleep ? (m => N.podcastSleep(m)) : null;
if (!set) return;
const onRadio = r ? r.kind === 'radio' : radio === true;
const t = sleepState(local);
const own = player.sleep || {};
const on = !!sleepLabel(local);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const items = [];
if (r) items.push({custom: el('div', 'nui-pod-meta nui-pod-devnote', `Der Sleep-Timer gilt für ${remoteName(r)}.`)});
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (on) {
items.push({icon: '✕', label: 'Ausschalten', hint: sleepLabel(local).replace('☾ ', 'läuft: '), action: () => set(0)});
if (t.leftMs > 0) {
for (const m of [5, 10]) {
items.push({icon: '+', label: `${m} Minuten länger`, action: () => (!r && N.podcastSleepExtend
? N.podcastSleepExtend(m) : set(Math.min(SLEEP_MAX, Math.ceil(t.leftMs / 60000) + m)))});
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
}
for (const m of SLEEP_MINUTES) {
items.push({icon: '☾', label: `${m} Minuten`, hint: !r && m === own.last && !on ? 'zuletzt' : null, action: () => set(m)});
}
if (typeof customPrompt === 'function') {
// A time of one's own that was set last comes back as its own line.
const lastOwn = !r && own.last && !SLEEP_MINUTES.includes(own.last) ? own.last : 0;
if (lastOwn) items.push({icon: '☾', label: sleepMinutesLabel(lastOwn), hint: on ? null : 'zuletzt', action: () => set(lastOwn)});
items.push({icon: '✎', label: 'Eigene Zeit …', hint: 'z. B. 25, 2 h oder 1:30', action: () => customSleep(set)});
}
if (!onRadio) {
items.push({icon: '⏹', label: 'Am Ende der Folge', action: () => set(-1)});
items.push({icon: '⏹', label: 'Nach 2 Folgen', action: () => set(-2)});
items.push({icon: '⏹', label: 'Nach 3 Folgen', action: () => set(-3)});
}
if (!r && N.podcastSleepAuto) {
items.push({icon: own.auto ? '☑' : '☐', label: 'Nachts von selbst',
hint: own.auto ? `an: ${sleepWindow()}, ${sleepMinutesLabel(own.last || 15)}, sobald etwas startet`
: `aus · ${sleepWindow()}`,
action: () => {
N.podcastSleepAuto(!own.auto);
own.auto = !own.auto;
toast(own.auto ? `Sleep-Timer nachts (${sleepWindow()}) von selbst` : 'Sleep-Timer nachts: aus');
}});
items.push({icon: '⚙', label: 'Einstellungen', hint: sleepSettingsHint(), action: sleepSettings});
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
openSheet('Sleep-Timer', items);
}
async function customSleep(set) {
const v = await customPrompt('Sleep-Timer: wie lange? (Minuten, z. B. 25, 2 h oder 1:30)', '');
if (v == null || !String(v).trim()) return;
const m = parseSleepMinutes(v);
if (!m) { toast('Bitte 1 Minute bis 10 Stunden, z. B. 25, 2 h oder 1:30'); return; }
set(m);
toast(`Sleep-Timer: ${sleepMinutesLabel(m)}`);
}
function sleepSettingsHint() {
const p = devicePrefs();
const parts = [`nachts = ${sleepWindow()}`];
if ('sleep_shake' in p) parts.push(p.sleep_shake ? 'schütteln an' : 'schütteln aus');
if (p.sleep_vibrate) parts.push('vibrieren');
return parts.join(' · ');
}
// The night window and, in the app, shaking and buzzing.
function sleepSettings() {
const p = devicePrefs();
const from = hourOf(p.sleep_from, 22);
const to = hourOf(p.sleep_to, 6);
const again = () => setTimeout(sleepSettings, 60);
const setHour = (key, h) => { if (N.podcastSetPrefValue) N.podcastSetPrefValue(key, h); again(); };
const items = [
{icon: '◐', label: 'Nachts beginnt um', hint: `${from} Uhr`,
action: () => hourPicker('Nachts beginnt um …', from, h => setHour('sleep_from', h))},
{icon: '◑', label: 'Nachts endet um', hint: `${to} Uhr`,
action: () => hourPicker('Nachts endet um …', to, h => setHour('sleep_to', h))},
];
if ('sleep_shake' in p && N.podcastSetPref) {
items.push({icon: p.sleep_shake ? '☑' : '☐', label: 'Schütteln startet die Zeit neu',
hint: p.sleep_shake ? 'an: Handy schütteln, und die Minuten laufen von vorn' : 'aus',
action: () => { N.podcastSetPref('sleep_shake', !p.sleep_shake); again(); }});
}
if ('sleep_vibrate' in p && N.podcastSetPref) {
items.push({icon: p.sleep_vibrate ? '☑' : '☐', label: 'Vor dem Ende vibrieren',
hint: p.sleep_vibrate ? 'an: kurz, wenn die letzten 10 s leiser werden' : 'aus',
action: () => { N.podcastSetPref('sleep_vibrate', !p.sleep_vibrate); again(); }});
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
openSheet('Sleep-Timer: Einstellungen', items);
}
function hourPicker(title, now, done) {
const hours = [];
for (let i = 0; i < 24; i++) hours.push((18 + i) % 24);
openSheet(title, hours.map(h => ({icon: h === now ? '●' : '○', label: `${h} Uhr`, action: () => done(h)})));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
// ⏭: the next episode plays. The host decides about this one ("keep skipped"): kept
// unplayed in the queue, or played. With nothing after it, it only counts as played
// when skipped episodes aren't kept.
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function finishAndNext() {
const id = currentId();
if (!id) return;
const ids = queue().map(q => q.episode__id);
const at = ids.indexOf(id);
const last = at < 0 ? !ids.length : at === ids.length - 1;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
N.podcastNext();
if (last && !keepSkipped()) N.podcastMarkPlayed(id, true);
else if (last) toast('Das ist die letzte Folge der Warteschlange');
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
function showQueue() {
closePlayer();
view = 'queue';
feedView = null;
showTab('podcasts');
render();
}
// ---- another device plays (player sync, like Spotify Connect) ------------------------------
// The host (Bridge.kt / podcasts-web.js) shares one player among the user's
// devices. While another device is the active one (online, playing or
// paused), player.remote is what it plays: the bar and the player show it,
// their buttons control it (N.playerRemote), "Hier abspielen" plays it here
// from its place, and the device sheet moves it to any device that is online.
function remote() {
return player.remote && N.playerRemote ? player.remote : null;
}
function remoteName(r) {
return r.device || 'einem anderen Gerät';
}
function remoteKey(r) {
return r.kind + ':' + (r.kind === 'radio' ? (r.station && r.station.url) || '' : r.episodeId);
}
function remoteLine(r) {
return (r.playing ? '▸ läuft auf ' : '‖ pausiert auf ') + remoteName(r);
}
function remoteCmd(action, value) {
if (N.playerRemote) N.playerRemote(action, Math.round(value || 0));
}
function remoteToggle() {
const r = remote();
if (r) remoteCmd(r.playing ? 'pause' : 'play');
}
function takeOver() {
if (N.playerTakeOver) N.playerTakeOver();
}
// Which device plays: the ones online, this one first; choosing one moves the playback there.
function deviceMenu() {
if (!N.playerTransfer) return;
const all = (player.devices || []).filter(d => d.online || d.me);
const devices = all.filter(d => d.me).concat(all.filter(d => !d.me));
const r = remote();
const radio = r ? null : radioNow();
const activePlays = r ? r.playing : player.kind === 'podcast' ? !!player.playing : !!(radio && radio.playing);
const items = devices.map(d => ({
icon: d.active ? '◉' : '○',
label: d.me ? 'Dieses Gerät' : d.name || 'Unbekanntes Gerät',
hint: d.active ? (activePlays ? 'spielt gerade' : 'pausiert') : d.me ? null : 'hierhin verschieben',
active: !!d.active,
action: () => N.playerTransfer(d.id),
}));
if (devices.length < 2) {
items.push({custom: el('div', 'nui-pod-meta nui-pod-devnote',
'Keine anderen Geräte online. Sobald diora auf einem anderen Gerät offen ist, erscheint es hier.')});
}
openSheet('Wiedergabe auf …', items);
}
function devicesButton() {
const b = el('button', 'btn nui-pod-devbtn', '⇄ Wiedergabe auf …');
b.type = 'button';
b.addEventListener('click', deviceMenu);
return b;
}
// The full player for what another device plays: the same layout, remote buttons.
function openRemotePlayer() {
const r = remote();
if (!r) return;
closePlayer();
sheetKind = 'remote';
const onRadio = r.kind === 'radio';
const {sheet: made, panel, top} = podSheet('nui-pod-remote', 'Player schließen', closePlayer, 'Player');
sheet = made;
sheet.dataset.remote = remoteKey(r);
const cover = art(onRadio ? r.station && r.station.logo : r.artwork, 'player');
if (onRadio) cover.classList.add('nui-radio-art');
panel.append(top, cover, el('div', 'nui-pod-player-title'), el('div', 'nui-pod-player-feed nui-pod-remote-line'));
const controls = el('div', 'nui-pod-controls');
if (onRadio) {
controls.append(button('▶', 'Abspielen', remoteToggle, 'nui-pod-play'));
} else {
panel.append(el('div', 'nui-pod-player-feed nui-pod-remote-feed'));
const range = el('input', 'nui-pod-seek');
range.type = 'range';
range.min = 0;
range.step = 1;
range.addEventListener('input', () => {
seeking = true;
sheet.querySelector('.nui-pod-time-pos').textContent = clock(range.value);
});
range.addEventListener('change', () => {
remoteCmd('seek', range.value * 1000);
seeking = false;
});
const times = el('div', 'nui-pod-times');
times.append(el('span', 'nui-pod-time-pos'), el('span', 'nui-pod-time-left'));
panel.append(range, times);
const prefs = devicePrefs();
const back = prefs.skip_back || 15;
const fwd = prefs.skip_forward || 30;
controls.append(
button(String(back), `${back} Sekunden zurück`, () => remoteCmd('skip', -back), 'nui-pod-back'),
button('▶', 'Abspielen', remoteToggle, 'nui-pod-play'),
button(String(fwd), `${fwd} Sekunden vor`, () => remoteCmd('skip', fwd), 'nui-pod-fwd'),
);
}
panel.append(controls);
if (N.playerTakeOver) {
const here = el('button', 'btn nui-pod-here', '⇩ Hier abspielen');
here.type = 'button';
here.addEventListener('click', takeOver);
panel.append(here);
}
const extra = el('div', 'nui-pod-extra');
if (!onRadio) extra.append(button('⏭', 'Nächste Folge', () => remoteCmd('next'), 'nui-pod-next'));
extra.append(button('☾', `Sleep-Timer auf ${remoteName(r)}`, () => sleepMenu(onRadio), 'nui-pod-sleep'));
if (N.playerTransfer) extra.append(onRadio ? devicesButton() : button('⇄', 'Wiedergabe auf …', deviceMenu, 'nui-pod-devices'));
if (!onRadio) extra.append(button('≡', 'Warteschlange', showQueue));
panel.append(extra);
if (!onRadio && r.episodeId) {
const notes = el('div', 'nui-pod-notesbox');
panel.append(notes);
showDetails(notes, r.episodeId, false);
}
document.body.append(sheet);
renderRemotePlayer();
}
function renderRemotePlayer() {
const r = remote();
if (!r || !sheet) return;
const onRadio = r.kind === 'radio';
sheet.querySelector('.nui-pod-player-title').textContent = (onRadio ? r.station && r.station.name : r.title) || '';
sheet.querySelector('.nui-pod-remote-line').textContent = remoteLine(r);
const p = sheet.querySelector('.nui-pod-play');
p.textContent = r.playing ? (onRadio ? '■' : '❚❚') : '▶';
p.title = (r.playing ? (onRadio ? 'Stopp' : 'Pause') : 'Abspielen') + ' auf ' + remoteName(r);
const sl = sheet.querySelector('.nui-pod-sleep');
sl.textContent = sleepLabel() || '☾';
sl.classList.toggle('on', !!sleepLabel());
if (onRadio) return;
sheet.querySelector('.nui-pod-remote-feed').textContent = r.feed || '';
const range = sheet.querySelector('.nui-pod-seek');
const dur = Math.round((r.dur || 0) / 1000);
const pos = Math.round((r.pos || 0) / 1000);
range.max = dur || 1;
if (!seeking) {
range.value = pos;
sheet.querySelector('.nui-pod-time-pos').textContent = clock(pos);
}
sheet.querySelector('.nui-pod-time-left').textContent = dur ? '−' + clock(dur - pos) : '';
markChapter(sheet, pos);
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
// ---- downloads (Downloads.kt) ------------------------------------------------------------
// Episodes kept on the phone to play without a connection. Each episode row
// gets a mark (↓ fetch it, a ring while it loads, ✓ when it's here), the ⋯
// menus and the settings sheet get their entries. The app downloads the
// first episodes of the queue by itself and tells us via __dioraDownloads.
// How many of the queue's first episodes load by themselves; 0 = all of them.
const DL_COUNTS = [1, 2, 3, 5, 10, 0];
const dlCountLabel = n => (n === 0 ? 'Alle' : n === 1 ? '1 Folge' : n + ' Folgen');
const DL_PARALLEL = [1, 2, 3, 4];
// Why a download waits (DownloadPlan.waitReason).
const DL_REASONS = {
paused: 'pausiert', wifi: 'wartet auf WLAN', offline: 'wartet auf eine Verbindung', queue: 'in der Reihe',
retry: 'neuer Versuch',
};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
// The limit and the clean-up the AntennaPod way (Downloads.kt, DownloadPlan.toClean).
const DL_LIMITS = [5, 10, 25, 50, 100, 500, 0];
const DL_CLEANUPS = [
{value: 'queue', label: 'Wenn Platz gebraucht wird',
hint: 'beim Limit gehen Folgen, die nicht in der Warteschlange stehen: gehörte zuerst'},
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
{value: 'never', label: 'Nie', hint: 'beim Limit lädt die App nichts mehr von selbst'},
];
const dlLimitLabel = n => (n ? n + ' Folgen' : 'Unbegrenzt');
function size(bytes) {
if (bytes >= 1073741824) return (bytes / 1073741824).toFixed(1).replace('.', ',') + ' GB';
return Math.max(1, Math.round((bytes || 0) / 1048576)) + ' MB';
}
function dlStart(e) {
N.podcastDownload(JSON.stringify(e.raw));
window.__dioraDownloads(Object.assign({}, downloads,
{[e.id]: {state: 'queued', progress: 0, bytes: 0, total: 0, title: e.title, reason: 'queue'}}));
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
toast('Wird heruntergeladen');
}
function dlDelete(e, running, quiet) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
N.podcastDeleteDownload(e.id);
// "Löschen nimmt aus der Warteschlange" (account): the app takes it out too.
const out = !running && deleteDequeues(e.id);
if (!quiet) toast(running ? 'Download abgebrochen' : out ? 'Datei gelöscht · auch aus Warteschlange' : 'Datei gelöscht');
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
// The row's download button (the episode lists): ↓, a ring while it loads, ✓ when it's here.
function dlButton(e) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const d = downloads[e.id];
const state = d ? d.state : null;
let b;
if (state === 'done') {
b = button('✓', 'Heruntergeladen', () => openSheet(e.title, dlMenuItems(e)), 'nui-pod-dl nui-pod-dl-done');
} else if (state === 'queued' || state === 'downloading') {
b = button('', state === 'queued' ? 'Wartet auf den Download' : 'Wird heruntergeladen',
() => openSheet(e.title, dlMenuItems(e)), 'nui-pod-dl nui-pod-dl-ring');
b.append(el('span'));
} else {
b = button('↓', state === 'failed' ? 'Download hat nicht geklappt — nochmal' : 'Herunterladen', () => dlStart(e),
'nui-pod-dl' + (state === 'failed' ? ' nui-pod-dl-failed' : ''));
}
b.dataset.dl = e.id;
dlRing(b);
return b;
}
// Elsewhere a small mark in front of the length: an arrow in a circle when it's here,
// a filling ring while it loads, ! when it failed.
function dlMark(e) {
if (!DL) return null;
const d = downloads[e.id];
if (!d) return null;
const m = el('span', 'nui-pod-dlmark nui-pod-dlmark-' + d.state);
m.dataset.dlmark = e.id;
m.title = d.state === 'done' ? 'Heruntergeladen' : d.state === 'failed' ? 'Download hat nicht geklappt' : 'Wird heruntergeladen';
m.textContent = d.state === 'done' ? '↓' : d.state === 'failed' ? '!' : '';
dlRing(m);
return m;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
function dlRing(b) {
const d = downloads[b.dataset.dl || b.dataset.dlmark];
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (!d || (d.state !== 'queued' && d.state !== 'downloading')) return;
b.style.setProperty('--p', (d.progress || 0) + '%');
if (b.dataset.dl) b.firstChild.textContent = d.state === 'queued' ? (d.reason === 'paused' ? '‖' : '…') : d.progress + '';
}
// "wartet auf WLAN", "neuer Versuch um 14:05" …
function dlReason(d) {
if (d.reason === 'retry' && d.retryAt) {
return 'neuer Versuch um ' + new Date(d.retryAt).toLocaleTimeString('de-DE', {hour: '2-digit', minute: '2-digit'});
}
return DL_REASONS[d.reason] || 'wartet';
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
function dlMenuItems(e) {
if (!DL || !(e.raw.episode__audio_url || e.raw.audio_url)) return [];
const d = downloads[e.id];
if (!d || d.state === 'failed') {
return [{icon: '↓', label: 'Herunterladen', hint: d ? 'hat eben nicht geklappt (' + (d.error || '?') + ')' : 'zum Hören ohne Verbindung',
action: () => dlStart(e)}];
}
if (d.state === 'done') {
return [{icon: '✕', svg: 'trash', label: deleteDequeues(e.id) ? 'Datei löschen · auch aus der Warteschlange' : 'Datei löschen',
hint: 'nur die Datei auf diesem Gerät · ' + size(d.bytes), action: () => dlDelete(e)}];
}
return [
d.state === 'queued' && d.order > 0 && N.podcastDownloadFirst &&
{icon: '⇈', label: 'Zuerst laden', hint: 'vor die anderen wartenden', action: () => N.podcastDownloadFirst(e.id)},
{icon: '✕', label: 'Download abbrechen', hint: d.state === 'queued' ? dlReason(d) : d.progress + ' %',
action: () => dlDelete(e, true)},
];
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
function dlSet(changes) {
Object.assign(dlSettings, changes);
N.podcastSetDownloadSettings(JSON.stringify(changes));
settingsMenu();
}
function dlSettingsItems() {
if (!DL) return [];
const s = dlSettings = parse(N.podcastDownloadSettings(), dlSettings);
const done = Object.values(downloads).filter(d => d.state === 'done');
const bytes = done.reduce((n, d) => n + (d.bytes || 0), 0);
return [
{icon: s.auto ? '☑' : '☐', label: 'Automatisch herunterladen',
hint: s.auto ? (s.count === 0 ? 'die ganze Warteschlange' : `die ersten ${s.count} Folgen der Warteschlange`) : 'aus',
action: () => dlSet({auto: !s.auto})},
s.auto && {icon: '#', label: 'Wie viele Folgen', hint: dlCountLabel(s.count), action: () => openSheet('Automatisch herunterladen',
DL_COUNTS.map(n => ({icon: s.count === n ? '●' : '○', label: dlCountLabel(n), hint: n === 0 ? 'die ganze Warteschlange' : null,
active: s.count === n,
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
action: () => dlSet({count: n})})))},
s.auto && {icon: s.mobile ? '☑' : '☐', label: 'Auch über mobile Daten', hint: s.mobile ? 'nicht nur im WLAN' : 'sonst nur im WLAN',
action: () => dlSet({mobile: !s.mobile})},
s.parallel != null && {icon: '⇉', label: 'Gleichzeitig laden', hint: s.parallel === 1 ? '1 Folge' : s.parallel + ' Folgen',
action: () => openSheet('Gleichzeitig laden', DL_PARALLEL.map(n => ({
icon: s.parallel === n ? '●' : '○', label: n === 1 ? '1 Folge' : n + ' Folgen', active: s.parallel === n,
hint: n === 3 ? 'Standard' : null, action: () => dlSet({parallel: n})})))},
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
{icon: s.deletePlayed ? '☑' : '☐', label: 'Gehörte Folgen löschen',
hint: 'sobald die Folge zu Ende ist – nicht bei Favoriten',
action: () => dlSet({deletePlayed: !s.deletePlayed})},
s.limit != null && {icon: '▦', label: 'Höchstens behalten',
hint: dlLimitLabel(s.limit) + ` · jetzt ${Object.values(downloads).filter(d => d.state !== 'failed').length}`,
action: () => openSheet('Höchstens so viele Folgen auf dem Telefon', DL_LIMITS.map(n => ({
icon: s.limit === n ? '●' : '○', label: dlLimitLabel(n), active: s.limit === n,
hint: n === 25 ? 'Standard' : null, action: () => dlSet({limit: n})})))},
'enqueue' in s && {icon: s.enqueue ? '☑' : '☐', label: 'Heruntergeladene Folgen einreihen',
hint: s.enqueue ? 'an: was geladen wird, kommt in die Warteschlange' : 'aus',
action: () => dlSet({enqueue: !s.enqueue})},
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
s.cleanup && {icon: '♻', label: 'Aufräumen', hint: (DL_CLEANUPS.find(c => c.value === s.cleanup) || DL_CLEANUPS[0]).label,
action: () => openSheet('Downloads aufräumen', DL_CLEANUPS.map(c => ({
icon: s.cleanup === c.value ? '●' : '○', label: c.label, hint: c.hint, active: s.cleanup === c.value,
action: () => dlSet({cleanup: c.value})})))},
done.length > 0 && {icon: '✕', svg: 'trash', label: `Alle Dateien löschen (${size(bytes)})`,
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
hint: done.length === 1 ? '1 Folge' : done.length + ' Folgen',
action: () => openSheet(`Alle ${done.length} Dateien löschen?`, [
{icon: '✕', svg: 'trash', label: 'Alle Dateien löschen', hint: size(bytes), danger: true,
action: () => { N.podcastDeleteAllDownloads(); toast('Dateien gelöscht'); }},
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
{icon: '‹', label: 'Abbrechen', action: () => {}},
])},
];
}
window.__dioraDownloads = function (map, info) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const before = downloads;
downloads = (typeof map === 'string' ? parse(map, downloads) : map) || {};
if (info) dlInfo = typeof info === 'string' ? parse(info, dlInfo) : info;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const keys = Object.keys(downloads);
const same = keys.length === Object.keys(before).length &&
keys.every(id => before[id] && before[id].state === downloads[id].state && before[id].reason === downloads[id].reason);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
// Only the numbers moved: turn the rings, no new list (keeps a drag or a scroll going).
if (same) {
document.querySelectorAll('#nui-pod [data-dl], #nui-pod [data-dlmark]').forEach(dlRing);
dlIndicatorText();
} else {
render();
}
renderDownloads();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
};
function dlCounts() {
const c = {running: 0, waiting: 0, failed: 0, done: 0, bytes: 0};
for (const d of Object.values(downloads)) {
if (d.state === 'downloading') c.running++;
else if (d.state === 'queued') c.waiting++;
else if (d.state === 'failed') c.failed++;
else if (d.state === 'done') { c.done++; c.bytes += d.bytes || 0; }
}
return c;
}
function dlSummary() {
const c = dlCounts();
const parts = [];
if (c.running) parts.push(c.running + (c.running === 1 ? ' läuft' : ' laufen'));
if (c.waiting) parts.push(c.waiting + (c.waiting === 1 ? ' wartet' : ' warten') + (dlInfo.paused ? ' (pausiert)' : ''));
if (c.failed) parts.push(c.failed + ' fehlgeschlagen');
if (c.done) parts.push(c.done + ' fertig');
return parts.join(' · ') || 'keine';
}
// The top bar's sign while downloads run or wait: "↓ 3/68" (the one loading of all in this round,
// like the notification), "‖ 68" while paused. Opens the overview.
function dlIndicator() {
if (!DL || !N.podcastDownloadsInfo) return null;
const b = button('', 'Downloads', openDownloads, 'nui-pod-dlind');
b.id = 'nui-pod-dlind';
dlIndicatorText(b);
return b;
}
function dlIndicatorText(b) {
b = b || $id('nui-pod-dlind');
if (!b) return;
const c = dlCounts();
const busy = c.running + c.waiting;
b.hidden = !busy;
if (!busy) return;
const total = Math.max(dlInfo.total || 0, busy);
const done = Math.min(dlInfo.done || 0, total);
b.textContent = dlInfo.paused ? '‖ ' + busy : c.running ? `↓ ${Math.min(done + 1, total)}/${total}` : '↓ ' + busy;
b.title = 'Downloads: ' + dlSummary();
b.setAttribute('aria-label', b.title);
}
// ---- the downloads overview: what runs, waits, failed, is done ---------------------------
let dlSheet = null;
let dlBody = null;
function closeDownloads() {
if (!dlSheet) return false;
dlSheet.remove();
dlSheet = dlBody = null;
return true;
}
function openDownloads() {
if (!DL) return;
closeDownloads();
const {sheet: made, panel, top} = podSheet('nui-pod-notes-sheet nui-pod-dls', 'Schließen', closeDownloads);
dlSheet = made;
dlBody = el('div', 'nui-pod-dls-body');
panel.append(top, el('div', 'nui-pod-player-title', 'Downloads'), dlBody);
document.body.append(dlSheet);
renderDownloads(true);
}
// The finished downloads: the episode list with the filter „Geladen“ (▶ plays, swipes delete).
function showDownloaded() {
closeDownloads();
closePlayer();
selected = null;
feedView = null;
preview = null;
view = 'episodes';
try { localStorage.setItem('nui_pod_view', view); } catch (e) {}
if (typeof showTab === 'function') showTab('podcasts');
setEpisodeList(['downloaded'], 'new');
scrollTop(0);
}
function dlTitle(d) {
return plain(d.title) || 'Folge ' + d.id;
}
// The line under the title: how far, why it waits, what went wrong.
function dlStatus(d) {
const of = d.total ? `${size(d.bytes)} von ${size(d.total)}` : size(d.bytes);
if (d.state === 'downloading') return (d.total ? d.progress + ' % · ' : '') + of;
if (d.state === 'failed') return d.error || 'hat nicht geklappt';
const parts = [dlReason(d)];
if (d.bytes) parts.push(of + ' geladen');
if (d.reason === 'retry' && d.error) parts.push(d.error);
return parts.join(' · ');
}
function dlActions(d) {
const items = [];
if (d.state === 'queued' && d.order > 0 && N.podcastDownloadFirst) {
items.push({icon: '⇈', label: 'Zuerst laden', hint: 'vor die anderen wartenden', action: () => N.podcastDownloadFirst(d.id)});
}
if ((d.state === 'failed' || (d.state === 'queued' && d.error)) && N.podcastRetryDownload) {
items.push({icon: '↻', label: 'Erneut versuchen', hint: d.error || null, action: () => N.podcastRetryDownload(d.id)});
}
items.push(d.state === 'failed'
? {icon: '✕', label: 'Löschen', action: () => N.podcastDeleteDownload(d.id)}
: {icon: '✕', label: 'Abbrechen', hint: d.bytes ? 'was schon geladen ist, geht mit' : null,
action: () => N.podcastDeleteDownload(d.id)});
return items;
}
function dlRow(d) {
const row = el('div', 'nui-pod-dlrow' + (d.state === 'failed' ? ' nui-pod-dlrow-failed' : ''));
row.dataset.dlrow = d.id;
const main = el('div', 'nui-pod-main');
main.append(el('div', 'nui-pod-title', dlTitle(d)));
const f = d.feedId && feeds().find(x => x.id === d.feedId);
const meta = el('div', 'nui-pod-meta nui-pod-dlstatus');
main.append(meta);
if (f) main.append(el('div', 'nui-pod-meta', plain(f.title)));
if (d.state === 'downloading' || (d.state === 'queued' && d.bytes)) {
const bar = el('div', 'nui-pod-progress nui-pod-stat-bar');
bar.append(el('span'));
main.append(bar);
}
row.append(main);
// The likely tap right there; all of them in the row's sheet.
if (d.state === 'queued' && d.order > 0 && N.podcastDownloadFirst) {
row.append(button('⇈', 'Zuerst laden', () => N.podcastDownloadFirst(d.id)));
}
if (d.state === 'failed' && N.podcastRetryDownload) {
row.append(button('↻', 'Erneut versuchen', () => N.podcastRetryDownload(d.id)));
}
row.append(button('✕', d.state === 'failed' ? 'Löschen' : 'Abbrechen', () => N.podcastDeleteDownload(d.id)));
row.addEventListener('click', () => openSheet(dlTitle(d), dlActions(d)));
dlRowNumbers(row, d);
return row;
}
function dlRowNumbers(row, d) {
row.querySelector('.nui-pod-dlstatus').textContent = dlStatus(d);
const fill = row.querySelector('.nui-pod-progress > span');
if (fill) fill.style.width = (d.total ? Math.round(d.bytes / d.total * 100) : 0) + '%';
}
function renderDownloads(force) {
if (!dlBody) return;
const all = Object.keys(downloads).map(id => Object.assign({id: +id}, downloads[id]));
const running = all.filter(d => d.state === 'downloading');
const waiting = all.filter(d => d.state === 'queued').sort((a, b) => (a.order || 0) - (b.order || 0));
const failed = all.filter(d => d.state === 'failed');
const done = all.filter(d => d.state === 'done');
// The same rows as before: only the numbers move (keeps a tap or the scroll going).
const sig = [running, waiting, failed].map(l => l.map(d => `${d.id}:${d.reason || ''}:${d.error || ''}`).join(','))
.join('|') + `|${done.length}|${!!dlInfo.paused}`;
if (!force && sig === dlBody.dataset.sig) {
for (const d of running.concat(waiting)) {
const row = dlBody.querySelector(`[data-dlrow="${d.id}"]`);
if (row) dlRowNumbers(row, d);
}
return;
}
dlBody.dataset.sig = sig;
const panel = dlBody.parentElement;
const scroll = panel.scrollTop;
dlBody.textContent = '';
const head = el('div', 'nui-pod-dls-head');
head.append(el('div', 'nui-pod-summary', dlSummary()));
const tools = el('div', 'nui-pod-offer-row');
if (N.podcastPauseDownloads && (running.length || waiting.length || dlInfo.paused)) {
const paused = !!dlInfo.paused;
tools.append(button(paused ? '▶ Fortsetzen' : '‖ Alle pausieren', paused ? 'Downloads fortsetzen' : 'Alle Downloads pausieren',
() => { dlInfo.paused = !paused; N.podcastPauseDownloads(!paused); renderDownloads(true); }, 'btn'));
}
if (failed.length > 1 && N.podcastRetryDownload) {
tools.append(button('↻ Alle erneut', 'Alle fehlgeschlagenen erneut versuchen', () => N.podcastRetryDownload(0), 'btn'));
}
if (tools.childNodes.length) head.append(tools);
dlBody.append(head);
const section = (title, list) => {
if (!list.length) return;
dlBody.append(el('div', 'nui-pod-dls-sec', `${title} (${list.length})`));
const box = el('div', 'nui-pod-list');
for (const d of list) box.append(dlRow(d));
dlBody.append(box);
};
section('Läuft', running);
section('Wartet', waiting);
section('Fehlgeschlagen', failed);
if (done.length) {
dlBody.append(el('div', 'nui-pod-dls-sec', 'Fertig'));
const row = el('div', 'nui-pod-dls-donerow');
row.append(el('div', 'nui-pod-meta nui-pod-dls-done',
`${done.length} ${done.length === 1 ? 'Folge' : 'Folgen'} auf dem Telefon · ${size(done.reduce((n, d) => n + (d.bytes || 0), 0))}`),
button('Anzeigen ›', 'Die geladenen Folgen als Liste', showDownloaded, 'btn nui-pod-dls-show'));
dlBody.append(row);
}
if (!all.length) dlBody.append(el('div', 'nui-pod-empty', 'Keine Downloads. Beim ↓ einer Folge lädst du sie aufs Telefon.'));
panel.scrollTop = scroll;
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
// ---- hooks from the app ---------------------------------------------------------------
// A podcast address from another app is waiting (MainActivity): show its preview.
window.__dioraSubscribe = function () {
const url = N.podcastTakeSubscribe ? N.podcastTakeSubscribe() : '';
if (url) openPreview(url);
};
window.__dioraPreview = function (id, d) {
if (!preview || preview.id !== id) return;
if (d && d.ok) preview = {id, url: d.url, data: d};
else if (d && d.choices && d.choices.length) preview = {id, url: preview.url, choices: d.choices};
else preview = {id, url: preview.url, error: (d && d.error) || 'Dieser Feed ließ sich nicht laden.'};
render();
};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
window.__dioraPodcasts = function (json) {
state = typeof json === 'string' ? parse(json, state) : json;
moveFeedListsToServer();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
render();
};
// The app won't stream this episode over mobile data without a yes ("Nachfragen", RadioService.mayStream).
window.__dioraAskStream = function (id, title) {
if (!N.podcastAllowStream) return;
openSheet('Über mobile Daten streamen?', [
{icon: '▶', label: 'Ja, diese Folge streamen', hint: plain(title) || null, action: () => N.podcastAllowStream(id)},
{icon: '✕', label: 'Nein', hint: 'im WLAN oder heruntergeladen hören', action: () => {}},
]);
};
// A new-episode notification was tapped: that podcast's page (gone meanwhile: „Neu“).
window.__dioraOpenFeed = function (feedId) {
if (typeof showTab !== 'function') return;
closeStats();
closeDownloads();
closeNotes();
closePlayer();
selected = null;
showTab('podcasts');
const f = feeds().find(x => x.id === feedId);
if (f) {
openFeed(f);
} else {
feedView = null;
view = 'inbox';
render();
scrollTop(0);
}
};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
window.__dioraPodcastsRefreshed = function (ok) {
refreshing = false;
if (!ok) toast(navigator.onLine ? 'Abrufen hat nicht geklappt' : 'Offline — es gilt der letzte Stand');
render();
};
window.__dioraPlayer = function (p) {
const before = player;
player = p || {kind: null};
// Something starts playing here (an episode, the radio): Ambient stops (app.js).
if (player.playing && !before.playing && !player.remote && window.__dioraAmbientStop) window.__dioraAmbientStop();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
renderMini();
renderPlayer();
// The current episode changed: the queue view marks it.
if (before.episodeId !== player.episodeId || before.kind !== player.kind) render();
else if (!!before.playing !== !!player.playing) playButtons();
if (epScreen && (before.episodeId !== player.episodeId || !!before.playing !== !!player.playing)) episodeScreenParts();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
};
// What plays, for the screensaver (native-saver.js): the player's report and the chapter at its place.
const nowAsked = new Set();
window.__dioraPlayerNow = function () {
if (!player || player.kind !== 'podcast') return player;
const d = details.get(player.episodeId);
if (!d && player.episodeId && !nowAsked.has(player.episodeId)) {
nowAsked.add(player.episodeId);
Promise.resolve(loadDetails(player.episodeId)).catch(() => {});
}
let chapter = '';
const chapters = (d && d.chapters) || [];
const t = (player.pos || 0) / 1000;
for (const c of chapters) if ((c.start || 0) <= t + 0.5) chapter = c.title || '';
return Object.assign({}, player, {chapter: plain(chapter)});
};
// Ambient starts (app.js): an episode playing here pauses.
window.__dioraPausePlayer = function () {
if (player.kind !== 'podcast' || !player.playing || player.remote) return;
// Pause, not toggle: Android's audio focus may have paused it a moment before.
if (N.podcastPause) N.podcastPause(); else if (N.podcastToggle) N.podcastToggle();
};
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
// The app loads this in <head>; the browser may load it after the page is parsed.
function start(fn) {
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fn);
else fn();
}
start(() => {
if (typeof showTab !== 'function') return;
// The web's podcast tab loads its own lists; the app has them already.
window.loadPodcastTab = function () {};
window.refreshAllFeeds = async function () {};
build();
buildMini();
ambientSleepButton();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
player = parse(N.playerState(), {kind: null});
moveFeedListsToServer();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
render();
renderMini();
const back = window.__dioraBack;
window.__dioraBack = function () {
if (closeStats()) return true;
if (closeDownloads()) return true;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (closeNotes()) return true;
if (closePlayer()) return true;
const onPodcasts = typeof _currentTopTab !== 'undefined' && _currentTopTab === 'podcasts';
if (onPodcasts && stopSelecting()) return true;
// The tab's own pages go back one by one, before the next handler: that one would
// leave the tab for Home (native-radio.js). A menu or dialog on top closes first.
if (onPodcasts && !somethingOnTop()) {
if (closePreview()) return true;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if (feedView) { feedView = null; render(); return true; }
// From the search back to the list it was opened from.
if (view === 'search') {
try { view = localStorage.getItem('nui_pod_view') || 'queue'; } catch (e) { view = 'queue'; }
if (!VIEWS.some(v => v.id === view)) view = 'queue';
render();
return true;
}
}
if (back && back()) return true;
if (onPodcasts) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
showTab('books');
return true;
}
return false;
};
// A menu, dialog or note sheet over the page (native-ui.js, app.js's modal).
function somethingOnTop() {
if (document.querySelector('#nui-sheet, .nui-backdrop, .note-bottom-sheet')) return true;
const modal = $id('modal-dialog');
return !!modal && getComputedStyle(modal).display !== 'none';
}
// A swipe waiting for „Rückgängig“ is done when the page goes away.
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') commitSwipe(); });
window.addEventListener('pagehide', commitSwipe);
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
// Pull the latest state once per start; the server has done the fetching.
N.podcastRefresh(false);
window.__dioraSubscribe();
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die Android-App: native-podcast.js aus diora-android, unverändert kopiert nach static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf), dazu die Podcast-Teile von native.css. static/js/podcasts-web.js spielt im Browser die Rolle der App (window.DioraPodcastHost statt DioraNative): - Zustand aus /podcasts/state/, in localStorage für einen schnellen Start; Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“ - eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist, wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“), automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit Ausblenden - Sperrbildschirm und Medientasten über navigator.mediaSession - Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs) - keine Downloads (die Oberfläche blendet sie aus) - Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender legt die Folge weg - Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten Bildschirmen eine mittige Spalte (760 px) In der App und der Desktop-App (window.DioraNative vorhanden) tut podcasts-web.js nichts. Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab). Service-Worker-Cache auf v52, neue Dateien im App-Shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
});
})();