@heroiclands/package-build 20.6.0 → 21.0.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 +181 -0
- package/CONTENT.md +134 -44
- package/bin/content-build.mjs +37 -5
- package/bin/package-build.mjs +77 -0
- package/content-config.mjs +59 -1
- package/docs/api.md +149 -19
- package/docs/commands.md +75 -0
- package/docs/configuration.md +37 -10
- package/docs/content-format.md +450 -49
- package/engine/content-format.mjs +52 -3
- package/engine/content-images.mjs +699 -0
- package/engine/dependency-bump.mjs +218 -0
- package/engine/frontmatter-lint.mjs +89 -2
- package/engine/helpers.mjs +81 -142
- package/engine/index.mjs +15 -0
- package/engine/infobox-registry.mjs +81 -0
- package/engine/infobox-render.mjs +381 -0
- package/engine/infobox.mjs +963 -0
- package/engine/item-registry.mjs +5 -5
- package/engine/journals.mjs +22 -1
- package/engine/map-notes.mjs +11 -5
- package/engine/metadata-index.mjs +5 -0
- package/engine/note-vocabulary.mjs +57 -2
- package/engine/pathnames.mjs +374 -0
- package/engine/pdf-build.mjs +208 -9
- package/engine/pdf-render.mjs +461 -21
- package/engine/pdf-toc.mjs +77 -5
- package/engine/scenes.mjs +2 -1
- package/engine/site-build.mjs +115 -8
- package/engine/site-index.mjs +93 -4
- package/engine/wikilinks.mjs +93 -0
- package/hm3/default-item-art.mjs +14 -15
- package/hm3/index.mjs +3 -0
- package/hm3/infobox.mjs +64 -0
- package/package.json +3 -2
- package/sohl/being-info.mjs +9 -3
- package/sohl/default-item-art.mjs +18 -16
- package/sohl/index.mjs +3 -0
- package/sohl/infobox.mjs +499 -0
- package/types/content-config.d.mts +7 -0
- package/types/engine/content-format.d.mts +36 -0
- package/types/engine/content-images.d.mts +281 -0
- package/types/engine/dependency-bump.d.mts +89 -0
- package/types/engine/frontmatter-lint.d.mts +23 -0
- package/types/engine/helpers.d.mts +30 -72
- package/types/engine/index.d.mts +5 -0
- package/types/engine/infobox-registry.d.mts +36 -0
- package/types/engine/infobox-render.d.mts +87 -0
- package/types/engine/infobox.d.mts +443 -0
- package/types/engine/item-registry.d.mts +5 -5
- package/types/engine/journals.d.mts +9 -1
- package/types/engine/note-vocabulary.d.mts +51 -0
- package/types/engine/pathnames.d.mts +189 -0
- package/types/engine/pdf-build.d.mts +46 -0
- package/types/engine/pdf-render.d.mts +99 -1
- package/types/engine/pdf-toc.d.mts +10 -5
- package/types/engine/site-build.d.mts +19 -3
- package/types/engine/site-index.d.mts +35 -3
- package/types/engine/wikilinks.d.mts +22 -0
- package/types/hm3/default-item-art.d.mts +5 -6
- package/types/hm3/index.d.mts +1 -0
- package/types/hm3/infobox.d.mts +22 -0
- package/types/sohl/being-info.d.mts +4 -3
- package/types/sohl/index.d.mts +1 -0
- package/types/sohl/infobox.d.mts +145 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The default HTML for one link value: an anchor when the medium supplied a
|
|
3
|
+
* URL, and the text alone otherwise.
|
|
4
|
+
*
|
|
5
|
+
* An unresolved reference keeps its words. A page that names something the
|
|
6
|
+
* index has not heard of is better than a page silently missing a row.
|
|
7
|
+
*
|
|
8
|
+
* @param {object} value - A `link` value.
|
|
9
|
+
* @returns {string} HTML.
|
|
10
|
+
*/
|
|
11
|
+
export function linkToHtml(value: object): string;
|
|
12
|
+
/**
|
|
13
|
+
* One link value as a Foundry document reference.
|
|
14
|
+
*
|
|
15
|
+
* A compendium journal links **inside** Foundry: a website URL on a panel a
|
|
16
|
+
* player reads at the table sends them out of the game, and a UUID resolves to
|
|
17
|
+
* the document whether or not the world has imported it. Foundry's enricher
|
|
18
|
+
* reads the reference out of the page's own HTML, so no markup is needed
|
|
19
|
+
* around it.
|
|
20
|
+
*
|
|
21
|
+
* @param {object} value - A `link` value.
|
|
22
|
+
* @returns {string} A `@UUID` reference, or the text where there is none.
|
|
23
|
+
*/
|
|
24
|
+
export function linkToUuid(value: object): string;
|
|
25
|
+
/**
|
|
26
|
+
* Every box as HTML, in the order given.
|
|
27
|
+
*
|
|
28
|
+
* @param {readonly object[]} boxes - From `buildInfoboxes`.
|
|
29
|
+
* @param {object} [options] - Options.
|
|
30
|
+
* @param {(value: object) => string} [options.link] - How this medium draws a
|
|
31
|
+
* link. Defaults to an anchor on the value's `url`.
|
|
32
|
+
* @returns {string} HTML, empty when there is nothing to draw.
|
|
33
|
+
*/
|
|
34
|
+
export function infoboxesToHtml(boxes: readonly object[], { link }?: {
|
|
35
|
+
link?: ((value: object) => string) | undefined;
|
|
36
|
+
}): string;
|
|
37
|
+
/**
|
|
38
|
+
* Whether a section holds anything at all.
|
|
39
|
+
*
|
|
40
|
+
* A section with nothing in it is not drawn: rule 4 says an absent field is
|
|
41
|
+
* absent, and a heading over nothing is the em-dash placeholder in another
|
|
42
|
+
* form. The declaration answers it — {@link module:engine/infobox.sectionHolds}
|
|
43
|
+
* — because the same question decides whether a system box carries a statement
|
|
44
|
+
* instead of sections, and two answers to it would come apart.
|
|
45
|
+
*
|
|
46
|
+
* @param {object} section - The section.
|
|
47
|
+
* @returns {boolean} Whether to draw it.
|
|
48
|
+
*/
|
|
49
|
+
export function sectionHasContent(section: object): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* One link value as Typst.
|
|
52
|
+
*
|
|
53
|
+
* An internal destination — a note the book also prints — is a label
|
|
54
|
+
* reference, so the link works on paper as a cross-reference and in a PDF
|
|
55
|
+
* viewer as a jump. Everything else is set as its own words.
|
|
56
|
+
*
|
|
57
|
+
* @param {object} value - A `link` value.
|
|
58
|
+
* @param {Map<string, string>} links - Address slug → the book's anchor.
|
|
59
|
+
* @param {(anchor: string) => string} labelFor - The anchor's Typst label.
|
|
60
|
+
* @returns {string} Typst markup.
|
|
61
|
+
*/
|
|
62
|
+
export function linkToTypst(value: object, links: Map<string, string>, labelFor: (anchor: string) => string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Every box as one Typst panel each, breaking between sections.
|
|
65
|
+
*
|
|
66
|
+
* The panel is `breakable: true` and each section inside it is
|
|
67
|
+
* `breakable: false`, which is what puts a break between `ATTRIBUTES` and
|
|
68
|
+
* `SKILLS` on a richly-statted character and never inside either.
|
|
69
|
+
*
|
|
70
|
+
* @param {readonly object[]} boxes - From `buildInfoboxes`.
|
|
71
|
+
* @param {object} [options] - Options.
|
|
72
|
+
* @param {(value: object) => string} [options.link] - How the book draws a link.
|
|
73
|
+
* @returns {string} Typst markup, empty when there is nothing to draw.
|
|
74
|
+
*/
|
|
75
|
+
export function infoboxesToTypst(boxes: readonly object[], { link }?: {
|
|
76
|
+
link?: ((value: object) => string) | undefined;
|
|
77
|
+
}): string;
|
|
78
|
+
/**
|
|
79
|
+
* The Typst definitions an infobox panel is drawn with.
|
|
80
|
+
*
|
|
81
|
+
* Emitted once at the head of the book rather than inlined per entry: 2,500
|
|
82
|
+
* entries each carrying their own rules is a megabyte of repetition, and the
|
|
83
|
+
* one place a reader changes the panel's appearance should be one place.
|
|
84
|
+
*
|
|
85
|
+
* @returns {string} Typst markup.
|
|
86
|
+
*/
|
|
87
|
+
export function infoboxTypstPreamble(): string;
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One field's overlay entry, qualified by type where the declaration qualifies
|
|
3
|
+
* it.
|
|
4
|
+
*
|
|
5
|
+
* `<type>.<field>` wins over the bare name, so a spelling two types use for two
|
|
6
|
+
* quantities can be said differently on each without splitting the overlay.
|
|
7
|
+
*
|
|
8
|
+
* @param {Readonly<Record<string, object>>} presentation - The overlay.
|
|
9
|
+
* @param {string|undefined} type - The note's type.
|
|
10
|
+
* @param {string} name - The field's declared name.
|
|
11
|
+
* @returns {object} The entry, or an empty one.
|
|
12
|
+
*/
|
|
13
|
+
export function overlayFor(presentation: Readonly<Record<string, object>>, type: string | undefined, name: string): object;
|
|
14
|
+
/**
|
|
15
|
+
* Whether a value is a word meaning "nothing here" rather than a value.
|
|
16
|
+
*
|
|
17
|
+
* @param {unknown} value - The authored value.
|
|
18
|
+
* @returns {boolean} Whether it is one of {@link UNSET_VALUES}.
|
|
19
|
+
*/
|
|
20
|
+
export function isUnsetSentinel(value: unknown): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Whether a value is worth a row.
|
|
23
|
+
*
|
|
24
|
+
* Rule 4: an absent field is absent. `null`, `""` and `[]` are how the corpus
|
|
25
|
+
* writes "nobody filled this in" — a note that declares every key of its type
|
|
26
|
+
* and leaves most of them empty is the ordinary shape, not the exception — and
|
|
27
|
+
* so is a sentinel, which is the same absence written as a word. Judged here
|
|
28
|
+
* rather than per field, because a sentinel that reaches a page reaches it the
|
|
29
|
+
* same way whichever box was building the row.
|
|
30
|
+
*
|
|
31
|
+
* @param {unknown} value - The authored value.
|
|
32
|
+
* @returns {boolean} Whether to emit it.
|
|
33
|
+
*/
|
|
34
|
+
export function hasValue(value: unknown): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* A key as a reader sees it: `healingRate` → `Healing rate`.
|
|
37
|
+
*
|
|
38
|
+
* The last dotted segment only — a nested key's container is already said by
|
|
39
|
+
* the section it sits in, and "Appearance eye color" says it twice.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} name - The declared key.
|
|
42
|
+
* @returns {string} The label.
|
|
43
|
+
*/
|
|
44
|
+
export function humanizeFieldName(name: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* An enumerated value as a reader sees it: `graying_brown` → `graying brown`.
|
|
47
|
+
*
|
|
48
|
+
* @param {unknown} value - The authored value.
|
|
49
|
+
* @returns {string} The text.
|
|
50
|
+
*/
|
|
51
|
+
export function humanizeValue(value: unknown): string;
|
|
52
|
+
/**
|
|
53
|
+
* A value as a row shows it.
|
|
54
|
+
*
|
|
55
|
+
* A **single lowercase token** is an enumerated value — `male`, `medium`,
|
|
56
|
+
* `city-state` — and a row reading "male" beside a name reads as a fault
|
|
57
|
+
* rather than as a value, so its words are capitalised. Anything else is prose
|
|
58
|
+
* the author wrote and is shown as written: title-casing "a dispossessed
|
|
59
|
+
* noble" would be this build editing the corpus.
|
|
60
|
+
*
|
|
61
|
+
* @param {unknown} value - The authored value.
|
|
62
|
+
* @returns {string} The text.
|
|
63
|
+
*/
|
|
64
|
+
export function presentValue(value: unknown): string;
|
|
65
|
+
/**
|
|
66
|
+
* Whether a built value is worth a row.
|
|
67
|
+
*
|
|
68
|
+
* Separate from {@link hasValue}, which judges what an author wrote: a `link`
|
|
69
|
+
* value is a mapping and a `links` value is a list of them, and both are
|
|
70
|
+
* exactly what the authored form is not.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} kind - One of {@link INFOBOX_VALUE_KINDS}.
|
|
73
|
+
* @param {unknown} value - The built value.
|
|
74
|
+
* @returns {boolean} Whether to emit the row.
|
|
75
|
+
*/
|
|
76
|
+
export function hasRenderableValue(kind: string, value: unknown): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* The value kind a `data:` declaration implies.
|
|
79
|
+
*
|
|
80
|
+
* Read from the declaration's own shape rather than from the value, so two
|
|
81
|
+
* notes of one type describe the same field the same way — a one-entry list
|
|
82
|
+
* and a two-entry list are both `links`.
|
|
83
|
+
*
|
|
84
|
+
* @param {object} field - A `DataFieldSpec`.
|
|
85
|
+
* @param {unknown} value - The authored value, for the shapes the declaration
|
|
86
|
+
* makes no claim about.
|
|
87
|
+
* @returns {string} One of {@link INFOBOX_VALUE_KINDS}.
|
|
88
|
+
*/
|
|
89
|
+
export function valueKindOf(field: object, value: unknown): string;
|
|
90
|
+
/**
|
|
91
|
+
* One row, with its unit on it.
|
|
92
|
+
*
|
|
93
|
+
* **The unit goes on the value, not on the label**, because it belongs to the
|
|
94
|
+
* quantity rather than to the name of the quantity. A price is 160d and a
|
|
95
|
+
* weight is 1.1 lbs; splitting that across two cells — `Price (d)` beside
|
|
96
|
+
* `160` — makes a reader reassemble one fact from two places, and reads worst
|
|
97
|
+
* in the book, whose label column is a narrow small-caps rule.
|
|
98
|
+
*
|
|
99
|
+
* A medium cannot supply it. Appending `d` to a price means knowing which row
|
|
100
|
+
* is the price, which is the one thing a generic renderer must never know — so
|
|
101
|
+
* the unit is declared here and travels as part of the value.
|
|
102
|
+
*
|
|
103
|
+
* The declared string is appended **verbatim**, which is what lets a symbol
|
|
104
|
+
* hug its number (`160d`) and a word stand off it (`1.1 lbs`). The row's kind
|
|
105
|
+
* becomes `text`: a number with a unit on it is no longer a number, and saying
|
|
106
|
+
* otherwise would invite a medium to format it as one.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} kind - The row's kind.
|
|
109
|
+
* @param {unknown} value - The built value.
|
|
110
|
+
* @param {string} [unit] - The declared unit, or nothing.
|
|
111
|
+
* @returns {{kind: string, value: unknown}} The row's kind and value.
|
|
112
|
+
*/
|
|
113
|
+
export function applyUnit(kind: string, value: unknown, unit?: string): {
|
|
114
|
+
kind: string;
|
|
115
|
+
value: unknown;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* One reference, resolved as far as the medium's index reaches.
|
|
119
|
+
*
|
|
120
|
+
* An unresolved reference keeps its own text rather than being dropped: a page
|
|
121
|
+
* that names something the index has not heard of is better than a page
|
|
122
|
+
* silently missing a row.
|
|
123
|
+
*
|
|
124
|
+
* @param {unknown} ref - The authored reference — a shortcode or an address.
|
|
125
|
+
* @param {(ref: unknown, hint?: object) => object|undefined} resolve - The
|
|
126
|
+
* medium's resolver.
|
|
127
|
+
* @param {object} [hint] - What the reference is expected to name, where the
|
|
128
|
+
* declaration says: `{type}`. A shortcode is unique within a type and not
|
|
129
|
+
* across a tree, so a hint is what stops a region resolving to a polity.
|
|
130
|
+
* @returns {{text: string, url?: string, uuid?: string, address?: string}} The value.
|
|
131
|
+
*/
|
|
132
|
+
export function linkValue(ref: unknown, resolve: (ref: unknown, hint?: object) => object | undefined, hint?: object): {
|
|
133
|
+
text: string;
|
|
134
|
+
url?: string;
|
|
135
|
+
uuid?: string;
|
|
136
|
+
address?: string;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* The note infobox: the subject from `data:`, plus the name every note has.
|
|
140
|
+
*
|
|
141
|
+
* The rows are {@link NOTE_VOCABULARY}'s declaration for the type, in its
|
|
142
|
+
* order, minus what {@link NOTE_FIELD_PRESENTATION} withholds and minus every
|
|
143
|
+
* field the note left empty. A type that declares no `data:` fields still gets
|
|
144
|
+
* the box, because `Name` is a fact about every note.
|
|
145
|
+
*
|
|
146
|
+
* @param {object} fm - The note's frontmatter.
|
|
147
|
+
* @param {object} [options] - Options.
|
|
148
|
+
* @param {(ref: unknown) => object|undefined} [options.resolve] - Resolves a
|
|
149
|
+
* reference to `{name, url?, uuid?, address?}`.
|
|
150
|
+
* @param {object} [options.vocabulary] - The note vocabulary to read.
|
|
151
|
+
* @param {object} [options.presentation] - The overlay to read.
|
|
152
|
+
* @returns {object} The box.
|
|
153
|
+
*/
|
|
154
|
+
export function noteInfobox(fm: object, options?: {
|
|
155
|
+
resolve?: ((ref: unknown) => object | undefined) | undefined;
|
|
156
|
+
vocabulary?: object | undefined;
|
|
157
|
+
presentation?: object | undefined;
|
|
158
|
+
}): object;
|
|
159
|
+
/**
|
|
160
|
+
* Declare one system's half of the infobox.
|
|
161
|
+
*
|
|
162
|
+
* The **mechanism** is here and the **declaration** is the system's, which is
|
|
163
|
+
* the `engine/` ÷ `sohl/` line everywhere else in this package: what a box
|
|
164
|
+
* looks like is note-format knowledge, and which of a system's fields are
|
|
165
|
+
* worth a row is that system's.
|
|
166
|
+
*
|
|
167
|
+
* A declaration is refused at module evaluation rather than when some note
|
|
168
|
+
* reaches it: it is a small piece of authored data loaded once per build, so a
|
|
169
|
+
* mistake in it should stop the build immediately and name the fault.
|
|
170
|
+
*
|
|
171
|
+
* @param {object} declaration - The system's declaration.
|
|
172
|
+
* @param {string} declaration.system - The system id.
|
|
173
|
+
* @param {string} declaration.title - What the box is called.
|
|
174
|
+
* @param {Readonly<Record<string, readonly object[]>>} [declaration.fields] -
|
|
175
|
+
* Note type → that system's declared field list, read for the generic rows
|
|
176
|
+
* section. The same list the compiler obeys, never a copy of it.
|
|
177
|
+
* @param {Readonly<Record<string, {label?: string, withheld?: string}>>} [declaration.presentation] -
|
|
178
|
+
* The presentation overlay: what one of this system's fields is called where
|
|
179
|
+
* humanising its key is wrong, and the fields that carry no row at all.
|
|
180
|
+
* **Not a second field list** — the fields come from `fields`, and a field
|
|
181
|
+
* the overlay does not mention still gets a row, so a field added to the
|
|
182
|
+
* system reaches the box with no edit here.
|
|
183
|
+
* @param {Record<string, (fm: object, ctx: object) => object[]>} [declaration.sections] -
|
|
184
|
+
* Note type → a builder returning that type's sections, for a type whose box
|
|
185
|
+
* is derived rather than read field by field.
|
|
186
|
+
* @returns {object} The frozen declaration.
|
|
187
|
+
* @throws {Error} When it names no system or no title.
|
|
188
|
+
*/
|
|
189
|
+
export function defineInfobox({ system, title, fields, presentation, sections }?: {
|
|
190
|
+
system: string;
|
|
191
|
+
title: string;
|
|
192
|
+
fields?: Readonly<Record<string, readonly object[]>> | undefined;
|
|
193
|
+
presentation?: Readonly<Record<string, {
|
|
194
|
+
label?: string;
|
|
195
|
+
withheld?: string;
|
|
196
|
+
}>> | undefined;
|
|
197
|
+
sections?: Record<string, (fm: object, ctx: object) => object[]> | undefined;
|
|
198
|
+
}): object;
|
|
199
|
+
/**
|
|
200
|
+
* Whether a field's value is the one its own declaration would have supplied.
|
|
201
|
+
*
|
|
202
|
+
* _A field answered by its default is a fact about the compiler, not about the
|
|
203
|
+
* note_ — and that is true of the **value**, not of where it was written. A
|
|
204
|
+
* corpus writes its defaults out: `improveFlag: false`, `combatCategory: none`
|
|
205
|
+
* and `initSkillMult: 0` are typed into hundreds of notes that mean nothing by
|
|
206
|
+
* them, and a row for each says the compiler's word back to a reader who came
|
|
207
|
+
* for the note's.
|
|
208
|
+
*
|
|
209
|
+
* A field declaring no default has nothing to be equal to, so every value it
|
|
210
|
+
* holds is the note's.
|
|
211
|
+
*
|
|
212
|
+
* @param {object} field - The declaration.
|
|
213
|
+
* @param {unknown} raw - The resolved value.
|
|
214
|
+
* @returns {boolean} Whether the value is the declaration's own.
|
|
215
|
+
*/
|
|
216
|
+
export function isDeclaredDefault(field: object, raw: unknown): boolean;
|
|
217
|
+
/**
|
|
218
|
+
* The generic rows section: what this note authors in one system's block.
|
|
219
|
+
*
|
|
220
|
+
* Read through the system's own field declaration, in its order, and only
|
|
221
|
+
* where the note said something the declaration does not already say — a value
|
|
222
|
+
* equal to the field's own default is the compiler's answer wherever it was
|
|
223
|
+
* typed.
|
|
224
|
+
*
|
|
225
|
+
* A field the **note box** already put on the page is skipped, so the panel
|
|
226
|
+
* does not say one fact twice. It is what the note box *shows* rather than
|
|
227
|
+
* what its vocabulary declares: a gear item's weight is declared under `data:`
|
|
228
|
+
* and authored at `sohl.system.weightBase`, and standing down on the
|
|
229
|
+
* declaration alone left the fact on no surface at all.
|
|
230
|
+
*
|
|
231
|
+
* @param {object} fm - The note's frontmatter.
|
|
232
|
+
* @param {readonly object[]} fields - The system's field declaration.
|
|
233
|
+
* @param {object} ctx - `{ block, resolve, resolveField, taken, presentation }`.
|
|
234
|
+
* @returns {object[]} Zero or one section.
|
|
235
|
+
*/
|
|
236
|
+
export function systemRowsSection(fm: object, fields: readonly object[], { block, resolve, resolveField, taken, presentation }: object): object[];
|
|
237
|
+
/**
|
|
238
|
+
* Every box a note carries, in the order every medium renders them.
|
|
239
|
+
*
|
|
240
|
+
* The note box first — it is the subject itself — then one box per system the
|
|
241
|
+
* note's type maps to, in the order the maps are declared.
|
|
242
|
+
*
|
|
243
|
+
* @param {object} fm - The note's frontmatter.
|
|
244
|
+
* @param {object} options - Options.
|
|
245
|
+
* @param {readonly object[]} options.maps - The document-subtype maps this
|
|
246
|
+
* build ships, which decide the box set.
|
|
247
|
+
* @param {readonly object[]} [options.providers] - The systems' infobox
|
|
248
|
+
* declarations, keyed by `system`.
|
|
249
|
+
* @param {(fm: object, block: string) => boolean} options.carriesBlock -
|
|
250
|
+
* Whether the note says anything about a system, which decides
|
|
251
|
+
* {@link NOT_AVAILABLE}.
|
|
252
|
+
* @param {(field: object, fm: object, opts: object) => object} options.resolveField -
|
|
253
|
+
* Resolves one declared field against the note.
|
|
254
|
+
* @param {(ref: unknown) => object|undefined} [options.resolve] - Resolves a
|
|
255
|
+
* reference to `{name, url?, uuid?, address?}`.
|
|
256
|
+
* @param {object} [options.vocabulary] - The note vocabulary to read.
|
|
257
|
+
* @returns {object[]} The boxes.
|
|
258
|
+
*/
|
|
259
|
+
export function buildInfoboxes(fm: object, options: {
|
|
260
|
+
maps: readonly object[];
|
|
261
|
+
providers?: readonly object[] | undefined;
|
|
262
|
+
carriesBlock: (fm: object, block: string) => boolean;
|
|
263
|
+
resolveField: (field: object, fm: object, opts: object) => object;
|
|
264
|
+
resolve?: ((ref: unknown) => object | undefined) | undefined;
|
|
265
|
+
vocabulary?: object | undefined;
|
|
266
|
+
}): object[];
|
|
267
|
+
/**
|
|
268
|
+
* Whether a section holds anything a medium would draw.
|
|
269
|
+
*
|
|
270
|
+
* Keyed by {@link INFOBOX_LAYOUTS}, so a layout added there is understood here
|
|
271
|
+
* without a second edit.
|
|
272
|
+
*
|
|
273
|
+
* @param {object} section - The section.
|
|
274
|
+
* @returns {boolean} Whether it holds anything.
|
|
275
|
+
*/
|
|
276
|
+
export function sectionHolds(section: object): boolean;
|
|
277
|
+
/**
|
|
278
|
+
* The box ids a note's type requires, in order.
|
|
279
|
+
*
|
|
280
|
+
* Derived from the same two declarations {@link buildInfoboxes} reads, so the
|
|
281
|
+
* assertion below compares an emitter's output against the format rather than
|
|
282
|
+
* against a restatement of it.
|
|
283
|
+
*
|
|
284
|
+
* @param {object} fm - The note's frontmatter.
|
|
285
|
+
* @param {object} options - `{ maps }`.
|
|
286
|
+
* @returns {string[]} The ids.
|
|
287
|
+
*/
|
|
288
|
+
export function requiredInfoboxIds(fm: object, { maps }: object): string[];
|
|
289
|
+
/**
|
|
290
|
+
* Refuse a page carrying anything but the boxes its type maps to.
|
|
291
|
+
*
|
|
292
|
+
* _Every page carries exactly the boxes its type maps to_ is the property the
|
|
293
|
+
* format states and the reason the box set is derived rather than declared.
|
|
294
|
+
* Asserted where the boxes are emitted, in every medium, so a missing infobox
|
|
295
|
+
* is a failure rather than something nobody notices — and so is a box for a
|
|
296
|
+
* system that will compile no document, which is a note claiming a system it
|
|
297
|
+
* does not reach.
|
|
298
|
+
*
|
|
299
|
+
* @param {readonly object[]} boxes - What was built.
|
|
300
|
+
* @param {object} fm - The note's frontmatter.
|
|
301
|
+
* @param {object} options - `{ maps, where }`.
|
|
302
|
+
* @returns {readonly object[]} The boxes, unchanged, so a caller may assert
|
|
303
|
+
* inline.
|
|
304
|
+
* @throws {Error} Naming the note, what is missing and what is extra.
|
|
305
|
+
*/
|
|
306
|
+
export function assertInfoboxSet(boxes: readonly object[], fm: object, { maps, where }?: object): readonly object[];
|
|
307
|
+
/**
|
|
308
|
+
* How a section arranges what it holds.
|
|
309
|
+
*
|
|
310
|
+
* Closed, and keyed by the property the section carries its content in — a
|
|
311
|
+
* renderer reads one of four keys and needs no other knowledge of the data.
|
|
312
|
+
*
|
|
313
|
+
* @type {Readonly<Record<string, string>>}
|
|
314
|
+
*/
|
|
315
|
+
export const INFOBOX_LAYOUTS: Readonly<Record<string, string>>;
|
|
316
|
+
/**
|
|
317
|
+
* What a row's `value` is.
|
|
318
|
+
*
|
|
319
|
+
* Closed. `link` and `links` carry `{text, url?, uuid?, address?}` — the three
|
|
320
|
+
* addresses a medium may need, whichever of them its own index could supply.
|
|
321
|
+
*
|
|
322
|
+
* @type {readonly string[]}
|
|
323
|
+
*/
|
|
324
|
+
export const INFOBOX_VALUE_KINDS: readonly string[];
|
|
325
|
+
/**
|
|
326
|
+
* What a mapped system that produced no document for this note says.
|
|
327
|
+
*
|
|
328
|
+
* Stated rather than inferred from an empty block, because an absence is a
|
|
329
|
+
* poor signal: noticing that something is missing requires already knowing it
|
|
330
|
+
* should have been there, and a reader meeting one page has no way to know.
|
|
331
|
+
*
|
|
332
|
+
* @type {string}
|
|
333
|
+
*/
|
|
334
|
+
export const NOT_AVAILABLE: string;
|
|
335
|
+
/**
|
|
336
|
+
* What a system that does produce a document, and holds nothing a reader has
|
|
337
|
+
* not already been shown, says.
|
|
338
|
+
*
|
|
339
|
+
* The third state of a system box, and the reason it is stated rather than
|
|
340
|
+
* drawn as an empty panel: a heading over nothing asserts that something
|
|
341
|
+
* should have been there. It is not {@link NOT_AVAILABLE} — the system
|
|
342
|
+
* compiles this note, and a reader told otherwise would go looking for a
|
|
343
|
+
* document that exists. It is that everything this system holds about the note
|
|
344
|
+
* is either the subject's own fact, carried by the note box above, or a value
|
|
345
|
+
* the compiler would have supplied anyway.
|
|
346
|
+
*
|
|
347
|
+
* @type {string}
|
|
348
|
+
*/
|
|
349
|
+
export const NOTHING_BEYOND_PROFILE: string;
|
|
350
|
+
/** The note infobox's id, which is not a system id. @type {string} */
|
|
351
|
+
export const NOTE_BOX_ID: string;
|
|
352
|
+
/** The note infobox's title. @type {string} */
|
|
353
|
+
export const NOTE_BOX_TITLE: string;
|
|
354
|
+
/** The note infobox's single section id. @type {string} */
|
|
355
|
+
export const NOTE_SECTION_ID: string;
|
|
356
|
+
/**
|
|
357
|
+
* What a **duration pair** is called.
|
|
358
|
+
*
|
|
359
|
+
* A note states an interval twice over — once as a roll formula and once as a
|
|
360
|
+
* flat number of seconds — and the declaration names the two by what they hold:
|
|
361
|
+
* `courseDurationFormula` and `courseDurationBase`. A page wants the thing
|
|
362
|
+
* being timed and which of the two it is reading.
|
|
363
|
+
*
|
|
364
|
+
* Shared by the note box's overlay and by a system's, because the two
|
|
365
|
+
* declarations spell these fields identically and a reader meeting an
|
|
366
|
+
* affliction written one way and one written the other should meet one word.
|
|
367
|
+
*
|
|
368
|
+
* @type {Readonly<Record<string, {label: string}>>}
|
|
369
|
+
*/
|
|
370
|
+
export const DURATION_LABELS: Readonly<Record<string, {
|
|
371
|
+
label: string;
|
|
372
|
+
}>>;
|
|
373
|
+
/**
|
|
374
|
+
* What a gear item's two measured quantities are called, and measured in.
|
|
375
|
+
*
|
|
376
|
+
* Price is in pence and weight in pounds throughout the corpus, and the same
|
|
377
|
+
* fact is authored under `data:` on one note and at its destination path on
|
|
378
|
+
* another — so both overlays read the one declaration and a reader meets one
|
|
379
|
+
* word and one unit whichever box the row landed in.
|
|
380
|
+
*
|
|
381
|
+
* @type {Readonly<Record<string, {label?: string, unit: string}>>}
|
|
382
|
+
*/
|
|
383
|
+
export const GEAR_UNITS: Readonly<Record<string, {
|
|
384
|
+
label?: string;
|
|
385
|
+
unit: string;
|
|
386
|
+
}>>;
|
|
387
|
+
/**
|
|
388
|
+
* The presentation overlay: what a `data:` field is called, and whether it
|
|
389
|
+
* belongs in the box at all.
|
|
390
|
+
*
|
|
391
|
+
* **This is not a second field list.** The fields come from
|
|
392
|
+
* {@link NOTE_VOCABULARY}; this says only what the vocabulary cannot — the
|
|
393
|
+
* label where humanising the key is wrong, the group a field composes into,
|
|
394
|
+
* and the handful of keys that are not reader-facing.
|
|
395
|
+
*
|
|
396
|
+
* Three properties, all optional:
|
|
397
|
+
*
|
|
398
|
+
* - `label` — what the row is called. Absent means the key, humanised.
|
|
399
|
+
* - `withheld` — why the field carries no row. Two reasons only: it is
|
|
400
|
+
* machinery — something that steers a build or an interface rather than
|
|
401
|
+
* describing the subject — or it is an image, which rule 2 keeps out of the
|
|
402
|
+
* box.
|
|
403
|
+
* - `unit` — what the quantity is measured in, appended to the value verbatim.
|
|
404
|
+
* See {@link applyUnit}.
|
|
405
|
+
* - `group` / `phrase` — the field composes into one row with its group mates
|
|
406
|
+
* rather than taking a row of its own. `phrase` turns the value into its
|
|
407
|
+
* clause; without one the value stands alone, so a field added to a group
|
|
408
|
+
* still appears.
|
|
409
|
+
*
|
|
410
|
+
* A key is either a field name or `<type>.<field name>`, and the qualified one
|
|
411
|
+
* wins. One spelling is not one quantity across the vocabulary: `data.weight`
|
|
412
|
+
* is a being's body weight, which reads as a clause of its appearance, and a
|
|
413
|
+
* gear item's mass, which is a row. An overlay keyed on the bare word would
|
|
414
|
+
* compose a coin's weight into its appearance.
|
|
415
|
+
*
|
|
416
|
+
* @type {Readonly<Record<string, {label?: string, withheld?: string,
|
|
417
|
+
* unit?: string, group?: string, phrase?: (value: any) => string}>>}
|
|
418
|
+
*/
|
|
419
|
+
export const NOTE_FIELD_PRESENTATION: Readonly<Record<string, {
|
|
420
|
+
label?: string;
|
|
421
|
+
withheld?: string;
|
|
422
|
+
unit?: string;
|
|
423
|
+
group?: string;
|
|
424
|
+
phrase?: (value: any) => string;
|
|
425
|
+
}>>;
|
|
426
|
+
/**
|
|
427
|
+
/**
|
|
428
|
+
* The words a note writes when it means "there is nothing here".
|
|
429
|
+
*
|
|
430
|
+
* A corpus states an absence three ways, and only two of them are the absence
|
|
431
|
+
* of a value: the key is omitted, or it holds the field's own default. The
|
|
432
|
+
* third is a **sentinel** — a token standing in for the unset state, spelled
|
|
433
|
+
* however the note happened to spell it. `potency: na` and `category: none`
|
|
434
|
+
* are not a sodium potion and a category called None; they are two authors
|
|
435
|
+
* writing "not applicable" in the space a value would go.
|
|
436
|
+
*
|
|
437
|
+
* Compared after {@link normalizeToken} strips everything but letters, so
|
|
438
|
+
* `n/a`, `N/A` and `not applicable` are one word and `none of the above` is
|
|
439
|
+
* not one of them.
|
|
440
|
+
*
|
|
441
|
+
* @type {readonly string[]}
|
|
442
|
+
*/
|
|
443
|
+
export const UNSET_VALUES: readonly string[];
|
|
@@ -61,11 +61,11 @@ export function itemFields(type: string, system?: string): readonly object[] | u
|
|
|
61
61
|
* `defaultItemArt` was written for. Only the error's *owner* changed: it now
|
|
62
62
|
* names the registry the consumer declares and can add to.
|
|
63
63
|
*
|
|
64
|
-
* **Resolved by the same rule a note's `img:` is.** The
|
|
65
|
-
* {@link resolveImg}, so `icons/relic.svg` means the consumer's own
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* One spelling, one meaning, wherever it is written.
|
|
64
|
+
* **Resolved by the same rule a note's `img:` is.** The pathname goes through
|
|
65
|
+
* {@link resolveImg}, so `icons/relic.svg` means the consumer's own package in
|
|
66
|
+
* the registry exactly as it does on a note, and `sohl/assets/…` — as every
|
|
67
|
+
* SoHL default is written — names the `sohl` package's file wherever it is
|
|
68
|
+
* compiled. One spelling, one meaning, wherever it is written.
|
|
69
69
|
*
|
|
70
70
|
* @param {string} type - the item type.
|
|
71
71
|
* @param {string} [system] - The system compiling it, where a build declares
|
|
@@ -136,9 +136,16 @@ export function buildPages(rawPages: Array<object>, entryId: string, noteName: s
|
|
|
136
136
|
* entry: a module may ship the same content for two systems, and each pack's
|
|
137
137
|
* documents record the system version they were built against. A
|
|
138
138
|
* caller with no pack in hand gets the package-wide block.
|
|
139
|
+
* @param {string} [params.infobox] - The note's infobox, already rendered to
|
|
140
|
+
* HTML. Prepended to the entry's first page, which is where the format puts
|
|
141
|
+
* it: the box is generated content in document order, before the prose, and
|
|
142
|
+
* a Foundry page is narrow enough that inlining it is the only arrangement
|
|
143
|
+
* that reads. It is **not** a page of its own — a page is what a UUID
|
|
144
|
+
* addresses, and a summary a reader has to navigate to is a summary they do
|
|
145
|
+
* not see.
|
|
139
146
|
* @returns {object} The JournalEntry document, keyed for the pack.
|
|
140
147
|
*/
|
|
141
|
-
export function buildJournalEntry({ id, name, markdown, leadName, folder, flags, stats, }: {
|
|
148
|
+
export function buildJournalEntry({ id, name, markdown, leadName, folder, flags, stats, infobox, }: {
|
|
142
149
|
id: string;
|
|
143
150
|
name: string;
|
|
144
151
|
markdown: string;
|
|
@@ -146,6 +153,7 @@ export function buildJournalEntry({ id, name, markdown, leadName, folder, flags,
|
|
|
146
153
|
folder?: string | null | undefined;
|
|
147
154
|
flags?: object | undefined;
|
|
148
155
|
stats?: object | undefined;
|
|
156
|
+
infobox?: string | undefined;
|
|
149
157
|
}): object;
|
|
150
158
|
/**
|
|
151
159
|
* Journals pack compiler.
|
|
@@ -6,6 +6,33 @@
|
|
|
6
6
|
* @returns {readonly string[]} The tags, in declaration order.
|
|
7
7
|
*/
|
|
8
8
|
export function declaredTags(type: string, groups?: object): readonly string[];
|
|
9
|
+
/**
|
|
10
|
+
* The declared groups scoped to this type, in declaration order.
|
|
11
|
+
*
|
|
12
|
+
* The one reading of `types` that the flattened list and the slot check share,
|
|
13
|
+
* so the two can never disagree about which groups a `being` is held to.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} type - The note's type.
|
|
16
|
+
* @param {object} [groups] - The grouped declaration.
|
|
17
|
+
* @returns {object[]} The groups that apply.
|
|
18
|
+
*/
|
|
19
|
+
export function applicableTagGroups(type: string, groups?: object): object[];
|
|
20
|
+
/**
|
|
21
|
+
* The single-valued slots a note of this type has, in declaration order.
|
|
22
|
+
*
|
|
23
|
+
* A group carrying `exclusive` states alternatives rather than attributes, so a
|
|
24
|
+
* note carrying two of its tags has answered one question twice. Only such a
|
|
25
|
+
* group is returned: the check has nothing to say about a group whose tags
|
|
26
|
+
* genuinely accumulate.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} type - The note's type.
|
|
29
|
+
* @param {object} [groups] - The grouped declaration.
|
|
30
|
+
* @returns {{slot: string, tags: readonly string[]}[]} The slots and their values.
|
|
31
|
+
*/
|
|
32
|
+
export function exclusiveTagGroups(type: string, groups?: object): {
|
|
33
|
+
slot: string;
|
|
34
|
+
tags: readonly string[];
|
|
35
|
+
}[];
|
|
9
36
|
/**
|
|
10
37
|
* Whether a note carries a given tag, however the author wrote it.
|
|
11
38
|
*
|
|
@@ -143,6 +170,21 @@ export const DRAFT_TAG: "draft";
|
|
|
143
170
|
* Kind and character are separate groups because one slot could not hold both: a
|
|
144
171
|
* fishing village is a `village` that is `fishing`, and the single-valued field
|
|
145
172
|
* this replaced had to spell it `Fishing Village` as a value of its own.
|
|
173
|
+
*
|
|
174
|
+
* **A group carrying `exclusive` is a single-valued slot**, and that is the one
|
|
175
|
+
* closure a tag vocabulary can make. Its tags are not several things the subject
|
|
176
|
+
* may be at once — they are the alternative answers to one question, so a note
|
|
177
|
+
* naming two of them has named none, and both together are refused as an error.
|
|
178
|
+
* The property is opt-in and changes nothing for a group without it: a place is
|
|
179
|
+
* freely a `port` and a `town`, and `draft` is orthogonal to everything. The
|
|
180
|
+
* value is what the slot is called, for the message a reader gets.
|
|
181
|
+
*
|
|
182
|
+
* **Closure stops at the slot, and deliberately.** A tag outside an exclusive
|
|
183
|
+
* group's list does not fill that group's slot and is not refused for failing
|
|
184
|
+
* to — `tags:` is open and a being tagged `undead` is describing the subject in
|
|
185
|
+
* the author's own words. What is refused is a near miss of a declared value,
|
|
186
|
+
* and two values of one slot; there is no third refusal to make without taking
|
|
187
|
+
* back the openness of the region these tags sit in.
|
|
146
188
|
*/
|
|
147
189
|
export const DECLARED_TAGS: Readonly<{
|
|
148
190
|
/** What a place *is*. */
|
|
@@ -165,6 +207,15 @@ export const DECLARED_TAGS: Readonly<{
|
|
|
165
207
|
types: string[];
|
|
166
208
|
tags: readonly string[];
|
|
167
209
|
}>;
|
|
210
|
+
/**
|
|
211
|
+
* What kind of being this is — a person, or one of the beasts and made
|
|
212
|
+
* things. A being is one or the other, so the group is a slot.
|
|
213
|
+
*/
|
|
214
|
+
beingKind: Readonly<{
|
|
215
|
+
types: string[];
|
|
216
|
+
exclusive: "kind";
|
|
217
|
+
tags: readonly string[];
|
|
218
|
+
}>;
|
|
168
219
|
/** A note's working state, which any note may carry. */
|
|
169
220
|
state: Readonly<{
|
|
170
221
|
types: null;
|