Bücher: Titel in "Zuletzt gelesen"/Ordner-Kacheln unsichtbar (Button-Farbvererbung); Regal-Lookup jetzt auch für Altbestand (SW v38)
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 24s

<button> erbt in den meisten Browsern nicht automatisch color von der Seite —
.book-recent-item und .book-folder-tile hatten keine explizite Farbe gesetzt,
wodurch der Titeltext (kein separates color wie beim gedimmten Autor-Span)
unsichtbar auf dunklem Grund landete.

Der Metadaten-Button funktionierte bisher nur für Bücher, die nach der letzten
Änderung hochgeladen wurden (ISBN wurde nur dort extrahiert). Fehlt die ISBN im
Meta-Blob, wird sie jetzt bei Bedarf direkt aus der bereits clientseitig
entschlüsselbaren Buchdatei nachgeholt (gleiche OPF-Quelle wie beim Öffnen) und
für künftige Abrufe mitgespeichert — kein erneuter Upload nötig. Nur EPUB, PDFs
liefern über pdf.js keine verlässliche ISBN.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011j4LcoeddwFt6Rw8Wyaknj
This commit is contained in:
marwin 2026-08-17 12:12:17 +02:00
parent 3e301b6f61
commit e6b3ee620e
3 changed files with 56 additions and 9 deletions

View file

@ -1684,8 +1684,10 @@ body.dnd-mode .timer-display {
border: 1px solid var(--border);
border-radius: var(--radius);
background: none;
color: var(--fg);
text-align: left;
cursor: pointer;
font: inherit;
}
.book-recent-item-title {
font-size: 13px;
@ -1717,8 +1719,10 @@ body.dnd-mode .timer-display {
border: 1px solid var(--border);
border-radius: var(--radius);
background: none;
color: var(--fg);
cursor: pointer;
text-align: center;
font: inherit;
}
.book-folder-tile-icon {
font-size: 22px;

View file

@ -3618,6 +3618,35 @@ async function assignBookFolder(bookId) {
}
}
// Fallback for books uploaded before ISBN extraction existed (or repaired/re-encrypted
// without it): pulls the already-accessible encrypted book bytes, decrypts and unzips just
// far enough to read the OPF identifiers — same source openBook() itself parses — without
// doing a full reader render. EPUB only; PDFs never carried a reliable ISBN via pdf.js.
async function _extractIsbnFromBookFile(bookId) {
try {
const key = await getOrCreateEncKey();
let data_ct, data_iv;
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());
}
const plain = await decryptBytes(key, data_iv, data_ct);
const zip = await JSZip.loadAsync(plain);
const containerXml = await zip.file('META-INF/container.xml').async('text');
const opfPath = new DOMParser()
.parseFromString(containerXml, 'application/xml')
.querySelector('rootfile')?.getAttribute('full-path');
if (!opfPath) return '';
const opfDoc = new DOMParser().parseFromString(await zip.file(opfPath).async('text'), 'application/xml');
return _extractIsbnFromOpf(opfDoc);
} catch (e) {
return '';
}
}
// Manually triggered (never automatic) — looks up which library shelf/DDC category this
// book falls under via the server-side DNB→Open Library proxy, purely as an organizational
// hint (shown as a badge). See books/views.py:lookup_book_metadata for why this one call is
@ -3625,20 +3654,34 @@ async function assignBookFolder(bookId) {
async function lookupBookMetadata(bookId) {
const book = _lastBookListData.find(b => b.id === bookId);
if (!book) return;
const isbn = (bookMetaCache[bookId] || {}).isbn || book.isbn || '';
const cached = bookMetaCache[bookId] || {};
let isbn = cached.isbn || book.isbn || '';
const isPdf = (cached.type || book.type) === 'pdf';
if (!isbn) {
await customAlert('Keine ISBN in den Metadaten dieses Buchs gefunden (nur bei neueren Uploads automatisch erkannt).');
return;
if (isPdf) {
await customAlert('Keine ISBN gespeichert — bei PDFs wird sie nicht automatisch erkannt.');
return;
}
// Older upload without a stored ISBN — extract it from the book file itself (may take
// a moment for large books, since it downloads/decrypts the full file if not cached).
isbn = await _extractIsbnFromBookFile(bookId);
if (!isbn) {
await customAlert('Keine ISBN in diesem Buch gefunden.');
return;
}
}
try {
const res = await fetch(`/books/metadata-lookup/?isbn=${encodeURIComponent(isbn)}`);
const data = await res.json();
if (!data.label) {
await customAlert('Keine Regal-Kategorie gefunden (weder bei der DNB noch bei Open Library).');
return;
}
await _updateBookMeta(bookId, {shelfTag: data.label});
// Persist the (possibly newly-found) ISBN either way, so a repeat lookup never needs
// to re-download/decrypt the book file again.
await _updateBookMeta(bookId, data.label ? {isbn, shelfTag: data.label} : {isbn});
renderBookList(_lastBookListData);
if (!data.label) {
await customAlert('Keine Regal-Kategorie gefunden (weder bei der DNB noch bei Open Library). Die ISBN wurde für spätere Versuche gespeichert.');
}
} catch (e) {
await customAlert('Metadaten-Abruf fehlgeschlagen: ' + e.message);
}

View file

@ -2,7 +2,7 @@
* diora service worker caches the app shell for offline use.
*/
const CACHE = 'diora-v37';
const CACHE = 'diora-v38';
const PODCAST_CACHE = 'diora-podcast-v1';
const SHELL = [
'/static/css/app.css',