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>
98 lines
3.6 KiB
JavaScript
98 lines
3.6 KiB
JavaScript
'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(); }
|
|
}
|
|
if (readerSettings.pdfPaginated && currentPdfDoc) {
|
|
if (e.key === 'ArrowRight') { e.preventDefault(); pdfGoToPage(pdfCurrentPage + 1); }
|
|
if (e.key === 'ArrowLeft') { e.preventDefault(); pdfGoToPage(pdfCurrentPage - 1); }
|
|
}
|
|
|
|
// 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;
|
|
}
|