@heroiclands/package-build 23.1.0 → 23.2.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 +71 -0
- package/bin/content-commands.mjs +68 -149
- package/bin/package-build.mjs +56 -10
- package/ci/check-banners.mjs +42 -6
- package/ci/confirm-npm-version.mjs +135 -0
- package/ci/content-format-reference.mjs +14 -5
- package/config.mjs +1 -0
- package/container.mjs +185 -4
- package/content-config.mjs +333 -210
- package/deploy.mjs +116 -10
- package/docs/api.md +238 -246
- package/docs/authoring/assets.md +11 -5
- package/docs/authoring/frontmatter.md +1 -1
- package/docs/authoring/links-and-markup.md +87 -0
- package/docs/commands.md +138 -99
- package/docs/configuration.md +277 -125
- package/docs/content-format.md +2 -0
- package/docs/diagnostics.md +48 -13
- package/docs/guides/book.md +341 -21
- package/docs/guides/e2e.md +13 -2
- package/docs/guides/icons.md +14 -4
- package/docs/guides/module-project.md +18 -8
- package/docs/guides/release-and-deploy.md +54 -6
- package/docs/guides/site.md +5 -2
- package/docs/project-setup.md +30 -10
- package/docs/reference/format-details.md +373 -170
- package/docs/reference/note-types.md +128 -109
- package/docs/reference/theme-pages.md +31 -6
- package/e2e.mjs +140 -28
- package/engine/actor-compiler.mjs +22 -31
- package/engine/address-charset.mjs +4 -17
- package/engine/address-fields.mjs +1 -1
- package/engine/address-render.mjs +12 -1
- package/engine/address-values.mjs +6 -0
- package/engine/address.mjs +46 -33
- package/engine/art-fields.mjs +30 -5
- package/engine/art-slots.mjs +10 -1
- package/engine/asset-bindings.mjs +12 -1
- package/engine/base-compiler.mjs +34 -65
- package/engine/being-age.mjs +2 -7
- package/engine/being-measurements.mjs +12 -1
- package/engine/calendar-artifacts.mjs +6 -0
- package/engine/calendar-choice.mjs +12 -1
- package/engine/calendar-format.mjs +12 -1
- package/engine/calendar-human.mjs +12 -1
- package/engine/compile-corpus.mjs +3 -1
- package/engine/content-address.mjs +9 -12
- package/engine/content-alerts.mjs +13 -1
- package/engine/content-blocks.mjs +14 -3
- package/engine/content-embeds.mjs +11 -3
- package/engine/content-figures.mjs +9 -0
- package/engine/content-footnotes.mjs +12 -1
- package/engine/content-format-check.mjs +11 -262
- package/engine/content-format.yaml +5 -2
- package/engine/content-images.mjs +3 -0
- package/engine/content-index.mjs +443 -20
- package/engine/content-links.mjs +29 -35
- package/engine/content-lint.mjs +2 -3
- package/engine/content-poetry.mjs +12 -1
- package/engine/content-slug.mjs +2 -14
- package/engine/content-spans.mjs +13 -1
- package/engine/content-tables.mjs +12 -1
- package/engine/culture-choice.mjs +210 -13
- package/engine/data-keys.mjs +19 -16
- package/engine/date-conversion.mjs +12 -1
- package/engine/declaration-check.mjs +9 -0
- package/engine/derived-fields.mjs +6 -7
- package/engine/diagnostics.mjs +34 -4
- package/engine/docs-checks.mjs +9 -0
- package/engine/document-subtypes.mjs +47 -34
- package/engine/event-views.mjs +139 -14
- package/engine/exported-scene.mjs +92 -28
- package/engine/extension-attributes.mjs +12 -1
- package/engine/field-spec.mjs +11 -102
- package/engine/folder-notes.mjs +0 -64
- package/engine/foreign-catalog.mjs +2 -1
- package/engine/format-generated.mjs +9 -0
- package/engine/foundry-entries.mjs +10 -28
- package/engine/frontmatter-lint.mjs +83 -392
- package/engine/frontmatter.mjs +20 -70
- package/engine/generate.mjs +10 -15
- package/engine/heading-attributes.mjs +12 -1
- package/engine/helpers.mjs +41 -102
- package/engine/holdings.mjs +1 -1
- package/engine/homepage.mjs +3 -10
- package/engine/ids.mjs +1 -167
- package/engine/index-records.mjs +6 -4
- package/engine/index.mjs +0 -3
- package/engine/infobox-render.mjs +15 -3
- package/engine/infobox.mjs +13 -14
- package/engine/item-compiler.mjs +4 -10
- package/engine/item-docs.mjs +3 -5
- package/engine/item-registry.mjs +2 -10
- package/engine/journals.mjs +1 -5
- package/engine/literature-notes.mjs +4 -12
- package/engine/macros.mjs +1 -2
- package/engine/map-graphviz.mjs +12 -1
- package/engine/markdown-expressions.mjs +12 -1
- package/engine/markdownlint-run.mjs +567 -0
- package/engine/metadata-index.mjs +42 -12
- package/engine/note-addresses.mjs +143 -35
- package/engine/note-claims.mjs +14 -28
- package/engine/note-dates.mjs +3 -3
- package/engine/note-frontmatter.mjs +6 -0
- package/engine/note-ids.mjs +8 -14
- package/engine/note-package.mjs +8 -25
- package/engine/note-state.mjs +1 -3
- package/engine/note-vocabulary.mjs +148 -84
- package/engine/number-words.mjs +12 -1
- package/engine/pack-config.mjs +15 -25
- package/engine/pack-router.mjs +10 -16
- package/engine/pathnames.mjs +8 -22
- package/engine/pdf-build.mjs +773 -121
- package/engine/pdf-coverage.mjs +188 -0
- package/engine/pdf-index.mjs +54 -7
- package/engine/pdf-pages.mjs +247 -0
- package/engine/pdf-render.mjs +114 -4
- package/engine/pdf-toc.mjs +29 -32
- package/engine/project-init.mjs +46 -6
- package/engine/prose-config.mjs +1 -1
- package/engine/prose-lint.mjs +52 -88
- package/engine/readability-lint.mjs +9 -0
- package/engine/reckoning-markers.mjs +12 -1
- package/engine/reference-targets.mjs +7 -0
- package/engine/runtime-only-fields.mjs +5 -8
- package/engine/scene-levels.mjs +2 -2
- package/engine/scenes.mjs +1 -1
- package/engine/shortcode-references.mjs +7 -0
- package/engine/site-build.mjs +30 -16
- package/engine/site-index.mjs +10 -16
- package/engine/site-preview.mjs +6 -0
- package/engine/site-root.mjs +8 -1
- package/engine/social-tie-terms.mjs +12 -1
- package/engine/social-ties.mjs +3 -0
- package/engine/sql-tables.mjs +56 -20
- package/engine/standing-terms.mjs +11 -1
- package/engine/standings.mjs +11 -27
- package/engine/system-block.mjs +39 -141
- package/engine/system-vocabulary.mjs +6 -0
- package/engine/walk-files.mjs +69 -0
- package/engine/web-wikilinks.mjs +23 -3
- package/engine/wikilink-syntax.mjs +32 -21
- package/engine/wikilinks.mjs +27 -16
- package/engine/yaml-lint.mjs +7 -4
- package/githooks/attribution.mjs +8 -0
- package/githooks/commit-message.mjs +9 -0
- package/hm3/actors.mjs +9 -48
- package/hm3/default-item-art.mjs +9 -9
- package/hm3/item-fields.mjs +2 -9
- package/hm3/template-priority.mjs +6 -7
- package/hugo-theme/data/banner-kinds.yaml +78 -0
- package/hugo-theme/data/banners.yaml +2 -3
- package/hugo-theme/layouts/404.html +3 -3
- package/hugo-theme/layouts/partials/banner-url.html +31 -7
- package/hugo-theme/layouts/partials/hero-banner.html +13 -14
- package/hugo-theme/layouts/partials/related.html +1 -1
- package/hugo-theme/layouts/partials/warn-missing-banner.html +1 -1
- package/hugo-theme/static/css/style.css +1 -1
- package/manifest.mjs +11 -4
- package/markdownlint-config.mjs +1 -1
- package/package.json +6 -3
- package/release.mjs +83 -18
- package/sohl/actors.mjs +16 -27
- package/sohl/default-item-art.mjs +3 -3
- package/sohl/document-subtypes.mjs +1 -16
- package/sohl/infobox.mjs +2 -3
- package/sohl/item-builders.mjs +5 -8
- package/sohl/item-fields.mjs +5 -35
- package/sohl/items.mjs +0 -4
- package/sohl/kb-passes.mjs +3 -3
- package/types/container.d.mts +116 -5
- package/types/content-config.d.mts +83 -35
- package/types/deploy.d.mts +42 -6
- package/types/e2e.d.mts +35 -3
- package/types/engine/address-charset.d.mts +4 -17
- package/types/engine/address.d.mts +17 -8
- package/types/engine/art-fields.d.mts +7 -4
- package/types/engine/art-slots.d.mts +6 -0
- package/types/engine/base-compiler.d.mts +15 -17
- package/types/engine/content-address.d.mts +9 -12
- package/types/engine/content-embeds.d.mts +4 -2
- package/types/engine/content-format-check.d.mts +0 -64
- package/types/engine/content-index.d.mts +82 -3
- package/types/engine/content-links.d.mts +2 -4
- package/types/engine/culture-choice.d.mts +51 -8
- package/types/engine/data-keys.d.mts +2 -2
- package/types/engine/derived-fields.d.mts +1 -2
- package/types/engine/diagnostics.d.mts +27 -4
- package/types/engine/document-subtypes.d.mts +12 -31
- package/types/engine/event-views.d.mts +14 -2
- package/types/engine/exported-scene.d.mts +3 -2
- package/types/engine/field-spec.d.mts +13 -122
- package/types/engine/folder-notes.d.mts +0 -39
- package/types/engine/foreign-catalog.d.mts +2 -1
- package/types/engine/foundry-entries.d.mts +4 -5
- package/types/engine/frontmatter-lint.d.mts +2 -2
- package/types/engine/frontmatter.d.mts +19 -45
- package/types/engine/generate.d.mts +1 -1
- package/types/engine/helpers.d.mts +25 -42
- package/types/engine/homepage.d.mts +3 -10
- package/types/engine/ids.d.mts +0 -114
- package/types/engine/index-records.d.mts +6 -4
- package/types/engine/index.d.mts +0 -1
- package/types/engine/infobox-render.d.mts +7 -1
- package/types/engine/infobox.d.mts +3 -2
- package/types/engine/item-docs.d.mts +1 -1
- package/types/engine/item-registry.d.mts +2 -4
- package/types/engine/literature-notes.d.mts +1 -1
- package/types/engine/markdownlint-run.d.mts +40 -0
- package/types/engine/metadata-index.d.mts +9 -1
- package/types/engine/note-addresses.d.mts +27 -2
- package/types/engine/note-claims.d.mts +3 -5
- package/types/engine/note-ids.d.mts +2 -4
- package/types/engine/note-package.d.mts +1 -1
- package/types/engine/note-state.d.mts +1 -3
- package/types/engine/note-vocabulary.d.mts +28 -21
- package/types/engine/pack-config.d.mts +2 -6
- package/types/engine/pack-router.d.mts +4 -4
- package/types/engine/pathnames.d.mts +3 -3
- package/types/engine/pdf-build.d.mts +98 -12
- package/types/engine/pdf-coverage.d.mts +60 -0
- package/types/engine/pdf-index.d.mts +34 -5
- package/types/engine/pdf-pages.d.mts +143 -0
- package/types/engine/pdf-render.d.mts +45 -1
- package/types/engine/pdf-toc.d.mts +12 -14
- package/types/engine/prose-config.d.mts +1 -1
- package/types/engine/prose-lint.d.mts +11 -10
- package/types/engine/runtime-only-fields.d.mts +2 -2
- package/types/engine/site-build.d.mts +9 -5
- package/types/engine/site-index.d.mts +4 -5
- package/types/engine/site-root.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +31 -2
- package/types/engine/standing-terms.d.mts +2 -1
- package/types/engine/standings.d.mts +7 -9
- package/types/engine/system-block.d.mts +10 -44
- package/types/engine/walk-files.d.mts +15 -0
- package/types/engine/wikilink-syntax.d.mts +18 -19
- package/types/engine/yaml-lint.d.mts +5 -4
- package/types/hm3/actors.d.mts +1 -2
- package/types/hm3/item-fields.d.mts +2 -4
- package/types/hm3/template-priority.d.mts +6 -7
- package/types/manifest.d.mts +3 -2
- package/types/release.d.mts +12 -7
- package/types/sohl/actors.d.mts +2 -1
- package/types/sohl/document-subtypes.d.mts +1 -2
- package/types/sohl/item-fields.d.mts +3 -7
- package/engine/retired-fields.mjs +0 -631
- package/types/engine/retired-fields.d.mts +0 -354
package/engine/ids.mjs
CHANGED
|
@@ -134,163 +134,6 @@ export const PACK_BY_TYPE = Object.freeze({
|
|
|
134
134
|
),
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
-
/**
|
|
138
|
-
* Content types that no longer exist, and what replaced each one.
|
|
139
|
-
*
|
|
140
|
-
* `character` and `creature` were retired in favour of the single `being` they
|
|
141
|
-
* had always compiled into. They are recorded here rather than
|
|
142
|
-
* simply deleted because deleting them is the one change that fails *quietly*:
|
|
143
|
-
* every type not named in {@link PACK_BY_TYPE} falls through to the open item
|
|
144
|
-
* set below, so a note or a link left on the old spelling would be routed to
|
|
145
|
-
* the items pack — a wrong answer, arrived at silently, which is exactly the
|
|
146
|
-
* failure mode the open-set default exists to avoid for real item types.
|
|
147
|
-
*
|
|
148
|
-
* Keeping the names lets {@link assertTypeNotRetired} say what happened and
|
|
149
|
-
* what to write instead. Entries stay for as long as content in the wild might
|
|
150
|
-
* still carry them.
|
|
151
|
-
*
|
|
152
|
-
* @type {Readonly<Record<string, string>>}
|
|
153
|
-
*/
|
|
154
|
-
export const RETIRED_TYPES = Object.freeze({
|
|
155
|
-
character: "being",
|
|
156
|
-
creature: "being",
|
|
157
|
-
// The three map spellings, retired in favour of the single `map` whose
|
|
158
|
-
// subType they became. Recorded rather than deleted for the same
|
|
159
|
-
// reason as the two above: an unnamed type falls through to the open item
|
|
160
|
-
// set, so a note or link left on one would be routed to the items pack.
|
|
161
|
-
battlemap: "map",
|
|
162
|
-
localmap: "map",
|
|
163
|
-
regionalmap: "map",
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Throw if `type` names a retired content type.
|
|
168
|
-
*
|
|
169
|
-
* @param {string} type - The note's declared `type`, or a link's qualifier.
|
|
170
|
-
* @param {string} [where] - What carries it — a file path, a link target —
|
|
171
|
-
* appended to the message so the reader can go straight to it.
|
|
172
|
-
* @throws {Error} Naming the replacement type.
|
|
173
|
-
*/
|
|
174
|
-
export function assertTypeNotRetired(type, where) {
|
|
175
|
-
const replacement = RETIRED_TYPES[type];
|
|
176
|
-
if (!replacement) return;
|
|
177
|
-
throw new Error(
|
|
178
|
-
`Content type "${type}" was retired in favour of "${replacement}"` +
|
|
179
|
-
(where ? ` — ${where}` : "") +
|
|
180
|
-
`. Both compiled to the same document, so the fix is mechanical: ` +
|
|
181
|
-
`write "${replacement}".`,
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/* -------------------------------------------------------------------- */
|
|
186
|
-
/* Renamed in favour of another type — the retirement window */
|
|
187
|
-
/* -------------------------------------------------------------------- */
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Content types that were **renamed**, and what each is called now.
|
|
191
|
-
*
|
|
192
|
-
* A retired type and a renamed one are different cases, and only the first can
|
|
193
|
-
* be refused. `character` above was retired *outright* — nothing a note wrote
|
|
194
|
-
* made it right, so {@link assertTypeNotRetired} throws. A renamed type has a
|
|
195
|
-
* replacement, the two spellings mean the same thing, and a note carrying the
|
|
196
|
-
* old one compiles into exactly the document it always did: refusing it would
|
|
197
|
-
* fail a build over a note that is not wrong.
|
|
198
|
-
*
|
|
199
|
-
* So these retire in the three steps `package:` took, and this table is
|
|
200
|
-
* the **first**: both spellings resolve, the current one is canonical, and the
|
|
201
|
-
* retired one is *reported* — never refused. The sweep of the content trees and
|
|
202
|
-
* the refusal come after, once no tree writes the old name. That is the same
|
|
203
|
-
* rule `RETIRED_FIELD_ALIASES` states for a renamed *field*, and for the same
|
|
204
|
-
* reason: a consumer must be able to adopt the new toolchain before its content
|
|
205
|
-
* moves, and there are some 31,000 references to move.
|
|
206
|
-
*
|
|
207
|
-
* **Why these three, and why this way round.** The specification renamed
|
|
208
|
-
* `armorgear`, `concoctiongear` and `projectilegear` to `armor`, `concoction`
|
|
209
|
-
* and `projectile`, on the argument that the suffix named the *SoHL document
|
|
210
|
-
* subtype* a note compiled into rather than the thing the note is about. That
|
|
211
|
-
* rename is **reversed**: the gear-suffixed spellings are the vocabulary, and
|
|
212
|
-
* the bare ones are what retires.
|
|
213
|
-
*
|
|
214
|
-
* Nothing had adopted the bare spellings, which is what makes the reversal
|
|
215
|
-
* cheap and the direction obvious: across all five content trees, **every note
|
|
216
|
-
* still authors the suffix** — 331 `armorgear` and 18 `projectilegear` in
|
|
217
|
-
* `sohl`, 71 `concoctiongear` in `thalorna`, and not one note anywhere writes
|
|
218
|
-
* `armor`, `concoction` or `projectile`. The rename produced 349 warnings and
|
|
219
|
-
* zero adopters.
|
|
220
|
-
*
|
|
221
|
-
* `weapongear` never moved, and the reversal restores the consistency its
|
|
222
|
-
* staying put had broken: all five gear types spell the suffix again.
|
|
223
|
-
*
|
|
224
|
-
* **Keyed by the retired spelling**, which is the opposite of
|
|
225
|
-
* `RETIRED_FIELD_ALIASES`. The direction follows the operation: a field alias
|
|
226
|
-
* is scoped by the schema that declares the *current* name, so that is what a
|
|
227
|
-
* reader asks with; a type alias is applied to a value read off a note, so what
|
|
228
|
-
* a reader has in hand is the retired spelling and what it needs is the
|
|
229
|
-
* replacement.
|
|
230
|
-
*
|
|
231
|
-
* **The address is not normalised.** A note's canonical address — and therefore
|
|
232
|
-
* its document id — carries its `type` as authored, so both spellings keep the
|
|
233
|
-
* addresses they already publish and a tree that has not swept compiles
|
|
234
|
-
* byte-identically. Renaming the type in a note *is* an address move, which is
|
|
235
|
-
* the sweep's business and not this table's.
|
|
236
|
-
*
|
|
237
|
-
* @type {Readonly<Record<string, string>>}
|
|
238
|
-
*/
|
|
239
|
-
export const RENAMED_TYPES = Object.freeze({
|
|
240
|
-
armor: "armorgear",
|
|
241
|
-
concoction: "concoctiongear",
|
|
242
|
-
projectile: "projectilegear",
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* The current spelling of a content type: itself, or what it was renamed to.
|
|
247
|
-
*
|
|
248
|
-
* Every table keyed by note type is keyed by the **current** name, and every
|
|
249
|
-
* lookup goes through this — which is the whole of the window's behaviour, in
|
|
250
|
-
* one function, so no two readers can disagree about which vocabulary a note
|
|
251
|
-
* is held to.
|
|
252
|
-
*
|
|
253
|
-
* A non-string passes through untouched: callers hand this whatever the
|
|
254
|
-
* frontmatter carried, and inventing a type for a number would hide the
|
|
255
|
-
* missing-`type:` finding that belongs to the linter.
|
|
256
|
-
*
|
|
257
|
-
* @param {any} type - The type as authored, or as a link spells it.
|
|
258
|
-
* @returns {any} The current spelling.
|
|
259
|
-
*/
|
|
260
|
-
export function currentType(type) {
|
|
261
|
-
if (typeof type !== "string") return type;
|
|
262
|
-
return RENAMED_TYPES[type] ?? type;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* What a note declaring a renamed type is told, in one place.
|
|
267
|
-
*
|
|
268
|
-
* Shared by every reporter, because an author meets whichever runs first and
|
|
269
|
-
* they should read the same. It names the type to write rather than a value to
|
|
270
|
-
* correct — no value makes the retired spelling right — and it says the note
|
|
271
|
-
* compiles either way, so a reader knows this is a rename to schedule rather
|
|
272
|
-
* than a build to unbreak.
|
|
273
|
-
*
|
|
274
|
-
* @param {string} retired - The spelling the note used.
|
|
275
|
-
* @param {string} current - What to write instead.
|
|
276
|
-
* @param {string} [where] - What carries it — a file path, a link target —
|
|
277
|
-
* appended to the message. Omit it where the caller emits through a
|
|
278
|
-
* diagnostic, whose locator already starts the line.
|
|
279
|
-
* @returns {string} The message, unpunctuated at the end as a finding is.
|
|
280
|
-
*/
|
|
281
|
-
export function renamedTypeMessage(retired, current, where) {
|
|
282
|
-
return (
|
|
283
|
-
`content type "${retired}" was renamed to "${current}" — the suffix ` +
|
|
284
|
-
`named a SoHL document subtype rather than the thing the note is ` +
|
|
285
|
-
`about` +
|
|
286
|
-
(where ? ` — ${where}` : "") +
|
|
287
|
-
`. Both spellings compile to the same document, so the note is not ` +
|
|
288
|
-
`wrong; write "${current}", and move every \`(type, shortcode)\` ` +
|
|
289
|
-
`reference and wikilink to it in the same change. "${retired}" is ` +
|
|
290
|
-
`refused in a later release`
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
137
|
/** Where every other content type compiles: the items pack. */
|
|
295
138
|
export const ITEM_PACK = Object.freeze({ pack: "items", docType: "Item" });
|
|
296
139
|
|
|
@@ -313,18 +156,9 @@ export const ITEM_PACK = Object.freeze({ pack: "items", docType: "Item" });
|
|
|
313
156
|
*
|
|
314
157
|
* @param {string} type - The target note's `type`.
|
|
315
158
|
* @returns {{pack: string, docType: string}} The pack and document type.
|
|
316
|
-
* @throws {Error} If `type` names a retired content type — see
|
|
317
|
-
* {@link RETIRED_TYPES}. The open-set default would otherwise route it to the
|
|
318
|
-
* items pack and say nothing.
|
|
319
159
|
*/
|
|
320
160
|
export function packForType(type) {
|
|
321
|
-
|
|
322
|
-
// Through {@link currentType} like every other type-keyed lookup, so the
|
|
323
|
-
// rule holds without exception. None of today's renamed types is named in
|
|
324
|
-
// `PACK_BY_TYPE` — all three are items and take the default — but a router
|
|
325
|
-
// that read the authored spelling would be the one table left to remember
|
|
326
|
-
// when the next rename lands.
|
|
327
|
-
return PACK_BY_TYPE[currentType(type)] ?? ITEM_PACK;
|
|
161
|
+
return PACK_BY_TYPE[type] ?? ITEM_PACK;
|
|
328
162
|
}
|
|
329
163
|
|
|
330
164
|
/**
|
package/engine/index-records.mjs
CHANGED
|
@@ -110,15 +110,17 @@ export function noteFile(contentBase, record) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* The note's
|
|
113
|
+
* The note's frontmatter from an index record, without the keys the index
|
|
114
|
+
* derives.
|
|
114
115
|
*
|
|
115
116
|
* The inverse of the record's spread, and exact rather than best-effort: a
|
|
116
117
|
* record is the note's frontmatter plus {@link DERIVED_KEYS}, and a note that
|
|
117
118
|
* authors one of those keys fails the walk — so removing them cannot remove
|
|
118
119
|
* anything the note wrote. That enforced pairing is what lets a pass read the
|
|
119
|
-
* corpus from the index
|
|
120
|
-
*
|
|
121
|
-
*
|
|
120
|
+
* corpus from the index without reasoning about `address:` and `anchors:` as
|
|
121
|
+
* though someone had written them. The record's `data.culture` is the note's
|
|
122
|
+
* resolved culture, so a pass reading the frontmatter sees an inherited culture
|
|
123
|
+
* as well as a written one.
|
|
122
124
|
*
|
|
123
125
|
* Lives beside the list it is the inverse of, so the two cannot drift.
|
|
124
126
|
*
|
package/engine/index.mjs
CHANGED
|
@@ -69,9 +69,6 @@ export * as contentPackage from "./content-package.mjs";
|
|
|
69
69
|
/** Which content package a note belongs to, and refusing one that says so. */
|
|
70
70
|
export * as notePackage from "./note-package.mjs";
|
|
71
71
|
|
|
72
|
-
/** Frontmatter fields a note may no longer declare, and the refusal of them. */
|
|
73
|
-
export * as retiredFields from "./retired-fields.mjs";
|
|
74
|
-
|
|
75
72
|
/** Schema fields a note may never declare, because play writes them. */
|
|
76
73
|
export * as runtimeOnlyFields from "./runtime-only-fields.mjs";
|
|
77
74
|
|
|
@@ -36,7 +36,12 @@
|
|
|
36
36
|
|
|
37
37
|
import { encodeAddresses } from "./address-values.mjs";
|
|
38
38
|
import { sectionHolds } from "./infobox.mjs";
|
|
39
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
absolutePageUrl,
|
|
41
|
+
crossBookReference,
|
|
42
|
+
escapeTypst,
|
|
43
|
+
escapeTypstString,
|
|
44
|
+
} from "./pdf-render.mjs";
|
|
40
45
|
|
|
41
46
|
/**
|
|
42
47
|
* How many cells an attribute grid packs into a row.
|
|
@@ -239,12 +244,19 @@ export function sectionHasContent(section) {
|
|
|
239
244
|
* @param {Map<string, string>} links - Address slug → the book's anchor.
|
|
240
245
|
* @param {(anchor: string) => string} labelFor - The anchor's Typst label.
|
|
241
246
|
* @param {string} [site] - The absolute address this book's pages are served at.
|
|
247
|
+
* @param {{get: (key: string) => (object|undefined)}} [elsewhere] - The notes
|
|
248
|
+
* another book is home to. One of them is not a link — a PDF link cannot
|
|
249
|
+
* leave its file — and is set as
|
|
250
|
+
* {@link module:engine/pdf-render.crossBookReference}.
|
|
242
251
|
* @returns {string} Typst markup.
|
|
243
252
|
*/
|
|
244
|
-
export function linkToTypst(value, links, labelFor, site) {
|
|
253
|
+
export function linkToTypst(value, links, labelFor, site, elsewhere) {
|
|
245
254
|
const text = escapeTypst(value?.text ?? "");
|
|
246
|
-
const
|
|
255
|
+
const address = value?.address ? encodeAddresses(value.address) : undefined;
|
|
256
|
+
const anchor = address ? links?.get(address) : undefined;
|
|
247
257
|
if (anchor) return `#link(<${labelFor(anchor)}>)[${text}]`;
|
|
258
|
+
const away = address ? elsewhere?.get(address) : undefined;
|
|
259
|
+
if (away) return crossBookReference(text, away);
|
|
248
260
|
if (value?.url)
|
|
249
261
|
return `#link("${escapeTypstString(absolutePageUrl(value.url, site))}")[${text}]`;
|
|
250
262
|
return text;
|
package/engine/infobox.mjs
CHANGED
|
@@ -114,11 +114,10 @@
|
|
|
114
114
|
|
|
115
115
|
import { isAddressTuple, parseAddress, renderAddress } from "./address.mjs";
|
|
116
116
|
import { getFrontmatter } from "./frontmatter.mjs";
|
|
117
|
-
import { currentType } from "./ids.mjs";
|
|
118
117
|
import { NOTE_VOCABULARY, dataFields } from "./note-vocabulary.mjs";
|
|
119
|
-
import { readAliasedField } from "./retired-fields.mjs";
|
|
120
118
|
import { subtypeRow } from "./document-subtypes.mjs";
|
|
121
119
|
import { authoredKey } from "./system-block.mjs";
|
|
120
|
+
import { cultureNamesItself } from "./culture-choice.mjs";
|
|
122
121
|
import { formatDateInCalendar, formatNoteDate, parseNoteDate } from "./note-dates.mjs";
|
|
123
122
|
import { displayBeingHeight, displayBeingWeight } from "./being-measurements.mjs";
|
|
124
123
|
import {
|
|
@@ -317,6 +316,7 @@ export const NOTE_FIELD_PRESENTATION = Object.freeze({
|
|
|
317
316
|
// Scene itself, not a fact a summary row can carry.
|
|
318
317
|
"map.scene": Object.freeze({ withheld: "canvas machinery, not a summary row" }),
|
|
319
318
|
"map.fixup": Object.freeze({ withheld: "canvas machinery, not a summary row" }),
|
|
319
|
+
"map.thumbnail": Object.freeze({ withheld: "an image, which the box never carries" }),
|
|
320
320
|
|
|
321
321
|
assocSkill: Object.freeze({ label: "Skill" }),
|
|
322
322
|
assocAffiliation: Object.freeze({ label: "Affiliation" }),
|
|
@@ -385,7 +385,7 @@ export const NOTE_FIELD_PRESENTATION = Object.freeze({
|
|
|
385
385
|
*/
|
|
386
386
|
export function overlayFor(presentation, type, name) {
|
|
387
387
|
const key = authoredKey(name);
|
|
388
|
-
return presentation?.[`${
|
|
388
|
+
return presentation?.[`${type}.${key}`] ?? presentation?.[key] ?? {};
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
/** What a composed group's row is called. @type {Readonly<Record<string, string>>} */
|
|
@@ -622,9 +622,9 @@ export const MEMBERSHIPS_SECTION = Object.freeze({ id: "affiliations", label: "A
|
|
|
622
622
|
* Each entry is the line a reader meets — `War Chief, Hárár (5), of Vrystwald
|
|
623
623
|
* Tribes` — with the body's name as the link and the standing before it as the
|
|
624
624
|
* entry's `lead`, drawn as plain text. The rung's title is read from the body's
|
|
625
|
-
* own ladder, carried on the reference as its `standings` digest, so a
|
|
625
|
+
* own ladder, carried on the reference as its `standings` digest, so renaming a
|
|
626
626
|
* rung renames every line pointing at it. A body that publishes no page keeps
|
|
627
|
-
* its name as plain text, and a membership
|
|
627
|
+
* its name as plain text, and a membership stating no standing is the name alone.
|
|
628
628
|
*
|
|
629
629
|
* The entries are a `list` section of their own rather than one `links` row,
|
|
630
630
|
* because a line already holds commas and a `links` row joins its values with
|
|
@@ -916,7 +916,7 @@ function declaredKeyRows(fields, raw) {
|
|
|
916
916
|
*
|
|
917
917
|
* The declared string is appended **verbatim**, which is what lets a symbol
|
|
918
918
|
* hug its number (`160d`) and a word stand off it (`1.1 lbs`). The row's kind
|
|
919
|
-
* becomes `text`: a number with a unit on it is
|
|
919
|
+
* becomes `text`: a number with a unit on it is not a number, and saying
|
|
920
920
|
* otherwise would invite a medium to format it as one.
|
|
921
921
|
*
|
|
922
922
|
* @param {string} kind - The row's kind.
|
|
@@ -960,7 +960,8 @@ export function linkValue(ref, resolve, hint) {
|
|
|
960
960
|
* order, minus what {@link NOTE_FIELD_PRESENTATION} withholds and minus every
|
|
961
961
|
* field the note left empty. A being's top-level `subType` adds its profile
|
|
962
962
|
* type. A type that declares no `data:` fields still gets the box, because
|
|
963
|
-
* `Name` is a fact about every note.
|
|
963
|
+
* `Name` is a fact about every note. `data.culture` is the resolved culture,
|
|
964
|
+
* inherited ones included, and is left out where it names the note itself.
|
|
964
965
|
*
|
|
965
966
|
* @param {object} fm - The note's frontmatter.
|
|
966
967
|
* @param {object} [options] - Options.
|
|
@@ -1022,12 +1023,10 @@ function noteBox(
|
|
|
1022
1023
|
for (const field of dataFields(fm?.type, vocabulary) ?? []) {
|
|
1023
1024
|
const overlay = overlayFor(presentation, fm?.type, field.name);
|
|
1024
1025
|
if (overlay.withheld) continue;
|
|
1025
|
-
|
|
1026
|
-
//
|
|
1027
|
-
//
|
|
1028
|
-
|
|
1029
|
-
const own = getFrontmatter(data, field.name, undefined);
|
|
1030
|
-
let raw = own !== undefined ? own : readAliasedField(fm, field.name, { inData: true });
|
|
1026
|
+
let raw = getFrontmatter(data, field.name, undefined);
|
|
1027
|
+
// A culture note resolves to itself, and a row linking it to itself
|
|
1028
|
+
// says nothing.
|
|
1029
|
+
if (field.name === "culture" && cultureNamesItself(fm, raw, { contentPackage })) continue;
|
|
1031
1030
|
if (
|
|
1032
1031
|
((fm?.type === "being" && ["born", "died"].includes(field.name)) ||
|
|
1033
1032
|
(fm?.type === "place" && field.name === "present")) &&
|
|
@@ -1333,7 +1332,7 @@ export function buildInfoboxes(fm, options) {
|
|
|
1333
1332
|
sections: [],
|
|
1334
1333
|
};
|
|
1335
1334
|
if (available && provider) {
|
|
1336
|
-
const type =
|
|
1335
|
+
const type = fm.type;
|
|
1337
1336
|
const ctx = {
|
|
1338
1337
|
block: map.block,
|
|
1339
1338
|
resolve,
|
package/engine/item-compiler.mjs
CHANGED
|
@@ -57,7 +57,6 @@ import { itemDocEntryId, itemDocPointer } from "./item-docs.mjs";
|
|
|
57
57
|
// configuration, so the types this pass claims and the builders it compiles
|
|
58
58
|
// them with are one table — the consuming repository's, not this package's.
|
|
59
59
|
import { itemTypes, itemBuilder, itemArt, itemFields } from "./item-registry.mjs";
|
|
60
|
-
import { currentType } from "./ids.mjs";
|
|
61
60
|
// Which Foundry Item subtype a note's `type` compiles into. Looked up in the
|
|
62
61
|
// system's declared map, never inferred from the type itself.
|
|
63
62
|
import { documentSubtype, subtypeRow } from "./document-subtypes.mjs";
|
|
@@ -198,10 +197,7 @@ export class SystemItemCompiler extends BasePackCompiler {
|
|
|
198
197
|
* @returns {boolean} True for a whitelisted item type.
|
|
199
198
|
*/
|
|
200
199
|
selects(fm) {
|
|
201
|
-
|
|
202
|
-
// spelling, and a note still on a renamed one compiles unchanged
|
|
203
|
-
// during the retirement window.
|
|
204
|
-
if (!fm.type || !itemTypes().has(currentType(fm.type))) return false;
|
|
200
|
+
if (!fm.type || !itemTypes().has(fm.type)) return false;
|
|
205
201
|
const map = /** @type {typeof SystemItemCompiler} */ (this.constructor).documentSubtypes;
|
|
206
202
|
const row = subtypeRow(/** @type {never} */ (map), fm.type);
|
|
207
203
|
return !row || row.document === "Item";
|
|
@@ -382,10 +378,8 @@ export class SystemItemCompiler extends BasePackCompiler {
|
|
|
382
378
|
});
|
|
383
379
|
|
|
384
380
|
const effects = blockProperty(fm, system, "effects");
|
|
385
|
-
// Read through the system block like every other item field.
|
|
386
|
-
//
|
|
387
|
-
// `folder:` id spelling having been retired with the per-pack YAML it
|
|
388
|
-
// resolved against.
|
|
381
|
+
// Read through the system block like every other item field.
|
|
382
|
+
// `packFolder` names a folder note by its address.
|
|
389
383
|
const folder = this.folderResolver(blockDataProperty(fm, system, "packFolder", null), {
|
|
390
384
|
isAddress: true,
|
|
391
385
|
});
|
|
@@ -415,7 +409,7 @@ export class SystemItemCompiler extends BasePackCompiler {
|
|
|
415
409
|
|
|
416
410
|
/** @inheritdoc */
|
|
417
411
|
onCompiled(fm) {
|
|
418
|
-
this.counts[
|
|
412
|
+
this.counts[fm.type]++;
|
|
419
413
|
}
|
|
420
414
|
|
|
421
415
|
/** @inheritdoc */
|
package/engine/item-docs.mjs
CHANGED
|
@@ -22,10 +22,8 @@
|
|
|
22
22
|
* which {@link sohl.utils.descriptionLinkTarget} recognises and
|
|
23
23
|
* Display Description follows.
|
|
24
24
|
*
|
|
25
|
-
* The prose then exists once
|
|
26
|
-
*
|
|
27
|
-
* which only 133 KB was distinct text. Nothing about actors changes —
|
|
28
|
-
* they embed whatever the item carries, and what the item carries is now a link.
|
|
25
|
+
* The prose then exists once, not as a copy on every actor carrying the item.
|
|
26
|
+
* Actors embed whatever the item carries, and what the item carries is now a link.
|
|
29
27
|
*
|
|
30
28
|
* **Two passes, no shared state.** The items pass writes the pointer; the
|
|
31
29
|
* journals pass writes the entry it points at. Neither can see the other's
|
|
@@ -106,7 +104,7 @@ export function itemDocEntryId(itemId) {
|
|
|
106
104
|
* item beats a bare UUID.
|
|
107
105
|
* @param {string} pageId - The id of the selected entry page, from
|
|
108
106
|
* {@link journalPageId}.
|
|
109
|
-
* @param {object} [fm] - The source note,
|
|
107
|
+
* @param {object} [fm] - The source note, which routes GM-only prose.
|
|
110
108
|
* @returns {string} The pointer to store in `system.docHtml`.
|
|
111
109
|
*/
|
|
112
110
|
export function itemDocPointer(packageId, itemId, name, pageId, fm) {
|
package/engine/item-registry.mjs
CHANGED
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
* @module
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
|
-
import { currentType } from "./ids.mjs";
|
|
44
43
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
45
44
|
import { resolveImg } from "./helpers.mjs";
|
|
46
45
|
|
|
@@ -51,10 +50,8 @@ import { resolveImg } from "./helpers.mjs";
|
|
|
51
50
|
*
|
|
52
51
|
* **Derived, never authored.** These are the keys of the consuming
|
|
53
52
|
* repository's `itemBuilders` registry, so the whitelist and the builder table
|
|
54
|
-
* are the same list and cannot drift apart.
|
|
55
|
-
*
|
|
56
|
-
* behind it, so every `type: trait` note passed the gate and then failed to
|
|
57
|
-
* compile.
|
|
53
|
+
* are the same list and cannot drift apart. A whitelist naming a type with no builder would
|
|
54
|
+
* let every note of that type pass the gate and then fail to compile.
|
|
58
55
|
*
|
|
59
56
|
* An accessor rather than a hoisted constant, so that importing this module
|
|
60
57
|
* needs no configuration.
|
|
@@ -84,11 +81,6 @@ export function itemTypes() {
|
|
|
84
81
|
*/
|
|
85
82
|
function lookup(what, table, type, system) {
|
|
86
83
|
const config = loadPackConfig();
|
|
87
|
-
// The registry is keyed by the current spelling of a note type, and a note
|
|
88
|
-
// still on a renamed one resolves through it unchanged. One place, because
|
|
89
|
-
// every type-keyed table this module reaches — builders, art, fields — is
|
|
90
|
-
// indexed here.
|
|
91
|
-
type = currentType(type);
|
|
92
84
|
if (system !== undefined) {
|
|
93
85
|
const perSystem = /** @type {Record<string, Record<string, unknown>>} */ (
|
|
94
86
|
config[`${table}BySystem`]
|
package/engine/journals.mjs
CHANGED
|
@@ -796,15 +796,11 @@ export class Journals extends BasePackCompiler {
|
|
|
796
796
|
// sits under the same heading a reader found the item under.
|
|
797
797
|
//
|
|
798
798
|
// An address is resolved wherever it is written, including here — and
|
|
799
|
-
// resolving it *here*
|
|
800
|
-
// describe. A folder note has one definition and one address, so the
|
|
799
|
+
// resolving it *here* keeps the folders agreeing. A folder note has one definition and one address, so the
|
|
801
800
|
// journals pack materialises the very folder the items pack does, by
|
|
802
801
|
// the same id. There is no second folder file left to disagree
|
|
803
802
|
// with the first, and so no arrangement to assume: the mirroring
|
|
804
803
|
// failure is unrepresentable rather than merely reported.
|
|
805
|
-
//
|
|
806
|
-
// The id spelling used to cross packs verbatim here, on the assumption
|
|
807
|
-
// both declared it — the arrangement retired with the YAML.
|
|
808
804
|
const { value: authoredFolder } = folderField(fm);
|
|
809
805
|
const folder = this.folderResolver(authoredFolder, { isAddress: true });
|
|
810
806
|
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* A work a people tells, sings or writes: the `literature` lore subType and the
|
|
16
|
-
*
|
|
16
|
+
* three optional facts only such a note may state about itself. Whose work it
|
|
17
|
+
* is, `data.culture`, is a field every lore note but a culture note may state,
|
|
18
|
+
* declared in `culture-choice.mjs`.
|
|
17
19
|
*
|
|
18
20
|
* `DataFieldSpec` declares the keys a type accepts, not the subType that may
|
|
19
21
|
* write them, so {@link checkLiteratureNote} scopes these keys to `literature`
|
|
@@ -23,7 +25,6 @@
|
|
|
23
25
|
*/
|
|
24
26
|
|
|
25
27
|
import { isAddressTuple, parseAddress, renderAddress } from "./address.mjs";
|
|
26
|
-
import { checkCultureChoice } from "./culture-choice.mjs";
|
|
27
28
|
import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
28
29
|
|
|
29
30
|
/** The `lore` subType whose notes are works of literature. */
|
|
@@ -66,20 +67,11 @@ export function checkLanguageChoice(note, { index } = {}) {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
/**
|
|
69
|
-
* The fields a `literature` note may state. Every one is optional.
|
|
70
|
+
* The fields only a `literature` note may state. Every one is optional.
|
|
70
71
|
*
|
|
71
72
|
* @type {ReadonlyArray<object>}
|
|
72
73
|
*/
|
|
73
74
|
export const LITERATURE_FIELDS = Object.freeze([
|
|
74
|
-
{
|
|
75
|
-
name: "culture",
|
|
76
|
-
shape: "an Address",
|
|
77
|
-
kind: "address",
|
|
78
|
-
ref: "lore",
|
|
79
|
-
accepts: ["lore"],
|
|
80
|
-
check: checkCultureChoice,
|
|
81
|
-
describe: "The people whose work this is, as a culture lore note.",
|
|
82
|
-
},
|
|
83
75
|
{
|
|
84
76
|
name: "form",
|
|
85
77
|
shape: "string",
|
package/engine/macros.mjs
CHANGED
|
@@ -264,8 +264,7 @@ export function buildMacroEntry(fm, { command, folder = null, stats = defaultSta
|
|
|
264
264
|
// into chat instead of running.
|
|
265
265
|
type: resolveMacroType(fm, name),
|
|
266
266
|
author: null,
|
|
267
|
-
//
|
|
268
|
-
// shared default, one that writes `icon: ""` ships blank on purpose.
|
|
267
|
+
// A macro note that names no art gets the shared default.
|
|
269
268
|
img: (img === undefined ? null : img) ?? DEFAULT_MACRO_IMG,
|
|
270
269
|
scope: resolveMacroScope(fm, name),
|
|
271
270
|
command,
|
package/engine/map-graphviz.mjs
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
|
|
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
|
+
*/
|
|
2
13
|
|
|
3
14
|
/** Render DOT maps with the Graphviz runtime installed by npm. */
|
|
4
15
|
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
/*
|
|
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
|
+
*/
|
|
2
13
|
|
|
3
14
|
import Handlebars from "handlebars";
|
|
4
15
|
|