2026-10-04 00:18:06 +02:00
|
|
|
|
// Copied from diora-android 8ee6c2b 2026-10-04: 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);
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// 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'},
|
|
|
|
|
|
];
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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;
|
|
|
|
|
|
const START_PAGES = [
|
|
|
|
|
|
!IN_APP && {id: '', label: 'Zuletzt benutzt', hint: 'Standard im Browser'},
|
|
|
|
|
|
{id: 'books', label: 'Bücher', hint: IN_APP ? 'Standard' : null},
|
|
|
|
|
|
{id: 'queue', label: 'Podcasts: Warteschlange'},
|
|
|
|
|
|
{id: 'inbox', label: 'Podcasts: Neu'},
|
|
|
|
|
|
{id: 'radio', label: 'Radio'},
|
|
|
|
|
|
].filter(Boolean);
|
|
|
|
|
|
function startPage() {
|
|
|
|
|
|
let v = null;
|
|
|
|
|
|
try { v = localStorage.getItem('nui_start_page'); } catch (e) {}
|
|
|
|
|
|
return START_PAGES.some(p => p.id === v) ? v : START_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'},
|
|
|
|
|
|
];
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
// 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];
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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;
|
2026-10-02 16:29:15 +02:00
|
|
|
|
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;
|
2026-10-03 14:40:39 +02:00
|
|
|
|
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
|
|
|
|
|
|
try { locked = localStorage.getItem('nui_pod_locked') === '1'; } catch (e) {}
|
|
|
|
|
|
try { view = localStorage.getItem('nui_pod_view') || 'queue'; } catch (e) {}
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// 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 || [];
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// 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 || [];
|
|
|
|
|
|
const settings = () => state.settings || {};
|
|
|
|
|
|
const currentId = () => (player.kind === 'podcast' ? player.episodeId : 0);
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
// 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) {
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// 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 '';
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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`;
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let toastTimer = 0;
|
|
|
|
|
|
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');
|
|
|
|
|
|
clearTimeout(toastTimer);
|
|
|
|
|
|
toastTimer = setTimeout(() => t.classList.remove('nui-show'), 2600);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Feed titles sometimes arrive with HTML entities still in them (&).
|
|
|
|
|
|
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,
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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,
|
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,
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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,
|
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
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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 : ''));
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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 = '';
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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);
|
2026-10-02 19:30:03 +02:00
|
|
|
|
};
|
|
|
|
|
|
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) {
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
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');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// 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)};
|
|
|
|
|
|
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
// „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};
|
|
|
|
|
|
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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)},
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
!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),
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
{icon: '✕', svg: 'listx', label: 'Aus der Warteschlange nehmen', hint: dlDone(e.id) ? 'Datei bleibt' : null,
|
|
|
|
|
|
action: () => N.podcastDequeue(e.id)},
|
|
|
|
|
|
discardItem(e, opts),
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:30:03 +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)},
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
{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')},
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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),
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
{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]))},
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
discardItem(e, opts),
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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', [
|
2026-10-02 10:30:00 +02:00
|
|
|
|
{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(),
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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
|
|
|
|
]),
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +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(),
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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(),
|
2026-10-02 16:29:15 +02:00
|
|
|
|
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
|
|
|
|
]),
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
mobileGroup(),
|
2026-10-02 12:44:49 +02:00
|
|
|
|
DL && {icon: '↓', label: 'Downloads', hint: dlSummary(), action: openDownloads},
|
|
|
|
|
|
DL && group('⚙︎', 'Download-Einstellungen', 'automatisch, gleichzeitig, Limit, aufräumen', () => [
|
2026-10-02 11:46:37 +02:00
|
|
|
|
// 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(),
|
|
|
|
|
|
]),
|
2026-10-02 16:36:00 +02:00
|
|
|
|
backgroundSyncItem(),
|
2026-10-02 19:30:03 +02:00
|
|
|
|
{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},
|
|
|
|
|
|
]),
|
|
|
|
|
|
{icon: '▤', label: 'Hörstatistik', action: openStats},
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 16:36:00 +02:00
|
|
|
|
// 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(); },
|
|
|
|
|
|
})))};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
// 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`,
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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),
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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;
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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);
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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;
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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 => (START_PAGES.find(p => p.id === id) || START_PAGES[0]).label;
|
|
|
|
|
|
return {icon: '⌂', label: 'Startseite', hint: label(now) + (IN_APP ? ' · beim Öffnen der App' : ' · beim Öffnen von diora'),
|
|
|
|
|
|
action: () => openSheet('Beim Öffnen zeigen …', START_PAGES.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);
|
|
|
|
|
|
},
|
|
|
|
|
|
})))};
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---- 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)},
|
|
|
|
|
|
])};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 16:29:15 +02:00
|
|
|
|
// The notification's two extra buttons (app only: NotifyButtons.kt), like AntennaPod's.
|
|
|
|
|
|
const NOTIFY = [
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
{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(' · ')},
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
{key: 'sleep', label: 'Sleep-Timer', hint: () => `${sleepMinutesLabel((player.sleep || {}).last || 15)} an / aus, auch beim Radio`},
|
2026-10-02 16:29:15 +02:00
|
|
|
|
];
|
|
|
|
|
|
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 => ({
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
icon: chosen.includes(n.key) ? '☑' : '☐', label: n.label, hint: n.hint(),
|
2026-10-02 16:29:15 +02:00
|
|
|
|
// 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) {
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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');
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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);
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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.
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
items.some(e => fresh.has(e.id)) && {label: 'Aus „Neu“', svg: 'listx', run: ids => {
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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();
|
|
|
|
|
|
}},
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
discardAction(),
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
].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)));
|
|
|
|
|
|
}
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// 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;
|
|
|
|
|
|
});
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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() {
|
2026-10-03 14:40:39 +02:00
|
|
|
|
// 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; }
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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();
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
// 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'})));
|
2026-10-02 19:30:03 +02:00
|
|
|
|
bar.append(...[text, dlIndicator(), searchButton(), button('⟳', 'Alle Feeds abrufen', () => refresh(true)),
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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);
|
|
|
|
|
|
|
2026-10-02 16:29:15 +02:00
|
|
|
|
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));
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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);
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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);
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:30:03 +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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 12:44:49 +02:00
|
|
|
|
// 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(), {});
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
const needed = (Object.keys(downloads).length || 0) + o.count + (s.auto ? (s.count === 0 ? queue().length : s.count || 0) : 0);
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// 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) {
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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;
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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;
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
if (e.duration) meta.push(started ? minutes(shown(e.duration - e.pos, e.feedId)) + ' übrig' : minutes(shown(e.duration, e.feedId)));
|
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);
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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));
|
2026-10-02 19:30:03 +02:00
|
|
|
|
return row;
|
|
|
|
|
|
}
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
const how = {select: !opts.noSelect, list, filters: opts.filters || []};
|
|
|
|
|
|
const menu = () => (list === 'queue' ? queueMenu(e, how) : episodeMenu(e, how));
|
2026-10-02 19:30:03 +02:00
|
|
|
|
row.append(primaryButton(e, list), button('⋯', 'Mehr', menu, 'nui-pod-menu'));
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
row.addEventListener('click', () => openEpisode(e, how));
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// Swipes: touch only, not while the queue is locked.
|
|
|
|
|
|
const swipes = list && SWIPE_LISTS[list] && !(list === 'queue' && locked) ? list : '';
|
|
|
|
|
|
rowGestures(row, menu, swipes && ((act, dir) => swipeDone(e, swipes, act, opts.filters || [], dir)), 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// 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;
|
2026-10-03 14:40:39 +02:00
|
|
|
|
let holding = false; // this row counts in swipingSince
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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');
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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;
|
2026-10-02 19:30:03 +02:00
|
|
|
|
};
|
|
|
|
|
|
const finish = done => {
|
2026-10-03 14:40:39 +02:00
|
|
|
|
if (holding) {
|
|
|
|
|
|
holding = false;
|
|
|
|
|
|
swipingSince = 0;
|
|
|
|
|
|
if (renderAfterSwipe) { renderAfterSwipe = false; setTimeout(render, done ? 200 : 220); }
|
|
|
|
|
|
}
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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 => {
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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;
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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();
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
if (!onHandle && !ev.target.closest('button')) {
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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';
|
2026-10-03 14:40:39 +02:00
|
|
|
|
// The swipe before stays undoable (its bar too) until this one is done (offerUndo).
|
|
|
|
|
|
holding = true;
|
|
|
|
|
|
swipingSince = Date.now();
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
// 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“).
|
2026-10-02 19:30:03 +02:00
|
|
|
|
const SWIPE_LISTS = {
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
queue: {label: 'Warteschlange', left: N.podcastDiscard ? 'discard' : 'delete', right: 'dequeue'},
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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: '+'},
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
// Where there are files (DL is known only further down: getters).
|
|
|
|
|
|
{id: 'dequeue', get label() { return DL ? 'Aus Warteschlange · Datei bleibt' : 'Aus der Warteschlange'; },
|
|
|
|
|
|
icon: '✕', svg: 'listx'},
|
2026-10-02 19:30:03 +02:00
|
|
|
|
{id: 'download', label: 'Herunterladen', icon: '↓', dl: true},
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
{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: '✓'},
|
2026-10-02 19:30:03 +02:00
|
|
|
|
{id: 'favorite', label: 'Favorit an / aus', icon: '★'},
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
{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'},
|
2026-10-02 19:30:03 +02:00
|
|
|
|
];
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
// 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]);
|
2026-10-02 19:30:03 +02:00
|
|
|
|
|
|
|
|
|
|
function swipePrefs() {
|
|
|
|
|
|
let all = {};
|
|
|
|
|
|
try { all = JSON.parse(localStorage.getItem('nui_pod_swipes') || '{}') || {}; } catch (e) {}
|
|
|
|
|
|
return all;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
// 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) {}
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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);
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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};
|
|
|
|
|
|
}
|
2026-10-02 19:30:03 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
// „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);
|
2026-10-02 19:30:03 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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`,
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
SWIPE_ACTIONS.filter(canSwipe).map(a => ({
|
2026-10-02 19:30:03 +02:00
|
|
|
|
icon: swipeAction(list, dir) === a.id ? '●' : '○', label: a.label, active: swipeAction(list, dir) === a.id,
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
hint: [a.hint, a.id === l[dir] ? 'Standard' : null].filter(Boolean).join(' · ') || null,
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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).
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
function undoBar(text, svg) {
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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';
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
bar.append(el('span', 'nui-pod-undo-icon'), el('span', 'nui-pod-undo-text'));
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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;
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
const icon = bar.querySelector('.nui-pod-undo-icon');
|
|
|
|
|
|
icon.textContent = '';
|
|
|
|
|
|
if (svg) icon.append(svgIcon(svg, 18));
|
|
|
|
|
|
icon.hidden = !svg;
|
2026-10-02 19:30:03 +02:00
|
|
|
|
bar.classList.add('nui-show');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function offerUndo(e, text, change) {
|
|
|
|
|
|
commitSwipe();
|
|
|
|
|
|
pendingSwipe = Object.assign({id: e.id}, change);
|
|
|
|
|
|
pendingSwipe.timer = setTimeout(commitSwipe, 4000);
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
undoBar(text, change.svg);
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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';
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
offerUndo(e, dlDone(e.id) ? 'Aus Warteschlange · Datei bleibt' : 'Aus Warteschlange',
|
|
|
|
|
|
{hide, svg: 'listx', run: () => { N.podcastDequeue(e.id); o.in_queue = false; }});
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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.
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
const out = !running && deleteDequeues(e.id);
|
2026-10-02 19:30:03 +02:00
|
|
|
|
const hide = list === 'downloads' || filters.includes('downloaded') || (list === 'queue' && out);
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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)});
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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';
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
offerUndo(e, 'Aus Neu entfernt', {hide, svg: 'listx', run: () => N.podcastDismiss(JSON.stringify([e.id]))});
|
2026-10-02 19:30:03 +02:00
|
|
|
|
return !hide;
|
|
|
|
|
|
}
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
if (act === 'discard' && N.podcastDiscard) return discardOne(e, list, filters);
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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(); }},
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
{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) {
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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(); }},
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
{label: 'Ausblenden', svg: 'listx', run: ids => { N.podcastDismiss(JSON.stringify(ids)); stopSelecting(); }},
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
DL && {label: 'Laden', run: ids => downloadMany(chosen(ids))},
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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');
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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');
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
// 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');
|
2026-10-02 19:30:03 +02:00
|
|
|
|
row.append(art(f.artwork_url, null, null, f.title));
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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() {
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
const sort = FEED_SORTS.find(s => s.id === feedSort) || FEED_SORTS[0];
|
|
|
|
|
|
const items = feeds().slice().sort((a, b) => sort.cmp(a, b) || byTitle(a, 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
|
|
|
|
if (!items.length) {
|
|
|
|
|
|
empty('Noch keine Abos. Unter „Suche“ findest du Podcasts.');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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([button('⇅', 'Sortieren und Zahl wählen', feedSortMenu)]));
|
|
|
|
|
|
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');
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function openFeed(f) {
|
|
|
|
|
|
feedView = {feed: f, episodes: null};
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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 r = await fetch(`/podcasts/feeds/${f.id}/episodes/`);
|
|
|
|
|
|
if (!r.ok) throw new Error(r.status);
|
|
|
|
|
|
const data = await r.json();
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// 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;
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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');
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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');
|
2026-10-02 19:30:03 +02:00
|
|
|
|
info.append(el('div', 'nui-pod-title', plain(f.title)));
|
|
|
|
|
|
if (f.author) info.append(el('div', 'nui-pod-meta', plain(f.author)));
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
head.append(info, button('ⓘ', 'Über diesen Podcast', () => openFeedInfo(f), 'nui-pod-infobtn'));
|
2026-10-02 19:30:03 +02:00
|
|
|
|
if (feedHasSettings(live)) 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);
|
2026-10-02 19:30:03 +02:00
|
|
|
|
const line = feedHasSettings(live) ? 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:30:03 +02:00
|
|
|
|
function feedHasSettings(live) {
|
|
|
|
|
|
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(', ');
|
2026-10-02 19:30:03 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// "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)})))},
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 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;
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
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;
|
2026-10-02 19:30:03 +02:00
|
|
|
|
const row = episodeRow(e, {showDate: true, list: downloadsOnly(filters) ? 'downloads' : 'feed', filters});
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
// 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;
|
|
|
|
|
|
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
function feedListPrefs(id) {
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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 : []};
|
|
|
|
|
|
}
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
try { return JSON.parse(localStorage.getItem('nui_pod_feedlists') || '{}')[id] || {}; } catch (e) { return {}; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function saveFeedListPrefs(id, prefs) {
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
// 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});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
// ---- 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();
|
|
|
|
|
|
notesSheet = el('div');
|
|
|
|
|
|
notesSheet.id = 'nui-pod-player';
|
|
|
|
|
|
notesSheet.className = 'nui-pod-notes-sheet nui-pod-info';
|
|
|
|
|
|
const panel = el('div', 'nui-pod-player-panel');
|
|
|
|
|
|
const top = el('div', 'nui-pod-player-top');
|
|
|
|
|
|
top.append(button('⌄', 'Schließen', closeNotes));
|
|
|
|
|
|
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)));
|
2026-10-02 19:30:03 +02:00
|
|
|
|
head.append(art(live.artwork_url, 'big', null, live.title), title);
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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);
|
|
|
|
|
|
notesSheet.append(panel);
|
|
|
|
|
|
notesSheet.addEventListener('click', ev => { if (ev.target === notesSheet) closeNotes(); });
|
|
|
|
|
|
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; }
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// 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/) ----------------------
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
// 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 || !!N.podcastDownloads)));
|
|
|
|
|
|
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
|
|
|
|
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +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;
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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 {
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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();
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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) {
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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;
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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() {
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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; }
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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');
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
for (const e of items) {
|
|
|
|
|
|
const o = e.raw;
|
2026-10-02 19:30:03 +02:00
|
|
|
|
const row = episodeRow(e, {showFeed: true, showDate: epSort !== 'played', list: downloadsOnly(epFilters) ? 'downloads' : 'episodes',
|
|
|
|
|
|
filters: epFilters});
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
if (epSort === 'played' && o.last_played_at) {
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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;
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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; })};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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();
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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);
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
// 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();
|
|
|
|
|
|
statsSheet = el('div');
|
|
|
|
|
|
statsSheet.id = 'nui-pod-player';
|
|
|
|
|
|
statsSheet.className = 'nui-pod-notes-sheet';
|
|
|
|
|
|
const panel = el('div', 'nui-pod-player-panel');
|
|
|
|
|
|
const top = el('div', 'nui-pod-player-top');
|
|
|
|
|
|
top.append(button('⌄', 'Schließen', closeStats));
|
|
|
|
|
|
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);
|
|
|
|
|
|
statsSheet.append(panel);
|
|
|
|
|
|
statsSheet.addEventListener('click', ev => { if (ev.target === statsSheet) closeStats(); });
|
|
|
|
|
|
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 {
|
|
|
|
|
|
const r = await fetch('/podcasts/stats/' + (range.days ? `?days=${range.days}` : ''));
|
|
|
|
|
|
if (!r.ok) throw new Error(r.status);
|
|
|
|
|
|
d = await r.json();
|
|
|
|
|
|
} 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');
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
// ---- 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';
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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');
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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);
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
|
|
|
|
|
|
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');
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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');
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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;
|
2026-10-02 16:29:15 +02:00
|
|
|
|
if (/^https?:\/\//i.test(q)) { if (N.podcastPreview) openPreview(q); else subscribe(q, q); return; }
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
searchDraft = null;
|
|
|
|
|
|
const test = matcher(q);
|
|
|
|
|
|
const s = searchResults = {
|
|
|
|
|
|
q,
|
|
|
|
|
|
feeds: feeds().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();
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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 {
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
const r = await fetch(`/podcasts/episodes/?q=${encodeURIComponent(s.q)}¬es=1&limit=30&offset=${offset}`);
|
|
|
|
|
|
if (!r.ok) throw new Error(r.status);
|
|
|
|
|
|
const d = await r.json();
|
|
|
|
|
|
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();
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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) {
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
|
|
|
|
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
|
|
|
|
}
|
Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
„Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +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);
|
2026-10-02 16:29:15 +02:00
|
|
|
|
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.');
|
2026-10-02 16:29:15 +02:00
|
|
|
|
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');
|
2026-10-02 19:30:03 +02:00
|
|
|
|
head.append(button('‹', 'Zurück', closePreview), art(d.artwork, 'big', null, d.title));
|
2026-10-02 16:29:15 +02:00
|
|
|
|
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');
|
2026-10-02 19:30:03 +02:00
|
|
|
|
row.append(art(e.artwork, null, d.artwork, d.title));
|
2026-10-02 16:29:15 +02:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 16:29:15 +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) -----------------------------------------------------------
|
|
|
|
|
|
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
// 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 => {
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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;
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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 => {
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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;
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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)` : '';
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
// 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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'));
|
2026-10-04 00:18:06 +02:00
|
|
|
|
text.addEventListener('click', () => (miniKind === 'ambient' ? showTab('ambient')
|
|
|
|
|
|
: miniKind === 'radio' ? openRadioPlayer() : openPlayer()));
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
const devices = button('⇄', 'Wiedergabe auf …', deviceMenu, 'nui-mini-devices');
|
|
|
|
|
|
const here = button('⇩', 'Hier abspielen', takeOver, 'nui-mini-here');
|
|
|
|
|
|
devices.hidden = here.hidden = 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
|
|
|
|
mini.append(art('', 'mini'), text,
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
button('↺', '15 s zurück', () => (miniKind === 'remote'
|
|
|
|
|
|
? remoteCmd('skip', -(devicePrefs().skip_back || 15)) : N.podcastSkip(false)), 'nui-pod-skip'),
|
2026-10-02 12:44:49 +02:00
|
|
|
|
button('☆', 'Sender speichern', () => window.__dioraRadioSave && window.__dioraRadioSave(), 'nui-mini-save'),
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
devices, here,
|
|
|
|
|
|
button('▶', 'Abspielen', () => (miniKind === 'remote' ? remoteToggle()
|
2026-10-04 00:18:06 +02:00
|
|
|
|
: miniKind === 'ambient' ? ambientToggle()
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
: miniKind === 'radio' ? window.__dioraRadioToggle && window.__dioraRadioToggle() : N.podcastToggle()), 'nui-mini-toggle'));
|
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);
|
2026-10-02 12:44:49 +02:00
|
|
|
|
// 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.
|
|
|
|
|
|
const tabbar = $id('nui-tabbar');
|
|
|
|
|
|
if (tabbar && !tabbar.querySelector('[data-tab="podcasts"]')) {
|
|
|
|
|
|
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"]'));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-03 23:22:00 +02:00
|
|
|
|
// 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));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-04 00:18:06 +02:00
|
|
|
|
// 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;
|
|
|
|
|
|
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, '');
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
// The bar names a running sleep timer: "… · ☾ 12 min".
|
|
|
|
|
|
function sleepSuffix() {
|
|
|
|
|
|
const l = sleepLabel();
|
|
|
|
|
|
return l ? ' · ' + l : '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
// What the bar shows: 'podcast', 'radio' (a station is chosen), 'remote'
|
|
|
|
|
|
// (another device is the active one) or ''.
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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() {
|
2026-10-03 23:22:00 +02:00
|
|
|
|
renderAmbientSleep();
|
2026-10-04 00:18:06 +02:00
|
|
|
|
if (renderAmbientMini()) return;
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
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';
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
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;
|
2026-10-04 00:18:06 +02:00
|
|
|
|
const ambArt = mini.querySelector('.nui-amb-art');
|
|
|
|
|
|
if (ambArt) ambArt.remove();
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
const radio = isPod || r ? null : radioNow();
|
|
|
|
|
|
miniKind = r ? 'remote' : isPod ? 'podcast' : radio ? 'radio' : '';
|
2026-10-02 12:44:49 +02:00
|
|
|
|
mini.hidden = !miniKind;
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
mini.classList.toggle('nui-mini-radio', miniKind === 'radio' || (!!r && r.kind === 'radio'));
|
|
|
|
|
|
mini.classList.toggle('nui-mini-remote', !!r);
|
2026-10-02 12:44:49 +02:00
|
|
|
|
document.documentElement.classList.toggle('nui-pod-mini-open', !!miniKind);
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
mini.querySelector('.nui-pod-skip').hidden = !(isPod || r) || (!!r && r.kind !== 'podcast');
|
2026-10-02 12:44:49 +02:00
|
|
|
|
mini.querySelector('.nui-mini-save').hidden = miniKind !== 'radio' || radio.saved;
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
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) || '';
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
mini.querySelector('.nui-mini-track').textContent = remoteLine(r) + sleepSuffix();
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-10-02 12:44:49 +02:00
|
|
|
|
if (miniKind === 'radio') {
|
|
|
|
|
|
mini.querySelector('.nui-mini-station').textContent = radio.name;
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
mini.querySelector('.nui-mini-track').textContent = (radio.buffering ? 'Verbinde …'
|
|
|
|
|
|
: radio.playing ? (radio.track || '● Live') : 'Angehalten') + sleepSuffix();
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
mini.querySelector('.nui-mini-track').textContent = (player.buffering && player.playing ? 'Lädt …'
|
|
|
|
|
|
: (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';
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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 r = await fetch(`/podcasts/episodes/${id}/`);
|
|
|
|
|
|
if (!r.ok) throw new Error(r.status);
|
|
|
|
|
|
const d = await r.json();
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
// 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 || [];
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// 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)));
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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'));
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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();
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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);
|
|
|
|
|
|
};
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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);
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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));
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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 {
|
|
|
|
|
|
const r = await fetch(`/podcasts/episodes/${id}/transcript/`);
|
|
|
|
|
|
if (!r.ok) throw new Error(r.status);
|
|
|
|
|
|
t = await r.json();
|
|
|
|
|
|
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'});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
// ---- 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;
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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
|
|
|
|
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +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();
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
ctx = 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
|
|
|
|
notesSheet = el('div');
|
|
|
|
|
|
notesSheet.id = 'nui-pod-player';
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
notesSheet.className = 'nui-pod-notes-sheet nui-pod-episode';
|
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 panel = el('div', 'nui-pod-player-panel');
|
|
|
|
|
|
const top = el('div', 'nui-pod-player-top');
|
|
|
|
|
|
top.append(button('⌄', 'Schließen', closeNotes));
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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');
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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
|
|
|
|
notesSheet.append(panel);
|
|
|
|
|
|
notesSheet.addEventListener('click', ev => { if (ev.target === notesSheet) closeNotes(); });
|
|
|
|
|
|
document.body.append(notesSheet);
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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;
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 12:44:49 +02:00
|
|
|
|
// 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() {
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
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();
|
2026-10-02 12:44:49 +02:00
|
|
|
|
sheetKind = 'podcast';
|
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 = el('div');
|
|
|
|
|
|
sheet.id = 'nui-pod-player';
|
|
|
|
|
|
sheet.innerHTML = '<div class="nui-pod-player-panel" role="dialog" aria-label="Player"></div>';
|
|
|
|
|
|
const panel = sheet.firstChild;
|
|
|
|
|
|
const top = el('div', 'nui-pod-player-top');
|
|
|
|
|
|
top.append(button('⌄', 'Player schließen', closePlayer));
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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;
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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');
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
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'),
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
button('⏭', keepSkipped() ? 'Nächste Folge (diese bleibt in der Warteschlange)' : 'Folge abschließen, nächste spielen',
|
|
|
|
|
|
finishAndNext, 'nui-pod-next'),
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
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);
|
|
|
|
|
|
sheet.addEventListener('click', e => { if (e.target === sheet) closePlayer(); });
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 12:44:49 +02:00
|
|
|
|
// 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';
|
|
|
|
|
|
sheet = el('div');
|
|
|
|
|
|
sheet.id = 'nui-pod-player';
|
|
|
|
|
|
sheet.innerHTML = '<div class="nui-pod-player-panel" role="dialog" aria-label="Radio"></div>';
|
|
|
|
|
|
const panel = sheet.firstChild;
|
|
|
|
|
|
const top = el('div', 'nui-pod-player-top');
|
|
|
|
|
|
top.append(button('⌄', 'Player schließen', closePlayer));
|
|
|
|
|
|
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);
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
if (N.playerTransfer) extra.append(devicesButton());
|
2026-10-02 12:44:49 +02:00
|
|
|
|
panel.append(controls, extra);
|
|
|
|
|
|
sheet.addEventListener('click', e => { if (e.target === sheet) closePlayer(); });
|
|
|
|
|
|
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();
|
|
|
|
|
|
};
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
window.__dioraOpenPlayer = kind => (kind === 'radio' && !remote() ? openRadioPlayer() : openPlayer());
|
2026-10-02 12:44:49 +02:00
|
|
|
|
|
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;
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
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; }
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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 = player.feed || '';
|
|
|
|
|
|
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;
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
// 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;
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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
|
|
|
|
}
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +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.
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
// 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',
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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)),
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
})).concat(forPodcast && own ? [{icon: '↺', label: 'Wie alle anderen Podcasts', action: () => N.podcastSpeedForPodcast(0)}] : [])
|
|
|
|
|
|
.concat(presetsEditItem()));
|
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)},
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
].concat(presetsEditItem()));
|
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: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
// ---- sleep timer (runs in the app, also with the screen off; episodes and the radio) ----
|
|
|
|
|
|
|
2026-10-03 23:22:00 +02:00
|
|
|
|
// 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();
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
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
|
|
|
|
|
2026-10-03 23:22:00 +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 '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
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).
|
2026-10-03 23:22:00 +02:00
|
|
|
|
function sleepMenu(radio, local) {
|
|
|
|
|
|
const r = local ? null : remote();
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
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;
|
2026-10-03 23:22:00 +02:00
|
|
|
|
const t = sleepState(local);
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
const own = player.sleep || {};
|
2026-10-03 23:22:00 +02:00
|
|
|
|
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 = [];
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
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) {
|
2026-10-03 23:22:00 +02:00
|
|
|
|
items.push({icon: '✕', label: 'Ausschalten', hint: sleepLabel(local).replace('☾ ', 'läuft: '), action: () => set(0)});
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
}
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +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
|
|
|
|
}
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +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(); }});
|
2026-10-02 12:44:49 +02:00
|
|
|
|
}
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
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
|
|
|
|
}
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +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
|
|
|
|
}
|
|
|
|
|
|
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +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;
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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();
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +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';
|
|
|
|
|
|
sheet = el('div');
|
|
|
|
|
|
sheet.id = 'nui-pod-player';
|
|
|
|
|
|
sheet.className = 'nui-pod-remote';
|
|
|
|
|
|
sheet.dataset.remote = remoteKey(r);
|
|
|
|
|
|
sheet.innerHTML = '<div class="nui-pod-player-panel" role="dialog" aria-label="Player"></div>';
|
|
|
|
|
|
const panel = sheet.firstChild;
|
|
|
|
|
|
const top = el('div', 'nui-pod-player-top');
|
|
|
|
|
|
top.append(button('⌄', 'Player schließen', closePlayer));
|
|
|
|
|
|
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'));
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
extra.append(button('☾', `Sleep-Timer auf ${remoteName(r)}`, () => sleepMenu(onRadio), 'nui-pod-sleep'));
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
sheet.addEventListener('click', e => { if (e.target === sheet) closePlayer(); });
|
|
|
|
|
|
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);
|
Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:58:55 +02:00
|
|
|
|
const sl = sheet.querySelector('.nui-pod-sleep');
|
|
|
|
|
|
sl.textContent = sleepLabel() || '☾';
|
|
|
|
|
|
sl.classList.toggle('on', !!sleepLabel());
|
Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.
Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
const DL = !!N.podcastDownloads;
|
|
|
|
|
|
let downloads = DL ? parse(N.podcastDownloads(), {}) : {};
|
|
|
|
|
|
let dlSettings = DL ? parse(N.podcastDownloadSettings(), {}) : {};
|
2026-10-02 12:44:49 +02:00
|
|
|
|
// 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(), {}) : {};
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
// 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');
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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 = [
|
2026-10-02 11:46:37 +02:00
|
|
|
|
{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));
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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);
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
// "Löschen nimmt aus der Warteschlange" (account): the app takes it out too.
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:30:03 +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);
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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) {
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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) + '%');
|
2026-10-02 19:30:03 +02:00
|
|
|
|
if (b.dataset.dl) b.firstChild.textContent = d.state === 'queued' ? (d.reason === 'paused' ? '‖' : '…') : d.progress + '';
|
2026-10-02 12:44:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// "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)}];
|
|
|
|
|
|
}
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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)}];
|
|
|
|
|
|
}
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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',
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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})},
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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})})))},
|
2026-10-02 11:46:37 +02:00
|
|
|
|
'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})})))},
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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',
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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: () => {}},
|
|
|
|
|
|
])},
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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) || {};
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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 &&
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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).
|
2026-10-02 12:44:49 +02:00
|
|
|
|
if (same) {
|
2026-10-02 19:30:03 +02:00
|
|
|
|
document.querySelectorAll('#nui-pod [data-dl], #nui-pod [data-dlmark]').forEach(dlRing);
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
|
|
2026-10-02 12:44:49 +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();
|
|
|
|
|
|
dlSheet = el('div');
|
|
|
|
|
|
dlSheet.id = 'nui-pod-player';
|
|
|
|
|
|
dlSheet.className = 'nui-pod-notes-sheet nui-pod-dls';
|
|
|
|
|
|
const panel = el('div', 'nui-pod-player-panel');
|
|
|
|
|
|
const top = el('div', 'nui-pod-player-top');
|
|
|
|
|
|
top.append(button('⌄', 'Schließen', closeDownloads));
|
|
|
|
|
|
dlBody = el('div', 'nui-pod-dls-body');
|
|
|
|
|
|
panel.append(top, el('div', 'nui-pod-player-title', 'Downloads'), dlBody);
|
|
|
|
|
|
dlSheet.append(panel);
|
|
|
|
|
|
dlSheet.addEventListener('click', ev => { if (ev.target === dlSheet) closeDownloads(); });
|
|
|
|
|
|
document.body.append(dlSheet);
|
|
|
|
|
|
renderDownloads(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// 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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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'
|
2026-10-02 19:30:03 +02:00
|
|
|
|
? {icon: '✕', label: 'Löschen', action: () => N.podcastDeleteDownload(d.id)}
|
|
|
|
|
|
: {icon: '✕', label: 'Abbrechen', hint: d.bytes ? 'was schon geladen ist, geht mit' : null,
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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'));
|
2026-10-02 19:30:03 +02:00
|
|
|
|
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);
|
2026-10-02 12:44:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
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 ---------------------------------------------------------------
|
|
|
|
|
|
|
2026-10-02 16:29:15 +02:00
|
|
|
|
// 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;
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
// 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: () => {}},
|
|
|
|
|
|
]);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-10-02 16:36:00 +02:00
|
|
|
|
// 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};
|
2026-10-03 23:08:57 +02:00
|
|
|
|
// 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();
|
2026-10-02 19:30:03 +02:00
|
|
|
|
else if (!!before.playing !== !!player.playing) playButtons();
|
Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
{"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
|
|
2026-10-03 23:08:57 +02:00
|
|
|
|
// Ambient starts (app.js): an episode playing here pauses.
|
|
|
|
|
|
window.__dioraPausePlayer = function () {
|
|
|
|
|
|
if (player.kind === 'podcast' && player.playing && !player.remote && 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();
|
2026-10-03 23:22:00 +02:00
|
|
|
|
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});
|
Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
die ersten vier sind anfangs leer (null) und werden mit POST
settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
Tempo bis 3,5×, neue Einstellungen werden gespeichert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
|
|
|
|
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;
|
2026-10-02 12:44:49 +02:00
|
|
|
|
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;
|
|
|
|
|
|
if (typeof _currentTopTab !== 'undefined' && _currentTopTab === 'podcasts' && stopSelecting()) return true;
|
|
|
|
|
|
if (back && back()) return true;
|
|
|
|
|
|
if (typeof _currentTopTab !== 'undefined' && _currentTopTab === 'podcasts') {
|
2026-10-02 16:29:15 +02:00
|
|
|
|
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; }
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
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;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-10-02 19:30:03 +02:00
|
|
|
|
// 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);
|
2026-10-02 16:29:15 +02:00
|
|
|
|
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
|
|
|
|
});
|
|
|
|
|
|
})();
|