Bücherliste: Seitenzahl neben dem Lesefortschritt
PDFs liefern die echte Seitenzahl, bei EPUBs wird sie aus der Zeichenzahl geschätzt (1800 Zeichen je Seite). Gespeichert in den verschlüsselten Metadaten beim Öffnen des Buchs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3d659fc809
commit
aa4cf23b58
2 changed files with 15 additions and 5 deletions
|
|
@ -1991,6 +1991,8 @@ let readerScrollSaveTimer = null;
|
|||
let _resizeObserver = null;
|
||||
let _currentPositionAnchor = '';
|
||||
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}
|
||||
|
||||
// Reading-position percentages (the progress readout, annotation "% im Buch"
|
||||
|
|
@ -2493,8 +2495,8 @@ async function loadBookList() {
|
|||
try {
|
||||
const metaBuf = await decryptBytes(key, b.meta_iv, b.meta_ct);
|
||||
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 || ''};
|
||||
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});
|
||||
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 || '', 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) {
|
||||
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});
|
||||
|
|
@ -2536,7 +2538,7 @@ async function loadBookList() {
|
|||
if (b.last_read) return 1;
|
||||
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');
|
||||
if (uploadArea) uploadArea.style.display = 'none';
|
||||
renderBookList(cachedBooks);
|
||||
|
|
@ -2698,7 +2700,7 @@ function _renderBookItemHtml(b) {
|
|||
<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="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>` : ''}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -2825,6 +2827,7 @@ async function _updateBookMeta(bookId, overrides) {
|
|||
isbn: cached.isbn || book.isbn || '',
|
||||
folder: cached.folder || book.folder || '',
|
||||
shelfTag: cached.shelfTag || book.shelfTag || '',
|
||||
pages: cached.pages || book.pages || 0,
|
||||
...overrides,
|
||||
};
|
||||
const key = await getOrCreateEncKey();
|
||||
|
|
@ -3767,6 +3770,13 @@ async function _openBookOnce(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
|
||||
await Promise.all([
|
||||
loadBookmarks(bookId),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* diora service worker — caches the app shell for offline use.
|
||||
*/
|
||||
|
||||
const CACHE = 'diora-v95';
|
||||
const CACHE = 'diora-v96';
|
||||
const SHELL = [
|
||||
'/static/css/app.css',
|
||||
'/static/js/app.js',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue