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';
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Sidebar
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
function openSidebar(title, htmlContent) {
|
|
|
|
|
$('sidebar-title').textContent = title;
|
|
|
|
|
$('sidebar-body').innerHTML = sanitizeSidebarHtml(htmlContent);
|
|
|
|
|
$('sidebar-overlay').style.display = '';
|
|
|
|
|
$('sidebar').classList.add('open');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeSidebar() {
|
|
|
|
|
$('sidebar').classList.remove('open');
|
|
|
|
|
$('sidebar-overlay').style.display = 'none';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.addEventListener('keydown', e => {
|
|
|
|
|
const overlay = $('reader-overlay');
|
|
|
|
|
const readerOpen = overlay && overlay.style.display !== 'none';
|
|
|
|
|
|
|
|
|
|
if (e.key === 'Escape') {
|
|
|
|
|
if (readerOpen) {
|
|
|
|
|
closeReader();
|
|
|
|
|
} else {
|
|
|
|
|
closeSidebar();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (readerOpen) {
|
|
|
|
|
if ((e.ctrlKey && e.key === 'f') || e.key === 'F3') {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
toggleReaderSearch();
|
|
|
|
|
}
|
|
|
|
|
if (readerSearchOpen) {
|
|
|
|
|
if (e.key === 'ArrowDown') { e.preventDefault(); readerSearchNext(); }
|
|
|
|
|
if (e.key === 'ArrowUp') { e.preventDefault(); readerSearchPrev(); }
|
|
|
|
|
}
|
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 by page: arrows, Space, PageUp/PageDown turn (scrolling has its own keys below).
|
|
|
|
|
if (readerSettings.pdfPaginated && currentPdfDoc && !readerSearchOpen
|
|
|
|
|
&& !/^(INPUT|TEXTAREA|SELECT)$/.test((document.activeElement && document.activeElement.tagName) || '')) {
|
|
|
|
|
const turn = {ArrowRight: 1, ArrowDown: 1, PageDown: 1, ' ': e.shiftKey ? -1 : 1,
|
|
|
|
|
ArrowLeft: -1, ArrowUp: -1, PageUp: -1}[e.key];
|
|
|
|
|
if (turn) { e.preventDefault(); pdfGoToPage(pdfCurrentPage + turn); 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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Vim-style scroll — ignore when typing in an input
|
|
|
|
|
const tag = document.activeElement?.tagName;
|
|
|
|
|
if (tag !== 'INPUT' && tag !== 'TEXTAREA') {
|
|
|
|
|
const contentEl = $('reader-content');
|
|
|
|
|
if (contentEl) {
|
|
|
|
|
const large = Math.round(contentEl.clientHeight * 0.85);
|
|
|
|
|
const continuousKeys = {'j': 1, 'k': -1};
|
|
|
|
|
if (continuousKeys[e.key] !== undefined && !e.repeat) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
_readerScrollDir = continuousKeys[e.key];
|
|
|
|
|
if (!_readerScrollInterval) {
|
|
|
|
|
_readerScrollLastTs = performance.now();
|
|
|
|
|
_readerScrollInterval = setInterval(_readerScrollTick, READER_SCROLL_TICK_MS);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (e.key === 'd') { e.preventDefault(); contentEl.scrollBy({top: large / 2, behavior: 'smooth'}); }
|
|
|
|
|
if (e.key === 'u') { e.preventDefault(); contentEl.scrollBy({top: -large / 2, behavior: 'smooth'}); }
|
|
|
|
|
if (e.key === 'f') { e.preventDefault(); contentEl.scrollBy({top: large, behavior: 'smooth'}); }
|
|
|
|
|
if (e.key === 'b') { e.preventDefault(); contentEl.scrollBy({top: -large, behavior: 'smooth'}); }
|
|
|
|
|
if (e.key === 'g') { e.preventDefault(); contentEl.scrollTop = 0; }
|
|
|
|
|
if (e.key === 'G') { e.preventDefault(); contentEl.scrollTop = contentEl.scrollHeight; }
|
|
|
|
|
if (currentPdfDoc && !readerSettings.pdfPaginated) {
|
|
|
|
|
if (e.key === 'n') { e.preventDefault(); pdfGoToPage(pdfCurrentPage + 1); }
|
|
|
|
|
if (e.key === 'p') { e.preventDefault(); pdfGoToPage(pdfCurrentPage - 1); }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.addEventListener('keyup', e => {
|
|
|
|
|
if (e.key === 'j' || e.key === 'k') {
|
|
|
|
|
clearInterval(_readerScrollInterval);
|
|
|
|
|
_readerScrollInterval = null;
|
|
|
|
|
_readerScrollDir = 0;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function sanitizeSidebarHtml(html) {
|
|
|
|
|
if (!html) return '<p class="muted">Keine Shownotes vorhanden.</p>';
|
|
|
|
|
const div = document.createElement('div');
|
|
|
|
|
div.innerHTML = html;
|
|
|
|
|
div.querySelectorAll('script, iframe, object, embed, style').forEach(el => el.remove());
|
|
|
|
|
div.querySelectorAll('*').forEach(el => {
|
|
|
|
|
Array.from(el.attributes).forEach(attr => {
|
|
|
|
|
if (attr.name.startsWith('on')) el.removeAttribute(attr.name);
|
|
|
|
|
});
|
|
|
|
|
if (el.tagName === 'A') {
|
|
|
|
|
el.setAttribute('target', '_blank');
|
|
|
|
|
el.setAttribute('rel', 'noopener noreferrer');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return div.innerHTML;
|
|
|
|
|
}
|