@heroiclands/package-build 22.25.0 → 22.27.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 +4 -0
- package/assets/styles/content-base.css +46 -3
- package/docs/api.md +35 -12
- package/docs/authoring/frontmatter.md +21 -0
- package/docs/authoring/links-and-markup.md +96 -149
- package/docs/content-format.md +6 -2
- package/docs/guides/government-migration.md +52 -0
- package/docs/guides/site.md +5 -5
- package/docs/reference/format-details.md +152 -246
- package/docs/reference/note-types.md +27 -28
- package/docs/reference/theme-pages.md +33 -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/calendar-notes.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 +1 -5
- package/engine/content-images.mjs +3 -3
- package/engine/content-poetry.mjs +141 -0
- package/engine/content-spans.mjs +177 -0
- 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 +197 -333
- package/engine/index.mjs +1 -1
- package/engine/infobox.mjs +11 -1
- package/engine/journals.mjs +8 -6
- package/engine/markdown-expressions.mjs +9 -3
- package/engine/metadata-index.mjs +5 -3
- package/engine/note-vocabulary.mjs +30 -13
- 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/wikilinks.mjs +18 -4
- 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 +1 -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/calendar-notes.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 +75 -118
- package/types/engine/infobox.d.mts +1 -0
- package/types/engine/markdown-expressions.d.mts +1 -1
- package/types/engine/note-vocabulary.d.mts +14 -2
- 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/populations.mjs
CHANGED
|
@@ -14,17 +14,14 @@
|
|
|
14
14
|
/**
|
|
15
15
|
* Whether a world's population figures agree from the region down.
|
|
16
16
|
*
|
|
17
|
-
* A place states `data.population` and an affiliation states
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* keys — a place's `data.parents` for geography, an affiliation's
|
|
26
|
-
* `data.domains` for tenure. Where that module asks whether anybody holds a
|
|
27
|
-
* place, this one asks whether the numbers add up.
|
|
17
|
+
* A place states `data.population` and an affiliation states its membership
|
|
18
|
+
* population. Three claims can be checked: the regions inside a place fit
|
|
19
|
+
* inside it, a settlement fits inside every place that contains it, and a page
|
|
20
|
+
* citing a figure states the figure that note states.
|
|
21
|
+
*
|
|
22
|
+
* Geography comes from a place's `data.parents`. Government is independent:
|
|
23
|
+
* an affiliation's membership population does not state the population of the
|
|
24
|
+
* territory it governs, so these checks do not compare the two.
|
|
28
25
|
*
|
|
29
26
|
* **Every finding is a warning.** A figure out of step with its neighbours is
|
|
30
27
|
* a thing an author wants to see while the note is open, and the same gap can
|
|
@@ -39,10 +36,9 @@
|
|
|
39
36
|
* **A stated figure is compared; an absent one is not.** A place or an
|
|
40
37
|
* affiliation with no `population` contributes nothing to a sum and is never
|
|
41
38
|
* the subject of a finding, so a half-written corpus is quiet rather than
|
|
42
|
-
* noisy. A dependency's places and affiliations take part
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* nowhere.
|
|
39
|
+
* noisy. A dependency's places and affiliations take part when their fetched
|
|
40
|
+
* entry supplies a population figure; an older index without one contributes
|
|
41
|
+
* no population.
|
|
46
42
|
*
|
|
47
43
|
* **There is deliberately no urban-share rule.** A share computed from the
|
|
48
44
|
* named settlements against a region's total measures nothing: the settlement
|
|
@@ -81,14 +77,6 @@ export const POPULATION_TOLERANCE = 1.05;
|
|
|
81
77
|
* @type {ReadonlyArray<{name: string, describe: string}>}
|
|
82
78
|
*/
|
|
83
79
|
export const POPULATION_RULES = Object.freeze([
|
|
84
|
-
Object.freeze({
|
|
85
|
-
name: "over-held land",
|
|
86
|
-
describe:
|
|
87
|
-
"The polities whose `domains` name a place count more people than the place " +
|
|
88
|
-
"states. A polity subordinate to another holding the same place is already " +
|
|
89
|
-
"counted in that polity's figure, and a settlement is exempt, its holder's " +
|
|
90
|
-
"figure counting a hinterland the settlement does not.",
|
|
91
|
-
}),
|
|
92
80
|
Object.freeze({
|
|
93
81
|
name: "over-full region",
|
|
94
82
|
describe:
|
|
@@ -133,19 +121,16 @@ const CORPUS = new WeakMap();
|
|
|
133
121
|
* @property {number} [population] The stated figure, absent where unstated.
|
|
134
122
|
* @property {string[]} parents A place's enclosing places, an affiliation's
|
|
135
123
|
* superiors.
|
|
136
|
-
* @property {string[]} domains What an affiliation holds.
|
|
137
124
|
*/
|
|
138
125
|
|
|
139
126
|
/**
|
|
140
|
-
* The whole tree, indexed
|
|
127
|
+
* The whole tree, indexed by place, affiliation and geographical parent.
|
|
141
128
|
*
|
|
142
129
|
* @typedef {object} Corpus
|
|
143
130
|
* @property {Map<string, PopulationNode>} places By shortcode.
|
|
144
131
|
* @property {Map<string, PopulationNode>} affiliations By shortcode.
|
|
145
132
|
* @property {Map<string, string[]>} childrenOf Place shortcode → the places
|
|
146
133
|
* whose `parents` name it.
|
|
147
|
-
* @property {Map<string, string[]>} holdersOf Place shortcode → the polities
|
|
148
|
-
* whose `domains` name it.
|
|
149
134
|
* @property {Map<string, PopulationNode>} byAddress `type/shortcode` → the
|
|
150
135
|
* node, for the citation rule.
|
|
151
136
|
* Package-blind by design: a
|
|
@@ -210,7 +195,6 @@ function localNode(note) {
|
|
|
210
195
|
title: String(fm.name?.full ?? shortcode),
|
|
211
196
|
population: figureOf(data.population),
|
|
212
197
|
parents: shortcodesOf(data.parents),
|
|
213
|
-
domains: type === "affiliation" ? shortcodesOf(data.domains) : [],
|
|
214
198
|
};
|
|
215
199
|
}
|
|
216
200
|
|
|
@@ -236,14 +220,13 @@ export function foreignNode(canonical, entry) {
|
|
|
236
220
|
title: String(entry.name ?? shortcode),
|
|
237
221
|
population: figureOf(entry.population),
|
|
238
222
|
parents: shortcodesOf(entry.parents),
|
|
239
|
-
domains: type === "affiliation" ? shortcodesOf(entry.domains) : [],
|
|
240
223
|
};
|
|
241
224
|
}
|
|
242
225
|
|
|
243
226
|
/**
|
|
244
227
|
* The corpus a link index describes.
|
|
245
228
|
*
|
|
246
|
-
* Population containment
|
|
229
|
+
* Population containment uses the same package-blind Shortcode
|
|
247
230
|
* identity as holdings and maps. Each type has one node per shortcode, with
|
|
248
231
|
* local notes taking precedence over fetched entries. A fetched place can
|
|
249
232
|
* attach to local geography, while its authored `parents` remains an Address.
|
|
@@ -289,18 +272,7 @@ function corpusOf(index) {
|
|
|
289
272
|
}
|
|
290
273
|
}
|
|
291
274
|
|
|
292
|
-
|
|
293
|
-
const holdersOf = new Map();
|
|
294
|
-
for (const node of affiliations.values()) {
|
|
295
|
-
if (node.subType !== "polity") continue;
|
|
296
|
-
for (const domain of node.domains) {
|
|
297
|
-
const list = holdersOf.get(domain);
|
|
298
|
-
if (list) list.push(node.shortcode);
|
|
299
|
-
else holdersOf.set(domain, [node.shortcode]);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
found = { places, affiliations, childrenOf, holdersOf, byAddress };
|
|
275
|
+
found = { places, affiliations, childrenOf, byAddress };
|
|
304
276
|
CORPUS.set(index, found);
|
|
305
277
|
return found;
|
|
306
278
|
}
|
|
@@ -348,27 +320,6 @@ function ancestors(corpus, shortcode) {
|
|
|
348
320
|
return out;
|
|
349
321
|
}
|
|
350
322
|
|
|
351
|
-
/**
|
|
352
|
-
* The polities holding a place, each counted once.
|
|
353
|
-
*
|
|
354
|
-
* A polity subordinate to another polity that holds the same place is already
|
|
355
|
-
* inside that polity's figure, so only the topmost of a chain counts.
|
|
356
|
-
*
|
|
357
|
-
* @param {Corpus} corpus - The corpus.
|
|
358
|
-
* @param {string} shortcode - The place.
|
|
359
|
-
* @returns {PopulationNode[]} The holders.
|
|
360
|
-
*/
|
|
361
|
-
function topHolders(corpus, shortcode) {
|
|
362
|
-
const holders = corpus.holdersOf.get(shortcode) ?? [];
|
|
363
|
-
const held = new Set(holders);
|
|
364
|
-
return holders
|
|
365
|
-
.map((name) => corpus.affiliations.get(name))
|
|
366
|
-
.filter(
|
|
367
|
-
/** @returns {node is PopulationNode} */
|
|
368
|
-
(node) => Boolean(node) && !(node?.parents ?? []).some((p) => held.has(p)),
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
323
|
/**
|
|
373
324
|
* The regions immediately inside a place.
|
|
374
325
|
*
|
|
@@ -416,7 +367,7 @@ function atFigure(note, rule, message) {
|
|
|
416
367
|
* runs it with the index every reference check resolves through. Without an
|
|
417
368
|
* index the questions cannot be asked and nothing is reported.
|
|
418
369
|
*
|
|
419
|
-
*
|
|
370
|
+
* Two geographical rules, each reported on the note being linted:
|
|
420
371
|
* `over-full region` on the place whose figure is exceeded, and
|
|
421
372
|
* `oversized settlement` on the settlement that exceeds one.
|
|
422
373
|
*
|
|
@@ -441,23 +392,6 @@ export function checkPopulation(note, { index } = {}) {
|
|
|
441
392
|
const allowed = stated * POPULATION_TOLERANCE;
|
|
442
393
|
const findings = [];
|
|
443
394
|
|
|
444
|
-
// A settlement is exempt: the polity holding a city-state counts the
|
|
445
|
-
// hinterland the city does not, so its figure is properly the larger.
|
|
446
|
-
if (subType !== "settlement") {
|
|
447
|
-
const holders = topHolders(corpus, shortcode);
|
|
448
|
-
const sum = holders.reduce((total, node) => total + (node.population ?? 0), 0);
|
|
449
|
-
if (sum > allowed) {
|
|
450
|
-
findings.push(
|
|
451
|
-
atFigure(
|
|
452
|
-
note,
|
|
453
|
-
"over-held land",
|
|
454
|
-
`the polities holding ${title} count ${counted(sum)} people against its ` +
|
|
455
|
-
`stated ${counted(stated)}`,
|
|
456
|
-
),
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
395
|
const regions = nearestRegions(corpus, shortcode);
|
|
462
396
|
const inside = regions.reduce((total, node) => total + (node.population ?? 0), 0);
|
|
463
397
|
if (inside > allowed) {
|
|
@@ -520,8 +454,8 @@ const CITED =
|
|
|
520
454
|
* restated figures drift. Each is located at the figure it wrote, counting
|
|
521
455
|
* repeats so two rows carrying one number land on their own lines.
|
|
522
456
|
*
|
|
523
|
-
* A citation naming a note
|
|
524
|
-
*
|
|
457
|
+
* A citation naming a note with no published population figure is skipped:
|
|
458
|
+
* there is nothing to disagree with.
|
|
525
459
|
*
|
|
526
460
|
* Declared as the `doc` vocabulary's type-level check.
|
|
527
461
|
*
|
package/engine/site-build.mjs
CHANGED
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
* @module
|
|
52
52
|
*/
|
|
53
53
|
|
|
54
|
+
import { renderAlerts, scanAlerts } from "./content-alerts.mjs";
|
|
54
55
|
import { positionOfYamlPath } from "./diagnostics.mjs";
|
|
55
56
|
import { decodeNoteAddresses, noteAddressContext, encodeAddresses } from "./note-addresses.mjs";
|
|
56
57
|
import fs from "node:fs";
|
|
@@ -62,11 +63,12 @@ import { addressSlug } from "./content-address.mjs";
|
|
|
62
63
|
import { protectCode } from "./code-fences.mjs";
|
|
63
64
|
import { renderMarkdownExpressions } from "./markdown-expressions.mjs";
|
|
64
65
|
import { expandContentTables } from "./content-tables.mjs";
|
|
66
|
+
import { renderSpans, scanSpans } from "./content-spans.mjs";
|
|
65
67
|
import { renderBlocks, renderWithheldSections, scanBlocks } from "./content-blocks.mjs";
|
|
66
68
|
import { scanHeadingAttributes, withheldSections } from "./heading-attributes.mjs";
|
|
67
69
|
import { renderFigureBlocks, scanFigures } from "./content-figures.mjs";
|
|
68
70
|
import { footnoteFindings } from "./content-footnotes.mjs";
|
|
69
|
-
import { collectAnchors } from "./anchors.mjs";
|
|
71
|
+
import { collectAnchors, markupAnchorFindings } from "./anchors.mjs";
|
|
70
72
|
import { checkImages, renderImageFigures } from "./content-images.mjs";
|
|
71
73
|
import { assetPathnameKey, pathnameProblem, resolvePathname } from "./pathnames.mjs";
|
|
72
74
|
import {
|
|
@@ -563,7 +565,7 @@ export function tableUniverse(pages) {
|
|
|
563
565
|
* about every other page, known only once the whole tree has resolved — see
|
|
564
566
|
* {@link module:engine/related-pages} — so an authored value is dropped the
|
|
565
567
|
* way `aliases` is, and {@link renderPages} writes the derived block once it
|
|
566
|
-
* holds the graph.
|
|
568
|
+
* holds the graph. Derived geographical and government lists are dropped for the
|
|
567
569
|
* same reason — see {@link module:engine/holdings}. **So is `map`**: the
|
|
568
570
|
* file a place page names is the one the build drew beside it — see
|
|
569
571
|
* {@link module:engine/site-maps} — and a page with no drawing names none.
|
|
@@ -601,6 +603,7 @@ export function pageFrontmatter(page, { decorate, webSrc, artSrc }) {
|
|
|
601
603
|
kbfolder: page.folder,
|
|
602
604
|
};
|
|
603
605
|
if (decorate) decorate(data, page);
|
|
606
|
+
if (data.type === "affiliation" && data.data) delete data.data.domains;
|
|
604
607
|
delete data.aliases;
|
|
605
608
|
delete data.related;
|
|
606
609
|
for (const key of HOLDINGS_KEYS) delete data[key];
|
|
@@ -754,7 +757,7 @@ export function renderSitePage(
|
|
|
754
757
|
return src;
|
|
755
758
|
};
|
|
756
759
|
const artSrc = (value, type, accepts) => artPathname(artIndex, value, type, accepts).pathname;
|
|
757
|
-
// The role lookup a
|
|
760
|
+
// The role lookup a captioned item's `map` counter reaches through —
|
|
758
761
|
// see `engine/content-figures.mjs`'s `resolveRole`. Addressed form, for
|
|
759
762
|
// the page's own scan below, which runs before an embed is rewritten into
|
|
760
763
|
// an ordinary image; `roleByWebSrc` is the same lookup keyed by the web
|
|
@@ -837,10 +840,14 @@ export function renderSitePage(
|
|
|
837
840
|
});
|
|
838
841
|
expressionErrors.push(...expressions.findings);
|
|
839
842
|
const data = pageFrontmatter(page, { decorate, webSrc, artSrc });
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
843
|
+
const figured = renderFigureBlocks(
|
|
844
|
+
protectCode(expressions.markdown, resolve),
|
|
845
|
+
undefined,
|
|
846
|
+
undefined,
|
|
847
|
+
{
|
|
848
|
+
resolveRole: (address) => roleByWebSrc.get(address),
|
|
849
|
+
},
|
|
850
|
+
);
|
|
844
851
|
for (const error of figureScan.errors)
|
|
845
852
|
captionErrors.push({
|
|
846
853
|
file: page.file,
|
|
@@ -848,7 +855,12 @@ export function renderSitePage(
|
|
|
848
855
|
column: error.column,
|
|
849
856
|
message: error.message,
|
|
850
857
|
});
|
|
851
|
-
for (const error of
|
|
858
|
+
for (const error of [
|
|
859
|
+
...scanBlocks(page.body).errors,
|
|
860
|
+
...scanAlerts(page.body).errors,
|
|
861
|
+
...scanSpans(page.body).errors,
|
|
862
|
+
...markupAnchorFindings(page.body),
|
|
863
|
+
])
|
|
852
864
|
secretErrors.push({
|
|
853
865
|
file: page.file,
|
|
854
866
|
line: (page.bodyLine ?? 1) + error.line - 1,
|
|
@@ -877,7 +889,10 @@ export function renderSitePage(
|
|
|
877
889
|
// The disclosure is written last, over the Markdown the page ships: the
|
|
878
890
|
// passes before this one carry line positions into their findings, and a
|
|
879
891
|
// line inserted ahead of them would move every one of them.
|
|
880
|
-
body: renderWithheldSections(
|
|
892
|
+
body: renderWithheldSections(
|
|
893
|
+
renderSpans(renderAlerts(renderBlocks(figured.markdown, "web").markdown).markdown)
|
|
894
|
+
.markdown,
|
|
895
|
+
),
|
|
881
896
|
data,
|
|
882
897
|
resolved,
|
|
883
898
|
tableErrors,
|
|
@@ -1053,13 +1068,26 @@ export function renderPages(pages, options) {
|
|
|
1053
1068
|
}
|
|
1054
1069
|
|
|
1055
1070
|
const related = relatedPages(edges, entries);
|
|
1056
|
-
//
|
|
1057
|
-
//
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1071
|
+
// Geography follows parents; governing-body lists follow explicit government
|
|
1072
|
+
// references across this package and every fetched index.
|
|
1073
|
+
const holdings = holdingsPages(
|
|
1074
|
+
[
|
|
1075
|
+
...pages.map((page) =>
|
|
1076
|
+
holdingsNode(page.fm, { title: pageTitle(page), url: page.url, package: page.pkg }),
|
|
1077
|
+
),
|
|
1078
|
+
...foreignHoldingsNodes(foreign?.index),
|
|
1079
|
+
],
|
|
1080
|
+
{
|
|
1081
|
+
governmentNodes: records
|
|
1082
|
+
.filter((record) => !isGmNote(record))
|
|
1083
|
+
.map((record) =>
|
|
1084
|
+
holdingsNode(record, {
|
|
1085
|
+
title: record.name?.full ?? record.shortcode,
|
|
1086
|
+
package: config?.contentPackage ?? pages[0]?.pkg,
|
|
1087
|
+
}),
|
|
1088
|
+
),
|
|
1089
|
+
},
|
|
1090
|
+
);
|
|
1063
1091
|
|
|
1064
1092
|
let withMap = 0;
|
|
1065
1093
|
const outputs = capture === true ? new Map() : null;
|
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/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
|
|
@@ -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.27.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",
|
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.
|
|
@@ -29,7 +29,7 @@ export function checkCalendarDateFormats(note: any): {
|
|
|
29
29
|
/**
|
|
30
30
|
* Check a `place` note's world facts.
|
|
31
31
|
*
|
|
32
|
-
* Declared on `place` beside its
|
|
32
|
+
* Declared on `place` beside its government advisory, so the lint runs both with the
|
|
33
33
|
* same index. Four rules, and each is a fact about the setting rather than
|
|
34
34
|
* about the file:
|
|
35
35
|
*
|
|
@@ -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;
|