Merge remote-tracking branch 'origin/master'

This commit is contained in:
mrwnslz 2026-10-08 18:52:10 +02:00
commit 8662f381ed

View file

@ -2019,6 +2019,8 @@ let readerScrollSaveTimer = null;
let _resizeObserver = null; let _resizeObserver = null;
let _currentPositionAnchor = ''; let _currentPositionAnchor = '';
let _currentBookCharCount = 0; let _currentBookCharCount = 0;
// Rough characters per printed book page, used to estimate a real page count for EPUBs.
const CHARS_PER_BOOK_PAGE = 1800;
const bookMetaCache = {}; // id → {title, author, type} const bookMetaCache = {}; // id → {title, author, type}
// Reading-position percentages (the progress readout, annotation "% im Buch" // Reading-position percentages (the progress readout, annotation "% im Buch"
@ -2608,8 +2610,8 @@ async function loadBookList() {
try { try {
const metaBuf = await decryptBytes(key, b.meta_iv, b.meta_ct); const metaBuf = await decryptBytes(key, b.meta_iv, b.meta_ct);
const meta = JSON.parse(new TextDecoder().decode(metaBuf)); const meta = JSON.parse(new TextDecoder().decode(metaBuf));
bookMetaCache[b.id] = {title: meta.title || '?', author: meta.author || '', type: meta.type || 'epub', filename: meta.filename || '', folder: meta.folder || '', isbn: meta.isbn || '', shelfTag: meta.shelfTag || ''}; bookMetaCache[b.id] = {title: meta.title || '?', author: meta.author || '', type: meta.type || 'epub', filename: meta.filename || '', folder: meta.folder || '', isbn: meta.isbn || '', shelfTag: meta.shelfTag || '', pages: meta.pages || 0};
decrypted.push({id: b.id, title: meta.title || '?', author: meta.author || '', type: meta.type || 'epub', folder: meta.folder || '', isbn: meta.isbn || '', shelfTag: meta.shelfTag || '', scroll_fraction: b.scroll_fraction, position_anchor: b.position_anchor || '', uploaded_at: b.uploaded_at, last_read: b.last_read || null, keyOk: true, is_read: !!b.is_read, has_highlights: !!b.has_highlights}); decrypted.push({id: b.id, title: meta.title || '?', author: meta.author || '', type: meta.type || 'epub', folder: meta.folder || '', isbn: meta.isbn || '', shelfTag: meta.shelfTag || '', pages: meta.pages || 0, scroll_fraction: b.scroll_fraction, position_anchor: b.position_anchor || '', uploaded_at: b.uploaded_at, last_read: b.last_read || null, keyOk: true, is_read: !!b.is_read, has_highlights: !!b.has_highlights});
} catch (e) { } catch (e) {
bookMetaCache[b.id] = {title: `Buch #${b.id}`, author: '', type: 'epub', filename: '', folder: '', isbn: '', shelfTag: ''}; bookMetaCache[b.id] = {title: `Buch #${b.id}`, author: '', type: 'epub', filename: '', folder: '', isbn: '', shelfTag: ''};
decrypted.push({id: b.id, title: `Buch #${b.id}`, author: '', type: 'epub', folder: '', isbn: '', shelfTag: '', scroll_fraction: b.scroll_fraction, position_anchor: b.position_anchor || '', uploaded_at: b.uploaded_at, last_read: b.last_read || null, keyOk: false, is_read: !!b.is_read, has_highlights: !!b.has_highlights}); decrypted.push({id: b.id, title: `Buch #${b.id}`, author: '', type: 'epub', folder: '', isbn: '', shelfTag: '', scroll_fraction: b.scroll_fraction, position_anchor: b.position_anchor || '', uploaded_at: b.uploaded_at, last_read: b.last_read || null, keyOk: false, is_read: !!b.is_read, has_highlights: !!b.has_highlights});
@ -2651,7 +2653,7 @@ async function loadBookList() {
if (b.last_read) return 1; if (b.last_read) return 1;
return (b.uploaded_at || '').localeCompare(a.uploaded_at || ''); return (b.uploaded_at || '').localeCompare(a.uploaded_at || '');
}); });
for (const b of cachedBooks) bookMetaCache[b.id] = {title: b.title, author: b.author, type: b.type || 'epub', filename: b.filename || '', folder: b.folder || '', isbn: b.isbn || '', shelfTag: b.shelfTag || ''}; for (const b of cachedBooks) bookMetaCache[b.id] = {title: b.title, author: b.author, type: b.type || 'epub', filename: b.filename || '', folder: b.folder || '', isbn: b.isbn || '', shelfTag: b.shelfTag || '', pages: b.pages || 0};
const uploadArea = $('book-upload-area'); const uploadArea = $('book-upload-area');
if (uploadArea) uploadArea.style.display = 'none'; if (uploadArea) uploadArea.style.display = 'none';
renderBookList(cachedBooks); renderBookList(cachedBooks);
@ -2813,7 +2815,7 @@ function _renderBookItemHtml(b) {
<strong class="book-title">${escapeHtml(b.title)}${keyWarning}${b.is_read ? ' <span class="muted book-read-badge">✓ gelesen</span>' : ''}</strong> <strong class="book-title">${escapeHtml(b.title)}${keyWarning}${b.is_read ? ' <span class="muted book-read-badge">✓ gelesen</span>' : ''}</strong>
<span class="muted book-author">${escapeHtml(b.author)}</span> <span class="muted book-author">${escapeHtml(b.author)}</span>
<span class="book-item-meta-row"> <span class="book-item-meta-row">
${pct > 0 ? `<span class="muted book-progress">${pct} % gelesen</span>` : ''} ${pct > 0 ? `<span class="muted book-progress">${pct} % gelesen${b.pages ? ` (≈ ${Math.max(1, Math.round(b.pages * pct / 100))} von ${b.pages} Seiten)` : ''}</span>` : (b.pages ? `<span class="muted book-progress">ca. ${b.pages} Seiten</span>` : '')}
${b.shelfTag ? `<span class="book-shelf-badge" title="Regal-Kategorie">${escapeHtml(b.shelfTag)}</span>` : ''} ${b.shelfTag ? `<span class="book-shelf-badge" title="Regal-Kategorie">${escapeHtml(b.shelfTag)}</span>` : ''}
</span> </span>
</div> </div>
@ -2940,6 +2942,7 @@ async function _updateBookMeta(bookId, overrides) {
isbn: cached.isbn || book.isbn || '', isbn: cached.isbn || book.isbn || '',
folder: cached.folder || book.folder || '', folder: cached.folder || book.folder || '',
shelfTag: cached.shelfTag || book.shelfTag || '', shelfTag: cached.shelfTag || book.shelfTag || '',
pages: cached.pages || book.pages || 0,
...overrides, ...overrides,
}; };
const key = await getOrCreateEncKey(); const key = await getOrCreateEncKey();
@ -3889,6 +3892,13 @@ async function _openBookOnce(bookId) {
currentBookId = bookId; 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 // Load bookmarks and highlights
await Promise.all([ await Promise.all([
loadBookmarks(bookId), loadBookmarks(bookId),