@heroiclands/package-build 22.27.0 → 23.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/bin/content-commands.mjs +53 -5
- package/ci/publish-npm.mjs +49 -0
- package/docs/api.md +11 -11
- package/docs/authoring/first-note.md +1 -1
- package/docs/content-format.md +2 -0
- package/docs/guides/site.md +4 -4
- package/docs/reference/format-details.md +70 -32
- package/docs/reference/note-types.md +5 -1
- package/docs/reference/theme-pages.md +32 -1
- package/engine/content-format.yaml +5 -0
- package/engine/folder-notes.mjs +2 -3
- package/engine/index.mjs +1 -1
- package/engine/literature-notes.mjs +133 -0
- package/engine/literature-works.mjs +153 -0
- package/engine/metadata-index.mjs +8 -0
- package/engine/note-vocabulary.mjs +15 -9
- package/engine/site-build.mjs +16 -1
- package/engine/stub-lint.mjs +26 -64
- package/githooks/pre-push +1 -1
- package/hugo-theme/layouts/partials/literature-works.html +29 -0
- package/hugo-theme/layouts/partials/page-body.html +1 -0
- package/package.json +3 -1
- package/sohl/default-item-art.mjs +6 -6
- package/types/engine/literature-notes.d.mts +29 -0
- package/types/engine/literature-works.d.mts +68 -0
- package/types/engine/note-vocabulary.d.mts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 23.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
Every page a work of literature names in its subjects lists that work under **In song and story**, with its form beside the title, including works from a package this one depends on.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
Affiliations, skills, attributes, mysteries, mystical abilities and miscellaneous gear without their own art show SoHL's default icons again instead of a broken image.
|
|
12
|
+
|
|
13
|
+
Folder notes need only a nonempty body: a short line or placeholder no longer warns or fails, with or without `draft`. An empty folder body is still an error.
|
|
14
|
+
|
|
15
|
+
Lore notes can be typed `literature` for epics, sagas, poems, legends and other works a people tells, sings or writes. A literature note may name its people, its form, its subjects and its language, and each appears in the note's infobox, with the people, subjects and language as links.
|
|
16
|
+
|
|
17
|
+
## 22.28.0
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
Notes now require a nonempty body, including drafts, folders and homepages. Short written notes still warn unless tagged as drafts.
|
|
22
|
+
|
|
3
23
|
## 22.27.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
package/bin/content-commands.mjs
CHANGED
|
@@ -226,7 +226,12 @@ const SHIPPED_ITEM_FIELDS = { sohl: ITEM_FIELDS, hm3: HM3_ITEM_FIELDS };
|
|
|
226
226
|
/** Register the content operations on the package command line. */
|
|
227
227
|
export function registerContentCommands(cli) {
|
|
228
228
|
return cli
|
|
229
|
-
.command(
|
|
229
|
+
.command(
|
|
230
|
+
withIndexPreflight(
|
|
231
|
+
withBodyPreflight(packageCommand(), (_config, argv) => argv.action === "compile"),
|
|
232
|
+
(_config, argv) => argv.action === "compile",
|
|
233
|
+
),
|
|
234
|
+
)
|
|
230
235
|
.command(depsCommand())
|
|
231
236
|
.command(docsCommand())
|
|
232
237
|
.command(withIndexPreflight(lintCommand()))
|
|
@@ -235,11 +240,17 @@ export function registerContentCommands(cli) {
|
|
|
235
240
|
.command(withIndexPreflight(linksCommand()))
|
|
236
241
|
.command(formatCommand())
|
|
237
242
|
.command(markdownCommand())
|
|
238
|
-
.command(
|
|
239
|
-
|
|
243
|
+
.command(
|
|
244
|
+
withBodyPreflight(
|
|
245
|
+
contentIndexCommand(),
|
|
246
|
+
() => true,
|
|
247
|
+
(config, argv) => argv.root || config.paths.content,
|
|
248
|
+
),
|
|
249
|
+
)
|
|
250
|
+
.command(withIndexPreflight(withBodyPreflight(siteCommand()), publishesContentPages))
|
|
240
251
|
.command(
|
|
241
252
|
withIndexPreflight(
|
|
242
|
-
pdfCommand(),
|
|
253
|
+
withBodyPreflight(pdfCommand(), (config) => Boolean(config.pdf)),
|
|
243
254
|
(config) => config.pdf && publishesContentPages(config),
|
|
244
255
|
),
|
|
245
256
|
)
|
|
@@ -380,6 +391,43 @@ function proseCommand() {
|
|
|
380
391
|
};
|
|
381
392
|
}
|
|
382
393
|
|
|
394
|
+
/** Refuse invalid note bodies before emitting metadata, packs, pages or a book. */
|
|
395
|
+
function withBodyPreflight(
|
|
396
|
+
command,
|
|
397
|
+
shouldCheck = () => true,
|
|
398
|
+
contentRoot = (config) => config.paths.content,
|
|
399
|
+
) {
|
|
400
|
+
const handler = command.handler;
|
|
401
|
+
return {
|
|
402
|
+
...command,
|
|
403
|
+
handler: async (argv) => {
|
|
404
|
+
try {
|
|
405
|
+
const config = loadPackConfig();
|
|
406
|
+
if (shouldCheck(config, argv)) {
|
|
407
|
+
const contentBase = contentRoot(config, argv);
|
|
408
|
+
if (!fs.existsSync(contentBase)) return handler(argv);
|
|
409
|
+
const records = indexRecordsFor({ config, contentBase });
|
|
410
|
+
const { findings } = lintNoteStates(contentBase, {
|
|
411
|
+
records,
|
|
412
|
+
contentPackage: config.contentPackage,
|
|
413
|
+
});
|
|
414
|
+
const errors = findings.filter((finding) => finding.severity === "error");
|
|
415
|
+
if (errors.length) {
|
|
416
|
+
for (const finding of errors) emitDiagnostic(finding);
|
|
417
|
+
process.exitCode = 1;
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
} catch (err) {
|
|
422
|
+
reportFailure(err);
|
|
423
|
+
process.exitCode = 1;
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
return handler(argv);
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
383
431
|
/** Check declared dependency indexes before an index-consuming command does any work. */
|
|
384
432
|
function withIndexPreflight(command, shouldCheck = () => true) {
|
|
385
433
|
const handler = command.handler;
|
|
@@ -1167,7 +1215,7 @@ function lintCommand() {
|
|
|
1167
1215
|
skipDirectories: config.skipDirectories,
|
|
1168
1216
|
});
|
|
1169
1217
|
|
|
1170
|
-
// Whether every
|
|
1218
|
+
// Whether every note has a body, and how
|
|
1171
1219
|
// finished the tree is. The counts and the oldest drafts are
|
|
1172
1220
|
// prose rather than findings: a note nobody has finished is not
|
|
1173
1221
|
// wrong, and 464 warnings a reader cannot clear is how a report
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
* Publish this package to npm, with provenance wherever it can be generated.
|
|
16
|
+
*
|
|
17
|
+
* `publishConfig.provenance` is `true`, and npm refuses a publish with
|
|
18
|
+
* `Automatic provenance generation not supported for provider: null` anywhere
|
|
19
|
+
* that cannot mint a Sigstore identity. That is a CI runner holding an OIDC
|
|
20
|
+
* token: GitHub Actions with `id-token: write`, which exposes
|
|
21
|
+
* `ACTIONS_ID_TOKEN_REQUEST_URL`, or GitLab CI with `SIGSTORE_ID_TOKEN`.
|
|
22
|
+
* Everywhere else — a desk — this passes `--provenance=false`, so the publish
|
|
23
|
+
* goes ahead unattested rather than failing.
|
|
24
|
+
*
|
|
25
|
+
* Arguments after `--` reach `npm publish` unchanged, so
|
|
26
|
+
* `npm run deploy:npm -- --dry-run` shows the tarball without publishing it.
|
|
27
|
+
* `prepack` builds the declarations and the asset index on the way.
|
|
28
|
+
*
|
|
29
|
+
* Not a `package-build` subcommand: it publishes this package and no other.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { spawnSync } from "node:child_process";
|
|
33
|
+
|
|
34
|
+
const env = process.env;
|
|
35
|
+
const provenance =
|
|
36
|
+
(env.GITHUB_ACTIONS === "true" && Boolean(env.ACTIONS_ID_TOKEN_REQUEST_URL)) ||
|
|
37
|
+
(env.GITLAB_CI === "true" && Boolean(env.SIGSTORE_ID_TOKEN));
|
|
38
|
+
|
|
39
|
+
if (!provenance) {
|
|
40
|
+
console.log("deploy:npm: no OIDC identity here; publishing without provenance.");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const args = ["publish", ...(provenance ? ["--provenance"] : ["--provenance=false"])];
|
|
44
|
+
const result = spawnSync("npm", [...args, ...process.argv.slice(2)], {
|
|
45
|
+
stdio: "inherit",
|
|
46
|
+
shell: process.platform === "win32",
|
|
47
|
+
});
|
|
48
|
+
if (result.error) throw result.error;
|
|
49
|
+
process.exit(result.status ?? 1);
|
package/docs/api.md
CHANGED
|
@@ -441,16 +441,16 @@ Drawing a declared infobox: HTML for separate Foundry pages appended after autho
|
|
|
441
441
|
|
|
442
442
|
The three states of a note — **stub**, **draft**, **full** — and the one observable fact they rest on. A note whose body is empty is a stub: it carries its facts into the index and is queryable there, and it has no page, no address and no link target. Nothing is stored: the ladder is derived once in the SQL `entries` view and no note can assert its own state.
|
|
443
443
|
|
|
444
|
-
| Export | Signature | Returns | Use it when
|
|
445
|
-
| --------------------- | ------------------------------------------- | --------------------------------------- |
|
|
446
|
-
| `NOTE_STATES` | `const NOTE_STATES` | `ReadonlyArray<string>` | naming the three states in ladder order
|
|
447
|
-
| `isEmptyBody` | `isEmptyBody(body)` | `boolean` | asking the exact question: is everything after the closing frontmatter fence whitespace?
|
|
448
|
-
| `isStubbableType` | `isStubbableType(type, vocabulary)` | `boolean` | asking whether an empty body suppresses this type's page; `folder` and `homepage`
|
|
449
|
-
| `isStubNote` | `isStubNote(frontmatter, body, vocabulary)` | `boolean` | deciding whether one note is a stub, which is what the index and the site build both ask
|
|
450
|
-
| `journalHasContent` | `journalHasContent(body)` | `boolean` | asking whether the JournalEntry a body compiles into would hold anything, which is what decides whether one is made at all
|
|
451
|
-
| `PLACEHOLDER_PHRASES` | `const PLACEHOLDER_PHRASES` | `ReadonlyArray<string>` | reading the phrases that stand in for prose nobody has written
|
|
452
|
-
| `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
|
|
453
|
-
| `bodyWordCount` | `bodyWordCount(body)` | `number` | counting rendered prose, with wikilinks as one word and closed dashes, slashes, and ellipses separating adjacent words
|
|
444
|
+
| Export | Signature | Returns | Use it when |
|
|
445
|
+
| --------------------- | ------------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
446
|
+
| `NOTE_STATES` | `const NOTE_STATES` | `ReadonlyArray<string>` | naming the three states in ladder order |
|
|
447
|
+
| `isEmptyBody` | `isEmptyBody(body)` | `boolean` | asking the exact question: is everything after the closing frontmatter fence whitespace? |
|
|
448
|
+
| `isStubbableType` | `isStubbableType(type, vocabulary)` | `boolean` | asking whether an empty body suppresses this type's page; `folder` and `homepage` retain their addresses in this classification, but still require bodies |
|
|
449
|
+
| `isStubNote` | `isStubNote(frontmatter, body, vocabulary)` | `boolean` | deciding whether one note is a stub, which is what the index and the site build both ask |
|
|
450
|
+
| `journalHasContent` | `journalHasContent(body)` | `boolean` | asking whether the JournalEntry a body compiles into would hold anything, which is what decides whether one is made at all |
|
|
451
|
+
| `PLACEHOLDER_PHRASES` | `const PLACEHOLDER_PHRASES` | `ReadonlyArray<string>` | reading the phrases that stand in for prose nobody has written |
|
|
452
|
+
| `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 |
|
|
453
|
+
| `bodyWordCount` | `bodyWordCount(body)` | `number` | counting rendered prose, with wikilinks as one word and closed dashes, slashes, and ellipses separating adjacent words |
|
|
454
454
|
|
|
455
455
|
### `engine.draftNotice`
|
|
456
456
|
|
|
@@ -471,7 +471,7 @@ What a draft note says about itself, in one sentence, on every surface it reache
|
|
|
471
471
|
|
|
472
472
|
### `engine.stubLint`
|
|
473
473
|
|
|
474
|
-
|
|
474
|
+
Every typed note must have a nonempty body. Empty and whitespace-only bodies are errors regardless of description or `draft` tag, including structural types such as `folder` and `homepage`. Placeholder-only bodies also produce errors. Nonempty bodies with fewer than 25 prose words produce warnings unless tagged `draft`. A `folder` note is held only to the nonempty rule: any body, placeholder or short, passes with or without `draft`. Untyped vault scaffolding is excluded. The lint, content-index, package compile, site and PDF commands enforce body errors before publishing. The state classification helpers still describe incomplete records for inspection; their classifications do not exempt notes from validation.
|
|
475
475
|
|
|
476
476
|
| Export | Signature | Returns | Use it when |
|
|
477
477
|
| ------------------ | ---------------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
@@ -45,4 +45,4 @@ The full Address is `<package>-note-lore-harbor`; `<package>` comes from `conten
|
|
|
45
45
|
|
|
46
46
|
Add `description` for a short page summary, `tags: [draft]` to mark unfinished prose, or `data` for fields the note's type permits. A system block such as `sohl:` or `hm3:` adds mechanics and can produce a Foundry Actor or Item. It does not replace the shared prose or make another JournalEntry. [Frontmatter](frontmatter.md) explains the allowed keys, and the [note-type reference](../reference/note-types.md) lists every type-specific field.
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
Every typed note must have a nonempty body after its frontmatter. An empty or whitespace-only body is an error, even with `tags: [draft]` or a description. This includes folder and homepage notes. A nonempty body with fewer than 25 prose words produces a warning unless the note has a `draft` tag. A folder note needs only a nonempty body: any content passes, with or without the tag. See [note states](../reference/format-details.md#three-states-of-a-note).
|
package/docs/content-format.md
CHANGED
|
@@ -9,6 +9,8 @@ subType: userguide
|
|
|
9
9
|
|
|
10
10
|
A content package begins with Markdown notes under `assets/content/`. Each note has YAML frontmatter describing its identity and data, followed by prose. Folder names beneath `assets/content/` are for authors; `type`, `subType`, and Addresses determine what the build creates.
|
|
11
11
|
|
|
12
|
+
Every typed note requires a nonempty body, including drafts, folders and homepages. An empty or whitespace-only body is an error. A nonempty body with fewer than 25 prose words warns unless tagged `draft`; the tag never exempts an empty body. A `folder` note needs only a nonempty body: any content passes, with or without `draft`.
|
|
13
|
+
|
|
12
14
|
Start with [your first note](authoring/first-note.md). It explains the smallest valid note and shows the JournalEntry, web page, book entry, and content-index record made from it. Then use the chapters below in the order your note needs them.
|
|
13
15
|
|
|
14
16
|
| Need | Read |
|
package/docs/guides/site.md
CHANGED
|
@@ -144,12 +144,12 @@ The build always writes `title`, `slug`, `url`, `kbfolder`, `package` and
|
|
|
144
144
|
note. `infoboxes` is assembled from the note's own fields, so no note authors
|
|
145
145
|
it and nothing in the note format accepts it.
|
|
146
146
|
|
|
147
|
-
The build writes `resolvedDates`, `related`, `contains`, `governed_by`, `governed_places
|
|
148
|
-
and `map` only when it has something to write. A page with no links either way
|
|
147
|
+
The build writes `resolvedDates`, `related`, `contains`, `governed_by`, `governed_places`,
|
|
148
|
+
`works` and `map` only when it has something to write. A page with no links either way
|
|
149
149
|
carries no `related`, and a place with no drawing carries no `map`.
|
|
150
150
|
|
|
151
|
-
An authored `aliases`, `related`, `contains`, `governed_by`, `governed_places
|
|
152
|
-
dropped. Each states a fact about the whole tree rather than about one note, so
|
|
151
|
+
An authored `aliases`, `related`, `contains`, `governed_by`, `governed_places`, `works` or
|
|
152
|
+
`map` is dropped. Each states a fact about the whole tree rather than about one note, so
|
|
153
153
|
the build replaces whatever a note wrote.
|
|
154
154
|
|
|
155
155
|
Artwork addresses are rewritten in place under `data`. A page's `data.icon`,
|
|
@@ -837,7 +837,7 @@ and the whole of it is the suffix.
|
|
|
837
837
|
|
|
838
838
|
```markdown
|
|
839
839
|
 <!-- this package's -->
|
|
840
|
-
 <!-- the sohl package's -->
|
|
841
841
|
```
|
|
842
842
|
|
|
843
843
|
The four forms, for a `harnensemble` note (the `harnensemble` content package
|
|
@@ -850,14 +850,14 @@ ships as the Foundry module `harn-ensemble`) writing `images/map.webp`:
|
|
|
850
850
|
| **Web** | `https://cdn.heroiclands.org/harnensemble/images/map.webp` |
|
|
851
851
|
| **Book** | `assets/images/map.webp`, staged beside the Typst source |
|
|
852
852
|
|
|
853
|
-
And for the same note writing `sohl/assets/icons/noun/
|
|
853
|
+
And for the same note writing `sohl/assets/icons/noun/shieldnn.svg`, a file the
|
|
854
854
|
system ships and this repository does not hold:
|
|
855
855
|
|
|
856
856
|
| Surface | Address |
|
|
857
857
|
| ----------- | ------------------------------------------------------------- |
|
|
858
|
-
| **Foundry** | `systems/sohl/assets/icons/noun/
|
|
859
|
-
| **Local** | `assets/icons/noun/
|
|
860
|
-
| **Web** | `https://cdn.heroiclands.org/sohl/icons/noun/
|
|
858
|
+
| **Foundry** | `systems/sohl/assets/icons/noun/shieldnn.svg` |
|
|
859
|
+
| **Local** | `assets/icons/noun/shieldnn.svg`, in the `sohl` repository |
|
|
860
|
+
| **Web** | `https://cdn.heroiclands.org/sohl/icons/noun/shieldnn.svg` |
|
|
861
861
|
| **Book** | not carried, unless a declared replacement carries it instead |
|
|
862
862
|
|
|
863
863
|
**The content package name and Foundry id are separate values.**
|
|
@@ -2006,8 +2006,20 @@ convention rather than declared in a field — see
|
|
|
2006
2006
|
|
|
2007
2007
|
### Three states of a note
|
|
2008
2008
|
|
|
2009
|
-
|
|
2010
|
-
|
|
2009
|
+
Every typed note requires a nonempty body after its closing frontmatter fence.
|
|
2010
|
+
An empty or whitespace-only body is an error, regardless of description or
|
|
2011
|
+
`tags: [draft]`. This includes `folder` and `homepage` notes. Untyped vault
|
|
2012
|
+
scaffolding is excluded. A nonempty body with fewer than 25 prose words warns
|
|
2013
|
+
unless tagged `draft`; the tag never exempts an empty body. A `folder` note is
|
|
2014
|
+
held only to the nonempty rule: any body, a placeholder or a single line
|
|
2015
|
+
included, passes with or without `draft`.
|
|
2016
|
+
|
|
2017
|
+
The `lint`, `content-index`, `package compile`, `site` and `pdf` commands enforce
|
|
2018
|
+
body errors. Write a short beginning and tag it `draft` when work is unfinished.
|
|
2019
|
+
|
|
2020
|
+
The internal classification below describes records, including incomplete notes
|
|
2021
|
+
that inspection APIs can still read. A **stub** is invalid authored content and
|
|
2022
|
+
must receive a body before these commands can publish it.
|
|
2011
2023
|
|
|
2012
2024
|
| State | Body | Marker | Page | Document | Link target |
|
|
2013
2025
|
| --------- | ------- | --------------- | ------------ | --------------------------------- | ----------- |
|
|
@@ -2039,13 +2051,13 @@ same.
|
|
|
2039
2051
|
|
|
2040
2052
|
| After the fence | Empty? | What happens |
|
|
2041
2053
|
| ----------------------- | ------ | --------------------------------------------------------------- |
|
|
2042
|
-
| Nothing, or blank lines | yes |
|
|
2054
|
+
| Nothing, or blank lines | yes | An error, with or without `draft` |
|
|
2043
2055
|
| An HTML comment | no | A note with a body that renders to nothing. The lint reports it |
|
|
2044
2056
|
| A horizontal rule | no | The same |
|
|
2045
2057
|
| A lone heading | no | The same |
|
|
2046
2058
|
| `_To be written._` | no | The same |
|
|
2047
2059
|
|
|
2048
|
-
An HTML comment is deliberately not empty
|
|
2060
|
+
An HTML comment is deliberately not empty, but an invisible marker does not supply prose. Replace placeholder-only content with written text. A `folder` note is the exception: any of these bodies satisfies it, and the lint reports nothing.
|
|
2049
2061
|
|
|
2050
2062
|
#### What a stub emits
|
|
2051
2063
|
|
|
@@ -2090,12 +2102,12 @@ What is suppressed with the page is the web address and the link target: a
|
|
|
2090
2102
|
wikilink into a stub is refused, and a table cell listing one prints its name as
|
|
2091
2103
|
plain text rather than as a link.
|
|
2092
2104
|
|
|
2093
|
-
####
|
|
2105
|
+
#### Structural classification does not exempt validation
|
|
2094
2106
|
|
|
2095
|
-
`folder`
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2107
|
+
A `folder` note's page is a generated section index, and the homepage is the
|
|
2108
|
+
site's front door. The classification helpers retain their addresses and report
|
|
2109
|
+
**full** even when their bodies are empty. Validation still rejects an empty
|
|
2110
|
+
body for both types.
|
|
2099
2111
|
|
|
2100
2112
|
Every type in the vocabulary declares whether an empty body suppresses its page,
|
|
2101
2113
|
so adding a type means answering the question rather than inheriting a default.
|
|
@@ -2784,6 +2796,15 @@ An Item can mark its readable description with `# ... {#description}`. That head
|
|
|
2784
2796
|
|
|
2785
2797
|
Generates a living (or undead, or spirit) being.
|
|
2786
2798
|
|
|
2799
|
+
**subType**:
|
|
2800
|
+
|
|
2801
|
+
- npc: A character meant to be encountered. It may describe a reusable template and need not be
|
|
2802
|
+
a singular individual.
|
|
2803
|
+
- character: A detailed, playable, singular individual.
|
|
2804
|
+
- creature: Any being that is neither a character nor an NPC.
|
|
2805
|
+
|
|
2806
|
+
See [a being's subtype describes the subject](#a-beings-subtype-describes-the-subject).
|
|
2807
|
+
|
|
2787
2808
|
| `data` property | Values | Description |
|
|
2788
2809
|
| --------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2789
2810
|
| `tokenIcon` | `Address` | What a token on the canvas wears — an `icon` address; defaults to `icon` |
|
|
@@ -3916,22 +3937,39 @@ the setting itself, rather than instructions or other apparatus for the GM.
|
|
|
3916
3937
|
which covers the _reckoning_ — a festival's date is calendar and the festival is not, and a
|
|
3917
3938
|
tournament is not a matter of time-reckoning at all — and from `culture`, which is a grouping
|
|
3918
3939
|
of people rather than an occasion they attend.
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
| `
|
|
3934
|
-
|
|
|
3940
|
+
- literature: A work a people composes and keeps — an epic, saga, poem, legend, hymn or
|
|
3941
|
+
chronicle, told, sung or written. Held apart from `history`, which states what happened: a
|
|
3942
|
+
legend is literature whether or not its events took place, and a chronicle is literature as a
|
|
3943
|
+
work even where its events are also history.
|
|
3944
|
+
|
|
3945
|
+
Each note a work names in `data.subjects` lists that work on its own site page, under **In
|
|
3946
|
+
song and story**, with the work's `form` beside its title; the subject note writes nothing.
|
|
3947
|
+
A work in a fetched index lists on this package's pages when that index carries its
|
|
3948
|
+
`subjects`.
|
|
3949
|
+
|
|
3950
|
+
Calendar fields are written only on a `calendar` note, and `culture`, `form`, `subjects` and
|
|
3951
|
+
`language` only on a `literature` note; written on any other lore subType, each is an error at
|
|
3952
|
+
its own key.
|
|
3953
|
+
|
|
3954
|
+
| `data` property | Values | Description |
|
|
3955
|
+
| ----------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
3956
|
+
| `epoch` | canonical `<year>.<day>` | The canonical day that equals calendar year 1, day 1 |
|
|
3957
|
+
| `months` | `{ name, abbreviation?, days }[]` | Ordered months; their days sum to the world's year |
|
|
3958
|
+
| `weekdays` | `{ name, abbreviation? }[]` | Ordered week days, indexed from zero; weeks run continuously |
|
|
3959
|
+
| `seasons` | `{ name, abbreviation?, start }[]` | Seasons starting on one-based days of year |
|
|
3960
|
+
| `namedDays` | `{ name, abbreviation?, day }[]` | Names assigned to one-based days of year |
|
|
3961
|
+
| `eras` | `{ shortcode, name, abbreviation?, marker?, proclaimedBy?, start, label? }[]` | Year counts; `start` is `null` or an in-calendar year |
|
|
3962
|
+
| `formats` | map of names to Calendaria patterns | Named date formats; `std` is the preferred default |
|
|
3963
|
+
| `culture` | `Address` | `literature` only: the people whose work it is, naming a lore note with `subType: culture` |
|
|
3964
|
+
| `form` | `string` | `literature` only: the kind of work in its people's own terms — `epic`, `saga`, `praise-song`, `elegy`. Free text |
|
|
3965
|
+
| `subjects` | `Address[]` | `literature` only: the beings, places, gods, events and other notes the work concerns |
|
|
3966
|
+
| `language` | `Address` | `literature` only: the tongue it is composed in, naming a skill note with `subType: language` |
|
|
3967
|
+
| `events` | `{ when, until?, recurs? }[]` | This note's dated occurrences, each with its own relationships and chronicle fields |
|
|
3968
|
+
| `events[].when` | a date, or `"0.<day>"` | The occurrence's anchor and first instance — required |
|
|
3969
|
+
| `events[].until` | a date | Where the occurrence ran to, or where a recurring series stopped |
|
|
3970
|
+
| `events[].recurs` | `{ every }` or `{ on }` | How further occurrences are found, absent for a one-time occurrence |
|
|
3971
|
+
| `events[].recurs.every` | whole number of years, 1 or more | A period counted on the canonical axis from `when` |
|
|
3972
|
+
| `events[].recurs.on` | list of dates, strictly increasing, each later than `when` | Recorded occurrences beyond the first, in place of a period |
|
|
3935
3973
|
|
|
3936
3974
|
`data.events` is available on every `lore` subType. Each entry's `kind`, `depth`, `summary`, `sources`, `standing`, `accounts`, `who`, `where`, `unresolved`, `follows` and `names` hold its chronicle detail; their shapes belong to whatever design governs chronicle records, and this format only asks that they sit inside an entry. The shared format checks `when`, `until` and `recurs`; a content package can check the rest. `events` itself does not appear as an infobox row, but a recurring entry's computed next occurrence does.
|
|
3937
3975
|
|
|
@@ -4735,8 +4773,8 @@ the map for the pack it is writing and falls back to `default` when there is no
|
|
|
4735
4773
|
such key, so a mistyped `journal:` files the folder wherever the default puts it
|
|
4736
4774
|
— exactly the hierarchy the key was written to override, and silently.
|
|
4737
4775
|
|
|
4738
|
-
**A folder note
|
|
4739
|
-
|
|
4776
|
+
**A folder note requires a body**, like every typed note. Its body does not
|
|
4777
|
+
produce a documentation journal or take part in `docEntryTypes`.
|
|
4740
4778
|
In a compiled Foundry journal, a wikilink to a folder prints its label as text:
|
|
4741
4779
|
the folder can materialise in several packs and has no single compendium UUID.
|
|
4742
4780
|
|
|
@@ -417,7 +417,7 @@ These fields are accepted by every note type. A field's value can still be irrel
|
|
|
417
417
|
|
|
418
418
|
### lore
|
|
419
419
|
|
|
420
|
-
**Subtypes:** `cosmology`, `deity`, `theology`, `arcana`, `spirit`, `economy`, `law`, `calendar`, `history`, `material`, `folk`, `culture`, `custom`, `bestiary`, `gathering`.
|
|
420
|
+
**Subtypes:** `cosmology`, `deity`, `theology`, `arcana`, `spirit`, `economy`, `law`, `calendar`, `history`, `material`, `folk`, `culture`, `custom`, `bestiary`, `gathering`, `literature`.
|
|
421
421
|
|
|
422
422
|
Lore records in-world knowledge. A `culture` describes a people; a `custom` describes how they practice a rite, observance, or usage. A `material` describes a physical constituent and its qualities, which may vary by region. See the [lore subtype definitions](format-details.md#type-lore) for the complete vocabulary.
|
|
423
423
|
|
|
@@ -430,6 +430,10 @@ Lore records in-world knowledge. A `culture` describes a people; a `custom` desc
|
|
|
430
430
|
| `data.namedDays` | list of `{ name, abbreviation?, day }` | Names assigned to particular days of the year. |
|
|
431
431
|
| `data.eras` | list of `{ shortcode, name, marker?, abbreviation?, proclaimedBy?, start, label? }` | The year-counts kept in this calendar. A marker names one era and uses these months. |
|
|
432
432
|
| `data.formats` | map of named Calendaria format strings | Named patterns for reading and writing this calendar's dates. |
|
|
433
|
+
| `data.culture` | an Address | The people whose work this is, as a culture lore note. |
|
|
434
|
+
| `data.form` | string | The kind of work in its people's own terms — an epic, a saga, a praise-song, an elegy. Free text. |
|
|
435
|
+
| `data.subjects` | list of Addresses | The beings, places, gods, events and other notes the work concerns. |
|
|
436
|
+
| `data.language` | an Address | The tongue the work is composed in, as a language skill note. |
|
|
433
437
|
| `data.events` | list of `{ when, until?, recurs? }` | This note's dated occurrences — a founding once, an anniversary that recurs, or a list of recorded happenings. |
|
|
434
438
|
|
|
435
439
|
### map
|
|
@@ -14,7 +14,7 @@ map, search, and the markup an image or a figure renders as — and names the
|
|
|
14
14
|
classes a consumer styles against.
|
|
15
15
|
|
|
16
16
|
**None of the sections below is an authoring instruction.** `infoboxes`,
|
|
17
|
-
`related`, `contains`, `governed_by`, `governed_places`, `map`, `package`, `slug`, `url`
|
|
17
|
+
`related`, `contains`, `governed_by`, `governed_places`, `works`, `map`, `package`, `slug`, `url`
|
|
18
18
|
and `title` cannot be written by a note at all: a note's top-level region is
|
|
19
19
|
closed and does not admit them, so each arrives already resolved by the time
|
|
20
20
|
the theme sees it. Read this page when a panel is missing or a class needs
|
|
@@ -215,6 +215,37 @@ uses, so a region with forty settlements stays scannable.
|
|
|
215
215
|
A page whose front matter declares none of `contains`, `governed_by` or
|
|
216
216
|
`governed_places` renders no card at all.
|
|
217
217
|
|
|
218
|
+
## The "In song and story" card
|
|
219
|
+
|
|
220
|
+
`partials/literature-works.html` renders a second card directly below the
|
|
221
|
+
government card, listing the works of literature that name the page's subject.
|
|
222
|
+
The build derives `works` from every `literature` lore note's `data.subjects`,
|
|
223
|
+
including works a fetched index carries, so no note writes it:
|
|
224
|
+
|
|
225
|
+
```yaml
|
|
226
|
+
---
|
|
227
|
+
works: [{ title: …, url: …, form: … }] # works naming this page, sorted by title
|
|
228
|
+
---
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`form` is the work's `data.form`, printed in parentheses beside its title, and is
|
|
232
|
+
absent when the work states none. A work naming one subject twice lists once, and
|
|
233
|
+
a work naming itself is not listed on its own page. An entry's `url` is absent
|
|
234
|
+
for a work that publishes no page, which renders as plain text through
|
|
235
|
+
`partials/holdings/entry.html`. A page no work names carries no `works` and
|
|
236
|
+
renders no card.
|
|
237
|
+
|
|
238
|
+
The card reuses the government card's classes, so a consumer styling one styles
|
|
239
|
+
both; `.literature-works` on the `<section>` tells them apart.
|
|
240
|
+
|
|
241
|
+
| class | drawn as |
|
|
242
|
+
| ----------------------- | --------------------------------- |
|
|
243
|
+
| `.literature-works` | the card, beside `.holdings` |
|
|
244
|
+
| `.holdings-table-label` | "In song and story" |
|
|
245
|
+
| `.holdings-table-grid` | the grid of works |
|
|
246
|
+
| `.holdings-entry-name` | a work's title, linked or plain |
|
|
247
|
+
| `.holdings-entry-kind` | the work's form, beside its title |
|
|
248
|
+
|
|
218
249
|
## The place map
|
|
219
250
|
|
|
220
251
|
A place page with `map: from-<shortcode>.svg` displays a **From here** panel
|
|
@@ -241,6 +241,10 @@ types:
|
|
|
241
241
|
- namedDays
|
|
242
242
|
- eras
|
|
243
243
|
- formats
|
|
244
|
+
- culture
|
|
245
|
+
- form
|
|
246
|
+
- subjects
|
|
247
|
+
- language
|
|
244
248
|
- events
|
|
245
249
|
subTypes:
|
|
246
250
|
- cosmology
|
|
@@ -258,6 +262,7 @@ types:
|
|
|
258
262
|
- custom
|
|
259
263
|
- bestiary
|
|
260
264
|
- gathering
|
|
265
|
+
- literature
|
|
261
266
|
map:
|
|
262
267
|
data:
|
|
263
268
|
- scene
|
package/engine/folder-notes.mjs
CHANGED
|
@@ -42,9 +42,8 @@
|
|
|
42
42
|
* because a `Folder` is a core Foundry document like a `JournalEntry` or a
|
|
43
43
|
* `Scene`, not a system's.
|
|
44
44
|
*
|
|
45
|
-
* **A folder note
|
|
46
|
-
* no documentation journal and takes no part in `docEntryTypes
|
|
47
|
-
* open decision, settled the way it recommended.
|
|
45
|
+
* **A folder note requires a body** under author validation. That body creates
|
|
46
|
+
* no documentation journal and takes no part in `docEntryTypes`.
|
|
48
47
|
*
|
|
49
48
|
* @module
|
|
50
49
|
*/
|
package/engine/index.mjs
CHANGED
|
@@ -93,7 +93,7 @@ export * as noteState from "./note-state.mjs";
|
|
|
93
93
|
/** What a draft note says about itself, in one sentence, on every surface. */
|
|
94
94
|
export * as draftNotice from "./draft-notice.mjs";
|
|
95
95
|
|
|
96
|
-
/**
|
|
96
|
+
/** Require written bodies on typed notes and report how finished a tree is. */
|
|
97
97
|
export * as stubLint from "./stub-lint.mjs";
|
|
98
98
|
|
|
99
99
|
/** What a place is next to and reachable from: the closed sets, and the checks. */
|