@heroiclands/package-build 25.5.4 → 25.6.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 +40 -0
- package/README.md +7 -0
- package/assets/styles/content-base.css +1 -1
- package/bin/content-commands.mjs +32 -8
- package/docs/api.md +81 -32
- package/docs/authoring/frontmatter.md +1 -1
- package/docs/authoring/links-and-markup.md +59 -10
- package/docs/commands.md +60 -17
- package/docs/configuration.md +3 -2
- package/docs/content-format.md +50 -1
- package/docs/diagnostics.md +17 -0
- package/docs/guides/module-project.md +11 -3
- package/docs/guides/reference-pages.md +4 -1
- package/docs/guides/site.md +13 -3
- package/docs/guides/writing-in-invented-scripts.md +5 -2
- package/docs/reference/format-details.md +233 -138
- package/docs/reference/note-types.md +165 -152
- package/docs/reference/theme-pages.md +1 -0
- package/e2e.mjs +1 -1
- package/engine/base-compiler.mjs +2 -0
- package/engine/compile-corpus.mjs +6 -2
- package/engine/content-charset.mjs +1 -1
- package/engine/content-format.yaml +9 -7
- package/engine/content-html.mjs +3 -2
- package/engine/content-index.mjs +23 -0
- package/engine/content-links.mjs +8 -0
- package/engine/content-render.mjs +14 -2
- package/engine/content-scripts.mjs +44 -6
- package/engine/content-session.mjs +11 -7
- package/engine/content-spans.mjs +3 -4
- package/engine/content-tables.mjs +12 -5
- package/engine/content-verse.mjs +52 -10
- package/engine/core-report.mjs +27 -18
- package/engine/culture-reach.mjs +3 -0
- package/engine/declared-sections.mjs +100 -0
- package/engine/derived-sections.mjs +36 -2
- package/engine/dictionary-rows.mjs +2 -1
- package/engine/generated-sections.mjs +2 -0
- package/engine/holdings.mjs +107 -3
- package/engine/index-records.mjs +3 -1
- package/engine/index.mjs +1 -1
- package/engine/language-data.mjs +0 -1
- package/engine/language-names.mjs +53 -23
- package/engine/language-rows.mjs +1 -3
- package/engine/language-terms.mjs +7 -10
- package/engine/map-dot.mjs +22 -0
- package/engine/markdown-blocks.mjs +23 -8
- package/engine/markdownlint-run.mjs +1 -1
- package/engine/markup-validation.mjs +2 -0
- package/engine/metadata-index.mjs +1 -0
- package/engine/note-addresses.mjs +16 -0
- package/engine/note-vocabulary.mjs +29 -13
- package/engine/npc-generate.mjs +52 -7
- package/engine/office-holders.mjs +76 -0
- package/engine/page-breaks.mjs +143 -0
- package/engine/pdf-build.mjs +12 -4
- package/engine/pdf-render.mjs +12 -2
- package/engine/place-kinds.mjs +106 -0
- package/engine/preview-index.mjs +4 -2
- package/engine/settlement-tables.mjs +410 -675
- package/engine/settlement-terms.mjs +118 -0
- package/engine/site-build.mjs +14 -7
- package/engine/site-preview.mjs +3 -0
- package/engine/site-render.mjs +18 -4
- package/engine/site-root.mjs +10 -1
- package/engine/site-serve-http.mjs +151 -0
- package/engine/site-serve.mjs +196 -126
- package/engine/sql-tables.mjs +101 -7
- package/engine/standing-terms.mjs +9 -5
- package/engine/standings.mjs +83 -31
- package/engine/unique-items.mjs +85 -0
- package/engine/verse-metre.mjs +50 -5
- package/engine/verse-regions.mjs +16 -0
- package/package.json +9 -4
- package/site-theme/data/banner-kinds.yaml +2 -0
- package/sohl/actors.mjs +4 -3
- package/types/e2e.d.mts +1 -1
- package/types/engine/content-html.d.mts +2 -1
- package/types/engine/content-scripts.d.mts +9 -1
- package/types/engine/content-verse.d.mts +11 -2
- package/types/engine/core-report.d.mts +6 -2
- package/types/engine/culture-reach.d.mts +3 -0
- package/types/engine/declared-sections.d.mts +30 -0
- package/types/engine/derived-sections.d.mts +5 -1
- package/types/engine/holdings.d.mts +34 -2
- package/types/engine/index-records.d.mts +2 -1
- package/types/engine/language-names.d.mts +18 -6
- package/types/engine/language-terms.d.mts +5 -1
- package/types/engine/markdown-blocks.d.mts +5 -1
- package/types/engine/npc-generate.d.mts +3 -0
- package/types/engine/office-holders.d.mts +33 -0
- package/types/engine/page-breaks.d.mts +38 -0
- package/types/engine/pdf-render.d.mts +2 -0
- package/types/engine/place-kinds.d.mts +18 -0
- package/types/engine/settlement-tables.d.mts +25 -158
- package/types/engine/settlement-terms.d.mts +53 -0
- package/types/engine/site-build.d.mts +3 -1
- package/types/engine/site-render.d.mts +6 -2
- package/types/engine/site-root.d.mts +3 -1
- package/types/engine/site-serve-http.d.mts +1 -0
- package/types/engine/site-serve.d.mts +12 -6
- package/types/engine/sql-tables.d.mts +35 -3
- package/types/engine/standing-terms.d.mts +3 -2
- package/types/engine/standings.d.mts +14 -2
- package/types/engine/unique-items.d.mts +12 -0
- package/types/engine/verse-metre.d.mts +2 -1
- package/types/engine/verse-regions.d.mts +8 -0
- package/vendor/markdownlint.mjs +855 -569
package/engine/pdf-build.mjs
CHANGED
|
@@ -1385,7 +1385,9 @@ async function buildBook(book, shared) {
|
|
|
1385
1385
|
url: site,
|
|
1386
1386
|
elsewhere,
|
|
1387
1387
|
findings,
|
|
1388
|
-
runs: runContext(resolved, records
|
|
1388
|
+
runs: runContext(resolved, records, {
|
|
1389
|
+
referenceName: (target) => resolveInfoboxRef(gates.index, target)?.name,
|
|
1390
|
+
}),
|
|
1389
1391
|
file: page.file,
|
|
1390
1392
|
bodyLine: page.bodyLine,
|
|
1391
1393
|
prepared: true,
|
|
@@ -1476,7 +1478,9 @@ async function buildBook(book, shared) {
|
|
|
1476
1478
|
elsewhere,
|
|
1477
1479
|
indexMentions: true,
|
|
1478
1480
|
findings,
|
|
1479
|
-
runs: runContext(resolved, records
|
|
1481
|
+
runs: runContext(resolved, records, {
|
|
1482
|
+
referenceName: (target) => resolveInfoboxRef(gates.index, target)?.name,
|
|
1483
|
+
}),
|
|
1480
1484
|
file,
|
|
1481
1485
|
}),
|
|
1482
1486
|
);
|
|
@@ -1508,7 +1512,9 @@ async function buildBook(book, shared) {
|
|
|
1508
1512
|
elsewhere,
|
|
1509
1513
|
indexMentions: true,
|
|
1510
1514
|
findings,
|
|
1511
|
-
runs: runContext(resolved, records
|
|
1515
|
+
runs: runContext(resolved, records, {
|
|
1516
|
+
referenceName: (target) => resolveInfoboxRef(gates.index, target)?.name,
|
|
1517
|
+
}),
|
|
1512
1518
|
file,
|
|
1513
1519
|
});
|
|
1514
1520
|
} catch {
|
|
@@ -1682,7 +1688,9 @@ async function buildBook(book, shared) {
|
|
|
1682
1688
|
copyright: shared.copyright,
|
|
1683
1689
|
license: shared.license,
|
|
1684
1690
|
index,
|
|
1685
|
-
runs: runContext(resolved, records
|
|
1691
|
+
runs: runContext(resolved, records, {
|
|
1692
|
+
referenceName: (target) => resolveInfoboxRef(gates.index, target)?.name,
|
|
1693
|
+
}),
|
|
1686
1694
|
});
|
|
1687
1695
|
// Last, over the whole document: a reference can only be checked once every
|
|
1688
1696
|
// declaration is in one string, and Typst treats a dangling one as fatal.
|
package/engine/pdf-render.mjs
CHANGED
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
* @module
|
|
63
63
|
*/
|
|
64
64
|
|
|
65
|
+
import { escapeInvalidPageBreaks, scanPageBreaks } from "./page-breaks.mjs";
|
|
65
66
|
import { createRenderer } from "./content-render.mjs";
|
|
66
67
|
import { bookDraftNoticePreamble } from "./draft-notice.mjs";
|
|
67
68
|
import { ICON_PATTERN, ICON_SIZES } from "./content-icons.mjs";
|
|
@@ -359,6 +360,7 @@ function reportUnrenderable(source, definitions, opts, original) {
|
|
|
359
360
|
}
|
|
360
361
|
}
|
|
361
362
|
for (const error of [
|
|
363
|
+
...scanPageBreaks(original).errors,
|
|
362
364
|
...scanSpans(source).errors,
|
|
363
365
|
...scanRuns(source, opts.runs).errors,
|
|
364
366
|
...markupAnchorFindings(source),
|
|
@@ -469,6 +471,7 @@ function reportUnrenderable(source, definitions, opts, original) {
|
|
|
469
471
|
* @param {Array<{line: number, generated: boolean}>} [opts.lineMap] - From
|
|
470
472
|
* table expansion, mapping each line handed in back to the line an author
|
|
471
473
|
* wrote.
|
|
474
|
+
* @param {boolean} [opts.pageBreaks] - Internal: whether this slice remains at the note top level.
|
|
472
475
|
* @param {boolean} [opts.prepared] - Whether this body has been through the
|
|
473
476
|
* passes that resolve a wikilink, an embed and an inline expression. A note's
|
|
474
477
|
* has; a front-matter file's and a prose file's have not, and markup of theirs
|
|
@@ -519,7 +522,8 @@ export function markdownToTypst(markdown, opts = {}) {
|
|
|
519
522
|
renderRuns(markingSource, opts.runs, undefined, { target: "book" })
|
|
520
523
|
);
|
|
521
524
|
const markedFootnotes = marked && definitions ? separateFootnotes(marked.markdown) : undefined;
|
|
522
|
-
const
|
|
525
|
+
const markedSource = markedFootnotes?.markdown ?? marked?.markdown ?? authored;
|
|
526
|
+
const source = opts.nested ? markedSource : escapeInvalidPageBreaks(markedSource);
|
|
523
527
|
if (markedFootnotes) definitions = markedFootnotes.definitions;
|
|
524
528
|
if (marked) {
|
|
525
529
|
sharedOptions.footnoteDefinitions = definitions;
|
|
@@ -547,6 +551,7 @@ export function markdownToTypst(markdown, opts = {}) {
|
|
|
547
551
|
footnotePrefix: opts.footnotePrefix ?? `footnote-${slugify(anchorPrefix || "body")}`,
|
|
548
552
|
scrolls: marked?.scrolls ?? opts.scrolls ?? new Map(),
|
|
549
553
|
verses: marked?.verses ?? opts.verses ?? new Map(),
|
|
554
|
+
pageBreaks: opts.pageBreaks !== false,
|
|
550
555
|
};
|
|
551
556
|
// A withheld section is set in the box a `:::secret` is set in, and its
|
|
552
557
|
// heading is set inside it: a printed page honours no reader permission, so
|
|
@@ -600,6 +605,7 @@ export function markdownToTypst(markdown, opts = {}) {
|
|
|
600
605
|
if (block.name === "div") {
|
|
601
606
|
const content = markdownToTypst(block.body, {
|
|
602
607
|
...sharedOptions,
|
|
608
|
+
pageBreaks: false,
|
|
603
609
|
insideAdmonition: false,
|
|
604
610
|
});
|
|
605
611
|
const anchor = block.id ? ` <${sectionLabel(anchorPrefix, block.id)}>` : "";
|
|
@@ -620,6 +626,7 @@ export function markdownToTypst(markdown, opts = {}) {
|
|
|
620
626
|
// of its own; a box holds none, and the scan has already said so.
|
|
621
627
|
const content = markdownToTypst(block.body, {
|
|
622
628
|
...sharedOptions,
|
|
629
|
+
pageBreaks: false,
|
|
623
630
|
insideAdmonition: !BLOCK_CONTAINERS.includes(block.name),
|
|
624
631
|
});
|
|
625
632
|
output.push(
|
|
@@ -695,7 +702,7 @@ export function markdownToTypst(markdown, opts = {}) {
|
|
|
695
702
|
/** Render one fragment with the note's footnote definitions available. */
|
|
696
703
|
function renderMarkdownSegment(source, md, ctx, definitions) {
|
|
697
704
|
const input = definitions ? `${source}\n\n${definitions}` : source;
|
|
698
|
-
const tokens = md.parse(input, { verses: ctx.verses });
|
|
705
|
+
const tokens = md.parse(input, { verses: ctx.verses, pageBreaks: ctx.pageBreaks });
|
|
699
706
|
const footnoteStart = tokens.findIndex((token) => token.type === "footnote_block_open");
|
|
700
707
|
const visible = footnoteStart < 0 ? tokens : tokens.slice(0, footnoteStart);
|
|
701
708
|
const footnotes = new Map();
|
|
@@ -791,6 +798,9 @@ function renderBlock(tokens, i, out, ctx) {
|
|
|
791
798
|
out.push(`\n#block(width: 100%)[${content}]${anchor}\n`);
|
|
792
799
|
return 1;
|
|
793
800
|
}
|
|
801
|
+
case "page_break":
|
|
802
|
+
out.push("\n#pagebreak(weak: true)\n\n");
|
|
803
|
+
return 1;
|
|
794
804
|
case "hr":
|
|
795
805
|
out.push("\n#line(length: 100%, stroke: 0.4pt)\n\n");
|
|
796
806
|
return 1;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* **What a district and a route require of their notes.**
|
|
16
|
+
*
|
|
17
|
+
* A `place/district` is a recognised division of a larger place, usually
|
|
18
|
+
* administrative: a city's quarter, a town's ward, a kingdom's shire. It always
|
|
19
|
+
* stands within a `place/settlement` or a `place/region`, named in
|
|
20
|
+
* `data.parents`. It may name its own governing affiliation in
|
|
21
|
+
* `data.government`; without one it falls under its parent's government.
|
|
22
|
+
*
|
|
23
|
+
* A `place/route` is a road, a pass or a sea lane, and the journeys it carries
|
|
24
|
+
* are its `data.routes`.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { isAddressTuple, parseAddress, renderAddress } from "./address.mjs";
|
|
30
|
+
import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
31
|
+
|
|
32
|
+
/** The place kinds a district may stand within. */
|
|
33
|
+
export const DISTRICT_PARENT_KINDS = Object.freeze(["settlement", "region"]);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The place one `parents` entry names, as the lint's index holds it.
|
|
37
|
+
*
|
|
38
|
+
* @param {unknown} value - The authored entry.
|
|
39
|
+
* @param {object} [index] - The lint's address index.
|
|
40
|
+
* @returns {{resolved: boolean, fm?: object}} Whether it resolves, and its
|
|
41
|
+
* frontmatter where it does.
|
|
42
|
+
*/
|
|
43
|
+
function parentOf(value, index) {
|
|
44
|
+
const tuple =
|
|
45
|
+
isAddressTuple(value) ? value : (
|
|
46
|
+
parseAddress(value, {
|
|
47
|
+
package: index?.contentPackage,
|
|
48
|
+
system: "note",
|
|
49
|
+
type: "place",
|
|
50
|
+
types: index?.types,
|
|
51
|
+
packages: index?.packages,
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
if (!isAddressTuple(tuple) || !index?.addressHit) return { resolved: false };
|
|
55
|
+
const hit = index.addressHit(renderAddress({ ...tuple, system: "note" }));
|
|
56
|
+
return hit ? { resolved: true, fm: hit.fm ?? hit } : { resolved: false };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check a district's parent and a route's journeys.
|
|
61
|
+
*
|
|
62
|
+
* A district naming no parent is reported at its `subType`; one whose parents
|
|
63
|
+
* all resolve and none is a settlement or a region is reported at `parents`.
|
|
64
|
+
* A parent that does not resolve is the Address check's finding, so a district
|
|
65
|
+
* whose only parents are unresolved earns nothing more here. A route stating no
|
|
66
|
+
* `data.routes` entry is reported at its `subType`.
|
|
67
|
+
*
|
|
68
|
+
* @param {object} note - The note: `file`, `raw` and parsed `fm`.
|
|
69
|
+
* @param {{index?: object}} [options] - The lint's options.
|
|
70
|
+
* @returns {object[]} Located findings.
|
|
71
|
+
*/
|
|
72
|
+
export function checkPlaceKind(note, { index } = {}) {
|
|
73
|
+
const subType = note.fm?.subType;
|
|
74
|
+
const at = (path, key) => ({
|
|
75
|
+
file: note.file,
|
|
76
|
+
...positionOfFrontmatterPath(note.raw ?? "", path, key ? { key: true } : undefined),
|
|
77
|
+
severity: "error",
|
|
78
|
+
});
|
|
79
|
+
if (subType === "district") {
|
|
80
|
+
const parents = note.fm?.data?.parents;
|
|
81
|
+
const list =
|
|
82
|
+
Array.isArray(parents) ? parents
|
|
83
|
+
: parents == null ? []
|
|
84
|
+
: [parents];
|
|
85
|
+
const message =
|
|
86
|
+
"a district stands within a settlement or a region; name one in `data.parents`";
|
|
87
|
+
if (!list.length) return [{ ...at(["subType"]), message }];
|
|
88
|
+
const found = list.map((value) => parentOf(value, index));
|
|
89
|
+
if (found.some((one) => !one.resolved)) return [];
|
|
90
|
+
if (found.some((one) => DISTRICT_PARENT_KINDS.includes(one.fm?.subType))) return [];
|
|
91
|
+
return [{ ...at(["data", "parents"], true), message }];
|
|
92
|
+
}
|
|
93
|
+
if (subType === "route") {
|
|
94
|
+
const routes = note.fm?.data?.routes;
|
|
95
|
+
if (Array.isArray(routes) && routes.length) return [];
|
|
96
|
+
return [
|
|
97
|
+
{
|
|
98
|
+
...at(["subType"]),
|
|
99
|
+
message:
|
|
100
|
+
"a route carries its journeys in `data.routes`; state at least one, " +
|
|
101
|
+
"each `{ to, bearing, mode, days }`",
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
return [];
|
|
106
|
+
}
|
package/engine/preview-index.mjs
CHANGED
|
@@ -37,7 +37,7 @@ import { noContentIndexPackages } from "./metadata-index.mjs";
|
|
|
37
37
|
import { isGmNote } from "./note-vocabulary.mjs";
|
|
38
38
|
import { assetAddressIndex } from "./art-fields.mjs";
|
|
39
39
|
import { assetPathnameKey, pathnameProblem, resolvePathname } from "./pathnames.mjs";
|
|
40
|
-
import { buildSiteIndex } from "./site-index.mjs";
|
|
40
|
+
import { buildSiteIndex, resolveInfoboxRef } from "./site-index.mjs";
|
|
41
41
|
import { siteBase } from "../content-config.mjs";
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -107,7 +107,9 @@ export function previewIndex({ config, records, foreign }) {
|
|
|
107
107
|
return key ? assets?.byPath?.get(key) : undefined;
|
|
108
108
|
};
|
|
109
109
|
return {
|
|
110
|
-
runs: runContext(config, records
|
|
110
|
+
runs: runContext(config, records, {
|
|
111
|
+
referenceName: (target) => resolveInfoboxRef(site, target)?.name,
|
|
112
|
+
}),
|
|
111
113
|
icons: config.icons,
|
|
112
114
|
lookupAsset,
|
|
113
115
|
resolveSrc: webSrc,
|