mirror of
https://github.com/Extroonie/extroonie.com.git
synced 2026-10-12 17:48:06 -07:00
81 lines
2.9 KiB
JavaScript
81 lines
2.9 KiB
JavaScript
(() => {
|
|
const references = document.querySelectorAll('.footnote-ref');
|
|
if (!references.length) return;
|
|
const preview = document.createElement('span');
|
|
preview.id = 'footnote-preview';
|
|
preview.className = 'footnote-preview';
|
|
preview.setAttribute('role', 'note');
|
|
preview.hidden = true;
|
|
document.body.append(preview);
|
|
let active;
|
|
let timer;
|
|
function close() {
|
|
clearTimeout(timer);
|
|
if (active && preview.contains(document.activeElement)) active.focus({ preventScroll: true });
|
|
active?.removeAttribute('aria-controls');
|
|
active = null;
|
|
preview.hidden = true;
|
|
}
|
|
function scheduleClose() {
|
|
clearTimeout(timer);
|
|
timer = setTimeout(() => {
|
|
if (!preview.contains(document.activeElement) && document.activeElement !== active) close();
|
|
}, 180);
|
|
}
|
|
function show(reference) {
|
|
if (active === reference) {
|
|
clearTimeout(timer);
|
|
return;
|
|
}
|
|
close();
|
|
const note = document.getElementById(decodeURIComponent(reference.hash.slice(1)));
|
|
if (!note) return;
|
|
const content = note.cloneNode(true);
|
|
content.querySelectorAll('.footnote-backref').forEach((link) => link.remove());
|
|
content.querySelectorAll('[id]').forEach((element) => element.removeAttribute('id'));
|
|
|
|
preview.replaceChildren(...content.childNodes);
|
|
active = reference;
|
|
reference.setAttribute('aria-controls', preview.id);
|
|
preview.setAttribute('aria-label', `Footnote ${reference.textContent}`);
|
|
reference.closest('sup').after(preview);
|
|
preview.hidden = false;
|
|
const rect = reference.getBoundingClientRect();
|
|
const gap = 8;
|
|
const inset = 16;
|
|
const left = Math.max(inset, Math.min(rect.left, innerWidth - preview.offsetWidth - inset));
|
|
const below = rect.bottom + gap;
|
|
const top = below + preview.offsetHeight <= innerHeight - inset ? below : rect.top - preview.offsetHeight - gap;
|
|
preview.style.left = `${left}px`;
|
|
preview.style.top = `${Math.max(inset, top)}px`;
|
|
}
|
|
for (const reference of references) {
|
|
reference.closest('sup').tabIndex = -1;
|
|
const note = document.getElementById(decodeURIComponent(reference.hash.slice(1)));
|
|
if (note) note.tabIndex = -1;
|
|
reference.addEventListener('pointerenter', (event) => {
|
|
if (event.pointerType === 'mouse') show(reference);
|
|
});
|
|
reference.addEventListener('pointerleave', scheduleClose);
|
|
reference.addEventListener('focus', () => {
|
|
if (reference.matches(':focus-visible')) show(reference);
|
|
});
|
|
reference.addEventListener('blur', scheduleClose);
|
|
reference.addEventListener('click', close);
|
|
}
|
|
preview.addEventListener('pointerenter', () => clearTimeout(timer));
|
|
preview.addEventListener('pointerleave', scheduleClose);
|
|
preview.addEventListener('focusin', () => clearTimeout(timer));
|
|
preview.addEventListener('focusout', scheduleClose);
|
|
document.addEventListener('keydown', (event) => {
|
|
if (event.key === 'Escape') close();
|
|
});
|
|
window.addEventListener('resize', close);
|
|
window.addEventListener(
|
|
'scroll',
|
|
() => {
|
|
if (!preview.contains(document.activeElement)) close();
|
|
},
|
|
{ passive: true },
|
|
);
|
|
})();
|