@heroiclands/package-build 22.21.0 → 22.23.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 (152) hide show
  1. package/CHANGELOG.md +223 -0
  2. package/assets/styles/content-base.css +162 -0
  3. package/bin/content-commands.mjs +77 -17
  4. package/bin/package-build.mjs +15 -2
  5. package/ci/content-format-reference.mjs +68 -1
  6. package/config.mjs +26 -0
  7. package/content-config.mjs +153 -6
  8. package/docs/api.md +120 -89
  9. package/docs/authoring/assets.md +14 -0
  10. package/docs/authoring/dates-and-calendars.md +41 -3
  11. package/docs/authoring/frontmatter.md +2 -2
  12. package/docs/authoring/links-and-markup.md +325 -35
  13. package/docs/commands.md +12 -10
  14. package/docs/configuration.md +117 -14
  15. package/docs/content-format.md +4 -2
  16. package/docs/guides/book.md +5 -4
  17. package/docs/guides/reference-pages.md +15 -0
  18. package/docs/reference/format-details.md +545 -104
  19. package/docs/reference/note-types.md +30 -13
  20. package/engine/actor-compiler.mjs +23 -0
  21. package/engine/anchored-sections.mjs +35 -4
  22. package/engine/anchors.mjs +26 -30
  23. package/engine/art-fields.mjs +118 -0
  24. package/engine/art-slots.mjs +7 -1
  25. package/engine/asset-index.mjs +160 -10
  26. package/engine/asset-replacement.mjs +93 -0
  27. package/engine/asset-types.mjs +240 -0
  28. package/engine/base-compiler.mjs +139 -22
  29. package/engine/base-styles.mjs +149 -0
  30. package/engine/bundles.mjs +13 -2
  31. package/engine/compendiums.mjs +8 -1
  32. package/engine/content-blocks.mjs +410 -0
  33. package/engine/content-embeds.mjs +19 -0
  34. package/engine/content-figures.mjs +468 -0
  35. package/engine/content-footnotes.mjs +197 -0
  36. package/engine/content-format-check.mjs +15 -12
  37. package/engine/content-format.yaml +1 -1
  38. package/engine/content-images.mjs +126 -25
  39. package/engine/content-index.mjs +38 -12
  40. package/engine/content-links.mjs +14 -6
  41. package/engine/content-tables.mjs +45 -4
  42. package/engine/content-tasklists.mjs +189 -0
  43. package/engine/docs-checks.mjs +4 -4
  44. package/engine/extension-attributes.mjs +48 -2
  45. package/engine/field-reference.mjs +31 -10
  46. package/engine/field-spec.mjs +10 -8
  47. package/engine/foreign-catalog.mjs +143 -18
  48. package/engine/frontmatter-lint.mjs +120 -65
  49. package/engine/frontmatter.mjs +3 -3
  50. package/engine/heading-attributes.mjs +321 -0
  51. package/engine/helpers.mjs +109 -41
  52. package/engine/homepage.mjs +8 -8
  53. package/engine/index.mjs +3 -0
  54. package/engine/infobox-registry.mjs +4 -1
  55. package/engine/infobox-render.mjs +15 -3
  56. package/engine/infobox.mjs +129 -15
  57. package/engine/itinerary-scenes.mjs +4 -10
  58. package/engine/journals.mjs +294 -50
  59. package/engine/lore-events.mjs +197 -0
  60. package/engine/macros.mjs +1 -1
  61. package/engine/markdown-expressions.mjs +434 -59
  62. package/engine/note-dates.mjs +354 -12
  63. package/engine/note-frontmatter.mjs +7 -16
  64. package/engine/note-vocabulary.mjs +183 -42
  65. package/engine/page-lists.mjs +358 -0
  66. package/engine/pathnames.mjs +36 -0
  67. package/engine/pdf-build.mjs +431 -74
  68. package/engine/pdf-images.mjs +64 -0
  69. package/engine/pdf-render.mjs +563 -66
  70. package/engine/rank-ladder.mjs +198 -0
  71. package/engine/reckoning-markers.mjs +1 -1
  72. package/engine/retired-fields.mjs +8 -13
  73. package/engine/scenes.mjs +26 -7
  74. package/engine/site-build.mjs +166 -23
  75. package/engine/site-config.mjs +10 -1
  76. package/engine/site-index.mjs +90 -3
  77. package/engine/site-preview.mjs +20 -7
  78. package/engine/social-ties.mjs +52 -14
  79. package/engine/sql-tables.mjs +66 -9
  80. package/engine/standings.mjs +89 -6
  81. package/engine/system-block.mjs +56 -30
  82. package/engine/web-wikilinks.mjs +48 -1
  83. package/hm3/actors.mjs +8 -5
  84. package/manifest.mjs +43 -24
  85. package/package.json +1 -1
  86. package/packagebuild-metadata.jsonl +13 -13
  87. package/sohl/actors.mjs +22 -99
  88. package/sohl/item-fields.mjs +5 -5
  89. package/sohl/items.mjs +1 -1
  90. package/sohl/note-schemas.mjs +11 -21
  91. package/types/config.d.mts +9 -0
  92. package/types/content-config.d.mts +48 -1
  93. package/types/engine/actor-compiler.d.mts +11 -0
  94. package/types/engine/anchored-sections.d.mts +24 -0
  95. package/types/engine/anchors.d.mts +9 -4
  96. package/types/engine/art-fields.d.mts +64 -0
  97. package/types/engine/asset-index.d.mts +43 -0
  98. package/types/engine/asset-replacement.d.mts +22 -0
  99. package/types/engine/asset-types.d.mts +18 -0
  100. package/types/engine/base-compiler.d.mts +19 -17
  101. package/types/engine/base-styles.d.mts +93 -0
  102. package/types/engine/content-blocks.d.mts +87 -0
  103. package/types/engine/content-embeds.d.mts +16 -0
  104. package/types/engine/content-figures.d.mts +101 -0
  105. package/types/engine/content-footnotes.d.mts +80 -0
  106. package/types/engine/content-format-check.d.mts +8 -6
  107. package/types/engine/content-images.d.mts +78 -12
  108. package/types/engine/content-index.d.mts +8 -2
  109. package/types/engine/content-tables.d.mts +12 -4
  110. package/types/engine/content-tasklists.d.mts +72 -0
  111. package/types/engine/extension-attributes.d.mts +22 -1
  112. package/types/engine/field-reference.d.mts +21 -0
  113. package/types/engine/field-spec.d.mts +20 -16
  114. package/types/engine/foreign-catalog.d.mts +39 -0
  115. package/types/engine/frontmatter.d.mts +1 -1
  116. package/types/engine/heading-attributes.d.mts +179 -0
  117. package/types/engine/helpers.d.mts +29 -10
  118. package/types/engine/homepage.d.mts +7 -7
  119. package/types/engine/index.d.mts +1 -0
  120. package/types/engine/infobox-registry.d.mts +4 -1
  121. package/types/engine/infobox-render.d.mts +6 -1
  122. package/types/engine/infobox.d.mts +6 -0
  123. package/types/engine/journals.d.mts +41 -12
  124. package/types/engine/lore-events.d.mts +14 -0
  125. package/types/engine/markdown-expressions.d.mts +46 -2
  126. package/types/engine/note-dates.d.mts +64 -1
  127. package/types/engine/note-frontmatter.d.mts +1 -4
  128. package/types/engine/note-vocabulary.d.mts +82 -6
  129. package/types/engine/page-lists.d.mts +126 -0
  130. package/types/engine/pathnames.d.mts +26 -0
  131. package/types/engine/pdf-build.d.mts +28 -6
  132. package/types/engine/pdf-images.d.mts +52 -0
  133. package/types/engine/pdf-render.d.mts +84 -2
  134. package/types/engine/rank-ladder.d.mts +32 -0
  135. package/types/engine/retired-fields.d.mts +4 -6
  136. package/types/engine/site-build.d.mts +27 -4
  137. package/types/engine/site-config.d.mts +9 -0
  138. package/types/engine/site-index.d.mts +67 -1
  139. package/types/engine/site-preview.d.mts +1 -1
  140. package/types/engine/sql-tables.d.mts +31 -7
  141. package/types/engine/standings.d.mts +21 -3
  142. package/types/engine/system-block.d.mts +37 -14
  143. package/types/engine/web-wikilinks.d.mts +3 -1
  144. package/types/hm3/actors.d.mts +21 -3
  145. package/types/manifest.d.mts +19 -23
  146. package/types/sohl/actors.d.mts +6 -7
  147. package/engine/content-admonitions.mjs +0 -114
  148. package/engine/content-captions.mjs +0 -181
  149. package/engine/content-secrets.mjs +0 -112
  150. package/types/engine/content-admonitions.d.mts +0 -24
  151. package/types/engine/content-captions.d.mts +0 -45
  152. package/types/engine/content-secrets.d.mts +0 -17
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Read a heading's text and the attribute block it may end with.
3
+ *
4
+ * A suffix that is not an attribute block — a set written in braces, a die
5
+ * expression — is left in the text and reported, so no surface silently prints
6
+ * a heading with words missing and no author is told to fix nothing.
7
+ *
8
+ * **One mistake draws one finding.** Braces holding nothing the grammar
9
+ * recognises are reported once, naming the braces: an author who wrote
10
+ * `{a, b}` meant no attribute block at all, and a complaint per fragment inside
11
+ * it says the same thing twice in words that name neither the heading nor the
12
+ * fix. Braces that *are* an attribute block, carrying one bad attribute beside
13
+ * good ones, relay the grammar's own complaint about that attribute.
14
+ *
15
+ * @param {string} heading - A heading's text, hashes already removed.
16
+ * @returns {{text: string, id: string, classes: string[],
17
+ * values: Record<string, string>, problems: string[], braces: boolean}}
18
+ * `text` is what a surface sets, `braces` whether a suffix was there at all.
19
+ */
20
+ export function splitHeadingAttributes(heading: string): {
21
+ text: string;
22
+ id: string;
23
+ classes: string[];
24
+ values: Record<string, string>;
25
+ problems: string[];
26
+ braces: boolean;
27
+ };
28
+ /**
29
+ * Whether a heading opens a journal page.
30
+ *
31
+ * A Foundry UUID addresses a page and nothing smaller, so a section a link can
32
+ * reach has to be one: a top-level heading starts a page, and so does any
33
+ * heading that names an anchor. {@link module:engine/journals.splitPages}
34
+ * decides *where* a page begins — a heading inside a fence or a named block is
35
+ * its business — and asks this which headings are eligible, so no caller
36
+ * re-decides it.
37
+ *
38
+ * @param {{level: number, id: string}} heading - From
39
+ * {@link scanHeadingAttributes}, or any `{level, id}` pair.
40
+ * @returns {boolean} Whether the heading opens a page.
41
+ */
42
+ export function opensPage({ level, id }: {
43
+ level: number;
44
+ id: string;
45
+ }): boolean;
46
+ /**
47
+ * Every heading in a body, with what its attribute block declares.
48
+ *
49
+ * Fenced code is skipped: `#` opens a comment in most of what gets fenced, and
50
+ * an example heading in a document is not a heading of it.
51
+ *
52
+ * @param {string} source - A note's markdown body.
53
+ * @param {number} [bodyLine=1] - The 1-based file line the body starts on, so a
54
+ * finding lands where an editor can open it.
55
+ * @returns {{headings: Array<{level: number, line: number, text: string,
56
+ * id: string, classes: string[], values: Record<string, string>}>,
57
+ * errors: Array<{line: number, column: number, message: string}>}}
58
+ */
59
+ export function scanHeadingAttributes(source: string, bodyLine?: number): {
60
+ headings: Array<{
61
+ level: number;
62
+ line: number;
63
+ text: string;
64
+ id: string;
65
+ classes: string[];
66
+ values: Record<string, string>;
67
+ }>;
68
+ errors: Array<{
69
+ line: number;
70
+ column: number;
71
+ message: string;
72
+ }>;
73
+ };
74
+ /**
75
+ * A markdown-it plugin writing a heading's attribute block onto its element.
76
+ *
77
+ * The website's renderer reads the block itself, so the surfaces that use
78
+ * markdown-it read it here and publish the same `id`, the same classes and the
79
+ * same attributes — rather than typesetting the braces an author wrote.
80
+ *
81
+ * @param {object} md - A markdown-it instance.
82
+ * @returns {void}
83
+ */
84
+ export function headingAttributesPlugin(md: object): void;
85
+ /**
86
+ * Parse one line as a heading, and say whether it starts a journal page.
87
+ *
88
+ * The single reading of that rule. {@link module:engine/journals.splitPages}
89
+ * builds its pages from it; `scanBlocks` and `scanFigures` refuse a heading
90
+ * that starts a page written where it cannot become one. All three ask this, so
91
+ * none of them can disagree about what a heading line means or about what starts
92
+ * a page — and the attribute block comes off here, so a class beside an anchor
93
+ * is not read as part of the anchor by any of them.
94
+ *
95
+ * @param {string} line - One line of a note's body.
96
+ * @returns {{level: number, text: string, id: string, classes: string[],
97
+ * values: Record<string, string>, problems: string[], startsPage: boolean}|null}
98
+ * `null` when the line is not a heading.
99
+ */
100
+ export function parseHeadingLine(line: string): {
101
+ level: number;
102
+ text: string;
103
+ id: string;
104
+ classes: string[];
105
+ values: Record<string, string>;
106
+ problems: string[];
107
+ startsPage: boolean;
108
+ } | null;
109
+ /**
110
+ * Every heading that opens a journal page, by the line it sits on.
111
+ *
112
+ * The walk that decides it: fenced code holds no headings, a named block holds
113
+ * no page — the block is one passage of the page it is written in — and what is
114
+ * left is read by {@link opensPage}. {@link module:engine/journals.splitPages}
115
+ * builds the pages from this, and the passes that honour a class on a
116
+ * page-opening heading ask the same question of the same answer.
117
+ *
118
+ * A figure fence also opens a page, which is `splitPages`' business: a fence is
119
+ * found by the figure pass, and the page it opens is named for its label.
120
+ *
121
+ * @param {string} source - A note's markdown body.
122
+ * @param {ReadonlySet<number>} [blockLines] - The 0-based lines sitting inside
123
+ * a named block, supplied by the caller. A named block is read by
124
+ * {@link module:engine/content-blocks.scanBlocks}, which knows every name in
125
+ * the registry, reads an opener's attribute block, and tracks nesting by
126
+ * depth — so the caller that already holds that answer passes it rather than
127
+ * a second reading of the same lines being kept here.
128
+ * @returns {Map<number, {level: number, line: number, text: string, id: string,
129
+ * classes: string[], values: Record<string, string>}>} Keyed by 0-based line.
130
+ */
131
+ export function pageOpenings(source: string, blockLines?: ReadonlySet<number>): Map<number, {
132
+ level: number;
133
+ line: number;
134
+ text: string;
135
+ id: string;
136
+ classes: string[];
137
+ values: Record<string, string>;
138
+ }>;
139
+ /**
140
+ * The spans a `.secret` heading withholds, and the classes written where no
141
+ * surface can honour them.
142
+ *
143
+ * A span runs from its heading to the line before the next heading that opens a
144
+ * page, which is the stretch Foundry makes one page of. A `.secret` on a heading
145
+ * that opens no page covers no page, so no surface can withhold it: that is a
146
+ * finding at the heading's own line rather than a class quietly ignored.
147
+ *
148
+ * @param {string} source - A note's markdown body.
149
+ * @param {number} [bodyLine=1] - The 1-based file line the body starts on.
150
+ * @returns {{sections: Array<{start: number, end: number, heading: object}>,
151
+ * errors: Array<{line: number, column: number, message: string}>}} `start` and
152
+ * `end` are 0-based line indexes, `end` exclusive.
153
+ */
154
+ export function withheldSections(source: string, bodyLine?: number): {
155
+ sections: Array<{
156
+ start: number;
157
+ end: number;
158
+ heading: object;
159
+ }>;
160
+ errors: Array<{
161
+ line: number;
162
+ column: number;
163
+ message: string;
164
+ }>;
165
+ };
166
+ /**
167
+ * A heading line: its hashes and the text after them.
168
+ *
169
+ * Shared with every pass that has to find a heading before reading what it
170
+ * declares, so the passes cannot disagree about which lines are headings.
171
+ */
172
+ export const HEADING_LINE: RegExp;
173
+ /**
174
+ * The class that withholds a section from players.
175
+ *
176
+ * Written on the heading that opens the section, so the section stays linkable:
177
+ * secrecy is a property of the section, not of the name a link resolves to.
178
+ */
179
+ export const WITHHELD_CLASS: "secret";
@@ -1,5 +1,21 @@
1
- /** Render a note body with Foundry's secret section markup. */
2
- export function renderFoundryMarkdown(body: any, captions: any, footnoteNumbers: any, docId: any): string;
1
+ /**
2
+ * Render a note body with Foundry's named-block markup.
3
+ *
4
+ * @param {string} body - The body, tables expanded and wikilinks resolved.
5
+ * @param {Array<{id: string, label: string}>} [figures] - Labels assigned by
6
+ * a note-wide scan, matched by id — see
7
+ * {@link module:engine/content-figures.renderFigureBlocks}.
8
+ * @param {Map<string, number>} [footnoteNumbers] - Shared across the note.
9
+ * @param {string} [docId] - Threaded into footnote anchors.
10
+ * @param {(address: string) => string|undefined} [resolveRole] - From a
11
+ * picture's address to the role its asset declares — see
12
+ * {@link module:engine/content-figures.scanFigures}.
13
+ * @returns {string} The rendered HTML.
14
+ */
15
+ export function renderFoundryMarkdown(body: string, figures?: Array<{
16
+ id: string;
17
+ label: string;
18
+ }>, footnoteNumbers?: Map<string, number>, docId?: string, resolveRole?: (address: string) => string | undefined): string;
3
19
  /**
4
20
  * Parses a markdown file with YAML frontmatter.
5
21
  *
@@ -206,11 +222,11 @@ export function makeFilename(name: string, id: string): string;
206
222
  * the other.
207
223
  *
208
224
  * **`title` does not follow this rule**, and must not be made to. On a
209
- * `type: affiliation` note `title` is *also* a declared item field whose default
210
- * is `""` (`sohl/item-fields.mjs`), resolved from the very same shared top-level
211
- * key the site emitter reads as the page title — so `title: null` stringifies
212
- * into the compiled document as the literal `"null"`. One key, two destinations
213
- * that disagree about what empty means.
225
+ * `type: affiliation` note `title` is a declared item field with its own
226
+ * default (`""`, in `sohl/item-fields.mjs`), authored only inside the item's
227
+ * own `sohl` block — the closed top-level vocabulary has no `title` entry, so
228
+ * there is no shared key for this function's null-versus-blank distinction to
229
+ * apply to.
214
230
  *
215
231
  * **`banner:` does not follow it either, deliberately.** It is not a file
216
232
  * inside a Foundry install: it reaches no compiled document and no book, its
@@ -426,18 +442,21 @@ export function convertNoteWikilinks(body: string, { type, id, pack, docPack, in
426
442
  unresolved: Array<object>;
427
443
  };
428
444
  /**
429
- * Expand prepared SQL tables before resolving wikilinks in generated cells.
445
+ * Expand prepared SQL tables and page lists before resolving wikilinks in the
446
+ * links they generate.
430
447
  * @param {string} body - The note body.
431
- * @param {object} ctx - Source and prepared query results.
448
+ * @param {object} ctx - Source and prepared results.
432
449
  * @param {string} ctx.name - Name used in diagnostics.
433
450
  * @param {number} [ctx.bodyLine] - The first body line in the source file.
434
451
  * @param {object[]} [ctx.sqlTables] - Prepared SQL results in document order.
452
+ * @param {object[]} [ctx.pageLists] - Prepared page lists in document order.
435
453
  * @returns {{markdown: string, lineMap: Array<{line: number, generated: boolean}>}}
436
454
  */
437
- export function expandNoteTables(body: string, { name, bodyLine, sqlTables }: {
455
+ export function expandNoteTables(body: string, { name, bodyLine, sqlTables, pageLists }: {
438
456
  name: string;
439
457
  bodyLine?: number | undefined;
440
458
  sqlTables?: object[] | undefined;
459
+ pageLists?: object[] | undefined;
441
460
  }): {
442
461
  markdown: string;
443
462
  lineMap: Array<{
@@ -119,7 +119,7 @@ export function checkHomepageCount(found: ReadonlyArray<{
119
119
  * `packageBuild.manifest.title` — the name Foundry already shows for the
120
120
  * package — so a homepage that adds nothing to it need not restate it. An
121
121
  * authored `name.full` wins, because a front page may greet a reader
122
- * differently from a package browser. A top-level `title` is also readable.
122
+ * differently from a package browser.
123
123
  *
124
124
  * Falls back to `contentPackage` last, so a package that has no manifest of its
125
125
  * own still yields a titled page rather than a blank heading.
@@ -244,12 +244,12 @@ export const HOMEPAGE_DESTINATION: string;
244
244
  * `name` and `shortcode` are permitted: the shortcode is what a link is
245
245
  * written with, and `name` titles the page like every other note's.
246
246
  *
247
- * **A named class, not an allow-list, and that boundary is the decision.** A
248
- * homepage's frontmatter is *emitted into the published page*
249
- * ({@link homepageFrontmatter}), so an unrecognised key is a Hugo or theme
250
- * parameter this build has never heard of and has no standing to refuse.
251
- * Rejecting unknown keys would make every new theme parameter wait on a
252
- * package-build release.
247
+ * **A named class, not an allow-list, and that boundary is the decision.**
248
+ * What this class answers is a key the top-level vocabulary *accepts* and a
249
+ * homepage still cannot use, which is a different question from an unknown
250
+ * key: the frontmatter lint refuses those for every note alike, naming the
251
+ * closed vocabulary, so repeating that judgement per type would be a second
252
+ * answer to one question.
253
253
  *
254
254
  * `aliases` is deliberately not in the class: it is a **retired** field, refused
255
255
  * on every note whatever its type, so it is answered there rather than
@@ -61,6 +61,7 @@ export * as contentCharset from "./content-charset.mjs";
61
61
  export * as contentIcons from "./content-icons.mjs";
62
62
  export * as contentHtml from "./content-html.mjs";
63
63
  export * as contentImages from "./content-images.mjs";
64
+ export * as contentTaskLists from "./content-tasklists.mjs";
64
65
  export * as contentEmbeds from "./content-embeds.mjs";
65
66
  export * as pathnames from "./pathnames.mjs";
66
67
  export * as contentLinks from "./content-links.mjs";
@@ -51,14 +51,17 @@ export function compilesSystemDocument(fm: object, map: object, router: object):
51
51
  * @param {object} [options.router] - The pack router deciding which system
52
52
  * compiles a document for this note. Defaults to the consuming repository's.
53
53
  * @param {object} [options.dates] - Calendar conversion context.
54
+ * @param {string} [options.contentPackage] - This build's content package,
55
+ * the default a note box's short-form Address resolves against.
54
56
  * @returns {object[]} The boxes, in the order every medium renders them.
55
57
  * @throws {Error} When the built set disagrees with what the note's type maps
56
58
  * to — see {@link module:engine/infobox.assertInfoboxSet}.
57
59
  */
58
- export function noteInfoboxes(fm: object, { resolve, router, dates }?: {
60
+ export function noteInfoboxes(fm: object, { resolve, router, dates, contentPackage }?: {
59
61
  resolve?: ((ref: unknown, hint?: object) => object | undefined) | undefined;
60
62
  router?: object | undefined;
61
63
  dates?: object | undefined;
64
+ contentPackage?: string | undefined;
62
65
  }): object[];
63
66
  /**
64
67
  * Every system's infobox declaration, in the order a page shows them.
@@ -54,12 +54,17 @@ export function sectionHasContent(section: object): boolean;
54
54
  * reference, so the link works on paper as a cross-reference and in a PDF
55
55
  * viewer as a jump. Everything else is set as its own words.
56
56
  *
57
+ * A panel's rows address pages the same way prose does, so a page the book does
58
+ * not print is set as the absolute URL a reader can follow — see
59
+ * {@link module:engine/pdf-render.absolutePageUrl}.
60
+ *
57
61
  * @param {object} value - A `link` value.
58
62
  * @param {Map<string, string>} links - Address slug → the book's anchor.
59
63
  * @param {(anchor: string) => string} labelFor - The anchor's Typst label.
64
+ * @param {string} [site] - The absolute address this book's pages are served at.
60
65
  * @returns {string} Typst markup.
61
66
  */
62
- export function linkToTypst(value: object, links: Map<string, string>, labelFor: (anchor: string) => string): string;
67
+ export function linkToTypst(value: object, links: Map<string, string>, labelFor: (anchor: string) => string, site?: string): string;
63
68
  /**
64
69
  * Every box as one Typst panel each, breaking between sections.
65
70
  *
@@ -154,12 +154,15 @@ export function linkValue(ref: unknown, resolve: (ref: unknown, hint?: object) =
154
154
  * @param {object} [options] - Options.
155
155
  * @param {(ref: unknown) => object|undefined} [options.resolve] - Resolves a
156
156
  * reference to `{name, url?, uuid?, address?}`.
157
+ * @param {string} [options.contentPackage] - This build's content package,
158
+ * the default a relation row's short-form Address resolves against.
157
159
  * @param {object} [options.vocabulary] - The note vocabulary to read.
158
160
  * @param {object} [options.presentation] - The overlay to read.
159
161
  * @returns {object} The box.
160
162
  */
161
163
  export function noteInfobox(fm: object, options?: {
162
164
  resolve?: ((ref: unknown) => object | undefined) | undefined;
165
+ contentPackage?: string | undefined;
163
166
  vocabulary?: object | undefined;
164
167
  presentation?: object | undefined;
165
168
  }): object;
@@ -268,6 +271,8 @@ export function systemRowsSection(fm: object, fields: readonly object[], { block
268
271
  * Resolves one declared field against the note.
269
272
  * @param {(ref: unknown) => object|undefined} [options.resolve] - Resolves a
270
273
  * reference to `{name, url?, uuid?, address?}`.
274
+ * @param {string} [options.contentPackage] - This build's content package,
275
+ * the default a note box's short-form Address resolves against.
271
276
  * @param {object} [options.vocabulary] - The note vocabulary to read.
272
277
  * @returns {object[]} The boxes.
273
278
  */
@@ -277,6 +282,7 @@ export function buildInfoboxes(fm: object, options: {
277
282
  compilesDocument: (fm: object, map: object) => boolean;
278
283
  resolveField: (field: object, fm: object, opts: object) => object;
279
284
  resolve?: ((ref: unknown) => object | undefined) | undefined;
285
+ contentPackage?: string | undefined;
280
286
  vocabulary?: object | undefined;
281
287
  }): object[];
282
288
  /**
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Splits a markdown body into pages by top-level H1 headings. Fenced
3
- * code blocks are respected so `# foo` inside ``` blocks doesn't trigger
4
- * a split. Content before the first H1 (if non-empty) becomes a leading
5
- * page. Each H1 yields a page whose name is the heading
3
+ * code blocks are respected so `# foo` inside ``` or ~~~ blocks doesn't
4
+ * trigger a split. Content before the first H1 (if non-empty) becomes a
5
+ * leading page. Each H1 yields a page whose name is the heading
6
6
  * text (with any `{#anchor-id}` suffix stripped out and surfaced as
7
- * `anchorSlug`).
7
+ * `anchorSlug`). The classes the heading declares come along as `classes`, so
8
+ * a surface that honours one has it without reading the suffix again.
8
9
  *
9
10
  * `leadName` names that leading page. A journal note's is "Introduction",
10
11
  * because it introduces the pages that follow. An item doc's is the item — a
@@ -12,15 +13,26 @@
12
13
  * calling that page "Introduction" would label the description as a preamble to
13
14
  * nothing.
14
15
  *
16
+ * A figure fence carries its own record along onto the page it starts, as
17
+ * `figure` — `null` for a page a heading started. {@link buildPages} reads it
18
+ * to build the `image` or `text` page a fence begins; nothing else sets it.
19
+ *
15
20
  * @param {string} body - Markdown body to split.
16
21
  * @param {string} [leadName] - Name of the page before the first heading.
22
+ * @param {(address: string) => string|undefined} [resolveRole] - From a
23
+ * picture's address to the role its asset declares, so a figure page's
24
+ * name reads `Map 1` rather than `Figure 1` where it is due — see
25
+ * {@link module:engine/content-figures.scanFigures}.
17
26
  * @returns {Array<{name: string, anchorSlug: string|null, level: number,
18
- * markdown: string}>} Pages in document order.
27
+ * classes: string[], figure: object|null, markdown: string}>} Pages in
28
+ * document order.
19
29
  */
20
- export function splitPages(body: string, leadName?: string): Array<{
30
+ export function splitPages(body: string, leadName?: string, resolveRole?: (address: string) => string | undefined): Array<{
21
31
  name: string;
22
32
  anchorSlug: string | null;
23
33
  level: number;
34
+ classes: string[];
35
+ figure: object | null;
24
36
  markdown: string;
25
37
  }>;
26
38
  /**
@@ -97,13 +109,24 @@ export function journalPageId(entryId: string, page: {
97
109
  * @param {Array<object>} rawPages - From {@link splitPages}.
98
110
  * @param {string} entryId - The owning JournalEntry's `_id`.
99
111
  * @param {string} noteName - The note, for error messages.
112
+ * A page whose heading carries `.secret` is given to the GM alone, by the one
113
+ * ownership value Foundry reads as "nobody but a GM". Every other page states
114
+ * no ownership and inherits the journal's. A figure-started page states none
115
+ * either: {@link scanFigures} validates a figure's classes against its own
116
+ * closed vocabulary, which carries no `.secret`.
117
+ *
118
+ * @param {Array<object>} figures - From {@link scanFigures}, numbered across
119
+ * the whole note. Read by a page a heading started; a page a figure started
120
+ * carries its own figure already, from {@link splitPages}.
121
+ * @param {(address: string) => string|undefined} [resolveRole] - See
122
+ * {@link splitPages}.
100
123
  * @returns {Array<{_id: string, name: string, type: string,
101
124
  * title: {show: boolean, level: number},
102
- * text: {format: number, content: string}, _key: string}>} The page
103
- * documents, in order.
125
+ * text: {format: number, content: string}, _key: string,
126
+ * ownership?: {default: number}}>} The page documents, in order.
104
127
  * @throws {Error} When the note has no content at all, or repeats an anchor.
105
128
  */
106
- export function buildPages(rawPages: Array<object>, entryId: string, noteName: string, captions: any): Array<{
129
+ export function buildPages(rawPages: Array<object>, entryId: string, noteName: string, figures: Array<object>, resolveRole?: (address: string) => string | undefined): Array<{
107
130
  _id: string;
108
131
  name: string;
109
132
  type: string;
@@ -116,6 +139,9 @@ export function buildPages(rawPages: Array<object>, entryId: string, noteName: s
116
139
  content: string;
117
140
  };
118
141
  _key: string;
142
+ ownership?: {
143
+ default: number;
144
+ };
119
145
  }>;
120
146
  /**
121
147
  * Assemble one JournalEntry document from a note's converted markdown.
@@ -133,7 +159,6 @@ export function buildPages(rawPages: Array<object>, entryId: string, noteName: s
133
159
  * @param {string} [params.leadName] - Name for the page before the first
134
160
  * heading; see {@link splitPages}.
135
161
  * @param {string|null} [params.folder] - The folder id, or `null`.
136
- * @param {object} [params.flags] - Document flags.
137
162
  * @param {object} [params.stats] - The `_stats` block to stamp. Passed by the
138
163
  * caller because it is a property of the *pack* being written, not of the
139
164
  * entry: a module may ship the same content for two systems, and each pack's
@@ -148,15 +173,18 @@ export function buildPages(rawPages: Array<object>, entryId: string, noteName: s
148
173
  * module:engine/draft-notice}. It leads the first authored page,
149
174
  * because a reader deciding whether to rely on the entry has to be told
150
175
  * before they read it rather than after.
176
+ * @param {(address: string) => string|undefined} [params.resolveRole] - From
177
+ * a picture's address to the role its asset declares, so a figure page's
178
+ * name reads `Map 1` rather than `Figure 1` where it is due — see
179
+ * {@link module:engine/content-figures.scanFigures}.
151
180
  * @returns {object} The JournalEntry document, keyed for the pack.
152
181
  */
153
- export function buildJournalEntry({ id, name, markdown, leadName, folder, flags, stats, infoboxes, notice, }: {
182
+ export function buildJournalEntry({ id, name, markdown, leadName, folder, stats, infoboxes, notice, resolveRole, }: {
154
183
  id: string;
155
184
  name: string;
156
185
  markdown: string;
157
186
  leadName?: string | undefined;
158
187
  folder?: string | null | undefined;
159
- flags?: object | undefined;
160
188
  stats?: object | undefined;
161
189
  infoboxes?: {
162
190
  id: string;
@@ -164,6 +192,7 @@ export function buildJournalEntry({ id, name, markdown, leadName, folder, flags,
164
192
  html: string;
165
193
  }[] | undefined;
166
194
  notice?: string | undefined;
195
+ resolveRole?: ((address: string) => string | undefined) | undefined;
167
196
  }): object;
168
197
  /**
169
198
  * Journals pack compiler.
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Validate one `lore` note's `data.events`.
3
+ *
4
+ * Registered as the `events` field's own `check` in `note-vocabulary.mjs`, so
5
+ * it runs on every `lore` note regardless of `subType` — an occasion is not a
6
+ * calendar fact, and nothing here is scoped to `subType: calendar`.
7
+ *
8
+ * @param {object} note - The note, as the link index hands it over.
9
+ * @param {{index?: object}} [options]
10
+ * @returns {object[]} Findings, each positioned at the entry that earned it.
11
+ */
12
+ export function checkLoreEvents(note: object, { index }?: {
13
+ index?: object;
14
+ }): object[];
@@ -3,15 +3,59 @@ export function sqlQueriesInMarkdown(body: any, fm?: {}): any[];
3
3
  /**
4
4
  * Expand scalar frontmatter references and registered functions in Markdown prose.
5
5
  * @param {string} body
6
- * @param {{fm?: object, dates?: object, sqlResults?: Map<string, object>, file?: string, bodyLine?: number}} [options]
6
+ * @param {{fm?: object, dates?: object, sqlResults?: Map<string, object>,
7
+ * figures?: {get: (id: string) => {label: string, caption: string,
8
+ * hasCaption: boolean}|undefined, note?: (address: string) =>
9
+ * {url: string|null, figures: Map<string, object>}|undefined,
10
+ * link?: Function}, file?: string, bodyLine?: number}} [options] -
11
+ * `figures` is the `ref` helper's own view of the corpus: `get` reads this
12
+ * note's `:::figure` fences by id, and `note` resolves another note's the
13
+ * way a wikilink would — omitted, a cross-note address is refused rather
14
+ * than looked up. `link` overrides how a resolved reference renders; the
15
+ * default is a Markdown link to the target's own anchor.
7
16
  */
8
- export function renderMarkdownExpressions(body: string, { fm, dates, sqlResults, file, bodyLine }?: {
17
+ export function renderMarkdownExpressions(body: string, { fm, dates, sqlResults, figures, file, bodyLine }?: {
9
18
  fm?: object;
10
19
  dates?: object;
11
20
  sqlResults?: Map<string, object>;
21
+ figures?: {
22
+ get: (id: string) => {
23
+ label: string;
24
+ caption: string;
25
+ hasCaption: boolean;
26
+ } | undefined;
27
+ note?: (address: string) => {
28
+ url: string | null;
29
+ figures: Map<string, object>;
30
+ } | undefined;
31
+ link?: Function;
32
+ };
12
33
  file?: string;
13
34
  bodyLine?: number;
14
35
  }): {
15
36
  markdown: string;
16
37
  findings: any[];
17
38
  };
39
+ /**
40
+ * An inline expression, as an author writes one.
41
+ *
42
+ * Hugo shortcodes begin `{{<` or `{{%` and stay in the source for Hugo.
43
+ *
44
+ * Exported because a renderer that meets one has met a body this pass never ran
45
+ * over, and has to be able to say so.
46
+ *
47
+ * @type {RegExp}
48
+ */
49
+ export const EXPRESSION: RegExp;
50
+ /**
51
+ * Each helper's parameters and what it does, keyed by name.
52
+ *
53
+ * The authoring document describes this surface, and a test holds the two
54
+ * together. A consumer reads it to list what an expression may call.
55
+ *
56
+ * @type {Readonly<Record<string, Readonly<{params: string, summary: string}>>>}
57
+ */
58
+ export const EXPRESSION_HELPERS: Readonly<Record<string, Readonly<{
59
+ params: string;
60
+ summary: string;
61
+ }>>>;
@@ -91,8 +91,71 @@ export function formatNoteDate(date: any, era: any, daysPerYear: any, formatName
91
91
  text: string;
92
92
  prose: string | null;
93
93
  } | null;
94
+ /**
95
+ * The occurrences of one recurring series overlapping a bound.
96
+ *
97
+ * **No materialized occurrence list.** An annual series anchored centuries
98
+ * back has thousands of occurrences to the present, and the only questions a
99
+ * build asks are "is this year one of them" and "which is next" — so an
100
+ * omitted bound answers with a single occurrence rather than every one a live
101
+ * series could have:
102
+ *
103
+ * - `to` omitted: the first occurrence at or after `from` (default, the
104
+ * anchor's own year) — "next".
105
+ * - `from` omitted, `to` given: the last occurrence at or before `to` —
106
+ * "the greatest occurrence at or before `until`".
107
+ * - Both given: every occurrence between them, inclusive — a membership
108
+ * probe is `from === to`.
109
+ *
110
+ * **The lower bound is not optional.** A candidate year before the anchor's
111
+ * own year is never an occurrence, however a bare modulo would read it: `%`
112
+ * takes the sign of its dividend in both JavaScript and SQL, so a year
113
+ * exactly one period before the anchor would otherwise match. `k` is clamped
114
+ * at zero rather than left to run negative.
115
+ *
116
+ * **The period advances the canonical year, never the era-relative year.**
117
+ * `date.canonicalYear` is already on the continuous axis `canonicalYear`
118
+ * builds — era year `-1` sits immediately before era year `1`, but canonical
119
+ * year `0` is an ordinary integer between them — so stepping by whole
120
+ * multiples of `every` on `canonicalYear` crosses an epoch with no special
121
+ * case. Counting in era-relative years instead would need a correction at
122
+ * every crossing and silently loses one year each time it is forgotten.
123
+ *
124
+ * **Precision, approximation and the day of year carry through untouched.**
125
+ * Every occurrence is the anchor's own record with only `canonicalYear`
126
+ * advanced, so a day-precision anchor's `canonicalDay` — including one inside
127
+ * a short intercalary month — names the same day of every occurrence, and an
128
+ * approximate anchor's occurrences are approximate too.
129
+ *
130
+ * @param {object} date - The anchor's resolved record
131
+ * ({@link parseNoteDate}), carrying a real `canonicalYear`. A year-0 "any
132
+ * year" anchor carries none and has no series through this function — its
133
+ * `recurs` is refused, and its own next occurrence is computed directly
134
+ * from the day of year alone.
135
+ * @param {{every?: number, on?: object[]}} [recurs] - The declared
136
+ * recurrence: a whole-year period, or an enumeration of further resolved
137
+ * date records (each already carrying its own `canonicalYear`). Neither
138
+ * admits the other.
139
+ * @param {object} [bounds]
140
+ * @param {number} [bounds.from] - The lower bound, inclusive, as a canonical
141
+ * year.
142
+ * @param {number} [bounds.to] - The upper bound, inclusive, as a canonical
143
+ * year.
144
+ * @returns {object[]} Occurrence records, in increasing order.
145
+ */
146
+ export function occurrencesOf(date: object, recurs?: {
147
+ every?: number;
148
+ on?: object[];
149
+ }, { from, to }?: {
150
+ from?: number | undefined;
151
+ to?: number | undefined;
152
+ }): object[];
94
153
  /** Normalize a note's declared dates for indexes and generated pages. */
95
- export function resolvedDateFields(fm: any, context: any): {};
154
+ export function resolvedDateFields(fm: any, context: any): {
155
+ events: any;
156
+ } | {
157
+ events?: undefined;
158
+ };
96
159
  /**
97
160
  * The literal that says a thing happened and the date is not recorded.
98
161
  *
@@ -1,10 +1,7 @@
1
1
  /** Return authored keys from an opening YAML frontmatter map. */
2
2
  export function authoredNoteKeys(markdown: any): string[];
3
- /** The complete ordered top-level vocabulary for addressed content notes. */
4
- export const NOTE_TOP_LEVEL_KEYS: readonly string[];
5
- /** Membership test for the top-level vocabulary. */
6
- export const NOTE_TOP_LEVEL_KEY_SET: Set<string>;
7
3
  /** The name properties every note accepts, in display order. */
8
4
  export const NOTE_NAME_KEYS: readonly string[];
9
5
  /** Additional name components accepted by an individual character. */
10
6
  export const CHARACTER_NAME_KEYS: readonly string[];
7
+ export { NOTE_TOP_LEVEL_FIELDS, NOTE_TOP_LEVEL_KEYS, NOTE_TOP_LEVEL_KEY_SET } from "./note-vocabulary.mjs";