MCPcopy
hub / github.com/marktext/marktext / transformFootnotes

Function transformFootnotes

packages/muya/src/state/renderToStaticHTML.ts:92–151  ·  view source on GitHub ↗
(html: string)

Source from the content-addressed store, hash-verified

90const CODE_PLACEHOLDER_RESTORE_RE = /_MUYA_FN_GUARD_(\d+)_/g;
91
92function transformFootnotes(html: string): string {
93 // 1. Lift every footnote-block out of the body, remembering the rendered
94 // definition html keyed by identifier. The body of the def is the inner
95 // html marked already produced — paragraphs, lists, code, etc.
96 const definitions = new Map<string, string>();
97 let body = html.replace(FOOTNOTE_DEF_RE, (_, id: string, inner: string) => {
98 // First definition wins for duplicate identifiers — matches the way
99 // pandoc / GFM linkrefs treat repeated labels and what the plan asks
100 // for (Section 十, risk #2).
101 if (!definitions.has(id))
102 definitions.set(id, inner);
103 return '';
104 });
105
106 if (definitions.size === 0)
107 return html;
108
109 // 2. Stash code spans / blocks so step 3 only scans live prose.
110 const codeSlots: string[] = [];
111 body = body.replace(CODE_GUARD_RE, (m) => {
112 codeSlots.push(m);
113 return `${CODE_PLACEHOLDER_PREFIX}${codeSlots.length - 1}_`;
114 });
115
116 // 3. Find inline `[^id]` references in source order. Numbering follows
117 // inline order (pandoc / GFM convention), not the order definitions
118 // appear in source. Orphan refs (no matching def) stay as plain text;
119 // repeats reuse the first-seen number.
120 const refNumber = new Map<string, number>();
121 let nextN = 1;
122 body = body.replace(FOOTNOTE_REF_RE, (match, id: string) => {
123 if (!definitions.has(id))
124 return match;
125 if (!refNumber.has(id))
126 refNumber.set(id, nextN++);
127 const n = refNumber.get(id)!;
128 return `<sup class="footnote-ref"><a href="#fn-${n}" id="fnref-${n}">${n}</a></sup>`;
129 });
130
131 // 4. Restore the protected code regions.
132 body = body.replace(CODE_PLACEHOLDER_RESTORE_RE, (_, i) => codeSlots[Number(i)]);
133
134 if (refNumber.size === 0)
135 return body;
136
137 // 5. Build the footnotes section in numeric order. Orphan definitions
138 // (defined but never referenced inline) are dropped — same as the
139 // parser-extension behaviour marktext shipped.
140 const orderedRefs = Array.from(refNumber.entries()).sort(
141 (a, b) => a[1] - b[1],
142 );
143 const items: string[] = [];
144 for (const [id, n] of orderedRefs) {
145 const inner = definitions.get(id) ?? '';
146 items.push(`<li id="fn-${n}">${appendBackref(inner, n)}</li>`);
147 }
148
149 const section = `\n<section class="footnotes">\n<ol>\n${items.join('\n')}\n</ol>\n</section>\n`;

Callers 1

renderToStaticHTMLFunction · 0.85

Calls 6

appendBackrefFunction · 0.85
pushMethod · 0.80
getMethod · 0.80
joinMethod · 0.80
hasMethod · 0.65
replaceMethod · 0.45

Tested by

no test coverage detected