@heroiclands/package-build 25.2.1 → 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.
Files changed (54) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +1 -1
  3. package/assets/styles/content-base.css +97 -116
  4. package/docs/api.md +30 -6
  5. package/docs/authoring/links-and-markup.md +214 -126
  6. package/docs/commands.md +2 -2
  7. package/docs/content-format.md +31 -6
  8. package/docs/diagnostics.md +27 -27
  9. package/docs/guides/writing-in-invented-scripts.md +89 -83
  10. package/docs/reference/format-details.md +6 -7
  11. package/docs/reference/note-types.md +15 -15
  12. package/docs/reference/theme-pages.md +38 -57
  13. package/engine/anchors.mjs +14 -0
  14. package/engine/code-fences.mjs +15 -6
  15. package/engine/content-blocks.mjs +7 -4
  16. package/engine/content-figures.mjs +19 -5
  17. package/engine/content-footnotes.mjs +60 -1
  18. package/engine/content-index.mjs +1 -1
  19. package/engine/content-render.mjs +66 -48
  20. package/engine/content-scripts.mjs +357 -101
  21. package/engine/content-spans.mjs +76 -10
  22. package/engine/content-verse.mjs +687 -358
  23. package/engine/first-use.mjs +43 -12
  24. package/engine/heading-attributes.mjs +2 -1
  25. package/engine/journals.mjs +8 -1
  26. package/engine/markdown-blocks.mjs +3 -2
  27. package/engine/markup-validation.mjs +17 -0
  28. package/engine/name-markup.mjs +1 -0
  29. package/engine/note-vocabulary.mjs +2 -2
  30. package/engine/pdf-render.mjs +89 -130
  31. package/engine/prose-lint.mjs +14 -2
  32. package/engine/roman-verse.mjs +68 -57
  33. package/engine/run-rows.mjs +1 -1
  34. package/engine/sql-tables.mjs +1 -1
  35. package/engine/verse-format.mjs +77 -0
  36. package/engine/verse-metre.mjs +49 -47
  37. package/engine/verse-regions.mjs +181 -0
  38. package/engine/wikilink-pattern.mjs +31 -0
  39. package/engine/wikilink-syntax.mjs +2 -16
  40. package/package.json +1 -1
  41. package/site-theme/static/css/style.css +68 -96
  42. package/types/engine/code-fences.d.mts +1 -3
  43. package/types/engine/content-blocks.d.mts +3 -1
  44. package/types/engine/content-footnotes.d.mts +23 -0
  45. package/types/engine/content-scripts.d.mts +12 -7
  46. package/types/engine/content-spans.d.mts +3 -1
  47. package/types/engine/content-verse.d.mts +263 -82
  48. package/types/engine/name-markup.d.mts +5 -0
  49. package/types/engine/roman-verse.d.mts +7 -6
  50. package/types/engine/verse-format.d.mts +9 -0
  51. package/types/engine/verse-metre.d.mts +4 -4
  52. package/types/engine/verse-regions.d.mts +78 -0
  53. package/types/engine/wikilink-pattern.d.mts +17 -0
  54. package/types/engine/wikilink-syntax.d.mts +1 -41
@@ -1380,118 +1380,90 @@ a.tag:hover {
1380
1380
  background: var(--bg-card);
1381
1381
  }
1382
1382
 
1383
- /* Verse groups are paragraphs; explicit stanza separators add their own gap. */
1384
- .single-content .verse {
1385
- margin: 1.5rem 0;
1386
- line-height: 1.7;
1387
- }
1388
-
1389
- .single-content .verse p {
1390
- margin: 0;
1383
+ .single-content .verse .verse-stanza p {
1384
+ margin: 0;
1391
1385
  }
1392
1386
 
1393
- .single-content .verse p + p {
1394
- margin-top: 1em;
1395
- }
1396
-
1397
- .single-content .verse .verse-line {
1398
- display: inline-block;
1399
- margin-inline-start: var(--verse-indent, 0em);
1400
- white-space: pre-wrap;
1401
- }
1402
-
1403
- .single-content .verse :is(.verse-line, .verse-part).i1 { --verse-indent: 1.25em; }
1404
- .single-content .verse :is(.verse-line, .verse-part).i2 { --verse-indent: 2.5em; }
1405
- .single-content .verse :is(.verse-line, .verse-part).i3 { --verse-indent: 3.75em; }
1406
- .single-content .verse :is(.verse-line, .verse-part).i4 { --verse-indent: 5em; }
1407
- .single-content .verse :is(.verse-line, .verse-part).i5 { --verse-indent: 6.25em; }
1408
- .single-content .verse :is(.verse-line, .verse-part).i6 { --verse-indent: 7.5em; }
1409
- .single-content .verse :is(.verse-line, .verse-part).i7 { --verse-indent: 8.75em; }
1410
- .single-content .verse :is(.verse-line, .verse-part).i8 { --verse-indent: 10em; }
1411
-
1412
- .single-content .verse .verse-stanza-break { display: block; height: 0.5em; }
1413
-
1414
- /* Numbered verse carries its numbers in a margin. A verse in columns is one
1415
- grid per verse line: its number, then each column's parts stacked, the
1416
- first in the normal tone and the rest muted, every part in the body serif
1417
- unless its face sets it. A narrow verse stacks the columns of each line. */
1418
- .single-content .verse.verse-numbered {
1419
- position: relative;
1420
- padding-inline-start: 3em;
1387
+ /* A quotation stays within its prose paragraph; canto titles retain heading type. */
1388
+ .single-content .verse.verse-inline {
1389
+ display: inline-block;
1390
+ vertical-align: baseline;
1391
+ max-inline-size: 100%;
1392
+ margin: 0;
1393
+ container-type: normal;
1421
1394
  }
1422
-
1423
- .single-content .verse .verse-number {
1424
- color: var(--text-muted);
1425
- font-size: 0.8em;
1426
- }
1427
-
1428
- .single-content .verse-numbered .verse-line .verse-number {
1429
- position: absolute;
1430
- inset-inline-start: 0;
1431
- width: 2.2em;
1432
- text-align: end;
1395
+ .single-content .verse.verse-inline .verse-stanza {
1396
+ display: block;
1397
+ margin: 0;
1433
1398
  }
1434
-
1435
- .single-content .verse.verse-columns {
1436
- container-type: inline-size;
1399
+ .single-content .verse.verse-inline .verse-stanza + .verse-stanza {
1400
+ margin-top: 0.5em;
1437
1401
  }
1438
-
1439
- .single-content .verse-numbered.verse-columns {
1440
- padding-inline-start: 0;
1402
+ .single-content .verse .verse-canto-heading {
1403
+ display: block;
1404
+ margin: 0.5em 0;
1405
+ font-family: var(--font-display);
1406
+ font-size: 1.1em;
1407
+ font-weight: bold;
1408
+ line-height: 1.3;
1441
1409
  }
1442
1410
 
1443
- .single-content .verse-columns .verse-row {
1444
- display: grid;
1445
- grid-auto-flow: column;
1446
- grid-auto-columns: 1fr;
1447
- direction: ltr;
1448
- grid-template-columns: repeat(var(--verse-columns, 2), minmax(0, 1fr));
1449
- column-gap: 1.5rem;
1450
- margin-bottom: 0.5em;
1411
+ /* Logical stanzas share labels across native and Roman presentations. */
1412
+ .single-content .verse {
1413
+ margin: 1.5rem 0;
1414
+ line-height: 1.7;
1415
+ container-type: inline-size;
1451
1416
  }
1452
-
1453
- .single-content .verse-numbered.verse-columns .verse-row {
1454
- grid-template-columns: 2.5em repeat(var(--verse-columns, 2), minmax(0, 1fr));
1417
+ .single-content .verse .verse-stanza {
1418
+ position: relative;
1419
+ margin: 0 0 1em;
1455
1420
  }
1456
-
1457
- .single-content .verse-columns .verse-number {
1458
- text-align: end;
1459
- padding-top: 0.2em;
1421
+ .single-content .verse-labelled .verse-stanza {
1422
+ padding-inline-start: 3em;
1460
1423
  }
1461
-
1462
- .single-content .verse-columns :is(.verse-column, .verse-part) {
1463
- display: block;
1464
- white-space: pre-wrap;
1424
+ .single-content .verse .verse-label {
1425
+ position: absolute;
1426
+ inset-inline-start: 0;
1427
+ width: 2.5em;
1428
+ text-align: end;
1429
+ font-size: 0.75em;
1430
+ color: color-mix(in srgb, currentColor 60%, transparent);
1465
1431
  }
1466
-
1467
- .single-content .verse-columns .verse-part {
1468
- font-family: var(--font-body);
1469
- padding-inline-start: var(--verse-indent, 0em);
1432
+ .single-content .verse .verse-line {
1433
+ display: inline-block;
1434
+ margin-inline-start: var(--verse-indent, 0em);
1435
+ white-space: pre-wrap;
1436
+ }
1437
+ .single-content .verse .verse-line.i1 { --verse-indent: 1.25em; }
1438
+ .single-content .verse .verse-line.i2 { --verse-indent: 2.5em; }
1439
+ .single-content .verse .verse-line.i3 { --verse-indent: 3.75em; }
1440
+ .single-content .verse .verse-line.i4 { --verse-indent: 5em; }
1441
+ .single-content .verse .verse-line.i5 { --verse-indent: 6.25em; }
1442
+ .single-content .verse .verse-line.i6 { --verse-indent: 7.5em; }
1443
+ .single-content .verse .verse-line.i7 { --verse-indent: 8.75em; }
1444
+ .single-content .verse .verse-line.i8 { --verse-indent: 10em; }
1445
+ .single-content .verse .verse-canto-break {
1446
+ display: block;
1447
+ height: 0.5em;
1470
1448
  }
1471
-
1472
- .single-content .verse-columns .verse-secondary {
1473
- color: var(--text-muted);
1449
+ .single-content .verse-paired .verse-row {
1450
+ display: grid;
1451
+ direction: ltr;
1452
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1453
+ column-gap: 1.5rem;
1454
+ align-items: start;
1474
1455
  }
1475
-
1476
- .single-content .verse-columns .verse-secondary * {
1477
- color: inherit;
1456
+ .single-content .verse-paired :is(.verse-native, .verse-roman) {
1457
+ min-width: 0;
1458
+ white-space: pre-wrap;
1478
1459
  }
1479
-
1480
- .single-content .verse-columns .verse-part[dir="rtl"] {
1481
- text-align: start;
1460
+ .single-content .verse-paired .verse-roman {
1461
+ font-family: serif;
1482
1462
  }
1483
-
1484
1463
  @container (max-width: 32rem) {
1485
- .single-content .verse-columns .verse-row {
1486
- grid-auto-flow: row;
1487
- grid-template-columns: 1fr;
1488
- }
1489
- .single-content .verse-numbered.verse-columns .verse-row {
1490
- grid-template-columns: 2.5em 1fr;
1491
- }
1492
- .single-content .verse-numbered.verse-columns .verse-column {
1493
- grid-column: 2;
1494
- }
1464
+ .single-content .verse-paired .verse-row {
1465
+ grid-template-columns: 1fr;
1466
+ }
1495
1467
  }
1496
1468
 
1497
1469
  /* Ornamental horizontal rule: thin line flanking a centered gold
@@ -17,13 +17,11 @@ export function parseHeaderArgs(info: string): {
17
17
  * @param {object} [options]
18
18
  * @param {boolean} [options.spans=true] - Include inline code spans. Set false
19
19
  * to consider only block-level code.
20
- * @param {boolean} [options.verseAsProse=true] - Treat verse bodies as inline prose.
21
20
  * @returns {Array<{start: number, end: number}>} Non-overlapping regions, in
22
21
  * source order. `end` is exclusive.
23
22
  */
24
- export function codeRegions(markdown: string | null | undefined, { spans, verseAsProse }?: {
23
+ export function codeRegions(markdown: string | null | undefined, { spans }?: {
25
24
  spans?: boolean | undefined;
26
- verseAsProse?: boolean | undefined;
27
25
  }): Array<{
28
26
  start: number;
29
27
  end: number;
@@ -43,10 +43,12 @@ export function scanBlocks(source: string): {
43
43
  * @param {object} [options]
44
44
  * @param {(markdown: string) => string} [options.renderInline] - How a title
45
45
  * is rendered; omitted, a title is plain inline Markdown.
46
+ * @param {Map<string, object>} [options.verses] - Shared encoded verse presentations.
46
47
  * @returns {{markdown: string, errors: Array<{line: number, column: number, message: string}>}}
47
48
  */
48
- export function renderBlocks(source: string, target: "foundry" | "web" | "preview", { renderInline }?: {
49
+ export function renderBlocks(source: string, target: "foundry" | "web" | "preview", { renderInline, verses }?: {
49
50
  renderInline?: ((markdown: string) => string) | undefined;
51
+ verses?: Map<string, object> | undefined;
50
52
  }): {
51
53
  markdown: string;
52
54
  errors: Array<{
@@ -81,6 +81,29 @@ export function footnoteFindings(source: string): Array<{
81
81
  column: number;
82
82
  message: string;
83
83
  }>;
84
+ /**
85
+ * Labels defined at the top level of this note.
86
+ * @param {string} source - Complete authored note Markdown.
87
+ * @returns {Set<string>} Same-note footnote labels.
88
+ */
89
+ export function footnoteLabels(source: string): Set<string>;
90
+ /**
91
+ * Actual defined references, excluding literal code, escapes and metadata.
92
+ * @param {string} source - Full note or raw inline fragment.
93
+ * @param {object} [options] - Definition context and source grammar.
94
+ * @param {ReadonlySet<string>} [options.definitions] - Same-note labels; derived from source when omitted.
95
+ * @param {boolean} [options.inline] - Treat all source as inline verse/prose.
96
+ * @returns {Array<{from:number,to:number,key:string,text:string}>} Original UTF-16 source ranges.
97
+ */
98
+ export function footnoteReferences(source: string, { definitions, inline }?: {
99
+ definitions?: ReadonlySet<string> | undefined;
100
+ inline?: boolean | undefined;
101
+ }): Array<{
102
+ from: number;
103
+ to: number;
104
+ key: string;
105
+ text: string;
106
+ }>;
84
107
  /**
85
108
  * A footnote reference's shape, wherever it is written — excluding a
86
109
  * definition's own `[^id]:` opener, which this never matches.
@@ -18,7 +18,7 @@ export function runContext(config: {
18
18
  };
19
19
  } | undefined, records?: Iterable<Record<string, any>>): RunContext;
20
20
  /**
21
- * Find the spans, divs and verse fences marked with `lang=`, `font=` or
21
+ * Find the spans, divs and verse divs marked with `lang=`, `font=` or
22
22
  * `enc=`, and every fault in their markup and their text.
23
23
  *
24
24
  * @param {string} source - Authored Markdown: a note body, or a whole file
@@ -50,11 +50,10 @@ export function scanRuns(source: string, ctx?: RunContext, syntax?: import("./co
50
50
  };
51
51
  /**
52
52
  * Rewrite text marked with `lang=`, `font=` and `enc=` into the plain span,
53
- * div and verse grammar the rest of each chain renders, one line for one line.
53
+ * div and verse grammar the rest of each chain renders.
54
54
  *
55
- * - **Numbered verse prefixes and indentation markers** stay outside run text.
56
- * Each authored run chooses its own language and presentation; physical
57
- * lines and source positions remain unchanged.
55
+ * - **Verse layout markers** stay outside encoded text. A shared logical
56
+ * structure retains authored source positions and both presentations.
58
57
  * - **The enc, if any, is applied first.** Characters Markdown would read are
59
58
  * then backslash-escaped, so they reach every later pass as characters.
60
59
  * - **Emphasis in a face** becomes an `<em>` or a `<strong>` carrying its
@@ -81,7 +80,7 @@ export function scanRuns(source: string, ctx?: RunContext, syntax?: import("./co
81
80
  * scanner projections.
82
81
  * @param {{target?: "site"|"foundry"|"book"}} [options] - The target the
83
82
  * Markdown is rendered for; only `site` writes the search marker.
84
- * @returns {{markdown: string, errors: Array<{file?: string, line: number, column: number, message: string}>, scrolls: Map<string, import("./content-scroll.mjs").ScrollLayout>}}
83
+ * @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}>}}
85
84
  * The rewritten Markdown, the findings of {@link scanRuns}, and each
86
85
  * rewritten scroll fence's layout, by its rewritten body.
87
86
  */
@@ -96,6 +95,12 @@ export function renderRuns(source: string, ctx?: RunContext, syntax?: import("./
96
95
  message: string;
97
96
  }>;
98
97
  scrolls: Map<string, import("./content-scroll.mjs").ScrollLayout>;
98
+ verses: Map<string, {
99
+ model: ReturnType<typeof import("./content-verse.mjs").parseVerse>;
100
+ classes: string[];
101
+ attributes: Record<string, string>;
102
+ inline: boolean;
103
+ }>;
99
104
  };
100
105
  /** The keys this module reads, which no construct passes through as written. */
101
106
  export const RUN_KEYS: readonly string[];
@@ -346,7 +351,7 @@ export type RunContext = {
346
351
  directions?: ReadonlyMap<string, "vertical-rl"> | undefined;
347
352
  };
348
353
  /**
349
- * One span, div, verse fence or scroll fence carrying run attributes.
354
+ * One span, div, verse div or scroll fence carrying run attributes.
350
355
  * Values combine explicit choices, class-only inheritance, encoding-document
351
356
  * language/font defaults and the Roman default encoding.
352
357
  */
@@ -3,10 +3,12 @@
3
3
  * @param {object} [options] - Surface presentation.
4
4
  * @param {boolean} [options.source] - Emit opaque authored source tokens for buffer preview.
5
5
  * @param {boolean} [options.spoilers] - Add focusable CSS spoiler wrappers (Foundry).
6
+ * @param {(token: any, state: any) => void} [options.verseInline] - Populate an opaque quoted-verse token during inline parsing.
6
7
  */
7
- export function spanMarkdownPlugin(md: typeof import("markdown-it"), { spoilers, source }?: {
8
+ export function spanMarkdownPlugin(md: typeof import("markdown-it"), { spoilers, source, verseInline }?: {
8
9
  source?: boolean | undefined;
9
10
  spoilers?: boolean | undefined;
11
+ verseInline?: ((token: any, state: any) => void) | undefined;
10
12
  }): void;
11
13
  /** Read span anchors and located attribute diagnostics outside literal code.
12
14
  * @param {string} source - Authored Markdown.