Bücher: Titel in "Zuletzt gelesen"/Ordner-Kacheln unsichtbar (Button-Farbvererbung); Regal-Lookup jetzt auch für Altbestand (SW v38)
<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:
parent
3e301b6f61
commit
e6b3ee620e
3 changed files with 56 additions and 9 deletions
|
|
@ -1684,8 +1684,10 @@ body.dnd-mode .timer-display {
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
background: none;
|
background: none;
|
||||||
|
color: var(--fg);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
}
|
}
|
||||||
.book-recent-item-title {
|
.book-recent-item-title {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|
@ -1717,8 +1719,10 @@ body.dnd-mode .timer-display {
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
background: none;
|
background: none;
|
||||||
|
color: var(--fg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
font: inherit;
|
||||||
}
|
}
|
||||||
.book-folder-tile-icon {
|
.book-folder-tile-icon {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
|
|
|
||||||
|
|
@ -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
|
// 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
|
// 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
|
// 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) {
|
async function lookupBookMetadata(bookId) {
|
||||||
const book = _lastBookListData.find(b => b.id === bookId);
|
const book = _lastBookListData.find(b => b.id === bookId);
|
||||||
if (!book) return;
|
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) {
|
if (!isbn) {
|
||||||
await customAlert('Keine ISBN in den Metadaten dieses Buchs gefunden (nur bei neueren Uploads automatisch erkannt).');
|
if (isPdf) {
|
||||||
|
await customAlert('Keine ISBN gespeichert — bei PDFs wird sie nicht automatisch erkannt.');
|
||||||
return;
|
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 {
|
try {
|
||||||
const res = await fetch(`/books/metadata-lookup/?isbn=${encodeURIComponent(isbn)}`);
|
const res = await fetch(`/books/metadata-lookup/?isbn=${encodeURIComponent(isbn)}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!data.label) {
|
// Persist the (possibly newly-found) ISBN either way, so a repeat lookup never needs
|
||||||
await customAlert('Keine Regal-Kategorie gefunden (weder bei der DNB noch bei Open Library).');
|
// to re-download/decrypt the book file again.
|
||||||
return;
|
await _updateBookMeta(bookId, data.label ? {isbn, shelfTag: data.label} : {isbn});
|
||||||
}
|
|
||||||
await _updateBookMeta(bookId, {shelfTag: data.label});
|
|
||||||
renderBookList(_lastBookListData);
|
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) {
|
} catch (e) {
|
||||||
await customAlert('Metadaten-Abruf fehlgeschlagen: ' + e.message);
|
await customAlert('Metadaten-Abruf fehlgeschlagen: ' + e.message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
* diora service worker — caches the app shell for offline use.
|
* 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 PODCAST_CACHE = 'diora-podcast-v1';
|
||||||
const SHELL = [
|
const SHELL = [
|
||||||
'/static/css/app.css',
|
'/static/css/app.css',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue