@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
@@ -42,8 +42,6 @@ export function exclusiveTagGroups(type: string, groups?: object): {
42
42
  * The spelling of the tag *itself* still is — a near miss is a near miss, and
43
43
  * the frontmatter lint is what reports it; nothing here guesses.
44
44
  *
45
- * Reads `tags` and the singular `tag` spelling.
46
- *
47
45
  * @param {object|null|undefined} fm - Parsed frontmatter.
48
46
  * @param {string} tag - The tag to look for, in its declared spelling.
49
47
  * @returns {boolean} Whether the note carries it.
@@ -135,6 +133,59 @@ export function dataFields(type: string, vocabulary?: Readonly<Record<string, Ty
135
133
  * it has no `subType` at all — see {@link TypeVocabulary}.
136
134
  */
137
135
  export function subTypes(type: string, vocabulary?: Readonly<Record<string, TypeVocabulary>>): readonly string[] | null | undefined;
136
+ /**
137
+ * One top-level frontmatter key.
138
+ *
139
+ * Narrower than a {@link DataFieldSpec}: the top level is a fixed list rather
140
+ * than a per-type vocabulary, nothing there is keyed by pack or resolved as an
141
+ * Address, and the value's shape is checked by whatever owns the region the key
142
+ * opens — `name`, `tags` and each system block each have their own check.
143
+ *
144
+ * @typedef {object} TopLevelFieldSpec
145
+ * @property {string} name - The key, as a note writes it.
146
+ * @property {boolean} [system] - The key opens a game system's block, so it is
147
+ * one of {@link module:engine/systems.SYSTEM_IDS} rather than a fixed key.
148
+ * @property {string} describe - One line, for the author-facing reference.
149
+ */
150
+ /**
151
+ * **The top-level vocabulary**, in the order a formatted note writes it.
152
+ *
153
+ * The region describes the note as a published artefact: what it is called,
154
+ * what kind of thing it is about, how it is classified, and which of the other
155
+ * two regions it opens. Everything else a note states is a fact about the
156
+ * subject, which belongs under `data:`, or a fact about one system's document,
157
+ * which belongs inside that system's block.
158
+ *
159
+ * **Closed, like the other two regions.** A key this list does not name is a
160
+ * finding at its own line, because a region that passed one through would turn
161
+ * a misspelling into a value nothing reads — `title` beside `name`, or `pack`
162
+ * where `data.pack` was meant.
163
+ *
164
+ * **The system blocks are derived from the system registry**, so recognising a
165
+ * further system is a change to {@link module:engine/systems.SYSTEM_IDS} and to
166
+ * nothing else. They sort by name after the fixed keys: a block is the largest
167
+ * thing a note writes, every one of them belongs at the end, and a rule a
168
+ * reader can restate in one sentence is worth more than a hand-kept order.
169
+ *
170
+ * Five readers derive from this one list — the frontmatter lint's accepted set,
171
+ * the message it prints, the formatter's key order, the content-format check,
172
+ * and the author-facing reference — so none of them can fall behind the others.
173
+ *
174
+ * @type {readonly TopLevelFieldSpec[]}
175
+ */
176
+ export const NOTE_TOP_LEVEL_FIELDS: readonly TopLevelFieldSpec[];
177
+ /**
178
+ * The top-level keys, in formatted order.
179
+ *
180
+ * @type {readonly string[]}
181
+ */
182
+ export const NOTE_TOP_LEVEL_KEYS: readonly string[];
183
+ /**
184
+ * Membership test for the top-level vocabulary.
185
+ *
186
+ * @type {ReadonlySet<string>}
187
+ */
188
+ export const NOTE_TOP_LEVEL_KEY_SET: ReadonlySet<string>;
138
189
  /**
139
190
  * The `data:` keys **every** note type accepts, whatever it is.
140
191
  *
@@ -174,15 +225,17 @@ export const GM_TAG: "gm";
174
225
  /**
175
226
  * The tags that **classify** a note, grouped by what they classify.
176
227
  *
177
- * `tags:` lives at the open top level and most tags belong there: a theme, a
178
- * region, a working state is the author's own and this build has no opinion
179
- * about it. A classifying tag is different, because something queries it — a
228
+ * `tags:` holds an open vocabulary of *values*, and most tags belong there: a
229
+ * theme, a region, a working state is the author's own and this build has no
230
+ * opinion about it. A classifying tag is different, because something queries
231
+ * it — a
180
232
  * settlement tagged `village` appears in the list of villages and an untagged
181
233
  * one does not, so `vilage` does not merely look wrong, it removes the note from
182
234
  * an index while the index still renders a table that looks complete.
183
235
  *
184
236
  * **This list is not a closed set.** An unrecognised tag is legal, because the
185
- * region is open; what is reported is a **near miss** — a tag close enough to a
237
+ * values are the author's; what is reported is a **near miss** — a tag close
238
+ * enough to a
186
239
  * declared one to be a typo of it.
187
240
  *
188
241
  * **Each group names the types it applies to**, and that scope is what makes the
@@ -418,3 +471,26 @@ export type TypeVocabulary = {
418
471
  index?: object;
419
472
  }) => object[]) | undefined;
420
473
  };
474
+ /**
475
+ * One top-level frontmatter key.
476
+ *
477
+ * Narrower than a {@link DataFieldSpec}: the top level is a fixed list rather
478
+ * than a per-type vocabulary, nothing there is keyed by pack or resolved as an
479
+ * Address, and the value's shape is checked by whatever owns the region the key
480
+ * opens — `name`, `tags` and each system block each have their own check.
481
+ */
482
+ export type TopLevelFieldSpec = {
483
+ /**
484
+ * - The key, as a note writes it.
485
+ */
486
+ name: string;
487
+ /**
488
+ * - The key opens a game system's block, so it is
489
+ * one of {@link module :engine/systems.SYSTEM_IDS} rather than a fixed key.
490
+ */
491
+ system?: boolean | undefined;
492
+ /**
493
+ * - One line, for the author-facing reference.
494
+ */
495
+ describe: string;
496
+ };
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Every `pagelist` fence in a markdown body, with the position each occupies.
3
+ *
4
+ * Positions are 0-based lines into the body as given, which is what a
5
+ * diagnostic about a directive needs and what the expander uses to splice
6
+ * results back in.
7
+ *
8
+ * **A malformed directive is still a directive.** It is returned carrying its
9
+ * problems rather than skipped, so the expander finds a prepared answer at its
10
+ * ordinal and reports the fault where the fence sits. That includes a fence
11
+ * nobody closed, which the author meant as a directive and which would
12
+ * otherwise swallow the rest of the note into a code block.
13
+ *
14
+ * @param {string} markdown - The note body, frontmatter already stripped.
15
+ * @returns {Array<{line: number, close: number, indent: string, tag: string,
16
+ * type: string, sort: string, descriptions: boolean, allowEmpty: boolean,
17
+ * problems: string[], block: string}>} One entry per fence, in document order.
18
+ */
19
+ export function findPageListBlocks(markdown: string): Array<{
20
+ line: number;
21
+ close: number;
22
+ indent: string;
23
+ tag: string;
24
+ type: string;
25
+ sort: string;
26
+ descriptions: boolean;
27
+ allowEmpty: boolean;
28
+ problems: string[];
29
+ block: string;
30
+ }>;
31
+ /**
32
+ * The pages a directive selects, as index records.
33
+ *
34
+ * A **stub** is left out: an empty body publishes no page, which the index says
35
+ * by holding no address, so there is nothing for the list to link to. A
36
+ * documentation journal and an asset are not pages an author tagged, which
37
+ * {@link module:engine/index-records.isNoteRecord} already answers.
38
+ *
39
+ * @param {object[]} records - Content-index records.
40
+ * @param {{tag: string, type: string}} directive - From {@link findPageListBlocks}.
41
+ * @returns {object[]} The selected records, unordered.
42
+ */
43
+ export function pagesForList(records: object[], { tag, type }: {
44
+ tag: string;
45
+ type: string;
46
+ }): object[];
47
+ /**
48
+ * Render one directive's answer as markdown.
49
+ *
50
+ * **The emitted link is the page's `address.slug`** — the `type-shortcode`
51
+ * form a wikilink resolves — which is exactly what a table's `_ref` cell
52
+ * writes. A short form names the package the link is written in, so it is
53
+ * unambiguous for a list of this package's own pages, and it resolves to the
54
+ * note's **page** on each surface: a compendium journal in Foundry, a URL on
55
+ * the web and in the book. An item note's page is its documentation journal,
56
+ * and the short form reaches it the way prose does.
57
+ *
58
+ * @param {object[]} records - Content-index records.
59
+ * @param {object} directive - From {@link findPageListBlocks}.
60
+ * @returns {{markdown: string, pages: number}} The list, and how many pages
61
+ * are in it.
62
+ */
63
+ export function renderPageList(records: object[], directive: object): {
64
+ markdown: string;
65
+ pages: number;
66
+ };
67
+ /**
68
+ * Answer every `pagelist` directive in a set of note bodies, ahead of expansion.
69
+ *
70
+ * **Why a separate pass.** The same reason the SQL tables have one:
71
+ * {@link module:engine/content-tables.expandContentTables} is synchronous and
72
+ * exported, and the corpus is enumerated once by whoever already holds it
73
+ * rather than derived again inside a renderer. A page list needs no database —
74
+ * it is a filter over the records — so preparing one costs a walk of what the
75
+ * caller already has.
76
+ *
77
+ * **Keyed by note, then by the directive's ordinal within it**, not by its
78
+ * line: the passes do not agree on what a body is, and trim it differently,
79
+ * but a directive's position in the sequence of fences is the same in each.
80
+ *
81
+ * A malformed directive is recorded rather than thrown, so one bad fence costs
82
+ * its own list and not the whole build's report.
83
+ *
84
+ * @param {object[]} records - Content-index records, already narrowed to the
85
+ * audience the surface publishes to.
86
+ * @param {Array<{source: string, markdown: string}>} sources - The bodies to scan.
87
+ * @returns {Map<string, Array<{markdown?: string, pages?: number, empty?: string,
88
+ * reason?: string, allowEmpty: boolean}>>} Note to results, in document order.
89
+ */
90
+ export function preparePageLists(records: object[], sources: Array<{
91
+ source: string;
92
+ markdown: string;
93
+ }>): Map<string, Array<{
94
+ markdown?: string;
95
+ pages?: number;
96
+ empty?: string;
97
+ reason?: string;
98
+ allowEmpty: boolean;
99
+ }>>;
100
+ /** The fence language that opens a page list. */
101
+ export const PAGE_LIST_LANGUAGE: "pagelist";
102
+ /**
103
+ * The orders a page list may come out in.
104
+ *
105
+ * Ordering is about output rather than about the pages, so it is an attribute
106
+ * rather than something read off a note. `name` is alphabetical, case and
107
+ * accents folded; `type` groups the note types and orders by name inside each.
108
+ *
109
+ * @type {readonly string[]}
110
+ */
111
+ export const PAGE_LIST_SORTS: readonly string[];
112
+ /**
113
+ * The attributes a page-list fence takes, with what each one is for.
114
+ *
115
+ * **This is the one list.** The fence is validated against it, the authoring
116
+ * document's table is generated from it, and the reference is checked against
117
+ * it — so an attribute added here is one both documents describe, and anything
118
+ * else an author writes is reported by name.
119
+ *
120
+ * @type {Readonly<Record<string, Readonly<{value: string, default: string, summary: string}>>>}
121
+ */
122
+ export const PAGE_LIST_ATTRIBUTES: Readonly<Record<string, Readonly<{
123
+ value: string;
124
+ default: string;
125
+ summary: string;
126
+ }>>>;
@@ -82,6 +82,32 @@ export function packageAddresses(config: object): Map<string, {
82
82
  * resolves on one surface and nowhere else.
83
83
  */
84
84
  export function resolvePathname(raw: string | null | undefined, config: object): PathnameForms | null;
85
+ /**
86
+ * The pathname one authored image address keys an asset's role and pixel
87
+ * size under — `<package>/assets/<suffix>`, the same key
88
+ * {@link module:engine/art-fields.assetImageInfoByPathname} builds from the
89
+ * asset record itself.
90
+ *
91
+ * **Why this exists apart from `resolvePathname`'s own forms.** An embed
92
+ * rewrites to that exact pathname — see
93
+ * {@link module:engine/content-embeds.resolveEmbeds} — but an author's own
94
+ * body image is free to write the bare, own-package form instead
95
+ * (`images/m.webp`), which names the identical file without naming the
96
+ * package. The two forms have to key the same lookup, and `resolvePathname`
97
+ * already answers which package a bare form belongs to; this just restates
98
+ * that answer in the one shape a renderer looks a picture's role up by.
99
+ *
100
+ * **Never throws.** A lookup is a best-effort read, not a validation — the
101
+ * pathname lint is what refuses a Foundry-spelled address — so a value
102
+ * {@link resolvePathname} refuses outright answers `null` here as well, the
103
+ * same as a blank or an external one.
104
+ *
105
+ * @param {string} src - The address, as authored or as an embed resolved it.
106
+ * @param {object} config - The resolved build configuration.
107
+ * @returns {string|null} The pathname, or `null` for an address naming no
108
+ * asset record to look up.
109
+ */
110
+ export function assetPathnameKey(src: string, config: object): string | null;
85
111
  /**
86
112
  * The directory a package ships its files in, and the segment that marks a
87
113
  * pathname's first segment as a package name.
@@ -13,17 +13,34 @@
13
13
  * makes the source a consumer can compile by hand with no flags, and what keeps
14
14
  * a build from touching a path outside its own output.
15
15
  *
16
- * Only a file **this** package ships can be staged. A pathname naming another
17
- * package's file, or a URL, names something no build here can open — a build
18
- * reaches no network — and the caller reports it as a picture the book will not
19
- * carry.
16
+ * A file **this** package ships stages directly. A pathname naming another
17
+ * package's file stages instead from a declared replacement's cached archive,
18
+ * when `opts.resolveReplacement` — the pure function
19
+ * {@link module:engine/asset-replacement.resolveAssetReplacement} —
20
+ * answers it; `opts.foreignIndex` is the merged index that answer is read
21
+ * against. A pathname naming a package no replacement carries it for, or a
22
+ * URL, names something no build here can open — a build reaches no network —
23
+ * and the caller reports it as a picture the book will not carry.
20
24
  *
21
25
  * @param {string} src - The pathname, as authored.
22
26
  * @param {object} config - The resolved configuration.
27
+ * @param {object} [opts]
28
+ * @param {Map<string, object>} [opts.foreignIndex] - Every fetched
29
+ * dependency's own records, keyed by canonical address — the index a
30
+ * replacement's record is read out of.
31
+ * @param {Function} [opts.resolveReplacement] - {@link
32
+ * module:engine/asset-replacement.resolveAssetReplacement}, or an
33
+ * equivalent a caller injects for a test. Omitted, a foreign pathname this
34
+ * package does not own stays declined exactly as it always has.
23
35
  * @returns {{from: string, to: string}|null} The file, and where under the
24
36
  * output directory it is staged.
37
+ * @throws {Error} When a declared replacement answers the address but its
38
+ * archive was never fetched.
25
39
  */
26
- export function stagedImagePath(src: string, config: object): {
40
+ export function stagedImagePath(src: string, config: object, opts?: {
41
+ foreignIndex?: Map<string, object> | undefined;
42
+ resolveReplacement?: Function | undefined;
43
+ }): {
27
44
  from: string;
28
45
  to: string;
29
46
  } | null;
@@ -66,13 +83,18 @@ export function pdfFileName(artifact: string, version: string): string;
66
83
  * @param {string} [opts.version] - Stamped on the title page and the file name.
67
84
  * @param {boolean} [opts.compile] - Whether to run Typst. False leaves the
68
85
  * `.typ` source, which is what the unit tests read.
86
+ * @param {Function} [opts.resolveReplacement] - {@link
87
+ * module:engine/asset-replacement.resolveAssetReplacement}, for tests.
88
+ * Loaded from that module otherwise, and only when a relationship declares
89
+ * `assetReplacement: true` — a build with none never imports it.
69
90
  * @returns {Promise<object>} `{ built, reason, findings, typ, pdf, stats }`.
70
91
  */
71
- export function buildPdf({ config, out, version, compile }?: {
92
+ export function buildPdf({ config, out, version, compile, resolveReplacement, }?: {
72
93
  config?: object | undefined;
73
94
  out?: string | undefined;
74
95
  version?: string | undefined;
75
96
  compile?: boolean | undefined;
97
+ resolveReplacement?: Function | undefined;
76
98
  }): Promise<object>;
77
99
  /**
78
100
  * The command line the compile runs, as data.
@@ -1,3 +1,33 @@
1
+ /**
2
+ * The width the book draws one picture at, in inches, when nothing authored
3
+ * narrows it to a named `size=`.
4
+ *
5
+ * > drawn width = min(the role's slot, natural pixels ÷ the print floor)
6
+ *
7
+ * **Every size here is a maximum, never a target**: a picture is never drawn
8
+ * larger than it was made. The print floor is {@link PDF_PAGE.dpi}, the same
9
+ * constant the page geometry above is set against, so a role's slot and the
10
+ * page it is drawn on cannot disagree about what a pixel is worth.
11
+ *
12
+ * An icon-type address takes the nominal icon width whatever role it might
13
+ * otherwise carry — `role` never reaches one, since the asset record refuses
14
+ * it there. A vector carries no pixel count, so nothing narrows its role's
15
+ * slot. A picture declaring no role answers `undefined`: the medium's
16
+ * ordinary size, decided elsewhere by a named `size=` or by its own natural
17
+ * pixels within whatever it is set in.
18
+ *
19
+ * @param {object} [asset] - What {@link module:engine/art-fields.assetImageInfoByPathname}
20
+ * records for the picture's address.
21
+ * @param {string} [asset.type] - The asset's type — `icon` or `image`.
22
+ * @param {string} [asset.role] - The picture's declared role.
23
+ * @param {number|""} [asset.width] - The file's own pixel width; blank for a vector.
24
+ * @returns {number|undefined} The drawn width, in inches.
25
+ */
26
+ export function bookImageWidthIn({ type, role, width }?: {
27
+ type?: string | undefined;
28
+ role?: string | undefined;
29
+ width?: number | "" | undefined;
30
+ }): number | undefined;
1
31
  /**
2
32
  * Optimize staged raster images without changing their authored files.
3
33
  *
@@ -36,3 +66,25 @@ export const pdfTextWidth: number;
36
66
  /** Usable height of a portrait page in inches. */
37
67
  export const pdfTextHeight: number;
38
68
  export function pdfColumnWidth(columns?: number): number;
69
+ /**
70
+ * The fraction of the page's one-column measure a picture's role may fill at
71
+ * most — one table, read by {@link bookImageWidthIn}, so a policy change here
72
+ * is the one line it costs rather than an edit to every note drawing a role.
73
+ *
74
+ * A `portrait` or an `emblem` names a subject or a mark, which sits beside
75
+ * prose rather than replacing a page of it; a `banner`, a `plate` and a `map`
76
+ * are each meant to be looked at in full. The measure is the page's own text
77
+ * width, {@link pdfTextWidth}, so a role's slot does not shrink when a page
78
+ * happens to be set in two columns — it is what the picture is *for* that
79
+ * sets the slot, not the layout it lands in.
80
+ *
81
+ * @type {Readonly<Record<string, number>>}
82
+ */
83
+ export const BOOK_ROLE_SLOTS: Readonly<Record<string, number>>;
84
+ /**
85
+ * The book's nominal width for an icon-type picture, in inches, whatever its
86
+ * file holds — almost every icon is an SVG with no pixel count to measure.
87
+ *
88
+ * @type {number}
89
+ */
90
+ export const BOOK_ICON_WIDTH_IN: number;
@@ -37,6 +37,24 @@ export function labelFor(anchor: string): string;
37
37
  * @returns {object} A markdown-it instance.
38
38
  */
39
39
  export function createParser(registry?: object): object;
40
+ /**
41
+ * A markdown body with its HTML comments taken out.
42
+ *
43
+ * A comment is an author's aside to the next author — a `markdownlint` pragma, a
44
+ * note recording why a notice is quoted verbatim — and no surface shows one: the
45
+ * packs and the website emit it into HTML, where it is a comment still. Typst
46
+ * has nowhere to put it, so markdown-it hands the delimiters over as prose and
47
+ * the aside is typeset. It comes out here instead, and a comment inside a fence
48
+ * or a code span stays, because there it is an example of a comment.
49
+ *
50
+ * **The line count is preserved.** Every finding this module reports is located
51
+ * in the markdown it was handed, so a pass that removed a line would move every
52
+ * position after it.
53
+ *
54
+ * @param {string} markdown - The body, as authored.
55
+ * @returns {string} The same body, its comments replaced by their own newlines.
56
+ */
57
+ export function stripHtmlComments(markdown: string): string;
40
58
  /**
41
59
  * Render markdown as Typst content.
42
60
  *
@@ -51,12 +69,36 @@ export function createParser(registry?: object): object;
51
69
  * the staged file's path, relative to the `.typ`. An address this does not
52
70
  * carry has no file the compiler can open, so the figure prints its caption
53
71
  * alone — see {@link renderImage}.
72
+ * @param {Map<string, {type: string, role?: string, width: number|"", height: number|""}>}
73
+ * [opts.assets] - An image's address as authored → what
74
+ * {@link module:engine/art-fields.assetImageInfoByPathname} records for it,
75
+ * which is what sizes a picture carrying no named `size=` — see
76
+ * {@link module:engine/pdf-images.bookImageWidthIn}. An address missing from
77
+ * this map draws at the medium's ordinary size, the same as today.
54
78
  * @param {number} [opts.headingOffset] - Added to every heading level, so a
55
79
  * note's own `##` nests beneath the entry heading the book gave it.
56
80
  * @param {string} [opts.anchorPrefix] - The entry's anchor, which namespaces
57
81
  * every `{#slug}` the body declares.
58
- * capital. Set for an entry, which begins a page; not for front matter or a
59
- * prose file, which carry headings of their own.
82
+ * @param {object[]} [opts.findings] - Collected here rather than thrown. Every
83
+ * construct this pass cannot render is reported into it — see
84
+ * {@link reportUnrenderable} — and a caller that passes none renders the same
85
+ * Typst and is told nothing.
86
+ * @param {string} [opts.file] - The file the markdown came from, named in every
87
+ * finding.
88
+ * @param {number} [opts.bodyLine] - The 1-based file line the body starts on, so
89
+ * a finding's line is the file's rather than the body's.
90
+ * @param {number} [opts.bodyColumn] - The 1-based column the body starts at.
91
+ * @param {Array<{line: number, generated: boolean}>} [opts.lineMap] - From
92
+ * table expansion, mapping each line handed in back to the line an author
93
+ * wrote.
94
+ * @param {boolean} [opts.prepared] - Whether this body has been through the
95
+ * passes that resolve a wikilink, an embed and an inline expression. A note's
96
+ * has; a front-matter file's and a prose file's have not, and markup of theirs
97
+ * left in one is reported rather than typeset in silence.
98
+ * @param {string} [opts.url] - The absolute address this book's pages are served
99
+ * at. Every link the book sets as a URL is resolved against it, because a
100
+ * reader holding a PDF has no page to resolve a path against — see
101
+ * {@link renderLink}.
60
102
  * @returns {string} Typst markup.
61
103
  */
62
104
  export function markdownToTypst(markdown: string, opts?: {
@@ -65,9 +107,49 @@ export function markdownToTypst(markdown: string, opts?: {
65
107
  links?: Map<string, string> | undefined;
66
108
  glyphs?: Map<string, string> | undefined;
67
109
  images?: Map<string, string> | undefined;
110
+ assets?: Map<string, {
111
+ type: string;
112
+ role?: string;
113
+ width: number | "";
114
+ height: number | "";
115
+ }> | undefined;
68
116
  headingOffset?: number | undefined;
69
117
  anchorPrefix?: string | undefined;
118
+ findings?: object[] | undefined;
119
+ file?: string | undefined;
120
+ bodyLine?: number | undefined;
121
+ bodyColumn?: number | undefined;
122
+ lineMap?: {
123
+ line: number;
124
+ generated: boolean;
125
+ }[] | undefined;
126
+ prepared?: boolean | undefined;
127
+ url?: string | undefined;
70
128
  }): string;
129
+ /**
130
+ * A page address as a reader of the book can follow it.
131
+ *
132
+ * **Every link the book sets as a URL is absolute.** A page's address is written
133
+ * from the root of the site that serves it — `/sohl/skill-guil/` — because on the
134
+ * website that is the shortest form that is right wherever the page is read
135
+ * from. A PDF is read from nowhere: a viewer handed a path has no document to
136
+ * resolve it against, so it either does nothing or looks for a file on the
137
+ * reader's own disk, and a book of a thousand pages carries a thousand links
138
+ * that do neither.
139
+ *
140
+ * So a root-relative address is resolved against the site's own, which is the
141
+ * one absolute address a build already holds. A **dependency's** page keeps its
142
+ * own package prefix, from the registry in
143
+ * {@link module:engine/content-address.PACKAGE_BASE}, so it resolves to the host
144
+ * serving that package — and where the registry names a host of its own the
145
+ * address is already absolute and is left exactly as it is.
146
+ *
147
+ * @param {string} url - The address, as the link pass resolved it.
148
+ * @param {string} [site] - The absolute address this book's pages are served at.
149
+ * @returns {string} An absolute URL, or the address unchanged when there is no
150
+ * site to resolve it against or it is already absolute.
151
+ */
152
+ export function absolutePageUrl(url: string, site?: string): string;
71
153
  /**
72
154
  * The Typst definitions the book's page furniture is drawn with.
73
155
  *
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Check one affiliation's `data.governance.ranks` against the rung contract.
3
+ *
4
+ * Each problem is its own finding, so a rung missing all three of its required
5
+ * keys reports three rather than the first: a reader fixing a ladder wants the
6
+ * whole of it in one pass.
7
+ *
8
+ * **A finding is located by the node it is about**, through the frontmatter's
9
+ * own YAML tree — a present-but-wrong value points at that value, and an absent
10
+ * key points at the rung that should have carried it, since a key that was
11
+ * never written has no position of its own. Where even the rung cannot be
12
+ * located the position is dropped rather than guessed, and the finding names
13
+ * the file alone.
14
+ *
15
+ * @param {object} note - The note being checked.
16
+ * @param {string} note.file - Its path, as a diagnostic spells it.
17
+ * @param {string} [note.raw] - Its source, for the position.
18
+ * @param {object} [note.fm] - Its parsed frontmatter.
19
+ * @returns {Array<{file: string, line?: number, column?: number,
20
+ * severity: string, message: string}>} The findings, in authored order.
21
+ */
22
+ export function checkRankLadder(note: {
23
+ file: string;
24
+ raw?: string | undefined;
25
+ fm?: object | undefined;
26
+ }): Array<{
27
+ file: string;
28
+ line?: number;
29
+ column?: number;
30
+ severity: string;
31
+ message: string;
32
+ }>;
@@ -256,12 +256,10 @@ export function legacyKeyMessage(block: string, field: {
256
256
  * What a note writing a field at the **top-level key `data:` gathered it off**
257
257
  * is told.
258
258
  *
259
- * {@link legacyKeyMessage}'s counterpart for the other retiring position. The
260
- * sweep
261
- * did not invent the facts `data:` holds — it collected them out of the note's
262
- * open top level — so `portrait:` beside `img:` is the *pre-`data:`* spelling
263
- * of `data.portrait`, and both are read for the same reason both in-block
264
- * spellings are: a package moves its corpus when it is ready, not on a flag day.
259
+ * {@link legacyKeyMessage}'s counterpart for the other retiring position.
260
+ * `portrait:` beside `img:` is the bare spelling of `data.portrait`, and both
261
+ * are read for the same reason both in-block spellings are: a package moves its
262
+ * corpus when it is ready, not on a flag day.
265
263
  *
266
264
  * It says nothing about which value is emitted, because that is not what an
267
265
  * author needs from it. The value is the same either way; what the finding
@@ -237,7 +237,7 @@ export function pageDestination(page: {
237
237
  bundle?: boolean | undefined;
238
238
  }): string;
239
239
  /** Transform one content page without writing the site mount. */
240
- export function renderSitePage(page: any, { index, foreign, universe, pass, decorate, linkable, sqlTables, config, artIndex, }: {
240
+ export function renderSitePage(page: any, { index, foreign, universe, pass, decorate, linkable, sqlTables, config, artIndex, figuresByAddress, }: {
241
241
  index: any;
242
242
  foreign: any;
243
243
  universe: any;
@@ -247,9 +247,10 @@ export function renderSitePage(page: any, { index, foreign, universe, pass, deco
247
247
  sqlTables: any;
248
248
  config: any;
249
249
  artIndex: any;
250
+ figuresByAddress?: Map<any, any> | undefined;
250
251
  }): {
251
252
  page: any;
252
- body: any;
253
+ body: string;
253
254
  data: object;
254
255
  resolved: any[];
255
256
  tableErrors: object[];
@@ -266,8 +267,27 @@ export function renderSitePage(page: any, { index, foreign, universe, pass, deco
266
267
  column: number;
267
268
  message: string;
268
269
  }[];
270
+ headingErrors: {
271
+ line: number;
272
+ column: number;
273
+ message: string;
274
+ file: any;
275
+ }[];
276
+ footnoteErrors: {
277
+ file: any;
278
+ line: number;
279
+ column: number;
280
+ message: string;
281
+ }[];
269
282
  wikiErrors: any[];
270
283
  imageErrors: any[];
284
+ embedErrors: {
285
+ file: string;
286
+ line: number;
287
+ column: number | undefined;
288
+ severity: "error";
289
+ message: string;
290
+ }[];
271
291
  };
272
292
  /**
273
293
  * Renders and writes every page.
@@ -304,7 +324,7 @@ export function renderSitePage(page: any, { index, foreign, universe, pass, deco
304
324
  * it by wikilink, and its own markdown links name content pages. `maps` is
305
325
  * each drawing by the URL of the page that carries it.
306
326
  * @returns {{written: number, byKind: Record<string, number>, tableErrors: object[],
307
- * wikiErrors: object[], imageErrors: object[],
327
+ * wikiErrors: object[], imageErrors: object[], embedErrors: object[],
308
328
  * related: Map<string, import("./related-pages.mjs").Related>,
309
329
  * maps: number}} `related`
310
330
  * is keyed by page URL, and holds the homepage's block beside every content
@@ -317,6 +337,7 @@ export function renderPages(pages: object[], options: object): {
317
337
  tableErrors: object[];
318
338
  wikiErrors: object[];
319
339
  imageErrors: object[];
340
+ embedErrors: object[];
320
341
  related: Map<string, import("./related-pages.mjs").Related>;
321
342
  maps: number;
322
343
  };
@@ -349,7 +370,8 @@ export function sitePageDecorator(config: any, index: any): (data: any, page: an
349
370
  * `sql` directive with none prepared is a table error: nothing here runs a
350
371
  * query.
351
372
  * @returns {{gates: object, stats: object|null, tableErrors: object[],
352
- * wikiErrors: object[], imageErrors: object[], mapFindings: object[],
373
+ * wikiErrors: object[], imageErrors: object[], embedErrors: object[],
374
+ * mapFindings: object[],
353
375
  * manifests: object|null}} `mapFindings` is what drawing the maps found —
354
376
  * warnings, never a reason to fail the build.
355
377
  */
@@ -362,6 +384,7 @@ export function buildSite({ config, sqlTables }?: {
362
384
  tableErrors: object[];
363
385
  wikiErrors: object[];
364
386
  imageErrors: object[];
387
+ embedErrors: object[];
365
388
  mapFindings: object[];
366
389
  manifests: object | null;
367
390
  };
@@ -178,12 +178,21 @@ export const DISABLE_KINDS: readonly string[];
178
178
  * `<span>` marking an unresolved link — and Goldmark drops raw HTML unless
179
179
  * told otherwise. A theme cannot supply this: Hugo does not merge a theme's
180
180
  * `markup` block.
181
+ *
182
+ * `extensions.taskList` is off because the format has no checkbox: Goldmark
183
+ * enables it by default, which is the only reason a list item opened with
184
+ * `[ ]` renders a `<input type="checkbox">` here and the same two characters as
185
+ * plain text in the pack and the book. Off, a list item's text begins right
186
+ * after its marker on every surface.
181
187
  */
182
188
  export const MARKUP: Readonly<{
183
189
  goldmark: Readonly<{
184
190
  renderer: Readonly<{
185
191
  unsafe: true;
186
192
  }>;
193
+ extensions: Readonly<{
194
+ taskList: false;
195
+ }>;
187
196
  }>;
188
197
  }>;
189
198
  /** Where the navigation is published. */