mirror of
https://github.com/PR0M3TH3AN/Artstr-Studio.git
synced 2026-11-06 21:50:40 +00:00
book(I4): footnote refs + chapter-end definitions (v1)
Inline \`[^N]\` markers render as Unicode-superscript numerals
(⁰¹²³…) and \`[^N]: definition\` lines collect into a "Footnotes"
section appended to the chapter (after the body, separated by a
horizontal rule).
How it works:
- _processBookFootnotes(md):
1. Sweeps definitions via /^[ \t]*\[\^id\]:[ \t]+(.+)$/gm,
stores them in a Map keyed by raw id, strips the line from
the body.
2. Renumbers: each defined id gets a 1..N number based on first
encounter of its \`[^id]\` ref in the body. Refs without a
matching def pass through unchanged.
3. Replaces every resolved ref with _bookSuperscript(num) so
¹² renders as actual superscript glyphs.
4. Appends a "### Footnotes" section with one bullet per
definition in canonical (first-ref) order.
Substitution sites:
- paginateChapter: runs after the {index} expansion + marker
stripping, before xref resolution. Only fires when \`[\^\` is
present in the source so most chapters pay zero cost.
- markdownToSafeHtml: same flow for the live preview pane.
Known v1 limitation (deferred to I4.2):
- Definitions render as a single end-of-chapter section rather than
per-page in a footnote-frame layer on the master. Per-page
footnote placement requires:
* A new footnote-frame layer kind on masters (parallel to
text-frame).
* Page-aware definition collection in the pagination engine.
* Page-break logic that keeps a ref + its def on the same page.
Out of scope for this slice; the chapter-end section is the
pragmatic v1.
Phase I scope: I1 ✅ I2.1 ✅ I2.2 ✅ I3 ✅ I4 ✅ I5 ✅ I6 ✅.
All spec'd Phase I features ship in v1 form.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
5e3e7d1d7b
commit
0651a3e4fc
1 file changed
+55
@@ -9955,6 +9955,53 @@
|
||||
});
|
||||
return map;
|
||||
}
|
||||
// Footnote support (I4 v1). Inline `[^N]` becomes a superscripted
|
||||
// marker (Unicode digits ⁰¹²³…). `[^N]: definition` lines collect
|
||||
// into a chapter-end footnotes section. Full per-page
|
||||
// footnote-frame layout (frame layer on masters with page-aware
|
||||
// definition placement) lands in I4.2.
|
||||
const _BOOK_FN_DEF_RX = /^[ \t]*\[\^([^\]]+)\]:[ \t]+(.+)$/gm;
|
||||
const _BOOK_FN_REF_RX = /\[\^([^\]]+)\]/g;
|
||||
const _BOOK_SUP_DIGITS = ['⁰','¹','²','³','⁴','⁵','⁶','⁷','⁸','⁹'];
|
||||
function _bookSuperscript(s) {
|
||||
return String(s).replace(/[0-9]/g, c => _BOOK_SUP_DIGITS[Number(c)] || c);
|
||||
}
|
||||
function _processBookFootnotes(md) {
|
||||
// Collect definitions (each on its own line). Strip them from
|
||||
// the main body so they don't render inline.
|
||||
const defs = new Map(); // id (string) → text
|
||||
const stripped = String(md || '').replace(_BOOK_FN_DEF_RX, (_, id, text) => {
|
||||
defs.set(String(id).trim(), text.trim());
|
||||
return ''; // drop the def line from body
|
||||
});
|
||||
if (!defs.size) {
|
||||
// No definitions — leave any stray `[^N]` refs as-is.
|
||||
return stripped.replace(/\n{3,}/g, '\n\n');
|
||||
}
|
||||
// Renumber: each defined id gets a sequential 1..N number based
|
||||
// on first encounter of its ref in the body. Refs that don't
|
||||
// resolve to a known def pass through unchanged.
|
||||
const order = []; // canonical order [{ id, num }]
|
||||
const numById = new Map();
|
||||
let nextNum = 1;
|
||||
const body = stripped.replace(_BOOK_FN_REF_RX, (whole, rawId) => {
|
||||
const id = String(rawId).trim();
|
||||
if (!defs.has(id)) return whole;
|
||||
if (!numById.has(id)) {
|
||||
numById.set(id, nextNum);
|
||||
order.push({ id, num: nextNum });
|
||||
nextNum++;
|
||||
}
|
||||
return _bookSuperscript(numById.get(id));
|
||||
});
|
||||
// Footnotes section appended to the body.
|
||||
const lines = ['', '---', '', '### Footnotes', ''];
|
||||
for (const { id, num } of order) {
|
||||
lines.push(`${_bookSuperscript(num)} ${defs.get(id)}`);
|
||||
}
|
||||
return body.replace(/\n{3,}/g, '\n\n') + '\n' + lines.join('\n');
|
||||
}
|
||||
|
||||
// Index marker support (I5). `^[term]` inline markers register
|
||||
// their term in a book-wide index keyed to the containing
|
||||
// chapter's first page. `{index}` expands at render time into
|
||||
@@ -10064,6 +10111,13 @@
|
||||
// substitution so a marker inside a link text doesn't confuse
|
||||
// the parser.
|
||||
md = _stripBookIndexMarkers(md);
|
||||
// Footnote refs (I4). Replace `[^N]` with a Unicode superscript
|
||||
// and append a "Footnotes" section if any `[^N]: definition`
|
||||
// lines were defined in the chapter source. Refs without a
|
||||
// matching def pass through unchanged.
|
||||
if (/\[\^/.test(md)) {
|
||||
md = _processBookFootnotes(md);
|
||||
}
|
||||
// Cross-reference substitution (I6). Markdown links pointing
|
||||
// at an in-book anchor (`](#chapter-3)` etc) get their link
|
||||
// text augmented with the destination's page number — slug
|
||||
@@ -11877,6 +11931,7 @@
|
||||
md = md.replace(/^[ \t]*\{index\}[ \t]*$/gm, idxMd);
|
||||
}
|
||||
md = _stripBookIndexMarkers(md);
|
||||
if (/\[\^/.test(md)) md = _processBookFootnotes(md);
|
||||
// Cross-reference substitution in the live preview too.
|
||||
if (/\]\(#[^)]+\)/.test(md)) {
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user