@heroiclands/package-build 22.20.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-content-build.md +0 -8
- package/CHANGELOG.md +204 -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 +46 -28
- 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 +265 -42
- package/docs/reference/note-types.md +45 -28
- 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 +62 -66
- package/engine/frontmatter.mjs +3 -3
- package/engine/heading-attributes.mjs +308 -0
- package/engine/helpers.mjs +19 -13
- package/engine/homepage.mjs +8 -8
- package/engine/index.mjs +3 -0
- package/engine/infobox-render.mjs +21 -4
- package/engine/infobox.mjs +167 -7
- package/engine/journals.mjs +36 -17
- package/engine/macros.mjs +1 -1
- package/engine/markdown-expressions.mjs +178 -53
- package/engine/note-addresses.mjs +17 -8
- package/engine/note-dates.mjs +49 -0
- package/engine/note-frontmatter.mjs +7 -16
- package/engine/note-vocabulary.mjs +216 -42
- 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/reference-targets.mjs +13 -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 +39 -4
- package/engine/site-preview.mjs +18 -7
- package/engine/social-ties.mjs +52 -14
- package/engine/sql-tables.mjs +66 -9
- package/engine/standing-terms.mjs +19 -0
- package/engine/standings.mjs +538 -0
- package/engine/svg-theme.mjs +15 -4
- package/engine/system-block.mjs +12 -14
- package/engine/web-wikilinks.mjs +48 -1
- package/engine/wikilinks.mjs +3 -1
- package/hm3/actors.mjs +6 -3
- package/manifest.mjs +13 -0
- package/package.json +1 -1
- package/sohl/actors.mjs +82 -103
- 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 +98 -8
- 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/standing-terms.d.mts +16 -0
- package/types/engine/standings.d.mts +147 -0
- package/types/engine/system-block.d.mts +11 -12
- package/types/engine/web-wikilinks.d.mts +3 -1
- package/types/engine/wikilinks.d.mts +2 -1
- package/types/hm3/actors.d.mts +3 -3
- package/types/sohl/actors.d.mts +25 -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
|
@@ -106,6 +106,12 @@ export type SiteEntry = {
|
|
|
106
106
|
* The page's published address.
|
|
107
107
|
*/
|
|
108
108
|
url: string;
|
|
109
|
+
/**
|
|
110
|
+
* The note's markdown body, for its anchors. A
|
|
111
|
+
* content entry always carries one; absent, the
|
|
112
|
+
* page is indexed as declaring none.
|
|
113
|
+
*/
|
|
114
|
+
body?: string | undefined;
|
|
109
115
|
};
|
|
110
116
|
/**
|
|
111
117
|
* The resolved index and everything a wikilink resolver reads beside it.
|
|
@@ -114,12 +120,18 @@ export type SiteIndex = {
|
|
|
114
120
|
/**
|
|
115
121
|
* Address → page. `draft` says the page
|
|
116
122
|
* carries the `draft` tag, which marks a
|
|
117
|
-
* link *into* it.
|
|
123
|
+
* link *into* it. `anchors` names the
|
|
124
|
+
* `{#slug}` sections it declares — a
|
|
125
|
+
* `Set` for a local page, or the
|
|
126
|
+
* `{slug: uuid}` map a foreign manifest
|
|
127
|
+
* publishes — and is absent where
|
|
128
|
+
* neither build recorded one.
|
|
118
129
|
*/
|
|
119
130
|
index: Map<string, {
|
|
120
131
|
url: string;
|
|
121
132
|
name?: string;
|
|
122
133
|
draft?: boolean;
|
|
134
|
+
anchors?: Set<string> | Record<string, string>;
|
|
123
135
|
}>;
|
|
124
136
|
/**
|
|
125
137
|
* Short addresses claimed by two
|
|
@@ -142,6 +142,10 @@ export function renderSqlTable(result: {
|
|
|
142
142
|
* @param {object} [opts]
|
|
143
143
|
* @param {(ref: string) => boolean} [opts.linkable] - Passed to
|
|
144
144
|
* {@link renderSqlTable}.
|
|
145
|
+
* @param {object[]} [opts.records] - Content-index records, narrowed to the
|
|
146
|
+
* audience this surface publishes to. Given them, the result also carries
|
|
147
|
+
* `pageLists`, so one prepared object answers every corpus-reading directive
|
|
148
|
+
* a body can hold and a caller looks results up in one place.
|
|
145
149
|
* **Keyed by note, then by the directive's ordinal within it** — not by its
|
|
146
150
|
* line. The passes do not agree on what a body is: `walkMarkdownTree` trims it,
|
|
147
151
|
* while the link checker strips the frontmatter fence and leaves the newlines
|
|
@@ -154,8 +158,9 @@ export function renderSqlTable(result: {
|
|
|
154
158
|
export function prepareSqlTables(db: object, sources: Array<{
|
|
155
159
|
source: string;
|
|
156
160
|
markdown: string;
|
|
157
|
-
}>, { linkable }?: {
|
|
161
|
+
}>, { linkable, records }?: {
|
|
158
162
|
linkable?: ((ref: string) => boolean) | undefined;
|
|
163
|
+
records?: object[] | undefined;
|
|
159
164
|
}): Promise<Map<string, object[]>>;
|
|
160
165
|
/** Evaluate scalar SQL calls before synchronous Markdown renderers run. */
|
|
161
166
|
export function prepareInlineSqlExpressions(db: any, sources: any): Promise<Map<any, any>>;
|
|
@@ -167,10 +172,14 @@ export function prepareInlineSqlExpressions(db: any, sources: any): Promise<Map<
|
|
|
167
172
|
* shared index
|
|
168
173
|
* describes, where N passes each derive the corpus their own way.
|
|
169
174
|
*
|
|
170
|
-
* **
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
175
|
+
* **Every corpus-reading directive is answered here**, which is why one call
|
|
176
|
+
* precedes every pass: the `sql` tables, the inline scalar queries, and the
|
|
177
|
+
* page lists, whose results ride on the same object as `pageLists`.
|
|
178
|
+
*
|
|
179
|
+
* **Nothing is opened for a tree that asks no query.** A tree with no `sql`
|
|
180
|
+
* directive costs one walk and no database at all, and so does one whose only
|
|
181
|
+
* directive is a page list — a page list is a filter over the records. That is
|
|
182
|
+
* what lets every pass call this unconditionally.
|
|
174
183
|
*
|
|
175
184
|
* @param {string} contentBase - Root of the content tree.
|
|
176
185
|
* @param {object} [opts]
|
|
@@ -180,8 +189,9 @@ export function prepareInlineSqlExpressions(db: any, sources: any): Promise<Map<
|
|
|
180
189
|
* A command that also builds a link index holds them already, and deriving
|
|
181
190
|
* them twice is the duplicated-corpus failure this closes.
|
|
182
191
|
* @param {"all"|"public"} [opts.audience] - Whether to exclude GM notes.
|
|
183
|
-
* @returns {Promise<Map<string, object[]>|undefined>} Results by note path,
|
|
184
|
-
* nothing when the tree
|
|
192
|
+
* @returns {Promise<Map<string, object[]>|undefined>} Results by note path,
|
|
193
|
+
* carrying `inline` and `pageLists` beside them, or nothing when the tree
|
|
194
|
+
* holds no such directive.
|
|
185
195
|
*/
|
|
186
196
|
export function prepareTreeSqlTables(contentBase: string, { config, skipDirectories, records, audience }?: {
|
|
187
197
|
config?: object | undefined;
|
|
@@ -200,3 +210,17 @@ export const RENDER_ALIASES: Readonly<{
|
|
|
200
210
|
ref: "_ref";
|
|
201
211
|
section: "_section";
|
|
202
212
|
}>;
|
|
213
|
+
/**
|
|
214
|
+
* The attributes an SQL fence takes, with what each one is for.
|
|
215
|
+
*
|
|
216
|
+
* **This is the one list.** The fence is validated against it and the authoring
|
|
217
|
+
* document is checked against it, so an attribute added here is one the document
|
|
218
|
+
* must describe, and anything else an author writes is reported by name.
|
|
219
|
+
*
|
|
220
|
+
* @type {Readonly<Record<string, Readonly<{value: string, default: string, summary: string}>>>}
|
|
221
|
+
*/
|
|
222
|
+
export const SQL_FENCE_ATTRIBUTES: Readonly<Record<string, Readonly<{
|
|
223
|
+
value: string;
|
|
224
|
+
default: string;
|
|
225
|
+
summary: string;
|
|
226
|
+
}>>>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The note type a standing is held in.
|
|
3
|
+
*
|
|
4
|
+
* A standing is always held in a body: the entry is keyed by that body's
|
|
5
|
+
* Address, which is what lets the rung and the post be checked against what
|
|
6
|
+
* the body itself declares.
|
|
7
|
+
*/
|
|
8
|
+
export const STANDING_BODY_TYPES: readonly string[];
|
|
9
|
+
/**
|
|
10
|
+
* The two keys one standing carries.
|
|
11
|
+
*
|
|
12
|
+
* `rank` is the level on the body's own `governance.ranks` ladder, as a number.
|
|
13
|
+
* `office` is a key of its `governance.offices` map, as a string. Both are
|
|
14
|
+
* optional — a membership with neither is the ordinary shape.
|
|
15
|
+
*/
|
|
16
|
+
export const STANDING_KEYS: readonly string[];
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The standings a being authored, in one shape whichever shape it wrote.
|
|
3
|
+
*
|
|
4
|
+
* The list form names bodies and no standings; the map form names both. Three
|
|
5
|
+
* spellings reach here and every reader downstream takes the same entries, so
|
|
6
|
+
* none of them asks which it was handed:
|
|
7
|
+
*
|
|
8
|
+
* - an **{@link AddressEntries}** — the map after the note boundary decoded its
|
|
9
|
+
* keys into typed Addresses, which is the shape every compile sees;
|
|
10
|
+
* - a **plain map**, which is the shape a checker reading raw YAML sees;
|
|
11
|
+
* - a **list**, which names bodies and says nothing about standing in them.
|
|
12
|
+
*
|
|
13
|
+
* An emptied map arrives from the property editor as `[]` and means what `{}`
|
|
14
|
+
* means — this being authors no membership — so both read as no entries.
|
|
15
|
+
*
|
|
16
|
+
* Each entry carries `body` for a message and `sourceKey` for a position: the
|
|
17
|
+
* authored key is what a reader has to find in the file, and the decoded
|
|
18
|
+
* Address is what a finding should name.
|
|
19
|
+
*
|
|
20
|
+
* @param {unknown} value - The authored `data.affiliations`.
|
|
21
|
+
* @returns {{form: "absent"|"list"|"map"|"malformed",
|
|
22
|
+
* entries: Array<{body: unknown, sourceKey: string|number, standing: unknown}>}}
|
|
23
|
+
* The form it was written in, and its entries.
|
|
24
|
+
*/
|
|
25
|
+
export function readStandings(value: unknown): {
|
|
26
|
+
form: "absent" | "list" | "map" | "malformed";
|
|
27
|
+
entries: Array<{
|
|
28
|
+
body: unknown;
|
|
29
|
+
sourceKey: string | number;
|
|
30
|
+
standing: unknown;
|
|
31
|
+
}>;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The rungs an affiliation's ladder declares, as level → title.
|
|
35
|
+
*
|
|
36
|
+
* A rung states its own `level`, so the ladder is read by that number rather
|
|
37
|
+
* than by its position in the list: a body may write its rungs in any order,
|
|
38
|
+
* and several start at zero.
|
|
39
|
+
*
|
|
40
|
+
* One title per level, which is what the rung check holds a ladder to: two
|
|
41
|
+
* rungs claiming one level is a finding on the second, so a ladder that reaches
|
|
42
|
+
* here carries at most one title for any number a member's `rank` can hold.
|
|
43
|
+
*
|
|
44
|
+
* @param {unknown} ranks - An affiliation's `data.governance.ranks`.
|
|
45
|
+
* @returns {Map<number, string>} Level → the title that level is called.
|
|
46
|
+
*/
|
|
47
|
+
export function rankLadder(ranks: unknown): Map<number, string>;
|
|
48
|
+
/**
|
|
49
|
+
* The offices an affiliation names, as key → description.
|
|
50
|
+
*
|
|
51
|
+
* An office is authored either as a bare description or as a `{description,
|
|
52
|
+
* holders}` map, and a reader of the post's name wants the same string from
|
|
53
|
+
* both.
|
|
54
|
+
*
|
|
55
|
+
* @param {unknown} offices - An affiliation's `data.governance.offices`.
|
|
56
|
+
* @returns {Map<string, string>} Office key → its description.
|
|
57
|
+
*/
|
|
58
|
+
export function officeRoster(offices: unknown): Map<string, string>;
|
|
59
|
+
/**
|
|
60
|
+
* The anchor an office's row is addressed by.
|
|
61
|
+
*
|
|
62
|
+
* Declared once, because the row that carries the anchor and the link that
|
|
63
|
+
* reaches it are drawn by different passes: two spellings of the same
|
|
64
|
+
* derivation is a link into a page that offers no such destination.
|
|
65
|
+
*
|
|
66
|
+
* An office whose name reduces to nothing URL-safe has no anchor, and the
|
|
67
|
+
* caller is the one that can say what to do without one.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} office - The office's key, as the body wrote it.
|
|
70
|
+
* @returns {string} The anchor, or `""` where the name carries nothing.
|
|
71
|
+
*/
|
|
72
|
+
export function officeAnchor(office: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* The anchor a rung's row is addressed by.
|
|
75
|
+
*
|
|
76
|
+
* The ladder's parallel to {@link officeAnchor}, prefixed apart from it so a
|
|
77
|
+
* body naming an office the same word as one of its ranks still gets two
|
|
78
|
+
* destinations rather than one row answering for both.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} title - The rung's title, as the body wrote it.
|
|
81
|
+
* @returns {string} The anchor, or `""` where the title carries nothing
|
|
82
|
+
* URL-safe.
|
|
83
|
+
*/
|
|
84
|
+
export function rankAnchor(title: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* What an affiliation lends to a reference to it.
|
|
87
|
+
*
|
|
88
|
+
* A reference's value answers what was found as well as where it is — the same
|
|
89
|
+
* reason a target's `subType` travels with it. A standing is read on the
|
|
90
|
+
* *being*, and what it is called is declared on the *body*, so the ladder and
|
|
91
|
+
* the office roster ride along to the one place that needs them.
|
|
92
|
+
*
|
|
93
|
+
* Nothing rides for a note that declares neither.
|
|
94
|
+
*
|
|
95
|
+
* @param {object} fm - The affiliation note's frontmatter.
|
|
96
|
+
* @returns {{ranks?: Record<string, string>, offices?: Record<string, string>}|undefined}
|
|
97
|
+
* What a reference carries, or nothing.
|
|
98
|
+
*/
|
|
99
|
+
export function standingsDigest(fm: object): {
|
|
100
|
+
ranks?: Record<string, string>;
|
|
101
|
+
offices?: Record<string, string>;
|
|
102
|
+
} | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* How a standing reads beside the body that confers it.
|
|
105
|
+
*
|
|
106
|
+
* The office leads, because it is the more particular of the two: a reader
|
|
107
|
+
* meeting "War Chief" has been told the rung as well. The body closes the
|
|
108
|
+
* phrase, which is the whole point of keying the entry by it — a standing with
|
|
109
|
+
* no body named is the lossy form.
|
|
110
|
+
*
|
|
111
|
+
* No article is supplied. One body's name reads with a "the" in front of it and
|
|
112
|
+
* the next does not, and inventing one is this build editing the corpus.
|
|
113
|
+
*
|
|
114
|
+
* @param {object} standing - `{rank, office}`, either or both absent.
|
|
115
|
+
* @param {string} body - What the body is called.
|
|
116
|
+
* @param {{ranks?: Record<string, string>}} [digest] - The body's ladder.
|
|
117
|
+
* @returns {string} The phrase, or the body's name where no standing is held.
|
|
118
|
+
*/
|
|
119
|
+
export function standingPhrase(standing: object, body: string, digest?: {
|
|
120
|
+
ranks?: Record<string, string>;
|
|
121
|
+
}): string;
|
|
122
|
+
/**
|
|
123
|
+
* Validate a being's memberships and the standing it holds in each.
|
|
124
|
+
*
|
|
125
|
+
* Five questions, and the last three are what keying the entry by its body
|
|
126
|
+
* buys:
|
|
127
|
+
*
|
|
128
|
+
* 1. the value is a map keyed by Address (or the list this window still takes);
|
|
129
|
+
* 2. every key names an affiliation that resolves, once;
|
|
130
|
+
* 3. the entry states a `rank`;
|
|
131
|
+
* 4. a `rank` is a level the named body's `governance.ranks` declares;
|
|
132
|
+
* 5. an `office` is a key of that body's `governance.offices`.
|
|
133
|
+
*
|
|
134
|
+
* Both of the last two are read from the named body's own frontmatter as the
|
|
135
|
+
* lint runs, so a renamed rung or a renamed post fails on every being pointing
|
|
136
|
+
* at it rather than on a list somebody has to remember to edit.
|
|
137
|
+
*
|
|
138
|
+
* @param {object} note - The note.
|
|
139
|
+
* @param {{index?: object}} [options] - The lint's options.
|
|
140
|
+
* @returns {object[]} Located findings.
|
|
141
|
+
*/
|
|
142
|
+
export function checkStandings(note: object, { index }?: {
|
|
143
|
+
index?: object;
|
|
144
|
+
}): object[];
|
|
145
|
+
import { STANDING_BODY_TYPES } from "./standing-terms.mjs";
|
|
146
|
+
import { STANDING_KEYS } from "./standing-terms.mjs";
|
|
147
|
+
export { STANDING_BODY_TYPES, STANDING_KEYS };
|
|
@@ -79,17 +79,18 @@ export function blockField(fm: object, block: string, key: string, defaultValue?
|
|
|
79
79
|
*/
|
|
80
80
|
export function sharedProperty(fm: object, source: string, defaultValue?: any): any;
|
|
81
81
|
/**
|
|
82
|
-
* A property a system block
|
|
82
|
+
* A property a system block declares, else the default.
|
|
83
83
|
*
|
|
84
|
-
* This is what gives `
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* in
|
|
84
|
+
* This is what gives `effects` and `flags` their per-system form without
|
|
85
|
+
* inventing a mechanism for each: a note states them inside the block whose
|
|
86
|
+
* document carries them, which is the only position the format accepts for
|
|
87
|
+
* either. A note needing one value for both systems writes it in both blocks —
|
|
88
|
+
* they are properties of a document, and there are two documents.
|
|
88
89
|
*
|
|
89
90
|
* @param {object} fm - The note's frontmatter.
|
|
90
91
|
* @param {string} block - The block key.
|
|
91
92
|
* @param {string} key - The property.
|
|
92
|
-
* @param {any} [defaultValue] - Returned when
|
|
93
|
+
* @param {any} [defaultValue] - Returned when the block does not declare it.
|
|
93
94
|
* @returns {any} The value.
|
|
94
95
|
*/
|
|
95
96
|
export function blockProperty(fm: object, block: string, key: string, defaultValue?: any): any;
|
|
@@ -128,12 +129,10 @@ export function legacyKeyOf(field: {
|
|
|
128
129
|
/**
|
|
129
130
|
* The bare top-level key a `data:`-sourced field is being swept off — step 3b.
|
|
130
131
|
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* every other key that move relocated. Deriving it is what keeps the two
|
|
136
|
-
* spellings of one field from disagreeing the way two declarations would.
|
|
132
|
+
* The retiring spelling of `data.portrait` is not a second declaration anyone
|
|
133
|
+
* has to write — it is `portrait`, mechanically, and the same holds for every
|
|
134
|
+
* other key the container gathered. Deriving it is what keeps the two spellings
|
|
135
|
+
* of one field from disagreeing the way two declarations would.
|
|
137
136
|
*
|
|
138
137
|
* **Only a `data.` source has one.** `protection.blunt` and `impact.die` are
|
|
139
138
|
* paths into containers a note has always written at the top level; they were
|
|
@@ -97,7 +97,9 @@ export function frontmatterWikilinks(fm: unknown): Array<{
|
|
|
97
97
|
* resolves against. `src` is the page's display
|
|
98
98
|
* path and `file` the source file a diagnostic should name — absent, `src`
|
|
99
99
|
* stands in. `resolved`, when supplied, is the array every resolved
|
|
100
|
-
* target's index entry is appended to.
|
|
100
|
+
* target's index entry is appended to. `anchors` is the `{#slug}` set
|
|
101
|
+
* *this* page declares, which a `[[#slug]]` self-link is checked against;
|
|
102
|
+
* absent, a self-link is not checked, exactly as before this existed.
|
|
101
103
|
* @returns {string} The body with embeds and wikilinks rewritten.
|
|
102
104
|
*/
|
|
103
105
|
export function resolveWebWikilinks(body: string, ctx: object): string;
|
|
@@ -133,7 +133,7 @@ export function convertWikilinks(markdown: string, { type, id, pack, docPack, in
|
|
|
133
133
|
* @param {object} index - From {@link buildWikilinkIndex}.
|
|
134
134
|
* @param {unknown} ref - The reference, as authored.
|
|
135
135
|
* @param {object} [hint] - `{type}`, where the caller knows what it expects.
|
|
136
|
-
* @returns {{name?: string, uuid?: string, address?: import("./address.mjs").AddressTuple, subType?: string}|undefined}
|
|
136
|
+
* @returns {{name?: string, uuid?: string, address?: import("./address.mjs").AddressTuple, subType?: string, standings?: object}|undefined}
|
|
137
137
|
* The target, or `undefined` where nothing answers.
|
|
138
138
|
*/
|
|
139
139
|
export function resolveReference(index: object, ref: unknown, hint?: object): {
|
|
@@ -141,6 +141,7 @@ export function resolveReference(index: object, ref: unknown, hint?: object): {
|
|
|
141
141
|
uuid?: string;
|
|
142
142
|
address?: import("./address.mjs").AddressTuple;
|
|
143
143
|
subType?: string;
|
|
144
|
+
standings?: object;
|
|
144
145
|
} | undefined;
|
|
145
146
|
import { ITEM_PACK } from "./ids.mjs";
|
|
146
147
|
import { PACK_BY_TYPE } from "./ids.mjs";
|
package/types/hm3/actors.d.mts
CHANGED
|
@@ -27,9 +27,9 @@ export class Hm3Actors extends SystemActorCompiler {
|
|
|
27
27
|
/**
|
|
28
28
|
* Build every embedded item an HM3 actor carries, from `hm3.items`.
|
|
29
29
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* HM3 keeps a character's abilities in `system.abilities` rather than as
|
|
31
|
+
* embedded documents, so an ability reaches the document through the
|
|
32
|
+
* `hm3.system` passthrough and never through this list.
|
|
33
33
|
*
|
|
34
34
|
* @param {Map<string, object>} itemsMap - The predefined items, by address.
|
|
35
35
|
* @param {string} actorId - The owning actor's id, seeding embedded ids.
|
package/types/sohl/actors.d.mts
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* SoHL's Actor compile pass.
|
|
3
3
|
*
|
|
4
4
|
* Declares SoHL's note-type → document-subtype map and builds a `being` note's
|
|
5
|
-
* document: the
|
|
6
|
-
*
|
|
7
|
-
* Everything else is {@link module:engine/actor-compiler}'s.
|
|
5
|
+
* document: the embedded item documents the frontmatter names, and the `system`
|
|
6
|
+
* block. Everything else is {@link module:engine/actor-compiler}'s.
|
|
8
7
|
*/
|
|
9
8
|
export class Actors extends SystemActorCompiler {
|
|
10
9
|
/**
|
|
@@ -17,10 +16,29 @@ export class Actors extends SystemActorCompiler {
|
|
|
17
16
|
*/
|
|
18
17
|
static documentSubtypes: import("../engine/document-subtypes.mjs").DocumentSubtypeMap;
|
|
19
18
|
/**
|
|
20
|
-
* Build all embedded items for an actor: one per `sohl.
|
|
21
|
-
*
|
|
19
|
+
* Build all embedded items for an actor: one per `sohl.items` entry, plus
|
|
20
|
+
* one per body the being belongs to.
|
|
22
21
|
*/
|
|
23
22
|
buildEmbeddedItems(itemsMap: any, actorId: any, fm: any, ctx: any): object[];
|
|
23
|
+
/**
|
|
24
|
+
* One embedded affiliation item per body the being belongs to.
|
|
25
|
+
*
|
|
26
|
+
* The body's own compiled item is the model, so a membership carries
|
|
27
|
+
* whatever that body declares — its subtype, its common skills, its
|
|
28
|
+
* standings toward others — and the being adds only what is true of *this*
|
|
29
|
+
* membership: the rung it stands on and the post it holds.
|
|
30
|
+
*
|
|
31
|
+
* A being naming a body that compiles to no item is a finding rather than a
|
|
32
|
+
* silently dropped membership: the note says the being belongs somewhere,
|
|
33
|
+
* and a sheet that shows nothing says it does not.
|
|
34
|
+
*
|
|
35
|
+
* @param {Map<string, object>} itemsMap - The predefined-item catalogue.
|
|
36
|
+
* @param {string} actorId - The owning actor's id.
|
|
37
|
+
* @param {object} fm - The note's frontmatter.
|
|
38
|
+
* @param {string} ctx - Diagnostic context.
|
|
39
|
+
* @returns {object[]} The embedded items, in the order the note names them.
|
|
40
|
+
*/
|
|
41
|
+
buildMemberships(itemsMap: Map<string, object>, actorId: string, fm: object, ctx: string): object[];
|
|
24
42
|
/**
|
|
25
43
|
* Bake each unopened skill's opening mastery level into the document.
|
|
26
44
|
*
|
|
@@ -38,8 +56,8 @@ export class Actors extends SystemActorCompiler {
|
|
|
38
56
|
*
|
|
39
57
|
* **The scores used are the ones just written.** `SkillLogic` resolves
|
|
40
58
|
* `attr.<code>` to an attribute's *effective* score, after active effects;
|
|
41
|
-
* all this pass has is the `scoreBase`
|
|
42
|
-
* compiled being carrying no attribute-altering effects the two agree,
|
|
59
|
+
* all this pass has is the `scoreBase` on each embedded attribute item. For
|
|
60
|
+
* a compiled being carrying no attribute-altering effects the two agree,
|
|
43
61
|
* which is every being in content today. One that did carry such an effect
|
|
44
62
|
* would bake a Skill Base its client then disagrees with — that is the
|
|
45
63
|
* limit of doing this at build time, and the point to revisit if it bites.
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
|
2
|
-
|
|
3
|
-
import MarkdownIt from "markdown-it";
|
|
4
|
-
import footnotePlugin from "markdown-it-footnote";
|
|
5
|
-
import deflistPlugin from "markdown-it-deflist";
|
|
6
|
-
import { parseExtensionAttributes } from "./extension-attributes.mjs";
|
|
7
|
-
|
|
8
|
-
const parser = new MarkdownIt({ html: true }).use(footnotePlugin).use(deflistPlugin);
|
|
9
|
-
const OPEN = /^:::(info|warn)(?:\s+(.*))?\s*$/;
|
|
10
|
-
const CLOSE = /^:::\s*$/;
|
|
11
|
-
const STYLES = Object.freeze({
|
|
12
|
-
info: { label: "Info", icon: "ℹ", color: "#2f6f9f", background: "#eef6fb" },
|
|
13
|
-
warn: { label: "Warning", icon: "⚠", color: "#9a6700", background: "#fff5db" },
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
/** Find inline info and warning blocks without interpreting code examples. */
|
|
17
|
-
export function scanAdmonitions(source) {
|
|
18
|
-
const lines = String(source ?? "").split("\n");
|
|
19
|
-
const blocks = [];
|
|
20
|
-
const errors = [];
|
|
21
|
-
let opening = null;
|
|
22
|
-
let codeFence = null;
|
|
23
|
-
for (let i = 0; i < lines.length; i++) {
|
|
24
|
-
const line = lines[i];
|
|
25
|
-
const fence = /^ {0,3}(`{3,}|~{3,})/.exec(line);
|
|
26
|
-
if (codeFence) {
|
|
27
|
-
if (fence && fence[1][0] === codeFence[0] && fence[1].length >= codeFence.length)
|
|
28
|
-
codeFence = null;
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
if (fence) {
|
|
32
|
-
codeFence = fence[1];
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
const open = OPEN.exec(line);
|
|
36
|
-
if (open) {
|
|
37
|
-
if (opening) {
|
|
38
|
-
errors.push({
|
|
39
|
-
line: i + 1,
|
|
40
|
-
column: 1,
|
|
41
|
-
message: "nested info and warning blocks are not supported",
|
|
42
|
-
});
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
const raw = open[2]?.trim() ?? "";
|
|
46
|
-
let id = "";
|
|
47
|
-
if (raw) {
|
|
48
|
-
if (!/^\{[^}\n]*\}$/.test(raw))
|
|
49
|
-
errors.push({
|
|
50
|
-
line: i + 1,
|
|
51
|
-
column: 1,
|
|
52
|
-
message: "block attributes need {#id} syntax",
|
|
53
|
-
});
|
|
54
|
-
else {
|
|
55
|
-
const attrs = parseExtensionAttributes(raw.slice(1, -1));
|
|
56
|
-
id = attrs.id;
|
|
57
|
-
for (const message of attrs.problems)
|
|
58
|
-
errors.push({ line: i + 1, column: 1, message });
|
|
59
|
-
if (attrs.classes.length || Object.keys(attrs.values).length)
|
|
60
|
-
errors.push({
|
|
61
|
-
line: i + 1,
|
|
62
|
-
column: 1,
|
|
63
|
-
message: "info and warn accept only an id",
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
opening = { start: i, kind: open[1], id };
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
if (CLOSE.test(line) && opening) {
|
|
71
|
-
const body = lines
|
|
72
|
-
.slice(opening.start + 1, i)
|
|
73
|
-
.join("\n")
|
|
74
|
-
.trim();
|
|
75
|
-
if (!body)
|
|
76
|
-
errors.push({
|
|
77
|
-
line: opening.start + 1,
|
|
78
|
-
column: 1,
|
|
79
|
-
message: `${opening.kind} block is empty`,
|
|
80
|
-
});
|
|
81
|
-
blocks.push({ ...opening, end: i, body });
|
|
82
|
-
opening = null;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (opening)
|
|
86
|
-
errors.push({
|
|
87
|
-
line: opening.start + 1,
|
|
88
|
-
column: 1,
|
|
89
|
-
message: `${opening.kind} block needs a closing ::: line`,
|
|
90
|
-
});
|
|
91
|
-
return { blocks, errors };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Render info and warning blocks as styled, labelled HTML. */
|
|
95
|
-
export function renderAdmonitions(source, renderMarkdown = parser.render.bind(parser)) {
|
|
96
|
-
const { blocks, errors } = scanAdmonitions(source);
|
|
97
|
-
if (errors.length) return { markdown: source, errors };
|
|
98
|
-
const lines = String(source ?? "").split("\n");
|
|
99
|
-
const output = [];
|
|
100
|
-
let cursor = 0;
|
|
101
|
-
for (const block of blocks) {
|
|
102
|
-
const style = STYLES[block.kind];
|
|
103
|
-
output.push(...lines.slice(cursor, block.start));
|
|
104
|
-
output.push(
|
|
105
|
-
`<aside${block.id ? ` id="${block.id}"` : ""} class="sohl-admonition sohl-admonition-${block.kind}" style="border-left: 3px solid ${style.color}; background: ${style.background}; padding: 0.5em 0.8em; margin: 0.75em 0;">`,
|
|
106
|
-
`<p><strong><span aria-hidden="true">${style.icon}</span> ${style.label}</strong></p>`,
|
|
107
|
-
renderMarkdown(block.body).trim(),
|
|
108
|
-
"</aside>",
|
|
109
|
-
);
|
|
110
|
-
cursor = block.end + 1;
|
|
111
|
-
}
|
|
112
|
-
output.push(...lines.slice(cursor));
|
|
113
|
-
return { markdown: output.join("\n"), errors: [] };
|
|
114
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import crypto from "node:crypto";
|
|
6
|
-
import markdownit from "markdown-it";
|
|
7
|
-
import footnotePlugin from "markdown-it-footnote";
|
|
8
|
-
import deflistPlugin from "markdown-it-deflist";
|
|
9
|
-
|
|
10
|
-
const webMarkdown = markdownit({ html: true }).use(footnotePlugin).use(deflistPlugin);
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Render whole-line `:::secret` blocks for one publishing surface.
|
|
14
|
-
* The source line of each syntax error is relative to the supplied body.
|
|
15
|
-
*
|
|
16
|
-
* @param {string} source - Markdown containing secret blocks.
|
|
17
|
-
* @param {"foundry"|"web"|"book"} target - Publishing surface.
|
|
18
|
-
* @param {(markdown: string) => string} [renderMarkdown] - Foundry's configured renderer.
|
|
19
|
-
* @returns {{markdown: string, errors: Array<{line: number, column: number, message: string}>}}
|
|
20
|
-
*/
|
|
21
|
-
export function renderSecretBlocks(
|
|
22
|
-
source,
|
|
23
|
-
target,
|
|
24
|
-
renderMarkdown = webMarkdown.render.bind(webMarkdown),
|
|
25
|
-
) {
|
|
26
|
-
const lines = String(source ?? "").split("\n");
|
|
27
|
-
const result = [];
|
|
28
|
-
const errors = [];
|
|
29
|
-
let opening = -1;
|
|
30
|
-
let body = [];
|
|
31
|
-
let codeFence = null;
|
|
32
|
-
|
|
33
|
-
for (let index = 0; index < lines.length; index++) {
|
|
34
|
-
const line = lines[index];
|
|
35
|
-
const fence = line.match(/^ {0,3}(`{3,}|~{3,})/);
|
|
36
|
-
if (codeFence) {
|
|
37
|
-
if (fence && fence[1][0] === codeFence[0] && fence[1].length >= codeFence.length) {
|
|
38
|
-
codeFence = null;
|
|
39
|
-
}
|
|
40
|
-
if (opening >= 0) body.push(line);
|
|
41
|
-
else result.push(line);
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (fence) {
|
|
45
|
-
codeFence = fence[1];
|
|
46
|
-
if (opening >= 0) body.push(line);
|
|
47
|
-
else result.push(line);
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (/^:::secret[ \t]*$/.test(line)) {
|
|
51
|
-
if (opening >= 0) {
|
|
52
|
-
errors.push({
|
|
53
|
-
line: index + 1,
|
|
54
|
-
column: 1,
|
|
55
|
-
message: "nested secret blocks are not supported",
|
|
56
|
-
});
|
|
57
|
-
body.push(line);
|
|
58
|
-
} else {
|
|
59
|
-
opening = index;
|
|
60
|
-
body = [];
|
|
61
|
-
}
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (/^:::[ \t]*$/.test(line)) {
|
|
65
|
-
if (opening < 0) {
|
|
66
|
-
errors.push({
|
|
67
|
-
line: index + 1,
|
|
68
|
-
column: 1,
|
|
69
|
-
message: "secret block has no opening :::secret line",
|
|
70
|
-
});
|
|
71
|
-
result.push(line);
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
const inner = body.join("\n").trim();
|
|
75
|
-
if (target === "book") {
|
|
76
|
-
result.push("", "**GM note**", "", inner, "");
|
|
77
|
-
} else {
|
|
78
|
-
const html = renderMarkdown(inner).trim();
|
|
79
|
-
if (target === "foundry") {
|
|
80
|
-
const id = crypto
|
|
81
|
-
.createHash("sha256")
|
|
82
|
-
.update(`${opening}:${inner}`)
|
|
83
|
-
.digest("hex")
|
|
84
|
-
.slice(0, 12);
|
|
85
|
-
result.push(
|
|
86
|
-
"",
|
|
87
|
-
`<section class="secret" id="secret-${id}">`,
|
|
88
|
-
html,
|
|
89
|
-
"</section>",
|
|
90
|
-
"",
|
|
91
|
-
);
|
|
92
|
-
} else {
|
|
93
|
-
result.push("", "<details><summary>Spoiler</summary>", html, "</details>", "");
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
opening = -1;
|
|
97
|
-
body = [];
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
if (opening >= 0) body.push(line);
|
|
101
|
-
else result.push(line);
|
|
102
|
-
}
|
|
103
|
-
if (opening >= 0) {
|
|
104
|
-
errors.push({
|
|
105
|
-
line: opening + 1,
|
|
106
|
-
column: 1,
|
|
107
|
-
message: "secret block needs a closing ::: line",
|
|
108
|
-
});
|
|
109
|
-
result.push(":::secret", ...body);
|
|
110
|
-
}
|
|
111
|
-
return { markdown: result.join("\n"), errors };
|
|
112
|
-
}
|