2026-10-19 20:19:22 +02:00
// Copied from diora-android 4f514cd 2026-10-19: 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-19 18:21:03 +02:00
// With the two lists (diora-web docs/podcasts.md, state.lists_v2): the queue is the
// Wiedergabeliste, the Merkliste holds what to hear some day, the Strom replaces „Neu“
// and „Folgen“.
const VIEWS _V2 = [
{ id : 'queue' , label : 'Wiedergabeliste' } ,
{ id : 'later' , label : 'Merkliste' } ,
{ id : 'stream' , label : 'Strom' } ,
{ id : 'feeds' , label : 'Abos' } ,
] ;
2026-10-19 19:17:33 +02:00
const VIEW _IDS = VIEWS . map ( v => v . id ) . concat ( 'later' , 'stream' , 'browse' , 'search' ) ;
2026-10-02 19:30:03 +02:00
// The app's start page (this device; native.js opens it, the browser host too).
const IN _APP = ! window . DioraPodcastHost ;
Home: neue Startseite mit Weiterlesen, Weiterhören, Radio-Favoriten und Ambient; sw v87
- static/js/home.js rendert #tab-home aus dem, was die Seite schon hat:
zuletzt gelesenes Buch (und drei weitere), laufende oder nächste Folge mit
Restzeit und „N neue Folgen“, Favoriten-Sender (sonst die ersten
gespeicherten) und zuletzt gehörter Sender, Drift mit der zuletzt
gewählten Klangwelt und Sleep-Timer. Läuft im Browser, in diora desktop und
in der Android-App (dort mit static/js/ kopiert).
- app.js: Tab 'home' (TOP_TABS), window.__dioraAmbientStart() lädt Drift bei
Bedarf und startet es; die gewählte Klangwelt wird gemerkt und beim Laden
wiederhergestellt.
- podcasts-web.js: Startseite Home, wenn nichts anderes gewählt ist.
- native-podcast.js aus diora-android 7dcc0c6 (Home in der Startseiten-Wahl,
__dioraPodcastView).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:34:02 +02:00
// A function: the app loads this file in <head>, before #tab-home exists.
const startPages = ( ) => [
document . getElementById ( 'tab-home' ) && { id : 'home' , label : 'Home' , hint : 'Standard' } ,
! IN _APP && { id : '' , label : 'Zuletzt benutzt' } ,
{ id : 'books' , label : 'Bücher' , hint : IN _APP && ! document . getElementById ( 'tab-home' ) ? 'Standard' : null } ,
2026-10-02 19:30:03 +02:00
{ id : 'queue' , label : 'Podcasts: Warteschlange' } ,
{ id : 'inbox' , label : 'Podcasts: Neu' } ,
{ id : 'radio' , label : 'Radio' } ,
2026-10-16 14:53:38 +02:00
// Sections of the page's navigation (nav.js), where the page has them.
window . DioraNav && DioraNav . sections ( ) . some ( s => s . id === 'audiobooks' ) && { id : 'audiobooks' , label : 'Hörbücher' } ,
window . DioraNav && DioraNav . sections ( ) . some ( s => s . id === 'news' ) && { id : 'news' , label : 'Nachrichten' } ,
2026-10-02 19:30:03 +02:00
] . filter ( Boolean ) ;
function startPage ( ) {
let v = null ;
try { v = localStorage . getItem ( 'nui_start_page' ) ; } catch ( e ) { }
Home: neue Startseite mit Weiterlesen, Weiterhören, Radio-Favoriten und Ambient; sw v87
- static/js/home.js rendert #tab-home aus dem, was die Seite schon hat:
zuletzt gelesenes Buch (und drei weitere), laufende oder nächste Folge mit
Restzeit und „N neue Folgen“, Favoriten-Sender (sonst die ersten
gespeicherten) und zuletzt gehörter Sender, Drift mit der zuletzt
gewählten Klangwelt und Sleep-Timer. Läuft im Browser, in diora desktop und
in der Android-App (dort mit static/js/ kopiert).
- app.js: Tab 'home' (TOP_TABS), window.__dioraAmbientStart() lädt Drift bei
Bedarf und startet es; die gewählte Klangwelt wird gemerkt und beim Laden
wiederhergestellt.
- podcasts-web.js: Startseite Home, wenn nichts anderes gewählt ist.
- native-podcast.js aus diora-android 7dcc0c6 (Home in der Startseiten-Wahl,
__dioraPodcastView).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:34:02 +02:00
const pages = startPages ( ) ;
return pages . some ( p => p . id === v ) ? v : pages [ 0 ] . id ;
2026-10-02 19:30:03 +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
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' ;
2026-10-19 18:21:03 +02:00
let streamSeen = false ; // podcastStreamSeen() sent for this visit (until the app was away 10 min)
let lastView = '' ;
let hiddenAt = 0 ;
document . addEventListener ( 'visibilitychange' , ( ) => {
if ( document . hidden ) { hiddenAt = Date . now ( ) ; return ; }
if ( hiddenAt && Date . now ( ) - hiddenAt > 10 * 60000 ) { streamSeen = false ; lastView = '' ; if ( view === 'stream' ) 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
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
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
// Downloads (Downloads.kt; see "downloads" further down): where the host has them.
const DL = ! ! N . podcastDownloads ;
let downloads = DL ? parse ( N . podcastDownloads ( ) , { } ) : { } ;
let dlSettings = DL ? parse ( N . podcastDownloadSettings ( ) , { } ) : { } ;
// All at a glance (Downloads.infoJson): paused, how many run and wait, done of all in this round.
let dlInfo = DL && N . podcastDownloadsInfo ? parse ( N . podcastDownloadsInfo ( ) , { } ) : { } ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
try { locked = localStorage . getItem ( 'nui_pod_locked' ) === '1' ; } catch ( e ) { }
try { view = localStorage . getItem ( 'nui_pod_view' ) || 'queue' ; } catch ( e ) { }
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-19 18:21:03 +02:00
const later = ( ) => state . later || [ ] ;
const v2 = ( ) => ! ! state . lists _v2 ;
const views = ( ) => ( v2 ( ) ? VIEWS _V2 : VIEWS ) ;
// A view of the other model (a start page, a notice, an older choice): its counterpart.
function fixView ( ) {
2026-10-19 19:17:33 +02:00
if ( ! v2 ( ) && view === 'browse' ) view = 'inbox' ;
2026-10-19 18:21:03 +02:00
if ( v2 ( ) && ( view === 'inbox' || view === 'episodes' ) ) view = 'stream' ;
if ( ! v2 ( ) && view === 'stream' ) view = 'inbox' ;
if ( ! v2 ( ) && view === 'later' ) view = 'queue' ;
}
// The play list's name.
const QNAME = ( ) => ( v2 ( ) ? 'Wiedergabeliste' : 'Warteschlange' ) ;
const inLater = e => later ( ) . some ( x => x . episode _ _id === e . id ) || ! ! ( e . raw && e . raw . in _later ) ;
2026-10-19 19:17:33 +02:00
// Favourite subscriptions (feed.favorite, docs/podcasts.md): ★ in Abos, the „★ Faves“ chips.
const isFave = feedId => { const f = feeds ( ) . find ( x => x . id === feedId ) ; return ! ! ( f && f . favorite ) ; } ;
let podFaves = false ;
try { podFaves = localStorage . getItem ( 'nui_pod_faves' ) === '1' ; } catch ( e ) { }
function setFaves ( on ) {
podFaves = ! ! on ;
try { localStorage . setItem ( 'nui_pod_faves' , podFaves ? '1' : '0' ) ; } catch ( e ) { }
render ( ) ;
scrollTop ( 0 ) ;
}
function toggleFeedFave ( f ) {
const on = ! f . favorite ;
f . favorite = on ;
N . podcastFeedSettings ( f . id , JSON . stringify ( { favorite : on } ) ) ;
toast ( on ? '★ Lieblings-Abo' : 'Kein Lieblings-Abo mehr' ) ;
render ( ) ;
}
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 || [ ] ;
Abos & Quellen: alle Abos an einem Ort (Navigation Phase 2)
Ein neuer Abschnitt (docs/sources.md, static/js/sources.js) für Podcasts,
YouTube-Kanäle, Hörbücher, Nachrichten und Blogs:
- ein Feld zum Hinzufügen: Adresse (Feed, Seite, YouTube) oder Name eines
Podcasts; die Art ist wählbar (Podcast, Hörbuch, Nachrichten, Blog);
/feeds/discover/ erkennt dafür Podcasts (Folgen mit Ton oder Video);
- eine Liste aller Abos mit Filtern (auch „Probleme“) und Suche;
- je Abo: Inhalt öffnen, Einstellungen (die vorhandenen Editoren), Art ändern,
abbestellen; OPML-Import und -Export.
In der Seitenleiste unten, auf dem Telefon im App-Menü; ⚙ in den Feeds führt
hierher. Abschnitte, die beim Laden schon gezeigt werden, füllen sich jetzt
auch (nav.js zeigt sie nach dem Laden aller Skripte noch einmal).
Podcast-UI aus diora-android 50c6f9c.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:23:12 +02:00
// "Abos & Quellen" (diora-web docs/sources.md, sources.js): where subscriptions are managed, if the page has it.
const hasSources = ( ) => ! ! window . DioraNav && DioraNav . sections ( ) . some ( s => s . id === 'sources' ) ;
const toSources = ( ) => DioraNav . go ( 'sources' ) ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
// Audiobooks (diora-web docs/audiobooks.md) are listened to in the library ("Bücher"), not here.
const isBook = f => ! ! f && f . kind === 'audiobook' ;
const podcastFeeds = ( ) => feeds ( ) . filter ( f => ! isBook ( f ) ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const settings = ( ) => state . settings || { } ;
const currentId = ( ) => ( player . kind === 'podcast' ? player . episodeId : 0 ) ;
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
}
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' ) ;
2026-10-14 18:05:23 +02:00
// The element is shared (radio and podcasts): so is its timer, or one hides the other's text early.
clearTimeout ( t . nuiTimer ) ;
t . nuiTimer = setTimeout ( ( ) => t . classList . remove ( 'nui-show' ) , 2600 ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
// GET from our server; a status other than 2xx throws (as a network error does).
async function getJson ( url ) {
const r = await fetch ( url ) ;
if ( ! r . ok ) throw new Error ( r . status ) ;
return r . json ( ) ;
}
// A full sheet (the player, shownotes, a podcast's info, statistics, downloads): the
// overlay — one id, the CSS and the TV's focus know it — its panel, the ⌄ at the top,
// and a tap beside the panel closes it. The caller fills the panel (top first) and
// puts the sheet on the page.
function podSheet ( cls , closeLabel , onClose , dialogLabel ) {
const sheet = el ( 'div' ) ;
sheet . id = 'nui-pod-player' ;
if ( cls ) sheet . className = cls ;
const panel = el ( 'div' , 'nui-pod-player-panel' ) ;
if ( dialogLabel ) {
panel . setAttribute ( 'role' , 'dialog' ) ;
panel . setAttribute ( 'aria-label' , dialogLabel ) ;
}
const top = el ( 'div' , 'nui-pod-player-top' ) ;
top . append ( button ( '⌄' , closeLabel , onClose ) ) ;
sheet . append ( panel ) ;
sheet . addEventListener ( 'click' , ev => { if ( ev . target === sheet ) onClose ( ) ; } ) ;
2026-10-16 21:06:41 +02:00
swipeToClose ( panel , onClose ) ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
return { sheet , panel , top } ;
}
2026-10-16 21:06:41 +02:00
// A sheet (the player, an episode's details …) follows the finger down from its top and
// closes when let go far enough down (or flicked); otherwise it springs back. Only while
// nothing under the finger can scroll up any more, and never on a slider.
function swipeToClose ( panel , onClose ) {
let g = null ;
const scrolledUp = target => {
for ( let n = target ; n && n !== panel . parentNode ; n = n . parentNode ) {
if ( n . nodeType === 1 && n . scrollTop > 0 && n . scrollHeight > n . clientHeight ) return true ;
}
return false ;
} ;
panel . addEventListener ( 'touchstart' , e => {
g = null ;
if ( e . touches . length !== 1 || e . target . closest ( 'input, textarea, select, [contenteditable="true"]' ) ) return ;
if ( scrolledUp ( e . target ) ) return ;
g = { y : e . touches [ 0 ] . clientY , x : e . touches [ 0 ] . clientX , t : Date . now ( ) , on : false , dy : 0 } ;
} , { passive : true } ) ;
panel . addEventListener ( 'touchmove' , e => {
if ( ! g || e . touches . length !== 1 ) return ;
const t = e . touches [ 0 ] ;
const dy = t . clientY - g . y ;
if ( ! g . on ) {
if ( dy < 0 || Math . abs ( t . clientX - g . x ) > Math . abs ( dy ) ) { g = null ; return ; }
if ( dy < 10 ) return ;
g . on = true ;
panel . style . transition = 'none' ;
}
e . preventDefault ( ) ; // the sheet moves, the page under it doesn't scroll
g . dy = dy ;
panel . style . transform = ` translateY( ${ Math . max ( 0 , dy ) } px) ` ;
} , { passive : false } ) ;
const end = ( ) => {
if ( ! g || ! g . on ) { g = null ; return ; }
const fast = g . dy / Math . max ( 1 , Date . now ( ) - g . t ) > 0.6 ; // a flick: px per ms
const far = g . dy > Math . min ( 160 , panel . offsetHeight * 0.25 ) ;
g = null ;
panel . style . transition = 'transform 0.18s ease-out' ;
if ( far || fast ) {
panel . style . transform = 'translateY(100%)' ;
setTimeout ( onClose , 150 ) ;
} else {
panel . style . transform = '' ;
setTimeout ( ( ) => { panel . style . transition = '' ; } , 200 ) ;
}
} ;
panel . addEventListener ( 'touchend' , end , { passive : true } ) ;
panel . addEventListener ( 'touchcancel' , end , { 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
// 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 ,
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
yt : isYouTube ( o . episode _ _audio _url ) ,
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
} ;
}
return {
id : o . id , title : o . title , feed : o . feed _ _title || ( feedView && feedView . feed . title ) , feedId : o . feed _ _id ,
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 ,
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
yt : isYouTube ( o . audio _url ) ,
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
} ;
}
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
// "Kapitel 7/24 · <book>" while a chapter of an audiobook plays (playerState().book).
function bookLine ( p ) {
const b = p && p . book ;
if ( ! b || ! b . chapters ) return '' ;
return ` Kapitel ${ b . index + 1 } / ${ b . chapters } ` + ( b . title || p . feed ? ' · ' + plain ( b . title || p . feed ) : '' ) ;
}
// A video of a subscribed YouTube channel (diora-web docs/youtube.md): its sound
// is found on this device; the feed knows no length until it has played.
function isYouTube ( url ) {
return /^https:\/\/www\.youtube\.com\/watch\?v=/ . test ( url || '' ) ;
}
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 ; }
2026-10-19 19:17:33 +02:00
// An audiobook's chapter: heard in the library (its place, the rest of the book), never queued.
if ( isBook ( feeds ( ) . find ( f => f . id === e . feedId ) ) && openAudiobook ( e . feedId ) ) return ;
2026-10-19 18:21:03 +02:00
// Two lists: ▶ puts it first in the Wiedergabeliste (in the Merkliste it stays until heard).
if ( v2 ( ) ) {
if ( inQueue ( e . id ) ) N . podcastMove ( e . id , 0 ) ;
else N . podcastEnqueue ( JSON . stringify ( [ e . raw ] ) , 'front' , currentId ( ) ) ;
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf 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 ) ) ;
2026-10-19 18:21:03 +02:00
toast ( 'In der ' + QNAME ( ) + ( label ? ': ' + label . label . toLowerCase ( ) : '' ) ) ;
}
2026-10-19 20:19:22 +02:00
// An episode into a research project (diora-web docs/projects.md); from the player with
// the place it's at (a time mark „ab 12:34“).
function linkProject ( e , position ) {
if ( ! window . DioraProjects ) return ;
const r = e . raw || queue ( ) . find ( q => q . episode _ _id === e . id ) || later ( ) . find ( q => q . episode _ _id === e . id ) || { } ;
const d = storedDetails ( e . id ) || { } ;
DioraProjects . link ( 'episode' , e . id , {
title : plain ( e . title || r . title || r . episode _ _title || d . title || '' ) ,
podcast : plain ( e . feed || r . feed _ _title || r . episode _ _feed _ _title || '' ) ,
author : plain ( d . author || r . author || '' ) ,
date : e . date || r . pub _date || r . episode _ _pub _date || d . pub _date || null ,
url : d . link || r . link || r . audio _url || r . episode _ _audio _url || '' ,
feed : e . feedId || r . feed _ _id || r . episode _ _feed _ _id || null ,
position : Math . max ( 0 , Math . round ( position || 0 ) ) ,
} ) ;
}
const projectItem = e => window . DioraProjects && { icon : '◇' , label : 'Mit Projekt verknüpfen …' ,
hint : ( DioraProjects . of ( 'episode' , e . id ) || [ ] ) . map ( p => p . name ) . join ( ', ' ) || null , action : ( ) => linkProject ( e , 0 ) } ;
2026-10-19 19:17:33 +02:00
function openAudiobook ( feedId ) {
if ( ! feedId || typeof window . _ _dioraAudiobookOpen !== 'function' ) return false ;
closeNotes ( ) ;
closePlayer ( ) ;
if ( window . DioraNav ) DioraNav . go ( 'audiobooks' ) ;
window . _ _dioraAudiobookOpen ( feedId ) ;
return true ;
}
2026-10-19 18:21:03 +02:00
// + : onto the Merkliste, or off it again.
function toggleLater ( e ) {
if ( ! N . podcastLaterAdd ) return ;
if ( inLater ( e ) ) {
N . podcastLaterRemove ( JSON . stringify ( [ e . id ] ) ) ;
if ( e . raw ) e . raw . in _later = false ;
toast ( 'Aus der Merkliste genommen' ) ;
} else {
N . podcastLaterAdd ( JSON . stringify ( [ e . raw ] ) ) ;
if ( e . raw ) e . raw . in _later = true ;
toast ( 'Auf der Merkliste' ) ;
}
for ( const l of [ epList && epList . items , feedView && feedView . episodes ] ) {
for ( const o of l || [ ] ) if ( o . id === e . id ) o . in _later = e . raw . in _later ;
}
render ( ) ;
}
function laterButton ( e ) {
const on = inLater ( e ) ;
return button ( on ? '✓' : '+ ' , on ? 'Auf der Merkliste – antippen nimmt es heraus' : 'Auf die Merkliste' , ( ) => toggleLater ( e ) ,
'nui-pod-mini' + ( on ? ' on' : '' ) ) ;
}
function starButton ( e ) {
const on = ! ! ( e . raw && e . raw . favorite ) ;
return button ( on ? '★' : '☆' , on ? 'Favorit' : 'Als Favorit markieren' , ( ) => toggleFavorite ( e , star => { e . raw . favorite = star ; } ) ,
'nui-pod-mini nui-pod-starbtn' + ( on ? ' on' : '' ) ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
// 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“.
2026-10-19 18:21:03 +02:00
const DISCARD _HINT _OLD = 'aus Warteschlange und Neu, Datei auf allen Geräten löschen – nicht als gehört' ;
const DISCARD _HINT _V2 = 'aus Wiedergabeliste und Merkliste, Datei auf allen Geräten löschen – nicht als gehört' ;
const discardHint = ( ) => ( v2 ( ) ? DISCARD _HINT _V2 : DISCARD _HINT _OLD ) ;
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 discardItem = ( e , opts ) => N . podcastDiscard &&
2026-10-19 18:21:03 +02:00
{ icon : '⊠' , svg : 'trash' , label : 'Folge löschen' , hint : discardHint ( ) , action : ( ) => discardOne ( e , ( opts && opts . list ) || '' , ( opts && opts . filters ) || [ ] ) } ;
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
// 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 ) {
2026-10-19 18:21:03 +02:00
const hide = list === 'queue' || list === 'inbox' || list === 'later' || list === 'downloads' ||
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
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. ` , [
2026-10-19 18:21:03 +02:00
{ icon : '⊠' , svg : 'trash' , label : 'Löschen' , hint : discardHint ( ) , danger : true , action : ( ) => {
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
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' ) } ,
2026-10-19 18:21:03 +02:00
{ icon : '✓' , label : 'Als gehört markieren' , hint : v2 ( ) ? 'verlässt beide Listen' : 'verlässt die Warteschlange' , action : ( ) => markPlayed ( 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
... dlMenuItems ( e ) ,
2026-10-19 18:21:03 +02:00
v2 ( ) && N . podcastLaterAdd && ! inLater ( e ) && { icon : '+ ' , label : 'Auf die Merkliste' , hint : 'für später' , action : ( ) => toggleLater ( e ) } ,
{ icon : '✕' , svg : 'listx' , label : 'Aus der ' + QNAME ( ) + ' nehmen' , hint : dlDone ( e . id ) && ! v2 ( ) ? 'Datei bleibt' : 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
action : ( ) => N . podcastDequeue ( e . id ) } ,
2026-10-19 20:19:22 +02:00
projectItem ( 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
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 ) {
2026-10-19 18:21:03 +02:00
if ( v2 ( ) ) { listsMenu ( e , opts ) ; 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 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
] ) ;
}
2026-10-19 18:21:03 +02:00
// An episode's menu with the two lists (Merkliste, Strom, a podcast's page).
function listsMenu ( e , opts ) {
const queued = inQueue ( e . id ) ;
const kept = inLater ( e ) ;
openSheet ( e . title , [
{ icon : '▶' , label : 'Abspielen' , hint : queued ? null : 'kommt an den Anfang der Wiedergabeliste' , action : ( ) => play ( e ) } ,
{ icon : '☰' , label : 'Folge ansehen' , hint : 'Shownotes, Kapitel, alles zu dieser Folge' , action : ( ) => openEpisode ( e , opts ) } ,
favoriteItem ( e ) ,
N . podcastLaterAdd && { icon : kept ? '✕' : '+ ' , svg : kept ? 'listx' : null , label : kept ? 'Aus der Merkliste nehmen' : 'Auf die Merkliste' ,
hint : kept ? null : 'für später; wird geladen' , action : ( ) => toggleLater ( e ) } ,
! queued && { icon : '⤴' , label : 'Als Nächstes hören' , hint : 'in die Wiedergabeliste nach der laufenden Folge' , action : ( ) => enqueue ( e , 'after_current' ) } ,
! queued && { icon : '⇊' , label : 'Ans Ende der Wiedergabeliste' , action : ( ) => enqueue ( e , 'back' ) } ,
queued && { icon : '✕' , svg : 'listx' , label : 'Aus der Wiedergabeliste nehmen' , action : ( ) => N . podcastDequeue ( e . id ) } ,
! e . played && { icon : '✓' , label : 'Als gehört markieren' , hint : kept || queued ? 'verlässt die Listen' : null , action : ( ) => markPlayed ( e ) } ,
e . played && { icon : '↺' , label : 'Als ungehört markieren' , action : ( ) => N . podcastMarkPlayed ( e . id , false ) } ,
... dlMenuItems ( e ) ,
2026-10-19 20:19:22 +02:00
projectItem ( e ) ,
2026-10-19 18:21:03 +02:00
discardItem ( e , opts ) ,
selectItem ( e , opts ) ,
] ) ;
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
function settingsMenu ( ) {
const s = settings ( ) ;
const choose = ( name , title ) => openSheet ( title , POSITIONS . map ( p => ( {
icon : s [ name ] === p . value ? '●' : '○' ,
label : p . label ,
active : s [ name ] === p . value ,
action : ( ) => N . podcastSettings ( JSON . stringify ( { [ name ] : p . value } ) ) ,
} ) ) ) ;
const label = v => ( POSITIONS . find ( p => p . value === v ) || POSITIONS [ 0 ] ) . label ;
const prefs = devicePrefs ( ) ;
// Grouped like AntennaPod's settings; each group opens its own list.
const group = ( icon , title , hint , items ) => ( { icon , label : title , hint , action : ( ) => openSheet ( title , items ( ) ) } ) ;
openSheet ( 'Podcasts' , [
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 ) } ,
2026-10-19 18:21:03 +02:00
'lists_v2' in state && N . podcastListsSwitch && { icon : '☷' , label : 'Wiedergabeliste und Merkliste' ,
hint : v2 ( ) ? 'an: kurze Wiedergabeliste, lange Merkliste, der Strom statt „Neu“' : 'aus: eine Warteschlange, „Neu“ und „Folgen“' ,
action : ( ) => listsSwitch ( ! v2 ( ) ) } ,
group ( '≡' , QNAME ( ) , 'wohin neue Folgen kommen, gehörte entfernen' , ( ) => [
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf 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 : '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 } ,
Abos & Quellen: alle Abos an einem Ort (Navigation Phase 2)
Ein neuer Abschnitt (docs/sources.md, static/js/sources.js) für Podcasts,
YouTube-Kanäle, Hörbücher, Nachrichten und Blogs:
- ein Feld zum Hinzufügen: Adresse (Feed, Seite, YouTube) oder Name eines
Podcasts; die Art ist wählbar (Podcast, Hörbuch, Nachrichten, Blog);
/feeds/discover/ erkennt dafür Podcasts (Folgen mit Ton oder Video);
- eine Liste aller Abos mit Filtern (auch „Probleme“) und Suche;
- je Abo: Inhalt öffnen, Einstellungen (die vorhandenen Editoren), Art ändern,
abbestellen; OPML-Import und -Export.
In der Seitenleiste unten, auf dem Telefon im App-Menü; ⚙ in den Feeds führt
hierher. Abschnitte, die beim Laden schon gezeigt werden, füllen sich jetzt
auch (nav.js zeigt sie nach dem Laden aller Skripte noch einmal).
Podcast-UI aus diora-android 50c6f9c.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:23:12 +02:00
hasSources ( ) && { icon : '⚙' , label : 'Abos & Quellen' , hint : 'alle Abos an einem Ort: Podcasts, YouTube, Nachrichten, Blogs' , action : toSources } ,
] . filter ( Boolean ) ) ,
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
{ icon : '▤' , label : 'Hörstatistik' , action : openStats } ,
] ) ;
}
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' } ,
] ;
2026-10-19 18:21:03 +02:00
function listsSwitch ( on ) {
N . podcastListsSwitch ( ! ! on ) ;
try { localStorage . setItem ( 'nui_pod_lists' , '1' ) ; } catch ( e ) { }
state . lists _v2 = ! ! on ; // until the state comes anew
view = on ? 'later' : 'queue' ;
toast ( on ? 'Die Warteschlange ist jetzt deine Merkliste; die Wiedergabeliste beginnt leer'
: 'Die Merkliste steht jetzt am Ende der Warteschlange' ) ;
render ( ) ;
}
// The two lists come on by themselves once per device, when the server has them (the
// plan of 2026-10-19, docs/podcasts.md); the settings switch turns them off again.
function listsOnOnce ( ) {
if ( ! ( 'lists_v2' in state ) || state . lists _v2 || ! N . podcastListsSwitch ) return ;
let asked = false ;
try { asked = localStorage . getItem ( 'nui_pod_lists' ) === '1' ; } catch ( e ) { }
if ( asked ) return ;
listsSwitch ( true ) ;
}
2026-10-02 16:36:00 +02:00
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 ( ) ;
Home: neue Startseite mit Weiterlesen, Weiterhören, Radio-Favoriten und Ambient; sw v87
- static/js/home.js rendert #tab-home aus dem, was die Seite schon hat:
zuletzt gelesenes Buch (und drei weitere), laufende oder nächste Folge mit
Restzeit und „N neue Folgen“, Favoriten-Sender (sonst die ersten
gespeicherten) und zuletzt gehörter Sender, Drift mit der zuletzt
gewählten Klangwelt und Sleep-Timer. Läuft im Browser, in diora desktop und
in der Android-App (dort mit static/js/ kopiert).
- app.js: Tab 'home' (TOP_TABS), window.__dioraAmbientStart() lädt Drift bei
Bedarf und startet es; die gewählte Klangwelt wird gemerkt und beim Laden
wiederhergestellt.
- podcasts-web.js: Startseite Home, wenn nichts anderes gewählt ist.
- native-podcast.js aus diora-android 7dcc0c6 (Home in der Startseiten-Wahl,
__dioraPodcastView).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:34:02 +02:00
const label = id => ( startPages ( ) . find ( p => p . id === id ) || startPages ( ) [ 0 ] ) . label ;
2026-10-02 19:30:03 +02:00
return { icon : '⌂' , label : 'Startseite' , hint : label ( now ) + ( IN _APP ? ' · beim Öffnen der App' : ' · beim Öffnen von diora' ) ,
Home: neue Startseite mit Weiterlesen, Weiterhören, Radio-Favoriten und Ambient; sw v87
- static/js/home.js rendert #tab-home aus dem, was die Seite schon hat:
zuletzt gelesenes Buch (und drei weitere), laufende oder nächste Folge mit
Restzeit und „N neue Folgen“, Favoriten-Sender (sonst die ersten
gespeicherten) und zuletzt gehörter Sender, Drift mit der zuletzt
gewählten Klangwelt und Sleep-Timer. Läuft im Browser, in diora desktop und
in der Android-App (dort mit static/js/ kopiert).
- app.js: Tab 'home' (TOP_TABS), window.__dioraAmbientStart() lädt Drift bei
Bedarf und startet es; die gewählte Klangwelt wird gemerkt und beim Laden
wiederhergestellt.
- podcasts-web.js: Startseite Home, wenn nichts anderes gewählt ist.
- native-podcast.js aus diora-android 7dcc0c6 (Home in der Startseiten-Wahl,
__dioraPodcastView).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:34:02 +02:00
action : ( ) => openSheet ( 'Beim Öffnen zeigen …' , startPages ( ) . filter ( p => p . id !== 'radio' || ! IN _APP ||
2026-10-02 19:30:03 +02:00
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.
2026-10-16 21:06:41 +02:00
function selectionBar ( items , actions , hint ) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf 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 bar = el ( 'div' , 'nui-pod-selbar' ) ;
const all = items . length > 0 && items . every ( e => selected . has ( e . id ) ) ;
bar . append (
button ( '✕' , 'Auswahl beenden' , stopSelecting ) ,
2026-10-16 21:06:41 +02:00
el ( 'span' , 'nui-pod-selcount' , selected . size ? ` ${ selected . size } ausgewählt ` : ( hint || 'Folgen antippen' ) ) ,
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf 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 ( 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 ★.' } ,
2026-10-19 18:21:03 +02:00
{ id : 'queued' , get label ( ) { return v2 ( ) ? 'Wiedergabeliste' : 'Warteschlange' ; } , test : o => inQueue ( o . id ) ,
get none ( ) { return 'Keine davon steht in der ' + QNAME ( ) + '.' ; } } ,
{ id : 'later' , label : 'Merkliste' , v2 : true , test : o => inLater ( { id : o . id , raw : o } ) , none : 'Keine davon steht auf der Merkliste.' } ,
2026-10-19 19:17:33 +02:00
{ id : 'fave_feeds' , label : '★ Faves' , v2 : true , test : o => isFave ( o . feed _ _id ) , none : 'Keine Folgen deiner Lieblings-Abos.' } ,
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
{ 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 = [
2026-10-19 17:23:06 +02:00
// Days and weeks (Heute, Gestern, …), in each the podcasts that seldom come first (timeSections).
{ id : 'rare' , label : 'Nach Zeitraum, seltene zuerst' , server : 'new' , all : true , key : o => - ( Date . parse ( o . pub _date ) || - Infinity ) } ,
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
{ 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' ;
}
2026-10-19 17:23:06 +02:00
// ---- by time: Heute, Gestern, Diese Woche, Diesen Monat, then the months (after Kino's feed;
// diora-web feeds.js the same for blogs). Within one, with rare: the podcasts that seldom come
// first (feed.interval_days from the server: the mean gap of its newest episodes), so a weekly
// show isn't lost among the daily news.
const MONTHS = [ 'Januar' , 'Februar' , 'März' , 'April' , 'Mai' , 'Juni' , 'Juli' , 'August' , 'September' , 'Oktober' ,
'November' , 'Dezember' ] ;
function timeLabel ( iso , now ) {
const t = Date . parse ( iso ) ;
if ( ! t ) return 'Ohne Datum' ;
const midnight = new Date ( now ) ;
midnight . setHours ( 0 , 0 , 0 , 0 ) ;
const days = Math . floor ( ( midnight . getTime ( ) - t ) / 86400000 ) + 1 ; // 0: today, 1: yesterday
if ( t > now ) return 'Demnächst' ;
if ( t >= midnight . getTime ( ) ) return 'Heute' ;
if ( days <= 1 ) return 'Gestern' ;
if ( days < 7 ) return 'Diese Woche' ;
if ( days < 30 ) return 'Diesen Monat' ;
const d = new Date ( t ) ;
return MONTHS [ d . getMonth ( ) ] + ( d . getFullYear ( ) === new Date ( now ) . getFullYear ( ) ? '' : ' ' + d . getFullYear ( ) ) ;
}
function intervalOf ( feedId ) {
const f = feeds ( ) . find ( x => x . id === feedId ) ;
return f && typeof f . interval _days === 'number' ? f . interval _days : 0 ;
}
// items (ep() shapes), newest first already: [{label, items}].
function timeSections ( items , rare ) {
const now = Date . now ( ) ;
const out = [ ] ;
for ( const e of items ) {
const label = timeLabel ( e . date , now ) ;
const last = out [ out . length - 1 ] ;
if ( last && last . label === label ) last . items . push ( e ) ;
else out . push ( { label , items : [ e ] } ) ;
}
if ( rare ) {
const gap = new Map ( ) ;
for ( const sec of out ) {
sec . items = sec . items . map ( ( e , i ) => [ e , i ] ) . sort ( ( a , b ) => {
for ( const e of [ a [ 0 ] , b [ 0 ] ] ) if ( ! gap . has ( e . feedId ) ) gap . set ( e . feedId , intervalOf ( e . feedId ) ) ;
return ( gap . get ( b [ 0 ] . feedId ) - gap . get ( a [ 0 ] . feedId ) ) || a [ 1 ] - b [ 1 ] ;
} ) . map ( x => x [ 0 ] ) ;
}
}
return out ;
}
// The list's rows, under headings by time when sorted so.
function appendRows ( list , items , sort , rowOf ) {
if ( sort !== 'new' && sort !== 'rare' ) {
for ( const e of items ) list . append ( rowOf ( e ) ) ;
return ;
}
for ( const sec of timeSections ( items , sort === 'rare' ) ) {
list . append ( el ( 'div' , 'nui-pod-when' , sec . label ) ) ;
for ( const e of sec . items ) list . append ( rowOf ( 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
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 ) {
2026-10-19 18:21:03 +02:00
if ( ( f . app && ! DL ) || ( f . all && ! allEpisodes ) || ( f . v2 && ! v2 ( ) ) ) continue ;
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 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 ) ;
2026-10-16 21:06:41 +02:00
loadFolderOrder ( ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf 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-16 21:06:41 +02:00
setTimeout ( loadFolderOrder , 1500 ) ;
// Pull to refresh (native-gestures.js).
window . _ _dioraPodcastRefresh = refresh ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
// ---- 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 {
2026-10-19 18:21:03 +02:00
fixView ( ) ;
if ( view === 'stream' && lastView !== 'stream' ) epList = null ; // fetched anew: what came since shows
if ( view === 'stream' && ! streamSeen ) {
// The Strom looked at: what came since the last visit keeps its mark for this one.
streamSeen = true ;
if ( N . podcastStreamSeen ) N . podcastStreamSeen ( ) ;
}
lastView = view ;
const tabs = el ( 'div' , 'nui-pod-tabs' + ( v2 ( ) ? ' nui-pod-tabs-v2' : '' ) ) ;
for ( const v of views ( ) ) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf 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' , '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 ) ;
2026-10-16 21:06:41 +02:00
if ( view === 'queue' ) folderChips ( queue ( ) . map ( ep ) , e => e . feedId ) ;
else if ( view === 'inbox' ) folderChips ( inbox ( ) . map ( ep ) , e => e . feedId ) ;
2026-10-19 18:21:03 +02:00
else if ( view === 'later' ) folderChips ( later ( ) . map ( ep ) , e => e . feedId ) ;
2026-10-19 19:17:33 +02:00
else if ( view === 'feeds' ) folderChips ( podcastFeeds ( ) , f => f . id , v2 ( ) ) ;
2026-10-19 18:21:03 +02:00
else if ( ( view === 'episodes' || view === 'stream' ) && ! selected ) folderChips ( podcastFeeds ( ) , f => f . 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
if ( view === 'queue' ) renderQueue ( ) ;
else if ( view === 'inbox' ) renderInbox ( ) ;
2026-10-19 18:21:03 +02:00
else if ( view === 'later' ) renderLater ( ) ;
else if ( view === 'episodes' || view === 'stream' ) renderEpisodes ( ) ;
2026-10-19 19:17:33 +02:00
else if ( view === 'browse' ) renderBrowse ( ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf 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 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 ) ) ) ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
if ( e . yt ) meta . push ( 'YouTube' ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if ( opts . showDate && e . date ) meta . push ( day ( e . date ) ) ;
if ( opts . showFeed && e . feed ) meta . push ( e . feed ) ;
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-19 18:21:03 +02:00
if ( list === 'stream' ) {
// The Strom: ★, + (Merkliste), ▶ (first in the Wiedergabeliste); the rest by a long press.
if ( opts . fresh ) row . classList . add ( 'nui-pod-fresh' ) ;
row . append ( ... [ starButton ( e ) , N . podcastLaterAdd && laterButton ( e ) , playButton ( e ) ] . filter ( Boolean ) ) ;
row . querySelectorAll ( ':scope > .nui-pod-mini' ) . forEach ( b => b . addEventListener ( 'click' , ev => ev . stopPropagation ( ) ) ) ;
} else {
if ( v2 ( ) && list === 'feed' && N . podcastLaterAdd ) row . append ( laterButton ( e ) ) ;
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 : '' ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
rowGestures ( row , menu , swipes && ( act => swipeDone ( e , swipes , act , opts . filters || [ ] ) ) , swipes ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
return row ;
}
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 ) {
2026-10-19 18:21:03 +02:00
// Two lists: downloads follow the lists; a podcast's page plays (and + keeps).
if ( v2 ( ) && list === 'feed' ) return false ;
2026-10-02 19:30:03 +02:00
return ( list === 'episodes' || list === 'feed' ) && DL && ! ! ( e . raw . audio _url || e . raw . episode _ _audio _url ) ;
}
2026-10-19 18:21:03 +02:00
function playButton ( e ) {
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 ;
}
2026-10-02 19:30:03 +02:00
function primaryButton ( e , list ) {
2026-10-19 18:21:03 +02:00
if ( list === 'queue' || list === 'downloads' || list === 'later' || ( v2 ( ) && list === 'feed' ) ) return playButton ( e ) ;
2026-10-02 19:30:03 +02:00
if ( dlPrimary ( e , list ) ) return dlButton ( e ) ;
const queued = inQueue ( e . id ) || ( list === 'inbox' && e . inQueue ) ;
2026-10-19 18:21:03 +02:00
return queued ? button ( '✓' , 'In der ' + QNAME ( ) , ( ) => toast ( 'Schon in der ' + QNAME ( ) ) , 'nui-pod-queued' )
2026-10-02 19:30:03 +02:00
: 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' } ,
2026-10-19 18:21:03 +02:00
// The two lists (docs/podcasts.md).
later : { label : 'Merkliste' , v2 : true , left : 'unlater' , right : 'queue' } ,
stream : { label : 'Strom' , v2 : true , left : 'favorite' , right : 'later' } ,
2026-10-02 19:30:03 +02:00
} ;
const SWIPE _ACTIONS = [
{ id : 'none' , label : 'Nichts' , icon : '' } ,
{ id : 'queue' , label : 'Einreihen' , icon : '+' } ,
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
// Where there are files.
{ id : 'dequeue' , label : DL ? 'Aus Warteschlange · Datei bleibt' : 'Aus der Warteschlange' ,
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' } ,
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' } ,
2026-10-19 18:21:03 +02:00
{ id : 'later' , label : 'Auf die Merkliste' , icon : '+ ' , host : 'podcastLaterAdd' } ,
{ id : 'unlater' , label : 'Aus der Merkliste' , icon : '✕' , svg : 'listx' , host : 'podcastLaterRemove' } ,
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 : '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' } ;
2026-10-19 18:21:03 +02:00
case 'later' : return { title : 'Merkliste' } ;
case 'unlater' : return { title : 'Aus der Merkliste' , hint : 'nicht gehört' } ;
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
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 ;
2026-10-19 18:21:03 +02:00
if ( act === 'later' ) {
if ( inLater ( e ) ) { toast ( 'Schon auf der Merkliste' ) ; return true ; }
offerUndo ( e , 'Auf der Merkliste' , { run : ( ) => {
N . podcastLaterAdd ( JSON . stringify ( [ o ] ) ) ;
o . in _later = true ;
render ( ) ;
} } ) ;
return true ;
}
if ( act === 'unlater' ) {
if ( ! inLater ( e ) ) { toast ( 'Nicht auf der Merkliste' ) ; return true ; }
const hide = list === 'later' ;
offerUndo ( e , 'Aus der Merkliste' , { hide , svg : 'listx' , run : ( ) => {
N . podcastLaterRemove ( JSON . stringify ( [ e . id ] ) ) ;
o . in _later = false ;
if ( ! hide ) render ( ) ;
} } ) ;
return ! hide ;
}
2026-10-02 19:30:03 +02:00
if ( act === 'queue' ) {
2026-10-19 18:21:03 +02:00
if ( inQueue ( e . id ) ) { toast ( 'Schon in der ' + QNAME ( ) ) ; return true ; }
2026-10-02 19:30:03 +02:00
// 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
2026-10-19 18:21:03 +02:00
? list === 'inbox' || list === 'later' || ( list === 'queue' && devicePrefs ( ) . remove _played !== false ) ||
2026-10-02 19:30:03 +02:00
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 ( ) {
2026-10-16 21:06:41 +02:00
const all = queue ( ) . map ( ep ) ;
if ( ! all . length ) {
2026-10-19 18:21:03 +02:00
empty ( v2 ( ) ? 'Die Wiedergabeliste ist leer. ▶ bei einer Folge – in der Merkliste, im Strom oder bei einem Abo – spielt sie und stellt sie hierher; ist die Liste durch, hört der Player auf.'
: 'Die Warteschlange ist leer. Unter „Neu“ oder bei einem Abo tippst du auf „+“, um Folgen einzureihen.' ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf 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 ;
}
2026-10-16 21:06:41 +02:00
const items = all . filter ( e => inFolder ( e . feedId ) ) ;
2026-10-19 18:21:03 +02:00
if ( ! items . length ) { empty ( folderEmpty ( 'in der ' + QNAME ( ) ) ) ; 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 ( ) ; } } ,
2026-10-19 18:21:03 +02:00
{ label : v2 ( ) ? 'Aus der Liste' : 'Aus Warteschlange' , svg : 'listx' , run : ids => { N . podcastDequeueAll ( JSON . stringify ( ids ) ) ; 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 ( ) ,
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf 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 ( ) : '' ;
2026-10-16 21:06:41 +02:00
head . append ( el ( 'div' , 'nui-pod-summary' , ` ${ items . length } ${ items . length === 1 ? 'Folge' : 'Folgen' } · ${ minutes ( total ) } ${ kept } ` +
( podFolder != null && items . length < all . length ? ` · von ${ all . 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
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 ) {
2026-10-16 21:06:41 +02:00
if ( ! locked && ! keepSorted ( ) && podFolder == 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
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 ) ;
}
2026-10-19 18:21:03 +02:00
// The Merkliste: by podcast, each one folds (this device remembers which are open); in a
// podcast its newest episode first. Heard or „Folge löschen“ takes an episode off.
let laterOpen = new Set ( ) ;
try { laterOpen = new Set ( JSON . parse ( localStorage . getItem ( 'nui_pod_later_open' ) || '[]' ) ) ; } catch ( e ) { }
function toggleLaterOpen ( feedId ) {
if ( laterOpen . has ( feedId ) ) laterOpen . delete ( feedId ) ; else laterOpen . add ( feedId ) ;
try { localStorage . setItem ( 'nui_pod_later_open' , JSON . stringify ( [ ... laterOpen ] ) ) ; } catch ( e ) { }
render ( ) ;
}
function renderLater ( ) {
const all = later ( ) . map ( ep ) ;
if ( ! all . length ) {
empty ( 'Die Merkliste ist leer. Mit + im Strom oder bei einem Abo merkst du dir Folgen; neue Folgen deiner Stamm-Podcasts kommen von selbst hierher (und werden geladen).' ) ;
return ;
}
const items = all . filter ( e => inFolder ( e . feedId ) ) ;
if ( ! items . length ) { empty ( folderEmpty ( 'auf der Merkliste' ) ) ; return ; }
if ( selected ) {
root . append ( selectionBar ( items , [
{ label : 'Abspielen' , run : ids => {
N . podcastEnqueue ( JSON . stringify ( items . filter ( e => ids . includes ( e . id ) ) . map ( e => e . raw ) ) , 'front' , currentId ( ) ) ;
toast ( ` ${ nEpisodes ( ids . length ) } vorn in der Wiedergabeliste ` ) ;
stopSelecting ( ) ;
} } ,
{ label : 'In die Liste' , run : ids => {
N . podcastEnqueue ( JSON . stringify ( items . filter ( e => ids . includes ( e . id ) ) . map ( e => e . raw ) ) , 'back' , currentId ( ) ) ;
toast ( ` ${ nEpisodes ( ids . length ) } am Ende der Wiedergabeliste ` ) ;
stopSelecting ( ) ;
} } ,
{ label : 'Gehört' , run : ids => { N . podcastMarkPlayedAll ( JSON . stringify ( ids ) , true ) ; stopSelecting ( ) ; } } ,
{ label : 'Herausnehmen' , svg : 'listx' , run : ids => { N . podcastLaterRemove ( JSON . stringify ( ids ) ) ; stopSelecting ( ) ; } } ,
DL && { label : 'Laden' , run : ids => downloadMany ( items . filter ( e => ids . includes ( e . id ) ) ) } ,
discardAction ( ) ,
] ) ) ;
} else {
const total = items . reduce ( ( n , e ) => n + shown ( Math . max ( 0 , ( e . duration || 0 ) - ( e . pos || 0 ) ) , e . feedId ) , 0 ) ;
const head = el ( 'div' , 'nui-pod-head' ) ;
head . append ( el ( 'div' , 'nui-pod-summary' , ` ${ nEpisodes ( items . length ) } · ${ minutes ( total ) } ` +
( podFolder != null && items . length < all . length ? ` · von ${ all . length } ` : '' ) ) ,
listTools ( [ button ( '☐' , 'Mehrere auswählen' , ( ) => startSelecting ( ) ) ] ) ) ;
root . append ( head ) ;
}
// By podcast, in the order of their newest episode (the list comes newest first).
const groups = new Map ( ) ;
for ( const e of items ) {
if ( ! groups . has ( e . feedId ) ) groups . set ( e . feedId , [ ] ) ;
groups . get ( e . feedId ) . push ( e ) ;
}
const list = el ( 'div' , 'nui-pod-list nui-pod-later' ) ;
for ( const [ feedId , eps ] of groups ) {
const open = ! ! selected || laterOpen . has ( feedId ) || groups . size === 1 ;
const head = el ( 'button' , 'nui-pod-group' + ( open ? ' open' : '' ) ) ;
head . type = 'button' ;
head . setAttribute ( 'aria-expanded' , open ? 'true' : 'false' ) ;
head . append ( art ( eps [ 0 ] . feedArt , null , null , eps [ 0 ] . feed ) ) ;
const text = el ( 'span' , 'nui-pod-group-text' ) ;
text . append ( el ( 'span' , 'nui-pod-group-title' , eps [ 0 ] . feed || '' ) ,
el ( 'span' , 'nui-pod-meta' , open ? nEpisodes ( eps . length ) : ` ${ nEpisodes ( eps . length ) } · ${ eps [ 0 ] . title } ` ) ) ;
head . append ( text , el ( 'span' , 'nui-pod-group-count' , String ( eps . length ) ) , el ( 'span' , 'nui-pod-chev' , open ? '▾' : '› ' ) ) ;
head . addEventListener ( 'click' , ( ) => { if ( ! selected ) toggleLaterOpen ( feedId ) ; } ) ;
list . append ( head ) ;
if ( open ) for ( const e of eps ) list . append ( episodeRow ( e , { showDate : true , list : 'later' } ) ) ;
}
root . append ( list ) ;
}
2026-10-19 19:17:33 +02:00
// ---- Stöbern (diora-web docs/podcasts.md): within the subscriptions, and a step out ---------
// Search in titles and shownotes, the week's topics, people, forgotten episodes,
// recommendations; a person's episodes elsewhere go onto the Merkliste (their podcast
// becomes „Auf Abruf“). Everything here needs a connection.
let browse = { mode : 'home' } ;
const browseCache = new Map ( ) ; // path → {at, data}
async function browseGet ( path , maxAge ) {
const hit = browseCache . get ( path ) ;
if ( hit && Date . now ( ) - hit . at < ( maxAge || 10 * 60000 ) ) return hit . data ;
const data = await getJson ( path ) ;
browseCache . set ( path , { at : Date . now ( ) , data } ) ;
return data ;
}
function openBrowse ( ) {
selected = null ;
browse = { mode : 'home' } ;
view = 'browse' ;
render ( ) ;
scrollTop ( 0 ) ;
}
function browseBack ( ) {
if ( browse . mode !== 'home' ) { browse = { mode : 'home' , q : browse . q && browse . mode === 'search' ? '' : '' } ; render ( ) ; scrollTop ( 0 ) ; return true ; }
view = 'stream' ;
render ( ) ;
scrollTop ( 0 ) ;
return true ;
}
// A section that loads by itself: its heading, „Wird geladen …“, then fill(box, data).
function browseSection ( title , path , fill , maxAge ) {
const sec = el ( 'div' , 'nui-pod-browsesec' ) ;
if ( title ) sec . append ( el ( 'div' , 'nui-pod-when' , title ) ) ;
const box = el ( 'div' , 'nui-pod-browsebox' ) ;
sec . append ( box ) ;
const hit = browseCache . get ( path ) ;
if ( hit && Date . now ( ) - hit . at < ( maxAge || 10 * 60000 ) ) {
fill ( box , hit . data ) ;
} else {
box . append ( el ( 'div' , 'nui-pod-meta' , 'Wird geladen …' ) ) ;
browseGet ( path , maxAge ) . then ( d => { box . textContent = '' ; fill ( box , d ) ; } )
. catch ( ( ) => { box . textContent = '' ; box . append ( el ( 'div' , 'nui-pod-meta' , navigator . onLine ? 'Ließ sich gerade nicht laden.' : 'Stöbern braucht eine Verbindung.' ) ) ; } ) ;
}
return sec ;
}
function browseRows ( box , rows , limit ) {
const list = el ( 'div' , 'nui-pod-list' ) ;
const shownRows = limit ? rows . slice ( 0 , limit ) : rows ;
for ( const o of shownRows ) list . append ( episodeRow ( ep ( o ) , { showFeed : true , showDate : true , list : 'stream' , noSelect : true } ) ) ;
box . append ( list ) ;
if ( ! rows . length ) box . append ( el ( 'div' , 'nui-pod-meta' , 'Nichts gefunden.' ) ) ;
return rows . length > shownRows . length ;
}
function moreLink ( box , label , onClick ) {
const b = el ( 'button' , 'nui-pod-bundle' , label ) ;
b . type = 'button' ;
b . addEventListener ( 'click' , onClick ) ;
box . append ( b ) ;
}
function chipRow ( items ) {
const row = el ( 'div' , 'nui-pod-filters nui-pod-browsechips' ) ;
for ( const it of items ) {
const b = el ( 'button' , 'nui-pod-chip' , it . label ) ;
b . type = 'button' ;
if ( it . count ) b . append ( el ( 'span' , 'nui-pod-chipcount' , ' ' + it . count ) ) ;
b . addEventListener ( 'click' , it . onClick ) ;
row . append ( b ) ;
}
return row ;
}
function renderBrowse ( ) {
const head = el ( 'div' , 'nui-pod-head nui-pod-browsehead' ) ;
const titles = { home : 'Stöbern' , search : 'Suche in deinen Podcasts' , topic : browse . label , person : browse . name ,
forgotten : 'Vergessen' , recommend : 'Empfehlungen' } ;
head . append ( button ( '‹ ' , browse . mode === 'home' ? 'Zurück zum Strom' : 'Zurück zum Stöbern' , browseBack , 'nui-pod-back' ) ,
el ( 'div' , 'nui-pod-browsetitle' , titles [ browse . mode ] || 'Stöbern' ) ) ;
root . append ( head ) ;
if ( browse . mode === 'home' || browse . mode === 'search' ) {
const form = el ( 'form' , 'nui-pod-search' ) ;
const input = el ( 'input' , 'search-input' ) ;
input . type = 'search' ;
input . id = 'nui-pod-q' ;
input . placeholder = 'In Titeln und Shownotes deiner Podcasts …' ;
input . setAttribute ( 'enterkeyhint' , 'search' ) ;
input . value = browse . q || '' ;
const go = el ( 'button' , 'btn' , 'Suchen' ) ;
go . type = 'submit' ;
form . append ( input , go ) ;
form . addEventListener ( 'submit' , ev => {
ev . preventDefault ( ) ;
const q = input . value . trim ( ) ;
browse = q ? { mode : 'search' , q } : { mode : 'home' } ;
render ( ) ;
} ) ;
root . append ( form ) ;
}
const body = el ( 'div' , 'nui-pod-browse' ) ;
root . append ( body ) ;
if ( browse . mode === 'search' ) {
body . append ( browseSection ( '' , ` /podcasts/discover/search/?q= ${ encodeURIComponent ( browse . q ) } &stream=1 ` ,
( box , d ) => browseRows ( box , d . episodes || [ ] ) ) ) ;
return ;
}
if ( browse . mode === 'topic' ) {
body . append ( browseSection ( '' , ` /podcasts/episodes/?ids= ${ browse . ids . join ( ',' ) } &limit=100 ` ,
( box , d ) => browseRows ( box , ( d . episodes || [ ] ) . sort ( ( a , b ) => ( Date . parse ( b . pub _date ) || 0 ) - ( Date . parse ( a . pub _date ) || 0 ) ) ) ) ) ;
return ;
}
if ( browse . mode === 'person' ) {
body . append ( browseSection ( 'In deinen Podcasts' , ` /podcasts/discover/person/?name= ${ encodeURIComponent ( browse . name ) } ` ,
( box , d ) => browseRows ( box , d . episodes || [ ] ) ) ) ;
body . append ( browseSection ( 'Anderswo' , ` /podcasts/discover/elsewhere/?name= ${ encodeURIComponent ( browse . name ) } ` ,
( box , d ) => elsewhereRows ( box , d . episodes || [ ] ) , 60 * 60000 ) ) ;
return ;
}
if ( browse . mode === 'forgotten' ) {
body . append ( browseSection ( 'Von den Podcasts, die du am meisten hörst' , '/podcasts/discover/forgotten/' ,
( box , d ) => browseRows ( box , d . loved || [ ] ) ) ) ;
body . append ( browseSection ( 'Von den seltenen' , '/podcasts/discover/forgotten/' , ( box , d ) => browseRows ( box , d . rare || [ ] ) ) ) ;
return ;
}
if ( browse . mode === 'recommend' ) {
body . append ( browseSection ( '' , '/podcasts/discover/recommend/' , ( box , d ) => recommendCards ( box , d . podcasts || [ ] ) , 60 * 60000 ) ) ;
return ;
}
// home
body . append ( browseSection ( 'Themen der Woche' , '/podcasts/discover/topics/?days=7' , ( box , d ) => {
const ts = d . topics || [ ] ;
if ( ! ts . length ) { box . append ( el ( 'div' , 'nui-pod-meta' , 'Diese Woche keine gemeinsamen Themen.' ) ) ; return ; }
box . append ( chipRow ( ts . map ( t => ( { label : t . label , count : t . ids . length ,
onClick : ( ) => { browse = { mode : 'topic' , label : t . label , ids : t . ids } ; render ( ) ; scrollTop ( 0 ) ; } } ) ) ) ) ;
} ) ) ;
body . append ( browseSection ( 'Personen' , '/podcasts/discover/people/' , ( box , d ) => {
const ps = ( d . people || [ ] ) . slice ( 0 , 24 ) ;
if ( ! ps . length ) { box . append ( el ( 'div' , 'nui-pod-meta' , 'Noch keine Namen gefunden.' ) ) ; return ; }
box . append ( chipRow ( ps . map ( x => ( { label : x . name , count : x . episodes ,
onClick : ( ) => { browse = { mode : 'person' , name : x . name } ; render ( ) ; scrollTop ( 0 ) ; } } ) ) ) ) ;
} , 60 * 60000 ) ) ;
body . append ( browseSection ( 'Vergessen' , '/podcasts/discover/forgotten/' , ( box , d ) => {
const rows = ( d . loved || [ ] ) . slice ( 0 , 3 ) . concat ( ( d . rare || [ ] ) . slice ( 0 , 2 ) ) ;
browseRows ( box , rows ) ;
moreLink ( box , 'Alles Vergessene › ' , ( ) => { browse = { mode : 'forgotten' } ; render ( ) ; scrollTop ( 0 ) ; } ) ;
} ) ) ;
body . append ( browseSection ( 'Empfohlen' , '/podcasts/discover/recommend/' , ( box , d ) => {
const ps = d . podcasts || [ ] ;
recommendCards ( box , ps . slice ( 0 , 3 ) ) ;
if ( ps . length > 3 ) moreLink ( box , ` Alle ${ ps . length } Empfehlungen › ` , ( ) => { browse = { mode : 'recommend' } ; render ( ) ; scrollTop ( 0 ) ; } ) ;
} , 60 * 60000 ) ) ;
}
// Episodes naming a person in podcasts not subscribed: + puts one on the Merkliste and
// subscribes its podcast „Auf Abruf“ (POST /podcasts/discover/take/).
function elsewhereRows ( box , items ) {
if ( ! items . length ) { box . append ( el ( 'div' , 'nui-pod-meta' , 'Anderswo nichts gefunden.' ) ) ; return ; }
const list = el ( 'div' , 'nui-pod-list' ) ;
for ( const it of items ) {
const row = el ( 'div' , 'nui-pod-row' ) ;
row . append ( art ( it . artwork _url , null , null , it . podcast ) ) ;
const main = el ( 'div' , 'nui-pod-main' ) ;
main . append ( el ( 'div' , 'nui-pod-title' , plain ( it . title ) ) ) ;
const meta = [ ] ;
if ( it . duration _seconds ) meta . push ( minutes ( it . duration _seconds ) ) ;
if ( it . pub _date ) meta . push ( day ( it . pub _date ) ) ;
if ( it . podcast ) meta . push ( plain ( it . podcast ) ) ;
main . append ( el ( 'div' , 'nui-pod-meta' , meta . join ( ' · ' ) ) ) ;
row . append ( main ) ;
const take = button ( '+ ' , 'Auf die Merkliste (der Podcast kommt „Auf Abruf“ in die Abos)' , async ( ) => {
take . disabled = true ;
try {
const r = await fetch ( '/podcasts/discover/take/' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' } , body : JSON . stringify ( it ) } ) ;
const d = await r . json ( ) ;
if ( ! r . ok || ! d . ok ) throw new Error ( d . error || r . status ) ;
take . textContent = '✓' ;
take . classList . add ( 'on' ) ;
toast ( d . subscribed ? ` Auf der Merkliste · „ ${ plain ( it . podcast ) } “ auf Abruf abonniert ` : 'Auf der Merkliste' ) ;
refresh ( false ) ;
} catch ( e ) {
take . disabled = false ;
toast ( navigator . onLine ? 'Ließ sich nicht merken' : 'Braucht eine Verbindung' ) ;
}
} , 'nui-pod-mini' ) ;
row . append ( take ) ;
list . append ( row ) ;
}
box . append ( list ) ;
}
// Podcasts like the ones heard most (fyyd): subscribe „Auf Abruf“ (or open the preview).
function recommendCards ( box , items ) {
if ( ! items . length ) { box . append ( el ( 'div' , 'nui-pod-meta' , 'Gerade keine Empfehlungen.' ) ) ; return ; }
const have = new Set ( feeds ( ) . map ( f => f . rss _url ) ) ;
for ( const p of items ) {
const card = el ( 'div' , 'nui-pod-row nui-pod-reco' ) ;
card . append ( art ( p . artwork _url , null , null , p . title ) ) ;
const main = el ( 'div' , 'nui-pod-main' ) ;
main . append ( el ( 'div' , 'nui-pod-title' , plain ( p . title ) ) ) ;
if ( p . because && p . because . length ) main . append ( el ( 'div' , 'nui-pod-meta' , 'weil du ' + p . because . slice ( 0 , 2 ) . map ( plain ) . join ( ' und ' ) + ' hörst' ) ) ;
if ( p . description ) main . append ( el ( 'div' , 'nui-pod-meta nui-pod-reco-desc' , plain ( p . description ) . slice ( 0 , 160 ) ) ) ;
card . append ( main ) ;
if ( have . has ( p . feed _url ) ) {
card . append ( el ( 'span' , 'nui-pod-badge' , 'Abo' ) ) ;
} else {
const sub = button ( '+ ' , 'Abonnieren – „Auf Abruf“: nicht im Strom, nur auf seiner Seite' , async ( ) => {
sub . disabled = true ;
if ( await subscribeOnDemand ( p . feed _url , p . title ) ) { sub . textContent = '✓' ; sub . classList . add ( 'on' ) ; } else sub . disabled = false ;
} , 'nui-pod-mini' ) ;
card . append ( sub ) ;
}
if ( N . podcastPreview ) main . addEventListener ( 'click' , ( ) => openPreview ( p . feed _url ) ) ;
box . append ( card ) ;
}
}
async function subscribeOnDemand ( url , title ) {
try {
const r = await fetch ( '/podcasts/feeds/add/' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( { rss _url : url , title } ) } ) ;
const d = await r . json ( ) ;
if ( ! r . ok || ! d . ok ) throw new Error ( d . error || r . status ) ;
await fetch ( ` /podcasts/feeds/ ${ d . feed _id } /settings/ ` , { method : 'POST' , headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( { new _episodes : 'ignore' } ) } ) ;
toast ( ` „ ${ plain ( d . title || title ) } “ auf Abruf abonniert ` ) ;
refresh ( false ) ;
return true ;
} catch ( e ) {
toast ( navigator . onLine ? 'Ließ sich nicht abonnieren' : 'Abonnieren braucht eine Verbindung' ) ;
return 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
function renderInbox ( ) {
2026-10-16 21:06:41 +02:00
const all = inbox ( ) . map ( ep ) ;
if ( ! all . 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
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 ;
}
2026-10-16 21:06:41 +02:00
const items = all . filter ( e => inFolder ( e . feedId ) ) ;
if ( ! items . length ) { empty ( folderEmpty ( 'neu' ) ) ; 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 ( 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 ( ) ;
2026-10-16 21:06:41 +02:00
head . append ( el ( 'div' , 'nui-pod-summary' , podFolder != null
? ` ${ items . length } neue ${ items . length === 1 ? 'Folge' : 'Folgen' } · von ${ total } `
: ` ${ 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-19 17:23:06 +02:00
appendRows ( list , items , 'rare' , e => 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 ) ;
}
2026-10-16 21:06:41 +02:00
// ---- folders (diora-web docs/library.md) ------------------------------------------------------
// Like tags: a subscription may be in several (feed.folders; `folder`, the first, is for older
// clients). Podcasts and audiobooks share the names and their order (/podcasts/folders/).
// One filter for Neu, Warteschlange and Abos, remembered on this device. Putting into
// folders, renaming and deleting go through the subscriptions' settings, so they work
// offline too; only the order is posted on its own (and again later when that failed).
// folders.js loads after this file.
const hasFolders = ( ) => ! ! window . DioraFolders ;
let podFolder = null ; // null: all, '': without a folder, else a name
let folderOrder = [ ] ;
try {
podFolder = JSON . parse ( localStorage . getItem ( 'nui_pod_folder' ) || 'null' ) ;
folderOrder = JSON . parse ( localStorage . getItem ( 'nui_pod_folder_order' ) || '[]' ) || [ ] ;
} catch ( e ) { }
const foldersOf = f => ( ! f ? [ ] : f . folders && f . folders . length ? f . folders . slice ( ) : f . folder ? [ f . folder ] : [ ] ) ;
const sameName = ( a , b ) => a . toLowerCase ( ) === b . toLowerCase ( ) ;
const dedupe = names => names . filter ( ( n , i ) => names . findIndex ( m => sameName ( m , n ) ) === i ) ;
// The names in their order: the order the server keeps first, the rest by name.
function orderedNames ( names ) {
const first = folderOrder . filter ( n => names . includes ( n ) ) ;
const rest = names . filter ( n => ! first . includes ( n ) ) . sort ( ( a , b ) => a . localeCompare ( b , 'de' , { sensitivity : 'base' } ) ) ;
return first . concat ( rest ) ;
}
const namesOf = list => orderedNames ( dedupe ( [ ] . concat ( ... list . map ( foldersOf ) ) ) ) ;
function inFolder ( feedId ) {
if ( podFolder == null ) return true ;
const fs = foldersOf ( feeds ( ) . find ( f => f . id === feedId ) ) ;
return podFolder === '' ? ! fs . length : fs . includes ( podFolder ) ;
}
function setFolder ( name ) {
podFolder = name ;
try { localStorage . setItem ( 'nui_pod_folder' , JSON . stringify ( name ) ) ; } catch ( e ) { }
render ( ) ;
scrollTop ( 0 ) ;
}
function folderEmpty ( what ) {
return podFolder === '' ? ` Ohne Ordner ist nichts ${ what } . ` : ` In „ ${ podFolder } “ ist nichts ${ what } . ` ;
}
// The chips (folders.js) above a list: each folder with how many of the list are in it.
2026-10-19 19:17:33 +02:00
function folderChips ( items , feedIdOf , faves ) {
2026-10-16 21:06:41 +02:00
if ( ! hasFolders ( ) ) return ;
const names = namesOf ( podcastFeeds ( ) ) ;
if ( ! names . length || ( podFolder && ! names . includes ( podFolder ) ) ) podFolder = null ;
if ( ! names . length ) return ;
const byId = new Map ( feeds ( ) . map ( f => [ f . id , f ] ) ) ;
const counts = new Map ( ) ;
let none = 0 ;
for ( const x of items ) {
const fs = foldersOf ( byId . get ( feedIdOf ( x ) ) ) ;
if ( ! fs . length ) none ++ ;
for ( const n of fs ) counts . set ( n , ( counts . get ( n ) || 0 ) + 1 ) ;
}
const box = el ( 'div' , 'nui-pod-folders' ) ;
2026-10-19 19:17:33 +02:00
const favesOn = faves && podFaves ;
const nFaves = faves ? items . filter ( x => isFave ( feedIdOf ( x ) ) ) . length : 0 ;
2026-10-16 21:06:41 +02:00
DioraFolders . chips ( box , {
folders : names . map ( name => ( { name , count : counts . get ( name ) || 0 } ) ) ,
active : podFolder ,
none : none || ( podFolder === '' ? '0' : 0 ) ,
2026-10-19 19:17:33 +02:00
lead : faves && nFaves ? [ { id : 'all' , label : 'Alle' , on : podFolder == null && ! favesOn } ,
{ id : 'faves' , label : '★ Faves' , count : nFaves , on : favesOn } ] : null ,
onPick : name => {
if ( name && typeof name === 'object' ) {
if ( name . lead === 'faves' ) { setFaves ( ! podFaves ) ; return ; }
podFaves = false ;
try { localStorage . setItem ( 'nui_pod_faves' , '0' ) ; } catch ( e ) { }
setFolder ( null ) ;
return ;
}
setFolder ( typeof name === 'string' ? name : null ) ;
} ,
2026-10-16 21:06:41 +02:00
onManage : manageFolders ,
} ) ;
root . append ( box ) ;
}
function saveFeedFolders ( feedId , names ) {
N . podcastFeedSettings ( feedId , JSON . stringify ( { folders : names } ) ) ;
}
// Each subscription's folders through fn (renaming, deleting a folder).
function rewriteFolders ( fn ) {
for ( const f of feeds ( ) ) {
const before = foldersOf ( f ) ;
if ( ! before . length ) continue ;
const after = dedupe ( fn ( before ) ) ;
if ( after . join ( '\n' ) !== before . join ( '\n' ) ) saveFeedFolders ( f . id , after ) ;
}
}
// "In Ordner …" for one subscription or several: what all of them are in comes ticked;
// unticking takes it from all, ticking puts all in; the folders only some are in stay.
function assignFolders ( list ) {
if ( ! list . length ) return ;
const common = foldersOf ( list [ 0 ] ) . filter ( n => list . every ( f => foldersOf ( f ) . includes ( n ) ) ) ;
DioraFolders . assign ( {
title : list . length === 1 ? ` „ ${ plain ( list [ 0 ] . title ) } “ in Ordner ` : ` ${ list . length } Abos in Ordner ` ,
folders : namesOf ( feeds ( ) ) ,
selected : common ,
onDone : picked => {
for ( const f of list ) {
const before = foldersOf ( f ) ;
const after = dedupe ( before . filter ( n => ! common . includes ( n ) || picked . includes ( n ) ) . concat ( picked ) ) ;
if ( after . join ( '\n' ) !== before . join ( '\n' ) ) saveFeedFolders ( f . id , after ) ;
}
toast ( list . length > 1 ? ` ${ list . length } Abos eingeordnet `
: picked . length ? 'In ' + picked . map ( n => ` „ ${ n } “ ` ) . join ( ', ' ) : 'In keinem Ordner' ) ;
stopSelecting ( ) ;
setTimeout ( render , 80 ) ;
} ,
} ) ;
}
function manageFolders ( ) {
DioraFolders . manage ( {
title : 'Ordner verwalten' ,
folders : namesOf ( feeds ( ) ) ,
onRename : ( old , name ) => {
// Onto an existing name: both become one (its spelling stays).
const target = namesOf ( feeds ( ) ) . find ( n => n !== old && sameName ( n , name ) ) || name ;
rewriteFolders ( fs => fs . map ( n => ( n === old ? target : n ) ) ) ;
keepOrder ( dedupe ( folderOrder . map ( n => ( n === old ? target : n ) ) ) , true ) ;
if ( podFolder === old ) podFolder = target ;
setTimeout ( render , 80 ) ;
} ,
onDelete : name => {
rewriteFolders ( fs => fs . filter ( n => n !== name ) ) ;
keepOrder ( folderOrder . filter ( n => n !== name ) , true ) ;
if ( podFolder === name ) podFolder = null ;
setTimeout ( render , 80 ) ;
} ,
onOrder : order => keepOrder ( order , true ) ,
} ) ;
}
async function postJsonTo ( url , body ) {
const headers = { 'Content-Type' : 'application/json' } ;
if ( typeof getCsrfToken === 'function' ) headers [ 'X-CSRFToken' ] = getCsrfToken ( ) ;
const r = await fetch ( url , { method : 'POST' , headers , body : JSON . stringify ( body ) } ) ;
if ( ! r . ok ) throw new Error ( r . status ) ;
return r . json ( ) ;
}
// The chips' order: kept here, and on the server (posted again later when that failed).
function keepOrder ( order , post ) {
const changed = order . join ( '\n' ) !== folderOrder . join ( '\n' ) ;
folderOrder = order . slice ( ) ;
try {
localStorage . setItem ( 'nui_pod_folder_order' , JSON . stringify ( folderOrder ) ) ;
if ( post ) localStorage . setItem ( 'nui_pod_folder_order_dirty' , '1' ) ;
} catch ( e ) { }
if ( post ) sendOrder ( ) ;
if ( changed && ! post ) render ( ) ;
}
async function sendOrder ( ) {
try {
const d = await postJsonTo ( '/podcasts/folders/' , { order : folderOrder } ) ;
try { localStorage . removeItem ( 'nui_pod_folder_order_dirty' ) ; } catch ( e ) { }
if ( d && Array . isArray ( d . order ) ) keepOrder ( d . order , false ) ;
} catch ( e ) { /* offline: again on the next load */ }
}
async function loadFolderOrder ( ) {
if ( ! hasFolders ( ) ) return ;
let dirty = false ;
try { dirty = localStorage . getItem ( 'nui_pod_folder_order_dirty' ) === '1' ; } catch ( e ) { }
if ( dirty ) { sendOrder ( ) ; return ; }
try {
const d = await getJson ( '/podcasts/folders/' ) ;
if ( d && Array . isArray ( d . order ) ) keepOrder ( d . order , false ) ;
} catch ( e ) { /* offline: the order kept here */ }
}
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-16 21:06:41 +02:00
if ( selected ) {
row . classList . add ( 'nui-pod-selecting' ) ;
row . append ( el ( 'span' , 'nui-pod-check' + ( selected . has ( f . id ) ? ' on' : '' ) , selected . has ( f . id ) ? '☑' : '☐' ) ) ;
}
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 ) ) ;
2026-10-16 21:06:41 +02:00
// Its folders, where the chips don't already say it.
const fs = foldersOf ( f ) . filter ( n => n !== podFolder ) ;
if ( fs . length && hasFolders ( ) ) main . append ( el ( 'div' , 'nui-pod-meta nui-pod-folderline' , '▣ ' + fs . join ( ', ' ) ) ) ;
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
row . append ( main ) ;
2026-10-16 21:06:41 +02:00
if ( selected ) {
row . addEventListener ( 'click' , ( ) => toggleSelected ( f . id ) ) ;
return row ;
}
2026-10-19 19:17:33 +02:00
if ( v2 ( ) && 'favorite' in f && N . podcastFeedSettings ) {
const star = button ( f . favorite ? '★' : '☆' , f . favorite ? 'Lieblings-Abo – antippen nimmt es heraus' : 'Als Lieblings-Abo markieren' ,
( ) => toggleFeedFave ( f ) , 'nui-pod-mini nui-pod-starbtn' + ( f . favorite ? ' on' : '' ) ) ;
star . addEventListener ( 'click' , ev => ev . stopPropagation ( ) ) ;
row . append ( star ) ;
}
2026-10-19 18:21:03 +02:00
if ( v2 ( ) ) {
if ( f . new _episodes === 'queue' || f . new _episodes === 'play' ) row . append ( el ( 'span' , 'nui-pod-badge' , 'Stamm' ) ) ;
else if ( f . new _episodes === 'ignore' ) row . append ( el ( 'span' , 'nui-pod-badge' , 'Abruf' ) ) ;
} else if ( f . new _episodes === 'ignore' ) row . append ( el ( 'span' , 'nui-pod-badge' , 'still' ) ) ;
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
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 ] ;
2026-10-19 19:17:33 +02:00
const items = podcastFeeds ( ) . filter ( f => inFolder ( f . id ) && ( ! podFaves || ! v2 ( ) || f . favorite ) )
. sort ( ( a , b ) => sort . cmp ( a , b ) || byTitle ( a , b ) ) ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
const books = feeds ( ) . length - items . length ;
const booksHint = ( ) => el ( 'div' , 'nui-pod-hint' ,
2026-10-16 15:45:14 +02:00
` ${ books === 1 ? 'Ein Hörbuch' : books + ' Hörbücher' } : unter Hören → Hörbücher ` ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if ( ! items . length ) {
2026-10-16 21:06:41 +02:00
if ( podFolder != null && podcastFeeds ( ) . length ) { empty ( folderEmpty ( 'abonniert' ) ) ; 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
empty ( 'Noch keine Abos. Unter „Suche“ findest du Podcasts.' ) ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
if ( books ) root . append ( booksHint ( ) ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
return ;
}
2026-10-16 21:06:41 +02:00
if ( selected ) {
root . append ( selectionBar ( items , [
hasFolders ( ) && N . podcastFeedSettings && { label : 'In Ordner …' , run : ids => assignFolders ( items . filter ( f => ids . includes ( f . id ) ) ) } ,
] , 'Abos antippen' ) ) ;
} else {
const head = el ( 'div' , 'nui-pod-head' ) ;
head . append ( el ( 'div' , 'nui-pod-summary' , ` ${ items . length } ${ items . length === 1 ? 'Abo' : 'Abos' } · ${ sort . label . toLowerCase ( ) } ` ) ,
listTools ( [
hasSources ( ) && button ( '⚙' , 'Verwalten: Abos & Quellen' , toSources ) ,
button ( '⇅' , 'Sortieren und Zahl wählen' , feedSortMenu ) ,
hasFolders ( ) && N . podcastFeedSettings && button ( '☐' , 'Mehrere auswählen (in Ordner)' , ( ) => startSelecting ( ) ) ,
] . filter ( Boolean ) ) ) ;
root . append ( head ) ;
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const list = el ( 'div' , 'nui-pod-list' ) ;
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 ) ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
if ( books ) root . append ( booksHint ( ) ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
Abos & Quellen: alle Abos an einem Ort (Navigation Phase 2)
Ein neuer Abschnitt (docs/sources.md, static/js/sources.js) für Podcasts,
YouTube-Kanäle, Hörbücher, Nachrichten und Blogs:
- ein Feld zum Hinzufügen: Adresse (Feed, Seite, YouTube) oder Name eines
Podcasts; die Art ist wählbar (Podcast, Hörbuch, Nachrichten, Blog);
/feeds/discover/ erkennt dafür Podcasts (Folgen mit Ton oder Video);
- eine Liste aller Abos mit Filtern (auch „Probleme“) und Suche;
- je Abo: Inhalt öffnen, Einstellungen (die vorhandenen Editoren), Art ändern,
abbestellen; OPML-Import und -Export.
In der Seitenleiste unten, auf dem Telefon im App-Menü; ⚙ in den Feeds führt
hierher. Abschnitte, die beim Laden schon gezeigt werden, füllen sich jetzt
auch (nav.js zeigt sie nach dem Laden aller Skripte noch einmal).
Podcast-UI aus diora-android 50c6f9c.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:23:12 +02:00
// For Abos & Quellen (sources.js): a podcast's settings sheet, its episode list.
window . _ _dioraPodcastFeedSettings = function ( feedId ) {
const f = feeds ( ) . find ( x => x . id === Number ( feedId ) ) ;
if ( ! f ) return false ;
feedSettingsSheet ( f ) ;
return true ;
} ;
2026-10-19 19:17:33 +02:00
// The search (diora-web docs/search.md): a row as the episode lists give it; true when opened.
window . _ _dioraPodcastOpenEpisode = function ( row ) {
if ( ! row || row . id == null ) return false ;
const e = ep ( row ) ;
if ( isBook ( feeds ( ) . find ( f => f . id === e . feedId ) ) && openAudiobook ( e . feedId ) ) return true ;
openEpisode ( e , { list : 'search' } ) ;
return true ;
} ;
Abos & Quellen: alle Abos an einem Ort (Navigation Phase 2)
Ein neuer Abschnitt (docs/sources.md, static/js/sources.js) für Podcasts,
YouTube-Kanäle, Hörbücher, Nachrichten und Blogs:
- ein Feld zum Hinzufügen: Adresse (Feed, Seite, YouTube) oder Name eines
Podcasts; die Art ist wählbar (Podcast, Hörbuch, Nachrichten, Blog);
/feeds/discover/ erkennt dafür Podcasts (Folgen mit Ton oder Video);
- eine Liste aller Abos mit Filtern (auch „Probleme“) und Suche;
- je Abo: Inhalt öffnen, Einstellungen (die vorhandenen Editoren), Art ändern,
abbestellen; OPML-Import und -Export.
In der Seitenleiste unten, auf dem Telefon im App-Menü; ⚙ in den Feeds führt
hierher. Abschnitte, die beim Laden schon gezeigt werden, füllen sich jetzt
auch (nav.js zeigt sie nach dem Laden aller Skripte noch einmal).
Podcast-UI aus diora-android 50c6f9c.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:23:12 +02:00
window . _ _dioraPodcastOpenFeed = function ( feedId ) {
const f = feeds ( ) . find ( x => x . id === Number ( feedId ) ) ;
if ( ! f ) return false ;
if ( window . DioraNav ) DioraNav . go ( 'podcasts' ) ;
else if ( typeof showTab === 'function' ) showTab ( 'podcasts' ) ;
openFeed ( f ) ;
return true ;
} ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
async function openFeed ( f ) {
feedView = { feed : f , episodes : null } ;
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 {
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
const data = await getJson ( ` /podcasts/feeds/ ${ f . id } /episodes/ ` ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if ( feedView && feedView . feed . id === f . id ) {
feedView = { feed : Object . assign ( { } , f , data . feed ) , episodes : data . episodes } ;
render ( ) ;
}
} catch ( e ) {
if ( feedView && feedView . feed . id === f . id ) {
feedView . episodes = [ ] ;
feedView . error = true ;
render ( ) ;
}
}
}
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' ) ) ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
if ( feedHasSettings ( ) ) head . append ( button ( '⚙︎' , 'Einstellungen für diesen Podcast' , ( ) => feedSettingsSheet ( f ) , 'nui-pod-infobtn nui-pod-gear' ) ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
root . append ( head ) ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
const line = feedHasSettings ( ) ? feedSummary ( f , live ) : '' ;
2026-10-02 19:30:03 +02:00
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 ) ;
}
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
// The host keeps per-podcast settings (the browser and the app do; an old host doesn't).
function feedHasSettings ( ) {
2026-10-02 19:30:03 +02:00
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' } ;
2026-10-19 18:21:03 +02:00
// With the two lists: the three levels of a subscription (docs/podcasts.md).
const LEVELS = [
{ value : 'queue' , label : 'Stamm' , hint : 'neue Folgen auf die Merkliste, geladen' } ,
{ value : 'play' , label : 'Stamm, gleich in die Wiedergabeliste' , hint : 'neue Folgen spielen als Nächstes, geladen' } ,
{ value : 'inbox' , label : 'Strom' , hint : 'neue Folgen im Strom – nichts abzuarbeiten, nichts geladen' } ,
{ value : 'ignore' , label : 'Auf Abruf' , hint : 'nicht im Strom; auf der Seite des Podcasts und in der Suche' } ,
] ;
const levelOf = f => LEVELS . find ( l => l . value === ( f . new _episodes || 'inbox' ) ) || LEVELS [ 2 ] ;
const REFRESH = [
{ value : '' , label : 'Automatisch' , hint : 'nach seinem Rhythmus: wer täglich kommt, stündlich; seltene täglich' } ,
{ value : 'hourly' , label : 'Stündlich' } ,
{ value : 'daily' , label : 'Täglich' } ,
{ value : 'weekly' , label : 'Wöchentlich' } ,
] ;
2026-10-02 19:30:03 +02:00
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 ) ;
2026-10-19 18:21:03 +02:00
if ( v2 ( ) && 'new_episodes' in live ) {
parts . push ( levelOf ( live ) . label ) ;
const r = REFRESH . find ( x => x . value === ( live . refresh _interval || '' ) ) ;
if ( live . refresh _interval && r ) parts . push ( 'abrufen: ' + r . label . toLowerCase ( ) ) ;
} else {
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' ) ;
}
2026-10-02 19:30:03 +02:00
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 ( ) ;
2026-10-19 18:21:03 +02:00
if ( v2 ( ) && 'new_episodes' in live && N . podcastFeedSettings ) {
const lv = levelOf ( live ) ;
items . push ( { icon : '◇' , label : 'Stufe: ' + lv . label , hint : lv . hint , active : lv . value !== 'inbox' ,
action : choose ( ` „ ${ title } “ ist … ` , LEVELS . map ( l => ( {
icon : l . value === lv . value ? '●' : '○' , label : l . label , hint : l . hint , active : l . value === lv . value ,
action : ( ) => save ( { new _episodes : l . value } ) ,
} ) ) ) } ) ;
if ( 'refresh_interval' in live ) {
const r = REFRESH . find ( x => x . value === ( live . refresh _interval || '' ) ) || REFRESH [ 0 ] ;
items . push ( { icon : '⟳' , label : 'Abrufen: ' + r . label , hint : r . hint || null , active : ! ! live . refresh _interval ,
action : choose ( 'Wie oft der Server nachsieht' , REFRESH . map ( x => ( {
icon : x . value === r . value ? '●' : '○' , label : x . label , hint : x . hint || null , active : x . value === r . value ,
action : ( ) => save ( { refresh _interval : x . value } ) ,
} ) ) ) } ) ;
}
}
2026-10-02 19:30:03 +02:00
if ( 'new_episodes' in live && N . podcastFeedSettings ) {
const ne = live . new _episodes || 'inbox' ;
2026-10-19 18:21:03 +02:00
if ( ! v2 ( ) ) items . push ( { icon : '◇' , label : 'Neue Folgen' , hint : NEW _WHERE [ ne ] || NEW _WHERE . inbox , active : ne !== 'inbox' ,
2026-10-02 19:30:03 +02:00
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.
2026-10-19 18:21:03 +02:00
if ( 'auto_download' in live && N . podcastDownloads && ! v2 ( ) ) {
2026-10-02 19:30:03 +02:00
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 ) } ) ) ) } ,
) ;
}
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
// An audiobook: its episodes are chapters, listened to in the library (diora-web docs/audiobooks.md).
if ( 'kind' in live && N . podcastFeedSettings ) {
const book = isBook ( live ) ;
2026-10-16 15:45:14 +02:00
items . push ( { icon : '▤' , label : book ? 'Hörbuch (unter Hören → Hörbücher)' : 'Als Hörbuch' ,
2026-10-19 18:21:03 +02:00
hint : book ? 'tippen: wieder als Podcast' : 'Folgen als Kapitel unter Hören → Hörbücher, nicht ' + ( v2 ( ) ? 'im Strom' : 'in „Neu“' ) ,
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
active : book , action : ( ) => save ( { kind : book ? 'podcast' : 'audiobook' } ) } ) ;
}
2026-10-16 21:06:41 +02:00
if ( hasFolders ( ) && N . podcastFeedSettings ) {
const fs = foldersOf ( live ) ;
items . push ( { icon : '▣' , label : 'In Ordner …' , hint : fs . length ? fs . join ( ', ' ) : 'in keinem' , active : fs . length > 0 ,
action : ( ) => assignFolders ( [ live ] ) } ) ;
}
2026-10-02 19:30:03 +02:00
// 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' ) ;
2026-10-19 17:23:06 +02:00
appendRows ( list , items , sort , e => {
2026-10-02 19:30:03 +02:00
const row = episodeRow ( e , { showDate : true , list : downloadsOnly ( filters ) ? 'downloads' : 'feed' , filters } ) ;
2026-10-19 17:23:06 +02:00
if ( e . raw . favorite ) row . querySelector ( '.nui-pod-title' ) . prepend ( el ( 'span' , 'nui-pod-star' , '★ ' ) ) ;
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
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 ( ) ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
const { sheet : made , panel , top } = podSheet ( 'nui-pod-notes-sheet nui-pod-info' , 'Schließen' , closeNotes ) ;
notesSheet = made ;
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-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 ) ;
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 = [ ] ;
2026-10-19 17:23:06 +02:00
let epSort = 'rare' ;
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 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 ;
2026-10-19 17:23:06 +02:00
// Since 0.5.71 by time with the seldom podcasts first; who had the newest first gets that once.
if ( ! localStorage . getItem ( 'nui_pod_rare' ) && epSort === 'new' ) epSort = 'rare' ;
localStorage . setItem ( 'nui_pod_rare' , '1' ) ;
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
} catch ( e ) { }
if ( ! Array . isArray ( epFilters ) ) epFilters = [ ] ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
epFilters = epFilters . filter ( id => FILTERS . some ( f => f . id === id && ( ! f . app || DL ) ) ) ;
2026-10-19 17:23:06 +02:00
if ( ! EP _SORTS . some ( s => s . id === epSort ) ) epSort = 'rare' ;
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
let epList = null ; // {key, items, total, loading, error}
2026-10-19 17:23:06 +02:00
// The folder too: "Folgen" with a folder chosen is all episodes of that category.
2026-10-19 18:21:03 +02:00
const epKey = ( ) => epFilters . slice ( ) . sort ( ) . join ( ',' ) + '|' + epSort + '|' + JSON . stringify ( podFolder ) + '|' + ( view === 'stream' ? 's' : '' ) ;
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 setEpisodeList ( filters , sort ) {
// The history goes by the last listening; leaving it, by date again.
if ( sort == null ) {
sort = epSort ;
2026-10-19 17:23:06 +02:00
if ( filters . includes ( 'history' ) && ! epFilters . includes ( 'history' ) && ( sort === 'new' || sort === 'rare' ) ) sort = 'played' ;
if ( ! filters . includes ( 'history' ) && epFilters . includes ( 'history' ) && sort === 'played' ) sort = 'rare' ;
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
}
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 ( ) ;
2026-10-19 17:23:06 +02:00
const sorting = EP _SORTS . find ( s => s . id === epSort ) || EP _SORTS [ 0 ] ;
2026-10-19 18:21:03 +02:00
// The Strom: every subscription but the „Auf Abruf“ ones (docs/podcasts.md).
const base = view === 'stream' ? [ 'stream' ] : [ ] ;
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 params = new URLSearchParams ( {
2026-10-19 18:21:03 +02:00
filter : base . concat ( epFilters . filter ( id => id !== 'downloaded' ) ) . join ( ',' ) , sort : sorting . server || sorting . id , limit : '100' ,
2026-10-19 17:23:06 +02:00
offset : String ( offset ) ,
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
} ) ;
2026-10-19 17:23:06 +02:00
if ( podFolder != null && hasFolders ( ) ) params . set ( 'folder' , podFolder ) ;
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 ( 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 ) {
2026-10-19 17:23:06 +02:00
empty ( epList . loading ? 'Wird geladen …' : podFolder != null && ! epFilters . length ? folderEmpty ( 'zu hören' ) : 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 ( [
2026-10-19 19:17:33 +02:00
view === 'stream' && button ( '✦' , 'Stöbern: suchen, Themen, Personen, Vergessenes, Empfehlungen' , openBrowse , 'nui-pod-browsebtn' ) ,
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
button ( '⇅' , 'Sortieren' , ( ) => sortMenuFor ( epSort , true , id => setEpisodeList ( epFilters , id ) ) ) ,
button ( '☐' , 'Mehrere auswählen' , ( ) => startSelecting ( ) ) ,
2026-10-19 19:17:33 +02:00
] . filter ( Boolean ) ) ) ;
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
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' ) ;
2026-10-19 18:21:03 +02:00
if ( view === 'stream' && ! selected && ( epSort === 'new' || epSort === 'rare' ) ) {
streamRows ( list , items ) ;
root . append ( list ) ;
moreButton ( ) ;
return ;
}
2026-10-19 17:23:06 +02:00
appendRows ( list , items , epSort , 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
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' , '★ ' ) ) ;
2026-10-19 17:23:06 +02:00
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
root . append ( list ) ;
2026-10-19 18:21:03 +02:00
moreButton ( ) ;
}
function moreButton ( ) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf 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 . 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 ) ;
}
}
2026-10-19 18:21:03 +02:00
// The Strom (docs/podcasts.md): by period; in one, the podcasts most listened to first
// (heard_365: episodes heard in 12 months), every one heard at all before the others,
// among those the rarely publishing first. Each podcast once per period: its newest
// episode and „+n ›“ for the others. New since the last look: a small mark.
const streamOpen = new Set ( ) ;
function streamRank ( feedId ) {
const f = feeds ( ) . find ( x => x . id === feedId ) || { } ;
return [ f . heard _365 || 0 , typeof f . interval _days === 'number' ? f . interval _days : 0 ] ;
}
function streamRows ( list , items ) {
const before = Date . parse ( state . stream _seen _before || '' ) || 0 ;
const fresh = e => ! ! before && Date . parse ( e . raw . discovered _at || '' ) > before ;
const rank = new Map ( ) ;
const rankOf = id => { if ( ! rank . has ( id ) ) rank . set ( id , streamRank ( id ) ) ; return rank . get ( id ) ; } ;
for ( const sec of timeSections ( items , false ) ) {
list . append ( el ( 'div' , 'nui-pod-when' , sec . label ) ) ;
const groups = new Map ( ) ;
sec . items . forEach ( ( e , i ) => {
if ( ! groups . has ( e . feedId ) ) groups . set ( e . feedId , { first : i , eps : [ ] } ) ;
groups . get ( e . feedId ) . eps . push ( e ) ;
} ) ;
const ordered = [ ... groups . entries ( ) ] . sort ( ( a , b ) => {
const [ ha , ia ] = rankOf ( a [ 0 ] ) ;
const [ hb , ib ] = rankOf ( b [ 0 ] ) ;
if ( ha !== hb ) return hb - ha ;
if ( ! ha && ia !== ib ) return ib - ia ;
return a [ 1 ] . first - b [ 1 ] . first ;
} ) ;
for ( const [ feedId , g ] of ordered ) {
const key = sec . label + '|' + feedId ;
const open = streamOpen . has ( key ) ;
const shownEps = open ? g . eps : g . eps . slice ( 0 , 1 ) ;
shownEps . forEach ( ( e , i ) => {
const row = episodeRow ( e , { showFeed : true , showDate : true , list : 'stream' , fresh : fresh ( e ) , filters : epFilters } ) ;
if ( i > 0 ) row . classList . add ( 'nui-pod-bundled' ) ;
list . append ( row ) ;
} ) ;
if ( g . eps . length > 1 ) {
const rest = g . eps . length - 1 ;
const freshRest = ! open && g . eps . slice ( 1 ) . some ( fresh ) ;
const b = el ( 'button' , 'nui-pod-bundle' + ( freshRest ? ' nui-pod-fresh' : '' ) ,
open ? 'weniger ‹ ' : ` + ${ rest } ${ rest === 1 ? 'weitere' : 'weitere' } von ${ g . eps [ 0 ] . feed || 'diesem Podcast' } › ` ) ;
b . type = 'button' ;
b . addEventListener ( 'click' , ( ) => {
if ( open ) streamOpen . delete ( key ) ; else streamOpen . add ( key ) ;
render ( ) ;
} ) ;
list . append ( 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
// ---- 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 ( ) ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
const { sheet : made , panel , top } = podSheet ( 'nui-pod-notes-sheet' , 'Schließen' , closeStats ) ;
statsSheet = made ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const tabs = el ( 'div' , 'nui-pod-subtabs' ) ;
const body = el ( 'div' , 'nui-pod-stats' ) ;
panel . append ( top , el ( 'div' , 'nui-pod-player-title' , 'Hörstatistik' ) , tabs , body ) ;
document . body . append ( statsSheet ) ;
const ranges = [ { days : 30 , label : '30 Tage' } , { days : 365 , label : 'Jahr' } , { days : 0 , label : 'Gesamt' } ] ;
const show = async range => {
tabs . querySelectorAll ( 'button' ) . forEach ( b => b . classList . toggle ( 'active' , b . dataset . days === String ( range . days ) ) ) ;
body . textContent = '' ;
body . append ( el ( 'div' , 'nui-pod-meta' , 'Wird geladen …' ) ) ;
let d = null ;
try {
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
d = await getJson ( '/podcasts/stats/' + ( range . days ? ` ?days= ${ range . days } ` : '' ) ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
} catch ( e ) {
body . textContent = '' ;
body . append ( el ( 'div' , 'nui-pod-meta' , 'Die Statistik braucht eine Verbindung.' ) ) ;
return ;
}
body . textContent = '' ;
const big = el ( 'div' , 'nui-pod-stat-total' ) ;
big . append ( el ( 'strong' , null , hours ( d . total _seconds || 0 ) ) , el ( 'span' , null ,
` ${ d . episodes _played || 0 } Folgen gehört · ${ d . episodes _started || 0 } angefangen ` ) ) ;
body . append ( big ) ;
const feedsList = d . feeds || [ ] ;
if ( ! feedsList . length ) { body . append ( el ( 'div' , 'nui-pod-meta' , 'In diesem Zeitraum nichts gehört.' ) ) ; return ; }
const max = Math . max ( ... feedsList . map ( f => f . seconds || 0 ) , 1 ) ;
for ( const f of feedsList ) {
const row = el ( 'div' , 'nui-pod-stat-row' ) ;
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 ) ;
Abos & Quellen: alle Abos an einem Ort (Navigation Phase 2)
Ein neuer Abschnitt (docs/sources.md, static/js/sources.js) für Podcasts,
YouTube-Kanäle, Hörbücher, Nachrichten und Blogs:
- ein Feld zum Hinzufügen: Adresse (Feed, Seite, YouTube) oder Name eines
Podcasts; die Art ist wählbar (Podcast, Hörbuch, Nachrichten, Blog);
/feeds/discover/ erkennt dafür Podcasts (Folgen mit Ton oder Video);
- eine Liste aller Abos mit Filtern (auch „Probleme“) und Suche;
- je Abo: Inhalt öffnen, Einstellungen (die vorhandenen Editoren), Art ändern,
abbestellen; OPML-Import und -Export.
In der Seitenleiste unten, auf dem Telefon im App-Menü; ⚙ in den Feeds führt
hierher. Abschnitte, die beim Laden schon gezeigt werden, füllen sich jetzt
auch (nav.js zeigt sie nach dem Laden aller Skripte noch einmal).
Podcast-UI aus diora-android 50c6f9c.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:23:12 +02:00
if ( hasSources ( ) ) {
const more = el ( 'button' , 'nui-pod-hint nui-pod-link' , 'Alles abonnieren und verwalten – auch YouTube, Nachrichten, Blogs: Abos & Quellen › ' ) ;
more . type = 'button' ;
more . addEventListener ( 'click' , toSources ) ;
root . append ( more ) ;
}
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 ,
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
feeds : podcastFeeds ( ) . filter ( f => test ( fold ( f . title ) + ' ' + fold ( 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
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 {
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
const d = await getJson ( ` /podcasts/episodes/?q= ${ encodeURIComponent ( s . q ) } ¬es=1&limit=30&offset= ${ offset } ` ) ;
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 . 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-16 21:06:41 +02:00
const openFromMini = ( ) => ( miniKind === 'tts' ? ttsShowBook ( )
2026-10-14 18:05:23 +02:00
: miniKind === 'ambient' ? showTab ( 'ambient' )
2026-10-16 21:06:41 +02:00
: miniKind === 'radio' ? openRadioPlayer ( ) : openPlayer ( ) ) ;
text . addEventListener ( 'click' , openFromMini ) ;
miniGestures ( mini , openFromMini ) ;
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 ;
2026-10-14 18:05:23 +02:00
const tts = ( sign , label , fn , cls ) => {
const b = button ( sign , label , ( ) => { const t = window . _ _dioraTts ; if ( t ) fn ( t ) ; } , 'nui-tts-btn ' + cls ) ;
b . hidden = true ;
return b ;
} ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
mini . append ( art ( '' , 'mini' ) , text ,
2026-10-14 18:05:23 +02:00
tts ( '⏮︎' , 'Voriger Satz' , t => t . prev ( ) , 'nui-tts-prev' ) ,
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-14 18:05:23 +02:00
: miniKind === 'tts' ? window . _ _dioraTts && window . _ _dioraTts . toggle ( )
2026-10-04 00:18:06 +02:00
: miniKind === 'ambient' ? ambientToggle ( )
2026-10-14 18:05:23 +02:00
: miniKind === 'radio' ? window . _ _dioraRadioToggle && window . _ _dioraRadioToggle ( ) : N . podcastToggle ( ) ) , 'nui-mini-toggle' ) ,
tts ( '⏭︎' , 'Nächster Satz' , t => t . next ( ) , 'nui-tts-next' ) ,
tts ( '■' , 'Vorlesen beenden' , t => t . stop ( ) , 'nui-tts-stop' ) ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
dock . prepend ( mini ) ;
2026-10-14 18:05:23 +02:00
// Read-aloud uses this bar; app.js leaves out its own (.tts-bar).
window . _ _dioraTtsDock = true ;
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
2026-10-16 14:53:38 +02:00
// A "Podcasts" button in the bottom bar, between books and radio (with the page's
// navigation, nav.js, the bar has the podcasts as a section already).
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const tabbar = $id ( 'nui-tabbar' ) ;
2026-10-16 14:53:38 +02:00
if ( tabbar && ! window . DioraNav && ! tabbar . querySelector ( '[data-tab="podcasts"]' ) ) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const b = el ( 'button' ) ;
b . type = 'button' ;
b . dataset . tab = 'podcasts' ;
b . append ( el ( 'span' , 'nui-tab-icon' , '◎' ) , document . createTextNode ( 'Podcasts' ) ) ;
b . addEventListener ( 'click' , ( ) => { showTab ( 'podcasts' ) ; scrollTop ( 0 ) ; } ) ;
tabbar . insertBefore ( b , tabbar . querySelector ( '[data-tab="radio"]' ) ) ;
}
}
2026-10-16 21:06:41 +02:00
// Ambient (app.js's #tab-ambient): its sleep timer, a ☾ in the section's header
// (native-ui.js), else at the frame's top right corner.
2026-10-03 23:22:00 +02:00
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' ;
2026-10-16 21:06:41 +02:00
const bar = tab . querySelector ( '.nui-section-bar' ) ;
if ( bar ) bar . append ( b ) ; else tab . append ( b ) ;
2026-10-03 23:22:00 +02:00
}
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 ;
2026-10-04 12:25:27 +02:00
// An episode or the radio plays here: the bar is theirs, whatever came before.
const r = radioNow ( ) ;
if ( ! a . playing && ( ( player . playing && ! player . remote ) || ( r && r . playing ) ) ) return false ;
if ( player . kind === 'podcast' && player . playing && ! player . remote ) return false ;
2026-10-04 00:18:06 +02:00
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 , '' ) ;
2026-10-14 18:05:23 +02:00
const ttsArt = box . querySelector ( '.nui-tts-art' ) ;
if ( ttsArt ) ttsArt . remove ( ) ;
2026-10-04 00:18:06 +02:00
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 ;
}
2026-10-14 18:05:23 +02:00
// Read-aloud (app.js, window.__dioraTts; on Android with the device's voice) has the bar
// while it runs: the book, the chapter, sentence back / pause / sentence on / stop.
function ttsState ( ) {
try { return window . _ _dioraTts ? window . _ _dioraTts . state ( ) : null ; } catch ( e ) { return null ; }
}
function ttsButtons ( on ) {
if ( ! mini ) return ;
mini . querySelectorAll ( '.nui-tts-btn' ) . forEach ( b => { b . hidden = ! on ; } ) ;
}
// A tap on the text: back to the book that's being read, if it isn't open.
function ttsShowBook ( ) {
const s = ttsState ( ) ;
if ( s && s . bookId && typeof openBook === 'function' && ( typeof currentBookId === 'undefined' || currentBookId !== s . bookId ) ) openBook ( s . bookId ) ;
}
function renderTtsMini ( ) {
const s = ttsState ( ) ;
document . documentElement . classList . toggle ( 'nui-tts-on' , ! ! ( s && s . active ) ) ;
if ( ! mini || ! s || ! s . active ) return false ;
miniKind = 'tts' ;
document . documentElement . classList . remove ( 'nui-pod-playing' ) ;
document . documentElement . classList . add ( 'nui-pod-mini-open' ) ;
mini . hidden = false ;
mini . classList . add ( 'nui-mini-radio' ) ;
mini . classList . remove ( 'nui-mini-remote' ) ;
for ( const c of [ '.nui-pod-skip' , '.nui-mini-save' , '.nui-mini-devices' , '.nui-mini-here' ] ) mini . querySelector ( c ) . hidden = true ;
ttsButtons ( true ) ;
mini . querySelector ( '.nui-mini-station' ) . textContent = s . title || 'Vorlesen' ;
mini . querySelector ( '.nui-mini-track' ) . textContent = ( s . paused ? 'Angehalten' : 'Vorlesen' ) + ( s . chapter ? ' · ' + s . chapter : '' ) ;
const t = mini . querySelector ( '.nui-mini-toggle' ) ;
t . textContent = s . paused ? '▶' : '❚❚' ;
t . title = s . paused ? 'Weiterlesen' : 'Pause' ;
const box = mini . querySelector ( '.nui-pod-art' ) ;
setArt ( box , '' ) ;
const amb = box . querySelector ( '.nui-amb-art' ) ;
if ( amb ) amb . remove ( ) ;
if ( ! box . querySelector ( '.nui-tts-art' ) ) { box . textContent = '' ; box . append ( el ( 'span' , 'nui-pod-initial nui-tts-art' , '❝' ) ) ; }
mini . querySelector ( '.nui-pod-mini-bar span' ) . style . width = typeof s . progress === 'number' ? Math . min ( 100 , s . progress * 100 ) + '%' : '0' ;
return true ;
}
window . addEventListener ( 'diora-tts' , ( ) => renderMini ( ) ) ;
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-14 18:05:23 +02:00
if ( renderTtsMini ( ) ) return ;
ttsButtons ( false ) ;
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-14 18:05:23 +02:00
const ambArt = mini . querySelector ( '.nui-amb-art, .nui-tts-art' ) ;
2026-10-04 00:18:06 +02:00
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 …'
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
: ( bookLine ( player ) || player . feed || '' ) + ( player . dur ? ' · ' + clock ( shown ( ( player . dur - player . pos ) / 1000 , 0 , player . speed || 1 ) ) + ' übrig' : '' ) )
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
+ 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 {
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
const d = await getJson ( ` /podcasts/episodes/ ${ id } / ` ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
keepDetails ( id , d ) ;
return d ;
} catch ( e ) {
return storedDetails ( id ) ;
}
}
// Timestamps in shownotes ("12:34", "1:02:03") become links that jump there.
const STAMP = /\b(?:(\d{1,2}):)?(\d{1,2}):(\d{2})\b/g ;
function linkStamps ( root ) {
const walker = document . createTreeWalker ( root , NodeFilter . SHOW _TEXT ) ;
const nodes = [ ] ;
while ( walker . nextNode ( ) ) {
const n = walker . currentNode ;
if ( ! n . parentElement . closest ( 'a' ) && STAMP . test ( n . nodeValue ) ) nodes . push ( n ) ;
STAMP . lastIndex = 0 ;
}
for ( const n of nodes ) {
const frag = document . createDocumentFragment ( ) ;
let last = 0 ;
n . nodeValue . replace ( STAMP , ( m , h , mi , se , at ) => {
const secs = ( parseInt ( h || '0' , 10 ) * 3600 ) + parseInt ( mi , 10 ) * 60 + parseInt ( se , 10 ) ;
if ( parseInt ( se , 10 ) > 59 ) return m ;
frag . append ( document . createTextNode ( n . nodeValue . slice ( last , at ) ) ) ;
const a = el ( 'a' , 'nui-pod-stamp' , m ) ;
a . href = '#' ;
a . dataset . t = String ( secs ) ;
frag . append ( a ) ;
last = at + m . length ;
return m ;
} ) ;
frag . append ( document . createTextNode ( n . nodeValue . slice ( last ) ) ) ;
n . replaceWith ( frag ) ;
}
}
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 {
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
t = await getJson ( ` /podcasts/episodes/ ${ id } /transcript/ ` ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
transcripts . set ( id , t ) ;
} catch ( e ) {
body . textContent = '' ;
body . append ( el ( 'div' , 'nui-pod-meta' , navigator . onLine ? 'Für diese Folge gibt es kein lesbares Transkript.' : 'Das Transkript braucht eine Verbindung.' ) ) ;
return ;
}
}
body . textContent = '' ;
const list = el ( 'div' , 'nui-pod-transcript' ) ;
for ( const seg of t . segments || [ ] ) {
const p = el ( 'p' , 'nui-pod-seg' ) ;
p . dataset . start = String ( seg . start || 0 ) ;
if ( seg . speaker ) p . append ( el ( 'strong' , 'nui-pod-speaker' , seg . speaker + ': ' ) ) ;
p . append ( document . createTextNode ( seg . text || '' ) ) ;
if ( live ) p . addEventListener ( 'click' , ( ) => N . podcastSeek ( Math . round ( ( seg . start || 0 ) * 1000 ) ) ) ;
list . append ( p ) ;
}
list . addEventListener ( 'touchstart' , ( ) => { transcriptTouched = Date . now ( ) ; } , { passive : true } ) ;
body . append ( list ) ;
markTranscript ( body , ( player . pos || 0 ) / 1000 ) ;
}
function markTranscript ( scope , seconds ) {
const segs = scope . querySelectorAll ( '.nui-pod-seg' ) ;
if ( ! segs . length ) return ;
let current = null ;
segs . forEach ( p => { if ( parseFloat ( p . dataset . start ) <= seconds + 0.3 ) current = p ; } ) ;
segs . forEach ( p => p . classList . toggle ( 'current' , p === current ) ) ;
if ( current && current . dataset . shown !== '1' && Date . now ( ) - transcriptTouched > 8000 ) {
segs . forEach ( p => { p . dataset . shown = '' ; } ) ;
current . dataset . shown = '1' ;
current . scrollIntoView ( { block : 'center' , behavior : 'smooth' } ) ;
}
}
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 || { } ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
const { sheet : made , panel , top } = podSheet ( 'nui-pod-notes-sheet nui-pod-episode' , 'Schließen' , closeNotes ) ;
notesSheet = made ;
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 ) ;
2026-10-19 19:17:33 +02:00
// Similar episodes from the other podcasts (docs/podcasts.md, Stöbern).
if ( v2 ( ) && e . id > 0 ) {
const sim = el ( 'div' , 'nui-pod-similar' ) ;
panel . append ( sim ) ;
browseGet ( ` /podcasts/discover/similar/ ${ e . id } / ` , 60 * 60000 ) . then ( d => {
const rows = ( d . episodes || [ ] ) . slice ( 0 , 6 ) ;
if ( ! rows . length || ! sim . isConnected ) return ;
sim . append ( el ( 'div' , 'nui-pod-when' , 'Ähnliche Folgen' ) ) ;
const list = el ( 'div' , 'nui-pod-list' ) ;
for ( const o of rows ) list . append ( episodeRow ( ep ( o ) , { showFeed : true , showDate : true , list : 'stream' , noSelect : true } ) ) ;
sim . append ( list ) ;
} ) . catch ( ( ) => { } ) ;
}
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
document . body . append ( notesSheet ) ;
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 ) ;
} ;
2026-10-19 20:19:22 +02:00
if ( v2 ( ) && N . podcastLaterAdd ) {
if ( inLater ( e ) ) act ( 'listx' , '' , 'Aus der Merkliste' , ( ) => toggleLater ( e ) ) ;
else act ( null , '+ ' , 'Merken' , ( ) => toggleLater ( e ) ) ;
if ( inQueue ( e . id ) ) act ( 'listx' , '' , 'Aus der Liste' , ( ) => { N . podcastDequeue ( e . id ) ; e . raw . in _queue = false ; } ) ;
} else if ( inQueue ( e . id ) ) act ( 'listx' , '' , 'Aus Warteschlange' , ( ) => { N . podcastDequeue ( e . id ) ; e . raw . in _queue = false ; } ) ;
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 act ( null , '+' , 'Einreihen' , ( ) => { enqueue ( e ) ; e . raw . in _queue = true ; } ) ;
2026-10-19 20:19:22 +02:00
if ( window . DioraProjects ) act ( null , '◇' , 'Projekt' , ( ) => linkProject ( e , 0 ) ) ;
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 ( 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 = '' ;
2026-10-16 21:06:41 +02:00
// ---- gestures on the player (native-gestures.js has the reader's and the lists') -----------
// The mini player: up opens the big player; sideways skips back / forward (an episode,
// read-aloud's sentences) or goes to the previous / next saved station.
function miniGestures ( bar , open ) {
let g = null ;
let swiped = 0 ;
bar . addEventListener ( 'touchstart' , e => {
g = e . touches . length === 1 ? { x : e . touches [ 0 ] . clientX , y : e . touches [ 0 ] . clientY , t : Date . now ( ) } : null ;
} , { passive : true } ) ;
bar . addEventListener ( 'touchend' , e => {
if ( ! g ) return ;
const t = e . changedTouches [ 0 ] ;
const dx = t . clientX - g . x ;
const dy = t . clientY - g . y ;
const quick = Date . now ( ) - g . t < 700 ;
g = null ;
if ( ! quick ) return ;
if ( dy < - 40 && Math . abs ( dy ) > Math . abs ( dx ) * 1.2 ) {
swiped = Date . now ( ) ;
open ( ) ;
} else if ( Math . abs ( dx ) > 60 && Math . abs ( dx ) > Math . abs ( dy ) * 1.5 ) {
swiped = Date . now ( ) ;
miniStep ( dx < 0 ) ;
}
} , { passive : true } ) ;
// The tap that a swipe ends with is no tap.
bar . addEventListener ( 'click' , e => {
if ( Date . now ( ) - swiped < 450 ) { e . stopPropagation ( ) ; e . preventDefault ( ) ; }
} , true ) ;
}
function miniStep ( forward ) {
const prefs = devicePrefs ( ) ;
const secs = forward ? ( prefs . skip _forward || 30 ) : ( prefs . skip _back || 15 ) ;
if ( miniKind === 'remote' ) {
remoteCmd ( 'skip' , forward ? secs : - secs ) ;
toast ( ( forward ? '+' : '− ' ) + secs + ' s' ) ;
} else if ( miniKind === 'tts' ) {
const t = window . _ _dioraTts ;
if ( t ) forward ? t . next ( ) : t . prev ( ) ;
} else if ( miniKind === 'radio' ) {
radioStep ( forward ) ;
} else if ( miniKind !== 'ambient' ) {
N . podcastSkip ( forward ) ;
toast ( ( forward ? '+' : '− ' ) + secs + ' s' ) ;
}
}
// The previous / next of the saved stations (the radio's list, in its order).
function radioStep ( forward ) {
const rows = [ ... document . querySelectorAll ( '#saved-tbody tr[data-id]' ) ] ;
if ( ! rows . length || typeof playStation !== 'function' ) return ;
const now = typeof currentStation !== 'undefined' && currentStation ? currentStation . url : null ;
const at = rows . findIndex ( r => r . dataset . url === now ) ;
const row = rows [ ( ( at < 0 ? ( forward ? - 1 : 0 ) : at ) + ( forward ? 1 : - 1 ) + rows . length ) % rows . length ] ;
playStation ( row . dataset . url , row . dataset . name , parseInt ( row . dataset . id , 10 ) || null ) ;
toast ( '◉ ' + row . dataset . name ) ;
}
// The big player's cover: tap twice on its left / right half to skip back / forward.
function coverGestures ( cover ) {
let last = 0 ;
let lastSide = null ;
cover . addEventListener ( 'touchend' , e => {
if ( e . changedTouches . length !== 1 ) return ;
const t = e . changedTouches [ 0 ] ;
const r = cover . getBoundingClientRect ( ) ;
const side = t . clientX < r . left + r . width / 2 ? 'back' : 'fwd' ;
const now = Date . now ( ) ;
if ( now - last < 320 && side === lastSide ) {
e . preventDefault ( ) ;
last = 0 ;
const prefs = devicePrefs ( ) ;
const fwd = side === 'fwd' ;
N . podcastSkip ( fwd ) ;
const mark = el ( 'span' , 'nui-pod-cover-skip ' + ( fwd ? 'nui-right' : 'nui-left' ) ,
( fwd ? '+' : '− ' ) + ( fwd ? ( prefs . skip _forward || 30 ) : ( prefs . skip _back || 15 ) ) + ' s' ) ;
cover . append ( mark ) ;
setTimeout ( ( ) => mark . remove ( ) , 700 ) ;
return ;
}
last = now ;
lastSide = side ;
} , { passive : 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
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' ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
const { sheet : made , panel , top } = podSheet ( '' , 'Player schließen' , closePlayer , 'Player' ) ;
sheet = made ;
2026-10-16 21:06:41 +02:00
const cover = art ( player . artwork , 'player' , null , player . feed ) ;
coverGestures ( cover ) ;
panel . append ( top , cover ) ;
2026-10-02 19:30:03 +02:00
const chapBox = el ( 'div' , 'nui-pod-chapbox' ) ;
chapBox . hidden = true ;
2026-10-19 19:17:33 +02:00
const feedLine = el ( 'div' , 'nui-pod-player-feed nui-pod-player-link' ) ;
feedLine . setAttribute ( 'role' , 'button' ) ;
feedLine . title = 'Zur Seite des Podcasts' ;
// Tapping the podcast's (or the audiobook's) name: its page.
feedLine . addEventListener ( 'click' , ( ) => {
const id = player . feedId ;
if ( ! id ) return ;
if ( isBook ( feeds ( ) . find ( f => f . id === id ) ) || player . book ) {
if ( openAudiobook ( id ) ) return ;
}
closePlayer ( ) ;
window . _ _dioraPodcastOpenFeed ( id ) ;
} ) ;
panel . append ( el ( 'div' , 'nui-pod-player-title' ) , feedLine , 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' ) ,
2026-10-19 20:19:22 +02:00
window . DioraProjects && button ( '◇' , 'Mit Projekt verknüpfen – ab dieser Stelle' , ( ) => linkProject ( {
id : player . episodeId , title : player . title , feed : player . feed , feedId : player . feedId ,
} , ( player . pos || 0 ) / 1000 ) , 'nui-pod-project' ) ,
button ( '≡' , QNAME ( ) , showQueue ) ,
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
] . filter ( Boolean ) ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
panel . append ( extra ) ;
const notes = el ( 'div' , 'nui-pod-notesbox' ) ;
panel . append ( notes ) ;
document . body . append ( sheet ) ;
sheetEpisode = player . episodeId ;
showDetails ( notes , player . episodeId , true ) ;
renderPlayer ( ) ;
}
function closePlayer ( ) {
if ( ! sheet ) return false ;
sheet . remove ( ) ;
sheet = null ;
return true ;
}
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' ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
const { sheet : made , panel , top } = podSheet ( '' , 'Player schließen' , closePlayer , 'Radio' ) ;
sheet = made ;
2026-10-02 12:44:49 +02:00
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 ) ;
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 ( ) ) ;
Home: neue Startseite mit Weiterlesen, Weiterhören, Radio-Favoriten und Ambient; sw v87
- static/js/home.js rendert #tab-home aus dem, was die Seite schon hat:
zuletzt gelesenes Buch (und drei weitere), laufende oder nächste Folge mit
Restzeit und „N neue Folgen“, Favoriten-Sender (sonst die ersten
gespeicherten) und zuletzt gehörter Sender, Drift mit der zuletzt
gewählten Klangwelt und Sleep-Timer. Läuft im Browser, in diora desktop und
in der Android-App (dort mit static/js/ kopiert).
- app.js: Tab 'home' (TOP_TABS), window.__dioraAmbientStart() lädt Drift bei
Bedarf und startet es; die gewählte Klangwelt wird gemerkt und beim Laden
wiederhergestellt.
- podcasts-web.js: Startseite Home, wenn nichts anderes gewählt ist.
- native-podcast.js aus diora-android 7dcc0c6 (Home in der Startseiten-Wahl,
__dioraPodcastView).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:34:02 +02:00
// Home (diora-web static/js/home.js): one of the lists, e.g. "inbox" for the new episodes.
window . _ _dioraPodcastView = function ( id ) {
if ( VIEW _IDS . indexOf ( id ) < 0 ) return ;
selected = null ;
feedView = null ;
preview = null ;
view = id ;
try { localStorage . setItem ( 'nui_pod_view' , view ) ; } catch ( e ) { }
render ( ) ;
scrollTop ( 0 ) ;
} ;
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 || '' ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
sheet . querySelector ( '.nui-pod-player-feed' ) . textContent = bookLine ( player ) || player . feed || '' ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
const range = sheet . querySelector ( '.nui-pod-seek' ) ;
const dur = Math . round ( ( player . dur || 0 ) / 1000 ) ;
const pos = Math . round ( ( player . pos || 0 ) / 1000 ) ;
range . max = dur || 1 ;
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 ) } ] : [ ] )
2026-10-04 12:25:27 +02:00
. concat ( presetsEditItem ( ) , silenceSpeedItems ( ) ) ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
if ( ! N . podcastSpeedForPodcast || ! player . feedId ) { pick ( false ) ; return ; }
openSheet ( 'Geschwindigkeit' , [
{ icon : '◎' , label : 'Für alle Podcasts' , hint : own ? null : speedLabel ( player . speed || 1 ) , action : ( ) => pick ( false ) } ,
{ icon : '◉' , label : ` Nur für „ ${ plain ( player . feed ) } “ ` , hint : own ? speedLabel ( player . speed || 1 ) : 'noch keine eigene' ,
action : ( ) => pick ( true ) } ,
2026-10-04 12:25:27 +02:00
] . concat ( presetsEditItem ( ) , silenceSpeedItems ( ) ) ) ;
}
// „Stille überspringen“ where AntennaPod has it: in the speed menu. For all podcasts (this
// device), and for the playing one its own choice (server) — where the player can skip silence.
function silenceSpeedItems ( ) {
const prefs = devicePrefs ( ) ;
if ( ! N . podcastSetPref || ! ( 'skip_silence' in prefs ) ) return [ ] ;
const device = ! ! prefs . skip _silence ;
const live = player . feedId ? feeds ( ) . find ( x => x . id === player . feedId ) : null ;
const mine = live && 'skip_silence' in live ? live . skip _silence : undefined ;
const items = [ { icon : device ? '☑' : '☐' , label : 'Stille überspringen' ,
hint : ( device ? 'an' : 'aus' ) + ' · alle Podcasts' + ( mine === true ? ' · hier: immer' : mine === false ? ' · hier: nie' : '' ) ,
action : ( ) => { N . podcastSetPref ( 'skip_silence' , ! device ) ; toast ( device ? 'Stille wird nicht mehr übersprungen' : 'Stille wird übersprungen' ) ; } } ] ;
if ( mine !== undefined && N . podcastFeedSettings ) {
const title = plain ( player . feed ) ;
const save = v => N . podcastFeedSettings ( player . feedId , JSON . stringify ( { skip _silence : v } ) ) ;
items . push ( { icon : '∿' , label : ` Stille bei „ ${ title } “ ` , hint : mine === true ? 'immer überspringen' : mine === false ? 'nie überspringen' : 'wie alle Podcasts' ,
action : ( ) => openSheet ( ` Stille bei „ ${ title } “ … ` , [
{ icon : mine == null ? '●' : '○' , label : 'Wie alle Podcasts' , hint : device ? 'überspringen' : 'nicht überspringen' , action : ( ) => save ( null ) } ,
{ icon : mine === true ? '●' : '○' , label : 'Immer überspringen' , action : ( ) => save ( true ) } ,
{ icon : mine === false ? '●' : '○' , label : 'Nie überspringen' , action : ( ) => save ( false ) } ,
] ) } ) ;
}
return items ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
}
Sleep-Timer: 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' ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
const { sheet : made , panel , top } = podSheet ( 'nui-pod-remote' , 'Player schließen' , closePlayer , 'Player' ) ;
sheet = made ;
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
sheet . dataset . remote = remoteKey ( r ) ;
const cover = art ( onRadio ? r . station && r . station . logo : r . artwork , 'player' ) ;
if ( onRadio ) cover . classList . add ( 'nui-radio-art' ) ;
panel . append ( top , cover , el ( 'div' , 'nui-pod-player-title' ) , el ( 'div' , 'nui-pod-player-feed nui-pod-remote-line' ) ) ;
const controls = el ( 'div' , 'nui-pod-controls' ) ;
if ( onRadio ) {
controls . append ( button ( '▶' , 'Abspielen' , remoteToggle , 'nui-pod-play' ) ) ;
} else {
panel . append ( el ( 'div' , 'nui-pod-player-feed nui-pod-remote-feed' ) ) ;
const range = el ( 'input' , 'nui-pod-seek' ) ;
range . type = 'range' ;
range . min = 0 ;
range . step = 1 ;
range . addEventListener ( 'input' , ( ) => {
seeking = true ;
sheet . querySelector ( '.nui-pod-time-pos' ) . textContent = clock ( range . value ) ;
} ) ;
range . addEventListener ( 'change' , ( ) => {
remoteCmd ( 'seek' , range . value * 1000 ) ;
seeking = false ;
} ) ;
const times = el ( 'div' , 'nui-pod-times' ) ;
times . append ( el ( 'span' , 'nui-pod-time-pos' ) , el ( 'span' , 'nui-pod-time-left' ) ) ;
panel . append ( range , times ) ;
const prefs = devicePrefs ( ) ;
const back = prefs . skip _back || 15 ;
const fwd = prefs . skip _forward || 30 ;
controls . append (
button ( String ( back ) , ` ${ back } Sekunden zurück ` , ( ) => remoteCmd ( 'skip' , - back ) , 'nui-pod-back' ) ,
button ( '▶' , 'Abspielen' , remoteToggle , 'nui-pod-play' ) ,
button ( String ( fwd ) , ` ${ fwd } Sekunden vor ` , ( ) => remoteCmd ( 'skip' , fwd ) , 'nui-pod-fwd' ) ,
) ;
}
panel . append ( controls ) ;
if ( N . playerTakeOver ) {
const here = el ( 'button' , 'btn nui-pod-here' , '⇩ Hier abspielen' ) ;
here . type = 'button' ;
here . addEventListener ( 'click' , takeOver ) ;
panel . append ( here ) ;
}
const extra = el ( 'div' , 'nui-pod-extra' ) ;
if ( ! onRadio ) extra . append ( button ( '⏭' , 'Nächste Folge' , ( ) => remoteCmd ( 'next' ) , 'nui-pod-next' ) ) ;
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 ) ;
}
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.
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 ( ) ;
Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.
Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
const { sheet : made , panel , top } = podSheet ( 'nui-pod-notes-sheet nui-pod-dls' , 'Schließen' , closeDownloads ) ;
dlSheet = made ;
2026-10-02 12:44:49 +02:00
dlBody = el ( 'div' , 'nui-pod-dls-body' ) ;
panel . append ( top , el ( 'div' , 'nui-pod-player-title' , 'Downloads' ) , dlBody ) ;
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 ( ) ;
2026-10-19 18:21:03 +02:00
listsOnOnce ( ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf 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 ( ) ;
} ;
2026-10-19 18:21:03 +02:00
setTimeout ( listsOnOnce , 2000 ) ;
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf 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 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-12 20:05:13 +02:00
// What plays, for the screensaver (native-saver.js): the player's report and the chapter at its place.
const nowAsked = new Set ( ) ;
window . _ _dioraPlayerNow = function ( ) {
if ( ! player || player . kind !== 'podcast' ) return player ;
const d = details . get ( player . episodeId ) ;
if ( ! d && player . episodeId && ! nowAsked . has ( player . episodeId ) ) {
nowAsked . add ( player . episodeId ) ;
Promise . resolve ( loadDetails ( player . episodeId ) ) . catch ( ( ) => { } ) ;
}
let chapter = '' ;
const chapters = ( d && d . chapters ) || [ ] ;
const t = ( player . pos || 0 ) / 1000 ;
for ( const c of chapters ) if ( ( c . start || 0 ) <= t + 0.5 ) chapter = c . title || '' ;
return Object . assign ( { } , player , { chapter : plain ( chapter ) } ) ;
} ;
2026-10-03 23:08:57 +02:00
// Ambient starts (app.js): an episode playing here pauses.
window . _ _dioraPausePlayer = function ( ) {
2026-10-04 12:25:27 +02:00
if ( player . kind !== 'podcast' || ! player . playing || player . remote ) return ;
// Pause, not toggle: Android's audio focus may have paused it a moment before.
if ( N . podcastPause ) N . podcastPause ( ) ; else if ( N . podcastToggle ) N . podcastToggle ( ) ;
2026-10-03 23:08:57 +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
// 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 ;
2026-10-14 18:05:23 +02:00
const onPodcasts = typeof _currentTopTab !== 'undefined' && _currentTopTab === 'podcasts' ;
if ( onPodcasts && stopSelecting ( ) ) return true ;
// The tab's own pages go back one by one, before the next handler: that one would
// leave the tab for Home (native-radio.js). A menu or dialog on top closes first.
if ( onPodcasts && ! somethingOnTop ( ) ) {
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' ; }
2026-10-19 19:17:33 +02:00
if ( ! views ( ) . some ( v => v . id === view ) ) view = 'queue' ;
2026-10-02 19:30:03 +02:00
render ( ) ;
return true ;
}
2026-10-19 19:17:33 +02:00
if ( view === 'browse' ) return browseBack ( ) ;
2026-10-14 18:05:23 +02:00
}
if ( back && back ( ) ) return true ;
if ( onPodcasts ) {
Podcasts im Browser: die Podcast-Seite der App statt des alten Podcast-Tabs
Der Podcast-Tab der Web-App zeigt jetzt dieselbe Oberfläche wie die
Android-App: native-podcast.js aus diora-android, unverändert kopiert nach
static/podcasts/ (tools/sync_podcast_ui.py, mit Quell-Commit im Kopf),
dazu die Podcast-Teile von native.css.
static/js/podcasts-web.js spielt im Browser die Rolle der App
(window.DioraPodcastHost statt DioraNative):
- Zustand aus /podcasts/state/, in localStorage für einen schnellen Start;
Änderungen sofort lokal und der Reihe nach an den Server (Outbox, übersteht
Neuladen und Offline), wie Podcasts.kt, inkl. „immer sortiert halten“
- eigener Player (nicht das Radio-Audio-Element), Warteschlange = Playlist,
wie RadioService.EpisodeTracker: Stelle fortsetzen, Intro/Outro, Fortschritt
alle 10 s und bei Pause, „gehört“ kurz vor Schluss (auch bei „nächste“),
automatisch weiter, nach Pause etwas zurück (0,8/3/10/20 s), Tempo und
Lautstärke (bis 1) pro Podcast, Sleep-Timer nur während des Abspielens mit
Ausblenden
- Sperrbildschirm und Medientasten über navigator.mediaSession
- Abspiel-Einstellungen dieses Geräts in localStorage (wie PlaybackPrefs)
- keine Downloads (die Oberfläche blendet sie aus)
- Radio und Podcast nie gleichzeitig: eine Folge stoppt das Radio, ein Sender
legt die Folge weg
- Bottom-Sheet und Mini-Player-Dock über der Radio-Leiste; auf breiten
Bildschirmen eine mittige Spalte (760 px)
In der App und der Desktop-App (window.DioraNative vorhanden) tut
podcasts-web.js nichts.
Entfernt: der alte Podcast-Tab (Markup, ~1000 Zeilen in app.js, CSS), die
Podcast-Leiste im Player, INITIAL_PODCAST_FEEDS und das Herunterladen von
Folgen in den Browser-Cache (der Service Worker räumt diora-podcast-v1 ab).
Service-Worker-Cache auf v52, neue Dateien im App-Shell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:26:32 +02:00
showTab ( 'books' ) ;
return true ;
}
return false ;
} ;
2026-10-14 18:05:23 +02:00
// A menu, dialog or note sheet over the page (native-ui.js, app.js's modal).
function somethingOnTop ( ) {
if ( document . querySelector ( '#nui-sheet, .nui-backdrop, .note-bottom-sheet' ) ) return true ;
const modal = $id ( 'modal-dialog' ) ;
return ! ! modal && getComputedStyle ( modal ) . display !== 'none' ;
}
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
} ) ;
} ) ( ) ;