levineuwirth.org/static/js/citations.js

87 lines
2.8 KiB
JavaScript

/* citations.js — hover tooltip for inline citation markers.
On hover of a .cite-marker, reads the matching bibliography entry from
the DOM and shows it in a floating tooltip. On click, follows the href
to jump to the bibliography section. Phase 3 popups.js can supersede this. */
(function () {
'use strict';
let activeTooltip = null;
let hideTimer = null;
function makeTooltip(html) {
const el = document.createElement('div');
el.className = 'cite-tooltip';
el.innerHTML = html;
el.addEventListener('mouseenter', () => clearTimeout(hideTimer));
el.addEventListener('mouseleave', scheduleHide);
return el;
}
function positionTooltip(tooltip, anchor) {
document.body.appendChild(tooltip);
const aRect = anchor.getBoundingClientRect();
const tRect = tooltip.getBoundingClientRect();
let left = aRect.left + window.scrollX;
let top = aRect.top + window.scrollY - tRect.height - 10;
// Keep horizontally within viewport with margin
const maxLeft = window.innerWidth - tRect.width - 12;
left = Math.max(8, Math.min(left, maxLeft));
// Flip below anchor if not enough room above
if (top < window.scrollY + 8) {
top = aRect.bottom + window.scrollY + 10;
}
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
}
function scheduleHide() {
hideTimer = setTimeout(() => {
if (activeTooltip) {
activeTooltip.remove();
activeTooltip = null;
}
}, 180);
}
function getRefHtml(refEl) {
// Strip the [N] number span, return the remaining innerHTML
const clone = refEl.cloneNode(true);
const num = clone.querySelector('.ref-num');
if (num) num.remove();
return clone.innerHTML.trim();
}
function init() {
document.querySelectorAll('.cite-marker').forEach(marker => {
const link = marker.querySelector('a.cite-link');
if (!link) return;
const href = link.getAttribute('href');
if (!href || !href.startsWith('#')) return;
const refEl = document.getElementById(href.slice(1));
if (!refEl) return;
marker.addEventListener('mouseenter', () => {
clearTimeout(hideTimer);
if (activeTooltip) { activeTooltip.remove(); }
activeTooltip = makeTooltip(getRefHtml(refEl));
positionTooltip(activeTooltip, marker);
});
marker.addEventListener('mouseleave', scheduleHide);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();