@heroiclands/package-build 22.26.0 → 22.28.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 +17 -0
- package/README.md +3 -1
- package/assets/styles/content-base.css +46 -3
- package/bin/content-commands.mjs +53 -5
- package/ci/publish-npm.mjs +49 -0
- package/docs/api.md +44 -24
- package/docs/authoring/first-note.md +1 -1
- package/docs/authoring/frontmatter.md +2 -2
- package/docs/authoring/links-and-markup.md +96 -149
- package/docs/content-format.md +7 -3
- package/docs/guides/government-migration.md +52 -0
- package/docs/guides/site.md +5 -5
- package/docs/reference/format-details.md +131 -230
- package/docs/reference/note-types.md +27 -29
- package/docs/reference/theme-pages.md +31 -29
- package/engine/anchors.mjs +85 -13
- package/engine/art-fields.mjs +1 -1
- package/engine/base-compiler.mjs +9 -1
- package/engine/bundles.mjs +1 -1
- package/engine/code-fences.mjs +6 -2
- package/engine/content-alerts.mjs +195 -0
- package/engine/content-blocks.mjs +119 -241
- package/engine/content-embeds.mjs +1 -1
- package/engine/content-figures.mjs +166 -230
- package/engine/content-format-check.mjs +3 -2
- package/engine/content-format.yaml +0 -5
- package/engine/content-images.mjs +3 -3
- package/engine/content-poetry.mjs +141 -0
- package/engine/content-spans.mjs +177 -0
- package/engine/folder-notes.mjs +2 -3
- package/engine/foundry-entries.mjs +2 -0
- package/engine/frontmatter-lint.mjs +2 -1
- package/engine/heading-attributes.mjs +1 -1
- package/engine/helpers.mjs +8 -3
- package/engine/holdings.mjs +165 -273
- package/engine/index.mjs +2 -2
- package/engine/infobox.mjs +1 -1
- package/engine/journals.mjs +8 -6
- package/engine/markdown-expressions.mjs +9 -3
- package/engine/metadata-index.mjs +2 -3
- package/engine/note-vocabulary.mjs +19 -15
- package/engine/pdf-build.mjs +1 -1
- package/engine/pdf-render.mjs +90 -31
- package/engine/populations.mjs +17 -83
- package/engine/site-build.mjs +44 -16
- package/engine/site-preview.mjs +22 -6
- package/engine/stub-lint.mjs +20 -64
- package/engine/wikilinks.mjs +18 -4
- package/githooks/pre-push +1 -1
- package/hugo-theme/layouts/partials/holdings/entry.html +2 -2
- package/hugo-theme/layouts/partials/holdings.html +14 -14
- package/hugo-theme/static/css/style.css +40 -2
- package/package.json +3 -1
- package/sohl/item-fields.mjs +0 -16
- package/types/engine/anchors.d.mts +13 -4
- package/types/engine/art-fields.d.mts +1 -1
- package/types/engine/code-fences.d.mts +3 -1
- package/types/engine/content-alerts.d.mts +62 -0
- package/types/engine/content-blocks.d.mts +3 -10
- package/types/engine/content-embeds.d.mts +1 -1
- package/types/engine/content-figures.d.mts +13 -20
- package/types/engine/content-images.d.mts +1 -1
- package/types/engine/content-poetry.d.mts +32 -0
- package/types/engine/content-spans.d.mts +18 -0
- package/types/engine/holdings.d.mts +59 -105
- package/types/engine/markdown-expressions.d.mts +1 -1
- package/types/engine/note-vocabulary.d.mts +12 -4
- package/types/engine/populations.d.mts +4 -13
- package/types/engine/site-build.d.mts +3 -3
- package/types/engine/wikilinks.d.mts +5 -1
package/engine/site-preview.mjs
CHANGED
|
@@ -284,12 +284,28 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
|
|
|
284
284
|
});
|
|
285
285
|
const related = relatedPages(edges, entries).get(page.url);
|
|
286
286
|
if (related) result.data.related = related;
|
|
287
|
-
const holdings = holdingsPages(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
287
|
+
const holdings = holdingsPages(
|
|
288
|
+
[
|
|
289
|
+
...allPages.map((item) =>
|
|
290
|
+
holdingsNode(item.fm, {
|
|
291
|
+
title: item.name,
|
|
292
|
+
url: item.url,
|
|
293
|
+
package: item.pkg ?? config.contentPackage,
|
|
294
|
+
}),
|
|
295
|
+
),
|
|
296
|
+
...foreignHoldingsNodes(snapshot.gates.foreign.index),
|
|
297
|
+
],
|
|
298
|
+
{
|
|
299
|
+
governmentNodes: records
|
|
300
|
+
.filter((record) => !isGmNote(record))
|
|
301
|
+
.map((record) =>
|
|
302
|
+
holdingsNode(record, {
|
|
303
|
+
title: record.name?.full ?? record.shortcode,
|
|
304
|
+
package: config.contentPackage,
|
|
305
|
+
}),
|
|
306
|
+
),
|
|
307
|
+
},
|
|
308
|
+
);
|
|
293
309
|
Object.assign(result.data, holdings.get(page.url));
|
|
294
310
|
return {
|
|
295
311
|
ok: true,
|
package/engine/stub-lint.mjs
CHANGED
|
@@ -12,34 +12,16 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Every typed note needs a nonempty body.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* A missing or whitespace-only body is an error, whether or not the note has
|
|
18
|
+
* a `draft` tag or a frontmatter description. Placeholder-only bodies remain
|
|
19
|
+
* errors. A nonempty body below twenty-five words receives a warning when it
|
|
20
|
+
* is not tagged `draft`; the author decides whether that short note is done.
|
|
20
21
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* 2. **A stub is not tagged `draft`.** A thing not started is not a thing in
|
|
25
|
-
* progress. An **error**.
|
|
26
|
-
* 3. **A body that renders to nothing is an abandoned draft, not a stub.** This
|
|
27
|
-
* is what keeps the empty-body rule's severity honest: the softer cases are
|
|
28
|
-
* *reported* rather than silently treated as one state or the other. An
|
|
29
|
-
* **error**.
|
|
30
|
-
* 4. **A short unmarked body may be a draft that forgot its marker.** A
|
|
31
|
-
* **warning**, never an error — a note reading `See [[affiliation-meivor|Mëivōr]]`
|
|
32
|
-
* says everything it has to say, and a corpus holds many of them.
|
|
33
|
-
* 5. **A long-standing draft is a report, not a finding.** It is unfinished,
|
|
34
|
-
* which the tag already says, so the oldest are listed and nobody is asked
|
|
35
|
-
* to dismiss a warning they cannot clear.
|
|
36
|
-
* 6. **The counts are reported**, per package and per type, so the ratio of
|
|
37
|
-
* written to unwritten is visible on every run rather than discovered in a
|
|
38
|
-
* year.
|
|
39
|
-
*
|
|
40
|
-
* **What this does not decide is what earns a body.** Whether a given village
|
|
41
|
-
* deserves prose is a question about a setting, and it belongs in the
|
|
42
|
-
* repository that ships it. The toolchain's job is to make promotion free.
|
|
22
|
+
* Counts and the oldest drafts remain reports. Classification and indexing
|
|
23
|
+
* still derive full, draft and stub states from the existing note records;
|
|
24
|
+
* validation reports missing content without changing those records.
|
|
43
25
|
*
|
|
44
26
|
* @module
|
|
45
27
|
*/
|
|
@@ -50,7 +32,7 @@ import path from "node:path";
|
|
|
50
32
|
import { authoredFrontmatter, isNoteRecord, isStub, noteFile } from "./index-records.mjs";
|
|
51
33
|
import { positionInFrontmatter, positionOfLiteral } from "./diagnostics.mjs";
|
|
52
34
|
import { parseMarkdownFile } from "./helpers.mjs";
|
|
53
|
-
import { bodyWordCount,
|
|
35
|
+
import { bodyWordCount, placeholderBody } from "./note-state.mjs";
|
|
54
36
|
import { isDraftNote } from "./note-vocabulary.mjs";
|
|
55
37
|
|
|
56
38
|
/**
|
|
@@ -67,16 +49,6 @@ export const SHORT_BODY_WORDS = 25;
|
|
|
67
49
|
/** How many of the oldest drafts the report names. */
|
|
68
50
|
const OLDEST_DRAFTS = 10;
|
|
69
51
|
|
|
70
|
-
/**
|
|
71
|
-
* Whether a note states a description.
|
|
72
|
-
*
|
|
73
|
-
* @param {Record<string, any>} fm - The note's authored frontmatter.
|
|
74
|
-
* @returns {boolean} Whether `description` carries a sentence.
|
|
75
|
-
*/
|
|
76
|
-
function hasDescription(fm) {
|
|
77
|
-
return String(fm?.description ?? "").trim() !== "";
|
|
78
|
-
}
|
|
79
|
-
|
|
80
52
|
/**
|
|
81
53
|
* Check one note's state, and say where the fault is.
|
|
82
54
|
*
|
|
@@ -87,29 +59,14 @@ function checkNote({ fm, body, file, raw }) {
|
|
|
87
59
|
const findings = [];
|
|
88
60
|
const at = (key, value) => positionInFrontmatter(raw, key, value, { topLevel: true });
|
|
89
61
|
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"a `description`, which is the only sentence a reader gets " +
|
|
99
|
-
"and what every table listing it prints",
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
if (isDraftNote(fm)) {
|
|
103
|
-
findings.push({
|
|
104
|
-
file,
|
|
105
|
-
...at("type", fm.type),
|
|
106
|
-
severity: "error",
|
|
107
|
-
message:
|
|
108
|
-
"this note has no body and is tagged `draft`, and a thing " +
|
|
109
|
-
"not started is not a thing in progress — remove the tag, " +
|
|
110
|
-
"or write a body",
|
|
111
|
-
});
|
|
112
|
-
}
|
|
62
|
+
if (String(body ?? "").trim() === "") {
|
|
63
|
+
findings.push({
|
|
64
|
+
file,
|
|
65
|
+
...at("type", fm.type),
|
|
66
|
+
severity: "error",
|
|
67
|
+
message:
|
|
68
|
+
"this note has no body — write a nonempty body, even when it is tagged `draft`",
|
|
69
|
+
});
|
|
113
70
|
return findings;
|
|
114
71
|
}
|
|
115
72
|
|
|
@@ -122,8 +79,7 @@ function checkNote({ fm, body, file, raw }) {
|
|
|
122
79
|
severity: "error",
|
|
123
80
|
message:
|
|
124
81
|
`this note's body says only "${phrase}", so it publishes a page ` +
|
|
125
|
-
`that tells a reader nothing —
|
|
126
|
-
`stub, or write it`,
|
|
82
|
+
`that tells a reader nothing — replace the placeholder with content`,
|
|
127
83
|
});
|
|
128
84
|
return findings;
|
|
129
85
|
}
|
|
@@ -204,8 +160,8 @@ export function lintNoteStates(contentBase, { records, contentPackage } = {}) {
|
|
|
204
160
|
if (state === "draft") drafts.push({ file, modified: fs.statSync(absPath).mtime });
|
|
205
161
|
|
|
206
162
|
// Held to the rules only where there is a note to hold: a file that
|
|
207
|
-
// declares no type declares nothing for them to read
|
|
208
|
-
//
|
|
163
|
+
// declares no type declares nothing for them to read; requiring its
|
|
164
|
+
// body would report vault scaffolding as content.
|
|
209
165
|
if (record.type) findings.push(...checkNote({ fm, body, file, raw }));
|
|
210
166
|
}
|
|
211
167
|
|
package/engine/wikilinks.mjs
CHANGED
|
@@ -100,7 +100,8 @@ export function anchorPageId(noteId, anchorSlug) {
|
|
|
100
100
|
*
|
|
101
101
|
* @param {Array<{type: string, id: string, shortcode?: string|null,
|
|
102
102
|
* name?: string, pack?: string, docPack?: string, none?: boolean,
|
|
103
|
-
* draft?: boolean
|
|
103
|
+
* draft?: boolean, anchors?: Set<string>, anchorUuids?: Record<string,string>,
|
|
104
|
+
* docAnchorUuids?: Record<string,string>}>} docs -
|
|
104
105
|
* One entry per content note. `pack` / `docPack` name the packs the note's
|
|
105
106
|
* document and its documentation entry landed in; omitted, the conventional
|
|
106
107
|
* one-pack-per-type names stand in. `none` says the note declares
|
|
@@ -153,9 +154,11 @@ export function buildWikilinkIndex(
|
|
|
153
154
|
// that stored value. Nothing downstream assembles a UUID from parts, so a
|
|
154
155
|
// link and its target cannot disagree about where the document lives.
|
|
155
156
|
const uuidByDoc = new Map();
|
|
157
|
+
const byId = new Map();
|
|
156
158
|
|
|
157
159
|
for (const d of docs) {
|
|
158
160
|
if (!d.id || !d.type) continue;
|
|
161
|
+
byId.set(`${d.type}/${d.id}`, d);
|
|
159
162
|
types.add(norm(d.type));
|
|
160
163
|
|
|
161
164
|
uuidByDoc.set(
|
|
@@ -172,6 +175,8 @@ export function buildWikilinkIndex(
|
|
|
172
175
|
// type and a UUID carries the pack name, so the address
|
|
173
176
|
// cannot be derived from the type alone.
|
|
174
177
|
uuid: compendiumUuid(packageId, d.type, d.id, d.pack),
|
|
178
|
+
anchors: d.anchorUuids,
|
|
179
|
+
docAnchors: d.docAnchorUuids,
|
|
175
180
|
// The readable note has a separate JournalEntry whose id
|
|
176
181
|
// is derived from the item's id.
|
|
177
182
|
docUuid: compendiumUuid(packageId, "doc", itemDocEntryId(d.id), d.docPack),
|
|
@@ -216,6 +221,7 @@ export function buildWikilinkIndex(
|
|
|
216
221
|
foreignReferences,
|
|
217
222
|
types,
|
|
218
223
|
uuidByDoc,
|
|
224
|
+
byId,
|
|
219
225
|
packageId,
|
|
220
226
|
/** The content package this build publishes, which an art address defaults to. */
|
|
221
227
|
contentPackage: contentPackage ?? packageId,
|
|
@@ -433,7 +439,7 @@ export function convertWikilinks(markdown, { type, id, pack, docPack, index, cap
|
|
|
433
439
|
// Kept for the foreign fallback below, which needs the parsed address.
|
|
434
440
|
let qualifiedRead = null;
|
|
435
441
|
if (target === "" && slug) {
|
|
436
|
-
doc = { type, id, pack, docPack };
|
|
442
|
+
doc = index.byId?.get(`${type}/${id}`) ?? { type, id, pack, docPack };
|
|
437
443
|
} else {
|
|
438
444
|
const qualified = parsed.problem ?? parsed.target;
|
|
439
445
|
qualifiedRead = qualified;
|
|
@@ -546,7 +552,13 @@ export function convertWikilinks(markdown, { type, id, pack, docPack, index, cap
|
|
|
546
552
|
// reader. A foreign anchor has always been checked this way —
|
|
547
553
|
// the manifest carries the map — and a local one now is too, from the
|
|
548
554
|
// anchor set the index carries.
|
|
549
|
-
|
|
555
|
+
const anchorUuids = itemDoc ? addresses.docAnchors : addresses.anchors;
|
|
556
|
+
if (
|
|
557
|
+
slug &&
|
|
558
|
+
isJournal &&
|
|
559
|
+
((doc.anchors && !doc.anchors.has(slug)) ||
|
|
560
|
+
(anchorUuids && !Object.hasOwn(anchorUuids, slug)))
|
|
561
|
+
) {
|
|
550
562
|
unresolved.push({
|
|
551
563
|
link: all,
|
|
552
564
|
target,
|
|
@@ -558,7 +570,9 @@ export function convertWikilinks(markdown, { type, id, pack, docPack, index, cap
|
|
|
558
570
|
return unresolvedLink(text || doc.name || target, target);
|
|
559
571
|
}
|
|
560
572
|
const uuid =
|
|
561
|
-
slug && isJournal ?
|
|
573
|
+
slug && isJournal ?
|
|
574
|
+
(anchorUuids?.[slug] ?? pageUuid(entryUuid, anchorPageId(entryId, slug)))
|
|
575
|
+
: entryUuid;
|
|
562
576
|
const link = `@UUID[${uuid}]{${text}}`;
|
|
563
577
|
// A link into a note that exists but is not written renders marked.
|
|
564
578
|
// Presentation only — the UUID above is unchanged, and a
|
package/githooks/pre-push
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# container, over a clean export of HEAD — so a red GitHub check is something
|
|
5
5
|
# you chose rather than something you discover.
|
|
6
6
|
#
|
|
7
|
-
# The steps are not listed here. `
|
|
7
|
+
# The steps are not listed here. `ci/ci-steps.mjs` reads them out of
|
|
8
8
|
# `.github/workflows/build.yml`, because a hook holding its own copy of the
|
|
9
9
|
# command list is a second statement of one thing, and the copy is the one that
|
|
10
10
|
# goes stale — silently, since running four of five steps still exits 0.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{{- /*
|
|
2
2
|
One entry of a holdings list — `{title, url, type, subType}`, as
|
|
3
|
-
`@heroiclands/package-build` derives from `contains`, `
|
|
4
|
-
`
|
|
3
|
+
`@heroiclands/package-build` derives from `contains`, `governed_by` and
|
|
4
|
+
`governed_places` (see the "holdings card" section of the README).
|
|
5
5
|
|
|
6
6
|
`url` is absent for a stub: a note with an empty body, which carries its
|
|
7
7
|
facts into every list that names it but publishes no page of its own. The
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{{- /*
|
|
2
|
-
|
|
2
|
+
Geography and government card.
|
|
3
3
|
|
|
4
4
|
Three small tables, rendered after the body and before the Related card
|
|
5
5
|
(see `page-body.html`), from front matter `@heroiclands/package-build`
|
|
6
6
|
derives and a note never authors itself:
|
|
7
7
|
|
|
8
8
|
contains: [{ title, url, type, subType }] places sitting within this one
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
governed_by: [{ title, url, type, subType }] affiliations governing this place
|
|
10
|
+
governed_places: [{ title, url, type, subType }] places this affiliation governs
|
|
11
11
|
|
|
12
|
-
A place page carries `contains` and/or `
|
|
13
|
-
carries `
|
|
12
|
+
A place page carries `contains` and/or `governed_by`; an affiliation page
|
|
13
|
+
carries `governed_places`. Neither is checked here — presence of the key is the
|
|
14
14
|
only test, the same silent-disappear convention the Related card follows:
|
|
15
15
|
a page carrying none of the three renders no card at all.
|
|
16
16
|
|
|
17
|
-
`contains` and `
|
|
17
|
+
`contains` and `governed_places` list places, so each is broken into groups by
|
|
18
18
|
kind (`holdings/kind-groups.html`) — the towns together, the hamlets
|
|
19
|
-
together. `
|
|
19
|
+
together. `governed_by` lists affiliations, whose kinds have no fixed
|
|
20
20
|
hierarchy to group by, so it stays one flat grid with the kind printed
|
|
21
21
|
beside each name instead of as a group heading.
|
|
22
22
|
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
was established.
|
|
28
28
|
*/ -}}
|
|
29
29
|
{{- $within := .Params.contains -}}
|
|
30
|
-
{{- $
|
|
31
|
-
{{- $
|
|
30
|
+
{{- $government := .Params.governed_by -}}
|
|
31
|
+
{{- $governed := .Params.governed_places -}}
|
|
32
32
|
|
|
33
|
-
{{- if or $within $
|
|
33
|
+
{{- if or $within $government $governed -}}
|
|
34
34
|
<section class="holdings" data-pagefind-ignore>
|
|
35
35
|
{{- with $within }}
|
|
36
36
|
<div class="holdings-table">
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
{{ partial "holdings/kind-groups.html" . }}
|
|
39
39
|
</div>
|
|
40
40
|
{{- end }}
|
|
41
|
-
{{- with $
|
|
41
|
+
{{- with $government }}
|
|
42
42
|
<div class="holdings-table">
|
|
43
|
-
<h3 class="holdings-table-label">
|
|
43
|
+
<h3 class="holdings-table-label">Governed by</h3>
|
|
44
44
|
<ul class="holdings-table-grid">
|
|
45
45
|
{{- range . }}
|
|
46
46
|
<li>
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
</ul>
|
|
52
52
|
</div>
|
|
53
53
|
{{- end }}
|
|
54
|
-
{{- with $
|
|
54
|
+
{{- with $governed }}
|
|
55
55
|
<div class="holdings-table">
|
|
56
|
-
<h3 class="holdings-table-label">
|
|
56
|
+
<h3 class="holdings-table-label">Governed places</h3>
|
|
57
57
|
{{ partial "holdings/kind-groups.html" . }}
|
|
58
58
|
</div>
|
|
59
59
|
{{- end }}
|
|
@@ -2868,9 +2868,9 @@ summary.info-sidebar-title::after {
|
|
|
2868
2868
|
group's entries laid out as a CSS grid that reflows its own column count
|
|
2869
2869
|
from the available width — two or three columns on a desktop, one on a
|
|
2870
2870
|
phone — with no breakpoint needed. Related groups by the connected page's
|
|
2871
|
-
`type`; Holdings groups `contains` and `
|
|
2871
|
+
`type`; Holdings groups `contains` and `governed_places` by place `subType`
|
|
2872
2872
|
(`.holdings-kind-group`, one level deeper, for the region/settlement/site
|
|
2873
|
-
hierarchy) and leaves `
|
|
2873
|
+
hierarchy) and leaves `governed_by` a flat grid with the kind printed beside
|
|
2874
2874
|
each name. */
|
|
2875
2875
|
|
|
2876
2876
|
.related,
|
|
@@ -2980,3 +2980,41 @@ summary.info-sidebar-title::after {
|
|
|
2980
2980
|
padding: 1.25rem 1.25rem;
|
|
2981
2981
|
}
|
|
2982
2982
|
}
|
|
2983
|
+
|
|
2984
|
+
/* GitHub-style author alerts remain open and inherit the prose foreground. */
|
|
2985
|
+
.single-content .alert {
|
|
2986
|
+
border-inline-start: 0.25rem solid var(--pb-alert-color);
|
|
2987
|
+
padding: 0.5rem 1rem;
|
|
2988
|
+
margin: 1rem 0;
|
|
2989
|
+
color: inherit;
|
|
2990
|
+
background: transparent;
|
|
2991
|
+
}
|
|
2992
|
+
.single-content .alert-title {
|
|
2993
|
+
display: flex;
|
|
2994
|
+
align-items: center;
|
|
2995
|
+
gap: 0.5em;
|
|
2996
|
+
margin: 0 0 0.5rem;
|
|
2997
|
+
font-weight: 600;
|
|
2998
|
+
color: var(--pb-alert-color);
|
|
2999
|
+
}
|
|
3000
|
+
.single-content .alert-icon {
|
|
3001
|
+
width: 1em;
|
|
3002
|
+
height: 1em;
|
|
3003
|
+
flex-shrink: 0;
|
|
3004
|
+
fill: currentColor;
|
|
3005
|
+
}
|
|
3006
|
+
.single-content .alert > :last-child {
|
|
3007
|
+
margin-bottom: 0;
|
|
3008
|
+
}
|
|
3009
|
+
.single-content .alert-note {
|
|
3010
|
+
--pb-alert-color: #4493f8;
|
|
3011
|
+
}
|
|
3012
|
+
.single-content .alert-tip {
|
|
3013
|
+
--pb-alert-color: #3fb950;
|
|
3014
|
+
}
|
|
3015
|
+
.single-content .alert-important {
|
|
3016
|
+
--pb-alert-color: #ab7df8;
|
|
3017
|
+
}
|
|
3018
|
+
.single-content .alert-warning {
|
|
3019
|
+
--pb-alert-color: #d29922;
|
|
3020
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.28.0",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -182,6 +182,8 @@
|
|
|
182
182
|
"scripts": {
|
|
183
183
|
"test": "vitest run",
|
|
184
184
|
"test:watch": "vitest",
|
|
185
|
+
"build": "node bin/package-build.mjs ci",
|
|
186
|
+
"deploy:npm": "node ci/publish-npm.mjs",
|
|
185
187
|
"prepack": "run-s build:types build:asset-index",
|
|
186
188
|
"build:types": "tsc -p tsconfig.dts.json",
|
|
187
189
|
"build:asset-index": "node ci/emit-asset-index.mjs",
|
package/sohl/item-fields.mjs
CHANGED
|
@@ -449,22 +449,6 @@ export const ITEM_FIELDS = Object.freeze({
|
|
|
449
449
|
default: null,
|
|
450
450
|
describe: "Where the affiliation's authority sits, by place address.",
|
|
451
451
|
},
|
|
452
|
-
{
|
|
453
|
-
// The geographic relation, kept apart from the organisational one
|
|
454
|
-
// above: `parents` is *subordinate to*, this is *holds sway over*.
|
|
455
|
-
//
|
|
456
|
-
// Authored plural and emitted singular, as the content format's
|
|
457
|
-
// mapping row states (`data.domains` → `system.domain`). The two
|
|
458
|
-
// spellings are deliberate rather than a slip, so the declaration
|
|
459
|
-
// carries both rather than either side guessing.
|
|
460
|
-
name: "data.domains",
|
|
461
|
-
legacyKey: "domains",
|
|
462
|
-
to: "domain",
|
|
463
|
-
...ADDRESS_LIST,
|
|
464
|
-
address: PLACE_ITEMS,
|
|
465
|
-
default: [],
|
|
466
|
-
describe: "Places this affiliation holds sway over, by address.",
|
|
467
|
-
},
|
|
468
452
|
]),
|
|
469
453
|
|
|
470
454
|
affliction: Object.freeze([
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The identifiers a note declares on headings, captions, divs, poems and spans.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* slug and starts an addressable journal page.
|
|
4
|
+
* Headings and captions may start journal pages. Div, poem and inline-span
|
|
5
|
+
* anchors address locations within their containing pages.
|
|
7
6
|
*
|
|
8
7
|
* @param {string} body - The note's markdown body, frontmatter already removed.
|
|
9
8
|
* @param {number} [bodyLine] - The 1-based file line the body starts on, from
|
|
@@ -22,3 +21,13 @@ export function collectAnchors(body: string, bodyLine?: number, resolveRole?: (a
|
|
|
22
21
|
level: number;
|
|
23
22
|
line: number;
|
|
24
23
|
}>;
|
|
24
|
+
/** Collisions involving a div, verse or inline-span anchor.
|
|
25
|
+
* Heading and caption collisions are diagnosed by their existing scanners.
|
|
26
|
+
* @param {string} body - Authored Markdown.
|
|
27
|
+
* @returns {Array<{line: number, column: number, message: string}>} Located collisions.
|
|
28
|
+
*/
|
|
29
|
+
export function markupAnchorFindings(body: string): Array<{
|
|
30
|
+
line: number;
|
|
31
|
+
column: number;
|
|
32
|
+
message: string;
|
|
33
|
+
}>;
|
|
@@ -144,7 +144,7 @@ export function resolveArtRecord(index: object, value: unknown, defaultType: str
|
|
|
144
144
|
* carries once an embed's address has been rewritten into the ordinary image
|
|
145
145
|
* every surface renders ({@link module:engine/content-embeds.resolveEmbeds}).
|
|
146
146
|
*
|
|
147
|
-
* This is the lookup a
|
|
147
|
+
* This is the lookup a captioned item's `map` counter reaches through —
|
|
148
148
|
* see {@link module:engine/content-figures.scanFigures}'s `resolveRole` —
|
|
149
149
|
* for a caller that reads a body after that rewrite: a Foundry journal, an
|
|
150
150
|
* item or actor's documentation, and the book.
|
|
@@ -17,11 +17,13 @@ export function parseHeaderArgs(info: string): {
|
|
|
17
17
|
* @param {object} [options]
|
|
18
18
|
* @param {boolean} [options.spans=true] - Include inline code spans. Set false
|
|
19
19
|
* to consider only block-level code.
|
|
20
|
+
* @param {boolean} [options.poetryAsProse=true] - Treat verse bodies as inline prose.
|
|
20
21
|
* @returns {Array<{start: number, end: number}>} Non-overlapping regions, in
|
|
21
22
|
* source order. `end` is exclusive.
|
|
22
23
|
*/
|
|
23
|
-
export function codeRegions(markdown: string | null | undefined, { spans }?: {
|
|
24
|
+
export function codeRegions(markdown: string | null | undefined, { spans, poetryAsProse }?: {
|
|
24
25
|
spans?: boolean | undefined;
|
|
26
|
+
poetryAsProse?: boolean | undefined;
|
|
25
27
|
}): Array<{
|
|
26
28
|
start: number;
|
|
27
29
|
end: number;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** Read alerts outside code examples, retaining inclusive source line ranges.
|
|
2
|
+
* @param {string} source - Authored Markdown.
|
|
3
|
+
*/
|
|
4
|
+
export function scanAlerts(source: string): {
|
|
5
|
+
blocks: {
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
depth: number;
|
|
9
|
+
type: string;
|
|
10
|
+
title: any;
|
|
11
|
+
id: string;
|
|
12
|
+
classes: never[];
|
|
13
|
+
attributes: {
|
|
14
|
+
[k: string]: any;
|
|
15
|
+
};
|
|
16
|
+
body: string;
|
|
17
|
+
}[];
|
|
18
|
+
errors: {
|
|
19
|
+
line: number;
|
|
20
|
+
column: number;
|
|
21
|
+
message: string;
|
|
22
|
+
}[];
|
|
23
|
+
};
|
|
24
|
+
/** Render alert wrappers while preserving ordinary Markdown and note footnotes.
|
|
25
|
+
* @param {string} source - Authored Markdown.
|
|
26
|
+
* @param {"web"|"foundry"} [target="web"] - Publishing surface.
|
|
27
|
+
*/
|
|
28
|
+
export function renderAlerts(source: string, target?: "web" | "foundry"): {
|
|
29
|
+
markdown: string;
|
|
30
|
+
errors: {
|
|
31
|
+
line: number;
|
|
32
|
+
column: number;
|
|
33
|
+
message: string;
|
|
34
|
+
}[];
|
|
35
|
+
};
|
|
36
|
+
/** Mark alert blockquotes and remove their marker before inline parsing.
|
|
37
|
+
* @param {import("markdown-it")} md - Markdown parser to extend.
|
|
38
|
+
*/
|
|
39
|
+
export function alertMarkdownPlugin(md: typeof import("markdown-it")): void;
|
|
40
|
+
/** Alert titles and static icons shared by all publishing surfaces. */
|
|
41
|
+
export const ALERT_TYPES: Readonly<{
|
|
42
|
+
note: {
|
|
43
|
+
title: string;
|
|
44
|
+
icon: string;
|
|
45
|
+
svg: string;
|
|
46
|
+
};
|
|
47
|
+
tip: {
|
|
48
|
+
title: string;
|
|
49
|
+
icon: string;
|
|
50
|
+
svg: string;
|
|
51
|
+
};
|
|
52
|
+
important: {
|
|
53
|
+
title: string;
|
|
54
|
+
icon: string;
|
|
55
|
+
svg: string;
|
|
56
|
+
};
|
|
57
|
+
warning: {
|
|
58
|
+
title: string;
|
|
59
|
+
icon: string;
|
|
60
|
+
svg: string;
|
|
61
|
+
};
|
|
62
|
+
}>;
|
|
@@ -67,20 +67,13 @@ export function renderBlocks(source: string, target: "foundry" | "web"): {
|
|
|
67
67
|
export function renderWithheldSections(source: string): string;
|
|
68
68
|
/** The blocks an author may open, with the heading each takes by default. */
|
|
69
69
|
export const BLOCK_NAMES: Readonly<{
|
|
70
|
-
info: "Info";
|
|
71
70
|
secret: "Secret";
|
|
72
|
-
warn: "Warn";
|
|
73
71
|
}>;
|
|
74
72
|
/**
|
|
75
|
-
*
|
|
73
|
+
* Container fences whose bodies are scanned and rendered recursively.
|
|
76
74
|
*
|
|
77
|
-
* A
|
|
78
|
-
*
|
|
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.
|
|
75
|
+
* A secret may hold general divs; a general div may hold another div or a
|
|
76
|
+
* secret. A secret nested directly within another secret is refused.
|
|
84
77
|
*
|
|
85
78
|
* @type {readonly string[]}
|
|
86
79
|
*/
|
|
@@ -49,7 +49,7 @@ export function resolveEmbed(index: object, embed: ParsedEmbed): {
|
|
|
49
49
|
* The role an address's asset declares, read through the same resolution an
|
|
50
50
|
* embed's address takes.
|
|
51
51
|
*
|
|
52
|
-
* This is the lookup a
|
|
52
|
+
* This is the lookup a captioned item's `map` counter reaches through —
|
|
53
53
|
* see {@link module:engine/content-figures.scanFigures}'s `resolveRole` — for
|
|
54
54
|
* an address still in its authored form, before {@link resolveEmbeds} has
|
|
55
55
|
* rewritten it into the ordinary image every surface renders.
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
/** Number a sequence of figures with counters shared across documents. */
|
|
2
2
|
export function numberFigures(figures: any, counts: any): any;
|
|
3
3
|
/**
|
|
4
|
-
* Read
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @param {string} source - A note body with expanded tables.
|
|
4
|
+
* Read leading captions and their following blocks outside literal fences.
|
|
5
|
+
* Historical figure API names remain for caption references and consumers.
|
|
6
|
+
* @param {string} source - The authored or expanded note body.
|
|
9
7
|
* @param {object} [options] - Options.
|
|
10
|
-
* @param {(address: string) => string|undefined} [options.resolveRole] -
|
|
11
|
-
* a picture's address to the role its asset declares, so a picture whose
|
|
12
|
-
* asset declares `role: map` counts as `map` rather than `figure`. Omitted,
|
|
13
|
-
* every picture counts as `figure` — the same reading a role other than
|
|
14
|
-
* `map` gets.
|
|
8
|
+
* @param {(address: string) => string|undefined} [options.resolveRole] - Asset role lookup.
|
|
15
9
|
* @returns {{figures: Array<{id: string, slug: string, caption: string,
|
|
16
|
-
* hasCaption: boolean, classes: string[],
|
|
17
|
-
*
|
|
18
|
-
* captionStart: number, captionEnd: number, close: number}>,
|
|
10
|
+
* hasCaption: boolean, classes: string[], attributes: Record<string,string>,
|
|
11
|
+
* kind: string, numbered: boolean, number: number, label: string, line: number,
|
|
12
|
+
* bodyStart: number, bodyEnd: number, captionStart: number, captionEnd: number, close: number}>,
|
|
19
13
|
* errors: Array<{line: number, column: number, message: string}>}}
|
|
20
14
|
*/
|
|
21
15
|
export function scanFigures(source: string, { resolveRole }?: {
|
|
@@ -27,7 +21,9 @@ export function scanFigures(source: string, { resolveRole }?: {
|
|
|
27
21
|
caption: string;
|
|
28
22
|
hasCaption: boolean;
|
|
29
23
|
classes: string[];
|
|
24
|
+
attributes: Record<string, string>;
|
|
30
25
|
kind: string;
|
|
26
|
+
numbered: boolean;
|
|
31
27
|
number: number;
|
|
32
28
|
label: string;
|
|
33
29
|
line: number;
|
|
@@ -44,7 +40,7 @@ export function scanFigures(source: string, { resolveRole }?: {
|
|
|
44
40
|
}>;
|
|
45
41
|
};
|
|
46
42
|
/**
|
|
47
|
-
* Render
|
|
43
|
+
* Render leading captions and their next blocks as HTML, leaving other Markdown untouched.
|
|
48
44
|
*
|
|
49
45
|
* A code, table or prose figure's contents are left as Markdown inside the
|
|
50
46
|
* wrapper, blank-line separated from its tags exactly as
|
|
@@ -84,6 +80,7 @@ export function renderFigureBlocks(source: string, renderMarkdown?: (markdown: s
|
|
|
84
80
|
/** A figure's automatically determined kind, and the word that labels it. */
|
|
85
81
|
export const FIGURE_NAMES: Readonly<{
|
|
86
82
|
code: "Code";
|
|
83
|
+
example: "Example";
|
|
87
84
|
table: "Table";
|
|
88
85
|
figure: "Figure";
|
|
89
86
|
map: "Map";
|
|
@@ -91,12 +88,8 @@ export const FIGURE_NAMES: Readonly<{
|
|
|
91
88
|
prose: "Prose";
|
|
92
89
|
}>;
|
|
93
90
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* `.border` states that the captioned thing is drawn inside a border, and each
|
|
97
|
-
* medium owns what that means. The vocabulary is closed: a class absent from it
|
|
98
|
-
* is a finding naming the class.
|
|
99
|
-
*
|
|
91
|
+
* Historical built-in presentation classes, retained for theme consumers.
|
|
92
|
+
* Captions now accept arbitrary classes through the shared attribute grammar.
|
|
100
93
|
* @type {readonly string[]}
|
|
101
94
|
*/
|
|
102
95
|
export const FIGURE_CLASSES: readonly string[];
|
|
@@ -67,7 +67,7 @@ export function escapeHtml(text: string): string;
|
|
|
67
67
|
* function, so the two cannot drift into styling the same directive through
|
|
68
68
|
* different class names.
|
|
69
69
|
*
|
|
70
|
-
* **No `<figcaption>` is emitted here.** A
|
|
70
|
+
* **No `<figcaption>` is emitted here.** A captioned item draws its own
|
|
71
71
|
* label and authored caption around whatever this returns; the alt text stays
|
|
72
72
|
* on the `img` element alone.
|
|
73
73
|
*
|