From 76068f6ed8aabaf665c15c08037460579d7e208f Mon Sep 17 00:00:00 2001 From: marwin Date: Tue, 4 Aug 2026 18:55:48 +0200 Subject: [PATCH] =?UTF-8?q?Reader:=20Fu=C3=9Fnoten-Popover=20erkennt=20jet?= =?UTF-8?q?zt=20mehr=20EPUB-Konventionen=20(SW=20v31)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Erkennung von Fußnoten-Links war auf class="footnote" + gleiche-Datei- Anker (href startsWith '#') hartkodiert. Andere Editionen markieren Fußnoten anders — z.B. die e-artnow-Ausgabe von Marx' "Das Kapital" (zeno.org-Konvertierung) nutzt gar keine Klasse und verlinkt mit einem als Kind (statt Vorfahre) des auf eine ANDERE Kapiteldatei ("../Text/Karl0045.html#F537") — dadurch griff weder die Klassen- noch die Hash-Prüfung, das Popover blieb tot. _looksLikeFootnoteLink() erkennt jetzt zusätzlich epub:type="noteref" (EPUB3- Standard), jede Klasse mit "note"/"footnote"/"fn" im Namen, und als Fallback jeden Link mit Fragment-Anker, der irgendwo mit einem verschachtelt ist (in beide Richtungen) — der in EPUBs praktisch universelle Marker für Fußnotenziffern. Die Fragment-Extraktion nimmt jetzt alles nach dem letzten '#' statt nur bei href.startsWith('#'), da das Buch als eine zusammenhängende DOM gerendert wird und getElementById dateiübergreifend funktioniert. Verifiziert per Playwright: Marx-Edition zeigt jetzt beim Hover 1326 erkannte Fußnoten-Links mit korrektem Popover-Inhalt; Žižeks "Weniger als nichts" (vorher funktionierend, class="footnote"-basiert) weiterhin unverändert funktionsfähig — keine Regression. Co-Authored-By: Claude Sonnet 5 --- static/js/app.js | 24 ++++++++++++++++++++++-- static/js/sw.js | 2 +- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/static/js/app.js b/static/js/app.js index 97c1309..1a1b1e1 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -2725,6 +2725,22 @@ function _injectImageBlobs(html, chapterDir, imageMap) { return html; } +// EPUBs mark footnote/endnote reference links very inconsistently across +// editions/tools — some use class="footnote" (same-file '#frag' href), some +// use the EPUB3 epub:type="noteref" attribute, and some (e.g. older +// Calibre/zeno.org conversions) use neither and just wrap a plain link to +// another chapter file's fragment in a — in either direction +// ( or ). Recognize all of these +// rather than only the single hardcoded class name. +function _looksLikeFootnoteLink(el, href) { + if (!href.includes('#')) return false; + const cls = (el.getAttribute('class') || '').toLowerCase(); + if (/\bnote|\bfootnote|\bfn\b/.test(cls)) return true; + const epubType = (el.getAttribute('epub:type') || '').toLowerCase(); + if (epubType.includes('noteref')) return true; + return !!(el.closest('sup') || el.querySelector('sup')); +} + function sanitizeEpubHtml(html) { const doc = new DOMParser().parseFromString(html, 'text/html'); @@ -2745,8 +2761,12 @@ function sanitizeEpubHtml(html) { if (href.startsWith('http://') || href.startsWith('https://')) { el.setAttribute('target', '_blank'); el.setAttribute('rel', 'noopener noreferrer'); - } else if (el.classList.contains('footnote') && href.startsWith('#')) { - el.dataset.footnoteRef = href.slice(1); + } else if (_looksLikeFootnoteLink(el, href)) { + // The whole book is one concatenated DOM, so a fragment id resolves + // via getElementById regardless of which chapter file it originally + // pointed into — strip any "../Text/other.html" path prefix and keep + // just the id after the last '#'. + el.dataset.footnoteRef = href.slice(href.indexOf('#') + 1); el.removeAttribute('href'); el.style.cursor = 'pointer'; } else { diff --git a/static/js/sw.js b/static/js/sw.js index 4bcf391..41ab2c4 100644 --- a/static/js/sw.js +++ b/static/js/sw.js @@ -2,7 +2,7 @@ * diora service worker — caches the app shell for offline use. */ -const CACHE = 'diora-v30'; +const CACHE = 'diora-v31'; const PODCAST_CACHE = 'diora-podcast-v1'; const SHELL = [ '/static/css/app.css',