@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.
- package/CHANGELOG.md +223 -0
- package/assets/styles/content-base.css +162 -0
- package/bin/content-commands.mjs +77 -17
- package/bin/package-build.mjs +15 -2
- package/ci/content-format-reference.mjs +68 -1
- package/config.mjs +26 -0
- package/content-config.mjs +153 -6
- package/docs/api.md +120 -89
- package/docs/authoring/assets.md +14 -0
- package/docs/authoring/dates-and-calendars.md +41 -3
- package/docs/authoring/frontmatter.md +2 -2
- package/docs/authoring/links-and-markup.md +325 -35
- package/docs/commands.md +12 -10
- package/docs/configuration.md +117 -14
- package/docs/content-format.md +4 -2
- package/docs/guides/book.md +5 -4
- package/docs/guides/reference-pages.md +15 -0
- package/docs/reference/format-details.md +545 -104
- package/docs/reference/note-types.md +30 -13
- package/engine/actor-compiler.mjs +23 -0
- package/engine/anchored-sections.mjs +35 -4
- package/engine/anchors.mjs +26 -30
- package/engine/art-fields.mjs +118 -0
- package/engine/art-slots.mjs +7 -1
- package/engine/asset-index.mjs +160 -10
- package/engine/asset-replacement.mjs +93 -0
- package/engine/asset-types.mjs +240 -0
- package/engine/base-compiler.mjs +139 -22
- package/engine/base-styles.mjs +149 -0
- package/engine/bundles.mjs +13 -2
- package/engine/compendiums.mjs +8 -1
- package/engine/content-blocks.mjs +410 -0
- package/engine/content-embeds.mjs +19 -0
- package/engine/content-figures.mjs +468 -0
- package/engine/content-footnotes.mjs +197 -0
- package/engine/content-format-check.mjs +15 -12
- package/engine/content-format.yaml +1 -1
- package/engine/content-images.mjs +126 -25
- package/engine/content-index.mjs +38 -12
- package/engine/content-links.mjs +14 -6
- package/engine/content-tables.mjs +45 -4
- package/engine/content-tasklists.mjs +189 -0
- package/engine/docs-checks.mjs +4 -4
- package/engine/extension-attributes.mjs +48 -2
- package/engine/field-reference.mjs +31 -10
- package/engine/field-spec.mjs +10 -8
- package/engine/foreign-catalog.mjs +143 -18
- package/engine/frontmatter-lint.mjs +120 -65
- package/engine/frontmatter.mjs +3 -3
- package/engine/heading-attributes.mjs +321 -0
- package/engine/helpers.mjs +109 -41
- package/engine/homepage.mjs +8 -8
- package/engine/index.mjs +3 -0
- package/engine/infobox-registry.mjs +4 -1
- package/engine/infobox-render.mjs +15 -3
- package/engine/infobox.mjs +129 -15
- package/engine/itinerary-scenes.mjs +4 -10
- package/engine/journals.mjs +294 -50
- package/engine/lore-events.mjs +197 -0
- package/engine/macros.mjs +1 -1
- package/engine/markdown-expressions.mjs +434 -59
- package/engine/note-dates.mjs +354 -12
- package/engine/note-frontmatter.mjs +7 -16
- package/engine/note-vocabulary.mjs +183 -42
- package/engine/page-lists.mjs +358 -0
- package/engine/pathnames.mjs +36 -0
- package/engine/pdf-build.mjs +431 -74
- package/engine/pdf-images.mjs +64 -0
- package/engine/pdf-render.mjs +563 -66
- package/engine/rank-ladder.mjs +198 -0
- package/engine/reckoning-markers.mjs +1 -1
- package/engine/retired-fields.mjs +8 -13
- package/engine/scenes.mjs +26 -7
- package/engine/site-build.mjs +166 -23
- package/engine/site-config.mjs +10 -1
- package/engine/site-index.mjs +90 -3
- package/engine/site-preview.mjs +20 -7
- package/engine/social-ties.mjs +52 -14
- package/engine/sql-tables.mjs +66 -9
- package/engine/standings.mjs +89 -6
- package/engine/system-block.mjs +56 -30
- package/engine/web-wikilinks.mjs +48 -1
- package/hm3/actors.mjs +8 -5
- package/manifest.mjs +43 -24
- package/package.json +1 -1
- package/packagebuild-metadata.jsonl +13 -13
- package/sohl/actors.mjs +22 -99
- package/sohl/item-fields.mjs +5 -5
- package/sohl/items.mjs +1 -1
- package/sohl/note-schemas.mjs +11 -21
- package/types/config.d.mts +9 -0
- package/types/content-config.d.mts +48 -1
- package/types/engine/actor-compiler.d.mts +11 -0
- package/types/engine/anchored-sections.d.mts +24 -0
- package/types/engine/anchors.d.mts +9 -4
- package/types/engine/art-fields.d.mts +64 -0
- package/types/engine/asset-index.d.mts +43 -0
- package/types/engine/asset-replacement.d.mts +22 -0
- package/types/engine/asset-types.d.mts +18 -0
- package/types/engine/base-compiler.d.mts +19 -17
- package/types/engine/base-styles.d.mts +93 -0
- package/types/engine/content-blocks.d.mts +87 -0
- package/types/engine/content-embeds.d.mts +16 -0
- package/types/engine/content-figures.d.mts +101 -0
- package/types/engine/content-footnotes.d.mts +80 -0
- package/types/engine/content-format-check.d.mts +8 -6
- package/types/engine/content-images.d.mts +78 -12
- package/types/engine/content-index.d.mts +8 -2
- package/types/engine/content-tables.d.mts +12 -4
- package/types/engine/content-tasklists.d.mts +72 -0
- package/types/engine/extension-attributes.d.mts +22 -1
- package/types/engine/field-reference.d.mts +21 -0
- package/types/engine/field-spec.d.mts +20 -16
- package/types/engine/foreign-catalog.d.mts +39 -0
- package/types/engine/frontmatter.d.mts +1 -1
- package/types/engine/heading-attributes.d.mts +179 -0
- package/types/engine/helpers.d.mts +29 -10
- package/types/engine/homepage.d.mts +7 -7
- package/types/engine/index.d.mts +1 -0
- package/types/engine/infobox-registry.d.mts +4 -1
- package/types/engine/infobox-render.d.mts +6 -1
- package/types/engine/infobox.d.mts +6 -0
- package/types/engine/journals.d.mts +41 -12
- package/types/engine/lore-events.d.mts +14 -0
- package/types/engine/markdown-expressions.d.mts +46 -2
- package/types/engine/note-dates.d.mts +64 -1
- package/types/engine/note-frontmatter.d.mts +1 -4
- package/types/engine/note-vocabulary.d.mts +82 -6
- package/types/engine/page-lists.d.mts +126 -0
- package/types/engine/pathnames.d.mts +26 -0
- package/types/engine/pdf-build.d.mts +28 -6
- package/types/engine/pdf-images.d.mts +52 -0
- package/types/engine/pdf-render.d.mts +84 -2
- package/types/engine/rank-ladder.d.mts +32 -0
- package/types/engine/retired-fields.d.mts +4 -6
- package/types/engine/site-build.d.mts +27 -4
- package/types/engine/site-config.d.mts +9 -0
- package/types/engine/site-index.d.mts +67 -1
- package/types/engine/site-preview.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +31 -7
- package/types/engine/standings.d.mts +21 -3
- package/types/engine/system-block.d.mts +37 -14
- package/types/engine/web-wikilinks.d.mts +3 -1
- package/types/hm3/actors.d.mts +21 -3
- package/types/manifest.d.mts +19 -23
- package/types/sohl/actors.d.mts +6 -7
- package/engine/content-admonitions.mjs +0 -114
- package/engine/content-captions.mjs +0 -181
- package/engine/content-secrets.mjs +0 -112
- package/types/engine/content-admonitions.d.mts +0 -24
- package/types/engine/content-captions.d.mts +0 -45
- 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:`
|
|
178
|
-
* region, a working state is the author's own and this build has no
|
|
179
|
-
* about it. A classifying tag is different, because something queries
|
|
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
|
-
*
|
|
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
|
-
*
|
|
17
|
-
* package's file
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
-
*
|
|
59
|
-
*
|
|
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.
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
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:
|
|
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[],
|
|
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. */
|