51 lines
1.7 KiB
JavaScript
Executable File
51 lines
1.7 KiB
JavaScript
Executable File
/* Event listener for footnotes and sidenotes*/
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
document.querySelectorAll('a.footref[href^="#fn"]').forEach(ref => {
|
|
const sup = ref.closest("sup") || ref;
|
|
// idempotent: don't insert twice
|
|
if (sup.nextElementSibling && sup.nextElementSibling.classList?.contains("footnote-sidenote")) return;
|
|
|
|
const targetId = ref.getAttribute("href").replace(/^#/, ""); // works for fn.2 or fn2
|
|
|
|
const anchor = document.getElementById(targetId);
|
|
if (!anchor) return;
|
|
|
|
const footdef = anchor.closest(".footdef") || anchor.parentElement;
|
|
if (!footdef) return;
|
|
|
|
// 1) Prefer leaf paragraphs to avoid div+p duplication
|
|
let paras = footdef.querySelectorAll("p.footpara");
|
|
if (!paras.length) {
|
|
// fallback: any .footpara elements that don't contain another .footpara
|
|
paras = footdef.querySelectorAll(".footpara:not(:has(.footpara))");
|
|
}
|
|
|
|
// 2) Build HTML, de-duplicating by text content
|
|
let parts = [];
|
|
if (paras.length) {
|
|
const seen = new Set();
|
|
parts = Array.from(paras).map(p => {
|
|
const txt = p.textContent.trim().replace(/\s+/g, " ");
|
|
if (seen.has(txt)) return "";
|
|
seen.add(txt);
|
|
return p.innerHTML.trim();
|
|
}).filter(Boolean);
|
|
}
|
|
|
|
// 3) Fallback: clean full block if no paras found
|
|
if (!parts.length) {
|
|
const clone = footdef.cloneNode(true);
|
|
clone.querySelectorAll("sup.footnum, a[role='doc-backlink']").forEach(n => n.remove());
|
|
parts = [clone.innerHTML.trim()];
|
|
}
|
|
|
|
// 4) Insert the sidenote
|
|
const sn = document.createElement("span");
|
|
sn.className = "sidenote footnote-sidenote";
|
|
sn.setAttribute("data-fn", (ref.textContent || "").trim());
|
|
sn.innerHTML = parts.join(" ");
|
|
|
|
sup.insertAdjacentElement("afterend", sn);
|
|
});
|
|
});
|