@heroiclands/package-build 25.3.0 → 25.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +1 -1
- package/assets/styles/content-base.css +32 -0
- package/docs/api.md +30 -6
- package/docs/authoring/links-and-markup.md +136 -53
- package/docs/commands.md +2 -2
- package/docs/content-format.md +14 -6
- package/docs/diagnostics.md +12 -11
- package/docs/guides/writing-in-invented-scripts.md +62 -35
- package/docs/reference/format-details.md +6 -6
- package/docs/reference/note-types.md +15 -15
- package/docs/reference/theme-pages.md +26 -18
- package/engine/anchors.mjs +14 -0
- package/engine/code-fences.mjs +15 -6
- package/engine/content-blocks.mjs +4 -2
- package/engine/content-figures.mjs +19 -5
- package/engine/content-footnotes.mjs +60 -1
- package/engine/content-index.mjs +1 -1
- package/engine/content-render.mjs +59 -43
- package/engine/content-scripts.mjs +204 -42
- package/engine/content-spans.mjs +73 -5
- package/engine/content-verse.mjs +320 -192
- package/engine/first-use.mjs +43 -12
- package/engine/heading-attributes.mjs +2 -1
- package/engine/journals.mjs +8 -1
- package/engine/markdown-blocks.mjs +3 -2
- package/engine/markup-validation.mjs +17 -0
- package/engine/name-markup.mjs +1 -0
- package/engine/note-vocabulary.mjs +2 -2
- package/engine/pdf-render.mjs +34 -23
- package/engine/prose-lint.mjs +14 -2
- package/engine/run-rows.mjs +1 -1
- package/engine/sql-tables.mjs +1 -1
- package/engine/verse-format.mjs +77 -0
- package/engine/verse-metre.mjs +18 -9
- package/engine/verse-regions.mjs +181 -0
- package/engine/wikilink-pattern.mjs +31 -0
- package/engine/wikilink-syntax.mjs +2 -16
- package/package.json +1 -1
- package/site-theme/static/css/style.css +24 -0
- package/types/engine/code-fences.d.mts +1 -3
- package/types/engine/content-footnotes.d.mts +23 -0
- package/types/engine/content-scripts.d.mts +4 -3
- package/types/engine/content-spans.d.mts +3 -1
- package/types/engine/content-verse.d.mts +69 -74
- package/types/engine/name-markup.d.mts +5 -0
- package/types/engine/verse-format.d.mts +9 -0
- package/types/engine/verse-metre.d.mts +4 -4
- package/types/engine/verse-regions.d.mts +78 -0
- package/types/engine/wikilink-pattern.d.mts +17 -0
- package/types/engine/wikilink-syntax.d.mts +1 -41
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* 2. **Captioned items** — each figure's wrapper and label. Its number, kind
|
|
48
48
|
* and role come from the figure scan expansion made, so a figure is
|
|
49
49
|
* numbered once; see {@link module:engine/content-figures.renderFigureBlocks}.
|
|
50
|
-
* 3. **Named blocks, verse and scrolls** — `:::` blocks, verse
|
|
50
|
+
* 3. **Named blocks, verse and scrolls** — `:::` blocks, verse divs and
|
|
51
51
|
* `scroll` fences.
|
|
52
52
|
* 4. **Alerts.**
|
|
53
53
|
* 5. **Spans and withheld sections** — written here on the `web` target; the
|
|
@@ -79,7 +79,8 @@ import { imagePlugin } from "./content-images.mjs";
|
|
|
79
79
|
import { renderRuns } from "./content-scripts.mjs";
|
|
80
80
|
import { renderSpans, spanMarkdownPlugin } from "./content-spans.mjs";
|
|
81
81
|
import { cartoucheFigures, renderSymbolSpans } from "./content-symbols.mjs";
|
|
82
|
-
import { inlineVerse,
|
|
82
|
+
import { inlineVerse, parseVerse, renderVerseQuotes } from "./content-verse.mjs";
|
|
83
|
+
import { verseDivPlugin } from "./verse-regions.mjs";
|
|
83
84
|
import { replaceOutsideCode } from "./code-fences.mjs";
|
|
84
85
|
import { dictionaryEntryPlugin } from "./dictionary-layout.mjs";
|
|
85
86
|
import { headingAttributesPlugin, headingIdsPlugin } from "./heading-attributes.mjs";
|
|
@@ -115,48 +116,59 @@ export function highlightCode(code, language) {
|
|
|
115
116
|
return hljs.highlight(code, { language: name, ignoreIllegals: true }).value;
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
/**
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
119
|
+
/** Parse logical verse lines in the note's shared inline environment. */
|
|
120
|
+
function parseVerseContent(token, md, env, region) {
|
|
121
|
+
const body = token.type === "verse_inline" ? token.meta.text : token.content;
|
|
122
|
+
const inline = token.type === "verse_inline";
|
|
123
|
+
const paired = inline ? undefined : env.verses?.get(body);
|
|
124
|
+
const footnotes = new Set(Object.keys(env.footnotes?.refs ?? {}).map((key) => key.slice(1)));
|
|
125
|
+
const model = paired?.model ?? region.model ?? parseVerse(body, { inline, footnotes });
|
|
126
|
+
token.meta ??= {};
|
|
127
|
+
token.meta.verse = region;
|
|
128
|
+
token.meta.versePaired = paired;
|
|
129
|
+
const parse = (text) => {
|
|
130
|
+
const children = [];
|
|
131
|
+
md.inline.parse(inlineVerse(text), md, env, children);
|
|
132
|
+
return children;
|
|
133
|
+
};
|
|
134
|
+
token.meta.verseNotes = (model.notes ?? []).map((line) => ({
|
|
135
|
+
children: parse(line.romanText ?? line.text),
|
|
136
|
+
}));
|
|
137
|
+
token.meta.verseGroups = model.groups.map((group) =>
|
|
138
|
+
group.cantoBreak ?
|
|
139
|
+
{
|
|
140
|
+
...group,
|
|
141
|
+
children: group.text ? parse(group.text) : [],
|
|
142
|
+
titleChildren: group.title ? parse(group.title) : [],
|
|
143
|
+
}
|
|
144
|
+
: {
|
|
145
|
+
...group,
|
|
146
|
+
lines: group.lines.map((line) => ({
|
|
147
|
+
...line,
|
|
148
|
+
children: parse(line.text),
|
|
149
|
+
...(paired ? { romanChildren: parse(line.romanText ?? line.text) } : {}),
|
|
150
|
+
})),
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** An opaque quoted verse is parsed once, before footnote collection. */
|
|
156
|
+
function quotedVerse(token, state) {
|
|
157
|
+
const classes = (token.attrGet("class") ?? "")
|
|
158
|
+
.split(/\s+/)
|
|
159
|
+
.filter((name) => name && name !== "verse");
|
|
160
|
+
const attributes = Object.fromEntries(
|
|
161
|
+
(token.attrs ?? []).filter(([key]) => !["id", "class"].includes(key)),
|
|
162
|
+
);
|
|
163
|
+
parseVerseContent(token, state.md, state.env, { id: token.attrGet("id"), classes, attributes });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** A verse div's raw body receives inline parsing without block interpretation. */
|
|
128
167
|
function verseInlinePlugin(md) {
|
|
129
168
|
md.core.ruler.after("inline", "verse_inline", (state) => {
|
|
130
169
|
for (const token of state.tokens) {
|
|
131
|
-
if (token.type !== "
|
|
132
|
-
|
|
133
|
-
token.meta ??= {};
|
|
134
|
-
const source = `${token.markup}${token.info}\n${token.content}${token.markup}`;
|
|
135
|
-
const poem = scanVerse(source, { checkWorks: false }).blocks[0];
|
|
136
|
-
if (!poem) continue;
|
|
137
|
-
const paired = state.env.verses?.get(token.content.replace(/\n$/, ""));
|
|
138
|
-
const model = paired?.model ?? poem.model;
|
|
139
|
-
token.meta.verse = poem;
|
|
140
|
-
token.meta.versePaired = paired;
|
|
141
|
-
const parse = (text) => {
|
|
142
|
-
const children = [];
|
|
143
|
-
state.md.inline.parse(inlineVerse(text), state.md, state.env, children);
|
|
144
|
-
return children;
|
|
145
|
-
};
|
|
146
|
-
token.meta.verseGroups = model.groups.map((group) =>
|
|
147
|
-
group.cantoBreak ?
|
|
148
|
-
{ ...group, children: group.text ? parse(group.text) : [] }
|
|
149
|
-
: {
|
|
150
|
-
...group,
|
|
151
|
-
lines: group.lines.map((line) => ({
|
|
152
|
-
...line,
|
|
153
|
-
children: parse(line.text),
|
|
154
|
-
...(paired ?
|
|
155
|
-
{ romanChildren: parse(line.romanText ?? line.text) }
|
|
156
|
-
: {}),
|
|
157
|
-
})),
|
|
158
|
-
},
|
|
159
|
-
);
|
|
170
|
+
if (token.type !== "verse_div") continue;
|
|
171
|
+
parseVerseContent(token, state.md, state.env, token.meta.verseRegion);
|
|
160
172
|
}
|
|
161
173
|
});
|
|
162
174
|
}
|
|
@@ -384,7 +396,8 @@ export function createRenderer(target, options = {}) {
|
|
|
384
396
|
use("footnotes", footnotePlugin);
|
|
385
397
|
if (book) {
|
|
386
398
|
use("alerts", alertMarkdownPlugin);
|
|
387
|
-
use("spans", spanMarkdownPlugin);
|
|
399
|
+
use("spans", spanMarkdownPlugin, { verseInline: quotedVerse });
|
|
400
|
+
use("verse-div", verseDivPlugin);
|
|
388
401
|
use("verse-inline", verseInlinePlugin);
|
|
389
402
|
use("script-emphasis", scriptEmphasisPlugin);
|
|
390
403
|
use("definition-lists", deflistPlugin);
|
|
@@ -456,6 +469,7 @@ export function createRenderer(target, options = {}) {
|
|
|
456
469
|
renderInline,
|
|
457
470
|
},
|
|
458
471
|
).markdown;
|
|
472
|
+
text = renderVerseQuotes(text);
|
|
459
473
|
if (target === "web") text = renderSpans(text).markdown;
|
|
460
474
|
return target === "web" ? renderWithheldSections(text) : text;
|
|
461
475
|
};
|
|
@@ -476,6 +490,8 @@ export function createRenderer(target, options = {}) {
|
|
|
476
490
|
)
|
|
477
491
|
),
|
|
478
492
|
renderInline:
|
|
479
|
-
book ? unsupported : (
|
|
493
|
+
book ? unsupported : (
|
|
494
|
+
(markdown) => restore(md.renderInline(renderVerseQuotes(String(markdown ?? ""))))
|
|
495
|
+
),
|
|
480
496
|
};
|
|
481
497
|
}
|
|
@@ -45,6 +45,7 @@ import { blockTokens } from "./markdown-blocks.mjs";
|
|
|
45
45
|
import { scanBlocks, BLOCK_CONTAINERS } from "./content-blocks.mjs";
|
|
46
46
|
import { scanVerse } from "./content-verse.mjs";
|
|
47
47
|
import { scanSpans } from "./content-spans.mjs";
|
|
48
|
+
import { footnoteLabels, footnoteReferences } from "./content-footnotes.mjs";
|
|
48
49
|
import { scanScrolls, scrollParagraphs } from "./content-scroll.mjs";
|
|
49
50
|
import { directionsFromRecords } from "./script-direction.mjs";
|
|
50
51
|
import { isLanguageTag } from "./language-tag.mjs";
|
|
@@ -126,7 +127,7 @@ const CONFIG = "package-build.config.yaml";
|
|
|
126
127
|
*/
|
|
127
128
|
|
|
128
129
|
/**
|
|
129
|
-
* One span, div, verse
|
|
130
|
+
* One span, div, verse div or scroll fence carrying run attributes.
|
|
130
131
|
* Values combine explicit choices, class-only inheritance, encoding-document
|
|
131
132
|
* language/font defaults and the Roman default encoding.
|
|
132
133
|
*
|
|
@@ -232,7 +233,7 @@ function withinOneEdit(a, b) {
|
|
|
232
233
|
}
|
|
233
234
|
|
|
234
235
|
/** The word each construct is called in a finding. */
|
|
235
|
-
const CONSTRUCT = Object.freeze({ span: "span", block: "div", verse: "verse
|
|
236
|
+
const CONSTRUCT = Object.freeze({ span: "span", block: "div", verse: "verse div" });
|
|
236
237
|
|
|
237
238
|
/** Line starts of a source, for offsets to positions. */
|
|
238
239
|
function lineStarts(text) {
|
|
@@ -834,9 +835,7 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
834
835
|
);
|
|
835
836
|
const errors = [];
|
|
836
837
|
const warnings = [];
|
|
837
|
-
const footnotes =
|
|
838
|
-
[...text.matchAll(/^ {0,3}\[\^([^\]\s]+)\]:/gm)].map((match) => match[1]),
|
|
839
|
-
);
|
|
838
|
+
const footnotes = footnoteLabels(text);
|
|
840
839
|
/** @param {{line: number, column: number}} at @param {string} message */
|
|
841
840
|
const report = (at, message) => errors.push({ line: at.line, column: at.column, message });
|
|
842
841
|
/** @param {Record<string, string>} values */
|
|
@@ -890,6 +889,7 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
890
889
|
const collect = (offset, body) => {
|
|
891
890
|
const scanned = offset === 0 && syntax ? syntax(body).blocks : scanBlocks(body);
|
|
892
891
|
for (const block of scanned.blocks) {
|
|
892
|
+
if (block.name === "verse" || block.classes?.includes("verse")) continue;
|
|
893
893
|
const first = offset + block.start;
|
|
894
894
|
const last = offset + block.end;
|
|
895
895
|
const opening = lines[first];
|
|
@@ -911,7 +911,7 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
911
911
|
for (const key of Object.keys(keys))
|
|
912
912
|
report(
|
|
913
913
|
at(key, "key"),
|
|
914
|
-
`\`${key}=\` is read on a span, a div or a verse
|
|
914
|
+
`\`${key}=\` is read on a span, a div or a verse div, not on a ${block.name} block`,
|
|
915
915
|
);
|
|
916
916
|
} else {
|
|
917
917
|
divs.set(first, block);
|
|
@@ -948,7 +948,6 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
948
948
|
for (const poem of scanVerse(text).blocks) {
|
|
949
949
|
poems.set(poem.start, poem);
|
|
950
950
|
const keys = runKeys(poem.attributes);
|
|
951
|
-
if (!Object.keys(keys).length && !poem.classes.includes(NATIVEFONT_CLASS)) continue;
|
|
952
951
|
const opening = lines[poem.start];
|
|
953
952
|
constructs.push({
|
|
954
953
|
kind: "verse",
|
|
@@ -1196,10 +1195,19 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1196
1195
|
}
|
|
1197
1196
|
|
|
1198
1197
|
const verseMarkers = new Map();
|
|
1199
|
-
const verseContexts =
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1198
|
+
const verseContexts = [
|
|
1199
|
+
...scanVerse(text, { checkWorks: false }).blocks,
|
|
1200
|
+
...spans
|
|
1201
|
+
.filter((span) => span.verseModel)
|
|
1202
|
+
.map((span) => ({
|
|
1203
|
+
bodyStart: span.start + 1,
|
|
1204
|
+
bodyEnd: span.contentEnd,
|
|
1205
|
+
body: span.text,
|
|
1206
|
+
model: span.verseModel,
|
|
1207
|
+
})),
|
|
1208
|
+
];
|
|
1209
|
+
for (const poem of verseContexts) {
|
|
1210
|
+
const bodyStart = poem.bodyStart;
|
|
1203
1211
|
for (const [from, to] of poem.model.layout)
|
|
1204
1212
|
verseMarkers.set(bodyStart + from, bodyStart + to);
|
|
1205
1213
|
for (const match of poem.body.matchAll(WIKILINK)) {
|
|
@@ -1227,11 +1235,7 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1227
1235
|
if (poem.body[at] !== "[") continue;
|
|
1228
1236
|
const end = closing(poem.body, at);
|
|
1229
1237
|
if (end < 0) continue;
|
|
1230
|
-
|
|
1231
|
-
if (/^\^[^\s\]]+$/.test(label) && footnotes.has(label.slice(1))) {
|
|
1232
|
-
verseMarkers.set(bodyStart + at, bodyStart + end + 1);
|
|
1233
|
-
at = end;
|
|
1234
|
-
} else if (poem.body[end + 1] === "(" && poem.body[at - 1] !== "!") {
|
|
1238
|
+
if (poem.body[end + 1] === "(" && poem.body[at - 1] !== "!") {
|
|
1235
1239
|
const past = closing(poem.body, end + 1);
|
|
1236
1240
|
if (past < 0) continue;
|
|
1237
1241
|
verseMarkers.set(bodyStart + at, bodyStart + at + 1);
|
|
@@ -1240,10 +1244,49 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1240
1244
|
}
|
|
1241
1245
|
}
|
|
1242
1246
|
|
|
1247
|
+
const noteReferences = [
|
|
1248
|
+
...new Map(
|
|
1249
|
+
[
|
|
1250
|
+
...footnoteReferences(text, { definitions: footnotes }),
|
|
1251
|
+
...spans.flatMap((span) => {
|
|
1252
|
+
const label = referenceLabel(span.text);
|
|
1253
|
+
if (!label) return [];
|
|
1254
|
+
const base = span.start + 1 + label[0];
|
|
1255
|
+
return footnoteReferences(span.text.slice(label[0], label[1]), {
|
|
1256
|
+
definitions: footnotes,
|
|
1257
|
+
inline: true,
|
|
1258
|
+
}).map((reference) => ({
|
|
1259
|
+
...reference,
|
|
1260
|
+
from: base + reference.from,
|
|
1261
|
+
to: base + reference.to,
|
|
1262
|
+
}));
|
|
1263
|
+
}),
|
|
1264
|
+
...verseContexts.flatMap((region) =>
|
|
1265
|
+
footnoteReferences(region.body, { definitions: footnotes, inline: true }).map(
|
|
1266
|
+
(reference) => ({
|
|
1267
|
+
...reference,
|
|
1268
|
+
from: reference.from + region.bodyStart,
|
|
1269
|
+
to: reference.to + region.bodyStart,
|
|
1270
|
+
}),
|
|
1271
|
+
),
|
|
1272
|
+
),
|
|
1273
|
+
].map((reference) => [reference.from, reference]),
|
|
1274
|
+
).values(),
|
|
1275
|
+
];
|
|
1276
|
+
|
|
1243
1277
|
/** Nested spans and verse markers excluded from a run, by absolute offset. */
|
|
1244
1278
|
const holesIn = (from, to) =>
|
|
1245
1279
|
new Map([
|
|
1280
|
+
...noteReferences
|
|
1281
|
+
.filter((reference) => reference.from >= from && reference.from < to)
|
|
1282
|
+
.map((reference) => [reference.from, reference.to]),
|
|
1246
1283
|
...[...verseMarkers].filter(([start]) => start >= from && start < to),
|
|
1284
|
+
...verseContexts
|
|
1285
|
+
.filter(
|
|
1286
|
+
(region) =>
|
|
1287
|
+
Number.isFinite(region.from) && region.from >= from && region.from < to,
|
|
1288
|
+
)
|
|
1289
|
+
.map((region) => [region.from, Math.min(region.to, to)]),
|
|
1247
1290
|
...active
|
|
1248
1291
|
.filter(
|
|
1249
1292
|
(construct) =>
|
|
@@ -1406,6 +1449,7 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1406
1449
|
selection: enc === undefined ? undefined : encodingFor(ctx, { enc, font, lang }),
|
|
1407
1450
|
holes: new Map(),
|
|
1408
1451
|
body: undefined,
|
|
1452
|
+
verseModel: construct.kind === "span" ? construct.node.verseModel : undefined,
|
|
1409
1453
|
contentEnd: construct.kind === "span" ? construct.node.contentEnd : undefined,
|
|
1410
1454
|
reference: undefined,
|
|
1411
1455
|
languageEmphasis: lang === undefined ? undefined : ctx.languageEmphases?.get(lang),
|
|
@@ -1416,7 +1460,10 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1416
1460
|
detail.conventions = conventionsOf(detail, ctx);
|
|
1417
1461
|
details.push(detail);
|
|
1418
1462
|
const nonEnglishSpan =
|
|
1419
|
-
construct.kind === "span" &&
|
|
1463
|
+
construct.kind === "span" &&
|
|
1464
|
+
!construct.node.verseModel &&
|
|
1465
|
+
run.tag !== undefined &&
|
|
1466
|
+
!/^en(?:-|$)/i.test(run.tag);
|
|
1420
1467
|
const emphasis = construct.own.classes.filter(
|
|
1421
1468
|
(name) => name === "emph" || name === "strong",
|
|
1422
1469
|
);
|
|
@@ -1447,7 +1494,14 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1447
1494
|
[from, to] = detail.reference;
|
|
1448
1495
|
}
|
|
1449
1496
|
const inline = text.slice(from, to);
|
|
1450
|
-
detail.holes =
|
|
1497
|
+
detail.holes =
|
|
1498
|
+
label ?
|
|
1499
|
+
new Map(
|
|
1500
|
+
noteReferences
|
|
1501
|
+
.filter((reference) => reference.from >= from && reference.to <= to)
|
|
1502
|
+
.map((reference) => [reference.from, reference.to]),
|
|
1503
|
+
)
|
|
1504
|
+
: holesIn(from, to);
|
|
1451
1505
|
if (nonEnglishSpan) {
|
|
1452
1506
|
const relative = new Map(
|
|
1453
1507
|
[...detail.holes].map(([from, to]) => [
|
|
@@ -1507,7 +1561,7 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1507
1561
|
said(
|
|
1508
1562
|
top,
|
|
1509
1563
|
token.info.trim().split(/\s/)[0] === "verse" ?
|
|
1510
|
-
"a verse
|
|
1564
|
+
"a verse div"
|
|
1511
1565
|
: "a code fence",
|
|
1512
1566
|
);
|
|
1513
1567
|
else if (Object.hasOwn(BLOCK_WHAT, token.type)) said(top, BLOCK_WHAT[token.type]);
|
|
@@ -1572,7 +1626,18 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1572
1626
|
const owned = details.filter((detail) => ownerOf(detail) === owner);
|
|
1573
1627
|
for (const detail of owned) detail.owner = owner;
|
|
1574
1628
|
const excluded = [
|
|
1629
|
+
...noteReferences
|
|
1630
|
+
.filter((reference) => reference.from >= bounds[0] && reference.from < bounds[1])
|
|
1631
|
+
.map((reference) => [reference.from, reference.to]),
|
|
1575
1632
|
...[...verseMarkers].filter(([start]) => start >= bounds[0] && start < bounds[1]),
|
|
1633
|
+
...verseContexts
|
|
1634
|
+
.filter(
|
|
1635
|
+
(region) =>
|
|
1636
|
+
Number.isFinite(region.from) &&
|
|
1637
|
+
region.from >= bounds[0] &&
|
|
1638
|
+
region.from < bounds[1],
|
|
1639
|
+
)
|
|
1640
|
+
.map((region) => [region.from, Math.min(region.to, bounds[1])]),
|
|
1576
1641
|
...owned.flatMap((detail) =>
|
|
1577
1642
|
detail.emphasis.map((delimiter) => [
|
|
1578
1643
|
delimiter.offset,
|
|
@@ -1588,17 +1653,16 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1588
1653
|
includeSpan: (span) =>
|
|
1589
1654
|
spanDetails.has(span.start) && ownerOf(spanDetails.get(span.start)) === owner,
|
|
1590
1655
|
};
|
|
1591
|
-
const poem =
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
: verseContexts.get(owner.run.line - 1);
|
|
1656
|
+
const poem = verseContexts
|
|
1657
|
+
.filter((region) => region.bodyStart <= bounds[0] && region.bodyEnd >= bounds[1])
|
|
1658
|
+
.sort((a, b) => a.bodyEnd - a.bodyStart - (b.bodyEnd - b.bodyStart))[0];
|
|
1595
1659
|
const verse = poem !== undefined;
|
|
1596
1660
|
const projection =
|
|
1597
1661
|
verse ?
|
|
1598
1662
|
plainRomanVerse(text, {
|
|
1599
1663
|
...projectionOptions,
|
|
1600
1664
|
model: poem.model,
|
|
1601
|
-
modelOffset:
|
|
1665
|
+
modelOffset: poem.bodyStart,
|
|
1602
1666
|
})
|
|
1603
1667
|
: plainRomanRun(text, projectionOptions);
|
|
1604
1668
|
const before = errors.length;
|
|
@@ -1776,7 +1840,7 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1776
1840
|
details.sort((a, b) => order(a.run, b.run));
|
|
1777
1841
|
errors.sort(order);
|
|
1778
1842
|
warnings.sort(order);
|
|
1779
|
-
return { runs, details, errors, warnings, spans, divs, poems };
|
|
1843
|
+
return { runs, details, errors, warnings, spans, divs, poems, references: noteReferences };
|
|
1780
1844
|
}
|
|
1781
1845
|
|
|
1782
1846
|
/**
|
|
@@ -1802,7 +1866,7 @@ function diagnosticColumns(source, findings) {
|
|
|
1802
1866
|
}
|
|
1803
1867
|
|
|
1804
1868
|
/**
|
|
1805
|
-
* Find the spans, divs and verse
|
|
1869
|
+
* Find the spans, divs and verse divs marked with `lang=`, `font=` or
|
|
1806
1870
|
* `enc=`, and every fault in their markup and their text.
|
|
1807
1871
|
*
|
|
1808
1872
|
* @param {string} source - Authored Markdown: a note body, or a whole file
|
|
@@ -2033,7 +2097,7 @@ function setRange(text, from, to, detail, lineStart, book) {
|
|
|
2033
2097
|
* scanner projections.
|
|
2034
2098
|
* @param {{target?: "site"|"foundry"|"book"}} [options] - The target the
|
|
2035
2099
|
* Markdown is rendered for; only `site` writes the search marker.
|
|
2036
|
-
* @returns {{markdown: string, errors: Array<{file?: string, line: number, column: number, message: string}>, scrolls: Map<string, import("./content-scroll.mjs").ScrollLayout>, verses: Map<string, {model: ReturnType<typeof import("./content-verse.mjs").parseVerse>, classes: string[], attributes: Record<string, string
|
|
2100
|
+
* @returns {{markdown: string, errors: Array<{file?: string, line: number, column: number, message: string}>, scrolls: Map<string, import("./content-scroll.mjs").ScrollLayout>, verses: Map<string, {model: ReturnType<typeof import("./content-verse.mjs").parseVerse>, classes: string[], attributes: Record<string, string>, inline: boolean}>}}
|
|
2037
2101
|
* The rewritten Markdown, the findings of {@link scanRuns}, and each
|
|
2038
2102
|
* rewritten scroll fence's layout, by its rewritten body.
|
|
2039
2103
|
*/
|
|
@@ -2046,7 +2110,7 @@ export function renderRuns(source, ctx = {}, syntax, options = {}) {
|
|
|
2046
2110
|
const verses = new Map();
|
|
2047
2111
|
if (!/\b(?:lang|font|enc)=|\bnativefont\b|\.(?:emph|strong)\b|⟦/.test(text))
|
|
2048
2112
|
return { markdown: text, errors: [], scrolls, verses };
|
|
2049
|
-
const { details, errors } = analyseRuns(text, ctx, syntax);
|
|
2113
|
+
const { details, errors, references } = analyseRuns(text, ctx, syntax);
|
|
2050
2114
|
if (!details.length)
|
|
2051
2115
|
return { markdown: text, errors: diagnosticColumns(text, errors), scrolls, verses };
|
|
2052
2116
|
const lines = text.split("\n");
|
|
@@ -2067,9 +2131,24 @@ export function renderRuns(source, ctx = {}, syntax, options = {}) {
|
|
|
2067
2131
|
|
|
2068
2132
|
/** @type {Array<{from: number, to: number, text: string}>} */
|
|
2069
2133
|
const edits = [];
|
|
2070
|
-
const poems =
|
|
2134
|
+
const poems = [
|
|
2135
|
+
...scanVerse(text, { checkWorks: false }).blocks.map((block) => ({
|
|
2136
|
+
...block,
|
|
2137
|
+
inline: false,
|
|
2138
|
+
})),
|
|
2139
|
+
...scanSpans(text)
|
|
2140
|
+
.spans.filter((span) => span.verseModel)
|
|
2141
|
+
.map((span) => ({
|
|
2142
|
+
...span,
|
|
2143
|
+
inline: true,
|
|
2144
|
+
model: span.verseModel,
|
|
2145
|
+
body: span.text,
|
|
2146
|
+
bodyStart: span.start + 1,
|
|
2147
|
+
bodyEnd: span.contentEnd,
|
|
2148
|
+
})),
|
|
2149
|
+
].sort((a, b) => a.bodyStart - b.bodyStart);
|
|
2071
2150
|
const logicalRanges = poems.flatMap((poem) => {
|
|
2072
|
-
const base =
|
|
2151
|
+
const base = poem.bodyStart;
|
|
2073
2152
|
return poem.model.groups.flatMap((group) =>
|
|
2074
2153
|
group.cantoBreak ? [] : group.lines.map((line) => [base + line.from, base + line.to]),
|
|
2075
2154
|
);
|
|
@@ -2260,22 +2339,79 @@ export function renderRuns(source, ctx = {}, syntax, options = {}) {
|
|
|
2260
2339
|
}
|
|
2261
2340
|
}
|
|
2262
2341
|
|
|
2342
|
+
const restoredReferences = new Map();
|
|
2343
|
+
for (const reference of references) {
|
|
2344
|
+
const nativeDiv = details.find(
|
|
2345
|
+
(detail) =>
|
|
2346
|
+
detail.run.kind === "verse" &&
|
|
2347
|
+
detail.selection &&
|
|
2348
|
+
detail.run.enc !== "default" &&
|
|
2349
|
+
detail.body &&
|
|
2350
|
+
reference.from >= detail.body[0] &&
|
|
2351
|
+
reference.to <= detail.body[1] &&
|
|
2352
|
+
!faulted(detail.run),
|
|
2353
|
+
);
|
|
2354
|
+
const nativeSpans = details
|
|
2355
|
+
.filter(
|
|
2356
|
+
(detail) =>
|
|
2357
|
+
detail.run.kind === "span" &&
|
|
2358
|
+
detail.selection &&
|
|
2359
|
+
detail.run.enc !== "default" &&
|
|
2360
|
+
reference.from > detail.run.start &&
|
|
2361
|
+
reference.to <= detail.contentEnd &&
|
|
2362
|
+
!faulted(detail.run),
|
|
2363
|
+
)
|
|
2364
|
+
.sort((a, b) => a.run.start - b.run.start);
|
|
2365
|
+
const outer = nativeSpans[0];
|
|
2366
|
+
if (!nativeDiv && !outer) continue;
|
|
2367
|
+
if (!edits.some((edit) => edit.from <= reference.from && edit.to >= reference.to))
|
|
2368
|
+
edits.push({ from: reference.from, to: reference.to, text: "" });
|
|
2369
|
+
if (nativeDiv) continue;
|
|
2370
|
+
if (!restoredReferences.has(outer.run.end)) restoredReferences.set(outer.run.end, []);
|
|
2371
|
+
restoredReferences.get(outer.run.end).push(reference);
|
|
2372
|
+
}
|
|
2373
|
+
for (const [at, notes] of restoredReferences)
|
|
2374
|
+
edits.push({
|
|
2375
|
+
from: at,
|
|
2376
|
+
to: at,
|
|
2377
|
+
text: notes
|
|
2378
|
+
.sort((a, b) => a.from - b.from)
|
|
2379
|
+
.map((reference) => reference.text)
|
|
2380
|
+
.join(""),
|
|
2381
|
+
});
|
|
2382
|
+
|
|
2263
2383
|
let markdown = text;
|
|
2264
2384
|
for (const edit of edits.sort((a, b) => b.from - a.from))
|
|
2265
2385
|
markdown = markdown.slice(0, edit.from) + edit.text + markdown.slice(edit.to);
|
|
2266
2386
|
// Models retain authored structure; a native script may draw the boundaries
|
|
2267
2387
|
// as glyphs which must not be reparsed as Roman delimiters.
|
|
2268
|
-
const rewritten =
|
|
2388
|
+
const rewritten = [
|
|
2389
|
+
...scanVerse(markdown, { checkWorks: false }).blocks.map((block) => ({
|
|
2390
|
+
...block,
|
|
2391
|
+
inline: false,
|
|
2392
|
+
})),
|
|
2393
|
+
...scanSpans(markdown)
|
|
2394
|
+
.spans.filter((span) => span.verseModel)
|
|
2395
|
+
.map((span) => ({
|
|
2396
|
+
...span,
|
|
2397
|
+
inline: true,
|
|
2398
|
+
body: span.text,
|
|
2399
|
+
bodyStart: span.start + 1,
|
|
2400
|
+
bodyEnd: span.contentEnd,
|
|
2401
|
+
})),
|
|
2402
|
+
].sort((a, b) => a.bodyStart - b.bodyStart);
|
|
2269
2403
|
const pairedEdits = [];
|
|
2270
2404
|
for (const [index, poem] of poems.entries()) {
|
|
2271
2405
|
const detail = details.find(
|
|
2272
|
-
(detail) =>
|
|
2406
|
+
(detail) =>
|
|
2407
|
+
detail.run.kind === (poem.inline ? "span" : "verse") &&
|
|
2408
|
+
detail.run.start === poem.start,
|
|
2273
2409
|
);
|
|
2274
2410
|
if (!detail || detail.run.enc === "default" || !detail.selection || faulted(detail.run))
|
|
2275
2411
|
continue;
|
|
2276
2412
|
const block = rewritten[index];
|
|
2277
2413
|
if (!block) continue;
|
|
2278
|
-
const base =
|
|
2414
|
+
const base = poem.bodyStart;
|
|
2279
2415
|
const applyLine = (line) => {
|
|
2280
2416
|
const patches = [];
|
|
2281
2417
|
for (const edit of edits) {
|
|
@@ -2327,6 +2463,10 @@ export function renderRuns(source, ctx = {}, syntax, options = {}) {
|
|
|
2327
2463
|
return true;
|
|
2328
2464
|
});
|
|
2329
2465
|
const model = structuredClone(poem.model);
|
|
2466
|
+
for (const note of model.notes ?? []) {
|
|
2467
|
+
note.romanText = romanLine(note.text);
|
|
2468
|
+
note.text = "";
|
|
2469
|
+
}
|
|
2330
2470
|
for (const group of model.groups) {
|
|
2331
2471
|
if (group.cantoBreak) {
|
|
2332
2472
|
group.text = detail.selection.encoding.controls.verseCanto ?? "";
|
|
@@ -2346,21 +2486,43 @@ export function renderRuns(source, ctx = {}, syntax, options = {}) {
|
|
|
2346
2486
|
});
|
|
2347
2487
|
}
|
|
2348
2488
|
}
|
|
2489
|
+
if (poem.inline) {
|
|
2490
|
+
const values = Object.fromEntries(
|
|
2491
|
+
block.attrs.filter(([key]) => !["id", "class"].includes(key)),
|
|
2492
|
+
);
|
|
2493
|
+
const classes = (block.attrs.find(([key]) => key === "class")?.[1] ?? "")
|
|
2494
|
+
.split(/\s+/)
|
|
2495
|
+
.filter((name) => name && name !== "verse");
|
|
2496
|
+
const attributes = attributeText({
|
|
2497
|
+
lang: values.lang,
|
|
2498
|
+
id: block.id,
|
|
2499
|
+
classes,
|
|
2500
|
+
values: Object.entries(values).filter(([key]) => key !== "lang"),
|
|
2501
|
+
});
|
|
2502
|
+
const native = model.groups
|
|
2503
|
+
.flatMap((group) =>
|
|
2504
|
+
group.cantoBreak ? [group.text ?? ""] : group.lines.map((line) => line.text),
|
|
2505
|
+
)
|
|
2506
|
+
.join("");
|
|
2507
|
+
pairedEdits.push({
|
|
2508
|
+
from: block.start,
|
|
2509
|
+
to: block.end,
|
|
2510
|
+
text: `[${native}]{${attributes}}`,
|
|
2511
|
+
});
|
|
2512
|
+
continue;
|
|
2513
|
+
}
|
|
2349
2514
|
const classes = [SCRIPT_CLASS, ...(detail.face ? [detail.face.class] : [])];
|
|
2350
2515
|
const attributes = {
|
|
2351
2516
|
...(detail.run.tag ? { lang: detail.run.tag } : {}),
|
|
2352
2517
|
...(detail.face?.direction === "rtl" ? { dir: "rtl" } : {}),
|
|
2353
2518
|
...(search ? { "data-pagefind-ignore": "true" } : {}),
|
|
2354
2519
|
};
|
|
2355
|
-
const
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
pairedEdits.push({
|
|
2359
|
-
at: rewrittenStarts[block.start + 1],
|
|
2360
|
-
text: `<!--verse-pair:${verses.size}-->\n`,
|
|
2361
|
-
});
|
|
2520
|
+
const marker = `<!--verse-pair:${verses.size + 1}-->${poem.inline ? "" : "\n"}`;
|
|
2521
|
+
const body = marker + block.body;
|
|
2522
|
+
verses.set(body, { model, classes, attributes, inline: poem.inline });
|
|
2523
|
+
pairedEdits.push({ from: block.bodyStart, to: block.bodyStart, text: marker });
|
|
2362
2524
|
}
|
|
2363
|
-
for (const edit of pairedEdits.sort((a, b) => b.
|
|
2364
|
-
markdown = markdown.slice(0, edit.
|
|
2525
|
+
for (const edit of pairedEdits.sort((a, b) => b.from - a.from))
|
|
2526
|
+
markdown = markdown.slice(0, edit.from) + edit.text + markdown.slice(edit.to);
|
|
2365
2527
|
return { markdown, errors: diagnosticColumns(text, errors), scrolls, verses };
|
|
2366
2528
|
}
|