Add IndexedDB book cache and fix PDF position on mobile
- Cache encrypted book data in IndexedDB (cache-first on open) - Evict books not read for 4 weeks on book list load - Fix PDF paginated mode not activating on book open (mobile) - enterPdfPaginatedMode() now called after position restore in openBook() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
dbe3b46f3e
commit
68bb7b5920
1 changed files with 73 additions and 4 deletions
|
|
@ -2617,6 +2617,65 @@ function restoreFromAnchor(contentEl, anchor) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Book cache — IndexedDB, evict after 4 weeks of inactivity
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
const _BOOK_CACHE_STORE = 'books';
|
||||||
|
const _BOOK_CACHE_EVICT_MS = 4 * 7 * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
function _openBookCacheDb() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = indexedDB.open('diora_book_cache', 1);
|
||||||
|
req.onupgradeneeded = e => e.target.result.createObjectStore(_BOOK_CACHE_STORE, {keyPath: 'id'});
|
||||||
|
req.onsuccess = e => resolve(e.target.result);
|
||||||
|
req.onerror = () => reject();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _getCachedBook(bookId) {
|
||||||
|
try {
|
||||||
|
const db = await _openBookCacheDb();
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
const req = db.transaction(_BOOK_CACHE_STORE).objectStore(_BOOK_CACHE_STORE).get(bookId);
|
||||||
|
req.onsuccess = e => resolve(e.target.result || null);
|
||||||
|
req.onerror = () => resolve(null);
|
||||||
|
});
|
||||||
|
} catch { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _setCachedBook(bookId, data_ct, data_iv) {
|
||||||
|
try {
|
||||||
|
const db = await _openBookCacheDb();
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
const tx = db.transaction(_BOOK_CACHE_STORE, 'readwrite');
|
||||||
|
tx.objectStore(_BOOK_CACHE_STORE).put({id: bookId, data_ct, data_iv, cached_at: Date.now()});
|
||||||
|
tx.oncomplete = resolve;
|
||||||
|
tx.onerror = resolve;
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _evictBookCache(bookList) {
|
||||||
|
try {
|
||||||
|
const db = await _openBookCacheDb();
|
||||||
|
const now = Date.now();
|
||||||
|
const metaById = Object.fromEntries(bookList.map(b => [b.id, b]));
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
const tx = db.transaction(_BOOK_CACHE_STORE, 'readwrite');
|
||||||
|
const store = tx.objectStore(_BOOK_CACHE_STORE);
|
||||||
|
store.openCursor().onsuccess = e => {
|
||||||
|
const cursor = e.target.result;
|
||||||
|
if (!cursor) { resolve(); return; }
|
||||||
|
const meta = metaById[cursor.value.id];
|
||||||
|
const lastRead = meta?.last_read ? new Date(meta.last_read).getTime() : 0;
|
||||||
|
if (!meta || (now - lastRead) > _BOOK_CACHE_EVICT_MS) cursor.delete();
|
||||||
|
cursor.continue();
|
||||||
|
};
|
||||||
|
tx.onerror = resolve;
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
// Reader settings
|
// Reader settings
|
||||||
let readerSettings = { fontSize: 16, lineHeight: 1.8, maxWidth: 65, theme: 'dark',
|
let readerSettings = { fontSize: 16, lineHeight: 1.8, maxWidth: 65, theme: 'dark',
|
||||||
pdfZoom: 100, pdfInverted: false, pdfPaginated: false };
|
pdfZoom: 100, pdfInverted: false, pdfPaginated: false };
|
||||||
|
|
@ -2668,6 +2727,7 @@ async function loadBookList() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const books = await res.json();
|
const books = await res.json();
|
||||||
|
_evictBookCache(books); // fire-and-forget
|
||||||
if (!Array.isArray(books)) {
|
if (!Array.isArray(books)) {
|
||||||
listEl.innerHTML = `<p class="muted">Unexpected response from server (not an array).</p>`;
|
listEl.innerHTML = `<p class="muted">Unexpected response from server (not an array).</p>`;
|
||||||
return;
|
return;
|
||||||
|
|
@ -2996,8 +3056,15 @@ async function openBook(bookId) {
|
||||||
try {
|
try {
|
||||||
loadReaderSettings();
|
loadReaderSettings();
|
||||||
const key = await getOrCreateEncKey();
|
const key = await getOrCreateEncKey();
|
||||||
const res = await fetch(`/books/${bookId}/data/`);
|
let data_ct, data_iv;
|
||||||
const {data_ct, data_iv} = await res.json();
|
const cached = await _getCachedBook(bookId);
|
||||||
|
if (cached) {
|
||||||
|
({data_ct, data_iv} = cached);
|
||||||
|
} else {
|
||||||
|
const res = await fetch(`/books/${bookId}/data/`);
|
||||||
|
({data_ct, data_iv} = await res.json());
|
||||||
|
_setCachedBook(bookId, data_ct, data_iv); // fire-and-forget
|
||||||
|
}
|
||||||
const plain = await decryptBytes(key, data_iv, data_ct);
|
const plain = await decryptBytes(key, data_iv, data_ct);
|
||||||
|
|
||||||
// Revoke any previous image blob URLs
|
// Revoke any previous image blob URLs
|
||||||
|
|
@ -3098,8 +3165,10 @@ async function openBook(bookId) {
|
||||||
const fraction = bookData ? (bookData.scroll_fraction || 0) : 0;
|
const fraction = bookData ? (bookData.scroll_fraction || 0) : 0;
|
||||||
const anchor = bookData ? (bookData.position_anchor || '') : '';
|
const anchor = bookData ? (bookData.position_anchor || '') : '';
|
||||||
_currentPositionAnchor = anchor;
|
_currentPositionAnchor = anchor;
|
||||||
if (isPdf && readerSettings.pdfPaginated && pdfTotalPages > 1) {
|
if (isPdf && readerSettings.pdfPaginated) {
|
||||||
if (fraction > 0) pdfCurrentPage = Math.max(1, Math.round(fraction * (pdfTotalPages - 1)) + 1);
|
if (fraction > 0 && pdfTotalPages > 1)
|
||||||
|
pdfCurrentPage = Math.max(1, Math.round(fraction * (pdfTotalPages - 1)) + 1);
|
||||||
|
enterPdfPaginatedMode();
|
||||||
} else if (!isPdf) {
|
} else if (!isPdf) {
|
||||||
// Wait for all images so scrollHeight is final
|
// Wait for all images so scrollHeight is final
|
||||||
const imgs = Array.from(contentEl.querySelectorAll('img'));
|
const imgs = Array.from(contentEl.querySelectorAll('img'));
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue