@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/site-build.mjs
CHANGED
|
@@ -62,12 +62,20 @@ import { addressSlug } from "./content-address.mjs";
|
|
|
62
62
|
import { protectCode } from "./code-fences.mjs";
|
|
63
63
|
import { renderMarkdownExpressions } from "./markdown-expressions.mjs";
|
|
64
64
|
import { expandContentTables } from "./content-tables.mjs";
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
68
|
-
import {
|
|
69
|
-
import {
|
|
70
|
-
import {
|
|
65
|
+
import { renderBlocks, renderWithheldSections, scanBlocks } from "./content-blocks.mjs";
|
|
66
|
+
import { scanHeadingAttributes, withheldSections } from "./heading-attributes.mjs";
|
|
67
|
+
import { renderFigureBlocks, scanFigures } from "./content-figures.mjs";
|
|
68
|
+
import { footnoteFindings } from "./content-footnotes.mjs";
|
|
69
|
+
import { collectAnchors } from "./anchors.mjs";
|
|
70
|
+
import { checkImages, renderImageFigures } from "./content-images.mjs";
|
|
71
|
+
import { assetPathnameKey, pathnameProblem, resolvePathname } from "./pathnames.mjs";
|
|
72
|
+
import {
|
|
73
|
+
buildSiteIndex,
|
|
74
|
+
figureIndexKeys,
|
|
75
|
+
resolveCrossNoteFigures,
|
|
76
|
+
resolveInfoboxRef,
|
|
77
|
+
wikiContext,
|
|
78
|
+
} from "./site-index.mjs";
|
|
71
79
|
import { frontmatterWikilinks, resolveWebWikilinks } from "./web-wikilinks.mjs";
|
|
72
80
|
import { loadForeignIndexes, noContentIndexPackages } from "./metadata-index.mjs";
|
|
73
81
|
import { noteInfoboxes } from "./infobox-registry.mjs";
|
|
@@ -86,7 +94,8 @@ import { isNoteRecord, noteFile } from "./index-records.mjs";
|
|
|
86
94
|
// The one statement of what an empty body means, shared with the index.
|
|
87
95
|
import { isStubNote } from "./note-state.mjs";
|
|
88
96
|
import { isGmNote } from "./note-vocabulary.mjs";
|
|
89
|
-
import { ART_SLOTS, artPathname, assetAddressIndex } from "./art-fields.mjs";
|
|
97
|
+
import { ART_SLOTS, artPathname, assetAddressIndex, pathnameRoles } from "./art-fields.mjs";
|
|
98
|
+
import { embedRole } from "./content-embeds.mjs";
|
|
90
99
|
import {
|
|
91
100
|
HOMEPAGE_DESTINATION,
|
|
92
101
|
checkHomepageCount,
|
|
@@ -601,8 +610,7 @@ export function pageFrontmatter(page, { decorate, webSrc, artSrc }) {
|
|
|
601
610
|
}
|
|
602
611
|
|
|
603
612
|
/**
|
|
604
|
-
* The title a content page publishes under
|
|
605
|
-
* note's name.
|
|
613
|
+
* The title a content page publishes under — the note's name.
|
|
606
614
|
*
|
|
607
615
|
* One rule, read by the page's own front matter and by every `related` entry
|
|
608
616
|
* that names the page, so a card lists a page by exactly the title its heading
|
|
@@ -612,7 +620,7 @@ export function pageFrontmatter(page, { decorate, webSrc, artSrc }) {
|
|
|
612
620
|
* @returns {string} The title.
|
|
613
621
|
*/
|
|
614
622
|
function pageTitle(page) {
|
|
615
|
-
return page.
|
|
623
|
+
return page.name;
|
|
616
624
|
}
|
|
617
625
|
|
|
618
626
|
/**
|
|
@@ -698,14 +706,18 @@ export function renderSitePage(
|
|
|
698
706
|
sqlTables,
|
|
699
707
|
config,
|
|
700
708
|
artIndex,
|
|
709
|
+
figuresByAddress = new Map(),
|
|
701
710
|
},
|
|
702
711
|
) {
|
|
703
712
|
const tableErrors = [];
|
|
704
713
|
const expressionErrors = [];
|
|
705
714
|
const secretErrors = [];
|
|
706
715
|
const captionErrors = [];
|
|
716
|
+
const headingErrors = [];
|
|
717
|
+
const footnoteErrors = [];
|
|
707
718
|
const wikiErrors = [];
|
|
708
719
|
const imageErrors = [];
|
|
720
|
+
const embedErrors = [];
|
|
709
721
|
const resolved = [];
|
|
710
722
|
const src = page.relPath ?? page.base;
|
|
711
723
|
const ctx = wikiContext(index, {
|
|
@@ -742,10 +754,36 @@ export function renderSitePage(
|
|
|
742
754
|
return src;
|
|
743
755
|
};
|
|
744
756
|
const artSrc = (value, type, accepts) => artPathname(artIndex, value, type, accepts).pathname;
|
|
757
|
+
// The role lookup a `:::figure` fence's `map` counter reaches through —
|
|
758
|
+
// see `engine/content-figures.mjs`'s `resolveRole`. Addressed form, for
|
|
759
|
+
// the page's own scan below, which runs before an embed is rewritten into
|
|
760
|
+
// an ordinary image; `roleByWebSrc` is the same lookup keyed by the web
|
|
761
|
+
// address each picture that reaches `renderImageFigures` resolves to,
|
|
762
|
+
// recorded as it is resolved rather than built across the whole corpus —
|
|
763
|
+
// `webSrc` itself reports an image's own problems as a side effect, which
|
|
764
|
+
// a speculative call over every asset would misfire for one this page
|
|
765
|
+
// never names.
|
|
766
|
+
const resolveRole = (address) => embedRole(artIndex, address);
|
|
767
|
+
const pathRoles = pathnameRoles(artIndex);
|
|
768
|
+
const roleByWebSrc = new Map();
|
|
769
|
+
const webSrcWithRole = (src) => {
|
|
770
|
+
const result = webSrc(src);
|
|
771
|
+
const role = pathRoles.get(src);
|
|
772
|
+
if (role) roleByWebSrc.set(result, role);
|
|
773
|
+
return result;
|
|
774
|
+
};
|
|
775
|
+
// The picture's role and pixel size, by the address it resolved to — read
|
|
776
|
+
// before `webSrc` translates it to the page's own host. A body image is
|
|
777
|
+
// free to write the bare, own-package form, so the address is normalized
|
|
778
|
+
// to the pathname the asset index keys by first.
|
|
779
|
+
const lookupAsset = (src) => {
|
|
780
|
+
const key = assetPathnameKey(src, config);
|
|
781
|
+
return key ? artIndex?.byPath?.get(key) : undefined;
|
|
782
|
+
};
|
|
745
783
|
const resolve = (text) => {
|
|
746
784
|
let transformed = pass.beforeLinks ? pass.beforeLinks(text, page) : text;
|
|
747
785
|
transformed = resolveWebWikilinks(transformed, ctx);
|
|
748
|
-
return renderImageFigures(transformed,
|
|
786
|
+
return renderImageFigures(transformed, webSrcWithRole, lookupAsset);
|
|
749
787
|
};
|
|
750
788
|
|
|
751
789
|
const { markdown, errors, lineMap } = expandContentTables(page.body, {
|
|
@@ -753,55 +791,104 @@ export function renderSitePage(
|
|
|
753
791
|
linkable,
|
|
754
792
|
source: src,
|
|
755
793
|
sqlTables: sqlTables?.get(page.file),
|
|
794
|
+
pageLists: sqlTables?.pageLists?.get(page.file),
|
|
756
795
|
self: { fm: searchableFrontmatter(page.fm, page.pkg), path: page.relPath },
|
|
757
796
|
});
|
|
758
797
|
tableErrors.push(...errors);
|
|
759
|
-
const
|
|
760
|
-
ctx.captionLabels = new Map(
|
|
798
|
+
const figureScan = scanFigures(markdown, { resolveRole });
|
|
799
|
+
ctx.captionLabels = new Map(
|
|
800
|
+
figureScan.figures.filter((figure) => figure.id).map((figure) => [figure.id, figure.label]),
|
|
801
|
+
);
|
|
802
|
+
// What the `ref` expression helper needs beyond the label: the caption
|
|
803
|
+
// text and whether one was authored, by the same id `ctx.captionLabels`
|
|
804
|
+
// keys on.
|
|
805
|
+
const figuresById = new Map(
|
|
806
|
+
figureScan.figures
|
|
807
|
+
.filter((figure) => figure.id)
|
|
808
|
+
.map((figure) => [
|
|
809
|
+
figure.id,
|
|
810
|
+
{ label: figure.label, caption: figure.caption, hasCaption: figure.hasCaption },
|
|
811
|
+
]),
|
|
812
|
+
);
|
|
813
|
+
// This page's own anchors, so a `[[#slug]]` self-link is checked against
|
|
814
|
+
// what the page actually declares rather than trusted unconditionally.
|
|
815
|
+
ctx.anchors = new Set(collectAnchors(markdown).map((anchor) => anchor.slug));
|
|
761
816
|
const expressions = renderMarkdownExpressions(markdown, {
|
|
762
817
|
fm: page.fm,
|
|
763
818
|
dates: index.dateContext,
|
|
764
819
|
file: page.file,
|
|
765
820
|
bodyLine: page.bodyLine,
|
|
766
821
|
sqlResults: sqlTables?.inline?.get(page.file),
|
|
822
|
+
figures: {
|
|
823
|
+
get: (id) => figuresById.get(id),
|
|
824
|
+
// A cross-note `ref` resolves the written address exactly as a
|
|
825
|
+
// wikilink does, against every page's figures scanned before any
|
|
826
|
+
// page's expressions render — see `renderPages`.
|
|
827
|
+
note: (target) =>
|
|
828
|
+
resolveCrossNoteFigures(target, {
|
|
829
|
+
contentTypes: index.contentTypes,
|
|
830
|
+
packages: index.packages,
|
|
831
|
+
noIndexPackages: index.noIndexPackages,
|
|
832
|
+
contentPackage: index.contentPackage,
|
|
833
|
+
siteIndexMap: index.index,
|
|
834
|
+
figuresByAddress,
|
|
835
|
+
}),
|
|
836
|
+
},
|
|
767
837
|
});
|
|
768
838
|
expressionErrors.push(...expressions.findings);
|
|
769
839
|
const data = pageFrontmatter(page, { decorate, webSrc, artSrc });
|
|
770
|
-
const
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
840
|
+
const blocks = renderBlocks(protectCode(expressions.markdown, resolve), "web");
|
|
841
|
+
const figured = renderFigureBlocks(blocks.markdown, undefined, undefined, {
|
|
842
|
+
resolveRole: (address) => roleByWebSrc.get(address),
|
|
843
|
+
});
|
|
844
|
+
for (const error of figureScan.errors)
|
|
774
845
|
captionErrors.push({
|
|
775
846
|
file: page.file,
|
|
776
847
|
line: (page.bodyLine ?? 1) + (lineMap[error.line - 1]?.line ?? error.line - 1),
|
|
777
848
|
column: error.column,
|
|
778
849
|
message: error.message,
|
|
779
850
|
});
|
|
780
|
-
for (const error of
|
|
851
|
+
for (const error of scanBlocks(page.body).errors)
|
|
781
852
|
secretErrors.push({
|
|
782
853
|
file: page.file,
|
|
783
854
|
line: (page.bodyLine ?? 1) + error.line - 1,
|
|
784
855
|
column: error.column,
|
|
785
856
|
message: error.message,
|
|
786
857
|
});
|
|
787
|
-
for (const error of
|
|
788
|
-
|
|
858
|
+
for (const error of scanHeadingAttributes(page.body, page.bodyLine ?? 1).errors)
|
|
859
|
+
headingErrors.push({ file: page.file, ...error });
|
|
860
|
+
for (const error of withheldSections(page.body, page.bodyLine ?? 1).errors)
|
|
861
|
+
headingErrors.push({ file: page.file, ...error });
|
|
862
|
+
for (const error of footnoteFindings(page.body))
|
|
863
|
+
footnoteErrors.push({
|
|
789
864
|
file: page.file,
|
|
790
865
|
line: (page.bodyLine ?? 1) + error.line - 1,
|
|
791
866
|
column: error.column,
|
|
792
867
|
message: error.message,
|
|
793
868
|
});
|
|
869
|
+
// An image sharing its paragraph with other text, or an address or title
|
|
870
|
+
// the lint already refuses — asked here too, so a site build run on its
|
|
871
|
+
// own fails on the same input `renderImageFigures` otherwise renders as
|
|
872
|
+
// though the directive were absent.
|
|
873
|
+
for (const error of checkImages(page.body, page.file, { bodyLine: page.bodyLine ?? 1 }))
|
|
874
|
+
embedErrors.push(error);
|
|
794
875
|
return {
|
|
795
876
|
page,
|
|
796
|
-
|
|
877
|
+
// The disclosure is written last, over the Markdown the page ships: the
|
|
878
|
+
// passes before this one carry line positions into their findings, and a
|
|
879
|
+
// line inserted ahead of them would move every one of them.
|
|
880
|
+
body: renderWithheldSections(figured.markdown),
|
|
797
881
|
data,
|
|
798
882
|
resolved,
|
|
799
883
|
tableErrors,
|
|
800
884
|
expressionErrors,
|
|
801
885
|
secretErrors,
|
|
802
886
|
captionErrors,
|
|
887
|
+
headingErrors,
|
|
888
|
+
footnoteErrors,
|
|
803
889
|
wikiErrors,
|
|
804
890
|
imageErrors,
|
|
891
|
+
embedErrors,
|
|
805
892
|
};
|
|
806
893
|
}
|
|
807
894
|
|
|
@@ -840,7 +927,7 @@ export function renderSitePage(
|
|
|
840
927
|
* it by wikilink, and its own markdown links name content pages. `maps` is
|
|
841
928
|
* each drawing by the URL of the page that carries it.
|
|
842
929
|
* @returns {{written: number, byKind: Record<string, number>, tableErrors: object[],
|
|
843
|
-
* wikiErrors: object[], imageErrors: object[],
|
|
930
|
+
* wikiErrors: object[], imageErrors: object[], embedErrors: object[],
|
|
844
931
|
* related: Map<string, import("./related-pages.mjs").Related>,
|
|
845
932
|
* maps: number}} `related`
|
|
846
933
|
* is keyed by page URL, and holds the homepage's block beside every content
|
|
@@ -873,12 +960,47 @@ export function renderPages(pages, options) {
|
|
|
873
960
|
types: index?.contentTypes ?? [],
|
|
874
961
|
});
|
|
875
962
|
|
|
963
|
+
// Every page's own figures, by the address a `ref` crossing into it would
|
|
964
|
+
// write — scanned once, before any page's own expressions render, so a
|
|
965
|
+
// page citing another's figure finds it already numbered. Numbering stays
|
|
966
|
+
// per page: each `scanFigures` call here starts its own counters, exactly
|
|
967
|
+
// as the page's own render does, so the number a cross-note `ref` reports
|
|
968
|
+
// is the one the target's own page carries.
|
|
969
|
+
const figuresByAddress = new Map();
|
|
970
|
+
for (const page of pages) {
|
|
971
|
+
const shortcode = page.fm.shortcode;
|
|
972
|
+
if (typeof shortcode !== "string" || !shortcode) continue;
|
|
973
|
+
const src = page.relPath ?? page.base;
|
|
974
|
+
const { markdown } = expandContentTables(page.body, {
|
|
975
|
+
docs: universe.get(page.pkg) ?? [],
|
|
976
|
+
linkable,
|
|
977
|
+
source: src,
|
|
978
|
+
sqlTables: sqlTables?.get(page.file),
|
|
979
|
+
pageLists: sqlTables?.pageLists?.get(page.file),
|
|
980
|
+
self: { fm: searchableFrontmatter(page.fm, page.pkg), path: page.relPath },
|
|
981
|
+
});
|
|
982
|
+
const byId = new Map(
|
|
983
|
+
scanFigures(markdown)
|
|
984
|
+
.figures.filter((figure) => figure.id)
|
|
985
|
+
.map((figure) => [
|
|
986
|
+
figure.id,
|
|
987
|
+
{ label: figure.label, caption: figure.caption, hasCaption: figure.hasCaption },
|
|
988
|
+
]),
|
|
989
|
+
);
|
|
990
|
+
const type = String(page.fm.type ?? "").toLowerCase();
|
|
991
|
+
for (const key of figureIndexKeys(page.pkg, type, shortcode))
|
|
992
|
+
figuresByAddress.set(key, byId);
|
|
993
|
+
}
|
|
994
|
+
|
|
876
995
|
const tableErrors = [];
|
|
877
996
|
const expressionErrors = [];
|
|
878
997
|
const secretErrors = [];
|
|
879
998
|
const captionErrors = [];
|
|
999
|
+
const headingErrors = [];
|
|
1000
|
+
const footnoteErrors = [];
|
|
880
1001
|
const wikiErrors = [];
|
|
881
1002
|
const imageErrors = [];
|
|
1003
|
+
const embedErrors = [];
|
|
882
1004
|
const byKind = {};
|
|
883
1005
|
// The link graph, as `(source URL, target URL)` — read off each page's
|
|
884
1006
|
// resolution below, and off the homepage's markdown links.
|
|
@@ -915,13 +1037,17 @@ export function renderPages(pages, options) {
|
|
|
915
1037
|
sqlTables,
|
|
916
1038
|
config,
|
|
917
1039
|
artIndex,
|
|
1040
|
+
figuresByAddress,
|
|
918
1041
|
});
|
|
919
1042
|
tableErrors.push(...result.tableErrors);
|
|
920
1043
|
expressionErrors.push(...result.expressionErrors);
|
|
921
1044
|
secretErrors.push(...result.secretErrors);
|
|
922
1045
|
captionErrors.push(...result.captionErrors);
|
|
1046
|
+
headingErrors.push(...result.headingErrors);
|
|
1047
|
+
footnoteErrors.push(...result.footnoteErrors);
|
|
923
1048
|
wikiErrors.push(...result.wikiErrors);
|
|
924
1049
|
imageErrors.push(...result.imageErrors);
|
|
1050
|
+
embedErrors.push(...result.embedErrors);
|
|
925
1051
|
rendered.push({ page, body: result.body, data: result.data });
|
|
926
1052
|
for (const hit of result.resolved) if (hit.url) edges.push([page.url, hit.url]);
|
|
927
1053
|
}
|
|
@@ -963,8 +1089,11 @@ export function renderPages(pages, options) {
|
|
|
963
1089
|
expressionErrors,
|
|
964
1090
|
secretErrors,
|
|
965
1091
|
captionErrors,
|
|
1092
|
+
headingErrors,
|
|
1093
|
+
footnoteErrors,
|
|
966
1094
|
wikiErrors,
|
|
967
1095
|
imageErrors,
|
|
1096
|
+
embedErrors,
|
|
968
1097
|
related,
|
|
969
1098
|
maps: withMap,
|
|
970
1099
|
...(capture ? { ...(outputs ? { outputs } : {}), edges, entries } : {}),
|
|
@@ -1015,6 +1144,7 @@ export function sitePageDecorator(config, index) {
|
|
|
1015
1144
|
resolve: (ref, hint) => resolveInfoboxRef(index, ref, hint),
|
|
1016
1145
|
router,
|
|
1017
1146
|
dates: index.dateContext,
|
|
1147
|
+
contentPackage: index.contentPackage,
|
|
1018
1148
|
});
|
|
1019
1149
|
};
|
|
1020
1150
|
}
|
|
@@ -1036,7 +1166,8 @@ export function sitePageDecorator(config, index) {
|
|
|
1036
1166
|
* `sql` directive with none prepared is a table error: nothing here runs a
|
|
1037
1167
|
* query.
|
|
1038
1168
|
* @returns {{gates: object, stats: object|null, tableErrors: object[],
|
|
1039
|
-
* wikiErrors: object[], imageErrors: object[],
|
|
1169
|
+
* wikiErrors: object[], imageErrors: object[], embedErrors: object[],
|
|
1170
|
+
* mapFindings: object[],
|
|
1040
1171
|
* manifests: object|null}} `mapFindings` is what drawing the maps found —
|
|
1041
1172
|
* warnings, never a reason to fail the build.
|
|
1042
1173
|
*/
|
|
@@ -1131,8 +1262,11 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1131
1262
|
expressionErrors: [],
|
|
1132
1263
|
secretErrors: [],
|
|
1133
1264
|
captionErrors: [],
|
|
1265
|
+
headingErrors: [],
|
|
1266
|
+
footnoteErrors: [],
|
|
1134
1267
|
wikiErrors: [],
|
|
1135
1268
|
imageErrors: [],
|
|
1269
|
+
embedErrors: [],
|
|
1136
1270
|
mapFindings: [],
|
|
1137
1271
|
stats: null,
|
|
1138
1272
|
};
|
|
@@ -1153,8 +1287,11 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1153
1287
|
expressionErrors: [],
|
|
1154
1288
|
secretErrors: [],
|
|
1155
1289
|
captionErrors: [],
|
|
1290
|
+
headingErrors: [],
|
|
1291
|
+
footnoteErrors: [],
|
|
1156
1292
|
wikiErrors: [],
|
|
1157
1293
|
imageErrors: [],
|
|
1294
|
+
embedErrors: [],
|
|
1158
1295
|
mapFindings: [],
|
|
1159
1296
|
stats: {
|
|
1160
1297
|
homepages: writeHomepages(homeRoot, homepages, resolved),
|
|
@@ -1201,8 +1338,11 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1201
1338
|
expressionErrors: [],
|
|
1202
1339
|
secretErrors: [],
|
|
1203
1340
|
captionErrors: [],
|
|
1341
|
+
headingErrors: [],
|
|
1342
|
+
footnoteErrors: [],
|
|
1204
1343
|
wikiErrors: [],
|
|
1205
1344
|
imageErrors: [],
|
|
1345
|
+
embedErrors: [],
|
|
1206
1346
|
mapFindings: [],
|
|
1207
1347
|
};
|
|
1208
1348
|
}
|
|
@@ -1259,8 +1399,11 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1259
1399
|
expressionErrors: rendered.expressionErrors,
|
|
1260
1400
|
secretErrors: rendered.secretErrors,
|
|
1261
1401
|
captionErrors: rendered.captionErrors,
|
|
1402
|
+
headingErrors: rendered.headingErrors,
|
|
1403
|
+
footnoteErrors: rendered.footnoteErrors,
|
|
1262
1404
|
wikiErrors: rendered.wikiErrors,
|
|
1263
1405
|
imageErrors: rendered.imageErrors,
|
|
1406
|
+
embedErrors: rendered.embedErrors,
|
|
1264
1407
|
mapFindings: drawn.findings,
|
|
1265
1408
|
stats: {
|
|
1266
1409
|
...rendered.byKind,
|
package/engine/site-config.mjs
CHANGED
|
@@ -102,9 +102,18 @@ export const DISABLE_KINDS = Object.freeze(["section", "taxonomy", "term", "RSS"
|
|
|
102
102
|
* `<span>` marking an unresolved link — and Goldmark drops raw HTML unless
|
|
103
103
|
* told otherwise. A theme cannot supply this: Hugo does not merge a theme's
|
|
104
104
|
* `markup` block.
|
|
105
|
+
*
|
|
106
|
+
* `extensions.taskList` is off because the format has no checkbox: Goldmark
|
|
107
|
+
* enables it by default, which is the only reason a list item opened with
|
|
108
|
+
* `[ ]` renders a `<input type="checkbox">` here and the same two characters as
|
|
109
|
+
* plain text in the pack and the book. Off, a list item's text begins right
|
|
110
|
+
* after its marker on every surface.
|
|
105
111
|
*/
|
|
106
112
|
export const MARKUP = Object.freeze({
|
|
107
|
-
goldmark: Object.freeze({
|
|
113
|
+
goldmark: Object.freeze({
|
|
114
|
+
renderer: Object.freeze({ unsafe: true }),
|
|
115
|
+
extensions: Object.freeze({ taskList: false }),
|
|
116
|
+
}),
|
|
108
117
|
});
|
|
109
118
|
|
|
110
119
|
/** Where the navigation is published. */
|
package/engine/site-index.mjs
CHANGED
|
@@ -51,6 +51,9 @@
|
|
|
51
51
|
|
|
52
52
|
import { buildReferenceTargets } from "./reference-targets.mjs";
|
|
53
53
|
import { standingsDigest } from "./standings.mjs";
|
|
54
|
+
// The leaf reader, shared with the content index and the link checker, so a
|
|
55
|
+
// page's anchors are read once rather than re-derived a third way here.
|
|
56
|
+
import { collectAnchors } from "./anchors.mjs";
|
|
54
57
|
|
|
55
58
|
import { resolveShortcodeReference } from "./shortcode-references.mjs";
|
|
56
59
|
|
|
@@ -63,10 +66,12 @@ import {
|
|
|
63
66
|
renderAddress,
|
|
64
67
|
isAddressTuple,
|
|
65
68
|
ownDocumentSystem,
|
|
69
|
+
expandAddress,
|
|
66
70
|
} from "./address.mjs";
|
|
67
71
|
import { NOTE_SYSTEM } from "./systems.mjs";
|
|
68
72
|
import { contentPackage } from "./content-package.mjs";
|
|
69
73
|
import { reckoningContext } from "./reckoning-markers.mjs";
|
|
74
|
+
import { presentAmongRecords } from "./being-age.mjs";
|
|
70
75
|
// The declared tag vocabulary, which is where `draft` is stated.
|
|
71
76
|
import { isDraftNote } from "./note-vocabulary.mjs";
|
|
72
77
|
|
|
@@ -83,16 +88,24 @@ import { isDraftNote } from "./note-vocabulary.mjs";
|
|
|
83
88
|
* @property {string} slug URL segment.
|
|
84
89
|
* @property {string} base Source file's basename, e.g. `Climbing.md`.
|
|
85
90
|
* @property {string} url The page's published address.
|
|
91
|
+
* @property {string} [body] The note's markdown body, for its anchors. A
|
|
92
|
+
* content entry always carries one; absent, the
|
|
93
|
+
* page is indexed as declaring none.
|
|
86
94
|
*/
|
|
87
95
|
|
|
88
96
|
/**
|
|
89
97
|
* The resolved index and everything a wikilink resolver reads beside it.
|
|
90
98
|
*
|
|
91
99
|
* @typedef {object} SiteIndex
|
|
92
|
-
* @property {Map<string, {url: string, name?: string, draft?: boolean}>} index
|
|
100
|
+
* @property {Map<string, {url: string, name?: string, draft?: boolean, anchors?: Set<string>|Record<string, string>}>} index
|
|
93
101
|
* Address → page. `draft` says the page
|
|
94
102
|
* carries the `draft` tag, which marks a
|
|
95
|
-
* link *into* it.
|
|
103
|
+
* link *into* it. `anchors` names the
|
|
104
|
+
* `{#slug}` sections it declares — a
|
|
105
|
+
* `Set` for a local page, or the
|
|
106
|
+
* `{slug: uuid}` map a foreign manifest
|
|
107
|
+
* publishes — and is absent where
|
|
108
|
+
* neither build recorded one.
|
|
96
109
|
* @property {Set<string>} ambiguous Short addresses claimed by two
|
|
97
110
|
* packages, and so deliberately absent
|
|
98
111
|
* from `index`.
|
|
@@ -229,6 +242,11 @@ export function buildSiteIndex(
|
|
|
229
242
|
draft: isDraftNote(e.fm),
|
|
230
243
|
...(e.fm.subType ? { subType: e.fm.subType } : {}),
|
|
231
244
|
...(standings ? { standings } : {}),
|
|
245
|
+
// The `{#anchor}` slugs this page declares, so a `#section`
|
|
246
|
+
// written against it can be checked rather than joined onto the
|
|
247
|
+
// URL unverified. A Set even when empty: a page that declares no
|
|
248
|
+
// anchor still has a definite answer for one an author names.
|
|
249
|
+
anchors: new Set(collectAnchors(e.body ?? "").map((anchor) => anchor.slug)),
|
|
232
250
|
};
|
|
233
251
|
|
|
234
252
|
const shortcode = e.fm.shortcode;
|
|
@@ -261,7 +279,12 @@ export function buildSiteIndex(
|
|
|
261
279
|
|
|
262
280
|
return {
|
|
263
281
|
contentPackage: ownPackage,
|
|
264
|
-
|
|
282
|
+
// Carried on the same context `resolvedDateFields` reads: a recurring
|
|
283
|
+
// event's `next` is computed against this present.
|
|
284
|
+
dateContext: {
|
|
285
|
+
...reckoningContext({ notes: records }),
|
|
286
|
+
present: presentAmongRecords(records),
|
|
287
|
+
},
|
|
265
288
|
referenceTargets: buildReferenceTargets(records, new Map([...foreignReferences, ...index])),
|
|
266
289
|
index,
|
|
267
290
|
ambiguous,
|
|
@@ -272,6 +295,70 @@ export function buildSiteIndex(
|
|
|
272
295
|
};
|
|
273
296
|
}
|
|
274
297
|
|
|
298
|
+
/**
|
|
299
|
+
* The address keys one page's figures are reachable by — the same two forms
|
|
300
|
+
* {@link buildSiteIndex} assigns the page itself, so a `ref` crossing into it
|
|
301
|
+
* resolves by exactly the address a wikilink would use.
|
|
302
|
+
*
|
|
303
|
+
* @param {string} pkg - The page's own content package.
|
|
304
|
+
* @param {string} type - The note's `type`.
|
|
305
|
+
* @param {string} shortcode - The note's `shortcode`.
|
|
306
|
+
* @returns {Set<string>} The short `type/shortcode` key and the canonical
|
|
307
|
+
* `package-system-type-shortcode` one.
|
|
308
|
+
*/
|
|
309
|
+
export function figureIndexKeys(pkg, type, shortcode) {
|
|
310
|
+
const system = ownDocumentSystem(type);
|
|
311
|
+
const keys = new Set([
|
|
312
|
+
`${type}/${shortcode}`.toLowerCase(),
|
|
313
|
+
canonicalKey(pkg, system, type, shortcode),
|
|
314
|
+
]);
|
|
315
|
+
if (system !== NOTE_SYSTEM) keys.add(canonicalKey(pkg, NOTE_SYSTEM, type, shortcode));
|
|
316
|
+
return keys;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Resolve a `ref` call's cross-note address to the target's own figures and
|
|
321
|
+
* its own address — the `figures.note` a `ref` helper's render context reads,
|
|
322
|
+
* built once and threaded into every page.
|
|
323
|
+
*
|
|
324
|
+
* Reads the written address exactly as a wikilink does
|
|
325
|
+
* ({@link readQualifier}, {@link expandAddress}), so a `ref` crossing into
|
|
326
|
+
* another note and a `[[…]]` link to the same note agree on what resolves and
|
|
327
|
+
* what does not: a target naming no known type or no fetched package's index
|
|
328
|
+
* is unresolved for both, and one address names one note for both.
|
|
329
|
+
*
|
|
330
|
+
* @param {string} target - The address as written, anchor already removed —
|
|
331
|
+
* {@link module:engine/wikilink-syntax.ParsedWikilink.target}.
|
|
332
|
+
* @param {object} options
|
|
333
|
+
* @param {Set<string>} options.contentTypes - Every type this build reads as
|
|
334
|
+
* an address qualifier.
|
|
335
|
+
* @param {Set<string>} options.packages - Every package an address may name.
|
|
336
|
+
* @param {Set<string>} options.noIndexPackages - Packages declared
|
|
337
|
+
* `contentIndex: false`.
|
|
338
|
+
* @param {string} options.contentPackage - The package a target with no
|
|
339
|
+
* package segment defaults to.
|
|
340
|
+
* @param {Map<string, {url: string}>} options.siteIndexMap - {@link SiteIndex}'s
|
|
341
|
+
* own `index`, read for the target's existence and its URL.
|
|
342
|
+
* @param {Map<string, Map<string, object>>} options.figuresByAddress - Every
|
|
343
|
+
* note's figures, by id, keyed as {@link figureIndexKeys} keys the note.
|
|
344
|
+
* @returns {{url: string, figures: Map<string, object>}|undefined} The
|
|
345
|
+
* target's own address and figures, or `undefined` when the address names
|
|
346
|
+
* no note this build resolves.
|
|
347
|
+
*/
|
|
348
|
+
export function resolveCrossNoteFigures(
|
|
349
|
+
target,
|
|
350
|
+
{ contentTypes, packages, noIndexPackages, contentPackage, siteIndexMap, figuresByAddress },
|
|
351
|
+
) {
|
|
352
|
+
const read = readQualifier(target, contentTypes, packages, noIndexPackages);
|
|
353
|
+
if (!read || read.reason) return undefined;
|
|
354
|
+
const key = expandAddress(read, { package: contentPackage, system: NOTE_SYSTEM });
|
|
355
|
+
const shortKey = `${read.itemDoc ? "doc" : ""}${read.type}/${read.shortcode}`.toLowerCase();
|
|
356
|
+
const entry = siteIndexMap.get(key) ?? siteIndexMap.get(shortKey);
|
|
357
|
+
if (!entry) return undefined;
|
|
358
|
+
const figures = figuresByAddress.get(key) ?? figuresByAddress.get(shortKey) ?? new Map();
|
|
359
|
+
return { url: entry.url, figures };
|
|
360
|
+
}
|
|
361
|
+
|
|
275
362
|
/**
|
|
276
363
|
* The per-page context a wikilink resolver takes.
|
|
277
364
|
*
|
package/engine/site-preview.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import { reckoningContext } from "./reckoning-markers.mjs";
|
|
|
18
18
|
import { cachedMetadataIndexes, noContentIndexPackages } from "./metadata-index.mjs";
|
|
19
19
|
import { buildSiteIndex } from "./site-index.mjs";
|
|
20
20
|
import { openNotesDatabase, prepareSqlTables, findSqlBlocks } from "./sql-tables.mjs";
|
|
21
|
+
import { findPageListBlocks } from "./page-lists.mjs";
|
|
21
22
|
import { isGmNote } from "./note-vocabulary.mjs";
|
|
22
23
|
import { relatedPages } from "./related-pages.mjs";
|
|
23
24
|
import { holdingsNode, holdingsPages, foreignHoldingsNodes } from "./holdings.mjs";
|
|
@@ -90,9 +91,13 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
|
|
|
90
91
|
);
|
|
91
92
|
try {
|
|
92
93
|
const sources = pages
|
|
93
|
-
.filter(
|
|
94
|
+
.filter(
|
|
95
|
+
(page) =>
|
|
96
|
+
findSqlBlocks(page.body).length || findPageListBlocks(page.body).length,
|
|
97
|
+
)
|
|
94
98
|
.map((page) => ({ source: page.file, markdown: page.body }));
|
|
95
|
-
const
|
|
99
|
+
const publicRecords = records.filter((record) => !isGmNote(record));
|
|
100
|
+
const sqlTables = await prepareSqlTables(db, sources, { records: publicRecords });
|
|
96
101
|
const rendered = renderPages(pages, {
|
|
97
102
|
index: gates.index,
|
|
98
103
|
foreign: gates.foreign,
|
|
@@ -214,9 +219,11 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
|
|
|
214
219
|
},
|
|
215
220
|
);
|
|
216
221
|
}
|
|
217
|
-
const sqlTables = await prepareSqlTables(
|
|
218
|
-
|
|
219
|
-
|
|
222
|
+
const sqlTables = await prepareSqlTables(
|
|
223
|
+
db,
|
|
224
|
+
[{ source: absolute, markdown: body }],
|
|
225
|
+
{ records: records.filter((record) => !isGmNote(record)) },
|
|
226
|
+
);
|
|
220
227
|
const allPages = snapshot.pages.map((item) =>
|
|
221
228
|
item.file === absolute ? page : item,
|
|
222
229
|
);
|
|
@@ -246,6 +253,10 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
|
|
|
246
253
|
findings.push({ ...error, severity: "error" });
|
|
247
254
|
for (const error of result.captionErrors)
|
|
248
255
|
findings.push({ ...error, severity: "error" });
|
|
256
|
+
for (const error of result.headingErrors)
|
|
257
|
+
findings.push({ ...error, severity: "error" });
|
|
258
|
+
for (const error of result.footnoteErrors)
|
|
259
|
+
findings.push({ ...error, severity: "error" });
|
|
249
260
|
for (const error of result.wikiErrors) {
|
|
250
261
|
const pos = positionOfLiteral(text, error.link, error.occurrence);
|
|
251
262
|
findings.push({
|
|
@@ -259,13 +270,15 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
|
|
|
259
270
|
const pos = positionOfLiteral(text, error.src, error.occurrence);
|
|
260
271
|
findings.push({ ...error, ...pos, severity: "error" });
|
|
261
272
|
}
|
|
273
|
+
for (const error of result.embedErrors)
|
|
274
|
+
findings.push({ ...error, severity: "error" });
|
|
262
275
|
if (findings.length) return { ok: false, findings };
|
|
263
276
|
const edges = snapshot.rendered.edges.filter(([source]) => source !== original.url);
|
|
264
277
|
for (const hit of result.resolved) if (hit.url) edges.push([page.url, hit.url]);
|
|
265
278
|
const entries = new Map(snapshot.rendered.entries);
|
|
266
279
|
entries.delete(original.url);
|
|
267
280
|
entries.set(page.url, {
|
|
268
|
-
title:
|
|
281
|
+
title: page.name,
|
|
269
282
|
url: page.url,
|
|
270
283
|
type: String(fm.type),
|
|
271
284
|
});
|
|
@@ -273,7 +286,7 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
|
|
|
273
286
|
if (related) result.data.related = related;
|
|
274
287
|
const holdings = holdingsPages([
|
|
275
288
|
...allPages.map((item) =>
|
|
276
|
-
holdingsNode(item.fm, { title: item.
|
|
289
|
+
holdingsNode(item.fm, { title: item.name, url: item.url }),
|
|
277
290
|
),
|
|
278
291
|
...foreignHoldingsNodes(snapshot.gates.foreign.index),
|
|
279
292
|
]);
|