@heroiclands/package-build 22.5.1 → 22.7.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 +21 -0
- package/CONTENT.md +24 -7
- package/docs/api.md +20 -0
- package/docs/content-format.md +129 -27
- package/engine/actor-compiler.mjs +12 -0
- package/engine/base-compiler.mjs +51 -36
- package/engine/draft-notice.mjs +175 -0
- package/engine/foundry-entries.mjs +25 -7
- package/engine/generate.mjs +81 -10
- package/engine/index.mjs +3 -0
- package/engine/infobox-registry.mjs +14 -13
- package/engine/item-compiler.mjs +12 -0
- package/engine/item-docs.mjs +9 -8
- package/engine/journals.mjs +23 -4
- package/engine/note-claims.mjs +98 -2
- package/engine/pdf-build.mjs +8 -1
- package/engine/pdf-render.mjs +6 -0
- package/engine/subtype-registry.mjs +42 -5
- package/hm3/actors.mjs +5 -1
- package/package.json +1 -1
- package/sohl/actors.mjs +6 -1
- package/types/engine/actor-compiler.d.mts +11 -0
- package/types/engine/base-compiler.d.mts +46 -16
- package/types/engine/draft-notice.d.mts +77 -0
- package/types/engine/generate.d.mts +29 -0
- package/types/engine/index.d.mts +1 -0
- package/types/engine/infobox-registry.d.mts +11 -12
- package/types/engine/item-compiler.d.mts +11 -0
- package/types/engine/item-docs.d.mts +9 -8
- package/types/engine/journals.d.mts +7 -1
- package/types/engine/note-claims.d.mts +6 -1
- package/types/engine/subtype-registry.d.mts +32 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 22.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
**Draft notes say so.** A note tagged `draft` states that it is unfinished and
|
|
8
|
+
that nothing in it is settled, at the head of its compendium entry, on the
|
|
9
|
+
character sheet a referee opens, and on its page in the book. A finished note
|
|
10
|
+
carries nothing.
|
|
11
|
+
|
|
12
|
+
## 22.6.0
|
|
13
|
+
|
|
14
|
+
### Minor Changes
|
|
15
|
+
|
|
16
|
+
**Compendium content**
|
|
17
|
+
|
|
18
|
+
- A person or thing with no game-system data no longer becomes a blank
|
|
19
|
+
compendium entry — the note still publishes its page and its journal, so
|
|
20
|
+
somebody can be written without being statted.
|
|
21
|
+
- A package carrying data for a game system it ships no compendium for is now
|
|
22
|
+
told so, rather than having it quietly dropped.
|
|
23
|
+
|
|
3
24
|
## 22.5.1
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/CONTENT.md
CHANGED
|
@@ -867,7 +867,12 @@ has no art.
|
|
|
867
867
|
A note is **system-agnostic**. The only system-specific things it carries are
|
|
868
868
|
the properties named after a system, and one note may carry more than one — a
|
|
869
869
|
`being` in `harn-ensemble` compiles into a SoHL `being` _and_ an HM3
|
|
870
|
-
`character`.
|
|
870
|
+
`character`.
|
|
871
|
+
|
|
872
|
+
**The block is what makes a game document.** A note carrying none produces no
|
|
873
|
+
Actor and no Item at all, in any package; its prose still compiles into a page,
|
|
874
|
+
a PDF page and a JournalEntry, so a person can be written without being statted.
|
|
875
|
+
Within a system's block:
|
|
871
876
|
|
|
872
877
|
| property | maps to |
|
|
873
878
|
| ------------------ | ------------------------------------------------------------- |
|
|
@@ -1006,12 +1011,19 @@ hold this package at a build that does not write it. A subtree the schema
|
|
|
1006
1011
|
declares but describes no further — a discriminated `TypedSchemaField`, stored
|
|
1007
1012
|
flat — is left alone rather than reported wholesale.
|
|
1008
1013
|
|
|
1009
|
-
**
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1014
|
+
**An Actor or Item pass takes only notes carrying the block it reads.** A note
|
|
1015
|
+
that says nothing about a system has no system data, and compiling it would emit
|
|
1016
|
+
a hollow document — a subtype, and none of the fields the subtype exists for. So
|
|
1017
|
+
a note carrying no block for any system produces no Actor and no Item anywhere,
|
|
1018
|
+
and is passed over in silence; its prose still compiles into a page, a PDF page
|
|
1019
|
+
and a JournalEntry. The question is asked of the pass's own system rather than
|
|
1020
|
+
of the pack's `system:`: a pack declaring none is compiled by the fallback pass,
|
|
1021
|
+
which reads one block like any other. A pass whose document is not system data
|
|
1022
|
+
at all — journals, macros, scenes — is not subject to the rule.
|
|
1023
|
+
|
|
1024
|
+
**A block for a system no pack of the class compiles is an error**, reported
|
|
1025
|
+
once per note per system with the position of the block key, because the
|
|
1026
|
+
authored data is discarded either way.
|
|
1015
1027
|
|
|
1016
1028
|
**`(type, shortcode)` resolves inside one system's catalogue.** A being names its
|
|
1017
1029
|
embedded items by address and never by pack, so the Item packs are read as one
|
|
@@ -1101,6 +1113,11 @@ page, no address and no link target. A note with a body and the `draft` tag is a
|
|
|
1101
1113
|
**draft**, and one with a body and no tag is **full**.
|
|
1102
1114
|
`docs/content-format.md` states the rule; this is what the lint does with it.
|
|
1103
1115
|
|
|
1116
|
+
A draft says so on every surface it reaches — its journal entry, the Actor a
|
|
1117
|
+
being compiles into, its page in the book — in one sentence at the head of the
|
|
1118
|
+
entry, above the infobox and the prose. A stub says nothing anywhere, because it
|
|
1119
|
+
publishes nothing to say it on.
|
|
1120
|
+
|
|
1104
1121
|
**A document that would be empty is not created**, and the index then names no
|
|
1105
1122
|
artefact — `uuid`, `path` and `address` are `null`. An unwritten note still
|
|
1106
1123
|
compiles every document derived from `data:`, so an arcane talent is still its
|
package/docs/api.md
CHANGED
|
@@ -427,6 +427,23 @@ The three states of a note — **stub**, **draft**, **full** — and the one obs
|
|
|
427
427
|
| `placeholderBody` | `placeholderBody(body)` | `Array<{phrase: string, line: number}>` | finding a body that reduces to nothing but placeholders, which is an abandoned draft rather than a stub |
|
|
428
428
|
| `bodyWordCount` | `bodyWordCount(body)` | `number` | counting the prose a reader meets, with a wikilink counting as the one word it renders |
|
|
429
429
|
|
|
430
|
+
### `engine.draftNotice`
|
|
431
|
+
|
|
432
|
+
What a draft note says about itself, in one sentence, on every surface it reaches. The words are stated once and each renderer composes its own markup around them, so a reader meeting the note on a page, in a compendium and in the book meets one statement. The mark is a notice rather than a hazard, and the book draws it rather than setting a glyph, because a face missing one is silent in Typst.
|
|
433
|
+
|
|
434
|
+
| Export | Signature | Returns | Use it when |
|
|
435
|
+
| ------------------------- | ------------------------------------ | -------- | ---------------------------------------------------------------------------------- |
|
|
436
|
+
| `DRAFT_NOTICE` | `const DRAFT_NOTICE` | `string` | reading the sentence itself, which is the floor every surface degrades to |
|
|
437
|
+
| `DRAFT_NOTICE_LABEL` | `const DRAFT_NOTICE_LABEL` | `string` | reading the label that opens the notice |
|
|
438
|
+
| `DRAFT_NOTICE_ICON` | `const DRAFT_NOTICE_ICON` | `string` | naming the Font Awesome mark the HTML surfaces draw |
|
|
439
|
+
| `DRAFT_NOTICE_CLASS` | `const DRAFT_NOTICE_CLASS` | `string` | styling the HTML notice from a package that wants to |
|
|
440
|
+
| `BOOK_DRAFT_NOTICE` | `const BOOK_DRAFT_NOTICE` | `string` | naming the Typst function the book's notice calls |
|
|
441
|
+
| `draftNoticeHtml` | `draftNoticeHtml()` | `string` | drawing the notice for a JournalEntry page or an Actor's prose field |
|
|
442
|
+
| `draftNoticeFor` | `draftNoticeFor(frontmatter)` | `string` | asking whether a note has earned a notice, which is the one place the tag is asked |
|
|
443
|
+
| `withDraftNotice` | `withDraftNotice(frontmatter, html)` | `string` | leading a system's own prose field with the notice where one is due |
|
|
444
|
+
| `draftNoticeTypst` | `draftNoticeTypst()` | `string` | drawing the notice for a leaf of the book |
|
|
445
|
+
| `bookDraftNoticePreamble` | `bookDraftNoticePreamble()` | `string` | emitting the Typst definition the book's notice is drawn by |
|
|
446
|
+
|
|
430
447
|
### `engine.stubLint`
|
|
431
448
|
|
|
432
449
|
An empty body must be deliberate. Four rules hold the state honest — a stub states what it is, a stub is not tagged `draft`, a body that renders to nothing is an abandoned draft, and a short unmarked body is asked whether it forgot its marker — and two reports say what the rules cannot decide.
|
|
@@ -627,6 +644,8 @@ Which note-type → document-subtype maps this toolchain ships, and the two ques
|
|
|
627
644
|
| `KNOWN_DOCUMENT_SUBTYPE_MAPS` | `const KNOWN_DOCUMENT_SUBTYPE_MAPS` | — | The note-type → document-subtype maps this toolchain ships. |
|
|
628
645
|
| `ACTOR_TYPES` | `const ACTOR_TYPES` | — | Every note type any shipped map compiles into an **Actor**. |
|
|
629
646
|
| `DEFAULT_DOCUMENT_SUBTYPES` | `const DEFAULT_DOCUMENT_SUBTYPES` | — | The map a pack declaring no `system:` compiles its documents against. |
|
|
647
|
+
| `SHIPPED_SYSTEMS` | `const SHIPPED_SYSTEMS` | — | Every system this toolchain ships a map for, by id. |
|
|
648
|
+
| `SYSTEM_DOCUMENT_CLASSES` | `const SYSTEM_DOCUMENT_CLASSES` | — | The Foundry document classes a system compiles its own data into. |
|
|
630
649
|
| `subtypeMapFor` | `function subtypeMapFor(system)` | {import("./document-subtypes.mjs").DocumentSubtypeMap\|undefined} Its map, or `undefined` where this toolchain ships none for it. | The map one system ships, by its id. |
|
|
631
650
|
| `schemaSubtypeOf` | `function schemaSubtypeOf(system, type)` | {string} The document subtype to look up. | The document subtype a note type compiles into for one system — the translation the _schema_ check needs, and the reason it needs one. |
|
|
632
651
|
|
|
@@ -1197,6 +1216,7 @@ Pack JSON generation — in-repo Markdown → per-entry JSON (build-only). Reads
|
|
|
1197
1216
|
|
|
1198
1217
|
| Export | Signature | Returns | Use it when |
|
|
1199
1218
|
| ----------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
1219
|
+
| `systemCompilers` | `function systemCompilers(passes)` | {Readonly<Record<string, Readonly<Record<string, Function>>>>} System id → document class → pass. | The system-specific compilers, derived from the passes themselves. |
|
|
1200
1220
|
| `compilerFor` | `function compilerFor(docType, system` | {Function\|undefined} The compiler class, or `undefined` for a document type nothing here compiles — which {@link generatePack} reports rather than defaulting past. | The compiler class a pack of one document type and one system gets. |
|
|
1201
1221
|
| `emittedArtFor` | `function emittedArtFor(type)` | {{document: string\|null, art: readonly string[]}\|null} What the type compiles into and the art it carries there, or `null` where no claim can be made — a retired type, which is reported as retired instead. | The art fields a note of one content type reaches its document through, and the document it reaches. |
|
|
1202
1222
|
| `packJsonDir` | `const packJsonDir` | {string} The pack's JSON directory. | Root of the build-only JSON tree for one pack. |
|
package/docs/content-format.md
CHANGED
|
@@ -197,13 +197,14 @@ _Not available_ on every affiliation page would suggest a gap in the note when
|
|
|
197
197
|
the truth is about the system's scope.
|
|
198
198
|
|
|
199
199
|
Whether a document was produced is the compile's own answer rather than a
|
|
200
|
-
reading of the frontmatter.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
200
|
+
reading of the frontmatter. Three things must hold: the note must carry that
|
|
201
|
+
system's block, its type must map to the system, and it must route to a pack of
|
|
202
|
+
the document class that map names. The block is the first of them because a
|
|
203
|
+
system block is what makes a game document — see
|
|
204
|
+
[A system block is what makes a game document](#a-system-block-is-what-makes-a-game-document).
|
|
205
|
+
A pack declaring **no** system is compiled by the fallback pass, which reads one
|
|
206
|
+
system's block and no other, so the question is the same one whether or not the
|
|
207
|
+
pack answered it.
|
|
207
208
|
|
|
208
209
|
#### Four section layouts
|
|
209
210
|
|
|
@@ -319,16 +320,51 @@ is declared, so a near miss is a finding that names what you probably meant.
|
|
|
319
320
|
| **being kind** | `being` | `character`, `creature` |
|
|
320
321
|
| **state** | any | `draft` |
|
|
321
322
|
|
|
322
|
-
**`draft` is the one tag either build reads.**
|
|
323
|
-
link
|
|
323
|
+
**`draft` is the one tag either build reads.** It does two things and nothing
|
|
324
|
+
else: a link _into_ a draft note renders marked, and the note itself states that
|
|
325
|
+
it is unfinished wherever a reader meets it.
|
|
326
|
+
|
|
327
|
+
A link into it renders as
|
|
324
328
|
`<span class="sohl-draft-link" title="Draft — not yet written">…</span>` in a
|
|
325
329
|
compiled journal and on the website alike, with the appearance supplied by the
|
|
326
|
-
consuming system's stylesheet or the site theme.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
330
|
+
consuming system's stylesheet or the site theme.
|
|
331
|
+
|
|
332
|
+
The note's own statement is the **draft notice**, and it reads the same on every
|
|
333
|
+
surface:
|
|
334
|
+
|
|
335
|
+
> **Draft.** This entry is unfinished. What it states may change, and nothing in
|
|
336
|
+
> it is settled.
|
|
337
|
+
|
|
338
|
+
It leads the entry — above the infobox and above the prose — because a reader
|
|
339
|
+
deciding whether to rely on an entry has to be told before they read it. Where
|
|
340
|
+
it lands:
|
|
341
|
+
|
|
342
|
+
| Surface | What carries it |
|
|
343
|
+
| ------------ | -------------------------------------------------------------------- |
|
|
344
|
+
| JournalEntry | a `blockquote.sohl-draft-notice` at the head of the first page |
|
|
345
|
+
| Actor | the dossier the sheet draws — SoHL's `dossier`, HM3's `biography` |
|
|
346
|
+
| The book | a drawn mark and a left rule between the entry's plate and its panel |
|
|
347
|
+
| The website | the theme's own notice, above the infobox rail and the prose |
|
|
348
|
+
|
|
349
|
+
**An Actor carries it once.** A being's prose reaches two fields, and a document
|
|
350
|
+
saying the same thing twice teaches a reader to skip it — a printed sheet draws
|
|
351
|
+
both in turn — so the dossier carries the notice and the appearance is
|
|
352
|
+
untouched. The two systems pair by the authored section each field reads rather
|
|
353
|
+
than by its name, so `dossier` and `biography` carry it while `appearance` and
|
|
354
|
+
`description` do not.
|
|
355
|
+
|
|
356
|
+
**The mark is a notice, not a hazard** — `fa-circle-exclamation`, which Foundry
|
|
357
|
+
bundles, so a journal page carries it with no module stylesheet loaded. The book
|
|
358
|
+
draws its own, because a consumer declaring no icon family is the ordinary case
|
|
359
|
+
and a glyph a face lacks is silent in Typst. Stripped of mark and rule the
|
|
360
|
+
sentence still states the whole thing, which is the floor every surface is built
|
|
361
|
+
to.
|
|
362
|
+
|
|
363
|
+
**Nothing else changes**: the note compiles, validates, publishes and resolves
|
|
364
|
+
exactly as any other, and it stays in the packs, in the link manifest and on the
|
|
365
|
+
site. That is what separates the tag from the retired `draft:` field, whose whole
|
|
366
|
+
effect was to move a note from published to unresolvable without saying so — and
|
|
367
|
+
which is refused, by name, if you write it.
|
|
332
368
|
|
|
333
369
|
**The group's scope is what makes the check work.** A group names the types it
|
|
334
370
|
applies to, and a place's kinds are only ever checked on a place. Without that
|
|
@@ -788,8 +824,9 @@ _compendium_ receives its document.
|
|
|
788
824
|
— an item's prose compiling into a `JournalEntry` of its own — is not what the
|
|
789
825
|
author was addressing, and is routed by the pass that produces it.
|
|
790
826
|
|
|
791
|
-
**`<system>.pack`
|
|
792
|
-
|
|
827
|
+
**`<system>.pack` names the pack for one system's document.** Routing is per
|
|
828
|
+
system: each block the note carries sends its own document where that block
|
|
829
|
+
says, and a note carrying two blocks may place them in two packs.
|
|
793
830
|
|
|
794
831
|
```yaml
|
|
795
832
|
pack: items-sohl
|
|
@@ -797,9 +834,22 @@ hm3:
|
|
|
797
834
|
pack: items-hm3
|
|
798
835
|
```
|
|
799
836
|
|
|
800
|
-
**Unstated,
|
|
801
|
-
|
|
802
|
-
|
|
837
|
+
**Unstated, a system's document goes to that system's default pack of its
|
|
838
|
+
class** — the pack of the class declaring that `system:`, or, where none does,
|
|
839
|
+
the pack of the class marked `default: true`. Where nothing answers, the build
|
|
840
|
+
refuses rather than guessing, and names the candidates.
|
|
841
|
+
|
|
842
|
+
**`pack` and `packFolder` are different questions.** `pack` names _which
|
|
843
|
+
compendium_ receives a document; `packFolder` names the folder note whose
|
|
844
|
+
address the document is filed under _within_ whichever compendium that is. They
|
|
845
|
+
compose, and both are read per system, so a note may place its two systems'
|
|
846
|
+
documents in different packs and different folders:
|
|
847
|
+
|
|
848
|
+
```yaml
|
|
849
|
+
sohl:
|
|
850
|
+
pack: characteristics
|
|
851
|
+
packFolder: miscgear
|
|
852
|
+
```
|
|
803
853
|
|
|
804
854
|
**`pack: none` compiles the note into no document.** The note is walked,
|
|
805
855
|
published as a page, present in the content index with an address and no
|
|
@@ -1662,11 +1712,15 @@ convention rather than declared in a field — see
|
|
|
1662
1712
|
A note has three states, and two of them are nothing but conventions the format
|
|
1663
1713
|
already carried.
|
|
1664
1714
|
|
|
1665
|
-
| State | Body | Marker | Page
|
|
1666
|
-
| --------- | ------- | --------------- |
|
|
1667
|
-
| **stub** | empty | none | no
|
|
1668
|
-
| **draft** | written | `tags: [draft]` | yes,
|
|
1669
|
-
| **full** | written | none | yes
|
|
1715
|
+
| State | Body | Marker | Page | Document | Link target |
|
|
1716
|
+
| --------- | ------- | --------------- | ------------ | --------------------------------- | ----------- |
|
|
1717
|
+
| **stub** | empty | none | no | yes, where one would not be empty | no |
|
|
1718
|
+
| **draft** | written | `tags: [draft]` | yes, noticed | yes, noticed | yes |
|
|
1719
|
+
| **full** | written | none | yes | yes | yes |
|
|
1720
|
+
|
|
1721
|
+
"Document" here is the document a note's frontmatter earns. A system's Actor or
|
|
1722
|
+
Item is earned by that system's block, in every state —
|
|
1723
|
+
[a system block is what makes a game document](#a-system-block-is-what-makes-a-game-document).
|
|
1670
1724
|
|
|
1671
1725
|
**Full is the absence of everything.** It is the default and carries no marker,
|
|
1672
1726
|
which is right: the overwhelming majority of a finished corpus should say
|
|
@@ -1763,8 +1817,8 @@ cannot be is the target of a wikilink, because a wikilink points at a page.
|
|
|
1763
1817
|
|
|
1764
1818
|
Note types fall into two groups, and only the first has a mapping table.
|
|
1765
1819
|
|
|
1766
|
-
**Types
|
|
1767
|
-
system, described by the tables further down.
|
|
1820
|
+
**Types that _may_ produce a system document** — a SoHL or HM3 Actor or Item,
|
|
1821
|
+
one per system, described by the tables further down.
|
|
1768
1822
|
|
|
1769
1823
|
**Types producing only a core document** — `lore`, `place`, `scenario`, `doc`,
|
|
1770
1824
|
`map`, `macro` and `homepage`. A core document is one Foundry defines rather than
|
|
@@ -1780,6 +1834,54 @@ having nothing worth summarising.
|
|
|
1780
1834
|
Every note in **both** groups still produces its JournalEntry and its web page.
|
|
1781
1835
|
The difference is only whether a system Actor or Item is created as well.
|
|
1782
1836
|
|
|
1837
|
+
#### A system block is what makes a game document
|
|
1838
|
+
|
|
1839
|
+
> **A note produces a system's Actor or Item only where it carries that
|
|
1840
|
+
> system's block.** A note carrying no block for any system produces no Actor
|
|
1841
|
+
> and no Item anywhere, whatever its packs declare.
|
|
1842
|
+
|
|
1843
|
+
The block is not a place to put optional detail — it is the statement that this
|
|
1844
|
+
person or thing is meant to be played. Its absence is a statement too, and it is
|
|
1845
|
+
made silently: the note is passed over by the Actor and Item passes exactly as
|
|
1846
|
+
any note they do not own, with no finding.
|
|
1847
|
+
|
|
1848
|
+
**Prose is untouched by this.** A body with content still compiles into one
|
|
1849
|
+
JournalEntry, one web page and one PDF page, however many blocks the note
|
|
1850
|
+
carries and whether or not it carries any; an empty body still compiles none of
|
|
1851
|
+
the three. So a note has three states, and each reaches the next by one edit in
|
|
1852
|
+
one file, with its address, its id, its index record and every link into it
|
|
1853
|
+
unchanged:
|
|
1854
|
+
|
|
1855
|
+
| The note carries | Produces |
|
|
1856
|
+
| -------------------------- | ---------------------------------------------- |
|
|
1857
|
+
| no block, empty body | an index record — a name that can be queried |
|
|
1858
|
+
| no block, a written body | a page, a PDF page and a JournalEntry |
|
|
1859
|
+
| a block, a written body | all of those, plus that system's Actor or Item |
|
|
1860
|
+
| two blocks, a written body | one document per system, and one of each other |
|
|
1861
|
+
|
|
1862
|
+
Write the person; add a system block when someone will need a token. A setting's
|
|
1863
|
+
cast is mostly people nobody rolls dice against, and a note about one of them
|
|
1864
|
+
carries no block and occupies no slot in a compendium a referee browses.
|
|
1865
|
+
|
|
1866
|
+
**A note carrying no document publishes no UUID for one.** Its address, its
|
|
1867
|
+
page and its documentation journal are published as they are for any other note,
|
|
1868
|
+
and the `foundry` entry that would name an Actor or an Item is absent — the same
|
|
1869
|
+
statement [`pack: none`](#the-pack-a-note-compiles-into) makes about a pack,
|
|
1870
|
+
made about a system.
|
|
1871
|
+
|
|
1872
|
+
**A block for a system this package ships no pack for is an error.** The
|
|
1873
|
+
authored data would be discarded either way, so the build says so, once per note
|
|
1874
|
+
per system, naming the system and the document class:
|
|
1875
|
+
|
|
1876
|
+
```
|
|
1877
|
+
assets/content/People/Aelric.md:7:1: error: carries a `hm3:` block, and no Actor pack in this package compiles hm3 documents, so the block is discarded — declare `system: hm3` on one of the `packs:`, or remove the block.
|
|
1878
|
+
```
|
|
1879
|
+
|
|
1880
|
+
A pack declaring no `system:` is compiled by the fallback pass, which reads one
|
|
1881
|
+
system's block — so a systemless Actor pack serves that system and not every
|
|
1882
|
+
system a note might write. A package shipping for a second system declares
|
|
1883
|
+
`system:` on a pack of each class it compiles.
|
|
1884
|
+
|
|
1783
1885
|
That includes actors. A being producing its Actor and nothing else would be
|
|
1784
1886
|
the one system-bearing note with no address at `none`, leaving a prose link
|
|
1785
1887
|
naming it nowhere to land. It carries a documentation journal like every other
|
|
@@ -501,6 +501,18 @@ export class SystemActorCompiler extends BasePackCompiler {
|
|
|
501
501
|
*/
|
|
502
502
|
static requiresSystemBlock = true;
|
|
503
503
|
|
|
504
|
+
/**
|
|
505
|
+
* The Foundry document class this pass writes.
|
|
506
|
+
*
|
|
507
|
+
* Declared by the class that does the writing, so the table pairing a
|
|
508
|
+
* system with its passes is derived from the passes themselves rather than
|
|
509
|
+
* written out a second time — see
|
|
510
|
+
* {@link module:engine/generate.systemCompilers}.
|
|
511
|
+
*
|
|
512
|
+
* @type {string}
|
|
513
|
+
*/
|
|
514
|
+
static documentClass = "Actor";
|
|
515
|
+
|
|
504
516
|
/**
|
|
505
517
|
* The note-type → document-subtype map this pass compiles against.
|
|
506
518
|
*
|
package/engine/base-compiler.mjs
CHANGED
|
@@ -183,13 +183,16 @@ export class BasePackCompiler {
|
|
|
183
183
|
|
|
184
184
|
/**
|
|
185
185
|
* Whether this pass's document **is** a system's data, and therefore takes
|
|
186
|
-
* only notes that carry
|
|
186
|
+
* only notes that carry the block this pass reads.
|
|
187
187
|
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* nothing to say about
|
|
192
|
-
* document**: a subtype, and none of the fields the subtype exists
|
|
188
|
+
* The question is asked of {@link BasePackCompiler#system} — the pass's own
|
|
189
|
+
* system, which an Actor or Item pass always has — rather than of the
|
|
190
|
+
* pack's `system:`, which a pack need not declare. A note carrying no such
|
|
191
|
+
* block has nothing to say about that system, so compiling it would emit a
|
|
192
|
+
* **hollow document**: a subtype, and none of the fields the subtype exists
|
|
193
|
+
* for. `harn-ensemble` ships an `actors-hm3` and an `actors-sohl` from one
|
|
194
|
+
* tree, and `sohl-thalorna` ships one `actors` declaring nothing; the rule
|
|
195
|
+
* is the same in both, because it is the pass that reads a block.
|
|
193
196
|
*
|
|
194
197
|
* False by default, because most passes write documents that are not
|
|
195
198
|
* system data at all. A JournalEntry of prose is the same document under
|
|
@@ -428,44 +431,56 @@ export class BasePackCompiler {
|
|
|
428
431
|
);
|
|
429
432
|
}
|
|
430
433
|
|
|
434
|
+
/**
|
|
435
|
+
* The system block this pass reads, where it reads one.
|
|
436
|
+
*
|
|
437
|
+
* `undefined` on the base class, because most passes read none: a
|
|
438
|
+
* JournalEntry, a Macro, a Scene and an Adventure are Foundry's documents
|
|
439
|
+
* rather than any system's. The Actor and Item passes answer with the
|
|
440
|
+
* block their {@link SystemActorCompiler.documentSubtypes} names, which is
|
|
441
|
+
* the same declaration that says which notes they claim and what each
|
|
442
|
+
* becomes.
|
|
443
|
+
*
|
|
444
|
+
* Stated here so {@link BasePackCompiler#eligibleFor} is total: a pass
|
|
445
|
+
* declaring `requiresSystemBlock` without a system reads no block, and a
|
|
446
|
+
* note can carry nothing for it.
|
|
447
|
+
*
|
|
448
|
+
* @returns {string|undefined} The block key, or `undefined`.
|
|
449
|
+
*/
|
|
450
|
+
get system() {
|
|
451
|
+
return undefined;
|
|
452
|
+
}
|
|
453
|
+
|
|
431
454
|
/**
|
|
432
455
|
* Whether a claimed, routed note may become this pack's document at all.
|
|
433
456
|
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
* documents
|
|
457
|
+
* **A system block is what makes a game document.** A pass whose document
|
|
458
|
+
* *is* a system's data compiles a note only where the note carries the
|
|
459
|
+
* block that pass reads, so a note saying nothing about any system becomes
|
|
460
|
+
* no Actor and no Item anywhere — its prose still compiles into a page, a
|
|
461
|
+
* PDF leaf and a JournalEntry, which are documents no system defines.
|
|
462
|
+
*
|
|
463
|
+
* Asked of this pass's own {@link BasePackCompiler#system}, never of the
|
|
464
|
+
* pack's `system:`. A pack declaring none is a single-system convenience
|
|
465
|
+
* compiled by the fallback pass for its document type, and that pass still
|
|
466
|
+
* reads exactly one block — so the note-side question is the same one
|
|
467
|
+
* whether or not the pack answered it.
|
|
468
|
+
*
|
|
469
|
+
* A note this pass has nothing to compile is **skipped**, counted like any
|
|
470
|
+
* other note the pack does not own. A deliberately unstatted person is the
|
|
471
|
+
* ordinary case, and a block deleted by accident shows in the diff that
|
|
472
|
+
* deleted it; what would otherwise be discarded in silence is the block
|
|
473
|
+
* written for a system no pack of the class serves, and that is reported by
|
|
474
|
+
* {@link module:engine/note-claims.unclaimedNoteFindings} rather than here,
|
|
475
|
+
* where a pass can see only its own half of the configuration.
|
|
439
476
|
*
|
|
440
477
|
* @param {object} fm - The note's frontmatter.
|
|
441
478
|
* @returns {boolean} True when the note may be compiled here; `false` when
|
|
442
|
-
* it
|
|
443
|
-
* skipped as quietly as any other note this pack does not own.
|
|
444
|
-
* @throws {Error} When this pack's system is absent from the note and no
|
|
445
|
-
* other configured system claims it. The error carries a `position` where
|
|
446
|
-
* the note's own file can be read.
|
|
479
|
+
* it carries nothing for this pass's system.
|
|
447
480
|
*/
|
|
448
481
|
eligibleFor(fm) {
|
|
449
|
-
if (!this.constructor.requiresSystemBlock
|
|
450
|
-
|
|
451
|
-
// Another system's pack of this document type will claim it. A
|
|
452
|
-
// note carrying only `hm3:` routes here because this pack is the
|
|
453
|
-
// *default* of its document type, and defaults are declared per type
|
|
454
|
-
// rather than per system — but it is not an incomplete note, it is
|
|
455
|
-
// another pass's. The single-system case is untouched: with no second
|
|
456
|
-
// system declared there is nothing for this to find, and the error
|
|
457
|
-
// below still fires.
|
|
458
|
-
const claimant = (this.router?.systemsOfType?.(this.docType) ?? []).find(
|
|
459
|
-
(system) => system !== this.packSystem && carriesSystemBlock(fm, system),
|
|
460
|
-
);
|
|
461
|
-
if (claimant) return false;
|
|
462
|
-
const label = fm?.name?.full ?? fm?.shortcode ?? fm?.id ?? "this note";
|
|
463
|
-
throw new Error(
|
|
464
|
-
`${label} carries no \`${this.packSystem}:\` block, so it has no ` +
|
|
465
|
-
`${this.packSystem} data to compile — but it routes to pack ` +
|
|
466
|
-
`"${this.packName}", which declares \`system: ${this.packSystem}\`. ` +
|
|
467
|
-
`Add the block, or route the note to a pack of another system.`,
|
|
468
|
-
);
|
|
482
|
+
if (!this.constructor.requiresSystemBlock) return true;
|
|
483
|
+
return carriesSystemBlock(fm, this.system);
|
|
469
484
|
}
|
|
470
485
|
|
|
471
486
|
/**
|
|
@@ -0,0 +1,175 @@
|
|
|
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
|
+
* **What a draft note says about itself**, in one sentence, on every surface it
|
|
16
|
+
* reaches.
|
|
17
|
+
*
|
|
18
|
+
* A note tagged `draft` compiles, validates, publishes and resolves like any
|
|
19
|
+
* other. What separates it from a finished note is a judgement only its author
|
|
20
|
+
* holds — the content is not settled — and a reader who is not told assumes the
|
|
21
|
+
* opposite. So each surface states it where the reader cannot miss it: at the
|
|
22
|
+
* head of the entry, above the infobox and above the prose.
|
|
23
|
+
*
|
|
24
|
+
* **The words are the same everywhere and are stated once here.** A reader who
|
|
25
|
+
* meets the same note on a page, in a compendium and in the book should meet
|
|
26
|
+
* one statement, not three that agree approximately. {@link DRAFT_NOTICE} is
|
|
27
|
+
* that statement; every renderer below composes its own markup around it and
|
|
28
|
+
* changes none of it.
|
|
29
|
+
*
|
|
30
|
+
* **A label alone will not do.** "Draft" names the writing, so it reads as
|
|
31
|
+
* rough prose; what has to land is that the facts may change. The label and the
|
|
32
|
+
* sentence are one unit, and the sentence carries the meaning by itself — which
|
|
33
|
+
* is the floor each surface is built to, because each of them can lose
|
|
34
|
+
* everything around it.
|
|
35
|
+
*
|
|
36
|
+
* ## What each surface can rely on
|
|
37
|
+
*
|
|
38
|
+
* - **Foundry** renders a JournalEntry's own HTML inside a sheet, so the floor
|
|
39
|
+
* is what a journal page carries with no module and no system stylesheet
|
|
40
|
+
* loaded. Foundry draws a left rule on a `blockquote` in any application
|
|
41
|
+
* window and bundles Font Awesome, so the rule and the mark are the host's
|
|
42
|
+
* and nothing here ships CSS. The class is a hook for a package that wants
|
|
43
|
+
* one, never a requirement.
|
|
44
|
+
* - **The book** sets Typst, where a glyph missing from the face is silent and
|
|
45
|
+
* a consumer declaring no icon family is the normal case — see {@link
|
|
46
|
+
* module:engine/content-icons}. So the mark is **drawn**, from two
|
|
47
|
+
* primitives, and cannot become a box.
|
|
48
|
+
* - **An Actor's prose** lands in a system's own sheet, in a field the system
|
|
49
|
+
* styles. It gets the same HTML the journal does; what it can rely on is the
|
|
50
|
+
* sentence.
|
|
51
|
+
*
|
|
52
|
+
* @module
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
import { isDraftNote } from "./note-vocabulary.mjs";
|
|
56
|
+
|
|
57
|
+
/** The word that opens the notice, and the only part a reader reads as a label. */
|
|
58
|
+
export const DRAFT_NOTICE_LABEL = "Draft.";
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The sentence, which states the fact and stops.
|
|
62
|
+
*
|
|
63
|
+
* It says the content may change rather than that the writing is rough,
|
|
64
|
+
* because that is the difference between a reader who treats the entry as
|
|
65
|
+
* provisional and one who builds on it. It gives no instruction: a notice that
|
|
66
|
+
* reaches one page in seven wears better as a statement than as a direction,
|
|
67
|
+
* and the fact carries the consequence for anyone who can act on it.
|
|
68
|
+
*
|
|
69
|
+
* @type {string}
|
|
70
|
+
*/
|
|
71
|
+
export const DRAFT_NOTICE =
|
|
72
|
+
"This entry is unfinished. What it states may change, and nothing in it is settled.";
|
|
73
|
+
|
|
74
|
+
/** The Font Awesome mark: a notice, not a hazard. */
|
|
75
|
+
export const DRAFT_NOTICE_ICON = "fa-circle-exclamation";
|
|
76
|
+
|
|
77
|
+
/** The class a package may style the HTML notice against. */
|
|
78
|
+
export const DRAFT_NOTICE_CLASS = "sohl-draft-notice";
|
|
79
|
+
|
|
80
|
+
/** The Typst function the book's notice calls. */
|
|
81
|
+
export const BOOK_DRAFT_NOTICE = "book-draft-notice";
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The notice as HTML, for a JournalEntry page and for an Actor's prose field.
|
|
85
|
+
*
|
|
86
|
+
* A `blockquote` because that is the element Foundry already sets apart —
|
|
87
|
+
* `body.game .app blockquote` carries a left rule and an indent — so the
|
|
88
|
+
* treatment survives with no stylesheet of this package's own. The mark is a
|
|
89
|
+
* Font Awesome element, which Foundry bundles; `aria-hidden` keeps it out of a
|
|
90
|
+
* screen reader, where the label reads it.
|
|
91
|
+
*
|
|
92
|
+
* @returns {string} The notice's HTML.
|
|
93
|
+
*/
|
|
94
|
+
export function draftNoticeHtml() {
|
|
95
|
+
return (
|
|
96
|
+
`<blockquote class="${DRAFT_NOTICE_CLASS}">` +
|
|
97
|
+
`<p><i class="fa-solid ${DRAFT_NOTICE_ICON}" aria-hidden="true"></i> ` +
|
|
98
|
+
`<strong>${DRAFT_NOTICE_LABEL}</strong> ${DRAFT_NOTICE}</p>` +
|
|
99
|
+
`</blockquote>`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The notice a note has earned, or nothing.
|
|
105
|
+
*
|
|
106
|
+
* The one place the tag is asked about on a compiled document, so the packs and
|
|
107
|
+
* the book cannot answer it differently. It reads {@link
|
|
108
|
+
* module:engine/note-vocabulary.isDraftNote}, which is the single reader of the
|
|
109
|
+
* tag itself.
|
|
110
|
+
*
|
|
111
|
+
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
112
|
+
* @returns {string} The notice's HTML, or `""` for a note that is settled.
|
|
113
|
+
*/
|
|
114
|
+
export function draftNoticeFor(fm) {
|
|
115
|
+
return isDraftNote(fm) ? draftNoticeHtml() : "";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Prose led by the notice, where the note is a draft.
|
|
120
|
+
*
|
|
121
|
+
* **Which field a system leads is the system's decision, and it makes it
|
|
122
|
+
* once.** A referee at the table opens an actor sheet rather than the
|
|
123
|
+
* documentation beside it, so the prose a sheet draws says it too — on one
|
|
124
|
+
* field, since a document showing the same statement twice teaches a reader to
|
|
125
|
+
* skip it. Both systems put it on the dossier: SoHL's `dossier` and HM3's
|
|
126
|
+
* `biography` read the same authored section, so the notice lands on the same
|
|
127
|
+
* prose whichever system compiles it.
|
|
128
|
+
*
|
|
129
|
+
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
130
|
+
* @param {string} html - The rendered prose.
|
|
131
|
+
* @returns {string} The prose, led by the notice where one is due.
|
|
132
|
+
*/
|
|
133
|
+
export function withDraftNotice(fm, html) {
|
|
134
|
+
const notice = draftNoticeFor(fm);
|
|
135
|
+
if (!notice) return html;
|
|
136
|
+
return html ? `${notice}\n${html}` : notice;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The `#let` the book's notice is drawn by.
|
|
141
|
+
*
|
|
142
|
+
* Emitted with the rest of the book's preamble. The mark is a circle with an
|
|
143
|
+
* exclamation set inside it: two primitives and the face already setting the
|
|
144
|
+
* page, so there is no font to resolve and nothing that can come out as a box.
|
|
145
|
+
*
|
|
146
|
+
* @returns {string} The Typst definition.
|
|
147
|
+
*/
|
|
148
|
+
export function bookDraftNoticePreamble() {
|
|
149
|
+
return (
|
|
150
|
+
`#let ${BOOK_DRAFT_NOTICE}(label, body) = block(\n` +
|
|
151
|
+
" width: 100%, above: 0.55em, below: 0.75em, breakable: false,\n" +
|
|
152
|
+
" inset: (left: 0.7em, top: 0.35em, bottom: 0.35em),\n" +
|
|
153
|
+
" stroke: (left: 1.6pt + book-faint))[\n" +
|
|
154
|
+
" #set par(justify: false, first-line-indent: 0em)\n" +
|
|
155
|
+
" #set text(size: 8.6pt, fill: book-faint)\n" +
|
|
156
|
+
" #box(baseline: 0.18em, circle(radius: 0.42em, stroke: 0.6pt + book-faint)[\n" +
|
|
157
|
+
' #align(center + horizon)[#text(size: 6.4pt, weight: "bold")[!]]\n' +
|
|
158
|
+
" ])\n" +
|
|
159
|
+
" #h(0.4em)\n" +
|
|
160
|
+
' #text(weight: "bold")[#label]\n' +
|
|
161
|
+
" #h(0.25em)\n" +
|
|
162
|
+
" #body\n" +
|
|
163
|
+
"]"
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The notice as Typst, for a leaf of the book.
|
|
169
|
+
*
|
|
170
|
+
* @returns {string} The call, ready to sit between the entry's plate and its
|
|
171
|
+
* infobox panel.
|
|
172
|
+
*/
|
|
173
|
+
export function draftNoticeTypst() {
|
|
174
|
+
return `#${BOOK_DRAFT_NOTICE}[${DRAFT_NOTICE_LABEL}][${DRAFT_NOTICE}]`;
|
|
175
|
+
}
|