@heroiclands/package-build 22.21.0 → 22.23.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 +223 -0
- package/assets/styles/content-base.css +162 -0
- package/bin/content-commands.mjs +77 -17
- package/bin/package-build.mjs +15 -2
- package/ci/content-format-reference.mjs +68 -1
- package/config.mjs +26 -0
- package/content-config.mjs +153 -6
- package/docs/api.md +120 -89
- package/docs/authoring/assets.md +14 -0
- package/docs/authoring/dates-and-calendars.md +41 -3
- package/docs/authoring/frontmatter.md +2 -2
- package/docs/authoring/links-and-markup.md +325 -35
- package/docs/commands.md +12 -10
- package/docs/configuration.md +117 -14
- package/docs/content-format.md +4 -2
- package/docs/guides/book.md +5 -4
- package/docs/guides/reference-pages.md +15 -0
- package/docs/reference/format-details.md +545 -104
- package/docs/reference/note-types.md +30 -13
- package/engine/actor-compiler.mjs +23 -0
- package/engine/anchored-sections.mjs +35 -4
- package/engine/anchors.mjs +26 -30
- package/engine/art-fields.mjs +118 -0
- package/engine/art-slots.mjs +7 -1
- package/engine/asset-index.mjs +160 -10
- package/engine/asset-replacement.mjs +93 -0
- package/engine/asset-types.mjs +240 -0
- package/engine/base-compiler.mjs +139 -22
- package/engine/base-styles.mjs +149 -0
- package/engine/bundles.mjs +13 -2
- package/engine/compendiums.mjs +8 -1
- package/engine/content-blocks.mjs +410 -0
- package/engine/content-embeds.mjs +19 -0
- package/engine/content-figures.mjs +468 -0
- package/engine/content-footnotes.mjs +197 -0
- package/engine/content-format-check.mjs +15 -12
- package/engine/content-format.yaml +1 -1
- package/engine/content-images.mjs +126 -25
- package/engine/content-index.mjs +38 -12
- 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/field-reference.mjs +31 -10
- package/engine/field-spec.mjs +10 -8
- package/engine/foreign-catalog.mjs +143 -18
- package/engine/frontmatter-lint.mjs +120 -65
- package/engine/frontmatter.mjs +3 -3
- package/engine/heading-attributes.mjs +321 -0
- package/engine/helpers.mjs +109 -41
- package/engine/homepage.mjs +8 -8
- package/engine/index.mjs +3 -0
- package/engine/infobox-registry.mjs +4 -1
- package/engine/infobox-render.mjs +15 -3
- package/engine/infobox.mjs +129 -15
- package/engine/itinerary-scenes.mjs +4 -10
- package/engine/journals.mjs +294 -50
- package/engine/lore-events.mjs +197 -0
- package/engine/macros.mjs +1 -1
- package/engine/markdown-expressions.mjs +434 -59
- package/engine/note-dates.mjs +354 -12
- package/engine/note-frontmatter.mjs +7 -16
- package/engine/note-vocabulary.mjs +183 -42
- package/engine/page-lists.mjs +358 -0
- package/engine/pathnames.mjs +36 -0
- package/engine/pdf-build.mjs +431 -74
- package/engine/pdf-images.mjs +64 -0
- package/engine/pdf-render.mjs +563 -66
- package/engine/rank-ladder.mjs +198 -0
- package/engine/reckoning-markers.mjs +1 -1
- package/engine/retired-fields.mjs +8 -13
- package/engine/scenes.mjs +26 -7
- package/engine/site-build.mjs +166 -23
- package/engine/site-config.mjs +10 -1
- package/engine/site-index.mjs +90 -3
- package/engine/site-preview.mjs +20 -7
- package/engine/social-ties.mjs +52 -14
- package/engine/sql-tables.mjs +66 -9
- package/engine/standings.mjs +89 -6
- package/engine/system-block.mjs +56 -30
- package/engine/web-wikilinks.mjs +48 -1
- package/hm3/actors.mjs +8 -5
- package/manifest.mjs +43 -24
- package/package.json +1 -1
- package/packagebuild-metadata.jsonl +13 -13
- package/sohl/actors.mjs +22 -99
- package/sohl/item-fields.mjs +5 -5
- package/sohl/items.mjs +1 -1
- package/sohl/note-schemas.mjs +11 -21
- package/types/config.d.mts +9 -0
- package/types/content-config.d.mts +48 -1
- package/types/engine/actor-compiler.d.mts +11 -0
- package/types/engine/anchored-sections.d.mts +24 -0
- package/types/engine/anchors.d.mts +9 -4
- package/types/engine/art-fields.d.mts +64 -0
- package/types/engine/asset-index.d.mts +43 -0
- package/types/engine/asset-replacement.d.mts +22 -0
- package/types/engine/asset-types.d.mts +18 -0
- package/types/engine/base-compiler.d.mts +19 -17
- package/types/engine/base-styles.d.mts +93 -0
- package/types/engine/content-blocks.d.mts +87 -0
- package/types/engine/content-embeds.d.mts +16 -0
- package/types/engine/content-figures.d.mts +101 -0
- package/types/engine/content-footnotes.d.mts +80 -0
- package/types/engine/content-format-check.d.mts +8 -6
- package/types/engine/content-images.d.mts +78 -12
- package/types/engine/content-index.d.mts +8 -2
- 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/field-reference.d.mts +21 -0
- package/types/engine/field-spec.d.mts +20 -16
- package/types/engine/foreign-catalog.d.mts +39 -0
- package/types/engine/frontmatter.d.mts +1 -1
- package/types/engine/heading-attributes.d.mts +179 -0
- package/types/engine/helpers.d.mts +29 -10
- package/types/engine/homepage.d.mts +7 -7
- package/types/engine/index.d.mts +1 -0
- package/types/engine/infobox-registry.d.mts +4 -1
- package/types/engine/infobox-render.d.mts +6 -1
- package/types/engine/infobox.d.mts +6 -0
- package/types/engine/journals.d.mts +41 -12
- package/types/engine/lore-events.d.mts +14 -0
- package/types/engine/markdown-expressions.d.mts +46 -2
- package/types/engine/note-dates.d.mts +64 -1
- package/types/engine/note-frontmatter.d.mts +1 -4
- package/types/engine/note-vocabulary.d.mts +82 -6
- package/types/engine/page-lists.d.mts +126 -0
- package/types/engine/pathnames.d.mts +26 -0
- package/types/engine/pdf-build.d.mts +28 -6
- package/types/engine/pdf-images.d.mts +52 -0
- package/types/engine/pdf-render.d.mts +84 -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 +27 -4
- package/types/engine/site-config.d.mts +9 -0
- package/types/engine/site-index.d.mts +67 -1
- package/types/engine/site-preview.d.mts +1 -1
- package/types/engine/sql-tables.d.mts +31 -7
- package/types/engine/standings.d.mts +21 -3
- package/types/engine/system-block.d.mts +37 -14
- package/types/engine/web-wikilinks.d.mts +3 -1
- package/types/hm3/actors.d.mts +21 -3
- package/types/manifest.d.mts +19 -23
- package/types/sohl/actors.d.mts +6 -7
- package/engine/content-admonitions.mjs +0 -114
- package/engine/content-captions.mjs +0 -181
- package/engine/content-secrets.mjs +0 -112
- package/types/engine/content-admonitions.d.mts +0 -24
- package/types/engine/content-captions.d.mts +0 -45
- package/types/engine/content-secrets.d.mts +0 -17
package/engine/base-compiler.mjs
CHANGED
|
@@ -62,9 +62,11 @@
|
|
|
62
62
|
*/
|
|
63
63
|
|
|
64
64
|
import { authoredFrontmatter } from "./index-records.mjs";
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
65
|
+
import { scanBlocks } from "./content-blocks.mjs";
|
|
66
|
+
import { scanHeadingAttributes, withheldSections } from "./heading-attributes.mjs";
|
|
67
|
+
import { scanFigures } from "./content-figures.mjs";
|
|
68
|
+
import { footnoteFindings } from "./content-footnotes.mjs";
|
|
69
|
+
import { checkImages } from "./content-images.mjs";
|
|
68
70
|
import { renderMarkdownExpressions } from "./markdown-expressions.mjs";
|
|
69
71
|
import { isGmNote } from "./note-vocabulary.mjs";
|
|
70
72
|
import { cloneAddressState } from "./address-values.mjs";
|
|
@@ -124,6 +126,25 @@ import { reckoningContext } from "./reckoning-markers.mjs";
|
|
|
124
126
|
* says nothing about. Counted as errors, never as skips.
|
|
125
127
|
*/
|
|
126
128
|
|
|
129
|
+
/**
|
|
130
|
+
* A `ref` call's Foundry rendering: a same-page wikilink rather than a
|
|
131
|
+
* Markdown link, so the wikilink pass that runs after expressions render
|
|
132
|
+
* addresses it exactly as an authored `[[#anchor|Text]]` is — by the
|
|
133
|
+
* JournalEntryPage the figure's own fence became, whether that page holds
|
|
134
|
+
* only the figure or carries it alongside other prose. A Markdown link's
|
|
135
|
+
* `#anchor` fragment resolves nowhere in Foundry, which addresses a page by
|
|
136
|
+
* UUID and an anchor within a page by that page's own anchor; this is why the
|
|
137
|
+
* two surfaces do not share a renderer.
|
|
138
|
+
*
|
|
139
|
+
* @param {{anchor: string}} target - The figure's own anchor.
|
|
140
|
+
* @param {string} label - The link's text, which may itself carry Markdown.
|
|
141
|
+
* @returns {string} `[[#anchor|label]]`, with a literal `|` in the label
|
|
142
|
+
* escaped so it cannot be read as the wikilink's own separator.
|
|
143
|
+
*/
|
|
144
|
+
function foundryRefLink({ anchor }, label) {
|
|
145
|
+
return `[[#${anchor}|${label.replace(/\|/g, "\\|")}]]`;
|
|
146
|
+
}
|
|
147
|
+
|
|
127
148
|
/**
|
|
128
149
|
* The shared walk → filter → expand → convert → build → write → count loop.
|
|
129
150
|
*
|
|
@@ -614,6 +635,34 @@ export class BasePackCompiler {
|
|
|
614
635
|
this.unresolvedLinks = 0;
|
|
615
636
|
}
|
|
616
637
|
|
|
638
|
+
/**
|
|
639
|
+
* Report every finding {@link BasePackCompiler#convertBody} collected
|
|
640
|
+
* across its passes, and decline the note if there were any.
|
|
641
|
+
*
|
|
642
|
+
* Reported directly, one `noteError` per finding, rather than thrown as a
|
|
643
|
+
* single wrapped message: the site build and the book report the same way,
|
|
644
|
+
* each finding at its own line, and a note with three problems meets all
|
|
645
|
+
* three in the run that found them instead of one per rebuild. The thrown
|
|
646
|
+
* sentinel carries `alreadyReported` so the compile loop's generic catch
|
|
647
|
+
* does not add a fourth, vaguer line on top of the three already said.
|
|
648
|
+
*
|
|
649
|
+
* @param {readonly {message: string, line?: number, column?: number}[]} findings
|
|
650
|
+
* @throws {Error} When `findings` is non-empty, after reporting all of them.
|
|
651
|
+
*/
|
|
652
|
+
reportConvertBodyFindings(findings) {
|
|
653
|
+
for (const finding of findings) {
|
|
654
|
+
this.errorCount++;
|
|
655
|
+
this.noteError(finding.message, { line: finding.line, column: finding.column });
|
|
656
|
+
}
|
|
657
|
+
if (findings.length) {
|
|
658
|
+
const error = new Error(
|
|
659
|
+
`${findings.length} finding(s) in the note body, each reported above`,
|
|
660
|
+
);
|
|
661
|
+
error.alreadyReported = true;
|
|
662
|
+
throw error;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
617
666
|
/**
|
|
618
667
|
* The body {@link BasePackCompiler#buildEntry} receives.
|
|
619
668
|
*
|
|
@@ -626,51 +675,114 @@ export class BasePackCompiler {
|
|
|
626
675
|
* that does not convert.
|
|
627
676
|
*/
|
|
628
677
|
convertBody(fm, body) {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
678
|
+
// Every pass's own findings are collected before any of them is
|
|
679
|
+
// reported, so a note with a malformed block *and* a malformed
|
|
680
|
+
// caption *and* a bad expression surfaces all three in one run
|
|
681
|
+
// instead of one per rebuild. Each pass already returns every finding
|
|
682
|
+
// it made — `errors` or `findings`, never just the first — so nothing
|
|
683
|
+
// here re-scans; it only stops taking `[0]`.
|
|
684
|
+
const findings = [];
|
|
685
|
+
for (const secretError of scanBlocks(body).errors) {
|
|
686
|
+
findings.push({
|
|
687
|
+
message: secretError.message,
|
|
634
688
|
line: (this.currentNote?.bodyLine ?? 1) + secretError.line - 1,
|
|
635
689
|
column:
|
|
636
690
|
secretError.line === 1 ?
|
|
637
691
|
(this.currentNote?.bodyColumn ?? 1)
|
|
638
692
|
: secretError.column,
|
|
639
|
-
};
|
|
640
|
-
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
// A heading's attribute block, and a `.secret` written where no surface
|
|
696
|
+
// can honour it. Read off the body as authored, before the tables
|
|
697
|
+
// expand, because a heading is a heading whatever a pass does with the
|
|
698
|
+
// prose under it — so a pass that converts nothing reports these too.
|
|
699
|
+
for (const headingError of [
|
|
700
|
+
...scanHeadingAttributes(body, this.currentNote?.bodyLine ?? 1).errors,
|
|
701
|
+
...withheldSections(body, this.currentNote?.bodyLine ?? 1).errors,
|
|
702
|
+
]) {
|
|
703
|
+
findings.push({
|
|
704
|
+
message: headingError.message,
|
|
705
|
+
line: headingError.line,
|
|
706
|
+
column:
|
|
707
|
+
headingError.line === (this.currentNote?.bodyLine ?? 1) ?
|
|
708
|
+
(this.currentNote?.bodyColumn ?? 1) + headingError.column - 1
|
|
709
|
+
: headingError.column,
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
for (const footnoteError of footnoteFindings(body)) {
|
|
713
|
+
findings.push({
|
|
714
|
+
message: footnoteError.message,
|
|
715
|
+
line: (this.currentNote?.bodyLine ?? 1) + footnoteError.line - 1,
|
|
716
|
+
column:
|
|
717
|
+
footnoteError.line === 1 ?
|
|
718
|
+
(this.currentNote?.bodyColumn ?? 1)
|
|
719
|
+
: footnoteError.column,
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
// An image sharing its paragraph with other text, a bad address or a
|
|
723
|
+
// title with nowhere to draw it — the same findings `lint` reports,
|
|
724
|
+
// asked here too so a build run on its own refuses what the lint
|
|
725
|
+
// already names rather than rendering the directive as though it were
|
|
726
|
+
// absent. `checkImages` already resolves `bodyLine` and `bodyColumn`
|
|
727
|
+
// internally, so its findings need no further adjustment.
|
|
728
|
+
for (const imageError of checkImages(body, "", {
|
|
729
|
+
bodyLine: this.currentNote?.bodyLine ?? 1,
|
|
730
|
+
bodyColumn: this.currentNote?.bodyColumn ?? 1,
|
|
731
|
+
})) {
|
|
732
|
+
findings.push({
|
|
733
|
+
message: imageError.message,
|
|
734
|
+
line: imageError.line,
|
|
735
|
+
column: imageError.column,
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
if (!this.constructor.convertsWikilinks) {
|
|
739
|
+
this.reportConvertBodyFindings(findings);
|
|
740
|
+
return body;
|
|
641
741
|
}
|
|
642
|
-
if (!this.constructor.convertsWikilinks) return body;
|
|
643
742
|
const name = resolveName(fm);
|
|
644
743
|
const { absPath, bodyLine, bodyColumn } = this.currentNote ?? {};
|
|
645
744
|
const { markdown: tabulated, lineMap } = expandNoteTables(body, {
|
|
646
745
|
name,
|
|
647
746
|
bodyLine,
|
|
648
747
|
sqlTables: absPath ? this.sqlTables?.get(absPath) : undefined,
|
|
748
|
+
pageLists: absPath ? this.sqlTables?.pageLists?.get(absPath) : undefined,
|
|
649
749
|
});
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
750
|
+
const figureScan = scanFigures(tabulated);
|
|
751
|
+
for (const captionError of figureScan.errors) {
|
|
752
|
+
findings.push({
|
|
753
|
+
message: captionError.message,
|
|
654
754
|
line:
|
|
655
755
|
(bodyLine ?? 1) +
|
|
656
756
|
(lineMap[captionError.line - 1]?.line ?? captionError.line - 1),
|
|
657
757
|
column: captionError.column,
|
|
658
|
-
};
|
|
659
|
-
throw error;
|
|
758
|
+
});
|
|
660
759
|
}
|
|
760
|
+
// What the `ref` expression helper needs: this note's own figures, by
|
|
761
|
+
// id, and how a reference renders — a same-page wikilink rather than
|
|
762
|
+
// a Markdown link, so the wikilink pass below addresses it exactly as
|
|
763
|
+
// an authored `[[#anchor|Text]]` is, by the page the figure's own
|
|
764
|
+
// fence became. Cross-note resolution is omitted: a journal addresses
|
|
765
|
+
// a page by UUID, which this pass has none of for another note.
|
|
766
|
+
const figuresById = new Map(
|
|
767
|
+
figureScan.figures
|
|
768
|
+
.filter((figure) => figure.id)
|
|
769
|
+
.map((figure) => [
|
|
770
|
+
figure.id,
|
|
771
|
+
{ label: figure.label, caption: figure.caption, hasCaption: figure.hasCaption },
|
|
772
|
+
]),
|
|
773
|
+
);
|
|
661
774
|
const expressions = renderMarkdownExpressions(tabulated, {
|
|
662
775
|
fm,
|
|
663
776
|
dates: reckoningContext(this.linkIndex),
|
|
664
777
|
sqlResults: absPath ? this.sqlTables?.inline?.get(absPath) : undefined,
|
|
665
778
|
file: absPath,
|
|
666
779
|
bodyLine,
|
|
780
|
+
figures: { get: (id) => figuresById.get(id), link: foundryRefLink },
|
|
667
781
|
});
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
const error = new Error(finding.message);
|
|
671
|
-
error.position = { line: finding.line, column: finding.column };
|
|
672
|
-
throw error;
|
|
782
|
+
for (const finding of expressions.findings) {
|
|
783
|
+
findings.push({ message: finding.message, line: finding.line, column: finding.column });
|
|
673
784
|
}
|
|
785
|
+
this.reportConvertBodyFindings(findings);
|
|
674
786
|
const { markdown, unresolved } = convertNoteWikilinks(expressions.markdown, {
|
|
675
787
|
type: fm.type,
|
|
676
788
|
id: fm.id,
|
|
@@ -1237,6 +1349,11 @@ export class BasePackCompiler {
|
|
|
1237
1349
|
stats.compiled++;
|
|
1238
1350
|
this.onCompiled(fm, doc);
|
|
1239
1351
|
} catch (err) {
|
|
1352
|
+
// `convertBody` already reported every finding it collected,
|
|
1353
|
+
// one `noteError` each, before throwing this sentinel — so the
|
|
1354
|
+
// generic wrap below would only restate "failed to compile" on
|
|
1355
|
+
// top of findings an author has already been told about.
|
|
1356
|
+
if (err.alreadyReported) continue;
|
|
1240
1357
|
this.errorCount++;
|
|
1241
1358
|
const repeatedAnchor = err.message.match(
|
|
1242
1359
|
/declares the anchor \{#([^}]+)\} on more than one heading/,
|
|
@@ -0,0 +1,149 @@
|
|
|
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
|
+
* The **base stylesheet** every package ships, and where it lands.
|
|
16
|
+
*
|
|
17
|
+
* Compiled content relies on a handful of rules to be legible — a definition
|
|
18
|
+
* list is the whole of it today — and five packages need them. Four of those
|
|
19
|
+
* have no stylesheet at all, so writing the rules by hand means one edit and
|
|
20
|
+
* four new files, each with a configuration entry, each free to diverge from
|
|
21
|
+
* the first time one of them is touched. This repository already ships
|
|
22
|
+
* configuration to consumers and already generates the manifest that declares
|
|
23
|
+
* a package's `styles`, so it can ship the sheet the same way: one file,
|
|
24
|
+
* staged into every package, corrected in one place.
|
|
25
|
+
*
|
|
26
|
+
* A package keeps its own stylesheet for its own needs. The base sheet carries
|
|
27
|
+
* only what all of them share.
|
|
28
|
+
*
|
|
29
|
+
* Everything here is pure but for {@link baseStylesheetSource}, which resolves
|
|
30
|
+
* a path against this module's own location.
|
|
31
|
+
*
|
|
32
|
+
* @module
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import path from "node:path";
|
|
36
|
+
import { fileURLToPath } from "node:url";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Where the shipped sheet lands inside the staged package.
|
|
40
|
+
*
|
|
41
|
+
* Under `styles/` beside a package's own sheet, and named for the toolchain so
|
|
42
|
+
* nothing a repository writes can collide with it. The path is relative to the
|
|
43
|
+
* staged package root, which is what both the stage entry and the manifest's
|
|
44
|
+
* `styles` are addressed in.
|
|
45
|
+
*
|
|
46
|
+
* @type {string}
|
|
47
|
+
*/
|
|
48
|
+
export const BASE_STYLESHEET_DEST = "styles/package-build-base.css";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The cascade layer the sheet loads into, per package kind.
|
|
52
|
+
*
|
|
53
|
+
* Foundry declares its layer order in `css/foundry2.css`, ending
|
|
54
|
+
* `… layouts, system, modules, exceptions` — the eighth and ninth being, by its
|
|
55
|
+
* own comments, default game system styles and default module styles. That is
|
|
56
|
+
* what this sheet is, so it takes the layer its kind is given and is therefore
|
|
57
|
+
* the weakest thing a package can load:
|
|
58
|
+
*
|
|
59
|
+
* - **An unlayered package sheet beats it**, because an unlayered rule beats
|
|
60
|
+
* every layered one whatever its specificity.
|
|
61
|
+
* - **A package sheet naming any other layer beats it too**, because a layer
|
|
62
|
+
* Foundry's order does not mention is appended after the ones it does.
|
|
63
|
+
*
|
|
64
|
+
* So a package overrides the base sheet from its own, whichever way it declares
|
|
65
|
+
* one, and the override needs no `!important` and no specificity contest.
|
|
66
|
+
*
|
|
67
|
+
* @param {"system"|"module"} artifact - Which artifact is shipped.
|
|
68
|
+
* @returns {string} The layer name.
|
|
69
|
+
*/
|
|
70
|
+
export function baseStyleLayer(artifact) {
|
|
71
|
+
return artifact === "system" ? "system" : "modules";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The manifest `styles` entry for the base sheet.
|
|
76
|
+
*
|
|
77
|
+
* Foundry's `styles` is an array of `{layer?, src}`, and a bare string is
|
|
78
|
+
* migrated into that shape on read, so the object form is written directly.
|
|
79
|
+
*
|
|
80
|
+
* @param {"system"|"module"} artifact - Which artifact is shipped.
|
|
81
|
+
* @returns {{src: string, layer: string}} The entry.
|
|
82
|
+
*/
|
|
83
|
+
export function baseStyleEntry(artifact) {
|
|
84
|
+
return { src: BASE_STYLESHEET_DEST, layer: baseStyleLayer(artifact) };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The absolute path of the sheet as installed, so the stage can copy it.
|
|
89
|
+
*
|
|
90
|
+
* Resolved against this module rather than against the consumer's working
|
|
91
|
+
* directory, because the file belongs to this package and arrives through
|
|
92
|
+
* `node_modules`.
|
|
93
|
+
*
|
|
94
|
+
* @returns {string} Absolute path of the shipped stylesheet.
|
|
95
|
+
*/
|
|
96
|
+
export function baseStylesheetSource() {
|
|
97
|
+
return path.resolve(
|
|
98
|
+
fileURLToPath(new URL("../assets/styles", import.meta.url)),
|
|
99
|
+
"content-base.css",
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The `[source, destination]` pair that stages the sheet.
|
|
105
|
+
*
|
|
106
|
+
* The destination is relative, in the same spelling the repository's own asset
|
|
107
|
+
* table uses, so the caller resolves both against the repository root exactly
|
|
108
|
+
* as it resolves every other entry.
|
|
109
|
+
*
|
|
110
|
+
* @param {string} stageDir - The configured stage directory, relative to the
|
|
111
|
+
* repository root.
|
|
112
|
+
* @returns {[string, string]} The pair, for `stageAssets`.
|
|
113
|
+
*/
|
|
114
|
+
export function baseStyleStageEntry(stageDir) {
|
|
115
|
+
return [baseStylesheetSource(), path.join(stageDir, BASE_STYLESHEET_DEST)];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The manifest's `styles`, with the base sheet named first.
|
|
120
|
+
*
|
|
121
|
+
* **First rather than last**, because `styles` is load order: a package's own
|
|
122
|
+
* sheet has to be read after the base one for an override written at equal
|
|
123
|
+
* specificity in an equal layer to take. The cascade layer makes the override
|
|
124
|
+
* work anyway, and the order makes it work for the right reason.
|
|
125
|
+
*
|
|
126
|
+
* A declared entry addressing the same file is not duplicated — a repository
|
|
127
|
+
* that named the staged path itself gets one entry, keeping whatever layer it
|
|
128
|
+
* chose, so declaring it is a way of placing it rather than an error.
|
|
129
|
+
*
|
|
130
|
+
* @param {object} options
|
|
131
|
+
* @param {unknown} [options.declared] - The declared `styles`, whatever shape
|
|
132
|
+
* it was written in; anything that is not a list is returned untouched.
|
|
133
|
+
* @param {"system"|"module"} options.artifact - Which artifact is shipped.
|
|
134
|
+
* @param {boolean} [options.enabled] - Whether the package takes the base
|
|
135
|
+
* sheet. `false` returns the declared list unchanged.
|
|
136
|
+
* @returns {unknown} The `styles` to emit, or `undefined` when there is
|
|
137
|
+
* nothing to emit at all.
|
|
138
|
+
*/
|
|
139
|
+
export function withBaseStyle({ declared, artifact, enabled = true }) {
|
|
140
|
+
if (!enabled) return declared;
|
|
141
|
+
const list =
|
|
142
|
+
Array.isArray(declared) ? declared
|
|
143
|
+
: declared === undefined ? []
|
|
144
|
+
: declared;
|
|
145
|
+
if (!Array.isArray(list)) return list;
|
|
146
|
+
const names = (entry) => (typeof entry === "string" ? entry : entry?.src);
|
|
147
|
+
if (list.some((entry) => names(entry) === BASE_STYLESHEET_DEST)) return list;
|
|
148
|
+
return [baseStyleEntry(artifact), ...list];
|
|
149
|
+
}
|
package/engine/bundles.mjs
CHANGED
|
@@ -75,6 +75,7 @@ import { hasDocEntry, itemDocEntryId } from "./item-docs.mjs";
|
|
|
75
75
|
import { folderField, renderFoundryMarkdown, resolveName } from "./helpers.mjs";
|
|
76
76
|
import { packForType } from "./ids.mjs";
|
|
77
77
|
import { readQualifier } from "./wikilinks.mjs";
|
|
78
|
+
import { pathnameRoles } from "./art-fields.mjs";
|
|
78
79
|
|
|
79
80
|
/**
|
|
80
81
|
* Load every compiled document a bundle may hold, keyed `<docType>/<id>`.
|
|
@@ -311,6 +312,12 @@ export class Bundles extends BasePackCompiler {
|
|
|
311
312
|
contents.push({ docType: member.docType, document });
|
|
312
313
|
}
|
|
313
314
|
|
|
315
|
+
// The markdown this pass reads has already had its embeds rewritten
|
|
316
|
+
// into ordinary images, each `src` the pathname `pathnameRoles` keys
|
|
317
|
+
// its roles by — so a `:::figure` of a map names itself `Map 1` here
|
|
318
|
+
// exactly as the website and the book name the same picture.
|
|
319
|
+
const roles = pathnameRoles(this.linkIndex);
|
|
320
|
+
|
|
314
321
|
return buildAdventure({
|
|
315
322
|
id: fm.id,
|
|
316
323
|
name,
|
|
@@ -323,9 +330,13 @@ export class Bundles extends BasePackCompiler {
|
|
|
323
330
|
// Foundry renders on the import card. That is why a bundle earns no
|
|
324
331
|
// separate documentation journal the way an item does: the document
|
|
325
332
|
// it compiles into already has somewhere to put the prose.
|
|
326
|
-
description:
|
|
333
|
+
description:
|
|
334
|
+
markdown.trim() ?
|
|
335
|
+
renderFoundryMarkdown(markdown, undefined, undefined, undefined, (pathname) =>
|
|
336
|
+
roles.get(pathname),
|
|
337
|
+
)
|
|
338
|
+
: "",
|
|
327
339
|
folder: this.folderResolver(authoredFolder, { isAddress: folderIsAddress }),
|
|
328
|
-
flags: fm.flags,
|
|
329
340
|
stats: this.stats,
|
|
330
341
|
contents,
|
|
331
342
|
});
|
package/engine/compendiums.mjs
CHANGED
|
@@ -191,7 +191,14 @@ function cleanPackEntry(
|
|
|
191
191
|
|
|
192
192
|
if (data.effects) data.effects.forEach((i) => cleanPackEntry(i, { clearSourceId: false }));
|
|
193
193
|
if (data.items) data.items.forEach((i) => cleanPackEntry(i, { clearSourceId: false }));
|
|
194
|
-
|
|
194
|
+
// A page's own stated default survives, because a page compiled as GM-only
|
|
195
|
+
// says so here and nowhere else; the per-user grants beside it do not, which
|
|
196
|
+
// is what the reset is for. `-1` is `INHERIT`, the value a page that says
|
|
197
|
+
// nothing takes.
|
|
198
|
+
if (data.pages)
|
|
199
|
+
data.pages.forEach((page) =>
|
|
200
|
+
cleanPackEntry(page, { ownership: page.ownership?.default ?? -1 }),
|
|
201
|
+
);
|
|
195
202
|
if (data.system?.description) data.system.description = cleanString(data.system.description);
|
|
196
203
|
if (data.system?.biography) data.system.biography = cleanString(data.system.biography);
|
|
197
204
|
if (data.system?.textReference)
|