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';
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Reader Settings
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
// Typography defaults. Phones keep the long-standing ones; a wide screen
|
|
|
|
|
|
// (desktop browser, diora desktop) starts bigger and wider.
|
|
|
|
|
|
const _READER_TYPE_DEFAULTS_NARROW = { fontSize: 16, lineHeight: 1.8, maxWidth: 65, fontWeight: null };
|
|
|
|
|
|
const _READER_TYPE_DEFAULTS_WIDE = { fontSize: 20, lineHeight: 1.6, maxWidth: 90, fontWeight: 375 };
|
|
|
|
|
|
const _READER_WIDTH_MAX = 140; // ch, the width slider's upper end ("Full" stays separate)
|
|
|
|
|
|
|
|
|
|
|
|
function _readerTypeDefaults() {
|
|
|
|
|
|
return window.matchMedia('(min-width: 900px)').matches
|
|
|
|
|
|
? _READER_TYPE_DEFAULTS_WIDE : _READER_TYPE_DEFAULTS_NARROW;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function loadReaderSettings(bookId) {
|
|
|
|
|
|
// Reset to defaults, then apply per-book overrides
|
|
|
|
|
|
Object.assign(readerSettings, { theme: 'app', fontFamily: 'serif', noBold: false,
|
|
|
|
|
|
pdfZoom: 100, pdfInverted: false, pdfPaginated: false, pdfSpread: false },
|
|
|
|
|
|
_readerTypeDefaults());
|
|
|
|
|
|
try {
|
|
|
|
|
|
const saved = JSON.parse(localStorage.getItem(`diora_reader_settings_${bookId}`) || '{}');
|
|
|
|
|
|
// Settings saved before v2 hold every value, changed or not. A value equal
|
|
|
|
|
|
// to the old default was never chosen, so it gives way to today's default.
|
|
|
|
|
|
if (!saved.v) {
|
|
|
|
|
|
for (const [key, old] of Object.entries(_READER_TYPE_DEFAULTS_NARROW)) {
|
|
|
|
|
|
if (saved[key] === old) delete saved[key];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Before v3 the reader's theme was always stored, "dark" by default: that now means "Wie die App".
|
|
|
|
|
|
if ((saved.v || 0) < 3 && saved.theme === 'dark') delete saved.theme;
|
|
|
|
|
|
delete saved.v;
|
|
|
|
|
|
Object.assign(readerSettings, saved);
|
|
|
|
|
|
if (saved.pdfPaginated === undefined) {
|
|
|
|
|
|
readerSettings.pdfPaginated = window.innerWidth < 768;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function saveReaderSettings() {
|
|
|
|
|
|
if (currentBookId) {
|
|
|
|
|
|
localStorage.setItem(`diora_reader_settings_${currentBookId}`, JSON.stringify({ ...readerSettings, v: 3 }));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function applyReaderSettings(isPdf) {
|
|
|
|
|
|
const overlay = $('reader-overlay');
|
|
|
|
|
|
const contentEl = $('reader-content');
|
|
|
|
|
|
if (!overlay || !contentEl) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (!isPdf) {
|
|
|
|
|
|
contentEl.style.fontSize = readerSettings.fontSize + 'px';
|
|
|
|
|
|
contentEl.style.lineHeight = readerSettings.lineHeight;
|
|
|
|
|
|
contentEl.style.setProperty('--reader-max-width', readerSettings.maxWidth + 'ch');
|
|
|
|
|
|
const fontMap = {
|
|
|
|
|
|
serif: "'Source Serif 4', Georgia, 'Times New Roman', serif",
|
|
|
|
|
|
sans: 'system-ui, -apple-system, sans-serif',
|
|
|
|
|
|
humanist: 'Verdana, Geneva, sans-serif',
|
|
|
|
|
|
mono: "'Courier New', monospace",
|
|
|
|
|
|
};
|
|
|
|
|
|
contentEl.style.fontFamily = fontMap[readerSettings.fontFamily] || fontMap.serif;
|
|
|
|
|
|
contentEl.classList.toggle('reader-no-bold', !!readerSettings.noBold);
|
|
|
|
|
|
// Weight: null keeps the stylesheet's. --reader-weight lets old WebViews
|
|
|
|
|
|
// without variable-font support (which drop weights like 425) pick a
|
|
|
|
|
|
// static cut instead.
|
|
|
|
|
|
contentEl.style.fontWeight = '';
|
|
|
|
|
|
if (readerSettings.fontWeight) {
|
|
|
|
|
|
contentEl.style.fontWeight = String(readerSettings.fontWeight);
|
|
|
|
|
|
contentEl.style.setProperty('--reader-weight', String(readerSettings.fontWeight));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
contentEl.style.removeProperty('--reader-weight');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (_currentPositionAnchor && currentBookId) {
|
|
|
|
|
|
_suppressScrollJumpDetect();
|
|
|
|
|
|
requestAnimationFrame(() => restoreFromAnchor($('reader-content'), _currentPositionAnchor));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Theme
|
|
|
|
|
|
overlay.classList.remove('reader-theme-sepia', 'reader-theme-bright');
|
|
|
|
|
|
const readerTheme = readerSettings.theme === 'app'
|
|
|
|
|
|
? (document.documentElement.getAttribute('data-theme') === 'light' ? 'bright' : 'dark')
|
|
|
|
|
|
: readerSettings.theme;
|
|
|
|
|
|
if (readerTheme === 'sepia') overlay.classList.add('reader-theme-sepia');
|
|
|
|
|
|
else if (readerTheme === 'bright') overlay.classList.add('reader-theme-bright');
|
|
|
|
|
|
|
|
|
|
|
|
// PDF invert
|
|
|
|
|
|
if (isPdf && readerSettings.pdfInverted) overlay.classList.add('pdf-inverted');
|
|
|
|
|
|
else overlay.classList.remove('pdf-inverted');
|
|
|
|
|
|
|
|
|
|
|
|
// Annotations (marker mode + margin panel) are EPUB-only for now — PDFs have no text layer yet
|
|
|
|
|
|
const marginBtn = $('reader-margin-btn');
|
|
|
|
|
|
if (marginBtn) marginBtn.style.display = isPdf ? 'none' : '';
|
|
|
|
|
|
if (isPdf && readerAnnotationsMarginOpen) toggleAnnotationsMargin();
|
|
|
|
|
|
document.querySelectorAll('.marker-mode-btn').forEach(b => { b.style.display = isPdf ? 'none' : ''; });
|
|
|
|
|
|
if (isPdf && markerModeActive) toggleMarkerMode();
|
2026-10-14 19:43:03 +02:00
|
|
|
|
// Line length or font changed: the column's place next to an open margin too.
|
|
|
|
|
|
if (readerAnnotationsMarginOpen) _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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Font weight slider: the variable Source Serif 4 takes any weight, the
|
|
|
|
|
|
// system fonts only their fixed cuts (multiples of 100).
|
|
|
|
|
|
function _readerWeightStep() {
|
|
|
|
|
|
return readerSettings.fontFamily === 'serif' ? 25 : 100;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function _readerWeight() {
|
|
|
|
|
|
const step = _readerWeightStep();
|
|
|
|
|
|
const w = readerSettings.fontWeight
|
|
|
|
|
|
|| parseInt(getComputedStyle($('reader-content')).fontWeight, 10) || 400;
|
|
|
|
|
|
return Math.round(w / step) * step;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleSettingsPanel() {
|
|
|
|
|
|
const overlay = $('reader-overlay');
|
|
|
|
|
|
const contentEl = $('reader-content');
|
|
|
|
|
|
if (!overlay || !contentEl) return;
|
|
|
|
|
|
|
|
|
|
|
|
const existing = document.getElementById('reader-settings-panel');
|
|
|
|
|
|
if (existing) {
|
|
|
|
|
|
existing.remove();
|
|
|
|
|
|
readerSettingsPanelOpen = false;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
readerSettingsPanelOpen = true;
|
|
|
|
|
|
const isPdf = !!currentPdfDoc;
|
|
|
|
|
|
|
|
|
|
|
|
const panel = document.createElement('div');
|
|
|
|
|
|
panel.id = 'reader-settings-panel';
|
|
|
|
|
|
panel.className = 'reader-settings-panel';
|
|
|
|
|
|
|
|
|
|
|
|
if (!isPdf) {
|
|
|
|
|
|
panel.innerHTML = `
|
|
|
|
|
|
<label>Schrift <button class="btn btn-sm" id="rs-font-minus">−</button> <input type="range" id="rs-font" min="12" max="24" step="1" value="${readerSettings.fontSize}"> <button class="btn btn-sm" id="rs-font-plus">+</button> <span id="rs-font-val">${readerSettings.fontSize}px</span></label>
|
|
|
|
|
|
<label>Zeilen <button class="btn btn-sm" id="rs-line-minus">−</button> <input type="range" id="rs-line" min="12" max="30" step="1" value="${Math.round(readerSettings.lineHeight * 10)}"> <button class="btn btn-sm" id="rs-line-plus">+</button> <span id="rs-line-val">${readerSettings.lineHeight}</span></label>
|
|
|
|
|
|
<label>Breite <button class="btn btn-sm" id="rs-width-minus">−</button> <input type="range" id="rs-width" min="40" max="${_READER_WIDTH_MAX}" step="5" value="${Math.min(readerSettings.maxWidth, _READER_WIDTH_MAX)}"> <button class="btn btn-sm" id="rs-width-plus">+</button> <span id="rs-width-val">${readerSettings.maxWidth}ch</span></label>
|
|
|
|
|
|
<button class="btn btn-sm" id="rs-width-full">Volle Breite</button>
|
|
|
|
|
|
<label>Stärke <button class="btn btn-sm" id="rs-weight-minus">−</button> <input type="range" id="rs-weight" min="200" max="800" step="${_readerWeightStep()}" value="${_readerWeight()}"> <button class="btn btn-sm" id="rs-weight-plus">+</button> <span id="rs-weight-val">${_readerWeight()}</span></label>
|
|
|
|
|
|
<button class="btn btn-sm ${readerSettings.fontFamily === 'serif' ? 'active' : ''}" data-rs-font="serif">Serif</button>
|
|
|
|
|
|
<button class="btn btn-sm ${readerSettings.fontFamily === 'sans' ? 'active' : ''}" data-rs-font="sans">Sans</button>
|
|
|
|
|
|
<button class="btn btn-sm ${readerSettings.fontFamily === 'humanist' ? 'active' : ''}" data-rs-font="humanist">Verdana</button>
|
|
|
|
|
|
<button class="btn btn-sm ${readerSettings.fontFamily === 'mono' ? 'active' : ''}" data-rs-font="mono">Mono</button>
|
|
|
|
|
|
<button class="btn btn-sm ${readerSettings.noBold ? 'active' : ''}" id="rs-no-bold">Kein Fett</button>
|
|
|
|
|
|
<button class="btn btn-sm ${readerSettings.theme === 'app' ? 'active' : ''}" data-rs-theme="app">Wie die App</button>
|
|
|
|
|
|
<button class="btn btn-sm ${readerSettings.theme === 'dark' ? 'active' : ''}" data-rs-theme="dark">Dunkel</button>
|
|
|
|
|
|
<button class="btn btn-sm ${readerSettings.theme === 'sepia' ? 'active' : ''}" data-rs-theme="sepia">Sepia</button>
|
|
|
|
|
|
<button class="btn btn-sm ${readerSettings.theme === 'bright' ? 'active' : ''}" data-rs-theme="bright">Hell</button>
|
|
|
|
|
|
`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
panel.innerHTML = `
|
|
|
|
|
|
<label>Zoom <button class="btn btn-sm" id="rs-zoom-minus">−</button> <input type="range" id="rs-zoom" min="50" max="200" step="10" value="${readerSettings.pdfZoom}"> <button class="btn btn-sm" id="rs-zoom-plus">+</button> <span id="rs-zoom-val">${readerSettings.pdfZoom}%</span></label>
|
|
|
|
|
|
<button class="btn btn-sm ${readerSettings.pdfInverted ? 'active' : ''}" id="rs-invert">Invertieren</button>
|
|
|
|
|
|
<button class="btn btn-sm ${readerSettings.pdfSpread ? 'active' : ''}" id="rs-spread">Doppelseite</button>
|
|
|
|
|
|
`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
overlay.insertBefore(panel, $('reader-body-row'));
|
|
|
|
|
|
|
|
|
|
|
|
if (!isPdf) {
|
|
|
|
|
|
const fontRange = panel.querySelector('#rs-font');
|
|
|
|
|
|
const fontVal = panel.querySelector('#rs-font-val');
|
|
|
|
|
|
fontRange.addEventListener('input', () => {
|
|
|
|
|
|
readerSettings.fontSize = parseInt(fontRange.value, 10);
|
|
|
|
|
|
fontVal.textContent = readerSettings.fontSize + 'px';
|
|
|
|
|
|
applyReaderSettings(false);
|
|
|
|
|
|
saveReaderSettings();
|
|
|
|
|
|
});
|
|
|
|
|
|
panel.querySelector('#rs-font-minus').addEventListener('click', () => {
|
|
|
|
|
|
fontRange.value = Math.max(+fontRange.min, parseInt(fontRange.value, 10) - 1);
|
|
|
|
|
|
fontRange.dispatchEvent(new Event('input'));
|
|
|
|
|
|
});
|
|
|
|
|
|
panel.querySelector('#rs-font-plus').addEventListener('click', () => {
|
|
|
|
|
|
fontRange.value = Math.min(+fontRange.max, parseInt(fontRange.value, 10) + 1);
|
|
|
|
|
|
fontRange.dispatchEvent(new Event('input'));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const lineRange = panel.querySelector('#rs-line');
|
|
|
|
|
|
const lineVal = panel.querySelector('#rs-line-val');
|
|
|
|
|
|
lineRange.addEventListener('input', () => {
|
|
|
|
|
|
readerSettings.lineHeight = (parseInt(lineRange.value, 10) / 10).toFixed(1);
|
|
|
|
|
|
lineVal.textContent = readerSettings.lineHeight;
|
|
|
|
|
|
applyReaderSettings(false);
|
|
|
|
|
|
saveReaderSettings();
|
|
|
|
|
|
});
|
|
|
|
|
|
panel.querySelector('#rs-line-minus').addEventListener('click', () => {
|
|
|
|
|
|
lineRange.value = Math.max(+lineRange.min, parseInt(lineRange.value, 10) - 1);
|
|
|
|
|
|
lineRange.dispatchEvent(new Event('input'));
|
|
|
|
|
|
});
|
|
|
|
|
|
panel.querySelector('#rs-line-plus').addEventListener('click', () => {
|
|
|
|
|
|
lineRange.value = Math.min(+lineRange.max, parseInt(lineRange.value, 10) + 1);
|
|
|
|
|
|
lineRange.dispatchEvent(new Event('input'));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const widthRange = panel.querySelector('#rs-width');
|
|
|
|
|
|
const widthVal = panel.querySelector('#rs-width-val');
|
|
|
|
|
|
widthRange.addEventListener('input', () => {
|
|
|
|
|
|
readerSettings.maxWidth = parseInt(widthRange.value, 10);
|
|
|
|
|
|
widthVal.textContent = readerSettings.maxWidth + 'ch';
|
|
|
|
|
|
applyReaderSettings(false);
|
|
|
|
|
|
saveReaderSettings();
|
|
|
|
|
|
});
|
|
|
|
|
|
panel.querySelector('#rs-width-minus').addEventListener('click', () => {
|
|
|
|
|
|
widthRange.value = Math.max(+widthRange.min, parseInt(widthRange.value, 10) - 5);
|
|
|
|
|
|
widthRange.dispatchEvent(new Event('input'));
|
|
|
|
|
|
});
|
|
|
|
|
|
panel.querySelector('#rs-width-plus').addEventListener('click', () => {
|
|
|
|
|
|
widthRange.value = Math.min(+widthRange.max, parseInt(widthRange.value, 10) + 5);
|
|
|
|
|
|
widthRange.dispatchEvent(new Event('input'));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
panel.querySelector('#rs-width-full').addEventListener('click', () => {
|
|
|
|
|
|
readerSettings.maxWidth = 999;
|
|
|
|
|
|
widthRange.value = 90;
|
|
|
|
|
|
widthVal.textContent = 'full';
|
|
|
|
|
|
applyReaderSettings(false);
|
|
|
|
|
|
saveReaderSettings();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const weightRange = panel.querySelector('#rs-weight');
|
|
|
|
|
|
const weightVal = panel.querySelector('#rs-weight-val');
|
|
|
|
|
|
weightRange.addEventListener('input', () => {
|
|
|
|
|
|
readerSettings.fontWeight = parseInt(weightRange.value, 10);
|
|
|
|
|
|
weightVal.textContent = readerSettings.fontWeight;
|
|
|
|
|
|
applyReaderSettings(false);
|
|
|
|
|
|
saveReaderSettings();
|
|
|
|
|
|
});
|
|
|
|
|
|
panel.querySelector('#rs-weight-minus').addEventListener('click', () => {
|
|
|
|
|
|
weightRange.value = Math.max(+weightRange.min, parseInt(weightRange.value, 10) - +weightRange.step);
|
|
|
|
|
|
weightRange.dispatchEvent(new Event('input'));
|
|
|
|
|
|
});
|
|
|
|
|
|
panel.querySelector('#rs-weight-plus').addEventListener('click', () => {
|
|
|
|
|
|
weightRange.value = Math.min(+weightRange.max, parseInt(weightRange.value, 10) + +weightRange.step);
|
|
|
|
|
|
weightRange.dispatchEvent(new Event('input'));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
panel.querySelectorAll('[data-rs-font]').forEach(btn => {
|
|
|
|
|
|
btn.addEventListener('click', () => {
|
|
|
|
|
|
readerSettings.fontFamily = btn.dataset.rsFont;
|
|
|
|
|
|
panel.querySelectorAll('[data-rs-font]').forEach(b => b.classList.toggle('active', b === btn));
|
|
|
|
|
|
// The system fonts only have cuts at multiples of 100.
|
|
|
|
|
|
weightRange.step = _readerWeightStep();
|
|
|
|
|
|
if (readerSettings.fontWeight) {
|
|
|
|
|
|
readerSettings.fontWeight = Math.round(readerSettings.fontWeight / weightRange.step) * weightRange.step;
|
|
|
|
|
|
}
|
|
|
|
|
|
applyReaderSettings(false);
|
|
|
|
|
|
saveReaderSettings();
|
|
|
|
|
|
weightRange.value = _readerWeight();
|
|
|
|
|
|
weightVal.textContent = _readerWeight();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
panel.querySelector('#rs-no-bold').addEventListener('click', function () {
|
|
|
|
|
|
readerSettings.noBold = !readerSettings.noBold;
|
|
|
|
|
|
this.classList.toggle('active', readerSettings.noBold);
|
|
|
|
|
|
applyReaderSettings(false);
|
|
|
|
|
|
saveReaderSettings();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
panel.querySelectorAll('[data-rs-theme]').forEach(btn => {
|
|
|
|
|
|
btn.addEventListener('click', () => {
|
|
|
|
|
|
readerSettings.theme = btn.dataset.rsTheme;
|
|
|
|
|
|
panel.querySelectorAll('[data-rs-theme]').forEach(b => b.classList.toggle('active', b === btn));
|
|
|
|
|
|
applyReaderSettings(false);
|
|
|
|
|
|
saveReaderSettings();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const zoomRange = panel.querySelector('#rs-zoom');
|
|
|
|
|
|
|
|
|
|
|
|
zoomRange.addEventListener('input', () => applyPdfZoom(parseInt(zoomRange.value, 10)));
|
|
|
|
|
|
panel.querySelector('#rs-zoom-minus').addEventListener('click', () =>
|
|
|
|
|
|
applyPdfZoom(Math.max(50, readerSettings.pdfZoom - 10)));
|
|
|
|
|
|
panel.querySelector('#rs-zoom-plus').addEventListener('click', () =>
|
|
|
|
|
|
applyPdfZoom(Math.min(200, readerSettings.pdfZoom + 10)));
|
|
|
|
|
|
|
|
|
|
|
|
panel.querySelector('#rs-invert').addEventListener('click', function () {
|
|
|
|
|
|
readerSettings.pdfInverted = !readerSettings.pdfInverted;
|
|
|
|
|
|
this.classList.toggle('active', readerSettings.pdfInverted);
|
|
|
|
|
|
applyReaderSettings(true);
|
|
|
|
|
|
saveReaderSettings();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
panel.querySelector('#rs-spread').addEventListener('click', function () {
|
|
|
|
|
|
readerSettings.pdfSpread = !readerSettings.pdfSpread;
|
|
|
|
|
|
this.classList.toggle('active', readerSettings.pdfSpread);
|
|
|
|
|
|
saveReaderSettings();
|
|
|
|
|
|
reRenderPdf();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function applyPdfZoom(newZoom) {
|
|
|
|
|
|
readerSettings.pdfZoom = newZoom;
|
|
|
|
|
|
const zoomRange = document.getElementById('rs-zoom');
|
|
|
|
|
|
const zoomVal = document.getElementById('rs-zoom-val');
|
|
|
|
|
|
if (zoomRange) zoomRange.value = newZoom;
|
|
|
|
|
|
if (zoomVal) zoomVal.textContent = newZoom + '%';
|
|
|
|
|
|
saveReaderSettings();
|
|
|
|
|
|
if (readerSettings.pdfPaginated) {
|
|
|
|
|
|
pdfSmartZoomPage(pdfCurrentPage);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await reRenderPdf();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function _currentScrollPage(contentEl) {
|
|
|
|
|
|
const wrappers = contentEl.querySelectorAll('.pdf-page-wrapper');
|
|
|
|
|
|
const viewTop = contentEl.scrollTop;
|
|
|
|
|
|
for (const w of wrappers) {
|
|
|
|
|
|
if (w.offsetTop + w.offsetHeight > viewTop) {
|
|
|
|
|
|
const n = parseInt(w.id.replace('pdf-page-', ''), 10);
|
|
|
|
|
|
if (n) return n;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return pdfCurrentPage || 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function reRenderPdf() {
|
|
|
|
|
|
if (!currentPdfBuffer) return;
|
|
|
|
|
|
const contentEl = $('reader-content');
|
|
|
|
|
|
if (!contentEl) return;
|
|
|
|
|
|
const pivot = readerSettings.pdfPaginated ? pdfCurrentPage : _currentScrollPage(contentEl);
|
|
|
|
|
|
await renderPdf(currentPdfBuffer, contentEl, undefined, pivot);
|
|
|
|
|
|
if (readerSettings.pdfPaginated) enterPdfPaginatedMode();
|
|
|
|
|
|
}
|