@heroiclands/package-build 22.21.0 → 22.22.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 +191 -0
- package/assets/styles/content-base.css +162 -0
- package/bin/content-commands.mjs +26 -3
- package/bin/package-build.mjs +15 -2
- package/ci/content-format-reference.mjs +68 -1
- package/config.mjs +26 -0
- package/content-config.mjs +73 -4
- package/docs/api.md +36 -18
- package/docs/authoring/dates-and-calendars.md +4 -2
- package/docs/authoring/frontmatter.md +2 -2
- package/docs/authoring/links-and-markup.md +237 -28
- package/docs/configuration.md +27 -1
- package/docs/content-format.md +3 -1
- package/docs/guides/reference-pages.md +15 -0
- package/docs/reference/format-details.md +224 -46
- package/docs/reference/note-types.md +21 -4
- package/engine/actor-compiler.mjs +23 -0
- package/engine/anchored-sections.mjs +35 -4
- package/engine/anchors.mjs +10 -20
- package/engine/base-compiler.mjs +86 -21
- package/engine/base-styles.mjs +149 -0
- package/engine/bundles.mjs +0 -1
- package/engine/compendiums.mjs +8 -1
- package/engine/content-blocks.mjs +410 -0
- package/engine/content-captions.mjs +57 -8
- package/engine/content-footnotes.mjs +189 -0
- package/engine/content-format-check.mjs +15 -12
- package/engine/content-index.mjs +2 -2
- package/engine/content-links.mjs +14 -6
- package/engine/content-tables.mjs +45 -4
- package/engine/content-tasklists.mjs +189 -0
- package/engine/docs-checks.mjs +4 -4
- package/engine/extension-attributes.mjs +48 -2
- package/engine/frontmatter-lint.mjs +47 -64
- package/engine/frontmatter.mjs +3 -3
- package/engine/heading-attributes.mjs +308 -0
- package/engine/helpers.mjs +14 -13
- package/engine/homepage.mjs +8 -8
- package/engine/index.mjs +3 -0
- package/engine/infobox-render.mjs +15 -3
- package/engine/infobox.mjs +83 -8
- package/engine/journals.mjs +36 -17
- package/engine/macros.mjs +1 -1
- package/engine/markdown-expressions.mjs +178 -53
- package/engine/note-dates.mjs +49 -0
- package/engine/note-frontmatter.mjs +7 -16
- package/engine/note-vocabulary.mjs +172 -37
- package/engine/page-lists.mjs +358 -0
- package/engine/pdf-build.mjs +76 -42
- package/engine/pdf-render.mjs +422 -49
- package/engine/rank-ladder.mjs +198 -0
- package/engine/reckoning-markers.mjs +1 -1
- package/engine/retired-fields.mjs +8 -13
- package/engine/scenes.mjs +10 -3
- package/engine/site-build.mjs +41 -12
- package/engine/site-config.mjs +10 -1
- package/engine/site-index.mjs +18 -2
- package/engine/site-preview.mjs +18 -7
- package/engine/social-ties.mjs +52 -14
- package/engine/sql-tables.mjs +66 -9
- package/engine/standings.mjs +89 -6
- package/engine/system-block.mjs +12 -14
- package/engine/web-wikilinks.mjs +48 -1
- package/hm3/actors.mjs +6 -3
- package/manifest.mjs +13 -0
- package/package.json +1 -1
- package/sohl/actors.mjs +22 -99
- package/sohl/item-fields.mjs +4 -5
- package/sohl/items.mjs +1 -1
- package/sohl/note-schemas.mjs +11 -21
- package/types/config.d.mts +9 -0
- package/types/engine/actor-compiler.d.mts +11 -0
- package/types/engine/anchored-sections.d.mts +24 -0
- package/types/engine/base-compiler.d.mts +19 -0
- package/types/engine/base-styles.d.mts +93 -0
- package/types/engine/content-blocks.d.mts +87 -0
- package/types/engine/content-captions.d.mts +14 -1
- package/types/engine/content-footnotes.d.mts +73 -0
- package/types/engine/content-format-check.d.mts +8 -6
- package/types/engine/content-tables.d.mts +12 -4
- package/types/engine/content-tasklists.d.mts +72 -0
- package/types/engine/extension-attributes.d.mts +22 -1
- package/types/engine/frontmatter.d.mts +1 -1
- package/types/engine/heading-attributes.d.mts +174 -0
- package/types/engine/helpers.d.mts +7 -4
- package/types/engine/homepage.d.mts +7 -7
- package/types/engine/index.d.mts +1 -0
- package/types/engine/infobox-render.d.mts +6 -1
- package/types/engine/journals.d.mts +14 -7
- package/types/engine/markdown-expressions.d.mts +23 -0
- package/types/engine/note-frontmatter.d.mts +1 -4
- package/types/engine/note-vocabulary.d.mts +82 -6
- package/types/engine/page-lists.d.mts +126 -0
- package/types/engine/pdf-render.d.mts +72 -2
- package/types/engine/rank-ladder.d.mts +32 -0
- package/types/engine/retired-fields.d.mts +4 -6
- package/types/engine/site-build.d.mts +13 -1
- package/types/engine/site-config.d.mts +9 -0
- package/types/engine/site-index.d.mts +13 -1
- package/types/engine/site-preview.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +31 -7
- package/types/engine/standings.d.mts +21 -3
- package/types/engine/system-block.d.mts +11 -12
- package/types/engine/web-wikilinks.d.mts +3 -1
- package/types/hm3/actors.d.mts +3 -3
- package/types/sohl/actors.d.mts +6 -7
- package/engine/content-admonitions.mjs +0 -114
- package/engine/content-secrets.mjs +0 -112
- package/types/engine/content-admonitions.d.mts +0 -24
- package/types/engine/content-secrets.d.mts +0 -17
package/sohl/actors.mjs
CHANGED
|
@@ -18,10 +18,9 @@
|
|
|
18
18
|
* reference translation through the system's map, embedding and its stable ids,
|
|
19
19
|
* the anchored prose sections — lives in
|
|
20
20
|
* {@link module:engine/actor-compiler}, where a second system reaches it.
|
|
21
|
-
* What is left here is SoHL's data model: the
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* block itself.
|
|
21
|
+
* What is left here is SoHL's data model: the `sohl.items` frontmatter that
|
|
22
|
+
* becomes embedded documents, the opening mastery level a skill is baked with,
|
|
23
|
+
* and the `system` block itself.
|
|
25
24
|
*
|
|
26
25
|
* One content type today, and the actor subtype it produces is **declared**
|
|
27
26
|
* rather than assumed to be the same word: `sohl/document-subtypes.mjs` maps
|
|
@@ -29,10 +28,16 @@
|
|
|
29
28
|
* `ACTOR_VAULT_TYPE = "being"` here and emitted `type: "being"` several hundred
|
|
30
29
|
* lines below, which made the two vocabularies agree by coincidence.
|
|
31
30
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
31
|
+
* Each entry in `sohl.items` is resolved by `(type, shortcode)` and deep-merged
|
|
32
|
+
* with the entry's other properties, so an attribute carries its `scoreBase`
|
|
33
|
+
* and a skill its `masteryLevelBase` as that entry's own `system` overlay.
|
|
34
|
+
*
|
|
35
|
+
* **A being's own document fields are authored under `sohl.system`**, which
|
|
36
|
+
* {@link module:engine/system-block.mergeSystemData} writes at the DataModel's
|
|
37
|
+
* own paths: a being's body is `sohl.system.body`, with
|
|
38
|
+
* `sohl.system.currentMoveMedium` and `sohl.system.movementProfiles` beside it.
|
|
39
|
+
* So this pass writes no body itself, and an incorporeal being authors none and
|
|
40
|
+
* keeps the schema's empty one.
|
|
36
41
|
*
|
|
37
42
|
* **A membership is derived, never authored here.** `data.affiliations` names
|
|
38
43
|
* the bodies a being belongs to and the standing it holds in each, and each
|
|
@@ -105,60 +110,12 @@ function defaultActorImg(subType) {
|
|
|
105
110
|
return img;
|
|
106
111
|
}
|
|
107
112
|
|
|
108
|
-
/**
|
|
109
|
-
* Normalize a being's persisted `system.body` from a `sohl.body` block. The
|
|
110
|
-
* authoring frontmatter mirrors the schema field-for-field: `sohl.body` nests
|
|
111
|
-
* `structure` / `weight` / `reachBase` / `bodyScaleBase` / `personalFatigue`,
|
|
112
|
-
* exactly like `system.body`.
|
|
113
|
-
*/
|
|
114
|
-
function normalizeBody(bodyObj) {
|
|
115
|
-
const b = bodyObj && typeof bodyObj === "object" ? bodyObj : {};
|
|
116
|
-
const weight = b.weight || {};
|
|
117
|
-
return {
|
|
118
|
-
structure: b.structure ?? { parts: [], adjacent: [] },
|
|
119
|
-
weight: {
|
|
120
|
-
base: weight.base == null ? null : Number(weight.base),
|
|
121
|
-
calc: String(weight.calc ?? "0"),
|
|
122
|
-
},
|
|
123
|
-
reachBase: Number(b.reachBase ?? 0) || 0,
|
|
124
|
-
bodyScaleBase: Number(b.bodyScaleBase ?? 1) || 1,
|
|
125
|
-
personalFatigue: String(b.personalFatigue ?? "enc"),
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** Normalize per-medium movement profiles from a `sohl.movementProfiles` list. */
|
|
130
|
-
function normalizeMovementProfiles(list) {
|
|
131
|
-
return (Array.isArray(list) ? list : []).map((p) => ({
|
|
132
|
-
medium: String(p.medium ?? "terrestrial"),
|
|
133
|
-
feetPerRound: Number(p.feetPerRound ?? 0) || 0,
|
|
134
|
-
leaguesPerWatch: Number(p.leaguesPerWatch ?? 0) || 0,
|
|
135
|
-
encumbrance: String(p.encumbrance ?? "0"),
|
|
136
|
-
strMod: String(p.strMod ?? "0"),
|
|
137
|
-
disabled: Boolean(p.disabled ?? false),
|
|
138
|
-
}));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Extract a being's body (+ its movement) from a `sohl` block that mirrors the
|
|
143
|
-
* schema: `sohl.body` (nested → `system.body`) and the flat
|
|
144
|
-
* `sohl.currentMoveMedium` / `sohl.movementProfiles` (→ the base-actor movement
|
|
145
|
-
* fields; movement is a universal actor capability, not part of the body).
|
|
146
|
-
*/
|
|
147
|
-
function extractBodyAndMovement(fm) {
|
|
148
|
-
return {
|
|
149
|
-
body: normalizeBody(sohlField(fm, "body", {})),
|
|
150
|
-
currentMoveMedium: String(sohlField(fm, "currentMoveMedium", "none")),
|
|
151
|
-
movementProfiles: normalizeMovementProfiles(sohlField(fm, "movementProfiles", [])),
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
113
|
/**
|
|
156
114
|
* SoHL's Actor compile pass.
|
|
157
115
|
*
|
|
158
116
|
* Declares SoHL's note-type → document-subtype map and builds a `being` note's
|
|
159
|
-
* document: the
|
|
160
|
-
*
|
|
161
|
-
* Everything else is {@link module:engine/actor-compiler}'s.
|
|
117
|
+
* document: the embedded item documents the frontmatter names, and the `system`
|
|
118
|
+
* block. Everything else is {@link module:engine/actor-compiler}'s.
|
|
162
119
|
*/
|
|
163
120
|
export class Actors extends SystemActorCompiler {
|
|
164
121
|
/**
|
|
@@ -184,30 +141,12 @@ export class Actors extends SystemActorCompiler {
|
|
|
184
141
|
}
|
|
185
142
|
|
|
186
143
|
/**
|
|
187
|
-
* Build all embedded items for an actor: one per `sohl.
|
|
188
|
-
*
|
|
144
|
+
* Build all embedded items for an actor: one per `sohl.items` entry, plus
|
|
145
|
+
* one per body the being belongs to.
|
|
189
146
|
*/
|
|
190
147
|
buildEmbeddedItems(itemsMap, actorId, fm, ctx) {
|
|
191
148
|
const items = [];
|
|
192
149
|
|
|
193
|
-
const attributes = sohlField(fm, "attributes", null);
|
|
194
|
-
if (attributes && typeof attributes === "object") {
|
|
195
|
-
for (const [shortcode, value] of Object.entries(attributes)) {
|
|
196
|
-
const overlay = { system: { scoreBase: Number(value) || 0 } };
|
|
197
|
-
const embedded = this.resolveEmbedded(
|
|
198
|
-
itemsMap,
|
|
199
|
-
actorId,
|
|
200
|
-
"attribute",
|
|
201
|
-
shortcode,
|
|
202
|
-
overlay,
|
|
203
|
-
`attr:${shortcode}`,
|
|
204
|
-
ctx,
|
|
205
|
-
{ fmKey: "attributes" },
|
|
206
|
-
);
|
|
207
|
-
if (embedded) items.push(embedded);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
150
|
const sohlItems = sohlField(fm, "items", null);
|
|
212
151
|
if (Array.isArray(sohlItems)) {
|
|
213
152
|
sohlItems.forEach((entry, index) => {
|
|
@@ -333,8 +272,8 @@ export class Actors extends SystemActorCompiler {
|
|
|
333
272
|
*
|
|
334
273
|
* **The scores used are the ones just written.** `SkillLogic` resolves
|
|
335
274
|
* `attr.<code>` to an attribute's *effective* score, after active effects;
|
|
336
|
-
* all this pass has is the `scoreBase`
|
|
337
|
-
* compiled being carrying no attribute-altering effects the two agree,
|
|
275
|
+
* all this pass has is the `scoreBase` on each embedded attribute item. For
|
|
276
|
+
* a compiled being carrying no attribute-altering effects the two agree,
|
|
338
277
|
* which is every being in content today. One that did carry such an effect
|
|
339
278
|
* would bake a Skill Base its client then disagrees with — that is the
|
|
340
279
|
* limit of doing this at build time, and the point to revisit if it bites.
|
|
@@ -387,6 +326,9 @@ export class Actors extends SystemActorCompiler {
|
|
|
387
326
|
const { value: authoredFolder, isAddress } = folderField(fm);
|
|
388
327
|
const folder = this.folderResolver(authoredFolder, { isAddress });
|
|
389
328
|
|
|
329
|
+
this.checkAnchoredSection(body || "", "appearance", ctx);
|
|
330
|
+
this.checkAnchoredSection(body || "", "dossier", ctx);
|
|
331
|
+
|
|
390
332
|
const system = {
|
|
391
333
|
// The frontmatter shortcode is the actor's stable `(type, shortcode)`
|
|
392
334
|
// key — and, for a being that is an archetype, its archetype
|
|
@@ -411,25 +353,6 @@ export class Actors extends SystemActorCompiler {
|
|
|
411
353
|
dossier: withDraftNotice(fm, renderSection(body || "", "dossier")),
|
|
412
354
|
};
|
|
413
355
|
|
|
414
|
-
// Fill `system.body` (+ the base-actor movement fields) from the being's
|
|
415
|
-
// frontmatter, rather than embedding a corpus item. The `sohl`
|
|
416
|
-
// block mirrors `system` field-for-field: `sohl.body` nests the body
|
|
417
|
-
// (`structure` / `weight` / …), with `currentMoveMedium` /
|
|
418
|
-
// `movementProfiles` flat alongside it. An **incorporeal** being omits
|
|
419
|
-
// `sohl.body` and keeps the schema's empty body.
|
|
420
|
-
const bodyField = sohlField(fm, "body", null);
|
|
421
|
-
if (bodyField && typeof bodyField === "object") {
|
|
422
|
-
const bodyData = extractBodyAndMovement(fm);
|
|
423
|
-
system.body = bodyData.body;
|
|
424
|
-
system.currentMoveMedium = bodyData.currentMoveMedium;
|
|
425
|
-
system.movementProfiles = bodyData.movementProfiles;
|
|
426
|
-
} else if (bodyField != null) {
|
|
427
|
-
this.noteError(
|
|
428
|
-
`${ctx}: sohl.body must be an inline object (structure/weight/…), got ${typeof bodyField}`,
|
|
429
|
-
);
|
|
430
|
-
this.errorCount++;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
356
|
// Being-only combat grouping (mirrors `system.defaultCombatGroup`).
|
|
434
357
|
const defaultCombatGroup = sohlField(fm, "defaultCombatGroup", undefined);
|
|
435
358
|
if (defaultCombatGroup !== undefined) {
|
package/sohl/item-fields.mjs
CHANGED
|
@@ -391,11 +391,10 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
391
391
|
...STRING,
|
|
392
392
|
default: "",
|
|
393
393
|
topLevelMeans:
|
|
394
|
-
"
|
|
395
|
-
"
|
|
396
|
-
"
|
|
397
|
-
"
|
|
398
|
-
"`sohl.items`.",
|
|
394
|
+
"An office's style of address belongs to the membership that " +
|
|
395
|
+
"holds the office, so nothing outside the item supplies it. " +
|
|
396
|
+
"Author it at `sohl.system.title` on the item, or at the " +
|
|
397
|
+
"`system.title` of the entry in a being's `sohl.items`.",
|
|
399
398
|
describe: "The style of address the office carries.",
|
|
400
399
|
},
|
|
401
400
|
{
|
package/sohl/items.mjs
CHANGED
|
@@ -93,7 +93,7 @@ export class Items extends SystemItemCompiler {
|
|
|
93
93
|
// it — an undeclared `system` key is discarded at construction
|
|
94
94
|
// without a warning.
|
|
95
95
|
templatePriority: systemTemplatePriority(fm, label),
|
|
96
|
-
actionDefs:
|
|
96
|
+
actionDefs: [],
|
|
97
97
|
notes: "",
|
|
98
98
|
docHtml: description || "",
|
|
99
99
|
};
|
package/sohl/note-schemas.mjs
CHANGED
|
@@ -99,37 +99,27 @@ const MACRO_FIELDS = Object.freeze([
|
|
|
99
99
|
const VEHICLE_FIELDS = Object.freeze([]);
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
* A `being` note — an actor, with its
|
|
102
|
+
* A `being` note — an actor, with its embedded items and its combat grouping.
|
|
103
|
+
*
|
|
104
|
+
* **A being's own document fields are authored under `sohl.system`**, which the
|
|
105
|
+
* passthrough writes at the DataModel's own paths — a body and the movement that
|
|
106
|
+
* goes with it are `sohl.system.body`, `sohl.system.currentMoveMedium` and
|
|
107
|
+
* `sohl.system.movementProfiles`, written together — so this declaration states
|
|
108
|
+
* none of them. **Attributes and skills are embedded items**, each named by the
|
|
109
|
+
* catalogue entry it copies, so both are `sohl.items` entries with the score or
|
|
110
|
+
* the mastery level as that entry's own `system` overlay.
|
|
111
|
+
*
|
|
112
|
+
* What is left is the two keys the pass reads off the block itself.
|
|
103
113
|
*
|
|
104
114
|
* @type {readonly import("../engine/field-spec.mjs").FieldSpec[]}
|
|
105
115
|
*/
|
|
106
116
|
const BEING_FIELDS = Object.freeze([
|
|
107
|
-
{
|
|
108
|
-
name: "body",
|
|
109
|
-
...MAP,
|
|
110
|
-
describe: "The being's body structure, by part shortcode.",
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
name: "attributes",
|
|
114
|
-
...MAP,
|
|
115
|
-
describe: "Attribute scores, by attribute shortcode.",
|
|
116
|
-
},
|
|
117
117
|
{
|
|
118
118
|
name: "items",
|
|
119
119
|
...LIST,
|
|
120
120
|
describe:
|
|
121
121
|
"Items embedded on the being, each addressed by (type, shortcode) or carrying enough fields to stand alone.",
|
|
122
122
|
},
|
|
123
|
-
{
|
|
124
|
-
name: "currentMoveMedium",
|
|
125
|
-
...STRING,
|
|
126
|
-
describe: "Which medium the being is currently moving through.",
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
name: "movementProfiles",
|
|
130
|
-
...LIST,
|
|
131
|
-
describe: "Movement rates, one profile per medium.",
|
|
132
|
-
},
|
|
133
123
|
{
|
|
134
124
|
name: "defaultCombatGroup",
|
|
135
125
|
...AS_AUTHORED,
|
package/types/config.d.mts
CHANGED
|
@@ -102,6 +102,9 @@ export const DERIVED_MANIFEST_KEYS: Readonly<Record<string, string>>;
|
|
|
102
102
|
* @property {string} stageDir The staged package root, relative to
|
|
103
103
|
* `rootDir`. Every asset `to:` lands under it.
|
|
104
104
|
* @property {readonly Readonly<AssetSpec>[]} assets
|
|
105
|
+
* @property {boolean} baseStyles Whether the package takes the shared base
|
|
106
|
+
* stylesheet, staged into `stageDir` and
|
|
107
|
+
* named first in the manifest's `styles`.
|
|
105
108
|
* @property {Readonly<Record<string, unknown>>} manifest The manifest
|
|
106
109
|
* specification, emitted as declared.
|
|
107
110
|
* @property {string|null} manifestFlags Module to load a `flags` function
|
|
@@ -235,6 +238,12 @@ export type PackageBuildConfig = {
|
|
|
235
238
|
*/
|
|
236
239
|
stageDir: string;
|
|
237
240
|
assets: readonly Readonly<AssetSpec>[];
|
|
241
|
+
/**
|
|
242
|
+
* Whether the package takes the shared base
|
|
243
|
+
* stylesheet, staged into `stageDir` and
|
|
244
|
+
* named first in the manifest's `styles`.
|
|
245
|
+
*/
|
|
246
|
+
baseStyles: boolean;
|
|
238
247
|
/**
|
|
239
248
|
* The manifest
|
|
240
249
|
* specification, emitted as declared.
|
|
@@ -248,6 +248,17 @@ export class SystemActorCompiler extends BasePackCompiler {
|
|
|
248
248
|
img: string;
|
|
249
249
|
token: string;
|
|
250
250
|
};
|
|
251
|
+
/**
|
|
252
|
+
* Report an `{#appearance}` or `{#dossier}` anchor on a heading
|
|
253
|
+
* {@link extractAnchorSection} cannot read, rather than letting the field
|
|
254
|
+
* it feeds compile blank with nothing to say why.
|
|
255
|
+
*
|
|
256
|
+
* @param {string} body - The note body.
|
|
257
|
+
* @param {string} anchorId - `"appearance"` or `"dossier"`.
|
|
258
|
+
* @param {string} ctx - The actor's label, for the message.
|
|
259
|
+
* @returns {void}
|
|
260
|
+
*/
|
|
261
|
+
checkAnchoredSection(body: string, anchorId: string, ctx: string): void;
|
|
251
262
|
/** @type {readonly string[]} */
|
|
252
263
|
itemsSourceDirs: readonly string[];
|
|
253
264
|
foreignSourceDirs: any;
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* heading exists. Fenced code blocks are respected so `# foo` inside
|
|
7
7
|
* ``` blocks does not trigger a match.
|
|
8
8
|
*
|
|
9
|
+
* An anchor declared on a lower heading is found by {@link collectAnchors}
|
|
10
|
+
* but not by the `#\s+` match below, so it extracts as absent rather than as
|
|
11
|
+
* wrong — {@link misplacedAnchorSection} is the check a caller runs first to
|
|
12
|
+
* tell the two apart.
|
|
13
|
+
*
|
|
9
14
|
* @param {string} body - The note body.
|
|
10
15
|
* @param {string} anchorId - The anchor to find.
|
|
11
16
|
* @returns {string} The section's markdown, or "".
|
|
@@ -19,3 +24,22 @@ export function extractAnchorSection(body: string, anchorId: string): string;
|
|
|
19
24
|
* @returns {string} The rendered HTML, or "".
|
|
20
25
|
*/
|
|
21
26
|
export function renderSection(body: string, anchorId: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Where an `{#appearance}` or `{#dossier}` anchor sits on a heading below the
|
|
29
|
+
* top level, which {@link extractAnchorSection} cannot read.
|
|
30
|
+
*
|
|
31
|
+
* {@link module:engine/anchors.collectAnchors} finds the anchor at any
|
|
32
|
+
* level, because a lower heading legitimately starts a journal page. It does
|
|
33
|
+
* not feed an actor field: an H1-only extractor reads nothing from one, and a
|
|
34
|
+
* caller that trusts the empty result as "no such section" rather than
|
|
35
|
+
* "found, but unreadable" compiles a blank field with nothing to say why.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} body - The note body.
|
|
38
|
+
* @param {string} anchorId - The anchor to check, case-insensitively.
|
|
39
|
+
* @param {number} [bodyLine=1] - The 1-based file line the body starts on.
|
|
40
|
+
* @returns {{line: number}|null} The misplaced anchor's line, or `null` when
|
|
41
|
+
* it is absent or already on an H1.
|
|
42
|
+
*/
|
|
43
|
+
export function misplacedAnchorSection(body: string, anchorId: string, bodyLine?: number): {
|
|
44
|
+
line: number;
|
|
45
|
+
} | null;
|
|
@@ -378,6 +378,25 @@ export class BasePackCompiler {
|
|
|
378
378
|
linkIndex: any;
|
|
379
379
|
sqlTables: any;
|
|
380
380
|
worldPresent: unknown;
|
|
381
|
+
/**
|
|
382
|
+
* Report every finding {@link BasePackCompiler#convertBody} collected
|
|
383
|
+
* across its passes, and decline the note if there were any.
|
|
384
|
+
*
|
|
385
|
+
* Reported directly, one `noteError` per finding, rather than thrown as a
|
|
386
|
+
* single wrapped message: the site build and the book report the same way,
|
|
387
|
+
* each finding at its own line, and a note with three problems meets all
|
|
388
|
+
* three in the run that found them instead of one per rebuild. The thrown
|
|
389
|
+
* sentinel carries `alreadyReported` so the compile loop's generic catch
|
|
390
|
+
* does not add a fourth, vaguer line on top of the three already said.
|
|
391
|
+
*
|
|
392
|
+
* @param {readonly {message: string, line?: number, column?: number}[]} findings
|
|
393
|
+
* @throws {Error} When `findings` is non-empty, after reporting all of them.
|
|
394
|
+
*/
|
|
395
|
+
reportConvertBodyFindings(findings: readonly {
|
|
396
|
+
message: string;
|
|
397
|
+
line?: number;
|
|
398
|
+
column?: number;
|
|
399
|
+
}[]): void;
|
|
381
400
|
/**
|
|
382
401
|
* The body {@link BasePackCompiler#buildEntry} receives.
|
|
383
402
|
*
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cascade layer the sheet loads into, per package kind.
|
|
3
|
+
*
|
|
4
|
+
* Foundry declares its layer order in `css/foundry2.css`, ending
|
|
5
|
+
* `… layouts, system, modules, exceptions` — the eighth and ninth being, by its
|
|
6
|
+
* own comments, default game system styles and default module styles. That is
|
|
7
|
+
* what this sheet is, so it takes the layer its kind is given and is therefore
|
|
8
|
+
* the weakest thing a package can load:
|
|
9
|
+
*
|
|
10
|
+
* - **An unlayered package sheet beats it**, because an unlayered rule beats
|
|
11
|
+
* every layered one whatever its specificity.
|
|
12
|
+
* - **A package sheet naming any other layer beats it too**, because a layer
|
|
13
|
+
* Foundry's order does not mention is appended after the ones it does.
|
|
14
|
+
*
|
|
15
|
+
* So a package overrides the base sheet from its own, whichever way it declares
|
|
16
|
+
* one, and the override needs no `!important` and no specificity contest.
|
|
17
|
+
*
|
|
18
|
+
* @param {"system"|"module"} artifact - Which artifact is shipped.
|
|
19
|
+
* @returns {string} The layer name.
|
|
20
|
+
*/
|
|
21
|
+
export function baseStyleLayer(artifact: "system" | "module"): string;
|
|
22
|
+
/**
|
|
23
|
+
* The manifest `styles` entry for the base sheet.
|
|
24
|
+
*
|
|
25
|
+
* Foundry's `styles` is an array of `{layer?, src}`, and a bare string is
|
|
26
|
+
* migrated into that shape on read, so the object form is written directly.
|
|
27
|
+
*
|
|
28
|
+
* @param {"system"|"module"} artifact - Which artifact is shipped.
|
|
29
|
+
* @returns {{src: string, layer: string}} The entry.
|
|
30
|
+
*/
|
|
31
|
+
export function baseStyleEntry(artifact: "system" | "module"): {
|
|
32
|
+
src: string;
|
|
33
|
+
layer: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The absolute path of the sheet as installed, so the stage can copy it.
|
|
37
|
+
*
|
|
38
|
+
* Resolved against this module rather than against the consumer's working
|
|
39
|
+
* directory, because the file belongs to this package and arrives through
|
|
40
|
+
* `node_modules`.
|
|
41
|
+
*
|
|
42
|
+
* @returns {string} Absolute path of the shipped stylesheet.
|
|
43
|
+
*/
|
|
44
|
+
export function baseStylesheetSource(): string;
|
|
45
|
+
/**
|
|
46
|
+
* The `[source, destination]` pair that stages the sheet.
|
|
47
|
+
*
|
|
48
|
+
* The destination is relative, in the same spelling the repository's own asset
|
|
49
|
+
* table uses, so the caller resolves both against the repository root exactly
|
|
50
|
+
* as it resolves every other entry.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} stageDir - The configured stage directory, relative to the
|
|
53
|
+
* repository root.
|
|
54
|
+
* @returns {[string, string]} The pair, for `stageAssets`.
|
|
55
|
+
*/
|
|
56
|
+
export function baseStyleStageEntry(stageDir: string): [string, string];
|
|
57
|
+
/**
|
|
58
|
+
* The manifest's `styles`, with the base sheet named first.
|
|
59
|
+
*
|
|
60
|
+
* **First rather than last**, because `styles` is load order: a package's own
|
|
61
|
+
* sheet has to be read after the base one for an override written at equal
|
|
62
|
+
* specificity in an equal layer to take. The cascade layer makes the override
|
|
63
|
+
* work anyway, and the order makes it work for the right reason.
|
|
64
|
+
*
|
|
65
|
+
* A declared entry addressing the same file is not duplicated — a repository
|
|
66
|
+
* that named the staged path itself gets one entry, keeping whatever layer it
|
|
67
|
+
* chose, so declaring it is a way of placing it rather than an error.
|
|
68
|
+
*
|
|
69
|
+
* @param {object} options
|
|
70
|
+
* @param {unknown} [options.declared] - The declared `styles`, whatever shape
|
|
71
|
+
* it was written in; anything that is not a list is returned untouched.
|
|
72
|
+
* @param {"system"|"module"} options.artifact - Which artifact is shipped.
|
|
73
|
+
* @param {boolean} [options.enabled] - Whether the package takes the base
|
|
74
|
+
* sheet. `false` returns the declared list unchanged.
|
|
75
|
+
* @returns {unknown} The `styles` to emit, or `undefined` when there is
|
|
76
|
+
* nothing to emit at all.
|
|
77
|
+
*/
|
|
78
|
+
export function withBaseStyle({ declared, artifact, enabled }: {
|
|
79
|
+
declared?: unknown;
|
|
80
|
+
artifact: "system" | "module";
|
|
81
|
+
enabled?: boolean | undefined;
|
|
82
|
+
}): unknown;
|
|
83
|
+
/**
|
|
84
|
+
* Where the shipped sheet lands inside the staged package.
|
|
85
|
+
*
|
|
86
|
+
* Under `styles/` beside a package's own sheet, and named for the toolchain so
|
|
87
|
+
* nothing a repository writes can collide with it. The path is relative to the
|
|
88
|
+
* staged package root, which is what both the stage entry and the manifest's
|
|
89
|
+
* `styles` are addressed in.
|
|
90
|
+
*
|
|
91
|
+
* @type {string}
|
|
92
|
+
*/
|
|
93
|
+
export const BASE_STYLESHEET_DEST: string;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find named blocks, leaving code examples alone.
|
|
3
|
+
*
|
|
4
|
+
* A block reaches `blocks` only when it is well formed and closed; everything
|
|
5
|
+
* else is a finding, and the surrounding blocks are unaffected.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} source - Markdown that may contain named blocks.
|
|
8
|
+
* @returns {{blocks: Array<{name: string, title: string, id: string, classes: string[], attributes: Record<string, string>, start: number, end: number, body: string}>, errors: Array<{line: number, column: number, message: string}>}}
|
|
9
|
+
*/
|
|
10
|
+
export function scanBlocks(source: string): {
|
|
11
|
+
blocks: Array<{
|
|
12
|
+
name: string;
|
|
13
|
+
title: string;
|
|
14
|
+
id: string;
|
|
15
|
+
classes: string[];
|
|
16
|
+
attributes: Record<string, string>;
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
body: string;
|
|
20
|
+
}>;
|
|
21
|
+
errors: Array<{
|
|
22
|
+
line: number;
|
|
23
|
+
column: number;
|
|
24
|
+
message: string;
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Render named blocks for one publishing surface.
|
|
29
|
+
*
|
|
30
|
+
* `foundry` emits a `<section>` whose heading is its first line; `web` emits a
|
|
31
|
+
* `<details>` the reader opens. Neither carries an inline `style`.
|
|
32
|
+
*
|
|
33
|
+
* The body is left as Markdown between the wrapper's tags, each separated from
|
|
34
|
+
* its tag by a blank line — see the module docs for why that is what lets the
|
|
35
|
+
* surrounding render treat the body as real content rather than an opaque
|
|
36
|
+
* fragment.
|
|
37
|
+
*
|
|
38
|
+
* A finding does not stop the well-formed blocks around it from rendering.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} source - Markdown containing named blocks.
|
|
41
|
+
* @param {"foundry"|"web"} target - Publishing surface.
|
|
42
|
+
* @returns {{markdown: string, errors: Array<{line: number, column: number, message: string}>}}
|
|
43
|
+
*/
|
|
44
|
+
export function renderBlocks(source: string, target: "foundry" | "web"): {
|
|
45
|
+
markdown: string;
|
|
46
|
+
errors: Array<{
|
|
47
|
+
line: number;
|
|
48
|
+
column: number;
|
|
49
|
+
message: string;
|
|
50
|
+
}>;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Wrap each withheld section in the disclosure a `secret` block renders as.
|
|
54
|
+
*
|
|
55
|
+
* The section's own Markdown is left as Markdown, with a blank line either side
|
|
56
|
+
* of it, so the page's renderer still reads it: the heading keeps its id, the
|
|
57
|
+
* prose keeps its links, and only the disclosure around them is written as HTML.
|
|
58
|
+
*
|
|
59
|
+
* **A spoiler, not access control.** The section is in the published page, so a
|
|
60
|
+
* reader who opens the element or reads the source reads it. Foundry is the one
|
|
61
|
+
* surface that withholds anything.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} source - A page's Markdown, headings intact.
|
|
64
|
+
* @returns {string} The same Markdown, each withheld section inside a
|
|
65
|
+
* `<details>`.
|
|
66
|
+
*/
|
|
67
|
+
export function renderWithheldSections(source: string): string;
|
|
68
|
+
/** The blocks an author may open, with the heading each takes by default. */
|
|
69
|
+
export const BLOCK_NAMES: Readonly<{
|
|
70
|
+
info: "Info";
|
|
71
|
+
secret: "Secret";
|
|
72
|
+
warn: "Warn";
|
|
73
|
+
}>;
|
|
74
|
+
/**
|
|
75
|
+
* Blocks that may hold another named block.
|
|
76
|
+
*
|
|
77
|
+
* A box inside a GM-only section is a real thing to write — the section is a
|
|
78
|
+
* container for whatever the GM reads, boxes included — so a `secret` holds one
|
|
79
|
+
* and the box stays inside it. A box inside a box is noise, so `info` and `warn`
|
|
80
|
+
* hold no named block and a second opener inside one is a finding.
|
|
81
|
+
*
|
|
82
|
+
* Exported because a renderer has to know it: the body of a container is read
|
|
83
|
+
* for blocks of its own, and the body of a box is not.
|
|
84
|
+
*
|
|
85
|
+
* @type {readonly string[]}
|
|
86
|
+
*/
|
|
87
|
+
export const BLOCK_CONTAINERS: readonly string[];
|
|
@@ -27,7 +27,20 @@ export function scanCaptions(source: string): {
|
|
|
27
27
|
message: string;
|
|
28
28
|
}>;
|
|
29
29
|
};
|
|
30
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Render captioned blocks as HTML, leaving other Markdown untouched.
|
|
32
|
+
*
|
|
33
|
+
* A code, table or prose caption's block is left as Markdown inside the
|
|
34
|
+
* wrapper, blank-line separated from its tags exactly as
|
|
35
|
+
* {@link module:engine/content-blocks.renderBlocks} leaves a named block's
|
|
36
|
+
* body — so the surrounding render sees it as part of its own document and a
|
|
37
|
+
* footnote reference inside it resolves against the note's own definitions.
|
|
38
|
+
*
|
|
39
|
+
* A figure's block is still rendered here, because its `<figcaption>` has to
|
|
40
|
+
* be stripped from HTML this pass controls directly; the surrounding render
|
|
41
|
+
* never sees it as Markdown, so a footnote reference inside a figure caption
|
|
42
|
+
* is not resolved, the same gap a block's own title carries.
|
|
43
|
+
*/
|
|
31
44
|
export function renderCaptionBlocks(source: any, renderMarkdown: ((src: string, env?: import("markdown-it").Env) => string) | undefined, numbers: any): {
|
|
32
45
|
markdown: any;
|
|
33
46
|
errors: {
|
|
@@ -8,3 +8,76 @@ export function separateFootnotes(source: string): {
|
|
|
8
8
|
markdown: string;
|
|
9
9
|
definitions: string;
|
|
10
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* A footnote definition written inside a list item or a block quote, where
|
|
13
|
+
* the web and Foundry can resolve it but the book drops it silently.
|
|
14
|
+
*
|
|
15
|
+
* A definition inside a table cell is not reported here: a table cell is
|
|
16
|
+
* inline content, so `[^id]:` there is never recognised as a definition on
|
|
17
|
+
* any surface and simply prints as written — the reference it was meant to
|
|
18
|
+
* answer is what {@link unresolvedFootnoteReferences} reports.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} source - A note's markdown body.
|
|
21
|
+
* @returns {Array<{line: number, column: number, label: string, message: string}>}
|
|
22
|
+
*/
|
|
23
|
+
export function misplacedFootnoteDefinitions(source: string): Array<{
|
|
24
|
+
line: number;
|
|
25
|
+
column: number;
|
|
26
|
+
label: string;
|
|
27
|
+
message: string;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* A footnote reference with no top-level definition to resolve against.
|
|
31
|
+
*
|
|
32
|
+
* Covers both commoner slips alike: a label never defined at all, and one
|
|
33
|
+
* "defined" only inside a table cell, which is inline content and never
|
|
34
|
+
* registers as a definition on any surface. A label misplaced in a list or a
|
|
35
|
+
* block quote is excluded — {@link misplacedFootnoteDefinitions} already
|
|
36
|
+
* reports that mistake at the definition's own line, and reporting it again
|
|
37
|
+
* at the reference would say the same thing twice.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} source - A note's markdown body.
|
|
40
|
+
* @param {string} [definitions] - Its top-level definitions, when a caller
|
|
41
|
+
* already extracted them with {@link separateFootnotes}. Derived from
|
|
42
|
+
* `source` when omitted; a caller already holding both passes its own
|
|
43
|
+
* extraction instead, so the two cannot disagree about what is defined.
|
|
44
|
+
* @returns {Array<{line: number, column: number, message: string}>}
|
|
45
|
+
*/
|
|
46
|
+
export function unresolvedFootnoteReferences(source: string, definitions?: string): Array<{
|
|
47
|
+
line: number;
|
|
48
|
+
column: number;
|
|
49
|
+
message: string;
|
|
50
|
+
}>;
|
|
51
|
+
/**
|
|
52
|
+
* A top-level footnote definition no reference anywhere in the note uses.
|
|
53
|
+
*
|
|
54
|
+
* `separateFootnotes` lifts it out correctly, nothing resolves against it,
|
|
55
|
+
* and the paragraph it held vanishes from every surface with the build
|
|
56
|
+
* exiting 0 — a dropped paragraph is not something a reader can notice is
|
|
57
|
+
* missing, so it is an error here rather than a warning, the same severity
|
|
58
|
+
* every other silent loss this pass catches already carries. A note mid-edit,
|
|
59
|
+
* with the reference already deleted and the definition not yet followed, is
|
|
60
|
+
* exactly the case this refuses — which is the point: the alternative is
|
|
61
|
+
* shipping it.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} source - A note's markdown body.
|
|
64
|
+
* @returns {Array<{line: number, column: number, message: string}>}
|
|
65
|
+
*/
|
|
66
|
+
export function unusedFootnoteDefinitions(source: string): Array<{
|
|
67
|
+
line: number;
|
|
68
|
+
column: number;
|
|
69
|
+
message: string;
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* Every footnote placement finding for one note body — the union
|
|
73
|
+
* {@link misplacedFootnoteDefinitions}, {@link unresolvedFootnoteReferences}
|
|
74
|
+
* and {@link unusedFootnoteDefinitions} report, in document order.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} source - A note's markdown body.
|
|
77
|
+
* @returns {Array<{line: number, column: number, message: string}>}
|
|
78
|
+
*/
|
|
79
|
+
export function footnoteFindings(source: string): Array<{
|
|
80
|
+
line: number;
|
|
81
|
+
column: number;
|
|
82
|
+
message: string;
|
|
83
|
+
}>;
|
|
@@ -180,12 +180,14 @@ export function checkDeclaredFields({ format, itemFields, system, severity }: {
|
|
|
180
180
|
/**
|
|
181
181
|
* Top-level keys the format names as the note's own, whatever its type.
|
|
182
182
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
183
|
+
* The top-level vocabulary itself, read rather than restated: this set exists
|
|
184
|
+
* to keep a note's own identity fields from being mistaken for a misplaced
|
|
185
|
+
* `data` property where the two share a name, and the keys that are a note's
|
|
186
|
+
* own are exactly the ones the region declares. `type` and `subType` are the
|
|
187
|
+
* pair that matters — several mapping tables name `subType` as a shared source,
|
|
188
|
+
* and it is authored at top level by design. A key the region does *not*
|
|
189
|
+
* declare, such as `id` beside `data.id`, is reported as the misplaced `data`
|
|
190
|
+
* property it is.
|
|
189
191
|
*
|
|
190
192
|
* @type {ReadonlySet<string>}
|
|
191
193
|
*/
|