@heroiclands/package-build 22.17.2 → 22.19.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 +22 -0
- package/README.md +1 -1
- package/docs/api.md +1 -1
- package/docs/authoring/frontmatter.md +4 -2
- package/docs/guides/icons.md +3 -0
- package/docs/reference/format-details.md +30 -22
- package/engine/actor-compiler.mjs +2 -3
- package/engine/art-fields.mjs +5 -10
- package/engine/frontmatter-lint.mjs +13 -0
- package/engine/infobox.mjs +16 -5
- package/engine/journals.mjs +25 -12
- package/engine/pdf-build.mjs +3 -6
- package/engine/site-build.mjs +0 -1
- package/package.json +2 -12
- package/types/engine/actor-compiler.d.mts +2 -3
- package/types/engine/art-fields.d.mts +5 -6
- package/types/engine/infobox.d.mts +3 -2
- package/types/engine/journals.d.mts +11 -10
- package/engine/documentation-links.mjs +0 -31
- package/types/engine/documentation-links.d.mts +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 22.19.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
**Beings**
|
|
8
|
+
|
|
9
|
+
- Beings can be described as Commoners, Entertainers, or Guildspeople, and Commoner stands alone when no other archetype fits.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Books place infoboxes after each entry’s authored content, and Foundry journals place each infobox on its own page after the authored pages.
|
|
14
|
+
|
|
15
|
+
## 22.18.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- Beings use their subtype to choose default art.
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
Being profiles identify each subject as Character, NPC, or Creature from its top-level subtype.
|
|
24
|
+
|
|
3
25
|
## 22.17.2
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ The generated project includes a homepage, website and book targets, npm scripts
|
|
|
18
18
|
|
|
19
19
|
## Documentation
|
|
20
20
|
|
|
21
|
-
The documentation lives in the Markdown files in this repository. Start with [the documentation home](docs/index.md).
|
|
21
|
+
The documentation lives in the Markdown files in this repository. Start with [the documentation home](docs/index.md). This toolchain does not build its own site or book. A Hugo theme dependency would form a cycle because the theme uses package-build for releases.
|
|
22
22
|
|
|
23
23
|
| Topic | Guide |
|
|
24
24
|
| ------------------------------------------------- | ------------------------------------------------------------ |
|
package/docs/api.md
CHANGED
|
@@ -417,7 +417,7 @@ The infobox declarations this toolchain ships, one per system, and the single ca
|
|
|
417
417
|
|
|
418
418
|
### `engine.infoboxRender`
|
|
419
419
|
|
|
420
|
-
Drawing a declared infobox: HTML for
|
|
420
|
+
Drawing a declared infobox: HTML for separate Foundry pages appended after authored pages, Typst panels after each note’s authored body in the book. The website's boxes travel in front matter and the site theme draws them, so there is no renderer for it here.
|
|
421
421
|
|
|
422
422
|
| Export | Signature | Returns | Use it when |
|
|
423
423
|
| ---------------------- | ------------------------------------- | --------- | ------------------------------------------------------------------------ |
|
|
@@ -21,7 +21,7 @@ data: {}
|
|
|
21
21
|
---
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
`name.full` is the displayed name; `name.aliases` helps search. `description` is the short page summary. `tags` hold draft state and classification. `data` holds facts about the subject shared by systems. A YAML comment is the place for an author-only note: commented values remain valid YAML but do not enter the content index or generated documents.
|
|
24
|
+
`name.full` is the displayed name; `name.aliases` helps search. `description` is the short page summary. `tags` hold draft state and descriptive labels. The note type's classification belongs in `subType` where it is declared. `data` holds facts about the subject shared by systems. A YAML comment is the place for an author-only note: commented values remain valid YAML but do not enter the content index or generated documents.
|
|
25
25
|
|
|
26
26
|
Use `tags: [gm]` for a note intended only for the GM. It is absent from the public website and book. Foundry includes it only when its document routes to a pack with `private: true`. A note with `gm` that also creates a prose JournalEntry needs a private JournalEntry pack. Links from untagged notes to GM notes are errors; GM notes may link to one another.
|
|
27
27
|
|
|
@@ -31,10 +31,12 @@ The formatter keeps a collection on one line when the full line is under 100 cha
|
|
|
31
31
|
|
|
32
32
|
The [note-type reference](../reference/note-types.md) lists every field accepted under `data`, including the shared `id`, `pack`, `packFolder`, `harnworld`, `icon`, and `banner` fields. A type such as `being` also has its own fields, including `culture`, `homes`, and `affiliations`. A field absent from a type's vocabulary is a lint error; an unrelated field is not silently repurposed.
|
|
33
33
|
|
|
34
|
+
A being's `data.archetypes` names the roles it can fill in an adventure. Choose every specific role that fits, such as `entertainer` for a performer or `guildsperson` for someone whose professional training or connections matter. Use `[commoner]` when no specific archetype fits. `commoner` cannot share the list with another value. The [archetype reference](../reference/format-details.md#actors) defines the available roles.
|
|
35
|
+
|
|
34
36
|
A `sohl:` or `hm3:` block holds values for that Foundry system. Its `system:` child contains values sent to the system's DataModel. The system block can override shared routing and art where the declared field permits it. `data.pack` and `data.packFolder` are shared routes; a system-specific route belongs in that system block. The [detailed mapping reference](../reference/format-details.md#mappings-every-type-shares) and [system-block explanation](../reference/format-details.md#frontmatter-has-three-regions) cover precedence and emitted fields.
|
|
35
37
|
|
|
36
38
|
One body describes the subject for both systems. A being's `{#appearance}` and `{#dossier}` sections feed SoHL Actor `system.appearance` and `system.dossier`, and HM3 Actor `system.description` and `system.biography`. An Item's `{#description}` page becomes a shared JournalEntryPage. Its resolved `@UUID[...]` pointer is stored in SoHL Item `system.docHtml` and HM3 Item `system.description`. An Item with no `{#description}` anchor uses its first page. Other H1 pages remain linkable.
|
|
37
39
|
|
|
38
40
|
## Publication and summaries
|
|
39
41
|
|
|
40
|
-
A note infobox draws from shared `data`. A system infobox draws from the system's fields, and a mapped system with no document says “Not available.” The same authored body appears in Foundry, on the website, and in the book;
|
|
42
|
+
A note infobox draws from shared `data`. A being profile shows its top-level `subType` as `Type: Character`, `Type: NPC`, or `Type: Creature`. A system infobox draws from the system's fields, and a mapped system with no document says “Not available.” The same authored body appears in Foundry, on the website, and in the book; the book places boxes after the authored body, Foundry appends one page per box, and the website places boxes in its responsive side rail. [Infobox details](../reference/format-details.md#the-infobox) describe field order and empty values.
|
package/docs/guides/icons.md
CHANGED
|
@@ -58,6 +58,9 @@ it does not set a document's profile art. The
|
|
|
58
58
|
[art-slot reference](../reference/format-details.md#the-four-art-slots) gives each
|
|
59
59
|
slot's type and document destination.
|
|
60
60
|
|
|
61
|
+
A being's `subType` selects its compiled default art: `character` and `npc`
|
|
62
|
+
use person art, and `creature` uses creature art.
|
|
63
|
+
|
|
61
64
|
## Write an inline icon in prose
|
|
62
65
|
|
|
63
66
|
Declare the glyph vocabulary in `package-build.config.yaml` or a separate YAML
|
|
@@ -11,10 +11,12 @@ subType: reference
|
|
|
11
11
|
|
|
12
12
|
Markdown generates a JournalEntry: content before the first H1 becomes its "Introduction" page, and each H1 begins another page. The document Address is `<package>-note-<note_type>-<shortcode>`.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
Each emitted infobox becomes a separate page at the end of the JournalEntry,
|
|
15
|
+
after every authored page. The page names are **Properties Infobox**,
|
|
16
|
+
**SoHL Infobox**, and **HM3 Infobox**, in that order where emitted. Generated
|
|
17
|
+
page ids derive from the entry and box identity, separately from authored
|
|
18
|
+
heading names and anchors. An authored heading may share an infobox page name.
|
|
19
|
+
What the boxes hold is [the infobox](#the-infobox) below.
|
|
18
20
|
|
|
19
21
|
**There are two kinds of infobox.**
|
|
20
22
|
|
|
@@ -62,16 +64,13 @@ For web pages, the full Markdown body renders as HTML with its infoboxes.
|
|
|
62
64
|
|
|
63
65
|
### The infobox
|
|
64
66
|
|
|
65
|
-
**The
|
|
66
|
-
prose.** It is not a floating sidebar and it is not defined in a rendering
|
|
67
|
-
template. The toolchain settles what each box holds; each medium lays that out
|
|
68
|
-
its own way.
|
|
67
|
+
**The toolchain settles what each box holds; each medium places it.**
|
|
69
68
|
|
|
70
|
-
| medium | placement
|
|
71
|
-
| ------- |
|
|
72
|
-
| book | flows in the column measure, breaking between sections | none
|
|
73
|
-
| website | side rail on wide screens, inline on narrow
|
|
74
|
-
| Foundry |
|
|
69
|
+
| medium | placement | collapsing |
|
|
70
|
+
| ------- | ------------------------------------------------------------------------------------------------------------- | ------------------------- |
|
|
71
|
+
| book | after the note's authored body, before the next entry; flows in the column measure, breaking between sections | none |
|
|
72
|
+
| website | side rail on wide screens, inline on narrow screens | `<details>`, default open |
|
|
73
|
+
| Foundry | one page per emitted box, after all authored pages | `<details>`, default open |
|
|
75
74
|
|
|
76
75
|
Two columns are print-only. A scrolling page has no fixed viewport, so columns
|
|
77
76
|
make a reader travel down and back up, and a narrow screen collapses to one
|
|
@@ -79,8 +78,8 @@ column regardless.
|
|
|
79
78
|
|
|
80
79
|
**Six rules hold in every medium.**
|
|
81
80
|
|
|
82
|
-
1. **
|
|
83
|
-
|
|
81
|
+
1. **Placement belongs to the medium**, as described above. Authored images
|
|
82
|
+
remain in the body in their authored order.
|
|
84
83
|
2. **It contains no image.** A picture is authored in the text with its own
|
|
85
84
|
directive, and its position governs.
|
|
86
85
|
3. **A section is the unit that flows.** Sections are whole and unbreakable and
|
|
@@ -101,6 +100,10 @@ column regardless.
|
|
|
101
100
|
6. **Order is the toolchain's.** A medium renders boxes, sections and rows in
|
|
102
101
|
the order given.
|
|
103
102
|
|
|
103
|
+
A note profile begins with `Name`. For a being with a top-level `subType`, it
|
|
104
|
+
follows with `Type: Character`, `Type: NPC`, or `Type: Creature`. A being with
|
|
105
|
+
no subtype has no `Type` row.
|
|
106
|
+
|
|
104
107
|
**Embedded models link to documentation in an infobox.** The model Address
|
|
105
108
|
continues to identify the native Item used for compilation. Its displayed link
|
|
106
109
|
uses that same package, type and Shortcode under `note`, which names the
|
|
@@ -438,16 +441,16 @@ surface:
|
|
|
438
441
|
> **Draft.** This entry is unfinished. What it states may change, and nothing in
|
|
439
442
|
> it is settled.
|
|
440
443
|
|
|
441
|
-
It leads the entry
|
|
444
|
+
It leads the entry, before the authored content, because a reader
|
|
442
445
|
deciding whether to rely on an entry has to be told before they read it. Where
|
|
443
446
|
it lands:
|
|
444
447
|
|
|
445
|
-
| Surface | What carries it
|
|
446
|
-
| ------------ |
|
|
447
|
-
| JournalEntry | a `blockquote.sohl-draft-notice` at the head of the first page
|
|
448
|
-
| Actor | the dossier the sheet draws — SoHL's `dossier`, HM3's `biography`
|
|
449
|
-
| The book | a drawn mark and a left rule between the entry's plate and its
|
|
450
|
-
| The website | the theme's own notice, above the infobox rail and the prose
|
|
448
|
+
| Surface | What carries it |
|
|
449
|
+
| ------------ | ---------------------------------------------------------------------------- |
|
|
450
|
+
| JournalEntry | a `blockquote.sohl-draft-notice` at the head of the first page |
|
|
451
|
+
| Actor | the dossier the sheet draws — SoHL's `dossier`, HM3's `biography` |
|
|
452
|
+
| The book | a drawn mark and a left rule between the entry's plate and its authored body |
|
|
453
|
+
| The website | the theme's own notice, above the infobox rail and the prose |
|
|
451
454
|
|
|
452
455
|
**An Actor carries it once.** A being's prose reaches two fields, and a document
|
|
453
456
|
saying the same thing twice teaches a reader to skip it — a printed sheet draws
|
|
@@ -2441,8 +2444,13 @@ For JournalEntries, the following rules apply:
|
|
|
2441
2444
|
- mariner: Handles boats and blue water.
|
|
2442
2445
|
- artisan: Builds, repairs, and appraises craft work.
|
|
2443
2446
|
- trader: Moves goods, values them, and knows markets.
|
|
2447
|
+
- commoner: Fits no more specific archetype; handles ordinary work and daily life.
|
|
2448
|
+
- entertainer: Performs for an audience through acting, music, comedy, or similar arts.
|
|
2449
|
+
- guildsperson: Has professional training, standing, or connections that open doors.
|
|
2444
2450
|
|
|
2445
2451
|
Note that archetypes are descriptive, not proscriptive, and a character may be described by multiple archetypes at once.
|
|
2452
|
+
`commoner` stands alone: a being with another fitting archetype does not also carry `commoner`.
|
|
2453
|
+
Guild membership alone does not require `guildsperson`; the being's training, standing, or connections must matter to the role.
|
|
2446
2454
|
|
|
2447
2455
|
#### Items
|
|
2448
2456
|
|
|
@@ -448,9 +448,8 @@ export class SystemActorCompiler extends BasePackCompiler {
|
|
|
448
448
|
* The actor's two pieces of art, resolved, with the being default beneath
|
|
449
449
|
* them.
|
|
450
450
|
*
|
|
451
|
-
* **The default follows the note's `subType
|
|
452
|
-
*
|
|
453
|
-
* to the package that ships them. A tree whose index cannot answer an
|
|
451
|
+
* **The default follows the note's `subType`**. Both addresses belong to
|
|
452
|
+
* the package that ships them. A tree whose index cannot answer an
|
|
454
453
|
* address — a dependency not yet fetched — falls through to the document
|
|
455
454
|
* subtype's own default, so the document is never left with no art.
|
|
456
455
|
*
|
package/engine/art-fields.mjs
CHANGED
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
47
|
import { encodeAddresses } from "./address-values.mjs";
|
|
48
|
-
import { hasTag } from "./note-vocabulary.mjs";
|
|
49
48
|
import { ASSET_SYSTEM, isAssetType } from "./asset-types.mjs";
|
|
50
49
|
import { ASSETS_SEGMENT } from "./pathnames.mjs";
|
|
51
50
|
import { isAssetRecord } from "./index-records.mjs";
|
|
@@ -231,7 +230,7 @@ export function artPathname(index, value, defaultType, accepts) {
|
|
|
231
230
|
}
|
|
232
231
|
|
|
233
232
|
/**
|
|
234
|
-
* The art a being falls back to, by
|
|
233
|
+
* The art a being falls back to, by its subtype.
|
|
235
234
|
*
|
|
236
235
|
* Both files ship in `sohl`, under `assets/icons/other/`, and both are named
|
|
237
236
|
* here as addresses rather than as paths for the reason every art reference is:
|
|
@@ -239,8 +238,8 @@ export function artPathname(index, value, defaultType, accepts) {
|
|
|
239
238
|
* package borrowing the default gets the same file the system ships.
|
|
240
239
|
*
|
|
241
240
|
* **Only the compiler can choose between them**, because only the compiler
|
|
242
|
-
* reads the note's
|
|
243
|
-
* and covers a world document created by hand, which no note describes.
|
|
241
|
+
* reads the note's subtype. A schema default is the last resort beneath this
|
|
242
|
+
* one, and covers a world document created by hand, which no note describes.
|
|
244
243
|
*
|
|
245
244
|
* @type {Readonly<Record<string, string>>}
|
|
246
245
|
*/
|
|
@@ -252,20 +251,16 @@ export const BEING_DEFAULT_ART = Object.freeze({
|
|
|
252
251
|
/**
|
|
253
252
|
* The default art address a being's subtype chooses, or `null`.
|
|
254
253
|
*
|
|
255
|
-
* An NPC and a character use person art; a creature uses creature art.
|
|
256
|
-
* tag lookup preserves defaults for package trees that have not migrated.
|
|
254
|
+
* An NPC and a character use person art; a creature uses creature art.
|
|
257
255
|
*
|
|
258
256
|
* @param {object} fm - The note's frontmatter.
|
|
259
|
-
* @returns {string|null} The address, or `null` when no subtype
|
|
257
|
+
* @returns {string|null} The address, or `null` when no subtype selects one.
|
|
260
258
|
*/
|
|
261
259
|
export function beingDefaultArt(fm) {
|
|
262
260
|
if (fm?.subType === "npc" || fm?.subType === "character") {
|
|
263
261
|
return BEING_DEFAULT_ART.character;
|
|
264
262
|
}
|
|
265
263
|
if (fm?.subType === "creature") return BEING_DEFAULT_ART.creature;
|
|
266
|
-
for (const [tag, address] of Object.entries(BEING_DEFAULT_ART)) {
|
|
267
|
-
if (hasTag(fm, tag)) return address;
|
|
268
|
-
}
|
|
269
264
|
return null;
|
|
270
265
|
}
|
|
271
266
|
|
|
@@ -1173,6 +1173,19 @@ export function lintNote(
|
|
|
1173
1173
|
const type = String(fm.type ?? "");
|
|
1174
1174
|
const raw = () => note.raw ?? "";
|
|
1175
1175
|
const at = (key, literal) => positionInFrontmatter(raw(), key, literal ?? undefined);
|
|
1176
|
+
if (
|
|
1177
|
+
type === "being" &&
|
|
1178
|
+
Array.isArray(fm.data?.archetypes) &&
|
|
1179
|
+
fm.data.archetypes.includes("commoner") &&
|
|
1180
|
+
fm.data.archetypes.length > 1
|
|
1181
|
+
) {
|
|
1182
|
+
findings.push({
|
|
1183
|
+
file: note.file,
|
|
1184
|
+
...at("archetypes", "commoner"),
|
|
1185
|
+
severity: "error",
|
|
1186
|
+
message: "`data.archetypes` may contain `commoner` only by itself",
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1176
1189
|
/**
|
|
1177
1190
|
* The in-block keys this note's own type claims for something other than
|
|
1178
1191
|
* the note-level field of that name, which every note-level check below
|
package/engine/infobox.mjs
CHANGED
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
*
|
|
52
52
|
* ## Six rules that hold in every medium
|
|
53
53
|
*
|
|
54
|
-
* 1. **
|
|
55
|
-
*
|
|
56
|
-
*
|
|
54
|
+
* 1. **Placement belongs to the medium.** The book puts boxes after the
|
|
55
|
+
* authored body, Foundry appends one page per box, and the website uses
|
|
56
|
+
* a side rail on wide screens and inline boxes on narrow screens.
|
|
57
57
|
* 2. **It contains no image.** A picture is authored in the text with its own
|
|
58
58
|
* directive, and its position governs. {@link NOTE_FIELD_PRESENTATION}
|
|
59
59
|
* withholds the art slots and `overlay` for that reason and no other.
|
|
@@ -187,6 +187,13 @@ export const NOTE_BOX_TITLE = "Profile";
|
|
|
187
187
|
/** The note infobox's single section id. @type {string} */
|
|
188
188
|
export const NOTE_SECTION_ID = "profile";
|
|
189
189
|
|
|
190
|
+
/** @type {Readonly<Record<string, string>>} */
|
|
191
|
+
const BEING_SUBTYPE_LABELS = Object.freeze({
|
|
192
|
+
npc: "NPC",
|
|
193
|
+
character: "Character",
|
|
194
|
+
creature: "Creature",
|
|
195
|
+
});
|
|
196
|
+
|
|
190
197
|
/**
|
|
191
198
|
* What a **duration pair** is called.
|
|
192
199
|
*
|
|
@@ -659,8 +666,9 @@ export function linkValue(ref, resolve, hint) {
|
|
|
659
666
|
*
|
|
660
667
|
* The rows are {@link NOTE_VOCABULARY}'s declaration for the type, in its
|
|
661
668
|
* order, minus what {@link NOTE_FIELD_PRESENTATION} withholds and minus every
|
|
662
|
-
* field the note left empty. A
|
|
663
|
-
*
|
|
669
|
+
* field the note left empty. A being's top-level `subType` adds its profile
|
|
670
|
+
* type. A type that declares no `data:` fields still gets the box, because
|
|
671
|
+
* `Name` is a fact about every note.
|
|
664
672
|
*
|
|
665
673
|
* @param {object} fm - The note's frontmatter.
|
|
666
674
|
* @param {object} [options] - Options.
|
|
@@ -698,6 +706,9 @@ function noteBox(
|
|
|
698
706
|
const shown = new Set();
|
|
699
707
|
const name = fm?.name?.full ?? fm?.title;
|
|
700
708
|
if (hasValue(name)) rows.push({ label: "Name", kind: "text", value: String(name) });
|
|
709
|
+
if (fm?.type === "being" && Object.hasOwn(BEING_SUBTYPE_LABELS, fm.subType)) {
|
|
710
|
+
rows.push({ label: "Type", kind: "text", value: BEING_SUBTYPE_LABELS[fm.subType] });
|
|
711
|
+
}
|
|
701
712
|
|
|
702
713
|
const data = isMapping(fm?.data) ? fm.data : {};
|
|
703
714
|
/** @type {Map<string, {label: string, entries: string[]}>} */
|
package/engine/journals.mjs
CHANGED
|
@@ -335,16 +335,13 @@ export function buildPages(rawPages, entryId, noteName, captions) {
|
|
|
335
335
|
* entry: a module may ship the same content for two systems, and each pack's
|
|
336
336
|
* documents record the system version they were built against. A
|
|
337
337
|
* caller with no pack in hand gets the package-wide block.
|
|
338
|
-
* @param {string} [params.
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
* that reads. It is **not** a page of its own — a page is what a UUID
|
|
343
|
-
* addresses, and a summary a reader has to navigate to is a summary they do
|
|
344
|
-
* not see.
|
|
338
|
+
* @param {Array<{id: string, name: string, html: string}>} [params.infoboxes] -
|
|
339
|
+
* Emitted boxes, each rendered to HTML. Each becomes a separate page after
|
|
340
|
+
* the authored pages. Its identity uses the box id in a generated-page
|
|
341
|
+
* namespace, independent of authored headings and anchors.
|
|
345
342
|
* @param {string} [params.notice] - A statement about the entry rather than
|
|
346
343
|
* about its subject, already rendered to HTML — see {@link
|
|
347
|
-
* module:engine/draft-notice}. It leads the first page,
|
|
344
|
+
* module:engine/draft-notice}. It leads the first authored page,
|
|
348
345
|
* because a reader deciding whether to rely on the entry has to be told
|
|
349
346
|
* before they read it rather than after.
|
|
350
347
|
* @returns {object} The JournalEntry document, keyed for the pack.
|
|
@@ -357,13 +354,21 @@ export function buildJournalEntry({
|
|
|
357
354
|
folder = null,
|
|
358
355
|
flags,
|
|
359
356
|
stats = defaultStats(),
|
|
360
|
-
|
|
357
|
+
infoboxes = [],
|
|
361
358
|
notice = "",
|
|
362
359
|
}) {
|
|
363
360
|
const rawPages = splitPages(markdown, leadName);
|
|
364
361
|
const pages = buildPages(rawPages, id, name, scanCaptions(markdown).captions);
|
|
365
|
-
|
|
366
|
-
|
|
362
|
+
for (const box of infoboxes) {
|
|
363
|
+
const pageId = makeId("journal-infobox-page", `${id}:${box.id}`);
|
|
364
|
+
pages.push({
|
|
365
|
+
_id: pageId,
|
|
366
|
+
name: box.name,
|
|
367
|
+
type: "text",
|
|
368
|
+
title: { show: true, level: 1 },
|
|
369
|
+
text: { format: 1, content: box.html },
|
|
370
|
+
_key: `!journal.pages!${id}.${pageId}`,
|
|
371
|
+
});
|
|
367
372
|
}
|
|
368
373
|
if (notice.trim() && pages.length) {
|
|
369
374
|
pages[0].text.content = `${notice}\n${pages[0].text.content}`;
|
|
@@ -522,7 +527,15 @@ export class Journals extends BasePackCompiler {
|
|
|
522
527
|
id,
|
|
523
528
|
name,
|
|
524
529
|
markdown,
|
|
525
|
-
|
|
530
|
+
infoboxes: boxes.map((box) => ({
|
|
531
|
+
id: box.id,
|
|
532
|
+
name: `${
|
|
533
|
+
box.id === "note" ? "Properties"
|
|
534
|
+
: box.id === "sohl" ? "SoHL"
|
|
535
|
+
: "HM3"
|
|
536
|
+
} Infobox`,
|
|
537
|
+
html: infoboxesToHtml([box], { link: linkToUuid }),
|
|
538
|
+
})),
|
|
526
539
|
// Whether the entry is settled, asked of the tag through the one
|
|
527
540
|
// reader of it. This sits here rather than in `buildJournalEntry`
|
|
528
541
|
// for the reason `skipNote` runs first: a note with no prose
|
package/engine/pdf-build.mjs
CHANGED
|
@@ -639,9 +639,7 @@ export async function buildPdf({ config, out, version = "", compile = true } = {
|
|
|
639
639
|
anchorPrefix,
|
|
640
640
|
captions: numberedCaptions,
|
|
641
641
|
});
|
|
642
|
-
//
|
|
643
|
-
// before the prose. An image the note authored ahead of it still comes
|
|
644
|
-
// first, because the image lives in the body and the body follows.
|
|
642
|
+
// Infoboxes follow the authored body within this note's entry.
|
|
645
643
|
const boxes = noteInfoboxes(page.fm, {
|
|
646
644
|
resolve: (ref, hint) => resolveInfoboxRef(gates.index, ref, hint),
|
|
647
645
|
router: routerFor(resolved),
|
|
@@ -651,10 +649,9 @@ export async function buildPdf({ config, out, version = "", compile = true } = {
|
|
|
651
649
|
link: (value) => linkToTypst(value, plan.links, labelFor),
|
|
652
650
|
});
|
|
653
651
|
// Whether the entry is settled comes before anything it says: the
|
|
654
|
-
// notice leads the leaf,
|
|
655
|
-
// holds on every other surface.
|
|
652
|
+
// notice leads the leaf, before the authored body.
|
|
656
653
|
const notice = isDraftNote(page.fm) ? draftNoticeTypst() : "";
|
|
657
|
-
const parts = [notice,
|
|
654
|
+
const parts = [notice, prose, panel].filter((part) => part);
|
|
658
655
|
return parts.join("\n\n");
|
|
659
656
|
};
|
|
660
657
|
|
package/engine/site-build.mjs
CHANGED
|
@@ -984,7 +984,6 @@ export function renderPages(pages, options) {
|
|
|
984
984
|
*/
|
|
985
985
|
const SITE_PASSES = Object.freeze({
|
|
986
986
|
sohlKb: () => require("../sohl/kb-passes.mjs").sohlKbPass,
|
|
987
|
-
packageDocs: () => require("./documentation-links.mjs").documentationLinksPass,
|
|
988
987
|
});
|
|
989
988
|
|
|
990
989
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.19.0",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -174,7 +174,6 @@
|
|
|
174
174
|
},
|
|
175
175
|
"devDependencies": {
|
|
176
176
|
"@changesets/cli": "^3.0.0",
|
|
177
|
-
"@heroiclands/hugo-theme": "^0.9.0",
|
|
178
177
|
"@types/node": "^26.2.0",
|
|
179
178
|
"npm-run-all": "^4.1.5",
|
|
180
179
|
"vitest": "^5.0.0"
|
|
@@ -198,16 +197,7 @@
|
|
|
198
197
|
"changeset": "changeset",
|
|
199
198
|
"changeset:version": "changeset version && node bin/package-build.mjs changelog group && npm install --package-lock-only",
|
|
200
199
|
"prepare": "git config core.hooksPath .githooks || true",
|
|
201
|
-
"build:deps": "node bin/package-build.mjs deps fetch"
|
|
202
|
-
"build:content-index": "node bin/package-build.mjs content-index",
|
|
203
|
-
"build:site-content": "node bin/package-build.mjs site",
|
|
204
|
-
"build:site-html": "hugo --source build/hugo --minify --gc --cleanDestinationDir",
|
|
205
|
-
"build:site-root": "node bin/package-build.mjs site-root",
|
|
206
|
-
"build:site": "run-s build:deps build:content-index build:site-content build:site-html build:site-root",
|
|
207
|
-
"build:book": "run-s build:deps build:content-index build:book-content",
|
|
208
|
-
"build:book-content": "node bin/package-build.mjs pdf",
|
|
209
|
-
"serve:site": "run-s build:deps build:content-index build:site-content serve:site-html",
|
|
210
|
-
"serve:site-html": "hugo server --source build/hugo"
|
|
200
|
+
"build:deps": "node bin/package-build.mjs deps fetch"
|
|
211
201
|
},
|
|
212
202
|
"engines": {
|
|
213
203
|
"node": ">=24.0.0"
|
|
@@ -230,9 +230,8 @@ export class SystemActorCompiler extends BasePackCompiler {
|
|
|
230
230
|
* The actor's two pieces of art, resolved, with the being default beneath
|
|
231
231
|
* them.
|
|
232
232
|
*
|
|
233
|
-
* **The default follows the note's `subType
|
|
234
|
-
*
|
|
235
|
-
* to the package that ships them. A tree whose index cannot answer an
|
|
233
|
+
* **The default follows the note's `subType`**. Both addresses belong to
|
|
234
|
+
* the package that ships them. A tree whose index cannot answer an
|
|
236
235
|
* address — a dependency not yet fetched — falls through to the document
|
|
237
236
|
* subtype's own default, so the document is never left with no art.
|
|
238
237
|
*
|
|
@@ -144,11 +144,10 @@ export function artPathname(index: object, value: unknown, defaultType: string,
|
|
|
144
144
|
/**
|
|
145
145
|
* The default art address a being's subtype chooses, or `null`.
|
|
146
146
|
*
|
|
147
|
-
* An NPC and a character use person art; a creature uses creature art.
|
|
148
|
-
* tag lookup preserves defaults for package trees that have not migrated.
|
|
147
|
+
* An NPC and a character use person art; a creature uses creature art.
|
|
149
148
|
*
|
|
150
149
|
* @param {object} fm - The note's frontmatter.
|
|
151
|
-
* @returns {string|null} The address, or `null` when no subtype
|
|
150
|
+
* @returns {string|null} The address, or `null` when no subtype selects one.
|
|
152
151
|
*/
|
|
153
152
|
export function beingDefaultArt(fm: object): string | null;
|
|
154
153
|
/**
|
|
@@ -180,7 +179,7 @@ export function unresolvedArtMessage(key: string, value: unknown): string;
|
|
|
180
179
|
export function unacceptedArtMessage(key: string, value: unknown, type: string, accepts: Iterable<string>): string;
|
|
181
180
|
export { ART_SLOTS } from "./art-slots.mjs";
|
|
182
181
|
/**
|
|
183
|
-
* The art a being falls back to, by
|
|
182
|
+
* The art a being falls back to, by its subtype.
|
|
184
183
|
*
|
|
185
184
|
* Both files ship in `sohl`, under `assets/icons/other/`, and both are named
|
|
186
185
|
* here as addresses rather than as paths for the reason every art reference is:
|
|
@@ -188,8 +187,8 @@ export { ART_SLOTS } from "./art-slots.mjs";
|
|
|
188
187
|
* package borrowing the default gets the same file the system ships.
|
|
189
188
|
*
|
|
190
189
|
* **Only the compiler can choose between them**, because only the compiler
|
|
191
|
-
* reads the note's
|
|
192
|
-
* and covers a world document created by hand, which no note describes.
|
|
190
|
+
* reads the note's subtype. A schema default is the last resort beneath this
|
|
191
|
+
* one, and covers a world document created by hand, which no note describes.
|
|
193
192
|
*
|
|
194
193
|
* @type {Readonly<Record<string, string>>}
|
|
195
194
|
*/
|
|
@@ -146,8 +146,9 @@ export function linkValue(ref: unknown, resolve: (ref: unknown, hint?: object) =
|
|
|
146
146
|
*
|
|
147
147
|
* The rows are {@link NOTE_VOCABULARY}'s declaration for the type, in its
|
|
148
148
|
* order, minus what {@link NOTE_FIELD_PRESENTATION} withholds and minus every
|
|
149
|
-
* field the note left empty. A
|
|
150
|
-
*
|
|
149
|
+
* field the note left empty. A being's top-level `subType` adds its profile
|
|
150
|
+
* type. A type that declares no `data:` fields still gets the box, because
|
|
151
|
+
* `Name` is a fact about every note.
|
|
151
152
|
*
|
|
152
153
|
* @param {object} fm - The note's frontmatter.
|
|
153
154
|
* @param {object} [options] - Options.
|
|
@@ -139,21 +139,18 @@ export function buildPages(rawPages: Array<object>, entryId: string, noteName: s
|
|
|
139
139
|
* entry: a module may ship the same content for two systems, and each pack's
|
|
140
140
|
* documents record the system version they were built against. A
|
|
141
141
|
* caller with no pack in hand gets the package-wide block.
|
|
142
|
-
* @param {string} [params.
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* that reads. It is **not** a page of its own — a page is what a UUID
|
|
147
|
-
* addresses, and a summary a reader has to navigate to is a summary they do
|
|
148
|
-
* not see.
|
|
142
|
+
* @param {Array<{id: string, name: string, html: string}>} [params.infoboxes] -
|
|
143
|
+
* Emitted boxes, each rendered to HTML. Each becomes a separate page after
|
|
144
|
+
* the authored pages. Its identity uses the box id in a generated-page
|
|
145
|
+
* namespace, independent of authored headings and anchors.
|
|
149
146
|
* @param {string} [params.notice] - A statement about the entry rather than
|
|
150
147
|
* about its subject, already rendered to HTML — see {@link
|
|
151
|
-
* module:engine/draft-notice}. It leads the first page,
|
|
148
|
+
* module:engine/draft-notice}. It leads the first authored page,
|
|
152
149
|
* because a reader deciding whether to rely on the entry has to be told
|
|
153
150
|
* before they read it rather than after.
|
|
154
151
|
* @returns {object} The JournalEntry document, keyed for the pack.
|
|
155
152
|
*/
|
|
156
|
-
export function buildJournalEntry({ id, name, markdown, leadName, folder, flags, stats,
|
|
153
|
+
export function buildJournalEntry({ id, name, markdown, leadName, folder, flags, stats, infoboxes, notice, }: {
|
|
157
154
|
id: string;
|
|
158
155
|
name: string;
|
|
159
156
|
markdown: string;
|
|
@@ -161,7 +158,11 @@ export function buildJournalEntry({ id, name, markdown, leadName, folder, flags,
|
|
|
161
158
|
folder?: string | null | undefined;
|
|
162
159
|
flags?: object | undefined;
|
|
163
160
|
stats?: object | undefined;
|
|
164
|
-
|
|
161
|
+
infoboxes?: {
|
|
162
|
+
id: string;
|
|
163
|
+
name: string;
|
|
164
|
+
html: string;
|
|
165
|
+
}[] | undefined;
|
|
165
166
|
notice?: string | undefined;
|
|
166
167
|
}): object;
|
|
167
168
|
/**
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
|
2
|
-
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
|
|
6
|
-
import { parseMarkdownFile } from "./helpers.mjs";
|
|
7
|
-
|
|
8
|
-
const LOCAL_MARKDOWN_LINK = /\[([^\]\n]+)\]\(([^\s)]+\.md(?:#[^\s)]*)?)\)/g;
|
|
9
|
-
|
|
10
|
-
/** Keep repository-relative documentation links usable on addressed pages. */
|
|
11
|
-
export function documentationLinksPass({ config, book = false }) {
|
|
12
|
-
const root = config.paths.content;
|
|
13
|
-
const cache = new Map();
|
|
14
|
-
return {
|
|
15
|
-
beforeLinks(text, page) {
|
|
16
|
-
return text.replace(LOCAL_MARKDOWN_LINK, (whole, label, target) => {
|
|
17
|
-
const [fileName, fragment] = target.split("#", 2);
|
|
18
|
-
const file = path.resolve(path.dirname(page.file), fileName);
|
|
19
|
-
if (!file.startsWith(`${root}${path.sep}`) || !fs.existsSync(file)) return whole;
|
|
20
|
-
let address = cache.get(file);
|
|
21
|
-
if (!address) {
|
|
22
|
-
const { frontmatter } = parseMarkdownFile(file);
|
|
23
|
-
if (!frontmatter?.type || !frontmatter?.shortcode) return whole;
|
|
24
|
-
address = `/${config.contentPackage}/${frontmatter.type}-${frontmatter.shortcode}/`;
|
|
25
|
-
cache.set(file, address);
|
|
26
|
-
}
|
|
27
|
-
return `[${label}](${address}${fragment && !book ? `#${fragment}` : ""})`;
|
|
28
|
-
});
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
}
|