@heroiclands/package-build 22.28.0 → 23.1.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 +72 -0
- package/README.md +1 -1
- package/bin/content-commands.mjs +1 -1
- package/ci/content-format-reference.mjs +46 -0
- package/docs/api.md +125 -142
- package/docs/authoring/assets.md +1 -1
- package/docs/authoring/dates-and-calendars.md +12 -6
- package/docs/authoring/first-note.md +1 -1
- package/docs/authoring/frontmatter.md +2 -2
- package/docs/authoring/links-and-markup.md +145 -29
- package/docs/commands.md +8 -10
- package/docs/configuration.md +13 -11
- package/docs/content-format.md +3 -3
- package/docs/guides/book.md +59 -4
- package/docs/guides/icons.md +4 -5
- package/docs/guides/packs.md +6 -0
- package/docs/guides/reference-pages.md +21 -1
- package/docs/guides/release-and-deploy.md +17 -1
- package/docs/guides/site.md +17 -18
- package/docs/reference/format-details.md +904 -275
- package/docs/reference/note-types.md +282 -91
- package/docs/reference/theme-pages.md +58 -66
- package/engine/address-values.mjs +124 -4
- package/engine/address.mjs +44 -3
- package/engine/anchors.mjs +103 -3
- package/engine/art-slots.mjs +2 -9
- package/engine/base-compiler.mjs +25 -6
- package/engine/calendar-artifacts.mjs +4 -1
- package/engine/calendar-notes.mjs +166 -102
- package/engine/content-format-check.mjs +2 -3
- package/engine/content-format.mjs +44 -1
- package/engine/content-format.yaml +84 -16
- package/engine/content-images.mjs +10 -1
- package/engine/content-index.mjs +37 -9
- package/engine/content-links.mjs +24 -5
- package/engine/content-tables.mjs +24 -3
- package/engine/data-keys.mjs +347 -0
- package/engine/derived-sections.mjs +322 -0
- package/engine/diagnostics.mjs +28 -0
- package/engine/event-fields.mjs +187 -0
- package/engine/event-rows.mjs +126 -0
- package/engine/event-views.mjs +305 -0
- package/engine/exported-scene.mjs +29 -53
- package/engine/frontmatter-lint.mjs +103 -77
- package/engine/frontmatter.mjs +7 -2
- package/engine/generate.mjs +53 -0
- package/engine/generated-sections.mjs +223 -0
- package/engine/heading-attributes.mjs +4 -2
- package/engine/helpers.mjs +12 -1
- package/engine/holdings.mjs +12 -33
- package/engine/ids.mjs +17 -11
- package/engine/index.mjs +4 -4
- package/engine/infobox-render.mjs +21 -6
- package/engine/infobox.mjs +217 -99
- package/engine/journals.mjs +104 -47
- package/engine/literature-notes.mjs +136 -0
- package/engine/literature-works.mjs +161 -0
- package/engine/map-graphviz.mjs +4 -18
- package/engine/markdown-expressions.mjs +116 -11
- package/engine/metadata-index.mjs +34 -0
- package/engine/near-miss.mjs +90 -0
- package/engine/note-addresses.mjs +75 -19
- package/engine/note-dates.mjs +5 -3
- package/engine/note-event-terms.mjs +364 -0
- package/engine/note-events.mjs +823 -0
- package/engine/note-vocabulary.mjs +195 -92
- package/engine/office-holders.mjs +20 -54
- package/engine/packagebuild-index.mjs +16 -1
- package/engine/pdf-build.mjs +97 -60
- package/engine/pdf-index.mjs +263 -0
- package/engine/pdf-render.mjs +47 -5
- package/engine/place-relation-terms.mjs +174 -0
- package/engine/place-relations.mjs +72 -160
- package/engine/rank-ladder.mjs +14 -41
- package/engine/reckoning-markers.mjs +2 -0
- package/engine/region-events.mjs +6 -12
- package/engine/scenes.mjs +73 -468
- package/engine/site-build.mjs +74 -85
- package/engine/site-index.mjs +3 -1
- package/engine/site-maps.mjs +43 -29
- package/engine/site-preview.mjs +40 -31
- package/engine/social-ties.mjs +2 -1
- package/engine/sql-tables.mjs +212 -9
- package/engine/standing-terms.mjs +145 -6
- package/engine/standings.mjs +37 -49
- package/engine/stub-lint.mjs +6 -0
- package/engine/web-wikilinks.mjs +21 -1
- package/engine/wikilink-syntax.mjs +34 -4
- package/engine/wikilinks.mjs +27 -2
- package/hugo-theme/layouts/partials/infobox/entry.html +4 -1
- package/hugo-theme/layouts/partials/page-body.html +0 -2
- package/hugo-theme/static/css/style.css +25 -55
- package/package.json +1 -2
- package/packagebuild-metadata.jsonl +13 -13
- package/sohl/default-item-art.mjs +6 -6
- package/sohl/note-schemas.mjs +8 -73
- package/types/engine/address-values.d.mts +50 -3
- package/types/engine/address.d.mts +21 -0
- package/types/engine/anchors.d.mts +49 -2
- package/types/engine/art-slots.d.mts +1 -1
- package/types/engine/base-compiler.d.mts +13 -4
- package/types/engine/calendar-notes.d.mts +7 -0
- package/types/engine/content-format.d.mts +52 -0
- package/types/engine/content-images.d.mts +5 -1
- package/types/engine/content-tables.d.mts +9 -1
- package/types/engine/data-keys.d.mts +145 -0
- package/types/engine/derived-sections.d.mts +73 -0
- package/types/engine/diagnostics.d.mts +15 -0
- package/types/engine/event-fields.d.mts +99 -0
- package/types/engine/event-rows.d.mts +12 -0
- package/types/engine/event-views.d.mts +67 -0
- package/types/engine/exported-scene.d.mts +5 -2
- package/types/engine/frontmatter-lint.d.mts +3 -3
- package/types/engine/generated-sections.d.mts +143 -0
- package/types/engine/holdings.d.mts +6 -20
- package/types/engine/ids.d.mts +15 -12
- package/types/engine/index.d.mts +1 -1
- package/types/engine/infobox.d.mts +13 -3
- package/types/engine/journals.d.mts +24 -11
- package/types/engine/literature-notes.d.mts +29 -0
- package/types/engine/literature-works.d.mts +67 -0
- package/types/engine/map-graphviz.d.mts +2 -10
- package/types/engine/markdown-expressions.d.mts +12 -3
- package/types/engine/near-miss.d.mts +42 -0
- package/types/engine/note-addresses.d.mts +7 -1
- package/types/engine/note-event-terms.d.mts +68 -0
- package/types/engine/note-events.d.mts +71 -0
- package/types/engine/note-vocabulary.d.mts +37 -8
- package/types/engine/office-holders.d.mts +3 -0
- package/types/engine/pdf-index.d.mts +35 -0
- package/types/engine/pdf-render.d.mts +8 -2
- package/types/engine/place-relation-terms.d.mts +83 -0
- package/types/engine/place-relations.d.mts +11 -66
- package/types/engine/rank-ladder.d.mts +2 -0
- package/types/engine/region-events.d.mts +6 -12
- package/types/engine/scenes.d.mts +6 -32
- package/types/engine/site-build.d.mts +8 -22
- package/types/engine/site-maps.d.mts +15 -7
- package/types/engine/sql-tables.d.mts +77 -2
- package/types/engine/standing-terms.d.mts +37 -4
- package/types/engine/standings.d.mts +14 -9
- package/types/engine/wikilink-syntax.d.mts +20 -0
- package/types/engine/wikilinks.d.mts +3 -2
- package/docs/guides/government-migration.md +0 -52
- package/engine/itinerary-scenes.mjs +0 -166
- package/engine/lore-events.mjs +0 -197
- package/engine/map-notes.mjs +0 -1309
- package/engine/map-raster.mjs +0 -51
- package/hugo-theme/layouts/partials/holdings/entry.html +0 -21
- package/hugo-theme/layouts/partials/holdings/kind-groups.html +0 -71
- package/hugo-theme/layouts/partials/holdings.html +0 -61
- package/hugo-theme/layouts/partials/place-map.html +0 -9
- package/types/engine/itinerary-scenes.d.mts +0 -29
- package/types/engine/lore-events.d.mts +0 -14
- package/types/engine/map-notes.d.mts +0 -297
- package/types/engine/map-raster.d.mts +0 -8
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A note's **generated sections**: the H1 sections the build appends after
|
|
16
|
+
* everything the author wrote, each under a fixed anchor.
|
|
17
|
+
*
|
|
18
|
+
* Two families share the one mechanism. The derived lists —
|
|
19
|
+
* {@link module:engine/derived-sections} — say where a place sits, who governs
|
|
20
|
+
* it, what it governs, which works concern the note and what the map from a
|
|
21
|
+
* place shows. The event views — {@link module:engine/event-views} — list the
|
|
22
|
+
* events concerning a note. Each section is Markdown an author could have
|
|
23
|
+
* written, so the website, the Foundry journal and the book receive one text
|
|
24
|
+
* and set it as they set an authored section: Foundry starts a page at the
|
|
25
|
+
* heading, the website a section in its table of contents, the book a section
|
|
26
|
+
* of the entry.
|
|
27
|
+
*
|
|
28
|
+
* Three rules hold for every section:
|
|
29
|
+
*
|
|
30
|
+
* - **An author's own anchor replaces it.** A note whose body declares a prose
|
|
31
|
+
* anchor with the section's slug keeps its own section and is given no
|
|
32
|
+
* generated one.
|
|
33
|
+
* - **A section with nothing to show is not generated.**
|
|
34
|
+
* - **Sections follow the author's text in the order of
|
|
35
|
+
* {@link GENERATED_SECTIONS}.**
|
|
36
|
+
*
|
|
37
|
+
* This module is a leaf: it holds the order, the join and the boundary, and
|
|
38
|
+
* imports nothing, so the table expansion every surface runs can read it
|
|
39
|
+
* without reaching the derivations.
|
|
40
|
+
*
|
|
41
|
+
* @module
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Every generated section, in the order it follows the author's text: where a
|
|
46
|
+
* place sits and who governs it, then what happened, then what was written
|
|
47
|
+
* about it, then the map.
|
|
48
|
+
*
|
|
49
|
+
* @type {ReadonlyArray<Readonly<{slug: string, heading: string}>>}
|
|
50
|
+
*/
|
|
51
|
+
export const GENERATED_SECTIONS = Object.freeze(
|
|
52
|
+
[
|
|
53
|
+
{ slug: "within", heading: "Within" },
|
|
54
|
+
{ slug: "governedby", heading: "Governed by" },
|
|
55
|
+
{ slug: "governedplaces", heading: "Governed places" },
|
|
56
|
+
{ slug: "chronology", heading: "Chronology" },
|
|
57
|
+
{ slug: "events", heading: "Events" },
|
|
58
|
+
{ slug: "accounts", heading: "Accounts" },
|
|
59
|
+
{ slug: "followed", heading: "What followed" },
|
|
60
|
+
{ slug: "insongandstory", heading: "In song and story" },
|
|
61
|
+
{ slug: "fromhere", heading: "From here" },
|
|
62
|
+
].map((section) => Object.freeze(section)),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The directory, inside a package's own pathname space, that holds the
|
|
67
|
+
* drawings the build makes. A generated section names a drawing as
|
|
68
|
+
* `generated/<name>`, and each surface serves it from where it drew it: the
|
|
69
|
+
* website inlines it, the Foundry compile stages it into the module under
|
|
70
|
+
* `assets/generated/`, and the book draws it beside its Typst source. An
|
|
71
|
+
* authored image is never addressed here.
|
|
72
|
+
*
|
|
73
|
+
* @type {string}
|
|
74
|
+
*/
|
|
75
|
+
export const GENERATED_DRAWING_DIR = "generated";
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The pathname a generated section names the map from a place by.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} shortcode - The place.
|
|
81
|
+
* @returns {string} `generated/from-<shortcode>.svg`.
|
|
82
|
+
*/
|
|
83
|
+
export function fromHereSource(shortcode) {
|
|
84
|
+
return `${GENERATED_DRAWING_DIR}/${fromHereFile(shortcode)}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The file the map from a place is drawn into, wherever a surface draws it.
|
|
89
|
+
*
|
|
90
|
+
* @param {string} shortcode - The place.
|
|
91
|
+
* @returns {string} `from-<shortcode>.svg`.
|
|
92
|
+
*/
|
|
93
|
+
export function fromHereFile(shortcode) {
|
|
94
|
+
return `from-${String(shortcode).toLowerCase()}.svg`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The drawing an image pathname names, when it names one the build makes.
|
|
99
|
+
*
|
|
100
|
+
* @param {string} src - An image's address, as the Markdown carries it.
|
|
101
|
+
* @returns {string|null} The drawn file's name, or `null` for any other image.
|
|
102
|
+
*/
|
|
103
|
+
export function generatedDrawing(src) {
|
|
104
|
+
const match = /^generated\/(from-[a-z0-9_-]+\.svg)$/.exec(String(src ?? "").trim());
|
|
105
|
+
return match ? match[1] : null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Whether an authored image addresses the directory the build's drawings use.
|
|
110
|
+
*
|
|
111
|
+
* @param {string} src - The address, exactly as authored.
|
|
112
|
+
* @returns {string} The problem, as a finding's sentence, or `""`.
|
|
113
|
+
*/
|
|
114
|
+
export function generatedDirectoryProblem(src) {
|
|
115
|
+
const s = String(src ?? "").trim();
|
|
116
|
+
if (s === GENERATED_DRAWING_DIR || s.startsWith(`${GENERATED_DRAWING_DIR}/`)) {
|
|
117
|
+
return (
|
|
118
|
+
`\`${s}\` is under \`${GENERATED_DRAWING_DIR}/\`, which holds the drawings ` +
|
|
119
|
+
"the build makes for its generated sections — address an authored image " +
|
|
120
|
+
"anywhere else in the package"
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return "";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Whether a note's own body declares a prose anchor with this slug.
|
|
128
|
+
*
|
|
129
|
+
* @param {object} record - The note's index record, whose `anchors` were read
|
|
130
|
+
* from the authored body.
|
|
131
|
+
* @param {string} slug - The section's anchor.
|
|
132
|
+
* @returns {boolean} Whether the author has written the section.
|
|
133
|
+
*/
|
|
134
|
+
export function authoredSection(record, slug) {
|
|
135
|
+
return (record?.anchors ?? []).some(
|
|
136
|
+
(anchor) => anchor?.kind === "prose" && anchor.slug === slug,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The heading line of one generated section, with its anchor.
|
|
142
|
+
*
|
|
143
|
+
* @param {string} slug - One of {@link GENERATED_SECTIONS}.
|
|
144
|
+
* @returns {string} `# <Heading> {#<slug>}`.
|
|
145
|
+
*/
|
|
146
|
+
export function sectionHeading(slug) {
|
|
147
|
+
const section = GENERATED_SECTIONS.find((one) => one.slug === slug);
|
|
148
|
+
if (!section) throw new Error(`no generated section is named "${slug}"`);
|
|
149
|
+
return `# ${section.heading} {#${section.slug}}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* One note's sections, joined in {@link GENERATED_SECTIONS} order.
|
|
154
|
+
*
|
|
155
|
+
* @param {Iterable<Map<string, string>|undefined>} parts - Each a map of slug
|
|
156
|
+
* to that section's Markdown, heading included.
|
|
157
|
+
* @returns {string} The sections, or `""` when there are none.
|
|
158
|
+
*/
|
|
159
|
+
export function joinSections(parts) {
|
|
160
|
+
const bySlug = new Map();
|
|
161
|
+
for (const part of parts) for (const [slug, text] of part ?? []) bySlug.set(slug, text);
|
|
162
|
+
return GENERATED_SECTIONS.filter((section) => bySlug.has(section.slug))
|
|
163
|
+
.map((section) => bySlug.get(section.slug).replace(/\s+$/, "") + "\n")
|
|
164
|
+
.join("\n");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A note's body with its generated sections after it — the one way the two
|
|
169
|
+
* are joined, so the pass that answers their fences and every pass that
|
|
170
|
+
* expands them read the same text.
|
|
171
|
+
*
|
|
172
|
+
* @param {string} body - The authored body.
|
|
173
|
+
* @param {string|undefined} sections - Its sections, from {@link joinSections}.
|
|
174
|
+
* @returns {string} The body the surfaces receive.
|
|
175
|
+
*/
|
|
176
|
+
export function appendGeneratedSections(body, sections) {
|
|
177
|
+
if (!sections) return body;
|
|
178
|
+
return `${String(body ?? "").replace(/\s+$/, "")}\n\n${sections}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The line a surface inserts where a note's generated sections begin, when it
|
|
183
|
+
* treats them differently from the author's text. Private-use characters, so
|
|
184
|
+
* no pass between the expansion and the split reads it as markup, and no
|
|
185
|
+
* output carries it: {@link splitGenerated} removes it.
|
|
186
|
+
*
|
|
187
|
+
* @type {string}
|
|
188
|
+
*/
|
|
189
|
+
export const GENERATED_BOUNDARY = "generated-sections";
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Mark where a note's generated sections begin.
|
|
193
|
+
*
|
|
194
|
+
* @param {string} markdown - The expanded body.
|
|
195
|
+
* @param {number|undefined} from - The 0-based line the sections start on,
|
|
196
|
+
* from {@link module:engine/content-tables.expandContentTables}.
|
|
197
|
+
* @returns {string} The body, with {@link GENERATED_BOUNDARY} on its own line
|
|
198
|
+
* before the sections; unchanged where there are none.
|
|
199
|
+
*/
|
|
200
|
+
export function markGenerated(markdown, from) {
|
|
201
|
+
if (from === undefined || from < 0) return markdown;
|
|
202
|
+
const lines = String(markdown).split("\n");
|
|
203
|
+
if (from > lines.length) return markdown;
|
|
204
|
+
lines.splice(from, 0, GENERATED_BOUNDARY);
|
|
205
|
+
return lines.join("\n");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Split a body at its {@link GENERATED_BOUNDARY}.
|
|
210
|
+
*
|
|
211
|
+
* @param {string} markdown - A body {@link markGenerated} marked.
|
|
212
|
+
* @returns {{authored: string, generated: string}} The author's text, and the
|
|
213
|
+
* generated sections after it — `""` when there are none.
|
|
214
|
+
*/
|
|
215
|
+
export function splitGenerated(markdown) {
|
|
216
|
+
const lines = String(markdown ?? "").split("\n");
|
|
217
|
+
const at = lines.indexOf(GENERATED_BOUNDARY);
|
|
218
|
+
if (at < 0) return { authored: String(markdown ?? ""), generated: "" };
|
|
219
|
+
return {
|
|
220
|
+
authored: lines.slice(0, at).join("\n"),
|
|
221
|
+
generated: lines.slice(at + 1).join("\n"),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
@@ -305,9 +305,11 @@ export function withheldSections(source, bodyLine = 1) {
|
|
|
305
305
|
sections.push({ start: index, end: starts[at + 1] ?? lines.length, heading });
|
|
306
306
|
}
|
|
307
307
|
// A heading that opens no page is not among the openings at all, so the
|
|
308
|
-
// class is looked for again across every heading the body states.
|
|
308
|
+
// class is looked for again across every heading the body states. The
|
|
309
|
+
// openings are keyed by body line index and a heading carries its file
|
|
310
|
+
// line, so the body's first line is subtracted to compare the two.
|
|
309
311
|
for (const heading of scanHeadingAttributes(source, bodyLine).headings) {
|
|
310
|
-
if (openings.has(heading.line -
|
|
312
|
+
if (openings.has(heading.line - bodyLine)) continue;
|
|
311
313
|
if (!heading.classes.includes(WITHHELD_CLASS)) continue;
|
|
312
314
|
errors.push({
|
|
313
315
|
line: heading.line,
|
package/engine/helpers.mjs
CHANGED
|
@@ -920,7 +920,18 @@ export function buildContentLinkIndex(
|
|
|
920
920
|
// discarded here — the walk yields the body and nothing read it.
|
|
921
921
|
// Read from the record rather than from a second reading of the
|
|
922
922
|
// note's headings — the one-anchor-reader rule.
|
|
923
|
-
anchors: new Set(
|
|
923
|
+
anchors: new Set(
|
|
924
|
+
(record.anchors ?? [])
|
|
925
|
+
.filter((anchor) => anchor.kind === "prose")
|
|
926
|
+
.map((anchor) => anchor.slug),
|
|
927
|
+
),
|
|
928
|
+
// The note's event ids, which a wikilink may not name: a link to
|
|
929
|
+
// one is refused with its own message rather than as unknown.
|
|
930
|
+
eventAnchors: new Set(
|
|
931
|
+
(record.anchors ?? [])
|
|
932
|
+
.filter((anchor) => anchor.kind === "event")
|
|
933
|
+
.map((anchor) => anchor.slug),
|
|
934
|
+
),
|
|
924
935
|
anchorUuids: record.foundry?.note?.anchors,
|
|
925
936
|
docAnchorUuids: documentation?.foundry?.note?.anchors,
|
|
926
937
|
});
|
package/engine/holdings.mjs
CHANGED
|
@@ -14,32 +14,17 @@
|
|
|
14
14
|
/**
|
|
15
15
|
* Derive containment and government lists from place facts. Geography comes
|
|
16
16
|
* only from `data.parents`; government comes only from `data.government`.
|
|
17
|
-
*
|
|
18
|
-
* through geographic or affiliation ancestors. Government identity preserves the Address package, type and shortcode.
|
|
17
|
+
* Neither graph expands through geographic or affiliation ancestors. Government identity preserves the Address package, type and shortcode.
|
|
19
18
|
* Its system normalizes to `note` because Item and journal documents share one page.
|
|
20
|
-
*
|
|
19
|
+
* The lists become a note's generated **Within**, **Governed by** and
|
|
20
|
+
* **Governed places** sections — see {@link module:engine/derived-sections}.
|
|
21
21
|
* @module
|
|
22
22
|
*/
|
|
23
23
|
import { isAddressTuple, ownDocumentSystem, parseAddress, renderAddress } from "./address.mjs";
|
|
24
24
|
import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
25
25
|
import { readCanonicalKey } from "./content-address.mjs";
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
* @type {readonly string[]}
|
|
29
|
-
*/
|
|
30
|
-
export const HOLDINGS_KEYS = Object.freeze([
|
|
31
|
-
"contains",
|
|
32
|
-
"governed_by",
|
|
33
|
-
"governed_places",
|
|
34
|
-
"held_by",
|
|
35
|
-
"holdings",
|
|
36
|
-
]);
|
|
37
|
-
/** @deprecated Government advisories apply to every inhabited place subtype.
|
|
38
|
-
* @type {readonly string[]}
|
|
39
|
-
*/
|
|
40
|
-
export const HELD_SUBTYPES = Object.freeze(["settlement", "site", "structure"]);
|
|
41
|
-
|
|
42
|
-
/** @typedef {{title:string,url?:string,type:string,subType?:string}} HoldingsEntry */
|
|
27
|
+
/** @typedef {{title:string,url?:string,address:string,type:string,subType?:string}} HoldingsEntry */
|
|
43
28
|
/**
|
|
44
29
|
* @typedef {object} HoldingsNode
|
|
45
30
|
* @property {string} shortcode
|
|
@@ -56,7 +41,7 @@ export const HELD_SUBTYPES = Object.freeze(["settlement", "site", "structure"]);
|
|
|
56
41
|
*/
|
|
57
42
|
/** @typedef {{contains?:HoldingsEntry[],governed_by?:HoldingsEntry[],governed_places?:HoldingsEntry[]}} Holdings */
|
|
58
43
|
|
|
59
|
-
/**
|
|
44
|
+
/** The shortcodes a `parents` value names, bare or qualified.
|
|
60
45
|
* @param {unknown} value
|
|
61
46
|
* @returns {string[]}
|
|
62
47
|
*/
|
|
@@ -124,6 +109,7 @@ function entryOf(node) {
|
|
|
124
109
|
return {
|
|
125
110
|
title: node.title,
|
|
126
111
|
...(listed(node) ? { url: node.url } : {}),
|
|
112
|
+
address: ownKey(node),
|
|
127
113
|
type: node.type,
|
|
128
114
|
...(node.subType === undefined ? {} : { subType: node.subType }),
|
|
129
115
|
};
|
|
@@ -138,10 +124,13 @@ function bySubTypeThenTitle(a, b) {
|
|
|
138
124
|
|
|
139
125
|
/** Read local facts without converting government omission into anarchy.
|
|
140
126
|
* @param {object} fm
|
|
141
|
-
* @param {{title:string,url?:string,package?:string,system?:string,governmentSystem?:string}} page
|
|
127
|
+
* @param {{title:string,url?:string,package?:string,system?:string,canonical?:string,governmentSystem?:string}} page
|
|
142
128
|
* @returns {HoldingsNode|null}
|
|
143
129
|
*/
|
|
144
|
-
export function holdingsNode(
|
|
130
|
+
export function holdingsNode(
|
|
131
|
+
fm,
|
|
132
|
+
{ title, url, package: pkg, system, canonical, governmentSystem },
|
|
133
|
+
) {
|
|
145
134
|
const type = String(fm?.type ?? "");
|
|
146
135
|
if (type !== "place" && type !== "affiliation") return null;
|
|
147
136
|
const shortcode = String(fm?.shortcode ?? "").toLowerCase();
|
|
@@ -155,6 +144,7 @@ export function holdingsNode(fm, { title, url, package: pkg, system, governmentS
|
|
|
155
144
|
url,
|
|
156
145
|
...(pkg ? { package: pkg } : {}),
|
|
157
146
|
...(system ? { system } : {}),
|
|
147
|
+
...(canonical ? { canonical } : {}),
|
|
158
148
|
...(governmentSystem ? { governmentSystem } : {}),
|
|
159
149
|
parents:
|
|
160
150
|
type === "place" ?
|
|
@@ -294,14 +284,3 @@ export function checkGovernment(note) {
|
|
|
294
284
|
},
|
|
295
285
|
];
|
|
296
286
|
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* The historical API name for the government advisory. Tenure is no longer checked.
|
|
300
|
-
* @deprecated Use checkGovernment.
|
|
301
|
-
* @param {object} note - The place note.
|
|
302
|
-
* @param {object} [_opts] - Historical options, no longer needed.
|
|
303
|
-
* @returns {object[]} Government advisory findings.
|
|
304
|
-
*/
|
|
305
|
-
export function checkHeld(note, _opts = {}) {
|
|
306
|
-
return checkGovernment(note);
|
|
307
|
-
}
|
package/engine/ids.mjs
CHANGED
|
@@ -40,25 +40,31 @@ export function makeId(namespace, value) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* Every content type that compiles into a Foundry `Scene` — a **map note
|
|
44
|
-
*
|
|
45
|
-
* compiler's business; everything else treats them alike.
|
|
43
|
+
* Every content type that compiles into a Foundry `Scene` — a **map note**,
|
|
44
|
+
* which carries its Scene as exported from Foundry.
|
|
46
45
|
*
|
|
47
|
-
* Declared in this leaf module because several passes
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
46
|
+
* Declared in this leaf module because several passes need it: the pack router
|
|
47
|
+
* below, the scenes pass, and the doc-carrying type set in `item-docs.mjs` (a
|
|
48
|
+
* map note's prose becomes a JournalEntry, exactly as an item's or a macro's
|
|
49
|
+
* does).
|
|
51
50
|
*
|
|
52
51
|
* @type {ReadonlySet<string>}
|
|
53
52
|
*/
|
|
54
53
|
export const MAP_TYPES = Object.freeze(new Set(["map"]));
|
|
55
54
|
|
|
56
55
|
/**
|
|
57
|
-
*
|
|
56
|
+
* Whether a content note's type compiles into a Scene.
|
|
58
57
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
|
|
58
|
+
* @param {string} [type] - The note's `type` frontmatter.
|
|
59
|
+
* @returns {boolean} True for a map type.
|
|
60
|
+
*/
|
|
61
|
+
export function isMapType(type) {
|
|
62
|
+
return MAP_TYPES.has(String(type));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The map subTypes. A subType says what kind of map a note is; every one
|
|
67
|
+
* carries its Scene as exported from Foundry.
|
|
62
68
|
*
|
|
63
69
|
* @type {readonly string[]}
|
|
64
70
|
*/
|
package/engine/index.mjs
CHANGED
|
@@ -280,8 +280,8 @@ export * as journals from "./journals.mjs";
|
|
|
280
280
|
/** The Macro compiler. */
|
|
281
281
|
export * as macros from "./macros.mjs";
|
|
282
282
|
|
|
283
|
-
/**
|
|
284
|
-
export * as
|
|
283
|
+
/** An exported Scene's compile: its fixups, keys and anchored pins. */
|
|
284
|
+
export * as exportedScene from "./exported-scene.mjs";
|
|
285
285
|
|
|
286
286
|
/** The Scene and Adventure compiler. */
|
|
287
287
|
export * as scenes from "./scenes.mjs";
|
|
@@ -303,8 +303,8 @@ export * as compendiums from "./compendiums.mjs";
|
|
|
303
303
|
|
|
304
304
|
// The region-event vocabulary stays flat as well as namespaced: the Foundry
|
|
305
305
|
// runtime imports these three by name through
|
|
306
|
-
// `@heroiclands/package-build/engine/region-events`, and
|
|
307
|
-
//
|
|
306
|
+
// `@heroiclands/package-build/engine/region-events`, and this barrel carries
|
|
307
|
+
// them by name too.
|
|
308
308
|
export {
|
|
309
309
|
CURATED_REGION_EVENTS,
|
|
310
310
|
EXCLUDED_REGION_EVENTS,
|
|
@@ -116,12 +116,17 @@ function valueToHtml(row, link) {
|
|
|
116
116
|
/**
|
|
117
117
|
* One entry of a `runin` group or a `list` section as HTML.
|
|
118
118
|
*
|
|
119
|
+
* An entry's optional `lead` is plain text set before it, so a line such as a
|
|
120
|
+
* membership — `War Chief, Hárár (5), of Vrystwald Tribes` — links the body's
|
|
121
|
+
* name and nothing else.
|
|
122
|
+
*
|
|
119
123
|
* @param {object} entry - The entry.
|
|
120
124
|
* @param {(value: object) => string} link - How this medium draws a link.
|
|
121
125
|
* @returns {string} HTML.
|
|
122
126
|
*/
|
|
123
127
|
function entryToHtml(entry, link) {
|
|
124
|
-
|
|
128
|
+
const lead = entry?.lead ? escapeHtml(entry.lead) : "";
|
|
129
|
+
return lead + (entry?.url || entry?.address ? link(entry) : escapeHtml(entry?.text));
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
/**
|
|
@@ -265,6 +270,19 @@ function valueToTypst(row, link) {
|
|
|
265
270
|
}
|
|
266
271
|
}
|
|
267
272
|
|
|
273
|
+
/**
|
|
274
|
+
* One entry of a `runin` group or a `list` section as Typst: its `lead` as
|
|
275
|
+
* plain text, then the entry, linked where the book can reach it.
|
|
276
|
+
*
|
|
277
|
+
* @param {object} entry - The entry.
|
|
278
|
+
* @param {(value: object) => string} link - How the book draws a link.
|
|
279
|
+
* @returns {string} Typst markup.
|
|
280
|
+
*/
|
|
281
|
+
function entryToTypst(entry, link) {
|
|
282
|
+
const lead = entry?.lead ? escapeTypst(entry.lead) : "";
|
|
283
|
+
return lead + (entry?.url || entry?.address ? link(entry) : escapeTypst(entry?.text));
|
|
284
|
+
}
|
|
285
|
+
|
|
268
286
|
/**
|
|
269
287
|
* One section as Typst, whole and unbreakable.
|
|
270
288
|
*
|
|
@@ -306,16 +324,13 @@ function sectionToTypst(section, link, lead = "") {
|
|
|
306
324
|
} else if (section.layout === "runin") {
|
|
307
325
|
for (const group of section.groups ?? []) {
|
|
308
326
|
const entries = (group.entries ?? [])
|
|
309
|
-
.map((entry) =>
|
|
310
|
-
entry?.url || entry?.address ? link(entry) : escapeTypst(entry?.text),
|
|
311
|
-
)
|
|
327
|
+
.map((entry) => entryToTypst(entry, link))
|
|
312
328
|
.join(", ");
|
|
313
329
|
body.push(`#infobox-runin("${escapeTypstString(group.label ?? "")}")[${entries}]`);
|
|
314
330
|
}
|
|
315
331
|
} else {
|
|
316
332
|
for (const entry of section.entries ?? []) {
|
|
317
|
-
|
|
318
|
-
body.push(`#infobox-runin("")[${text}]`);
|
|
333
|
+
body.push(`#infobox-runin("")[${entryToTypst(entry, link)}]`);
|
|
319
334
|
}
|
|
320
335
|
}
|
|
321
336
|
|