32 klassische Skripte in der alten Reihenfolge (01-core … 99-init), ohne Änderung am Code darin: geschnitten nur zwischen Anweisungen, je Datei 'use strict' wie vorher für die ganze Datei. Geprüft (acorn): kein Code, der beim Laden läuft, braucht einen Namen aus einer späteren Datei. Globale Funktionen und let/const bleiben global, die App-Schichten (native.js, native-eink.js, …) umhüllen sie wie bisher. Template und sw.js-SHELL listen die Dateien; diora-android build_web nimmt static/js rekursiv. Getestet: 14 Browser-Tests (e2e), Desktop-App (Buch, Lesestand, Leiste im Reader, Hell/Dunkel). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
76 lines
3.2 KiB
JavaScript
76 lines
3.2 KiB
JavaScript
'use strict';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Footnote popover
|
|
// ---------------------------------------------------------------------------
|
|
|
|
let currentFootnotePopover = null;
|
|
let _footnotePopoverTimer = null;
|
|
|
|
function showFootnotePopover(link) {
|
|
dismissFootnotePopover();
|
|
const targetEl = document.getElementById(link.dataset.footnoteRef);
|
|
if (!targetEl) return;
|
|
// The note's paragraph, also when the id sits on an inline element in it
|
|
// (<p><span><a id="F400" …>143</a></span> Note…</p>).
|
|
const inline = /^(A|SPAN|SUP|SUB|B|I|EM|STRONG|SMALL)$/.test(targetEl.tagName);
|
|
const container = targetEl.closest('.footnote') ||
|
|
(inline && targetEl.closest('p, li, dd, blockquote')) || targetEl.parentElement;
|
|
if (!container) return;
|
|
|
|
const clone = container.cloneNode(true);
|
|
clone.querySelector('sup')?.remove();
|
|
clone.querySelectorAll('.epub-pagenum').forEach(e => e.remove());
|
|
// The note's own number at its start, a link back to the text ("1 Note…").
|
|
const first = Array.from(clone.querySelectorAll('a')).find(a => a.textContent.trim());
|
|
const num = first ? first.textContent.trim() : '';
|
|
if (/^[\[(]?\d{1,4}[\])]?\.?$/.test(num) && clone.textContent.trim().startsWith(num)) first.remove();
|
|
// Only the number: the note (a poem, a long quote) runs on in the next
|
|
// paragraphs, up to the next note's anchor.
|
|
if (!clone.textContent.trim()) {
|
|
let len = 0;
|
|
for (let next = container.nextElementSibling; next && len < 6000; next = next.nextElementSibling) {
|
|
if (/^H[1-6]$/.test(next.tagName) || next.querySelector('[id]')) break;
|
|
if (!next.textContent.trim()) continue;
|
|
clone.appendChild(next.cloneNode(true));
|
|
len += next.textContent.length;
|
|
}
|
|
}
|
|
|
|
const popover = document.createElement('div');
|
|
popover.className = 'footnote-popover';
|
|
const _overlay = document.getElementById('reader-overlay');
|
|
['reader-theme-sepia', 'reader-theme-bright'].forEach(cls => {
|
|
if (_overlay?.classList.contains(cls)) popover.classList.add(cls);
|
|
});
|
|
popover.innerHTML = clone.innerHTML;
|
|
|
|
const closeBtn = document.createElement('button');
|
|
closeBtn.className = 'footnote-popover-close';
|
|
closeBtn.textContent = '✕';
|
|
closeBtn.addEventListener('click', dismissFootnotePopover);
|
|
popover.appendChild(closeBtn);
|
|
|
|
const rect = link.getBoundingClientRect();
|
|
popover.style.top = (rect.bottom + 8) + 'px';
|
|
popover.style.left = Math.max(8, rect.left - 8) + 'px';
|
|
|
|
popover.addEventListener('mouseenter', () => clearTimeout(_footnotePopoverTimer));
|
|
popover.addEventListener('mouseleave', () => {
|
|
_footnotePopoverTimer = setTimeout(dismissFootnotePopover, 250);
|
|
});
|
|
|
|
document.body.appendChild(popover);
|
|
currentFootnotePopover = popover;
|
|
currentFootnotePopover._refLink = link;
|
|
|
|
const pr = popover.getBoundingClientRect();
|
|
if (pr.right > window.innerWidth - 8) popover.style.left = (window.innerWidth - pr.width - 8) + 'px';
|
|
if (pr.bottom > window.innerHeight - 8) popover.style.top = (rect.top - pr.height - 8) + 'px';
|
|
}
|
|
|
|
function dismissFootnotePopover() {
|
|
clearTimeout(_footnotePopoverTimer);
|
|
currentFootnotePopover?.remove();
|
|
currentFootnotePopover = null;
|
|
}
|