Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Immersive reader mode — tap centre of screen to toggle bars
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
let _immBarsVisible = true;
|
|
|
|
|
|
|
|
|
|
|
|
function _immHandleTap(e) {
|
|
|
|
|
|
// Ignore taps on interactive elements (buttons, links, inputs, settings panel, footnote popover)
|
|
|
|
|
|
if (e.target.closest('button, a, input, select, label, #reader-settings-panel, .reader-header, .footnote-popover, #reader-margin, #highlight-popover, .note-bottom-sheet, .tts-bar')) return;
|
|
|
|
|
|
// In marker mode, taps have a dedicated meaning (highlight/create a note) —
|
|
|
|
|
|
// don't also toggle the immersive bars underneath.
|
|
|
|
|
|
if (markerModeActive) return;
|
|
|
|
|
|
// Don't toggle bars while the user has just finished selecting text (e.g. to highlight it)
|
|
|
|
|
|
const sel = window.getSelection();
|
|
|
|
|
|
if (sel && !sel.isCollapsed) return;
|
|
|
|
|
|
// If a footnote popover is open, dismiss it instead of toggling bars
|
|
|
|
|
|
if (currentFootnotePopover && !currentFootnotePopover.contains(e.target)) {
|
|
|
|
|
|
dismissFootnotePopover();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
_immBarsVisible = !_immBarsVisible;
|
|
|
|
|
|
document.body.classList.toggle('reader-immersive', !_immBarsVisible);
|
|
|
|
|
|
// Close settings panel when bars disappear
|
|
|
|
|
|
if (!_immBarsVisible && readerSettingsPanelOpen) {
|
|
|
|
|
|
const sp = document.getElementById('reader-settings-panel');
|
|
|
|
|
|
if (sp) sp.remove();
|
|
|
|
|
|
readerSettingsPanelOpen = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Nachrichten und Blogs im Buch-Reader, Doppelklick für Vollbild, Hörbücher wie die Bücher-Bibliothek
65-article.js (DioraArticle): Artikel öffnen im selben Reader wie Bücher – Schrift, Farben,
Seiten/Doppelseite mit Fußnoten am Seitenfuß, Suche, Vorlesen, Vollbild. Lesezeichen,
Markierungen und Notizspalte bleiben Büchern vorbehalten. Merken, Projekt und Original
stehen in der Kopfzeile (Apps: im Lesemenü). feeds.js gibt sein eigenes Overlay auf;
Fortschritt wie bisher (bis zum unteren Bildschirmrand gelesen, ab 90 % gelesen).
Einstellungen der Artikel gelten für alle Artikel zusammen.
62-reader.js: Doppelklick (Maus) auf den Text schaltet Vollbild an/aus, die Leisten gehen mit.
audiobooks.js: die Hörbücher sind gebaut wie die Bücher – Kopf mit Suche, Sortierung und +,
Chips Angefangen/Ungehört/Gehört und Ordner, „Weiterhören“ wie „Weiterlesen“, Kacheln aus
denselben Bausteinen (53-library.js gibt seine Styles auch an #audiobook-shelf), Cover quadratisch.
63-pages.js: bei nur einem Kapitel zeigt die Fußzeile nur die Seiten.
sw CACHE v158
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:15:00 +01:00
|
|
|
|
// A double click on the text (with a mouse): full screen, the bars out of the
|
|
|
|
|
|
// way; again (or Esc): back as it was. Fingers keep their double tap; beside
|
|
|
|
|
|
// the text a click turns the page (63-pages.js), so only on the text itself.
|
|
|
|
|
|
let _readerFsHidBars = false;
|
|
|
|
|
|
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
// What openBook hangs on the content and the page number for this book, taken off again in
|
|
|
|
|
|
// closeReader — else every book opened adds its own, and a PDF's handlers run under the next
|
|
|
|
|
|
// EPUB. (Not addEventListener's signal: the tolino's Chrome 61 ignores it.)
|
|
|
|
|
|
let _readerListeners = [];
|
|
|
|
|
|
|
|
|
|
|
|
function _readerListen(el, type, fn, opts) {
|
|
|
|
|
|
if (!el) return;
|
|
|
|
|
|
el.addEventListener(type, fn, opts);
|
|
|
|
|
|
_readerListeners.push([el, type, fn, opts]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function _readerUnlistenAll() {
|
|
|
|
|
|
for (const [el, type, fn, opts] of _readerListeners) el.removeEventListener(type, fn, opts);
|
|
|
|
|
|
_readerListeners = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Nachrichten und Blogs im Buch-Reader, Doppelklick für Vollbild, Hörbücher wie die Bücher-Bibliothek
65-article.js (DioraArticle): Artikel öffnen im selben Reader wie Bücher – Schrift, Farben,
Seiten/Doppelseite mit Fußnoten am Seitenfuß, Suche, Vorlesen, Vollbild. Lesezeichen,
Markierungen und Notizspalte bleiben Büchern vorbehalten. Merken, Projekt und Original
stehen in der Kopfzeile (Apps: im Lesemenü). feeds.js gibt sein eigenes Overlay auf;
Fortschritt wie bisher (bis zum unteren Bildschirmrand gelesen, ab 90 % gelesen).
Einstellungen der Artikel gelten für alle Artikel zusammen.
62-reader.js: Doppelklick (Maus) auf den Text schaltet Vollbild an/aus, die Leisten gehen mit.
audiobooks.js: die Hörbücher sind gebaut wie die Bücher – Kopf mit Suche, Sortierung und +,
Chips Angefangen/Ungehört/Gehört und Ordner, „Weiterhören“ wie „Weiterlesen“, Kacheln aus
denselben Bausteinen (53-library.js gibt seine Styles auch an #audiobook-shelf), Cover quadratisch.
63-pages.js: bei nur einem Kapitel zeigt die Fußzeile nur die Seiten.
sw CACHE v158
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:15:00 +01:00
|
|
|
|
function _readerDblClick(e) {
|
|
|
|
|
|
if (!matchMedia('(hover: hover) and (pointer: fine)').matches) return;
|
|
|
|
|
|
if (!e.target.closest('[data-epub-src], .pdf-page-wrapper')) return;
|
|
|
|
|
|
if (e.target.closest('button, a, input, select, textarea, label, [data-footnote-ref], mark.epub-highlight')) return;
|
|
|
|
|
|
// The double click also picked a word: that's not a marking.
|
|
|
|
|
|
const sel = window.getSelection();
|
|
|
|
|
|
if (sel) sel.removeAllRanges();
|
|
|
|
|
|
if (typeof dismissHighlightPopover === 'function') dismissHighlightPopover();
|
|
|
|
|
|
if (document.fullscreenElement) {
|
|
|
|
|
|
if (document.exitFullscreen) document.exitFullscreen().catch(() => {});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const el = document.documentElement;
|
|
|
|
|
|
if (!el.requestFullscreen) return;
|
|
|
|
|
|
el.requestFullscreen().then(() => {
|
|
|
|
|
|
if (_immBarsVisible) {
|
|
|
|
|
|
_immBarsVisible = false;
|
|
|
|
|
|
document.body.classList.add('reader-immersive');
|
|
|
|
|
|
_readerFsHidBars = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}).catch(() => {});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('fullscreenchange', () => {
|
|
|
|
|
|
if (document.fullscreenElement || !_readerFsHidBars) return;
|
|
|
|
|
|
_readerFsHidBars = false;
|
|
|
|
|
|
const overlay = $('reader-overlay');
|
|
|
|
|
|
if (overlay && overlay.style.display !== 'none' && !_immBarsVisible) {
|
|
|
|
|
|
_immBarsVisible = true;
|
|
|
|
|
|
document.body.classList.remove('reader-immersive');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
|
const row = $('reader-body-row');
|
|
|
|
|
|
if (row) row.addEventListener('dblclick', _readerDblClick);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
function enterReaderImmersiveMode() {
|
|
|
|
|
|
_immBarsVisible = true;
|
|
|
|
|
|
document.body.classList.remove('reader-immersive');
|
|
|
|
|
|
document.addEventListener('click', _immHandleTap);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function exitReaderImmersiveMode() {
|
|
|
|
|
|
_immBarsVisible = true;
|
|
|
|
|
|
document.body.classList.remove('reader-immersive');
|
|
|
|
|
|
document.removeEventListener('click', _immHandleTap);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Serializes openBook: it empties #reader-content, then appends the chapters
|
|
|
|
|
|
// across several awaits. A second call in between (a second tap while a slow
|
|
|
|
|
|
// e-reader is still decrypting) appended the whole book a second time — and
|
|
|
|
|
|
// progress saved from that doubled DOM had block indices beyond the real book,
|
|
|
|
|
|
// which then out-ranked every real position on the server.
|
|
|
|
|
|
let _openBookInFlight = null;
|
2026-10-19 14:57:03 +02:00
|
|
|
|
// The opening under way: {cancelled, ctrl} — "Abbrechen", closing the reader or opening another
|
|
|
|
|
|
// book end it (docs: a big scanned PDF used to take minutes and could not be left).
|
|
|
|
|
|
let _openToken = null;
|
|
|
|
|
|
|
|
|
|
|
|
function cancelBookOpening() {
|
|
|
|
|
|
const t = _openToken;
|
|
|
|
|
|
if (!t || t.cancelled) return false;
|
|
|
|
|
|
t.cancelled = true;
|
|
|
|
|
|
try { t.ctrl.abort(); } catch (e) {}
|
|
|
|
|
|
_pdfRenderGen++;
|
|
|
|
|
|
if (typeof _pdfStopLazy === 'function') _pdfStopLazy();
|
|
|
|
|
|
if (typeof _pdfLoadingTask !== 'undefined' && _pdfLoadingTask) { try { _pdfLoadingTask.destroy(); } catch (e) {} }
|
|
|
|
|
|
document.querySelectorAll('.pdf-loading-overlay').forEach(el => el.remove());
|
|
|
|
|
|
const overlay = $('reader-overlay');
|
|
|
|
|
|
if (overlay) overlay.style.display = 'none';
|
|
|
|
|
|
const contentEl = $('reader-content');
|
|
|
|
|
|
if (contentEl) contentEl.innerHTML = '';
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
if (currentPdfDoc) { try { currentPdfDoc.destroy(); } catch (e) {} }
|
2026-10-19 14:57:03 +02:00
|
|
|
|
currentPdfDoc = null;
|
|
|
|
|
|
currentPdfBuffer = null;
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
_readerUnlistenAll();
|
2026-10-19 14:57:03 +02:00
|
|
|
|
_readPos = null;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
window.cancelBookOpening = cancelBookOpening;
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
|
|
|
|
|
|
async function openBook(bookId) {
|
|
|
|
|
|
if (_openBookInFlight) {
|
|
|
|
|
|
if (_openBookInFlight.bookId === bookId) return _openBookInFlight.promise;
|
2026-10-19 14:57:03 +02:00
|
|
|
|
cancelBookOpening(); // another book: the one still loading is left
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
await _openBookInFlight.promise.catch(() => {});
|
|
|
|
|
|
}
|
|
|
|
|
|
const promise = _openBookOnce(bookId);
|
|
|
|
|
|
_openBookInFlight = {bookId, promise};
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await promise;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
if (_openBookInFlight && _openBookInFlight.promise === promise) _openBookInFlight = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
PDF: öffnet an der gespeicherten Seite, Leserfarben, Seitenzahlen wie im Buch (sw CACHE v166)
Der Lesestand wird gleich beim Öffnen erfragt (parallel zur Datei), ein PDF zeichnet zuerst
die Seite, an der es weitergeht, statt Seite 1; Titel, Inhaltsverzeichnis und Seitenzahlen
kommen nebenher und halten das Öffnen nicht mehr auf. Eine Zeichnung, die schon läuft, wird
abgewartet statt neu begonnen.
„Leserfarben“ (neben Originalfarben und Invertieren): die Seite in den Farben des Lesers
(Dunkel, Sepia) per SVG-Filter, Bilder darüber wie gedruckt (ihre Lage aus den
Zeichenbefehlen); ein Bild, das die halbe Seite füllt, ist ein Scan und wird mit umgefärbt.
pdf.js' eigene pageColors färben die Bilder mit um. Ein anderes Design ändert nur die Farben
des Filters.
Seitenzahlen des Buchs (PageLabels: „iv“, „A-3“) im Kopf mit der Seite der Datei daneben,
eingeben lässt sich beides; Lesezeichen und der Zurück-Knopf nennen sie. Das Test-PDF hat
dafür Seitenzahlen und ein farbiges Bild bekommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:42:26 +01:00
|
|
|
|
async /**
|
|
|
|
|
|
* Book [bookId]'s entry with its place: the server's (offline: the one kept here), or an own
|
|
|
|
|
|
* newer one the server doesn't have yet. null when there's none.
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function _bookProgress(bookId) {
|
|
|
|
|
|
let bookData = null;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const progressRes = await fetch('/books/', {cache: 'no-store'});
|
|
|
|
|
|
_noteServerNowHeader(progressRes);
|
|
|
|
|
|
const allBooks = await progressRes.json();
|
|
|
|
|
|
bookData = allBooks.find(b => b.id === bookId) || null;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// offline — use cached meta
|
|
|
|
|
|
return _bookProgressCached(bookId);
|
|
|
|
|
|
}
|
|
|
|
|
|
// The newer of the server's place and an own one it doesn't have yet.
|
|
|
|
|
|
return bookData ? _withPendingProgress(bookData) : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** The same from what's kept on this device only (no waiting for the network). */
|
|
|
|
|
|
async function _bookProgressCached(bookId) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const cached = await _loadCachedBookMeta();
|
|
|
|
|
|
const bookData = cached.find(b => b.id === bookId) || null;
|
|
|
|
|
|
return bookData ? _withPendingProgress(bookData) : null;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
async function _openBookOnce(bookId) {
|
|
|
|
|
|
// Nothing is saved for this book until its place is restored.
|
|
|
|
|
|
_readPos = {bookId, ready: false, key: null, readAt: 0, moved: false, hold: false};
|
|
|
|
|
|
const overlay = $('reader-overlay');
|
|
|
|
|
|
const contentEl = $('reader-content');
|
|
|
|
|
|
const titleEl = $('reader-title');
|
|
|
|
|
|
if (!overlay || !contentEl) return;
|
|
|
|
|
|
|
|
|
|
|
|
titleEl.textContent = 'Lade …';
|
|
|
|
|
|
contentEl.innerHTML = '';
|
|
|
|
|
|
overlay.style.display = '';
|
2026-10-19 14:57:03 +02:00
|
|
|
|
const tok = {cancelled: false, ctrl: new AbortController()};
|
|
|
|
|
|
_openToken = tok;
|
|
|
|
|
|
// A moment later (not for a book that opens at once): the spinner with "Abbrechen".
|
|
|
|
|
|
const loadingEl = document.createElement('div');
|
|
|
|
|
|
loadingEl.className = 'pdf-loading-overlay';
|
|
|
|
|
|
loadingEl.innerHTML = '<div class="reader-opening"><span class="pdf-loading-spinner"></span>' +
|
|
|
|
|
|
'<button type="button" class="btn btn-sm reader-opening-cancel">Abbrechen</button></div>';
|
|
|
|
|
|
loadingEl.querySelector('button').addEventListener('click', () => cancelBookOpening());
|
|
|
|
|
|
const showLoading = setTimeout(() => { if (!tok.cancelled) overlay.appendChild(loadingEl); }, 400);
|
|
|
|
|
|
const left = () => tok.cancelled;
|
PDF: öffnet an der gespeicherten Seite, Leserfarben, Seitenzahlen wie im Buch (sw CACHE v166)
Der Lesestand wird gleich beim Öffnen erfragt (parallel zur Datei), ein PDF zeichnet zuerst
die Seite, an der es weitergeht, statt Seite 1; Titel, Inhaltsverzeichnis und Seitenzahlen
kommen nebenher und halten das Öffnen nicht mehr auf. Eine Zeichnung, die schon läuft, wird
abgewartet statt neu begonnen.
„Leserfarben“ (neben Originalfarben und Invertieren): die Seite in den Farben des Lesers
(Dunkel, Sepia) per SVG-Filter, Bilder darüber wie gedruckt (ihre Lage aus den
Zeichenbefehlen); ein Bild, das die halbe Seite füllt, ist ein Scan und wird mit umgefärbt.
pdf.js' eigene pageColors färben die Bilder mit um. Ein anderes Design ändert nur die Farben
des Filters.
Seitenzahlen des Buchs (PageLabels: „iv“, „A-3“) im Kopf mit der Seite der Datei daneben,
eingeben lässt sich beides; Lesezeichen und der Zurück-Knopf nennen sie. Das Test-PDF hat
dafür Seitenzahlen und ein farbiges Bild bekommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:42:26 +01:00
|
|
|
|
// The place this book was read at: asked for at once, alongside the book's file (a PDF
|
|
|
|
|
|
// opens at that page instead of drawing page 1 first).
|
|
|
|
|
|
const progressP = _bookProgress(bookId).catch(() => null);
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
loadReaderSettings(bookId);
|
|
|
|
|
|
_charIndex = null;
|
|
|
|
|
|
const key = await getOrCreateEncKey();
|
|
|
|
|
|
let data_ct, data_iv;
|
|
|
|
|
|
const cached = await _getCachedBook(bookId);
|
2026-10-19 14:57:03 +02:00
|
|
|
|
if (left()) return;
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
if (cached) {
|
|
|
|
|
|
({data_ct, data_iv} = cached);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
let res;
|
|
|
|
|
|
try {
|
2026-10-19 14:57:03 +02:00
|
|
|
|
res = await fetch(`/books/${bookId}/data/`, {signal: tok.ctrl.signal});
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
} catch {
|
2026-10-19 14:57:03 +02:00
|
|
|
|
if (left()) return;
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
// offline and book not cached yet
|
|
|
|
|
|
overlay.style.display = 'none';
|
|
|
|
|
|
const toast = document.createElement('div');
|
|
|
|
|
|
toast.className = 'reader-toast';
|
|
|
|
|
|
toast.textContent = 'Buch nicht offline verfügbar — einmal online öffnen zum Cachen.';
|
|
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
|
|
setTimeout(() => toast.remove(), 3500);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
({data_ct, data_iv} = await res.json());
|
2026-10-19 14:57:03 +02:00
|
|
|
|
if (left()) return;
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
_setCachedBook(bookId, data_ct, data_iv); // fire-and-forget
|
|
|
|
|
|
}
|
|
|
|
|
|
const plain = await decryptBytes(key, data_iv, data_ct);
|
2026-10-19 14:57:03 +02:00
|
|
|
|
if (left()) return;
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
|
|
|
|
|
|
// Revoke any previous image blob URLs
|
|
|
|
|
|
for (const url of Object.values(currentImageMap)) URL.revokeObjectURL(url);
|
|
|
|
|
|
currentImageMap = {};
|
|
|
|
|
|
|
|
|
|
|
|
const cachedMeta = bookMetaCache[bookId] || {};
|
|
|
|
|
|
let title = cachedMeta.title || '';
|
|
|
|
|
|
let author = cachedMeta.author || '';
|
|
|
|
|
|
let toc = [];
|
|
|
|
|
|
let numPages = 0;
|
|
|
|
|
|
const isPdfBook = cachedMeta.type === 'pdf';
|
|
|
|
|
|
|
|
|
|
|
|
if (isPdfBook) {
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
// A document still open (another book) goes; renderPdf loads this one.
|
|
|
|
|
|
if (currentPdfDoc) { try { currentPdfDoc.destroy(); } catch (e) {} }
|
|
|
|
|
|
currentPdfDoc = null;
|
PDF: öffnet an der gespeicherten Seite, Leserfarben, Seitenzahlen wie im Buch (sw CACHE v166)
Der Lesestand wird gleich beim Öffnen erfragt (parallel zur Datei), ein PDF zeichnet zuerst
die Seite, an der es weitergeht, statt Seite 1; Titel, Inhaltsverzeichnis und Seitenzahlen
kommen nebenher und halten das Öffnen nicht mehr auf. Eine Zeichnung, die schon läuft, wird
abgewartet statt neu begonnen.
„Leserfarben“ (neben Originalfarben und Invertieren): die Seite in den Farben des Lesers
(Dunkel, Sepia) per SVG-Filter, Bilder darüber wie gedruckt (ihre Lage aus den
Zeichenbefehlen); ein Bild, das die halbe Seite füllt, ist ein Scan und wird mit umgefärbt.
pdf.js' eigene pageColors färben die Bilder mit um. Ein anderes Design ändert nur die Farben
des Filters.
Seitenzahlen des Buchs (PageLabels: „iv“, „A-3“) im Kopf mit der Seite der Datei daneben,
eingeben lässt sich beides; Lesezeichen und der Zurück-Knopf nennen sie. Das Test-PDF hat
dafür Seitenzahlen und ein farbiges Bild bekommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:42:26 +01:00
|
|
|
|
// The colours of the reader first: the pages may be drawn in them (Leserfarben).
|
|
|
|
|
|
applyReaderSettings(true);
|
|
|
|
|
|
// The page it opens at: the place saved, if it's known by now (else the one kept here).
|
|
|
|
|
|
const saved = await Promise.race([progressP, new Promise(r => setTimeout(() => r(undefined), 400))])
|
|
|
|
|
|
|| await _bookProgressCached(bookId);
|
|
|
|
|
|
const startFraction = saved ? (saved.scroll_fraction || 0) : 0;
|
|
|
|
|
|
const result = await renderPdf(plain, contentEl, undefined,
|
|
|
|
|
|
n => (startFraction > 0 && n > 1 ? Math.max(1, Math.min(n, Math.round(startFraction * (n - 1)) + 1)) : 1));
|
2026-10-19 14:57:03 +02:00
|
|
|
|
if (left() || !result) return;
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
title = result.title || title;
|
|
|
|
|
|
author = result.author || author;
|
|
|
|
|
|
toc = result.toc;
|
|
|
|
|
|
numPages = result.numPages;
|
PDF: öffnet an der gespeicherten Seite, Leserfarben, Seitenzahlen wie im Buch (sw CACHE v166)
Der Lesestand wird gleich beim Öffnen erfragt (parallel zur Datei), ein PDF zeichnet zuerst
die Seite, an der es weitergeht, statt Seite 1; Titel, Inhaltsverzeichnis und Seitenzahlen
kommen nebenher und halten das Öffnen nicht mehr auf. Eine Zeichnung, die schon läuft, wird
abgewartet statt neu begonnen.
„Leserfarben“ (neben Originalfarben und Invertieren): die Seite in den Farben des Lesers
(Dunkel, Sepia) per SVG-Filter, Bilder darüber wie gedruckt (ihre Lage aus den
Zeichenbefehlen); ein Bild, das die halbe Seite füllt, ist ein Scan und wird mit umgefärbt.
pdf.js' eigene pageColors färben die Bilder mit um. Ein anderes Design ändert nur die Farben
des Filters.
Seitenzahlen des Buchs (PageLabels: „iv“, „A-3“) im Kopf mit der Seite der Datei daneben,
eingeben lässt sich beides; Lesezeichen und der Zurück-Knopf nennen sie. Das Test-PDF hat
dafür Seitenzahlen und ein farbiges Bild bekommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:42:26 +01:00
|
|
|
|
// The table of contents follows when its pages are resolved.
|
|
|
|
|
|
result.tocLater.then(entries => {
|
|
|
|
|
|
if (currentBookId === bookId && currentPdfDoc && entries.length) currentBookToc = entries;
|
|
|
|
|
|
});
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
// pdf.js holds the document: the decrypted bytes needn't stay as well.
|
|
|
|
|
|
currentPdfBuffer = null;
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
} else {
|
|
|
|
|
|
currentPdfBuffer = null;
|
|
|
|
|
|
// Prepared before (same book file, same preparation code): only the images to unpack.
|
|
|
|
|
|
const prepared = await _getParsedBook(bookId, data_iv, key);
|
|
|
|
|
|
const result = prepared ? await _restoreParsedBook(prepared, plain) : await parseEpub(plain);
|
2026-10-19 14:57:03 +02:00
|
|
|
|
if (left()) return;
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
if (!prepared) {
|
|
|
|
|
|
// Kept for the next open once the book is on screen (encrypting takes a moment).
|
|
|
|
|
|
setTimeout(() => _setParsedBook(bookId, data_iv, key, result), 3000);
|
|
|
|
|
|
}
|
|
|
|
|
|
title = result.title || title;
|
|
|
|
|
|
author = result.author || author;
|
|
|
|
|
|
toc = result.toc;
|
|
|
|
|
|
currentImageMap = result.imageMap;
|
|
|
|
|
|
// Inject chapters progressively so the browser stays responsive on large books:
|
|
|
|
|
|
// a batch per ~50 ms of work (a fixed count waited a frame every 8 chapters).
|
|
|
|
|
|
const tmp = document.createElement('div');
|
|
|
|
|
|
for (let i = 0; i < result.chapters.length;) {
|
|
|
|
|
|
const frag = document.createDocumentFragment();
|
|
|
|
|
|
const start = performance.now();
|
|
|
|
|
|
do {
|
|
|
|
|
|
tmp.innerHTML = result.chapters[i++];
|
|
|
|
|
|
while (tmp.firstChild) frag.appendChild(tmp.firstChild);
|
|
|
|
|
|
} while (i < result.chapters.length && performance.now() - start < 50);
|
|
|
|
|
|
contentEl.appendChild(frag);
|
|
|
|
|
|
if (i < result.chapters.length) await new Promise(r => setTimeout(r, 0));
|
2026-10-19 14:57:03 +02:00
|
|
|
|
if (left()) return;
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
}
|
2026-10-19 14:57:03 +02:00
|
|
|
|
// On screen: no more need to offer "Abbrechen".
|
|
|
|
|
|
clearTimeout(showLoading);
|
|
|
|
|
|
loadingEl.remove();
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
// Used to decide whole-percent vs one-decimal display further down —
|
|
|
|
|
|
// see _formatBookPercent().
|
|
|
|
|
|
_currentBookCharCount = contentEl.textContent.length;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-19 14:57:03 +02:00
|
|
|
|
clearTimeout(showLoading);
|
|
|
|
|
|
loadingEl.remove();
|
|
|
|
|
|
if (_openToken === tok) _openToken = null;
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
currentBookToc = toc;
|
|
|
|
|
|
titleEl.textContent = title + (author ? ` — ${author}` : '');
|
|
|
|
|
|
|
|
|
|
|
|
currentBookId = bookId;
|
|
|
|
|
|
|
|
|
|
|
|
// Remember the book's real (PDF) or estimated (EPUB) page count in its encrypted meta so
|
|
|
|
|
|
// the book list can show it; rewritten only when it changed.
|
|
|
|
|
|
const bookPages = isPdfBook ? numPages : Math.max(1, Math.round(_currentBookCharCount / CHARS_PER_BOOK_PAGE));
|
|
|
|
|
|
if (bookPages > 0 && cachedMeta.pages !== bookPages) {
|
|
|
|
|
|
_updateBookMeta(bookId, {pages: bookPages}).catch(() => {});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Load bookmarks and highlights
|
|
|
|
|
|
await Promise.all([
|
|
|
|
|
|
loadBookmarks(bookId),
|
|
|
|
|
|
loadHighlights(bookId),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// Apply reader settings (theme, font size, etc.)
|
|
|
|
|
|
applyReaderSettings(isPdfBook);
|
|
|
|
|
|
|
|
|
|
|
|
// If this book already has notes/highlights, surface them right away
|
|
|
|
|
|
// instead of making the reader hide them behind a manual toggle.
|
|
|
|
|
|
// Not on phones: there the margin covers the text; it stays one tap away (✎).
|
|
|
|
|
|
if (!isPdfBook && currentHighlights.length && !readerAnnotationsMarginOpen && !_isMobileReader()) {
|
|
|
|
|
|
toggleAnnotationsMargin();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Touch: swipe (paginated) + pinch-to-zoom
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
_readerListen(contentEl, 'touchstart', _pdfTouchStart, {passive: true});
|
|
|
|
|
|
_readerListen(contentEl, 'touchmove', _pdfTouchMove, {passive: false});
|
|
|
|
|
|
_readerListen(contentEl, 'touchend', _pdfTouchEnd, {passive: true});
|
|
|
|
|
|
_readerListen(contentEl, 'touchcancel', _pdfTouchEnd, {passive: true});
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
|
|
|
|
|
|
// Set up progress input
|
|
|
|
|
|
const progressInput = $('reader-progress-input');
|
|
|
|
|
|
const progressSuffix = $('reader-progress-suffix');
|
|
|
|
|
|
const isPdf = isPdfBook;
|
|
|
|
|
|
|
|
|
|
|
|
if (progressInput) {
|
|
|
|
|
|
progressInput.style.display = '';
|
|
|
|
|
|
if (isPdf) {
|
|
|
|
|
|
progressInput.value = 1;
|
|
|
|
|
|
if (progressSuffix) progressSuffix.textContent = `/ ${numPages}`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
progressInput.value = _formatBookPercent(0);
|
|
|
|
|
|
if (progressSuffix) progressSuffix.textContent = '%';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
_readerListen(progressInput, 'change', function () {
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
_suppressScrollJumpDetect();
|
|
|
|
|
|
if (isPdf) {
|
PDF: öffnet an der gespeicherten Seite, Leserfarben, Seitenzahlen wie im Buch (sw CACHE v166)
Der Lesestand wird gleich beim Öffnen erfragt (parallel zur Datei), ein PDF zeichnet zuerst
die Seite, an der es weitergeht, statt Seite 1; Titel, Inhaltsverzeichnis und Seitenzahlen
kommen nebenher und halten das Öffnen nicht mehr auf. Eine Zeichnung, die schon läuft, wird
abgewartet statt neu begonnen.
„Leserfarben“ (neben Originalfarben und Invertieren): die Seite in den Farben des Lesers
(Dunkel, Sepia) per SVG-Filter, Bilder darüber wie gedruckt (ihre Lage aus den
Zeichenbefehlen); ein Bild, das die halbe Seite füllt, ist ein Scan und wird mit umgefärbt.
pdf.js' eigene pageColors färben die Bilder mit um. Ein anderes Design ändert nur die Farben
des Filters.
Seitenzahlen des Buchs (PageLabels: „iv“, „A-3“) im Kopf mit der Seite der Datei daneben,
eingeben lässt sich beides; Lesezeichen und der Zurück-Knopf nennen sie. Das Test-PDF hat
dafür Seitenzahlen und ein farbiges Bild bekommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:42:26 +01:00
|
|
|
|
// The book's page number ("iv", "12") or the file's.
|
|
|
|
|
|
const page = Math.min(numPages, Math.max(1, _pdfPageFromInput(this.value) || pdfCurrentPage || 1));
|
|
|
|
|
|
this.value = pdfPageLabel(page);
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
pdfGoToPage(page);
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
} else {
|
|
|
|
|
|
const pct = Math.min(100, Math.max(0, _parseLocalePercent(this.value)));
|
|
|
|
|
|
this.value = _formatBookPercent(pct / 100);
|
|
|
|
|
|
restoreFromAnchor(contentEl, _anchorForFraction(contentEl, pct / 100));
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
_readerListen(progressInput, 'click', function () { this.select(); });
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Restore scroll position — must happen BEFORE auto-save timer is started
|
|
|
|
|
|
_suppressScrollJumpDetect();
|
|
|
|
|
|
let openedProgress = null;
|
|
|
|
|
|
try {
|
|
|
|
|
|
let fraction = 0, anchor = '';
|
PDF: öffnet an der gespeicherten Seite, Leserfarben, Seitenzahlen wie im Buch (sw CACHE v166)
Der Lesestand wird gleich beim Öffnen erfragt (parallel zur Datei), ein PDF zeichnet zuerst
die Seite, an der es weitergeht, statt Seite 1; Titel, Inhaltsverzeichnis und Seitenzahlen
kommen nebenher und halten das Öffnen nicht mehr auf. Eine Zeichnung, die schon läuft, wird
abgewartet statt neu begonnen.
„Leserfarben“ (neben Originalfarben und Invertieren): die Seite in den Farben des Lesers
(Dunkel, Sepia) per SVG-Filter, Bilder darüber wie gedruckt (ihre Lage aus den
Zeichenbefehlen); ein Bild, das die halbe Seite füllt, ist ein Scan und wird mit umgefärbt.
pdf.js' eigene pageColors färben die Bilder mit um. Ein anderes Design ändert nur die Farben
des Filters.
Seitenzahlen des Buchs (PageLabels: „iv“, „A-3“) im Kopf mit der Seite der Datei daneben,
eingeben lässt sich beides; Lesezeichen und der Zurück-Knopf nennen sie. Das Test-PDF hat
dafür Seitenzahlen und ein farbiges Bild bekommen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:42:26 +01:00
|
|
|
|
const bookData = await progressP;
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
fraction = bookData ? (bookData.scroll_fraction || 0) : 0;
|
|
|
|
|
|
anchor = bookData ? (bookData.position_anchor || '') : '';
|
|
|
|
|
|
openedProgress = bookData;
|
|
|
|
|
|
_currentPositionAnchor = anchor;
|
|
|
|
|
|
if (isPdf && readerSettings.pdfPaginated) {
|
|
|
|
|
|
if (fraction > 0 && pdfTotalPages > 1)
|
|
|
|
|
|
pdfCurrentPage = Math.max(1, Math.round(fraction * (pdfTotalPages - 1)) + 1);
|
|
|
|
|
|
enterPdfPaginatedMode();
|
|
|
|
|
|
} else if (isPdf) {
|
|
|
|
|
|
// PDF scroll mode
|
|
|
|
|
|
await new Promise(r => requestAnimationFrame(r));
|
|
|
|
|
|
if (fraction > 0)
|
|
|
|
|
|
contentEl.scrollTop = fraction * (contentEl.scrollHeight - contentEl.clientHeight);
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
_pdfSetCurrentPage(_pdfTopPage(contentEl));
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
} else {
|
|
|
|
|
|
// EPUB — wait for images so scrollHeight is final
|
|
|
|
|
|
const imgs = Array.from(contentEl.querySelectorAll('img'));
|
|
|
|
|
|
if (imgs.length) {
|
|
|
|
|
|
await Promise.all(imgs.map(img =>
|
|
|
|
|
|
img.complete ? Promise.resolve()
|
|
|
|
|
|
: new Promise(r => { img.onload = r; img.onerror = r; })
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
await new Promise(r => requestAnimationFrame(r));
|
|
|
|
|
|
if (!restoreFromAnchor(contentEl, anchor) && fraction > 0) {
|
|
|
|
|
|
restoreFromAnchor(contentEl, _anchorForFraction(contentEl, fraction));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
|
|
|
|
|
|
|
// From here on, what the reader does is reading. The place shown has the
|
|
|
|
|
|
// time it was read at (elsewhere, or here earlier): nothing new to send yet.
|
|
|
|
|
|
if (_readPos && _readPos.bookId === bookId && currentBookId === bookId) {
|
|
|
|
|
|
const shownAnchor = isPdf ? '' : getPositionAnchor(contentEl);
|
|
|
|
|
|
const readAtServer = openedProgress && openedProgress.read_at != null ? openedProgress.read_at : null;
|
|
|
|
|
|
_readPos.key = _positionKey(shownAnchor, openedProgress ? (openedProgress.scroll_fraction || 0) : 0);
|
|
|
|
|
|
_readPos.readAt = readAtServer == null ? 0 : readAtServer - _serverOffset;
|
|
|
|
|
|
_readPos.ready = true;
|
|
|
|
|
|
_offerFurthestPosition(openedProgress);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Prime the "settled" baseline to wherever we just restored to, rather
|
|
|
|
|
|
// than waiting to lazily pick up whatever scroll event happens to fire
|
|
|
|
|
|
// first — that could otherwise be a later, unrelated layout correction.
|
|
|
|
|
|
_scrollRestTop = contentEl.scrollTop;
|
|
|
|
|
|
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
// PDF scrolling: the page shown at the top is the current one (header, bookmarks, the
|
|
|
|
|
|
// app's footer). EPUB: shown from the position anchor, see the anchor tracking below.
|
|
|
|
|
|
if (isPdf) {
|
|
|
|
|
|
let pageRaf = 0;
|
|
|
|
|
|
_readerListen(contentEl, 'scroll', () => {
|
|
|
|
|
|
if (pageRaf || readerSettings.pdfPaginated) return;
|
|
|
|
|
|
pageRaf = requestAnimationFrame(() => {
|
|
|
|
|
|
pageRaf = 0;
|
|
|
|
|
|
if (currentPdfDoc) _pdfSetCurrentPage(_pdfTopPage(contentEl));
|
|
|
|
|
|
});
|
|
|
|
|
|
}, {passive: true});
|
|
|
|
|
|
}
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
|
|
|
|
|
|
// Auto-save progress every 10s and on scroll (debounced 2s)
|
|
|
|
|
|
// Started AFTER restore so an early visibilitychange can't overwrite with position 0
|
|
|
|
|
|
readerScrollSaveTimer = setInterval(saveReaderProgress, 10000);
|
|
|
|
|
|
let _scrollDebounce = null;
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
_readerListen(contentEl, 'scroll', () => {
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
clearTimeout(_scrollDebounce);
|
|
|
|
|
|
_scrollDebounce = setTimeout(saveReaderProgress, 2000);
|
|
|
|
|
|
}, {passive: true});
|
|
|
|
|
|
|
|
|
|
|
|
// Keep _currentPositionAnchor fresh independent of the slower save debounce
|
|
|
|
|
|
// above: the resize observer below (or an immersive-bars toggle right after
|
|
|
|
|
|
// scrolling) would otherwise restore to a stale, pre-scroll anchor.
|
|
|
|
|
|
if (!isPdf) {
|
|
|
|
|
|
let _anchorTrackDebounce = null;
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
_readerListen(contentEl, 'scroll', () => {
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
clearTimeout(_anchorTrackDebounce);
|
|
|
|
|
|
_anchorTrackDebounce = setTimeout(() => {
|
|
|
|
|
|
_currentPositionAnchor = getPositionAnchor(contentEl);
|
|
|
|
|
|
const f = _fractionFromAnchor(contentEl, _currentPositionAnchor);
|
|
|
|
|
|
_noteReadingPosition(_currentPositionAnchor, f === null ? 0 : f);
|
|
|
|
|
|
if (progressInput && f !== null && document.activeElement !== progressInput) {
|
|
|
|
|
|
progressInput.value = _formatBookPercent(f);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 150);
|
|
|
|
|
|
}, {passive: true});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Restore anchor on viewport resize (e.g. screen rotation, font zoom)
|
|
|
|
|
|
if (!isPdf) {
|
|
|
|
|
|
_resizeObserver = new ResizeObserver(() => {
|
|
|
|
|
|
if (_currentPositionAnchor) {
|
|
|
|
|
|
_suppressScrollJumpDetect();
|
|
|
|
|
|
requestAnimationFrame(() => restoreFromAnchor(contentEl, _currentPositionAnchor));
|
|
|
|
|
|
}
|
|
|
|
|
|
_repositionMarginMarkers();
|
|
|
|
|
|
});
|
|
|
|
|
|
_resizeObserver.observe(contentEl);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Offer to jump back after a sudden, large scroll (accidental fling,
|
|
|
|
|
|
// fat-fingered 'g'/'G' keybind, etc.) — see _checkScrollJump above.
|
|
|
|
|
|
_scrollRestTop = null;
|
|
|
|
|
|
let _scrollJumpRaf = null;
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
_readerListen(contentEl, 'scroll', () => {
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
if (_scrollJumpRaf) return;
|
|
|
|
|
|
_scrollJumpRaf = requestAnimationFrame(() => {
|
|
|
|
|
|
_scrollJumpRaf = null;
|
|
|
|
|
|
_checkScrollJump(contentEl);
|
|
|
|
|
|
_repositionMarginMarkers();
|
|
|
|
|
|
});
|
|
|
|
|
|
}, {passive: true});
|
|
|
|
|
|
|
|
|
|
|
|
enterReaderImmersiveMode();
|
|
|
|
|
|
|
Zeitschriften: das Heft und seine Artikel
Das Heft bleibt das Dokument, der Artikel die Einheit, in der man sich
bewegt (docs/library.md):
- Artikel-Index auf dem Gerät aus dem Inhaltsverzeichnis des EPUB bzw. den
Lesezeichen des PDF (Rubriken, Anreißer, Lesezeit), verschlüsselt im Meta.
- Stand pro Artikel (gelesen / übersprungen, wo man war), verschlüsselt im
Meta, offline gepuffert.
- Bibliothek: Segmentbalken pro Heft, „7 von 23 gelesen“; ein Tipp öffnet den
Heft-Inhalt mit Rubriken, Stand je Artikel, „Im Heft weiterlesen“, „Heft
abschließen“.
- Reader: Kopfzeile „Artikel 8 von 23“, am Ende jedes Artikels eine Zäsur
(gelesen, Weiter im Heft, Zum Inhalt, überspringen); bei PDF eine Leiste
beim Weiterblättern. Durchgelesene Artikel zählen als gelesen, Sprünge nicht.
sw.js: diora-v127.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:14:28 +02:00
|
|
|
|
// An issue of a magazine: its articles (54-issues.js).
|
|
|
|
|
|
if (typeof _issueReaderOpened === 'function') _issueReaderOpened(bookId, isPdf);
|
|
|
|
|
|
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
} catch (e) {
|
2026-10-19 14:57:03 +02:00
|
|
|
|
if (left()) return; // cancelled: the half-loaded document may complain, that's all
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
console.error('openBook failed:', e);
|
|
|
|
|
|
overlay.style.display = 'none';
|
|
|
|
|
|
markBookBroken(bookId);
|
2026-10-19 14:57:03 +02:00
|
|
|
|
} finally {
|
|
|
|
|
|
clearTimeout(showLoading);
|
|
|
|
|
|
loadingEl.remove();
|
|
|
|
|
|
if (_openToken === tok) _openToken = null;
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function exportEncKey() {
|
|
|
|
|
|
const statusEl = $('book-key-status');
|
|
|
|
|
|
try {
|
|
|
|
|
|
const key = await getOrCreateEncKey();
|
|
|
|
|
|
const raw = await crypto.subtle.exportKey('raw', key);
|
|
|
|
|
|
const b64 = bytesToBase64(raw);
|
|
|
|
|
|
await navigator.clipboard.writeText(b64);
|
|
|
|
|
|
if (statusEl) statusEl.textContent = '✓ Schlüssel in die Zwischenablage kopiert';
|
|
|
|
|
|
setTimeout(() => { if (statusEl) statusEl.textContent = ''; }, 3000);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
if (statusEl) statusEl.textContent = 'Export fehlgeschlagen: ' + e.message;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function showImportKey() {
|
|
|
|
|
|
const body = $('sidebar-body');
|
|
|
|
|
|
openSidebar('Schlüssel importieren', `
|
|
|
|
|
|
<p class="muted">Füge den Schlüssel ein, den du im anderen Browser exportiert hast:</p>
|
|
|
|
|
|
<textarea id="import-key-input" class="search-input" rows="3" style="width:100%;resize:none;font-family:monospace;font-size:0.75rem;"></textarea>
|
|
|
|
|
|
<button class="btn" style="margin-top:8px;" data-import-key-apply>Übernehmen</button>
|
|
|
|
|
|
<p class="muted" style="margin-top:8px;">Das ersetzt den Schlüssel in diesem Browser. Hier hochgeladene Bücher sind erst wieder lesbar, wenn du den Schlüssel zurück abgleichst.</p>
|
|
|
|
|
|
`);
|
|
|
|
|
|
body.addEventListener('click', async function _importClick(e) {
|
|
|
|
|
|
if (!e.target.closest('[data-import-key-apply]')) return;
|
|
|
|
|
|
body.removeEventListener('click', _importClick);
|
|
|
|
|
|
const b64 = (body.querySelector('#import-key-input')?.value || '').trim();
|
|
|
|
|
|
const statusEl = $('book-key-status');
|
|
|
|
|
|
try {
|
|
|
|
|
|
const raw = base64ToBytes(b64);
|
|
|
|
|
|
const importedKey = await crypto.subtle.importKey('raw', raw, {name: 'AES-GCM', length: 256}, true, ['encrypt', 'decrypt']);
|
|
|
|
|
|
const re_exported = await crypto.subtle.exportKey('raw', importedKey);
|
|
|
|
|
|
localStorage.setItem(`diora_enc_key_${window.USER_ID}`, bytesToBase64(re_exported));
|
|
|
|
|
|
closeSidebar();
|
|
|
|
|
|
if (statusEl) statusEl.textContent = '✓ Schlüssel importiert – Bücher werden neu geladen …';
|
|
|
|
|
|
await loadBookList();
|
|
|
|
|
|
if (statusEl) setTimeout(() => { statusEl.textContent = ''; }, 3000);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
if ($('book-key-status')) $('book-key-status').textContent = 'Import fehlgeschlagen: ungültiger Schlüssel';
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function deleteBook(bookId) {
|
|
|
|
|
|
if (!await customConfirm('Dieses Buch löschen? Das lässt sich nicht rückgängig machen.', { danger: true })) return;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch(`/books/${bookId}/delete/`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {'X-CSRFToken': getCsrfToken()},
|
|
|
|
|
|
});
|
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
if (data.ok) loadBookList();
|
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Opened somewhere before the furthest place reached: offer it.
|
|
|
|
|
|
function _offerFurthestPosition(p) {
|
|
|
|
|
|
if (!p) return;
|
|
|
|
|
|
const furthest = p.furthest_fraction || 0;
|
|
|
|
|
|
if (furthest - (p.scroll_fraction || 0) < 0.02 || furthest >= 0.999) return;
|
|
|
|
|
|
const pos = _readPos;
|
|
|
|
|
|
_readerToast(`Weiteste Stelle: ${_formatBookPercent(furthest)} %`, 'Dorthin', () => {
|
|
|
|
|
|
if (_readPos !== pos) return;
|
|
|
|
|
|
_noteReaderInteraction();
|
|
|
|
|
|
_jumpToPosition(p.furthest_anchor || '', furthest);
|
|
|
|
|
|
}, 8000);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function saveReaderProgress(force = false) {
|
|
|
|
|
|
if (!currentBookId) return;
|
|
|
|
|
|
const contentEl = $('reader-content');
|
|
|
|
|
|
if (!contentEl) return;
|
|
|
|
|
|
|
|
|
|
|
|
let fraction;
|
|
|
|
|
|
if (readerSettings.pdfPaginated && currentPdfDoc && pdfTotalPages > 1) {
|
|
|
|
|
|
fraction = (pdfCurrentPage - 1) / (pdfTotalPages - 1);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
fraction = contentEl.scrollTop / (contentEl.scrollHeight - contentEl.clientHeight || 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Chapter wrappers carry unique ids; seeing one twice means the book was
|
|
|
|
|
|
// rendered twice. Positions from that DOM are wrong — don't store them.
|
|
|
|
|
|
if (!currentPdfDoc && contentEl.querySelectorAll('#epub-chapter-0').length > 1) {
|
|
|
|
|
|
console.warn('Book rendered twice, progress not saved');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Not restored yet (or another book): what's shown isn't a place read.
|
|
|
|
|
|
const pos = _readPos;
|
|
|
|
|
|
if (!pos || !pos.ready || pos.bookId !== currentBookId) return;
|
|
|
|
|
|
// A jump that's only a look: the place before it stays.
|
|
|
|
|
|
if (pos.hold && !force) return;
|
|
|
|
|
|
|
|
|
|
|
|
let anchor = '';
|
|
|
|
|
|
if (!currentPdfDoc) {
|
|
|
|
|
|
anchor = getPositionAnchor(contentEl);
|
|
|
|
|
|
_currentPositionAnchor = anchor;
|
|
|
|
|
|
// EPUB: share of the text, the same on every device and layout.
|
|
|
|
|
|
const textFraction = _fractionFromAnchor(contentEl, anchor);
|
|
|
|
|
|
if (textFraction !== null) fraction = textFraction;
|
|
|
|
|
|
}
|
|
|
|
|
|
fraction = Math.min(1.0, Math.max(0.0, fraction));
|
|
|
|
|
|
|
|
|
|
|
|
_noteReadingPosition(anchor, fraction);
|
|
|
|
|
|
if (force) {
|
|
|
|
|
|
pos.readAt = Date.now();
|
|
|
|
|
|
pos.moved = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Nothing read here since opening: the server has this place already.
|
|
|
|
|
|
if (!pos.moved) return;
|
|
|
|
|
|
|
|
|
|
|
|
const entry = {book: currentBookId, fraction, anchor, read_at: pos.readAt, device: _progressDevice(), ...(force && {force: true})};
|
|
|
|
|
|
const sig = `${entry.book}|${anchor}|${fraction.toFixed(5)}|${entry.read_at}`;
|
|
|
|
|
|
_lastProgressBeacon = entry;
|
|
|
|
|
|
if (!force && sig === pos.sentSig) return; // unchanged since the last save
|
|
|
|
|
|
pos.sentSig = sig;
|
|
|
|
|
|
_progressOutboxPut(entry);
|
|
|
|
|
|
_updateCachedBookProgress(currentBookId, fraction, anchor, force, pos.readAt);
|
|
|
|
|
|
const sent = await _sendProgress(entry);
|
|
|
|
|
|
if (!sent) pos.sentSig = null; // try again with the next save
|
|
|
|
|
|
if (sent && force) {
|
|
|
|
|
|
const toast = document.createElement('div');
|
|
|
|
|
|
toast.className = 'reader-toast';
|
|
|
|
|
|
toast.textContent = 'Position gesetzt';
|
|
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
|
|
setTimeout(() => toast.remove(), 1800);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeReader() {
|
2026-10-19 14:57:03 +02:00
|
|
|
|
// Still opening: just leave it (nothing to save yet).
|
|
|
|
|
|
if (cancelBookOpening()) return;
|
Zeitschriften: das Heft und seine Artikel
Das Heft bleibt das Dokument, der Artikel die Einheit, in der man sich
bewegt (docs/library.md):
- Artikel-Index auf dem Gerät aus dem Inhaltsverzeichnis des EPUB bzw. den
Lesezeichen des PDF (Rubriken, Anreißer, Lesezeit), verschlüsselt im Meta.
- Stand pro Artikel (gelesen / übersprungen, wo man war), verschlüsselt im
Meta, offline gepuffert.
- Bibliothek: Segmentbalken pro Heft, „7 von 23 gelesen“; ein Tipp öffnet den
Heft-Inhalt mit Rubriken, Stand je Artikel, „Im Heft weiterlesen“, „Heft
abschließen“.
- Reader: Kopfzeile „Artikel 8 von 23“, am Ende jedes Artikels eine Zäsur
(gelesen, Weiter im Heft, Zum Inhalt, überspringen); bei PDF eine Leiste
beim Weiterblättern. Durchgelesene Artikel zählen als gelesen, Sprünge nicht.
sw.js: diora-v127.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:14:28 +02:00
|
|
|
|
if (typeof _issueReaderClosed === 'function') _issueReaderClosed();
|
Nachrichten und Blogs im Buch-Reader, Doppelklick für Vollbild, Hörbücher wie die Bücher-Bibliothek
65-article.js (DioraArticle): Artikel öffnen im selben Reader wie Bücher – Schrift, Farben,
Seiten/Doppelseite mit Fußnoten am Seitenfuß, Suche, Vorlesen, Vollbild. Lesezeichen,
Markierungen und Notizspalte bleiben Büchern vorbehalten. Merken, Projekt und Original
stehen in der Kopfzeile (Apps: im Lesemenü). feeds.js gibt sein eigenes Overlay auf;
Fortschritt wie bisher (bis zum unteren Bildschirmrand gelesen, ab 90 % gelesen).
Einstellungen der Artikel gelten für alle Artikel zusammen.
62-reader.js: Doppelklick (Maus) auf den Text schaltet Vollbild an/aus, die Leisten gehen mit.
audiobooks.js: die Hörbücher sind gebaut wie die Bücher – Kopf mit Suche, Sortierung und +,
Chips Angefangen/Ungehört/Gehört und Ordner, „Weiterhören“ wie „Weiterlesen“, Kacheln aus
denselben Bausteinen (53-library.js gibt seine Styles auch an #audiobook-shelf), Cover quadratisch.
63-pages.js: bei nur einem Kapitel zeigt die Fußzeile nur die Seiten.
sw CACHE v158
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:15:00 +01:00
|
|
|
|
// An article (65-article.js): where it was read to, before the reader empties.
|
|
|
|
|
|
if (typeof _articleReaderClosed === 'function') _articleReaderClosed();
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
exitReaderImmersiveMode();
|
|
|
|
|
|
stopReadAloud();
|
|
|
|
|
|
// Save progress BEFORE hiding — scrollHeight/clientHeight return 0 once display:none
|
|
|
|
|
|
saveReaderProgress();
|
|
|
|
|
|
_readPos = null; // nothing more is saved for this book
|
|
|
|
|
|
if (bookmarksDirty) saveBookmarks();
|
|
|
|
|
|
if (highlightsDirty) saveHighlights();
|
|
|
|
|
|
|
|
|
|
|
|
const overlay = $('reader-overlay');
|
|
|
|
|
|
if (overlay) overlay.style.display = 'none';
|
|
|
|
|
|
if (readerScrollSaveTimer) {
|
|
|
|
|
|
clearInterval(readerScrollSaveTimer);
|
|
|
|
|
|
readerScrollSaveTimer = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_resizeObserver) {
|
|
|
|
|
|
_resizeObserver.disconnect();
|
|
|
|
|
|
_resizeObserver = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
_currentPositionAnchor = '';
|
|
|
|
|
|
|
|
|
|
|
|
// Close the margin panel
|
|
|
|
|
|
readerAnnotationsMarginOpen = false;
|
|
|
|
|
|
const marginEl = $('reader-margin');
|
|
|
|
|
|
if (marginEl) marginEl.classList.remove('open');
|
2026-10-14 19:43:03 +02:00
|
|
|
|
_centerReaderColumn();
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
const marginMarkersEl = $('reader-margin-markers');
|
|
|
|
|
|
if (marginMarkersEl) marginMarkersEl.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
|
|
// Disarm marker mode
|
|
|
|
|
|
markerModeActive = false;
|
|
|
|
|
|
document.querySelectorAll('.marker-mode-btn').forEach(b => b.classList.remove('active'));
|
|
|
|
|
|
dismissNoteBottomSheet();
|
|
|
|
|
|
|
|
|
|
|
|
// Clear search before wiping content
|
|
|
|
|
|
clearReaderSearch();
|
|
|
|
|
|
|
|
|
|
|
|
// Close settings panel if open
|
|
|
|
|
|
readerSettingsPanelOpen = false;
|
|
|
|
|
|
const sp = document.getElementById('reader-settings-panel');
|
|
|
|
|
|
if (sp) sp.remove();
|
|
|
|
|
|
|
|
|
|
|
|
// Reset progress input
|
|
|
|
|
|
const progressInput = $('reader-progress-input');
|
|
|
|
|
|
if (progressInput) { progressInput.style.display = 'none'; progressInput.value = 0; }
|
|
|
|
|
|
const progressSuffix = $('reader-progress-suffix');
|
|
|
|
|
|
if (progressSuffix) progressSuffix.textContent = '';
|
|
|
|
|
|
|
|
|
|
|
|
// Remove touch handlers and free image blob URLs
|
|
|
|
|
|
const contentEl = $('reader-content');
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
_readerUnlistenAll();
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
if (contentEl) {
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
contentEl.removeEventListener('click', _pdfPaginatedClick);
|
|
|
|
|
|
contentEl.removeEventListener('wheel', _pdfPaginatedWheel);
|
|
|
|
|
|
contentEl.classList.remove('pinch-active', 'pdf-paginated');
|
|
|
|
|
|
contentEl.style.overflow = '';
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
contentEl.innerHTML = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const url of Object.values(currentImageMap)) URL.revokeObjectURL(url);
|
|
|
|
|
|
currentImageMap = {};
|
|
|
|
|
|
|
|
|
|
|
|
// Reset all state
|
|
|
|
|
|
currentBookId = null;
|
|
|
|
|
|
currentBookToc = [];
|
|
|
|
|
|
_currentBookCharCount = 0;
|
2026-10-19 14:57:03 +02:00
|
|
|
|
if (typeof _pdfStopLazy === 'function') _pdfStopLazy();
|
|
|
|
|
|
if (currentPdfDoc) { try { currentPdfDoc.destroy(); } catch (e) {} }
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
currentPdfDoc = null;
|
2026-11-03 18:16:27 +01:00
|
|
|
|
if (typeof _pdfForgetDoc === 'function') _pdfForgetDoc();
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
currentPdfBuffer = null;
|
|
|
|
|
|
currentBookmarks = [];
|
|
|
|
|
|
bookmarksDirty = false;
|
|
|
|
|
|
currentHighlights = [];
|
|
|
|
|
|
highlightsDirty = false;
|
|
|
|
|
|
_lastProgressBeacon = null;
|
|
|
|
|
|
_lastBookmarkBeacon = null;
|
|
|
|
|
|
_lastHighlightBeacon = null;
|
|
|
|
|
|
dismissHighlightPopover();
|
|
|
|
|
|
pdfCurrentPage = 1;
|
|
|
|
|
|
pdfTotalPages = 0;
|
|
|
|
|
|
_pdfPageTextBoxCache = {};
|
|
|
|
|
|
|
|
|
|
|
|
// Remove PDF invert class
|
|
|
|
|
|
if (overlay) overlay.classList.remove('pdf-inverted');
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openTocSidebar() {
|
|
|
|
|
|
if (!currentBookToc.length) {
|
|
|
|
|
|
openSidebar('Inhaltsverzeichnis', '<p class="muted">Dieses Buch hat kein Inhaltsverzeichnis.</p>');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
let html = '<ul class="toc-list">';
|
|
|
|
|
|
for (const entry of currentBookToc) {
|
|
|
|
|
|
const indent = entry.depth * 14;
|
|
|
|
|
|
// Use data-toc-href — onclick would be stripped by sanitizeSidebarHtml
|
|
|
|
|
|
html += `<li style="padding-left:${indent}px">
|
|
|
|
|
|
<button class="btn-link toc-entry" data-toc-href="${escapeHtml(entry.href)}">${escapeHtml(entry.label)}</button>
|
|
|
|
|
|
</li>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
html += '</ul>';
|
|
|
|
|
|
openSidebar('Inhaltsverzeichnis', html);
|
|
|
|
|
|
// Attach delegated listener after sidebar body is populated
|
|
|
|
|
|
const body = $('sidebar-body');
|
|
|
|
|
|
body.addEventListener('click', function _tocClick(e) {
|
|
|
|
|
|
const btn = e.target.closest('.toc-entry');
|
|
|
|
|
|
if (btn) {
|
|
|
|
|
|
body.removeEventListener('click', _tocClick);
|
|
|
|
|
|
jumpToTocEntry(btn.getAttribute('data-toc-href') || '');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function jumpToTocEntry(href) {
|
|
|
|
|
|
closeSidebar();
|
|
|
|
|
|
_suppressScrollJumpDetect();
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
const contentEl = $('reader-content');
|
|
|
|
|
|
if (!contentEl) return;
|
|
|
|
|
|
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
// PDF page jump (scrolling or page by page)
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
if (href.startsWith('#pdf-page-')) {
|
PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
(_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
ein zweites Buch geben das alte Dokument frei; der entschlüsselte
PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
Outline, Links und einer fast leeren Seite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00
|
|
|
|
pdfGoToPage(parseInt(href.slice('#pdf-page-'.length), 10) || 1);
|
Refactoring Schritt 2: app.js nach Bereichen in static/js/app/ aufgeteilt; sw v106
32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne
Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei
'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der
beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale
Funktionen und let/const bleiben global, die App-Schichten (native.js,
native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen
die Dateien; diora-android build_web nimmt static/js rekursiv.
Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im
Reader, Hell/Dunkel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:30:43 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const hashIdx = href.indexOf('#');
|
|
|
|
|
|
const fragment = hashIdx >= 0 ? href.slice(hashIdx + 1) : '';
|
|
|
|
|
|
const filePath = hashIdx >= 0 ? href.slice(0, hashIdx) : href;
|
|
|
|
|
|
|
|
|
|
|
|
let target = null;
|
|
|
|
|
|
if (fragment) {
|
|
|
|
|
|
target = contentEl.querySelector(`#${CSS.escape(fragment)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!target && filePath) {
|
|
|
|
|
|
target = Array.from(contentEl.querySelectorAll('[data-epub-src]'))
|
|
|
|
|
|
.find(el => el.getAttribute('data-epub-src') === filePath) || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (target) {
|
|
|
|
|
|
const top = target.getBoundingClientRect().top - contentEl.getBoundingClientRect().top;
|
|
|
|
|
|
contentEl.scrollBy({top: top - 16, behavior: 'smooth'});
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 50);
|
|
|
|
|
|
}
|