diora-web/static/js/app/78-footnotes.js

77 lines
3.2 KiB
JavaScript
Raw Permalink Normal View History

'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;
}