@heroiclands/package-build 25.3.0 → 25.5.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 +39 -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 +143 -53
- package/docs/commands.md +8 -2
- package/docs/content-format.md +14 -6
- package/docs/diagnostics.md +12 -11
- package/docs/encoding-core.md +18 -4
- package/docs/guides/writing-in-invented-scripts.md +83 -37
- package/docs/reference/format-details.md +10 -11
- 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 +69 -1
- package/engine/content-index.mjs +1 -1
- package/engine/content-render.mjs +59 -43
- package/engine/content-scripts.mjs +301 -58
- package/engine/content-scroll.mjs +8 -3
- package/engine/content-spans.mjs +107 -6
- package/engine/content-verse.mjs +338 -198
- package/engine/encoding-standard.mjs +1 -0
- package/engine/encoding-template.mjs +5 -2
- package/engine/encoding.mjs +19 -14
- 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/markdownlint-run.mjs +190 -5
- 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/roman-characters.mjs +32 -10
- package/engine/roman-run.mjs +3 -0
- package/engine/roman-verse.mjs +1 -0
- package/engine/run-rows.mjs +1 -1
- package/engine/sql-tables.mjs +1 -1
- package/engine/verse-format.mjs +89 -0
- package/engine/verse-metre.mjs +18 -9
- package/engine/verse-regions.mjs +190 -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-scroll.d.mts +1 -1
- package/types/engine/content-spans.d.mts +3 -1
- package/types/engine/content-verse.d.mts +75 -75
- package/types/engine/name-markup.d.mts +5 -0
- package/types/engine/roman-run.d.mts +3 -1
- package/types/engine/verse-format.d.mts +10 -0
- package/types/engine/verse-metre.d.mts +4 -4
- package/types/engine/verse-regions.d.mts +82 -0
- package/types/engine/wikilink-pattern.d.mts +17 -0
- package/types/engine/wikilink-syntax.d.mts +1 -41
package/engine/content-spans.mjs
CHANGED
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
import MarkdownIt from "markdown-it";
|
|
15
15
|
import { alertMarkdownPlugin } from "./content-alerts.mjs";
|
|
16
16
|
import { slugify } from "./content-slug.mjs";
|
|
17
|
-
import { scanVerse } from "./content-verse.mjs";
|
|
17
|
+
import { scanVerse, parseVerse, inlineVerseFindings } from "./content-verse.mjs";
|
|
18
|
+
import { verseDivPlugin } from "./verse-regions.mjs";
|
|
19
|
+
import { codeRegions } from "./code-fences.mjs";
|
|
20
|
+
import { footnoteLabels } from "./content-footnotes.mjs";
|
|
18
21
|
import footnotePlugin from "markdown-it-footnote";
|
|
19
22
|
import { parseExtensionAttributes, refusedAttributes } from "./extension-attributes.mjs";
|
|
20
23
|
import { inlineText } from "./name-markup.mjs";
|
|
@@ -25,8 +28,9 @@ import { cartoucheProblems, glyphProblems } from "./content-glyphs.mjs";
|
|
|
25
28
|
* @param {object} [options] - Surface presentation.
|
|
26
29
|
* @param {boolean} [options.source] - Emit opaque authored source tokens for buffer preview.
|
|
27
30
|
* @param {boolean} [options.spoilers] - Add focusable CSS spoiler wrappers (Foundry).
|
|
31
|
+
* @param {(token: any, state: any) => void} [options.verseInline] - Populate an opaque quoted-verse token during inline parsing.
|
|
28
32
|
*/
|
|
29
|
-
export function spanMarkdownPlugin(md, { spoilers = false, source = false } = {}) {
|
|
33
|
+
export function spanMarkdownPlugin(md, { spoilers = false, source = false, verseInline } = {}) {
|
|
30
34
|
{
|
|
31
35
|
md.renderer.rules.span_open = (tokens, index, options, env, renderer) => {
|
|
32
36
|
const token = tokens[index];
|
|
@@ -63,7 +67,6 @@ export function spanMarkdownPlugin(md, { spoilers = false, source = false } = {}
|
|
|
63
67
|
state.src[state.src.indexOf("]", start + 2) + 1] === "]") ||
|
|
64
68
|
/@[A-Za-z]+$/.test(state.src.slice(0, start)) ||
|
|
65
69
|
state.src[start + 1] === "^" ||
|
|
66
|
-
state.src[start + 1] === "!" ||
|
|
67
70
|
["!", "]"].includes(state.src[start - 1]) ||
|
|
68
71
|
(state.src[start - 1] === "[" && state.linkLevel === 0)
|
|
69
72
|
)
|
|
@@ -71,6 +74,7 @@ export function spanMarkdownPlugin(md, { spoilers = false, source = false } = {}
|
|
|
71
74
|
const end = state.md.helpers.parseLinkLabel(state, start, false);
|
|
72
75
|
if (
|
|
73
76
|
end < 0 ||
|
|
77
|
+
(state.src[start + 1] === "!" && state.src[end + 1] !== "{") ||
|
|
74
78
|
["(", "["].includes(state.src[end + 1]) ||
|
|
75
79
|
(state.src[start + 1] === "[" && state.src[end + 1] !== "{")
|
|
76
80
|
)
|
|
@@ -110,7 +114,10 @@ export function spanMarkdownPlugin(md, { spoilers = false, source = false } = {}
|
|
|
110
114
|
state.src[start - 1] === "[" &&
|
|
111
115
|
!["lang", "font", "enc"].some((key) => Object.hasOwn(parsed.values, key)) &&
|
|
112
116
|
!parsed.classes.some(
|
|
113
|
-
(name) =>
|
|
117
|
+
(name) =>
|
|
118
|
+
name === "verse" ||
|
|
119
|
+
name === "script" ||
|
|
120
|
+
/^font-(?:mono|serif|sans)$/.test(name),
|
|
114
121
|
)
|
|
115
122
|
)
|
|
116
123
|
return false;
|
|
@@ -141,7 +148,12 @@ export function spanMarkdownPlugin(md, { spoilers = false, source = false } = {}
|
|
|
141
148
|
state.pos = finish;
|
|
142
149
|
return true;
|
|
143
150
|
}
|
|
144
|
-
const
|
|
151
|
+
const verse = parsed.classes.includes("verse") && typeof verseInline === "function";
|
|
152
|
+
const token = state.push(
|
|
153
|
+
verse ? "verse_inline" : "span_open",
|
|
154
|
+
emphasis ?? "span",
|
|
155
|
+
verse ? 0 : 1,
|
|
156
|
+
);
|
|
145
157
|
token.meta = {
|
|
146
158
|
span: true,
|
|
147
159
|
start,
|
|
@@ -164,6 +176,11 @@ export function spanMarkdownPlugin(md, { spoilers = false, source = false } = {}
|
|
|
164
176
|
if (parsed.classes.length) token.attrSet("class", parsed.classes.join(" "));
|
|
165
177
|
for (const [key, value] of Object.entries(parsed.values))
|
|
166
178
|
if (!refusedAttributes({ [key]: value }).length) token.attrSet(key, value);
|
|
179
|
+
if (verse) {
|
|
180
|
+
verseInline(token, state);
|
|
181
|
+
state.pos = finish;
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
167
184
|
const nested = [];
|
|
168
185
|
state.md.inline.parse(state.src.slice(start + 1, end), state.md, state.env, nested);
|
|
169
186
|
for (const child of nested)
|
|
@@ -184,6 +201,7 @@ export function spanMarkdownPlugin(md, { spoilers = false, source = false } = {}
|
|
|
184
201
|
}
|
|
185
202
|
|
|
186
203
|
const parser = new MarkdownIt({ html: true })
|
|
204
|
+
.use(verseDivPlugin)
|
|
187
205
|
.use(footnotePlugin)
|
|
188
206
|
.use(alertMarkdownPlugin)
|
|
189
207
|
.use(spanMarkdownPlugin);
|
|
@@ -197,6 +215,7 @@ export function scanSpans(source) {
|
|
|
197
215
|
tokens = parser.parse(text, env),
|
|
198
216
|
spans = [],
|
|
199
217
|
errors = [];
|
|
218
|
+
const sourcePositions = new Map();
|
|
200
219
|
const lines = text.split("\n"),
|
|
201
220
|
starts = [];
|
|
202
221
|
let offset = 0;
|
|
@@ -226,6 +245,14 @@ export function scanSpans(source) {
|
|
|
226
245
|
if (children[close].type === "span_open") depth++;
|
|
227
246
|
if (children[close].type === "span_close" && --depth === 0) break;
|
|
228
247
|
}
|
|
248
|
+
if ((child.attrGet("class") ?? "").split(/\s+/).includes("verse"))
|
|
249
|
+
sourcePositions.set(
|
|
250
|
+
start,
|
|
251
|
+
Array.from(
|
|
252
|
+
{ length: child.meta.text.length + 1 },
|
|
253
|
+
(_, index) => position(child.meta.start + 1 + index) - start - 1,
|
|
254
|
+
),
|
|
255
|
+
);
|
|
229
256
|
spans.push({
|
|
230
257
|
id: child.attrGet("id") ?? "",
|
|
231
258
|
line,
|
|
@@ -296,8 +323,82 @@ export function scanSpans(source) {
|
|
|
296
323
|
for (const poem of scanVerse(text, { checkWorks: false }).blocks) {
|
|
297
324
|
const children = [];
|
|
298
325
|
parser.inline.parse(poem.body, parser, env, children);
|
|
299
|
-
collect(children, (pos) =>
|
|
326
|
+
collect(children, (pos) => poem.bodyStart + pos);
|
|
300
327
|
}
|
|
328
|
+
const raw = text.split("");
|
|
329
|
+
for (const region of codeRegions(text))
|
|
330
|
+
for (let offset = region.start; offset < region.end; offset++)
|
|
331
|
+
if (!/[\r\n]/.test(raw[offset])) raw[offset] = " ";
|
|
332
|
+
const rawText = raw.join("");
|
|
333
|
+
const quoted = [];
|
|
334
|
+
parser.inline.parse(rawText, parser, env, quoted);
|
|
335
|
+
const verseTokens = quoted.filter(
|
|
336
|
+
(token) =>
|
|
337
|
+
token.meta?.span &&
|
|
338
|
+
(token.attrGet("class") ?? "").split(/\s+/).includes("verse") &&
|
|
339
|
+
!spans.some((span) => span.start === token.meta.start && span.end === token.meta.end),
|
|
340
|
+
);
|
|
341
|
+
collect(verseTokens, (pos) => pos);
|
|
342
|
+
for (const match of rawText.matchAll(/\{[^{}\r\n]*(?:\}|$)/gm)) {
|
|
343
|
+
if (
|
|
344
|
+
!parseExtensionAttributes(match[0].slice(1).replace(/\}$/, "")).classes.includes(
|
|
345
|
+
"verse",
|
|
346
|
+
)
|
|
347
|
+
)
|
|
348
|
+
continue;
|
|
349
|
+
if (rawText[match.index - 1] !== "]") continue;
|
|
350
|
+
if (
|
|
351
|
+
spans.some(
|
|
352
|
+
(span) =>
|
|
353
|
+
span.contentEnd + 1 === match.index &&
|
|
354
|
+
span.end >= match.index + match[0].length,
|
|
355
|
+
)
|
|
356
|
+
)
|
|
357
|
+
continue;
|
|
358
|
+
const { line, column } = located(match.index, match.index + match[0].length);
|
|
359
|
+
errors.push({
|
|
360
|
+
line,
|
|
361
|
+
column,
|
|
362
|
+
message:
|
|
363
|
+
"an inline .verse quotation needs balanced brackets and closing {…} attributes",
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
for (const span of spans)
|
|
367
|
+
if (
|
|
368
|
+
span.attrs.some(
|
|
369
|
+
([key, value]) => key === "class" && value.split(/\s+/).includes("verse"),
|
|
370
|
+
)
|
|
371
|
+
) {
|
|
372
|
+
const model = parseVerse(span.text, {
|
|
373
|
+
inline: true,
|
|
374
|
+
footnotes: footnoteLabels(text),
|
|
375
|
+
});
|
|
376
|
+
// The parser removes container prefixes and normalizes CRLF. Map
|
|
377
|
+
// every logical range back through its original source positions.
|
|
378
|
+
const positions = sourcePositions.get(span.start);
|
|
379
|
+
const at = (offset) => positions[offset];
|
|
380
|
+
const ranges = new Set([
|
|
381
|
+
...model.groups,
|
|
382
|
+
...model.cantos.flatMap((canto) => canto.stanzas),
|
|
383
|
+
...model.groups.flatMap((group) => (group.cantoBreak ? [] : group.lines)),
|
|
384
|
+
...model.notes,
|
|
385
|
+
]);
|
|
386
|
+
for (const range of ranges) {
|
|
387
|
+
range.from = at(range.from);
|
|
388
|
+
range.to = at(range.to);
|
|
389
|
+
if ("offsets" in range) range.offsets = range.offsets.map(at);
|
|
390
|
+
}
|
|
391
|
+
for (const boundary of model.boundaries)
|
|
392
|
+
boundary.sourceOffset = at(boundary.sourceOffset);
|
|
393
|
+
for (const finding of model.errors) finding.offset = at(finding.offset);
|
|
394
|
+
model.layout = model.layout.map(([from, to]) => [at(from), at(to)]);
|
|
395
|
+
for (let index = 1; index < positions.length; index++)
|
|
396
|
+
if (positions[index] > positions[index - 1] + 1)
|
|
397
|
+
model.layout.push([positions[index - 1] + 1, positions[index]]);
|
|
398
|
+
model.layout.sort((a, b) => a[0] - b[0]);
|
|
399
|
+
span.verseModel = model;
|
|
400
|
+
errors.push(...inlineVerseFindings(span, text));
|
|
401
|
+
}
|
|
301
402
|
spans.sort((a, b) => a.start - b.start);
|
|
302
403
|
errors.sort((a, b) => a.line - b.line || a.column - b.column);
|
|
303
404
|
return {
|