@heroiclands/package-build 23.0.0 → 23.1.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 +58 -0
- package/README.md +1 -1
- package/bin/content-commands.mjs +1 -1
- package/ci/content-format-reference.mjs +46 -0
- package/docs/api.md +124 -141
- package/docs/authoring/assets.md +1 -1
- package/docs/authoring/dates-and-calendars.md +12 -6
- package/docs/authoring/frontmatter.md +2 -2
- package/docs/authoring/links-and-markup.md +145 -29
- package/docs/commands.md +8 -10
- package/docs/configuration.md +13 -11
- package/docs/content-format.md +2 -2
- package/docs/guides/book.md +59 -4
- package/docs/guides/icons.md +4 -5
- package/docs/guides/packs.md +6 -0
- package/docs/guides/reference-pages.md +21 -1
- package/docs/guides/release-and-deploy.md +17 -1
- package/docs/guides/site.md +17 -18
- package/docs/reference/format-details.md +877 -276
- package/docs/reference/note-types.md +278 -91
- package/docs/reference/theme-pages.md +58 -97
- package/engine/address-values.mjs +124 -4
- package/engine/address.mjs +44 -3
- package/engine/anchors.mjs +103 -3
- package/engine/art-slots.mjs +2 -9
- package/engine/base-compiler.mjs +25 -6
- package/engine/calendar-artifacts.mjs +4 -1
- package/engine/calendar-notes.mjs +166 -102
- package/engine/content-format-check.mjs +2 -3
- package/engine/content-format.mjs +44 -1
- package/engine/content-format.yaml +79 -16
- package/engine/content-images.mjs +10 -1
- package/engine/content-index.mjs +37 -9
- package/engine/content-links.mjs +24 -5
- package/engine/content-tables.mjs +24 -3
- package/engine/data-keys.mjs +347 -0
- package/engine/derived-sections.mjs +322 -0
- package/engine/diagnostics.mjs +28 -0
- package/engine/event-fields.mjs +187 -0
- package/engine/event-rows.mjs +126 -0
- package/engine/event-views.mjs +305 -0
- package/engine/exported-scene.mjs +29 -53
- package/engine/frontmatter-lint.mjs +103 -77
- package/engine/frontmatter.mjs +7 -2
- package/engine/generate.mjs +53 -0
- package/engine/generated-sections.mjs +223 -0
- package/engine/heading-attributes.mjs +4 -2
- package/engine/helpers.mjs +12 -1
- package/engine/holdings.mjs +12 -33
- package/engine/ids.mjs +17 -11
- package/engine/index.mjs +4 -4
- package/engine/infobox-render.mjs +21 -6
- package/engine/infobox.mjs +217 -99
- package/engine/journals.mjs +104 -47
- package/engine/literature-notes.mjs +4 -1
- package/engine/literature-works.mjs +21 -13
- package/engine/map-graphviz.mjs +4 -18
- package/engine/markdown-expressions.mjs +116 -11
- package/engine/metadata-index.mjs +26 -0
- package/engine/near-miss.mjs +90 -0
- package/engine/note-addresses.mjs +75 -19
- package/engine/note-dates.mjs +5 -3
- package/engine/note-event-terms.mjs +364 -0
- package/engine/note-events.mjs +823 -0
- package/engine/note-vocabulary.mjs +184 -88
- package/engine/office-holders.mjs +20 -54
- package/engine/packagebuild-index.mjs +16 -1
- package/engine/pdf-build.mjs +97 -60
- package/engine/pdf-index.mjs +263 -0
- package/engine/pdf-render.mjs +47 -5
- package/engine/place-relation-terms.mjs +174 -0
- package/engine/place-relations.mjs +72 -160
- package/engine/rank-ladder.mjs +14 -41
- package/engine/reckoning-markers.mjs +2 -0
- package/engine/region-events.mjs +6 -12
- package/engine/scenes.mjs +73 -468
- package/engine/site-build.mjs +75 -101
- package/engine/site-index.mjs +3 -1
- package/engine/site-maps.mjs +43 -29
- package/engine/site-preview.mjs +40 -31
- package/engine/social-ties.mjs +2 -1
- package/engine/sql-tables.mjs +212 -9
- package/engine/standing-terms.mjs +145 -6
- package/engine/standings.mjs +37 -49
- package/engine/web-wikilinks.mjs +21 -1
- package/engine/wikilink-syntax.mjs +34 -4
- package/engine/wikilinks.mjs +27 -2
- package/hugo-theme/layouts/partials/infobox/entry.html +4 -1
- package/hugo-theme/layouts/partials/page-body.html +0 -3
- package/hugo-theme/static/css/style.css +25 -55
- package/package.json +1 -2
- package/packagebuild-metadata.jsonl +13 -13
- package/sohl/note-schemas.mjs +8 -73
- package/types/engine/address-values.d.mts +50 -3
- package/types/engine/address.d.mts +21 -0
- package/types/engine/anchors.d.mts +49 -2
- package/types/engine/art-slots.d.mts +1 -1
- package/types/engine/base-compiler.d.mts +13 -4
- package/types/engine/calendar-notes.d.mts +7 -0
- package/types/engine/content-format.d.mts +52 -0
- package/types/engine/content-images.d.mts +5 -1
- package/types/engine/content-tables.d.mts +9 -1
- package/types/engine/data-keys.d.mts +145 -0
- package/types/engine/derived-sections.d.mts +73 -0
- package/types/engine/diagnostics.d.mts +15 -0
- package/types/engine/event-fields.d.mts +99 -0
- package/types/engine/event-rows.d.mts +12 -0
- package/types/engine/event-views.d.mts +67 -0
- package/types/engine/exported-scene.d.mts +5 -2
- package/types/engine/frontmatter-lint.d.mts +3 -3
- package/types/engine/generated-sections.d.mts +143 -0
- package/types/engine/holdings.d.mts +6 -20
- package/types/engine/ids.d.mts +15 -12
- package/types/engine/index.d.mts +1 -1
- package/types/engine/infobox.d.mts +13 -3
- package/types/engine/journals.d.mts +24 -11
- package/types/engine/literature-works.d.mts +1 -2
- package/types/engine/map-graphviz.d.mts +2 -10
- package/types/engine/markdown-expressions.d.mts +12 -3
- package/types/engine/near-miss.d.mts +42 -0
- package/types/engine/note-addresses.d.mts +7 -1
- package/types/engine/note-event-terms.d.mts +68 -0
- package/types/engine/note-events.d.mts +71 -0
- package/types/engine/note-vocabulary.d.mts +37 -8
- package/types/engine/office-holders.d.mts +3 -0
- package/types/engine/pdf-index.d.mts +35 -0
- package/types/engine/pdf-render.d.mts +8 -2
- package/types/engine/place-relation-terms.d.mts +83 -0
- package/types/engine/place-relations.d.mts +11 -66
- package/types/engine/rank-ladder.d.mts +2 -0
- package/types/engine/region-events.d.mts +6 -12
- package/types/engine/scenes.d.mts +6 -32
- package/types/engine/site-build.d.mts +8 -22
- package/types/engine/site-maps.d.mts +15 -7
- package/types/engine/sql-tables.d.mts +77 -2
- package/types/engine/standing-terms.d.mts +37 -4
- package/types/engine/standings.d.mts +14 -9
- package/types/engine/wikilink-syntax.d.mts +20 -0
- package/types/engine/wikilinks.d.mts +3 -2
- package/docs/guides/government-migration.md +0 -52
- package/engine/itinerary-scenes.mjs +0 -166
- package/engine/lore-events.mjs +0 -197
- package/engine/map-notes.mjs +0 -1309
- package/engine/map-raster.mjs +0 -51
- package/hugo-theme/layouts/partials/holdings/entry.html +0 -21
- package/hugo-theme/layouts/partials/holdings/kind-groups.html +0 -71
- package/hugo-theme/layouts/partials/holdings.html +0 -61
- package/hugo-theme/layouts/partials/literature-works.html +0 -29
- package/hugo-theme/layouts/partials/place-map.html +0 -9
- package/types/engine/itinerary-scenes.d.mts +0 -29
- package/types/engine/lore-events.d.mts +0 -14
- package/types/engine/map-notes.d.mts +0 -297
- package/types/engine/map-raster.d.mts +0 -8
package/engine/site-build.mjs
CHANGED
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
* `/<package>/<type>-<shortcode>/`. Nothing is generated between them — no
|
|
37
37
|
* section directory, no listing, no `_index.md` but the root's. An index of
|
|
38
38
|
* what the package publishes is a `doc` note carrying a content table, and it
|
|
39
|
-
* is authored where every other page is.
|
|
40
|
-
*
|
|
41
|
-
*
|
|
39
|
+
* is authored where every other page is. Every page is one file; the map from a
|
|
40
|
+
* place is set inline in its **From here** section — see
|
|
41
|
+
* `engine/site-maps.mjs`.
|
|
42
42
|
*
|
|
43
43
|
* **Every gate reports; none exits.** The integrity checks a site build needs —
|
|
44
44
|
* a wikilink authored in frontmatter, a name that yields no slug, an unusable
|
|
@@ -62,13 +62,14 @@ import matter from "gray-matter";
|
|
|
62
62
|
import { addressSlug } from "./content-address.mjs";
|
|
63
63
|
import { protectCode } from "./code-fences.mjs";
|
|
64
64
|
import { renderMarkdownExpressions } from "./markdown-expressions.mjs";
|
|
65
|
+
import { eventNoteIndex } from "./event-fields.mjs";
|
|
65
66
|
import { expandContentTables } from "./content-tables.mjs";
|
|
66
67
|
import { renderSpans, scanSpans } from "./content-spans.mjs";
|
|
67
68
|
import { renderBlocks, renderWithheldSections, scanBlocks } from "./content-blocks.mjs";
|
|
68
69
|
import { scanHeadingAttributes, withheldSections } from "./heading-attributes.mjs";
|
|
69
70
|
import { renderFigureBlocks, scanFigures } from "./content-figures.mjs";
|
|
70
71
|
import { footnoteFindings } from "./content-footnotes.mjs";
|
|
71
|
-
import { collectAnchors, markupAnchorFindings } from "./anchors.mjs";
|
|
72
|
+
import { collectAnchors, eventAnchors, markupAnchorFindings } from "./anchors.mjs";
|
|
72
73
|
import { checkImages, renderImageFigures } from "./content-images.mjs";
|
|
73
74
|
import { assetPathnameKey, pathnameProblem, resolvePathname } from "./pathnames.mjs";
|
|
74
75
|
import {
|
|
@@ -95,7 +96,7 @@ import { resolvedDateFields } from "./note-dates.mjs";
|
|
|
95
96
|
import { isNoteRecord, noteFile } from "./index-records.mjs";
|
|
96
97
|
// The one statement of what an empty body means, shared with the index.
|
|
97
98
|
import { isStubNote } from "./note-state.mjs";
|
|
98
|
-
import {
|
|
99
|
+
import { isGmNote } from "./note-vocabulary.mjs";
|
|
99
100
|
import { ART_SLOTS, artPathname, assetAddressIndex, pathnameRoles } from "./art-fields.mjs";
|
|
100
101
|
import { embedRole } from "./content-embeds.mjs";
|
|
101
102
|
import {
|
|
@@ -108,9 +109,9 @@ import {
|
|
|
108
109
|
import { publishesContentPages } from "../content-config.mjs";
|
|
109
110
|
import { HUGO_CONTENT } from "./site-config.mjs";
|
|
110
111
|
import { homepageLinkTargets, relatedPages } from "./related-pages.mjs";
|
|
111
|
-
import {
|
|
112
|
-
import {
|
|
113
|
-
import {
|
|
112
|
+
import { drawSiteMaps, inlineDrawingFigure } from "./site-maps.mjs";
|
|
113
|
+
import { generatedDrawing, markGenerated, splitGenerated } from "./generated-sections.mjs";
|
|
114
|
+
import { IMAGE_PATTERN } from "./content-images.mjs";
|
|
114
115
|
|
|
115
116
|
const require = createRequire(import.meta.url);
|
|
116
117
|
|
|
@@ -566,10 +567,7 @@ export function tableUniverse(pages) {
|
|
|
566
567
|
* about every other page, known only once the whole tree has resolved — see
|
|
567
568
|
* {@link module:engine/related-pages} — so an authored value is dropped the
|
|
568
569
|
* way `aliases` is, and {@link renderPages} writes the derived block once it
|
|
569
|
-
* holds the graph.
|
|
570
|
-
* same reason — see {@link module:engine/holdings}. **So is `map`**: the
|
|
571
|
-
* file a place page names is the one the build drew beside it — see
|
|
572
|
-
* {@link module:engine/site-maps} — and a page with no drawing names none.
|
|
570
|
+
* holds the graph.
|
|
573
571
|
*
|
|
574
572
|
* @param {object} page - The page.
|
|
575
573
|
* @param {object} options
|
|
@@ -604,12 +602,8 @@ export function pageFrontmatter(page, { decorate, webSrc, artSrc }) {
|
|
|
604
602
|
kbfolder: page.folder,
|
|
605
603
|
};
|
|
606
604
|
if (decorate) decorate(data, page);
|
|
607
|
-
if (data.type === "affiliation" && data.data) delete data.data.domains;
|
|
608
605
|
delete data.aliases;
|
|
609
606
|
delete data.related;
|
|
610
|
-
for (const key of HOLDINGS_KEYS) delete data[key];
|
|
611
|
-
delete data[WORKS_KEY];
|
|
612
|
-
delete data.map;
|
|
613
607
|
if (webSrc && artSrc) resolveArtFields(data, webSrc, artSrc);
|
|
614
608
|
return data;
|
|
615
609
|
}
|
|
@@ -682,20 +676,11 @@ function isPlainObject(value) {
|
|
|
682
676
|
* `doc` note's `subType` may be spelled the same as another note's `type`, and
|
|
683
677
|
* `doc-gear.md` and `weapongear-gear.md` are distinct.
|
|
684
678
|
*
|
|
685
|
-
* **A page that carries a file beside itself is a leaf bundle**, `<slug>/index.md`,
|
|
686
|
-
* because that is the one shape under which Hugo hands a page its own
|
|
687
|
-
* resources — the map from a place, inlined by the theme through
|
|
688
|
-
* `.Resources.Get`. The directory is the page, not a section: it holds no
|
|
689
|
-
* `_index.md`, and the page's stated `url` keeps its address exactly where the
|
|
690
|
-
* flat file's was.
|
|
691
|
-
*
|
|
692
679
|
* @param {{slug: string}} page - The page.
|
|
693
|
-
* @param {object} [opts]
|
|
694
|
-
* @param {boolean} [opts.bundle=false] - Whether the page carries a resource.
|
|
695
680
|
* @returns {string} The file, relative to the mount.
|
|
696
681
|
*/
|
|
697
|
-
export function pageDestination(page
|
|
698
|
-
return
|
|
682
|
+
export function pageDestination(page) {
|
|
683
|
+
return `${page.slug}.md`;
|
|
699
684
|
}
|
|
700
685
|
|
|
701
686
|
/** Transform one content page without writing the site mount. */
|
|
@@ -712,6 +697,8 @@ export function renderSitePage(
|
|
|
712
697
|
config,
|
|
713
698
|
artIndex,
|
|
714
699
|
figuresByAddress = new Map(),
|
|
700
|
+
events,
|
|
701
|
+
drawings = new Map(),
|
|
715
702
|
},
|
|
716
703
|
) {
|
|
717
704
|
const tableErrors = [];
|
|
@@ -788,10 +775,21 @@ export function renderSitePage(
|
|
|
788
775
|
const resolve = (text) => {
|
|
789
776
|
let transformed = pass.beforeLinks ? pass.beforeLinks(text, page) : text;
|
|
790
777
|
transformed = resolveWebWikilinks(transformed, ctx);
|
|
791
|
-
return renderImageFigures(transformed, webSrcWithRole, lookupAsset);
|
|
778
|
+
return renderImageFigures(inlineDrawings(transformed), webSrcWithRole, lookupAsset);
|
|
792
779
|
};
|
|
780
|
+
// A generated **From here** section names the drawing the build made, which
|
|
781
|
+
// the asset host does not serve: it is set inline, so its place names stay
|
|
782
|
+
// links. A drawing this build did not make is left for the image pass,
|
|
783
|
+
// which reports it.
|
|
784
|
+
const inlineDrawings = (text) =>
|
|
785
|
+
text.replace(new RegExp(IMAGE_PATTERN.source, "g"), (whole, alt, src) => {
|
|
786
|
+
const name = generatedDrawing(src);
|
|
787
|
+
const drawing = name ? drawings.get(name) : undefined;
|
|
788
|
+
if (!drawing) return whole;
|
|
789
|
+
return inlineDrawingFigure(fs.readFileSync(drawing.file, "utf8"), alt);
|
|
790
|
+
});
|
|
793
791
|
|
|
794
|
-
const { markdown, errors, lineMap } = expandContentTables(page.body, {
|
|
792
|
+
const { markdown, errors, lineMap, generatedFrom } = expandContentTables(page.body, {
|
|
795
793
|
docs: universe.get(page.pkg) ?? [],
|
|
796
794
|
linkable,
|
|
797
795
|
source: src,
|
|
@@ -818,12 +816,14 @@ export function renderSitePage(
|
|
|
818
816
|
// This page's own anchors, so a `[[#slug]]` self-link is checked against
|
|
819
817
|
// what the page actually declares rather than trusted unconditionally.
|
|
820
818
|
ctx.anchors = new Set(collectAnchors(markdown).map((anchor) => anchor.slug));
|
|
821
|
-
|
|
819
|
+
ctx.eventAnchors = new Set(eventAnchors(page.fm).map((anchor) => anchor.slug));
|
|
820
|
+
const expressions = renderMarkdownExpressions(markGenerated(markdown, generatedFrom), {
|
|
822
821
|
fm: page.fm,
|
|
823
822
|
dates: index.dateContext,
|
|
824
823
|
file: page.file,
|
|
825
824
|
bodyLine: page.bodyLine,
|
|
826
825
|
sqlResults: sqlTables?.inline?.get(page.file),
|
|
826
|
+
events,
|
|
827
827
|
figures: {
|
|
828
828
|
get: (id) => figuresById.get(id),
|
|
829
829
|
// A cross-note `ref` resolves the written address exactly as a
|
|
@@ -842,14 +842,17 @@ export function renderSitePage(
|
|
|
842
842
|
});
|
|
843
843
|
expressionErrors.push(...expressions.findings);
|
|
844
844
|
const data = pageFrontmatter(page, { decorate, webSrc, artSrc });
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
845
|
+
// The author's text and the generated sections are resolved apart, so the
|
|
846
|
+
// links a generated section makes join no page's Related card: that card is
|
|
847
|
+
// what the authors link, and the sections already list what they derive.
|
|
848
|
+
const { authored, generated } = splitGenerated(expressions.markdown);
|
|
849
|
+
const authoredBody = protectCode(authored, resolve);
|
|
850
|
+
const authoredLinks = resolved.length;
|
|
851
|
+
const resolvedBody =
|
|
852
|
+
generated ? `${authoredBody}\n${protectCode(generated, resolve)}` : authoredBody;
|
|
853
|
+
const figured = renderFigureBlocks(resolvedBody, undefined, undefined, {
|
|
854
|
+
resolveRole: (address) => roleByWebSrc.get(address),
|
|
855
|
+
});
|
|
853
856
|
for (const error of figureScan.errors)
|
|
854
857
|
captionErrors.push({
|
|
855
858
|
file: page.file,
|
|
@@ -896,7 +899,7 @@ export function renderSitePage(
|
|
|
896
899
|
.markdown,
|
|
897
900
|
),
|
|
898
901
|
data,
|
|
899
|
-
resolved,
|
|
902
|
+
resolved: resolved.slice(0, authoredLinks),
|
|
900
903
|
tableErrors,
|
|
901
904
|
expressionErrors,
|
|
902
905
|
secretErrors,
|
|
@@ -931,25 +934,23 @@ export function renderSitePage(
|
|
|
931
934
|
* point the resolver answers, and inverted once between them. Nothing is
|
|
932
935
|
* resolved twice.
|
|
933
936
|
*
|
|
934
|
-
* **A map is
|
|
937
|
+
* **A map is set inline in its page.** The drawing from a place is made before
|
|
935
938
|
* this render — see {@link module:engine/site-maps} — and handed in keyed by
|
|
936
|
-
*
|
|
937
|
-
* copied beside it, and the front matter names it as `map`. Every other page
|
|
938
|
-
* is written flat, and carries no such key.
|
|
939
|
+
* the file name the page's **From here** section names.
|
|
939
940
|
*
|
|
940
941
|
* @param {object[]} pages - Every page.
|
|
941
942
|
* @param {object} options - Everything the render needs. `homepages` is the
|
|
942
943
|
* homepage as the index knows it — `{ fm, body, url, title }` — which takes
|
|
943
944
|
* no part in the render and every part in the graph: a content page links
|
|
944
945
|
* it by wikilink, and its own markdown links name content pages. `maps` is
|
|
945
|
-
* each drawing by the
|
|
946
|
+
* each drawing by the file name a **From here** section names.
|
|
946
947
|
* @returns {{written: number, byKind: Record<string, number>, tableErrors: object[],
|
|
947
948
|
* wikiErrors: object[], imageErrors: object[], embedErrors: object[],
|
|
948
949
|
* related: Map<string, import("./related-pages.mjs").Related>,
|
|
949
950
|
* maps: number}} `related`
|
|
950
951
|
* is keyed by page URL, and holds the homepage's block beside every content
|
|
951
952
|
* page's, so the caller can write it on the page this render does not.
|
|
952
|
-
* `maps` counts the pages
|
|
953
|
+
* `maps` counts the pages carrying a map.
|
|
953
954
|
*/
|
|
954
955
|
export function renderPages(pages, options) {
|
|
955
956
|
const {
|
|
@@ -1009,6 +1010,14 @@ export function renderPages(pages, options) {
|
|
|
1009
1010
|
figuresByAddress.set(key, byId);
|
|
1010
1011
|
}
|
|
1011
1012
|
|
|
1013
|
+
// Every note's events by Address, for an inline reference to an event's
|
|
1014
|
+
// field — the same lookup the Foundry compile and the book build.
|
|
1015
|
+
const events = eventNoteIndex(records, {
|
|
1016
|
+
contentPackage: index?.contentPackage,
|
|
1017
|
+
foreignIndex: foreign?.index,
|
|
1018
|
+
dates: index?.dateContext,
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1012
1021
|
const tableErrors = [];
|
|
1013
1022
|
const expressionErrors = [];
|
|
1014
1023
|
const secretErrors = [];
|
|
@@ -1055,6 +1064,8 @@ export function renderPages(pages, options) {
|
|
|
1055
1064
|
config,
|
|
1056
1065
|
artIndex,
|
|
1057
1066
|
figuresByAddress,
|
|
1067
|
+
events,
|
|
1068
|
+
drawings: maps,
|
|
1058
1069
|
});
|
|
1059
1070
|
tableErrors.push(...result.tableErrors);
|
|
1060
1071
|
expressionErrors.push(...result.expressionErrors);
|
|
@@ -1070,55 +1081,16 @@ export function renderPages(pages, options) {
|
|
|
1070
1081
|
}
|
|
1071
1082
|
|
|
1072
1083
|
const related = relatedPages(edges, entries);
|
|
1073
|
-
// Geography follows parents; governing-body lists follow explicit government
|
|
1074
|
-
// references across this package and every fetched index.
|
|
1075
|
-
const holdings = holdingsPages(
|
|
1076
|
-
[
|
|
1077
|
-
...pages.map((page) =>
|
|
1078
|
-
holdingsNode(page.fm, { title: pageTitle(page), url: page.url, package: page.pkg }),
|
|
1079
|
-
),
|
|
1080
|
-
...foreignHoldingsNodes(foreign?.index),
|
|
1081
|
-
],
|
|
1082
|
-
{
|
|
1083
|
-
governmentNodes: records
|
|
1084
|
-
.filter((record) => !isGmNote(record))
|
|
1085
|
-
.map((record) =>
|
|
1086
|
-
holdingsNode(record, {
|
|
1087
|
-
title: record.name?.full ?? record.shortcode,
|
|
1088
|
-
package: config?.contentPackage ?? pages[0]?.pkg,
|
|
1089
|
-
}),
|
|
1090
|
-
),
|
|
1091
|
-
},
|
|
1092
|
-
);
|
|
1093
|
-
|
|
1094
|
-
// Each work of literature lists on the pages of the subjects it names,
|
|
1095
|
-
// including works a fetched index carries.
|
|
1096
|
-
const works = worksPages(
|
|
1097
|
-
[
|
|
1098
|
-
...pages.map((page) =>
|
|
1099
|
-
worksNode(page.fm, { title: pageTitle(page), url: page.url, package: page.pkg }),
|
|
1100
|
-
),
|
|
1101
|
-
...foreignWorksNodes(foreign?.index),
|
|
1102
|
-
],
|
|
1103
|
-
{ types: new Set(Object.keys(NOTE_VOCABULARY)) },
|
|
1104
|
-
);
|
|
1105
1084
|
|
|
1106
1085
|
let withMap = 0;
|
|
1107
1086
|
const outputs = capture === true ? new Map() : null;
|
|
1108
1087
|
for (const { page, body, data } of rendered) {
|
|
1109
1088
|
const block = related.get(page.url);
|
|
1110
1089
|
if (block) data.related = block;
|
|
1111
|
-
|
|
1112
|
-
Object.assign(data, works.get(page.url));
|
|
1113
|
-
const map = maps.get(page.url);
|
|
1114
|
-
if (map) {
|
|
1115
|
-
data.map = map.name;
|
|
1116
|
-
withMap += 1;
|
|
1117
|
-
}
|
|
1090
|
+
if (/\{#fromhere\}/.test(sqlTables?.get(page.file)?.generated ?? "")) withMap += 1;
|
|
1118
1091
|
if (write) {
|
|
1119
|
-
const dest = path.join(outRoot, pageDestination(page
|
|
1092
|
+
const dest = path.join(outRoot, pageDestination(page));
|
|
1120
1093
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
1121
|
-
if (map) fs.copyFileSync(map.file, path.join(path.dirname(dest), map.name));
|
|
1122
1094
|
fs.writeFileSync(dest, matter.stringify(body, encodeAddresses(data)));
|
|
1123
1095
|
}
|
|
1124
1096
|
if (outputs) outputs.set(page.file, { markdown: body, frontmatter: data });
|
|
@@ -1181,7 +1153,11 @@ export function resolveSitePass(name, options) {
|
|
|
1181
1153
|
export function sitePageDecorator(config, index) {
|
|
1182
1154
|
const router = routerFor(config);
|
|
1183
1155
|
return (data, page) => {
|
|
1184
|
-
|
|
1156
|
+
// A being's dates reach the page; a note's events reach no page.
|
|
1157
|
+
const { events: _events, ...resolvedDates } = resolvedDateFields(
|
|
1158
|
+
page.fm,
|
|
1159
|
+
index.dateContext,
|
|
1160
|
+
);
|
|
1185
1161
|
if (Object.keys(resolvedDates).length) data.resolvedDates = resolvedDates;
|
|
1186
1162
|
data.infoboxes = noteInfoboxes(page.fm, {
|
|
1187
1163
|
resolve: (ref, hint) => resolveInfoboxRef(index, ref, hint),
|
|
@@ -1396,20 +1372,18 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1396
1372
|
config: resolved,
|
|
1397
1373
|
});
|
|
1398
1374
|
|
|
1399
|
-
// The map from each
|
|
1400
|
-
// be addressable and before any is written, so the page
|
|
1401
|
-
// drawing
|
|
1402
|
-
// in a drawing links through — the same `<base><slug>/`
|
|
1403
|
-
// composes.
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
})
|
|
1412
|
-
: { maps: new Map(), findings: [] };
|
|
1375
|
+
// The map from each place given a From here section, drawn now that every
|
|
1376
|
+
// page is known to be addressable and before any is written, so the page
|
|
1377
|
+
// writer sets each drawing inline in its section. Drawn with `base`, which
|
|
1378
|
+
// is what every name in a drawing links through — the same `<base><slug>/`
|
|
1379
|
+
// every other href composes. With `site.maps: false` no place is given the
|
|
1380
|
+
// section, nothing is drawn and nothing is asked of GraphViz.
|
|
1381
|
+
const drawn = drawSiteMaps({
|
|
1382
|
+
records: ctx.records,
|
|
1383
|
+
foreignIndex: gates.foreign.index,
|
|
1384
|
+
config: resolved,
|
|
1385
|
+
base,
|
|
1386
|
+
});
|
|
1413
1387
|
|
|
1414
1388
|
const rendered = renderPages(pages, {
|
|
1415
1389
|
outRoot: out,
|
package/engine/site-index.mjs
CHANGED
|
@@ -53,7 +53,7 @@ import { buildReferenceTargets } from "./reference-targets.mjs";
|
|
|
53
53
|
import { standingsDigest } from "./standings.mjs";
|
|
54
54
|
// The leaf reader, shared with the content index and the link checker, so a
|
|
55
55
|
// page's anchors are read once rather than re-derived a third way here.
|
|
56
|
-
import { collectAnchors } from "./anchors.mjs";
|
|
56
|
+
import { collectAnchors, eventAnchors } from "./anchors.mjs";
|
|
57
57
|
|
|
58
58
|
import { resolveShortcodeReference } from "./shortcode-references.mjs";
|
|
59
59
|
|
|
@@ -247,6 +247,8 @@ export function buildSiteIndex(
|
|
|
247
247
|
// URL unverified. A Set even when empty: a page that declares no
|
|
248
248
|
// anchor still has a definite answer for one an author names.
|
|
249
249
|
anchors: new Set(collectAnchors(e.body ?? "").map((anchor) => anchor.slug)),
|
|
250
|
+
// The note's event ids, which a wikilink may not name.
|
|
251
|
+
eventAnchors: new Set(eventAnchors(e.fm).map((anchor) => anchor.slug)),
|
|
250
252
|
};
|
|
251
253
|
|
|
252
254
|
const shortcode = e.fm.shortcode;
|
package/engine/site-maps.mjs
CHANGED
|
@@ -12,24 +12,22 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* The maps a site build
|
|
15
|
+
* The maps a site build draws for its place pages.
|
|
16
16
|
*
|
|
17
|
-
* A place that states, or is named in, a border or a route
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* the build renders does.
|
|
17
|
+
* A place that states, or is named in, a border or a route is given a
|
|
18
|
+
* **From here** section — see {@link module:engine/derived-sections} — naming
|
|
19
|
+
* the drawing `package-build map --from` makes. The site build draws that map
|
|
20
|
+
* for every such place and sets it inline in the section, so its place names
|
|
21
|
+
* are links to their pages: every drawing is made with the site's base, and
|
|
22
|
+
* every `href` in it composes `<base><slug>/` the way every other href the
|
|
23
|
+
* build renders does.
|
|
25
24
|
*
|
|
26
|
-
* The npm Graphviz runtime draws each map. `site.maps: false`
|
|
27
|
-
*
|
|
25
|
+
* The npm Graphviz runtime draws each map. `site.maps: false` gives no place
|
|
26
|
+
* the section, and nothing is drawn.
|
|
28
27
|
*
|
|
29
28
|
* The drawings land under `build/map/site/` — beside the author's own
|
|
30
29
|
* drawings under `build/map/`, and apart from them, because the site's
|
|
31
|
-
* carry links and the author's may not
|
|
32
|
-
* content mount. A build never mutates its inputs.
|
|
30
|
+
* carry links and the author's may not. A build never mutates its inputs.
|
|
33
31
|
*
|
|
34
32
|
* @module
|
|
35
33
|
*/
|
|
@@ -37,7 +35,9 @@
|
|
|
37
35
|
import fs from "node:fs";
|
|
38
36
|
import path from "node:path";
|
|
39
37
|
|
|
40
|
-
import {
|
|
38
|
+
import { placesWithMaps } from "./derived-sections.mjs";
|
|
39
|
+
import { fromHereFile } from "./generated-sections.mjs";
|
|
40
|
+
import { buildMaps } from "./map-build.mjs";
|
|
41
41
|
import { mapWorld } from "./map-places.mjs";
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -51,8 +51,7 @@ export const SITE_MAP_DIR = "build/map/site";
|
|
|
51
51
|
* A drawing as the page writer receives it.
|
|
52
52
|
*
|
|
53
53
|
* @typedef {object} SiteMap
|
|
54
|
-
* @property {string} name - The file's name
|
|
55
|
-
* value of the page's `map` key: `from-<shortcode>.svg`.
|
|
54
|
+
* @property {string} name - The file's name: `from-<shortcode>.svg`.
|
|
56
55
|
* @property {string} file - Where the drawing is, absolute.
|
|
57
56
|
*/
|
|
58
57
|
|
|
@@ -86,7 +85,7 @@ export function inlineSvg(svg) {
|
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
/**
|
|
89
|
-
* Draw the map from every
|
|
88
|
+
* Draw the map from every place of this package that is given one.
|
|
90
89
|
*
|
|
91
90
|
* @param {object} opts
|
|
92
91
|
* @param {Array<Record<string, any>>} opts.records - The package's index
|
|
@@ -98,8 +97,8 @@ export function inlineSvg(svg) {
|
|
|
98
97
|
* ending in a slash: what every name in a drawing links through.
|
|
99
98
|
* @returns {{maps: Map<string, SiteMap>, findings: Array<{file: string,
|
|
100
99
|
* line?: number, column?: number, severity: "error"|"warning",
|
|
101
|
-
* message: string}>}} The drawings, keyed by the
|
|
102
|
-
*
|
|
100
|
+
* message: string}>}} The drawings, keyed by the file name a **From here**
|
|
101
|
+
* section names, and warnings for relations that name no place.
|
|
103
102
|
*/
|
|
104
103
|
export function drawSiteMaps({ records, foreignIndex, config, base }) {
|
|
105
104
|
/** @type {Map<string, SiteMap>} */
|
|
@@ -109,6 +108,8 @@ export function drawSiteMaps({ records, foreignIndex, config, base }) {
|
|
|
109
108
|
// the map an earlier run drew.
|
|
110
109
|
fs.rmSync(outDir, { recursive: true, force: true });
|
|
111
110
|
|
|
111
|
+
const centres = placesWithMaps(records, { foreignIndex, config });
|
|
112
|
+
if (centres.length === 0) return { maps, findings: [] };
|
|
112
113
|
const world = mapWorld({
|
|
113
114
|
records,
|
|
114
115
|
foreignIndex,
|
|
@@ -116,20 +117,33 @@ export function drawSiteMaps({ records, foreignIndex, config, base }) {
|
|
|
116
117
|
base,
|
|
117
118
|
config,
|
|
118
119
|
});
|
|
119
|
-
// This package's related places, which are the ones with a page here to
|
|
120
|
-
// carry a map. A dependency's place is drawn on those maps and gets none
|
|
121
|
-
// of its own from this build.
|
|
122
|
-
const centres = relatedPlaces(world.places).filter((s) => world.places.get(s)?.local);
|
|
123
|
-
if (centres.length === 0) return { maps, findings: [] };
|
|
124
|
-
|
|
125
120
|
const { findings } = buildMaps({ world, outDir, from: centres });
|
|
126
121
|
for (const centre of centres) {
|
|
127
|
-
const
|
|
128
|
-
if (!place?.url) continue;
|
|
129
|
-
const name = `from-${centre}.svg`;
|
|
122
|
+
const name = fromHereFile(centre);
|
|
130
123
|
const file = path.join(outDir, name);
|
|
131
124
|
fs.writeFileSync(file, inlineSvg(fs.readFileSync(file, "utf8")));
|
|
132
|
-
maps.set(
|
|
125
|
+
maps.set(name, { name, file });
|
|
133
126
|
}
|
|
134
127
|
return { maps, findings };
|
|
135
128
|
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The figure the website sets a drawing in: the SVG inline, so its place names
|
|
132
|
+
* stay links, with no blank line inside it — a blank line would end the HTML
|
|
133
|
+
* block and hand the rest of the drawing to the Markdown renderer.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} svg - The drawing, as {@link inlineSvg} wrote it.
|
|
136
|
+
* @param {string} alt - What the drawing shows, for a screen reader.
|
|
137
|
+
* @returns {string} One HTML block.
|
|
138
|
+
*/
|
|
139
|
+
export function inlineDrawingFigure(svg, alt) {
|
|
140
|
+
const label = String(alt ?? "").replace(/[&<>"]/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
141
|
+
const body = String(svg)
|
|
142
|
+
.split("\n")
|
|
143
|
+
.filter((line) => line.trim() !== "")
|
|
144
|
+
.join("\n");
|
|
145
|
+
return (
|
|
146
|
+
`<figure class="note-image note-image-full-width" role="img" ` +
|
|
147
|
+
`aria-label="${label}">\n${body}\n</figure>`
|
|
148
|
+
);
|
|
149
|
+
}
|
package/engine/site-preview.mjs
CHANGED
|
@@ -17,17 +17,25 @@ import { applyComputedBeingAge, presentAmongRecords } from "./being-age.mjs";
|
|
|
17
17
|
import { reckoningContext } from "./reckoning-markers.mjs";
|
|
18
18
|
import { cachedMetadataIndexes, noContentIndexPackages } from "./metadata-index.mjs";
|
|
19
19
|
import { buildSiteIndex } from "./site-index.mjs";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
attachGeneratedSections,
|
|
22
|
+
findSqlBlocks,
|
|
23
|
+
generatedSectionsFor,
|
|
24
|
+
joinGeneratedSections,
|
|
25
|
+
openNotesDatabase,
|
|
26
|
+
prepareSqlTables,
|
|
27
|
+
} from "./sql-tables.mjs";
|
|
21
28
|
import { findPageListBlocks } from "./page-lists.mjs";
|
|
22
29
|
import { isGmNote } from "./note-vocabulary.mjs";
|
|
23
30
|
import { relatedPages } from "./related-pages.mjs";
|
|
24
|
-
import { holdingsNode, holdingsPages, foreignHoldingsNodes } from "./holdings.mjs";
|
|
25
31
|
import { assetAddressIndex } from "./art-fields.mjs";
|
|
32
|
+
import { eventNoteIndex } from "./event-fields.mjs";
|
|
26
33
|
import { frontmatterWikilinks } from "./web-wikilinks.mjs";
|
|
27
34
|
import { positionOfLiteral } from "./diagnostics.mjs";
|
|
28
35
|
import { addressSlug } from "./content-address.mjs";
|
|
29
36
|
import { homepageTitle } from "./homepage.mjs";
|
|
30
37
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
38
|
+
import { drawSiteMaps } from "./site-maps.mjs";
|
|
31
39
|
import {
|
|
32
40
|
collectContentPages,
|
|
33
41
|
collectHomepages,
|
|
@@ -97,7 +105,19 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
|
|
|
97
105
|
)
|
|
98
106
|
.map((page) => ({ source: page.file, markdown: page.body }));
|
|
99
107
|
const publicRecords = records.filter((record) => !isGmNote(record));
|
|
108
|
+
const generated = await generatedSectionsFor(db, publicRecords, contentBase, {
|
|
109
|
+
foreignIndex: gates.foreign.index,
|
|
110
|
+
config,
|
|
111
|
+
});
|
|
112
|
+
joinGeneratedSections(generated, sources);
|
|
100
113
|
const sqlTables = await prepareSqlTables(db, sources, { records: publicRecords });
|
|
114
|
+
attachGeneratedSections(sqlTables, generated);
|
|
115
|
+
const drawings = drawSiteMaps({
|
|
116
|
+
records,
|
|
117
|
+
foreignIndex: gates.foreign.index,
|
|
118
|
+
config,
|
|
119
|
+
base,
|
|
120
|
+
}).maps;
|
|
101
121
|
const rendered = renderPages(pages, {
|
|
102
122
|
index: gates.index,
|
|
103
123
|
foreign: gates.foreign,
|
|
@@ -108,10 +128,11 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
|
|
|
108
128
|
config,
|
|
109
129
|
records,
|
|
110
130
|
homepages: homeEntries,
|
|
131
|
+
maps: drawings,
|
|
111
132
|
write: false,
|
|
112
133
|
capture: "graph",
|
|
113
134
|
});
|
|
114
|
-
return { records, pages, homeEntries, gates, db, rendered };
|
|
135
|
+
return { records, pages, homeEntries, gates, db, rendered, drawings };
|
|
115
136
|
} catch (error) {
|
|
116
137
|
await db.close();
|
|
117
138
|
throw error;
|
|
@@ -219,11 +240,16 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
|
|
|
219
240
|
},
|
|
220
241
|
);
|
|
221
242
|
}
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
243
|
+
const publicRecords = records.filter((record) => !isGmNote(record));
|
|
244
|
+
const sources = [{ source: absolute, markdown: body }];
|
|
245
|
+
const generated = await generatedSectionsFor(db, publicRecords, contentBase, {
|
|
246
|
+
only: absolute,
|
|
247
|
+
foreignIndex: snapshot.gates.foreign.index,
|
|
248
|
+
config,
|
|
249
|
+
});
|
|
250
|
+
joinGeneratedSections(generated, sources);
|
|
251
|
+
const sqlTables = await prepareSqlTables(db, sources, { records: publicRecords });
|
|
252
|
+
attachGeneratedSections(sqlTables, generated);
|
|
227
253
|
const allPages = snapshot.pages.map((item) =>
|
|
228
254
|
item.file === absolute ? page : item,
|
|
229
255
|
);
|
|
@@ -240,6 +266,12 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
|
|
|
240
266
|
foreign: snapshot.gates.foreign,
|
|
241
267
|
types: index.contentTypes,
|
|
242
268
|
}),
|
|
269
|
+
events: eventNoteIndex(records, {
|
|
270
|
+
contentPackage: index.contentPackage,
|
|
271
|
+
foreignIndex: snapshot.gates.foreign?.index,
|
|
272
|
+
dates: index.dateContext,
|
|
273
|
+
}),
|
|
274
|
+
drawings: snapshot.drawings,
|
|
243
275
|
});
|
|
244
276
|
for (const error of result.tableErrors)
|
|
245
277
|
findings.push({
|
|
@@ -284,29 +316,6 @@ export async function prepareSitePreview({ config = loadPackConfig() } = {}) {
|
|
|
284
316
|
});
|
|
285
317
|
const related = relatedPages(edges, entries).get(page.url);
|
|
286
318
|
if (related) result.data.related = related;
|
|
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
|
-
);
|
|
309
|
-
Object.assign(result.data, holdings.get(page.url));
|
|
310
319
|
return {
|
|
311
320
|
ok: true,
|
|
312
321
|
markdown: result.body,
|
package/engine/social-ties.mjs
CHANGED
|
@@ -127,7 +127,8 @@ export function checkSocialTies(note, { index } = {}) {
|
|
|
127
127
|
});
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
-
|
|
130
|
+
// A term that is not text is the inner-key check's finding.
|
|
131
|
+
if ((typeof term === "string" || term === null || term === undefined) && !TERMS.has(term))
|
|
131
132
|
findings.push({
|
|
132
133
|
...at(sourceKey),
|
|
133
134
|
message: `data.socialTies value ${JSON.stringify(term)} must be one of ${SOCIAL_TIES.map(({ term: value }) => value).join(", ")}`,
|