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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
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;
|
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 result = await renderPdf(plain, contentEl);
|
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-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) {
|
|
|
|
|
|
const page = Math.min(numPages, Math.max(1, parseInt(this.value, 10) || 1));
|
|
|
|
|
|
this.value = 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 = '';
|
|
|
|
|
|
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
|
|
|
|
|
|
const cached = await _loadCachedBookMeta();
|
|
|
|
|
|
bookData = cached.find(b => b.id === bookId) || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
// The newer of the server's place and an own one it doesn't have yet.
|
|
|
|
|
|
if (bookData) bookData = _withPendingProgress(bookData);
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|