Reader zeigt Bücher jetzt als eine fortlaufende Ansicht über alle Kapitel statt Kapitel für Kapitel (diora_tui/blocks.py, layout.py, book_view.py, reader_screen.py). blockIndex wird exakt wie app.js' EPUB_BLOCK_SELECTOR gezählt (p/h1-6/li/blockquote/dt/dd/figcaption + kindlose divs), inklusive linear="no"-Spine-Einträgen — app.js filtert die nicht, und Skippen hätte sowohl Fußnoten-Ziele verfehlt als auch alle folgenden Blockindizes gegen den Web-Reader verschoben. innerFraction ist eine zeilenbasierte Näherung (Terminal hat keine Pixel-Geometrie), was funktioniert, weil der Furthest-Wins-Vergleich primär nach blockIndex sortiert. Rendering nutzt Textuals Line-API (ContinuousBookView.render_line) statt eines einzelnen riesigen Static — bei großen Büchern (mehrere reale heruntergeladene Bücher haben zehntausende Blocks) hätte ein Static den Layout/Paint-Pass auf über eine Minute gebracht. Zusätzlich cached diora_tui/cache.py das (Buch, Layout)-Paar pro (Datei, Breite) auf Platte für schnelles Wiederöffnen. Text ist auf 120 Zeichen begrenzt und zentriert. Fußnoten (f-Taste, FootnoteScreen): Erkennung wie app.js' _looksLikeFootnoteLink; Ziel-Auflösung sammelt IDs aus dem ganzen Block-Teilbaum (nicht nur vom Block-Tag selbst), weil Fußnoten-Ziele häufig auf einem inneren <a> statt dem umschließenden <p> sitzen. Progress-Sync ist jetzt bidirektional, ohne Übersetzungsschicht nötig, da beide Seiten dasselbe Anchor-Format nutzen: sync zieht book_progress aus dem Snapshot in den lokalen Store (furthest-wins); der Reader schickt bei offenen server-verknüpften Büchern Updates zurück (force: false, im Hintergrund-Worker). Verschlüsselungs-Key-Beschaffung ergänzt um den Fallback localStorage.getItem(...) falls die Clipboard-API in der Konsole verweigert wird. Getestet: Blockindex-/Fußnoten-Korrektheit gegen reale Bücher (u.a. 3686/3686 aufgelöste Fußnoten bei einem Zizek-Band), Anchor-Mathematik per Unit-Test, vollständiger Pilot-Test (Navigation, Scroll, Kapitelsprung, Fußnoten-Peek, Resize), Performance-Messung über mehrere Buchgrößen inkl. Cache-Effekt (größtes Buch: ~34k Blocks, kalt ~20-30s, warm ~5s), Save/Restore-Round-Trip 5x wiederholt gegen eine Race-Condition beim ersten Post-Load-Scroll, und Progress-Push End-to-End gegen einen echten Dev-Server verifiziert.
110 lines
5.9 KiB
Markdown
110 lines
5.9 KiB
Markdown
# diora-tui
|
|
|
|
EPUB-Reader im Terminal — die erste Stufe einer TUI-Version von diora.
|
|
|
|
Zeigt ein Buch als **eine durchgehende Ansicht** über alle Kapitel hinweg (wie diora's
|
|
Web-Reader), nicht Kapitel für Kapitel. Lesefortschritt wird als dieselbe
|
|
`"blockIndex:innerFraction"`-Positionsangabe geführt wie der Web-Reader (`books/models.py`,
|
|
`EBookProgress`) — `blockIndex` zählt dabei exakt wie `static/js/app.js`s
|
|
`EPUB_BLOCK_SELECTOR` (`p, h1-h6, li, blockquote, dt, dd, figcaption` + textbasierte
|
|
`div`s ohne Element-Kinder), fortlaufend über das ganze Buch. Dadurch ist die Position
|
|
zwischen TUI und Web-Reader direkt vergleichbar, und `diora-tui sync` kann Fortschritt in
|
|
beide Richtungen synchronisieren (siehe unten).
|
|
|
|
Fortschritt wird — genau wie im Web-Reader (`save_progress`) — nur vorwärts überschrieben
|
|
("furthest wins"), lokal in `progress.json` (`~/.local/share/diora-tui/`, via
|
|
`platformdirs`), damit ein älterer/gestaffelter Lauf nie eine bereits weiter gelesene
|
|
Position zurücksetzt.
|
|
|
|
## Setup
|
|
|
|
```bash
|
|
cd tui
|
|
pip install -e .
|
|
```
|
|
|
|
## Nutzung
|
|
|
|
```bash
|
|
diora-tui --library ~/Books # Standard: ~/Books
|
|
```
|
|
|
|
Tastenkürzel:
|
|
|
|
- `↑`/`k`, `↓`/`j` — zeilenweise scrollen
|
|
- `n` — nächstes Kapitel, `p` — vorheriges Kapitel
|
|
- `f` — Fußnote in der Nähe der aktuellen Position anzeigen (Peek-Overlay, `Escape`/`f`/`q`
|
|
zum Schließen); Erkennung folgt derselben Heuristik wie `app.js`s
|
|
`_looksLikeFootnoteLink` (Link in/um `<sup>`, Klassenname mit note/footnote/fn, oder
|
|
`epub:type="noteref"`)
|
|
- `Enter` — markiertes Buch aus der Bibliothek öffnen
|
|
- `Escape` / `q` — zurück zur Bibliothek (im Reader) bzw. beenden (in der Bibliothek)
|
|
|
|
Der Fließtext ist auf 120 Zeichen Breite begrenzt und horizontal zentriert (lesbarer als
|
|
volle Terminalbreite bei breiten Fenstern).
|
|
|
|
## Bücher + Fortschritt vom Server holen (`diora-tui sync`)
|
|
|
|
```bash
|
|
diora-tui sync # nutzt gespeicherte Zugangsdaten, sonst interaktive Abfrage
|
|
diora-tui sync --server https://diora.creamfresh.xyz --save # einmalig einrichten + speichern
|
|
```
|
|
|
|
Lädt alle EPUBs des Accounts über `GET /api/sync/` + `GET /books/<id>/data/` herunter,
|
|
entschlüsselt sie lokal (AES-256-GCM, kompatibel zu `static/js/app.js`) und legt sie als
|
|
normale `.epub`-Dateien in `--library` ab (Dateiname `<id> - <Titel>.epub`) — von da an
|
|
funktionieren sie wie jedes andere lokale Buch. Bereits heruntergeladene Bücher werden
|
|
beim nächsten Lauf übersprungen (kein erneuter Download). Der Fortschritt aus dem Snapshot
|
|
wird dabei ebenfalls übernommen (nur vorwärts, wie lokal auch).
|
|
|
|
Während ein so heruntergeladenes Buch geöffnet ist (erkennbar am `<id> - `-Dateinamens-
|
|
Präfix), schickt der Reader Fortschritts-Updates zusätzlich zurück an den Server
|
|
(`POST /books/<id>/progress/`, `force: false` — überschreibt also nie eine weiter
|
|
gelesene Position, egal ob die vom Web-Reader oder einem anderen Gerät stammt). Rein
|
|
lokale Bücher (ohne dieses Präfix) bleiben unangetastet, kein Netzwerkzugriff.
|
|
|
|
Dafür nötig, beim ersten Lauf abgefragt (danach optional lokal gespeichert unter
|
|
`~/.config/diora-tui/config.json`, `chmod 600`):
|
|
|
|
- **Server-URL** — z.B. `https://diora.creamfresh.xyz`.
|
|
- **API-Token** — diora → Einstellungen (`/accounts/settings/`) → "Personal Access Token".
|
|
- **Verschlüsselungs-Key** — der AES-256-Schlüssel, mit dem deine Bücher clientseitig
|
|
verschlüsselt wurden. Der `sync`-Prompt bietet zwei Wege:
|
|
1. **Aus Benutzername + Passwort ableiten** (Standard) — reproduziert exakt, was diora's
|
|
"Unlock with password"-Formular im Browser tut (PBKDF2-HMAC-SHA256, 200.000
|
|
Iterationen, Salt `"diora:" + username`; siehe `static/js/app.js:deriveAndStoreKey`).
|
|
Funktioniert nur, wenn der Account diesen Weg im Browser mindestens einmal benutzt
|
|
hat — sonst wurde der Key ursprünglich zufällig im Browser erzeugt, und diese
|
|
Ableitung trifft ihn nicht. `sync` meldet einen falschen Key als
|
|
Entschlüsselungsfehler (harmlos, kein Datenverlust), nie als falsches Ergebnis.
|
|
2. **Base64-Key direkt einfügen** — für den Fall, dass Weg 1 nicht passt. Der
|
|
Export-Button dafür ist im Browser-UI aktuell nicht verdrahtet (`exportEncKey()` in
|
|
`app.js` existiert, hat aber keinen sichtbaren Button); bis das nachgezogen ist, in
|
|
der Browser-Devtools-Konsole auf der diora-Seite (nicht `/accounts/settings/` — die
|
|
lädt `app.js` nicht) ausführen: `await exportEncKey()` — kopiert den Key ins
|
|
Clipboard, von dort ins `sync`-Prompt einfügen. Schlägt das mit einem
|
|
`NotAllowedError`/`InvalidAccessError` fehl, stattdessen direkt aus `localStorage`
|
|
lesen: `localStorage.getItem('diora_enc_key_' + window.USER_ID)`.
|
|
|
|
Der Key/Token wird genauso vertrauensvoll behandelt wie im Web-Client (dort liegt der
|
|
Schlüssel unverschlüsselt in `localStorage`): lokal als Klartext in einer 0600-Datei.
|
|
|
|
## Performance bei großen Büchern
|
|
|
|
Sehr große Bücher (mehrstellige Tausend Absätze — z.B. Sammelbände) können beim
|
|
*ersten* Öffnen mehrere Sekunden bis niedrige zweistellige Sekunden brauchen (Parsing +
|
|
Zeilenumbruch-Berechnung für die durchgehende Ansicht). Ein zweites Öffnen desselben
|
|
Buchs bei gleicher Terminalbreite ist dank Cache (`~/.cache/diora-tui/layout_cache/`)
|
|
deutlich schneller. Das Rendering selbst skaliert nicht mit der Buchgröße — nur die
|
|
tatsächlich sichtbaren Zeilen werden gezeichnet (Textual Line API), nicht das ganze Buch
|
|
auf einmal.
|
|
|
|
## Grenzen der aktuellen Version
|
|
|
|
- Nur EPUB, kein PDF — `sync` lädt PDFs im Account gar nicht erst herunter (übersprungen,
|
|
wird gemeldet), da der Reader sie ohnehin nicht darstellen kann.
|
|
- Text wird als Fließtext ohne Bild-/Layout-Rendering dargestellt.
|
|
- `innerFraction` (die Position *innerhalb* eines Blocks) ist eine Terminal-Näherung
|
|
(zeilenbasiert statt pixelbasiert wie im Browser) — für die Fortschritts-Sortierung
|
|
zählt primär `blockIndex`, der exakt mit dem Web-Reader übereinstimmt; `innerFraction`
|
|
ist nur ein Tiebreaker innerhalb desselben Blocks.
|