diora-web/static/js/app/26-sidebar.js

102 lines
3.9 KiB
JavaScript
Raw Permalink Normal View History

'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; }
}
// 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;
}