// Leaflet's own clipboard HTML marks a footnote as (schema toDOM for in-block copies, RenderYJSFragment for // block copies). Pasting that span as-is would parse into a footnote node // aliasing the source block's entity — the pasted ref renders as a working // footnote, but deleting it deletes the entity out from under the original // (the data-loss bug this file exists to prevent). Before blocks are built, // normalize every such ref to the shape external markdown footnotes take // (only data-footnote-def), from which the builder mints a fresh entity. // Refs that carry no definition (older copies, in-block copies) get one from // the local replicache via getDefHTML; refs whose entity can't be found // (cross-leaflet paste) are dropped rather than left aliasing. const INTERNAL_REF_SELECTOR = "span.footnote-ref[data-footnote-id]"; export async function resolveCopiedFootnoteRefs( children: HTMLElement[], getDefHTML: (footnoteEntityID: string) => Promise, ) { const refs = children.flatMap((child) => { const within = Array.from( child.querySelectorAll(INTERNAL_REF_SELECTOR), ) as HTMLElement[]; // Flattening can leave an inline run's wrapper span as a child itself. if (child.matches?.(INTERNAL_REF_SELECTOR)) within.push(child); return within; }); if (refs.length === 0) return; const defs = new Map(); for (const ref of refs) { const id = ref.getAttribute("data-footnote-id")!; if (ref.hasAttribute("data-footnote-def")) continue; if (!defs.has(id)) defs.set(id, await getDefHTML(id)); } for (const ref of refs) { const id = ref.getAttribute("data-footnote-id")!; ref.removeAttribute("data-footnote-id"); ref.classList.remove("footnote-ref"); if (ref.classList.length === 0) ref.removeAttribute("class"); if (ref.hasAttribute("data-footnote-def")) continue; const def = defs.get(id); if (def == null) ref.remove(); else ref.setAttribute("data-footnote-def", def); } }