@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,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What is wrong with an authored pathname, or `""` when nothing is.
|
|
3
|
+
*
|
|
4
|
+
* Config-free, and a sentence rather than a code, so the lint that has a line
|
|
5
|
+
* and a column to attach it to and the resolver that has only a file say the
|
|
6
|
+
* same thing about the same value.
|
|
7
|
+
*
|
|
8
|
+
* @param {string|null|undefined} raw - The pathname, as authored.
|
|
9
|
+
* @returns {string} The problem, as a finding's sentence, or `""`.
|
|
10
|
+
*/
|
|
11
|
+
export function pathnameProblem(raw: string | null | undefined): string;
|
|
12
|
+
/**
|
|
13
|
+
* Every content package this build can resolve a pathname against.
|
|
14
|
+
*
|
|
15
|
+
* The package being built; every game system it compiles content for, which
|
|
16
|
+
* Foundry serves from `systems/<id>`; and every other package it declares a
|
|
17
|
+
* relationship with, which states that package's Foundry id and — where the two
|
|
18
|
+
* words differ — what its content is called.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} config - The resolved build configuration.
|
|
21
|
+
* @returns {Map<string, {root: string|null, id: string|null, own: boolean}>} The
|
|
22
|
+
* packages, by content package name. `root` is the Foundry directory the
|
|
23
|
+
* package is served from, `null` where the package ships no Foundry package.
|
|
24
|
+
*/
|
|
25
|
+
export function packageAddresses(config: object): Map<string, {
|
|
26
|
+
root: string | null;
|
|
27
|
+
id: string | null;
|
|
28
|
+
own: boolean;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* Resolve one authored pathname into the address each surface serves.
|
|
32
|
+
*
|
|
33
|
+
* @param {string|null|undefined} raw - The pathname, as authored.
|
|
34
|
+
* @param {object} config - The resolved build configuration. Required rather
|
|
35
|
+
* than defaulted, which is what keeps this module a leaf: it reads a
|
|
36
|
+
* configuration and never loads one, so the lint can import it without a
|
|
37
|
+
* repository to resolve.
|
|
38
|
+
* @returns {PathnameForms|null} The four forms, or `null` when the note names
|
|
39
|
+
* no file at all.
|
|
40
|
+
* @throws {Error} When the pathname is written in Foundry's own spelling, which
|
|
41
|
+
* resolves on one surface and nowhere else.
|
|
42
|
+
*/
|
|
43
|
+
export function resolvePathname(raw: string | null | undefined, config: object): PathnameForms | null;
|
|
44
|
+
/**
|
|
45
|
+
* One authored pathname, and the four addresses it resolves to.
|
|
46
|
+
*
|
|
47
|
+
* A note names a file once — in `img:`, in `data.portrait:`, in the body of a
|
|
48
|
+
* markdown image — and four surfaces have to serve it: a Foundry install, this
|
|
49
|
+
* repository's own working tree, the website, and the book. Each addresses the
|
|
50
|
+
* same file differently, so the authored pathname is a *statement of
|
|
51
|
+
* ownership* and every surface derives its own address from it. One statement,
|
|
52
|
+
* four derivations, one rule.
|
|
53
|
+
*
|
|
54
|
+
* ## The rule
|
|
55
|
+
*
|
|
56
|
+
* **The first segment says which package owns the file, when it is followed by
|
|
57
|
+
* `assets/`.** Everything after `assets/` is the *suffix* — the path inside
|
|
58
|
+
* that package's shipped tree, and the one piece every form is built from.
|
|
59
|
+
*
|
|
60
|
+
* | Authored | Owner | Suffix |
|
|
61
|
+
* | --------------------------- | ---------------- | ------------------- |
|
|
62
|
+
* | `sohl/assets/icons/a.svg` | the `sohl` package | `icons/a.svg` |
|
|
63
|
+
* | `images/beings/b.webp` | **this** package | `images/beings/b.webp` |
|
|
64
|
+
*
|
|
65
|
+
* A pathname that does not open with `<package>/assets/` belongs to the package
|
|
66
|
+
* being built, and the whole of it is the suffix. That is the ordinary case and
|
|
67
|
+
* the one nearly every note writes.
|
|
68
|
+
*
|
|
69
|
+
* The four forms, for a `thalorna` note writing `images/map.webp` (`thalorna`
|
|
70
|
+
* ships as the Foundry module `sohl-thalorna`):
|
|
71
|
+
*
|
|
72
|
+
* | Form | Address |
|
|
73
|
+
* | --------- | -------------------------------------------------- |
|
|
74
|
+
* | `foundry` | `modules/sohl-thalorna/assets/images/map.webp` |
|
|
75
|
+
* | `local` | `assets/images/map.webp` |
|
|
76
|
+
* | `web` | `https://cdn.heroiclands.org/thalorna/images/map.webp` |
|
|
77
|
+
* | `pdf` | `assets/images/map.webp` |
|
|
78
|
+
*
|
|
79
|
+
* **`<package>` and `<foundry-id>` are two different names.** The package is
|
|
80
|
+
* `thalorna` — what the content is called, what the website serves it under,
|
|
81
|
+
* and what a note writes. The Foundry id is `sohl-thalorna` — what Foundry
|
|
82
|
+
* installs the module as, and the only place that name appears. They coincide
|
|
83
|
+
* for `sohl` and `hm3`, which is exactly why the two are kept apart here rather
|
|
84
|
+
* than treated as one value.
|
|
85
|
+
*
|
|
86
|
+
* `local` and `pdf` read the same and mean different places: `local` is the file
|
|
87
|
+
* in the owning repository's working tree, `pdf` is where the book stages a copy
|
|
88
|
+
* beside its Typst source. They are derived separately because only one of them
|
|
89
|
+
* is a file a build may open — see {@link PathnameForms.own}.
|
|
90
|
+
*
|
|
91
|
+
* ## What is not a package pathname
|
|
92
|
+
*
|
|
93
|
+
* **An off-install address passes through on every surface**: a URL, a
|
|
94
|
+
* protocol-relative `//host/…`, or a `/`-rooted path, which Foundry serves from
|
|
95
|
+
* the data root and which names no package at all. That is how a note addresses
|
|
96
|
+
* core Foundry art (`/icons/svg/mystery-man.svg`) or a package this build knows
|
|
97
|
+
* nothing about (`/systems/dnd5e/icons/spell.webp`).
|
|
98
|
+
*
|
|
99
|
+
* **A package this build has never heard of keeps its ownership.** The website
|
|
100
|
+
* and the book need only the package's name and the suffix, so both resolve;
|
|
101
|
+
* the Foundry address needs the package's kind and its Foundry id, which only a
|
|
102
|
+
* declared relationship carries, so that one form comes back `null` and the
|
|
103
|
+
* caller that needs it refuses. Reading such a pathname as this package's own
|
|
104
|
+
* would file one package's name inside another's tree and say nothing.
|
|
105
|
+
*
|
|
106
|
+
* **A `systems/…` or `modules/…` pathname is refused.** It is a Foundry address
|
|
107
|
+
* written where an ownership statement belongs: it resolves for Foundry and for
|
|
108
|
+
* nothing else, because neither the website nor the book has any such directory.
|
|
109
|
+
* {@link pathnameProblem} names the replacement, and every surface refuses the
|
|
110
|
+
* value rather than deriving an address from it — a wrong address that resolves
|
|
111
|
+
* to a 404 is the failure this module exists to remove, and inventing one here
|
|
112
|
+
* would reintroduce it one directory along.
|
|
113
|
+
*
|
|
114
|
+
* ## The two empties
|
|
115
|
+
*
|
|
116
|
+
* `null` — or an absent key, which arrives as `undefined` — means **unset**: the
|
|
117
|
+
* note names no file and the caller's default applies. `""` means **blank on
|
|
118
|
+
* purpose**: the note names no file and wants none, so no default may replace
|
|
119
|
+
* it. `resolvePathname` returns `null` for the first and a form object whose
|
|
120
|
+
* every address is `""` for the second, so the two stay distinguishable all the
|
|
121
|
+
* way to the caller.
|
|
122
|
+
*
|
|
123
|
+
* @module
|
|
124
|
+
*/
|
|
125
|
+
/**
|
|
126
|
+
* The directory a package ships its files in, and the segment that marks a
|
|
127
|
+
* pathname's first segment as a package name.
|
|
128
|
+
*
|
|
129
|
+
* One constant rather than a literal in six places: it is the second segment of
|
|
130
|
+
* an authored package pathname, the last segment of a Foundry asset root, the
|
|
131
|
+
* whole of the `local` form's prefix, and the directory the book stages into.
|
|
132
|
+
*
|
|
133
|
+
* @type {string}
|
|
134
|
+
*/
|
|
135
|
+
export const ASSETS_SEGMENT: string;
|
|
136
|
+
/**
|
|
137
|
+
* The surfaces one authored pathname resolves for.
|
|
138
|
+
*
|
|
139
|
+
* Exported so a test can assert that every form a resolution carries is one of
|
|
140
|
+
* these, and that none is missing — the guard against a fifth surface being
|
|
141
|
+
* added to one caller and forgotten in the resolver.
|
|
142
|
+
*
|
|
143
|
+
* @type {readonly string[]}
|
|
144
|
+
*/
|
|
145
|
+
export const PATHNAME_SURFACES: readonly string[];
|
|
146
|
+
/**
|
|
147
|
+
* One authored pathname, resolved.
|
|
148
|
+
*/
|
|
149
|
+
export type PathnameForms = {
|
|
150
|
+
/**
|
|
151
|
+
* The pathname exactly as the note wrote it.
|
|
152
|
+
*/
|
|
153
|
+
authored: string;
|
|
154
|
+
/**
|
|
155
|
+
* `blank` for `""`, `external`
|
|
156
|
+
* for an address no package owns, `package` for an owned file.
|
|
157
|
+
*/
|
|
158
|
+
state: "blank" | "external" | "package";
|
|
159
|
+
/**
|
|
160
|
+
* The content package that owns the file, or
|
|
161
|
+
* `null` when no package does.
|
|
162
|
+
*/
|
|
163
|
+
package: string | null;
|
|
164
|
+
/**
|
|
165
|
+
* The path inside that package's shipped tree.
|
|
166
|
+
*/
|
|
167
|
+
suffix: string | null;
|
|
168
|
+
/**
|
|
169
|
+
* Whether the owner is the package being built, and so
|
|
170
|
+
* whether `local` names a file this build may open.
|
|
171
|
+
*/
|
|
172
|
+
own: boolean;
|
|
173
|
+
/**
|
|
174
|
+
* The address inside a Foundry install.
|
|
175
|
+
*/
|
|
176
|
+
foundry: string | null;
|
|
177
|
+
/**
|
|
178
|
+
* The file in the owning repository's tree.
|
|
179
|
+
*/
|
|
180
|
+
local: string | null;
|
|
181
|
+
/**
|
|
182
|
+
* The address the website serves.
|
|
183
|
+
*/
|
|
184
|
+
web: string | null;
|
|
185
|
+
/**
|
|
186
|
+
* Where the book stages its copy.
|
|
187
|
+
*/
|
|
188
|
+
pdf: string | null;
|
|
189
|
+
};
|
|
@@ -1,3 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The file on disk an authored image pathname names, or `null`.
|
|
3
|
+
*
|
|
4
|
+
* Two of the four forms {@link module:engine/pathnames.resolvePathname}
|
|
5
|
+
* derives, used together: `local` is the file in this repository's own tree,
|
|
6
|
+
* and `pdf` is where the book stages a copy of it.
|
|
7
|
+
*
|
|
8
|
+
* **Typst decides where that copy goes.** It resolves a path against its root —
|
|
9
|
+
* the directory holding the source it is given — and refuses to read anything
|
|
10
|
+
* above it. So a file reaches the compiler by being copied under the output
|
|
11
|
+
* directory rather than by widening the root to the whole repository: the
|
|
12
|
+
* emitted `.typ` and everything it opens sit in one directory, which is what
|
|
13
|
+
* makes the source a consumer can compile by hand with no flags, and what keeps
|
|
14
|
+
* a build from touching a path outside its own output.
|
|
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.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} src - The pathname, as authored.
|
|
22
|
+
* @param {object} config - The resolved configuration.
|
|
23
|
+
* @returns {{from: string, to: string}|null} The file, and where under the
|
|
24
|
+
* output directory it is staged.
|
|
25
|
+
*/
|
|
26
|
+
export function stagedImagePath(src: string, config: object): {
|
|
27
|
+
from: string;
|
|
28
|
+
to: string;
|
|
29
|
+
} | null;
|
|
30
|
+
/**
|
|
31
|
+
* Copy every banner the document tree names into the output directory.
|
|
32
|
+
*
|
|
33
|
+
* **A missing banner is not a failure.** A section plate implies a banner per
|
|
34
|
+
* section and art arrives later than rendering does, so a section that names
|
|
35
|
+
* none draws its plate over the book's ink and says nothing about it. One that
|
|
36
|
+
* names a file the build cannot read is a different matter — that is a
|
|
37
|
+
* statement the tree makes and the build cannot honour — and it is reported.
|
|
38
|
+
*
|
|
39
|
+
* @param {object[]} entries - The plan's entries.
|
|
40
|
+
* @param {object} config - The resolved configuration.
|
|
41
|
+
* @param {string} outDir - Where the book is written.
|
|
42
|
+
* @param {object[]} findings - Collected here rather than thrown.
|
|
43
|
+
* @returns {Map<string, string>} Declared path → the staged file's path,
|
|
44
|
+
* relative to the `.typ`.
|
|
45
|
+
*/
|
|
46
|
+
export function stageBanners(entries: object[], config: object, outDir: string, findings?: object[]): Map<string, string>;
|
|
1
47
|
/**
|
|
2
48
|
* The file name a downloaded book identifies itself by.
|
|
3
49
|
*
|
|
@@ -47,10 +47,17 @@ export function createParser(registry?: object): object;
|
|
|
47
47
|
* @param {object} [opts.registry] - The icon registry, when no parser is passed.
|
|
48
48
|
* @param {Map<string, string>} [opts.links] - Address slug → plan anchor.
|
|
49
49
|
* @param {Map<string, string>} [opts.glyphs] - Icon name → `{font, char}`.
|
|
50
|
+
* @param {Map<string, string>} [opts.images] - An image's address as authored →
|
|
51
|
+
* the staged file's path, relative to the `.typ`. An address this does not
|
|
52
|
+
* carry has no file the compiler can open, so the figure prints its caption
|
|
53
|
+
* alone — see {@link renderImage}.
|
|
50
54
|
* @param {number} [opts.headingOffset] - Added to every heading level, so a
|
|
51
55
|
* note's own `##` nests beneath the entry heading the book gave it.
|
|
52
56
|
* @param {string} [opts.anchorPrefix] - The entry's anchor, which namespaces
|
|
53
57
|
* every `{#slug}` the body declares.
|
|
58
|
+
* @param {boolean} [opts.dropCap] - Whether to open the body with a raised
|
|
59
|
+
* capital. Set for an entry, which begins a page; not for front matter or a
|
|
60
|
+
* prose file, which carry headings of their own.
|
|
54
61
|
* @returns {string} Typst markup.
|
|
55
62
|
*/
|
|
56
63
|
export function markdownToTypst(markdown: string, opts?: {
|
|
@@ -58,9 +65,51 @@ export function markdownToTypst(markdown: string, opts?: {
|
|
|
58
65
|
registry?: object | undefined;
|
|
59
66
|
links?: Map<string, string> | undefined;
|
|
60
67
|
glyphs?: Map<string, string> | undefined;
|
|
68
|
+
images?: Map<string, string> | undefined;
|
|
61
69
|
headingOffset?: number | undefined;
|
|
62
70
|
anchorPrefix?: string | undefined;
|
|
71
|
+
dropCap?: boolean | undefined;
|
|
63
72
|
}): string;
|
|
73
|
+
/**
|
|
74
|
+
* The Typst definitions the book's page furniture is drawn with.
|
|
75
|
+
*
|
|
76
|
+
* Emitted once at the head of the document, for the reason the infobox panel's
|
|
77
|
+
* rules are: 2,000 entries each restating the plate, the running foot and the
|
|
78
|
+
* drop cap is a megabyte of repetition, and the one place a reader changes how
|
|
79
|
+
* the book looks should be one place.
|
|
80
|
+
*
|
|
81
|
+
* ## The geometry is stated, not discovered
|
|
82
|
+
*
|
|
83
|
+
* The page is US Letter with a 1.9cm margin, and the plate bleeds off all
|
|
84
|
+
* three edges it touches — so the plate has to know the paper's width and the
|
|
85
|
+
* margin it is escaping. Both are `#let` bindings here rather than numbers
|
|
86
|
+
* repeated down the file, and every other measure is arithmetic on them.
|
|
87
|
+
*
|
|
88
|
+
* ## Three things that cost time to discover, encoded here
|
|
89
|
+
*
|
|
90
|
+
* - **A title inherits the body's justification and hyphenation** unless told
|
|
91
|
+
* otherwise. Both are habits of body text that make a display line look
|
|
92
|
+
* broken, so the plate turns them off inside itself.
|
|
93
|
+
* - **The plate's height follows its title**, and the line count is derived
|
|
94
|
+
* from the title's *natural* width: measuring an already-wrapped block does
|
|
95
|
+
* not report the wrapped height, and a percentage width cannot resolve
|
|
96
|
+
* inside `measure`. So the title arrives as a string to be measured
|
|
97
|
+
* alongside the heading that is actually drawn.
|
|
98
|
+
* - **A float is not breakable.** A table taller than the page placed as one
|
|
99
|
+
* silently piles its rows on top of each other at the foot of the page —
|
|
100
|
+
* no warning, no error. So `book-wide` measures first and gives a table that
|
|
101
|
+
* will not fit its own single-column pages instead.
|
|
102
|
+
*
|
|
103
|
+
* ## The ornament is drawn, not typed
|
|
104
|
+
*
|
|
105
|
+
* The running foot's centre mark is a rotated square rather than a dingbat
|
|
106
|
+
* character, because the faces a consumer names and the faces a build runner
|
|
107
|
+
* carries are not the same set, and a missing glyph on 2,000 feet is a
|
|
108
|
+
* tofu box on every page of the book.
|
|
109
|
+
*
|
|
110
|
+
* @returns {string} Typst markup.
|
|
111
|
+
*/
|
|
112
|
+
export function bookTypstPreamble(): string;
|
|
64
113
|
/**
|
|
65
114
|
* The whole book, as one Typst document.
|
|
66
115
|
*
|
|
@@ -96,6 +145,46 @@ export function markdownToTypst(markdown: string, opts?: {
|
|
|
96
145
|
* `#outline()` needs no depth limit under this model: what prints is decided
|
|
97
146
|
* per heading, not by how deep the tree happens to go.
|
|
98
147
|
*
|
|
148
|
+
* ## An entry owns its page, and the page is set in two columns
|
|
149
|
+
*
|
|
150
|
+
* A reference book is consulted rather than read through. An entry beginning
|
|
151
|
+
* halfway down a page is harder to find, cannot carry its own running head
|
|
152
|
+
* honestly, and makes a page number in the contents point at the middle of
|
|
153
|
+
* something else — so every entry opens a page of its own, under a full-bleed
|
|
154
|
+
* plate carrying a kicker and its name.
|
|
155
|
+
*
|
|
156
|
+
* The body is set in **two columns**, the measure a reference work wants and
|
|
157
|
+
* the one every other decision follows from: an image with no width class is a
|
|
158
|
+
* column wide, the infobox flows in the column measure and breaks between its
|
|
159
|
+
* sections, and a table wider than {@link WIDE_TABLE_COLUMNS} spans the page.
|
|
160
|
+
* The columns are the *page's* rather than a `columns()` block's, because only
|
|
161
|
+
* a page with columns can carry a float that spans them — which is what the
|
|
162
|
+
* plate, a wide table and a full-width figure all need.
|
|
163
|
+
*
|
|
164
|
+
* Two columns are print's answer and print's alone: a scrolling page has no
|
|
165
|
+
* fixed viewport, so the website keeps one measure.
|
|
166
|
+
*
|
|
167
|
+
* ## What a section declares, its entries inherit
|
|
168
|
+
*
|
|
169
|
+
* {@link module:engine/pdf-toc.PRESENTATION_KEYS} travels down the document
|
|
170
|
+
* tree, and two of those keys are read here:
|
|
171
|
+
*
|
|
172
|
+
* - **`page`** — `banner:`, the plate's picture; `kicker:`, the line above an
|
|
173
|
+
* entry's name; and `columns:`, the measure the section's pages are set in.
|
|
174
|
+
* - **`footer`** — the name the running foot carries, which is the section's
|
|
175
|
+
* own title when nothing says otherwise.
|
|
176
|
+
*
|
|
177
|
+
* `header` and `infobox` are reserved and read by nothing: the running head is
|
|
178
|
+
* a foot in this design, and which infobox a note draws is decided by the
|
|
179
|
+
* note's type.
|
|
180
|
+
*
|
|
181
|
+
* ## A missing banner is a plate without a picture
|
|
182
|
+
*
|
|
183
|
+
* A section plate implies a banner per section, and art arrives later than
|
|
184
|
+
* rendering does. A section that names no banner — or names one the build
|
|
185
|
+
* cannot read — still gets its plate, its kicker and its title, set over the
|
|
186
|
+
* book's ink.
|
|
187
|
+
*
|
|
99
188
|
* ## Headings carry the structure, so nothing else has to
|
|
100
189
|
*
|
|
101
190
|
* Every section, every prose file and every entry is a real Typst heading at
|
|
@@ -113,9 +202,16 @@ export function markdownToTypst(markdown: string, opts?: {
|
|
|
113
202
|
* @param {string[]} [opts.front] - Rendered Typst for each front-matter file.
|
|
114
203
|
* @param {object} [opts.fonts] - `{ serif, sans, mono }` family names.
|
|
115
204
|
* @param {string} [opts.version] - Stamped on the title page when given.
|
|
205
|
+
* @param {string} [opts.preamble] - Definitions the bodies call, emitted once
|
|
206
|
+
* above the title page. A panel every entry draws is a set of rules stated
|
|
207
|
+
* here rather than repeated 2,500 times.
|
|
208
|
+
* @param {Map<string, string>} [opts.banners] - A banner as the document tree
|
|
209
|
+
* declared it → the staged file's path, relative to the `.typ`. A declared
|
|
210
|
+
* banner this map does not carry has no file the compiler can open, so the
|
|
211
|
+
* plate draws without a picture.
|
|
116
212
|
* @returns {string} A complete `.typ` document.
|
|
117
213
|
*/
|
|
118
|
-
export function renderBook({ plan, bodies, title, subtitle, front, fonts, version, }?: {
|
|
214
|
+
export function renderBook({ plan, bodies, title, subtitle, front, fonts, version, preamble, banners, }?: {
|
|
119
215
|
plan: object;
|
|
120
216
|
bodies: Map<string, string>;
|
|
121
217
|
title: string;
|
|
@@ -123,6 +219,8 @@ export function renderBook({ plan, bodies, title, subtitle, front, fonts, versio
|
|
|
123
219
|
front?: string[] | undefined;
|
|
124
220
|
fonts?: object | undefined;
|
|
125
221
|
version?: string | undefined;
|
|
222
|
+
preamble?: string | undefined;
|
|
223
|
+
banners?: Map<string, string> | undefined;
|
|
126
224
|
}): string;
|
|
127
225
|
/**
|
|
128
226
|
* Point every internal link at a label the document actually declares.
|
|
@@ -103,11 +103,16 @@ export function planDocument(nodes: object[], { selections }?: {
|
|
|
103
103
|
/**
|
|
104
104
|
* Presentation a node may declare, and that its descendants inherit.
|
|
105
105
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
106
|
+
* Inheritance is what makes them worth declaring at all — `Gear` says once
|
|
107
|
+
* which banner its entries are plated over, and nine sections beneath it
|
|
108
|
+
* agree.
|
|
109
|
+
*
|
|
110
|
+
* **`page` and `footer` are read by the renderer**, and their shapes are
|
|
111
|
+
* checked here so that a key nothing can act on is an error rather than a
|
|
112
|
+
* silence. `header` and `infobox` are reserved and read by nothing: the
|
|
113
|
+
* running head is a foot in this design, and which infobox a note draws is
|
|
114
|
+
* decided by the note's type. They keep their place in the format because the
|
|
115
|
+
* shape of the file is the thing consumers commit to.
|
|
111
116
|
*
|
|
112
117
|
* @type {readonly string[]}
|
|
113
118
|
*/
|
|
@@ -36,6 +36,14 @@ export function collectContentPages(contentBase: string, ctx: object): {
|
|
|
36
36
|
* addressed by type and slug: they are a book with chapters, and a reader
|
|
37
37
|
* follows their paths. A `README` is its directory's landing.
|
|
38
38
|
*
|
|
39
|
+
* **The section is the tree's, never the note's.** `tree.section` is the
|
|
40
|
+
* mount point a `trees` entry configures — fixed, physical, and the same
|
|
41
|
+
* value `site-index.mjs` indexes a tree page's address under. A note's own
|
|
42
|
+
* `subType` is a genre and reaches no address, the same contract
|
|
43
|
+
* `packageAddress()` holds for a content page: reading it here would move a
|
|
44
|
+
* page's URL, its file destination (`pageDestination`) and the address a
|
|
45
|
+
* wikilink cites it by, every time an author classified it.
|
|
46
|
+
*
|
|
39
47
|
* @param {object} tree - `{ from, rel, section, route }`.
|
|
40
48
|
* @param {object} ctx - `{ mount }`.
|
|
41
49
|
* @returns {{pages: object[], fmLinkFindings: object[]}}
|
|
@@ -229,11 +237,16 @@ export function sectionFrontmatter(meta: object): object;
|
|
|
229
237
|
* for.
|
|
230
238
|
* @param {(data: object, page: object) => void} [options.decorate] - Called
|
|
231
239
|
* with each page's frontmatter, for whatever a consumer's own pass adds.
|
|
240
|
+
* @param {(src: string) => string} [options.webSrc] - Translates an authored
|
|
241
|
+
* pathname into the address the website serves. Every artwork field goes
|
|
242
|
+
* through it, so a page's `img:` and its body images name the same file the
|
|
243
|
+
* same way.
|
|
232
244
|
* @returns {object} The frontmatter to write.
|
|
233
245
|
*/
|
|
234
|
-
export function pageFrontmatter(page: object, { readmeSections, decorate }: {
|
|
246
|
+
export function pageFrontmatter(page: object, { readmeSections, decorate, webSrc }: {
|
|
235
247
|
readmeSections?: Record<string, object> | undefined;
|
|
236
248
|
decorate?: ((data: object, page: object) => void) | undefined;
|
|
249
|
+
webSrc?: ((src: string) => string) | undefined;
|
|
237
250
|
}): object;
|
|
238
251
|
/**
|
|
239
252
|
* Where a page is written, relative to the output root.
|
|
@@ -273,13 +286,15 @@ export function pageDestination(page: any): string;
|
|
|
273
286
|
*
|
|
274
287
|
* @param {object[]} pages - Every page.
|
|
275
288
|
* @param {object} options - Everything the render needs.
|
|
276
|
-
* @returns {{written: number, byKind: Record<string, number>, tableErrors: object[],
|
|
289
|
+
* @returns {{written: number, byKind: Record<string, number>, tableErrors: object[],
|
|
290
|
+
* wikiErrors: object[], imageErrors: object[]}}
|
|
277
291
|
*/
|
|
278
292
|
export function renderPages(pages: object[], options: object): {
|
|
279
293
|
written: number;
|
|
280
294
|
byKind: Record<string, number>;
|
|
281
295
|
tableErrors: object[];
|
|
282
296
|
wikiErrors: object[];
|
|
297
|
+
imageErrors: object[];
|
|
283
298
|
};
|
|
284
299
|
/**
|
|
285
300
|
* Writes the Hugo sections a published tree declares.
|
|
@@ -398,7 +413,7 @@ export function resolveOutputRoot(rootDir: string, out: string): string;
|
|
|
398
413
|
* `sql` directive with none prepared is a table error: nothing here runs a
|
|
399
414
|
* query.
|
|
400
415
|
* @returns {{gates: object, stats: object|null, tableErrors: object[],
|
|
401
|
-
* wikiErrors: object[], manifests: object|null}}
|
|
416
|
+
* wikiErrors: object[], imageErrors: object[], manifests: object|null}}
|
|
402
417
|
*/
|
|
403
418
|
export function buildSite({ config, outRoot, sqlTables }?: {
|
|
404
419
|
config?: object | undefined;
|
|
@@ -409,6 +424,7 @@ export function buildSite({ config, outRoot, sqlTables }?: {
|
|
|
409
424
|
stats: object | null;
|
|
410
425
|
tableErrors: object[];
|
|
411
426
|
wikiErrors: object[];
|
|
427
|
+
imageErrors: object[];
|
|
412
428
|
manifests: object | null;
|
|
413
429
|
};
|
|
414
430
|
export { formatUnaddressableFinding };
|
|
@@ -49,6 +49,37 @@ export function wikiContext(built: SiteIndex, { src, file, type, errors, foreign
|
|
|
49
49
|
file?: string | undefined;
|
|
50
50
|
foreignIndex?: Map<string, object> | undefined;
|
|
51
51
|
}): object;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve one infobox reference against a site index.
|
|
54
|
+
*
|
|
55
|
+
* A note writes a reference three ways and all three reach here: a bare
|
|
56
|
+
* **shortcode** (`slntlncmpny`), a short **address** (`affiliation-slntlncmpny`)
|
|
57
|
+
* and a **canonical** one (`sohl-sohl-skill-melee`). The first is the ordinary
|
|
58
|
+
* case and the ambiguous one — a shortcode is unique within a type and not
|
|
59
|
+
* across a tree — so a caller that knows what it expects passes `hint.type`
|
|
60
|
+
* and the lookup is narrowed to it.
|
|
61
|
+
*
|
|
62
|
+
* **Without a hint the types are tried in sorted order**, so two trees holding
|
|
63
|
+
* the same note resolve it the same way. A shortcode two types both claim
|
|
64
|
+
* answers with the first alphabetically, which is a stable wrong answer rather
|
|
65
|
+
* than an unstable one; a caller that cares supplies the hint.
|
|
66
|
+
*
|
|
67
|
+
* The `address` on the answer is the slug form — `type-shortcode` — because
|
|
68
|
+
* that is what the book's own link map is keyed by and what a wikilink is
|
|
69
|
+
* written as.
|
|
70
|
+
*
|
|
71
|
+
* @param {SiteIndex} siteIndex - The index.
|
|
72
|
+
* @param {unknown} ref - The reference, as authored.
|
|
73
|
+
* @param {object} [hint] - `{type}`, where the caller knows it.
|
|
74
|
+
* @returns {{name?: string, url?: string, address?: string, subType?: string}|undefined}
|
|
75
|
+
* The page, or `undefined` where nothing answers.
|
|
76
|
+
*/
|
|
77
|
+
export function resolveInfoboxRef(siteIndex: SiteIndex, ref: unknown, hint?: object): {
|
|
78
|
+
name?: string;
|
|
79
|
+
url?: string;
|
|
80
|
+
address?: string;
|
|
81
|
+
subType?: string;
|
|
82
|
+
} | undefined;
|
|
52
83
|
/**
|
|
53
84
|
* One page the site will publish, as the index needs to see it.
|
|
54
85
|
*/
|
|
@@ -123,13 +154,14 @@ export type SiteIndex = {
|
|
|
123
154
|
*/
|
|
124
155
|
sections: Set<string>;
|
|
125
156
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
157
|
+
* `type:shortcode` → page, for callers
|
|
158
|
+
* resolving embedded references (a
|
|
159
|
+
* being's items, say).
|
|
129
160
|
*/
|
|
130
161
|
refIndex: Map<string, {
|
|
131
162
|
name: string;
|
|
132
163
|
url: string;
|
|
164
|
+
subType?: string;
|
|
133
165
|
}>;
|
|
134
166
|
/**
|
|
135
167
|
* Addresses claimed by
|
|
@@ -199,6 +199,28 @@ export function convertWikilinks(markdown: string, { type, id, pack, docPack, in
|
|
|
199
199
|
anchor?: string;
|
|
200
200
|
}>;
|
|
201
201
|
};
|
|
202
|
+
/**
|
|
203
|
+
* Resolve one reference against a compile's address index.
|
|
204
|
+
*
|
|
205
|
+
* The compile-time counterpart of
|
|
206
|
+
* {@link module:engine/site-index.resolveInfoboxRef}: the same three authored
|
|
207
|
+
* forms — a bare shortcode, a short address, a canonical one — answered with
|
|
208
|
+
* what a **compendium** can use. A local target answers with the UUID its own
|
|
209
|
+
* document was addressed by; a foreign one with the UUID its package
|
|
210
|
+
* published.
|
|
211
|
+
*
|
|
212
|
+
* @param {object} index - From {@link buildWikilinkIndex}.
|
|
213
|
+
* @param {unknown} ref - The reference, as authored.
|
|
214
|
+
* @param {object} [hint] - `{type}`, where the caller knows what it expects.
|
|
215
|
+
* @returns {{name?: string, uuid?: string, address?: string, subType?: string}|undefined}
|
|
216
|
+
* The target, or `undefined` where nothing answers.
|
|
217
|
+
*/
|
|
218
|
+
export function resolveReference(index: object, ref: unknown, hint?: object): {
|
|
219
|
+
name?: string;
|
|
220
|
+
uuid?: string;
|
|
221
|
+
address?: string;
|
|
222
|
+
subType?: string;
|
|
223
|
+
} | undefined;
|
|
202
224
|
import { ITEM_PACK } from "./ids.mjs";
|
|
203
225
|
import { PACK_BY_TYPE } from "./ids.mjs";
|
|
204
226
|
import { packForType } from "./ids.mjs";
|
|
@@ -18,12 +18,11 @@ export function hm3DefaultItemArt(type: string): string;
|
|
|
18
18
|
* no `img:` of its own, rather than shipping a mismatched icon. So every type
|
|
19
19
|
* `item-builders.mjs` declares has a row here.
|
|
20
20
|
*
|
|
21
|
-
* **Paths
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* neighbours.
|
|
21
|
+
* **Paths name the package that owns the file.** These icons are not the
|
|
22
|
+
* consuming package's — they are shipped by the HM3 system — so they are
|
|
23
|
+
* written `hm3/assets/…` and each surface derives its own address from that.
|
|
24
|
+
* That addresses the icons HM3's own compendiums already use, so an item
|
|
25
|
+
* compiled from a note looks like its hand-authored neighbours.
|
|
27
26
|
*
|
|
28
27
|
* **A one-to-many type gets one default**, because art is keyed by note type
|
|
29
28
|
* and a note type is what a registry entry addresses. `weapongear` compiles
|
package/types/hm3/index.d.mts
CHANGED
|
@@ -4,4 +4,5 @@ export * as documentSubtypes from "./document-subtypes.mjs";
|
|
|
4
4
|
export * as items from "./items.mjs";
|
|
5
5
|
export * as actors from "./actors.mjs";
|
|
6
6
|
export * as templatePriority from "./template-priority.mjs";
|
|
7
|
+
export * as infobox from "./infobox.mjs";
|
|
7
8
|
export { HM3_DEFAULT_ITEM_ART, hm3DefaultItemArt } from "./default-item-art.mjs";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** What this system's box is called. @type {string} */
|
|
2
|
+
export const HM3_INFOBOX_TITLE: string;
|
|
3
|
+
/**
|
|
4
|
+
* HM3's presentation overlay: what one of this system's fields is called where
|
|
5
|
+
* humanising its key gives the wrong word.
|
|
6
|
+
*
|
|
7
|
+
* **Not a second field list.** A field it does not mention still gets a row
|
|
8
|
+
* under its own humanised name, so a field added to {@link HM3_ITEM_FIELDS}
|
|
9
|
+
* reaches the box with no edit here.
|
|
10
|
+
*
|
|
11
|
+
* @type {Readonly<Record<string, {label?: string, withheld?: string}>>}
|
|
12
|
+
*/
|
|
13
|
+
export const HM3_FIELD_PRESENTATION: Readonly<Record<string, {
|
|
14
|
+
label?: string;
|
|
15
|
+
withheld?: string;
|
|
16
|
+
}>>;
|
|
17
|
+
/**
|
|
18
|
+
* HM3's infobox declaration.
|
|
19
|
+
*
|
|
20
|
+
* @type {object}
|
|
21
|
+
*/
|
|
22
|
+
export const HM3_INFOBOX: object;
|
|
@@ -26,13 +26,14 @@ export function isBeing(fm: {
|
|
|
26
26
|
* @param {object|null|undefined} sohl - The note's `sohl` frontmatter block.
|
|
27
27
|
* @param {Map<string, {name?: string, url?: string}>} index - Content index,
|
|
28
28
|
* `"<type>:<shortcode>"` → the item's page.
|
|
29
|
-
* @returns {object|null
|
|
30
|
-
*
|
|
29
|
+
* @returns {object|null} The block with its info-block fields filled in, or the
|
|
30
|
+
* input unchanged when there is nothing to derive from — with an absent block
|
|
31
|
+
* reported as `null`, the value an empty one already carries.
|
|
31
32
|
*/
|
|
32
33
|
export function deriveBeingInfo(sohl: object | null | undefined, index: Map<string, {
|
|
33
34
|
name?: string;
|
|
34
35
|
url?: string;
|
|
35
|
-
}>): object | null
|
|
36
|
+
}>): object | null;
|
|
36
37
|
/**
|
|
37
38
|
* The note `type` whose pages carry a being info block.
|
|
38
39
|
*
|
package/types/sohl/index.d.mts
CHANGED
|
@@ -3,6 +3,7 @@ export * as documentSubtypes from "./document-subtypes.mjs";
|
|
|
3
3
|
export * as items from "./items.mjs";
|
|
4
4
|
export * as actors from "./actors.mjs";
|
|
5
5
|
export * as kbPasses from "./kb-passes.mjs";
|
|
6
|
+
export * as infobox from "./infobox.mjs";
|
|
6
7
|
export { AFFILIATION_STANDINGS } from "./affiliation-standings.mjs";
|
|
7
8
|
export { DEFAULT_ITEM_ART, defaultItemArt } from "./default-item-art.mjs";
|
|
8
9
|
export { BEING_TYPE, GEAR_TYPE_TO_KEY, deriveBeingInfo, isBeing } from "./being-info.mjs";
|