- _saveBookMeta vergleicht local vs. Server last_read — neuere lokale
Daten (offline gelesen) überleben den nächsten Server-Abruf
- loadBookList pusht beim Online-Werden automatisch lokal neueren
Fortschritt an den Server bevor die Liste gerendert wird
- saveBookmarks/saveHighlights markieren fehlgeschlagene Saves als
pending sync (localStorage); beim nächsten online-Event werden alle
pending Annotationen zuerst gepusht, dann erst die Buchliste geladen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Statt padding-top gegen die Navbar zu kämpfen ist #tabs jetzt
position:fixed direkt unterhalb der Navbar (top:var(--nav-h),
z-index:150). Damit ist die Leiste garantiert immer sichtbar,
unabhängig vom Scroll-Zustand oder Offline-Status.
main-content padding-top auf calc(var(--nav-h)+3rem) erhöht um
Platz für Navbar+Tabs zu schaffen. Offline-Banner vereinfacht zu
einer body-Klasse (kein fixed DOM-Element mehr).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- main-content padding-top auf calc(var(--nav-h) + 0.75rem) erhöht
damit die Tab-Leiste (Radio/Focus/Podcasts/Books) sicher unterhalb
der fixed Navbar liegt — auf Desktop und Mobile
- SW v10: sendet SW_ACTIVATED an alle offenen Tabs wenn ein neuer SW
übernimmt; app.js lädt daraufhin neu (außer wenn ein Buch geöffnet
ist) — verhindert dass der Browser eine uralte HTTP-gecachte Version
ausliefert
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bei Offline-Start oder Wechsel in Flugzeugmodus: roter Banner
unterhalb der Navbar, automatischer Wechsel zum Books-Tab (einziger
sinnvoller Tab offline), Upload-Zone wird ausgeblendet
- Bei Rückkehr online: Banner verschwindet, Upload-Zone erscheint wieder,
Buchliste wird neu synchronisiert
- openBook unterscheidet jetzt zwischen "nicht gecacht" (Toast-Hinweis
zum einmaligen Online-Öffnen) und echtem Defekt (markBookBroken wie
bisher) — kein stilles Scheitern mehr
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ohne diesen Fix schlägt das Öffnen der App im Flugzeug fehl, weil der
Service Worker die HTML-Seite (/) nicht cached — der Browser zeigt
dann eine Netzwerkfehler-Seite bevor JS überhaupt startet.
Strategie: Network-first für Navigationsanfragen, bei Netzwerkfehler
die zuletzt gecachte Version ausliefern. Nach dem ersten Online-Besuch
funktioniert die App vollständig offline.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ohne navigator.storage.persist() ist der IndexedDB-Speicher "best-effort"
und kann vom Browser bei Speicherknappheit still gelöscht werden. Der
Aufruf erfolgt beim ersten Schreiben eines Buches in den Cache — Chrome
gewährt das für installierte PWAs automatisch, Firefox kann einen Dialog
zeigen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Chrome 98+ hat Probleme mit kombinierten purpose-Werten ("any maskable")
auf einem einzelnen Icon-Eintrag. Separate Einträge für "any" und
"maskable" sind zuverlässiger und entsprechen der Spec-Empfehlung.
SW-Version auf v8 erhöht, um nach den JS-Änderungen einen sauberen
Cache-Neustart zu erzwingen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Popover had transparent background because --bg-card is explicitly
transparent; now uses hardcoded colors with theme-aware overrides.
Also strips title attributes during EPUB sanitization to prevent
native browser tooltips from appearing while reading.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- IndexedDB v2 mit zwei neuen Stores: book_meta (Buchliste +
Lesefortschritt) und book_annotations (Lesezeichen & Highlights)
- loadBookList fällt bei Netzwerkfehler auf lokalen Meta-Cache zurück,
blendet Upload-Zone aus und zeigt Offline-Hinweis
- Leseposition (scroll_fraction + position_anchor) wird bei jedem
Auto-Save lokal gecacht, sodass offline-Öffnen am richtigen Ort
landet
- Lesezeichen und Highlights werden nach Server-Abruf lokal gecacht
und bei Netzwerkfehler von dort geladen; Änderungen offline bleiben
bis zur nächsten Synchronisierung erhalten
- Mobile Reader-Header: kompaktere Abstände, Fortschritts-Input auf
kleinen Screens ausgeblendet
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hovering over a footnote reference shows the footnote content in a
floating popover; moving the mouse away dismisses it. On mobile, a
tap toggles the popover with an ✕ close button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
replace-data endpoint now optionally accepts meta_ct/meta_iv.
The client extracts title/author from the uploaded file (same as
normal upload), re-encrypts metadata with the current key, and sends
both data and meta together — so broken books are fully restored
including their displayed title and author.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a book fails to open, a '!' button appears next to it in the list.
Clicking it prompts for the original file; the file is re-encrypted with
the current key and replaces the broken ciphertext on the server while
keeping all metadata, progress, highlights and bookmarks intact.
- Add POST /books/<pk>/replace-data/ endpoint (updates data only)
- Add _evictCachedBook() to clear IndexedDB cache for a single book
- Replace complex client-side re-encryption on password change with a
simple confirm() warning that books will need to be re-uploaded
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Publisher EPUBs often set font-weight via <b>/<strong> tags or inline
style attributes. A new toggle in the settings panel adds the
.reader-no-bold class which overrides all font weights to normal.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add font family selector (Serif/Sans/Verdana/Mono) to reader settings panel,
saved per book in localStorage
- Strip <style>/<script> blocks before regex and DOMParser processing to avoid
working over large base64-embedded font CSS in publisher EPUBs
- Parallelize image blob URL creation with Promise.all instead of sequential await
- Inject chapter HTML progressively in batches with requestAnimationFrame yields
to keep the UI responsive when loading many-chapter books
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Current page renders almost instantly, no overlay needed.
Overlay stays for initial book open only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
renderPdf now takes an optional pivotPage. Pass 1 builds the full DOM
and sizes all canvases (instant). Then scrolls to the pivot page
immediately so the user stays in place. Pass 2 renders pixels from
pivot→end, then pivot-1→start.
reRenderPdf detects the current visible page before re-rendering and
passes it as pivot, so zoom no longer resets scroll position.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scroll position was restored before browser finished layout, so
scrollHeight was still wrong. Now waits two animation frames after
reRenderPdf completes before restoring position.
Also shows loading overlay during zoom re-renders, not just on initial load.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses a 16ms interval (~60fps) instead of per-event smooth scroll,
so holding j/k scrolls fluidly. d/u/f/b remain one-shot with smooth.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moved overlay out of contentEl (which renderPdf clears immediately)
into the reader-overlay element. It now stays visible for the entire
render duration and is removed only after renderPdf resolves.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Displays a dark background with a spinning indicator in the reader
content area before renderPdf starts. renderPdf clears innerHTML
itself when it begins, so no explicit cleanup needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inline onclick='playStation("url", "name", null)' broke because
JSON.stringify wraps strings in double quotes, conflicting with the
onclick attribute quotes. Replaced with data attributes + addEventListener.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Zoom is now baked into the canvas render scale instead of being applied
via CSS zoom. CSS zoom only scales pixels already on the canvas, causing
blurriness at high DPR (e.g. PWA on mobile). Now the canvas is rendered
at the correct resolution for the chosen zoom level.
Also: stop nulling currentPdfDoc on re-render so PDF.js page cache is
preserved, making zoom re-renders significantly faster.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 📻 button now opens a sidebar with the currently playing station/track,
play/stop + volume control, and the saved stations list. The old focus
station configuration UI (presets, custom URL input, save-to-server) and
the auto-play-on-book-open behavior are removed.
Closes#6
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each book gets its own key (diora_reader_settings_{bookId}).
Opening a book resets to defaults then applies book-specific overrides,
so zoom, spread, invert, theme etc. are remembered independently per book.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spread mode: new toggle in reader settings renders pages side-by-side
(cover alone, then pairs 2-3, 4-5...) using full screen width. Each
page scales to half the container. Navigation and scroll position
tracking are unchanged since per-page IDs are preserved.
Pinch-to-zoom: captures 2-finger pinch on the PDF reader, blocking
native browser zoom. Live CSS zoom during gesture, snaps to nearest
10% step on release. Single-finger scroll unaffected. applyPdfZoom
moved to module level so touch handlers can call it. Touch listeners
cleaned up on reader close.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- HIGHLIGHTS_MAX_BYTES, BOOKMARKS_MAX_BYTES: books size limits now in settings
- PODCAST_INBOX_PAGE_SIZE: shared between podcasts/views.py and app.js via DIORA_CONFIG
- VOLUME_DEFAULT: radio stream player default volume
- ITUNES_TIMEOUT: unified iTunes API timeout (was 5s vs 6s inconsistency)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend settings are now the single source of truth for EBOOK_MAX_BYTES
and BG_MAX_BYTES. A new context processor exposes them to all templates,
and base.html injects them as window.DIORA_CONFIG so app.js reads from
there instead of hardcoded values.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create EBookProgress on upload so new books get a last_read timestamp
and sort to the top of the book list
- Update frontend file size check from 10 MB to 50 MB
- Fix backend error message to say 50 MB instead of 10 MB
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Listens for navigator.mediaDevices.devicechange and re-routes the
audio element to the new default output device without a page reload.
Debounced 500ms to handle BT device bursts. Podcasts resume at the
correct timestamp via loadedmetadata.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Hide volume label/slider/input on mobile (≤600px)
- Add − and + buttons flanking the PDF zoom slider
- Preserve scroll fraction across zoom changes in PDF scroll mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move auto-save timer setup to after position restore so an early
visibilitychange can't overwrite the saved position with 0
- Add missing restore path for non-paginated PDF scroll mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Cache encrypted book data in IndexedDB (cache-first on open)
- Evict books not read for 4 weeks on book list load
- Fix PDF paginated mode not activating on book open (mobile)
- enterPdfPaginatedMode() now called after position restore in openBook()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace timer/edge-hover system with a simple click-to-toggle.
Tapping interactive elements (buttons, links, settings) is ignored.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scale canvas by devicePixelRatio and constrain CSS size to logical
pixels so PDF pages render crisp on Retina and mobile displays.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces scroll_fraction-only position tracking with element-based
anchors ("{index}:{innerFraction}"). Position is now stable across
font size changes and different screen sizes. A ResizeObserver
restores the anchor on viewport/orientation changes.
Falls back to scroll_fraction for books without a saved anchor.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The custom /radio/stream-player/ page is served over HTTPS, so the browser
still applies mixed-content restrictions and upgrades http:// audio to https://,
which fails for streams without TLS support.
Fix: window.open(url, '_blank') navigates the tab directly to the HTTP URL.
The tab itself is then HTTP, bypassing mixed-content restrictions entirely.
Browser plays the stream natively with its built-in audio player.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When top bars are showing, use their actual combined offsetHeight (+12px buffer)
as the hide threshold instead of the fixed 60px. This prevents the bars from
disappearing while the mouse moves from the edge zone down to click header buttons.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After opening a book, a 5-second timer adds body.reader-immersive which:
- Slides navbar up (translateY(-100%))
- Slides now-playing bar down (translateY(100%))
- Expands reader overlay to full viewport (top:0, bottom:0)
- Collapses reader header (max-height:0)
Mouse near top edge (<60px): shows navbar + reader header
Mouse near bottom edge (<60px): shows now-playing bar
Touch at top/bottom edge: shows respective bar for 3s then hides again
closeReader() cleans up all classes and event listeners
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of trying a HTTPS upgrade (which fails for IP-based streams):
- playStation() detects http:// URL on https:// page, opens /radio/stream-player/
with url, name, vol as query params, then returns — main stream is already
stopped by the stopPlayback(false) call at the top of playStation()
- New view stream_player renders a standalone minimal player page
- Template: auto-plays on load, correct volume from URL param, volume changes
synced back to localStorage so main window picks it up next time,
live track metadata via SSE, tab title updates on track change,
close-tab button
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Browsers block HTTP (mixed content) audio from HTTPS pages. On playStation,
if the URL is http:// and page is https://, try the https:// version first.
If the stream fails to load, show a clear error in the track display instead
of silently doing nothing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove mouseup highlight selection listener
- Remove Paginated button from PDF settings panel
- Remove pagination auto-enable on book open
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend includes last_read from EBookProgress.updated_at.
Frontend sorts by last_read desc, unread books by uploaded_at desc.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add render generation counter (_pdfRenderGen) to abort overlapping renders
- Wrap all pages in #pdf-viewport div; zoom slider just updates style.zoom
- Use 'input' event on zoom slider for live preview without re-rendering
- enterPdfPaginatedMode resets viewport zoom to 1; exitPdfPaginatedMode restores it
- Remove pdfZoom factor from renderPdf scale (CSS zoom handles it now)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use arrayBuffer.slice(0) before passing to pdfjsLib.getDocument so PDF.js
doesn't transfer/detach currentPdfBuffer, enabling re-renders on zoom change
- Cap containerWidth to min(viewport-32, 900px) so PDF doesn't stretch across
the full viewport on wide screens
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>