@heroiclands/package-build 22.15.1 → 22.16.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 +95 -0
- package/README.md +39 -678
- package/bin/{content-build.mjs → content-commands.mjs} +197 -118
- package/bin/package-build.mjs +303 -42
- package/bin/report.mjs +1 -1
- package/ci/ci-docker.mjs +6 -3
- package/ci/ci-steps.mjs +6 -4
- package/ci/content-format-reference.mjs +106 -0
- package/config.mjs +22 -2
- package/content-config.mjs +74 -134
- package/docs/api.md +294 -234
- package/docs/authoring/assets.md +31 -0
- package/docs/authoring/dates-and-calendars.md +33 -0
- package/docs/authoring/first-note.md +48 -0
- package/docs/authoring/frontmatter.md +40 -0
- package/docs/authoring/links-and-markup.md +137 -0
- package/docs/commands.md +631 -162
- package/docs/configuration.md +176 -140
- package/docs/content-format.md +50 -4229
- package/docs/design.md +32 -0
- package/docs/diagnostics.md +29 -22
- package/docs/getting-started.md +21 -877
- package/docs/guides/book.md +153 -0
- package/docs/guides/e2e.md +143 -0
- package/docs/guides/icons.md +126 -0
- package/docs/guides/localization.md +120 -0
- package/docs/guides/module-project.md +915 -0
- package/docs/guides/packs.md +167 -0
- package/docs/guides/reference-pages.md +148 -0
- package/docs/guides/release-and-deploy.md +114 -0
- package/docs/guides/site.md +152 -0
- package/docs/guides/system-project.md +45 -0
- package/docs/index.md +16 -0
- package/docs/project-setup.md +54 -35
- package/docs/reference/format-details.md +4420 -0
- package/docs/reference/note-types.md +531 -0
- package/engine/address-render.mjs +26 -0
- package/engine/address.mjs +3 -20
- package/engine/anchors.mjs +15 -6
- package/engine/asset-bindings.mjs +6 -2
- package/engine/base-compiler.mjs +47 -11
- package/engine/being-measurements.mjs +70 -0
- package/engine/calendar-artifacts.mjs +1 -1
- package/engine/calendar-choice.mjs +32 -0
- package/engine/calendar-human.mjs +71 -0
- package/engine/calendar-notes.mjs +41 -3
- package/engine/calendars.mjs +8 -1
- package/engine/changelog-lint.mjs +2 -2
- package/engine/code-fences.mjs +23 -97
- package/engine/compile-corpus.mjs +6 -7
- package/engine/content-admonitions.mjs +114 -0
- package/engine/content-captions.mjs +181 -0
- package/engine/content-embeds.mjs +2 -2
- package/engine/content-footnotes.mjs +34 -0
- package/engine/content-format-check.mjs +2 -2
- package/engine/content-format.mjs +127 -73
- package/engine/content-format.yaml +745 -0
- package/engine/content-html.mjs +1 -1
- package/engine/content-icons.mjs +45 -92
- package/engine/content-images.mjs +22 -74
- package/engine/content-index.mjs +12 -3
- package/engine/content-links.mjs +21 -26
- package/engine/content-lint.mjs +1 -5
- package/engine/content-secrets.mjs +3 -1
- package/engine/content-tables.mjs +47 -1257
- package/engine/culture-choice.mjs +32 -0
- package/engine/date-conversion.mjs +47 -0
- package/engine/declaration-check.mjs +87 -0
- package/engine/docs-checks.mjs +140 -0
- package/engine/documentation-links.mjs +31 -0
- package/engine/exported-scene.mjs +1 -1
- package/engine/extension-attributes.mjs +74 -0
- package/engine/field-reference.mjs +1 -1
- package/engine/field-spec.mjs +4 -0
- package/engine/foreign-catalog.mjs +5 -5
- package/engine/format-generated.mjs +15 -0
- package/engine/foundry-entries.mjs +3 -1
- package/engine/frontmatter-lint.mjs +5 -1
- package/engine/frontmatter.mjs +10 -0
- package/engine/generate.mjs +2 -2
- package/engine/helpers.mjs +71 -117
- package/engine/homepage.mjs +2 -3
- package/engine/index-records.mjs +1 -0
- package/engine/index.mjs +18 -3
- package/engine/infobox-registry.mjs +3 -1
- package/engine/infobox.mjs +38 -20
- package/engine/item-compiler.mjs +10 -8
- package/engine/item-docs.mjs +7 -6
- package/engine/journals.mjs +51 -10
- package/engine/macros.mjs +1 -1
- package/engine/map-build.mjs +1 -1
- package/engine/map-dot.mjs +1 -1
- package/engine/markdown-expressions.mjs +117 -0
- package/engine/metadata-index.mjs +7 -3
- package/engine/note-addresses.mjs +1 -0
- package/engine/note-dates.mjs +283 -61
- package/engine/note-package.mjs +5 -25
- package/engine/note-state.mjs +6 -2
- package/engine/note-vocabulary.mjs +74 -19
- package/engine/number-words.mjs +75 -0
- package/engine/office-holders.mjs +188 -0
- package/engine/pack-config.mjs +2 -15
- package/engine/pack-router.mjs +24 -5
- package/engine/packages.mjs +3 -2
- package/engine/pathnames.mjs +21 -24
- package/engine/pdf-build.mjs +217 -55
- package/engine/pdf-fonts.mjs +2 -2
- package/engine/pdf-images.mjs +82 -0
- package/engine/pdf-render.mjs +174 -22
- package/engine/populations.mjs +3 -2
- package/engine/project-init.mjs +499 -0
- package/engine/prose-config.mjs +1 -1
- package/engine/prose-lint.mjs +7 -6
- package/engine/readability-lint.mjs +86 -0
- package/engine/reckoning-markers.mjs +205 -93
- package/engine/scenes.mjs +5 -2
- package/engine/schema-check.mjs +3 -3
- package/engine/site-build.mjs +87 -37
- package/engine/site-config.mjs +8 -8
- package/engine/site-index.mjs +4 -0
- package/engine/site-maps.mjs +1 -1
- package/engine/site-preview.mjs +19 -8
- package/engine/social-ties.mjs +5 -1
- package/engine/sql-tables.mjs +92 -22
- package/engine/web-wikilinks.mjs +15 -31
- package/engine/wikilink-syntax.mjs +4 -1
- package/engine/wikilinks.mjs +12 -14
- package/hm3/item-fields.mjs +1 -1
- package/hm3/items.mjs +12 -45
- package/index.mjs +2 -14
- package/manifest.mjs +31 -4
- package/markdownlint-config.mjs +1 -1
- package/package.json +32 -11
- package/prettier-config.mjs +1 -1
- package/sohl/being-info.mjs +3 -145
- package/sohl/index.mjs +1 -1
- package/sohl/infobox.mjs +2 -1
- package/sohl/item-builders.mjs +3 -27
- package/sohl/item-fields.mjs +61 -22
- package/sohl/kb-passes.mjs +2 -2
- package/sohl/note-schemas.mjs +0 -8
- package/stage.mjs +19 -0
- package/types/config.d.mts +12 -3
- package/types/content-config.d.mts +15 -81
- package/types/engine/address-render.d.mts +13 -0
- package/types/engine/address.d.mts +2 -11
- package/types/engine/anchors.d.mts +3 -4
- package/types/engine/asset-bindings.d.mts +9 -2
- package/types/engine/base-compiler.d.mts +1 -2
- package/types/engine/being-measurements.d.mts +16 -0
- package/types/engine/calendar-choice.d.mts +14 -0
- package/types/engine/calendar-human.d.mts +4 -0
- package/types/engine/calendar-notes.d.mts +2 -0
- package/types/engine/calendars.d.mts +5 -3
- package/types/engine/code-fences.d.mts +6 -72
- package/types/engine/compile-corpus.d.mts +1 -3
- package/types/engine/content-admonitions.d.mts +24 -0
- package/types/engine/content-captions.d.mts +45 -0
- package/types/engine/content-footnotes.d.mts +10 -0
- package/types/engine/content-format-check.d.mts +17 -4
- package/types/engine/content-format.d.mts +24 -11
- package/types/engine/content-icons.d.mts +14 -23
- package/types/engine/content-images.d.mts +2 -2
- package/types/engine/content-index.d.mts +9 -2
- package/types/engine/content-links.d.mts +3 -5
- package/types/engine/content-tables.d.mts +11 -140
- package/types/engine/culture-choice.d.mts +14 -0
- package/types/engine/date-conversion.d.mts +4 -0
- package/types/engine/declaration-check.d.mts +24 -0
- package/types/engine/docs-checks.d.mts +28 -0
- package/types/engine/documentation-links.d.mts +7 -0
- package/types/engine/exported-scene.d.mts +6 -2
- package/types/engine/extension-attributes.d.mts +14 -0
- package/types/engine/field-spec.d.mts +9 -0
- package/types/engine/format-generated.d.mts +7 -0
- package/types/engine/frontmatter.d.mts +14 -4
- package/types/engine/generate.d.mts +1 -1
- package/types/engine/helpers.d.mts +37 -92
- package/types/engine/homepage.d.mts +2 -3
- package/types/engine/index.d.mts +5 -0
- package/types/engine/infobox-registry.d.mts +3 -1
- package/types/engine/infobox.d.mts +17 -6
- package/types/engine/item-compiler.d.mts +4 -4
- package/types/engine/item-docs.d.mts +4 -3
- package/types/engine/journals.d.mts +10 -7
- package/types/engine/map-graphviz.d.mts +1 -1
- package/types/engine/markdown-expressions.d.mts +17 -0
- package/types/engine/note-dates.d.mts +47 -1
- package/types/engine/note-package.d.mts +3 -19
- package/types/engine/note-state.d.mts +2 -1
- package/types/engine/note-vocabulary.d.mts +9 -0
- package/types/engine/number-words.d.mts +4 -0
- package/types/engine/office-holders.d.mts +14 -0
- package/types/engine/pack-config.d.mts +1 -1
- package/types/engine/packages.d.mts +3 -2
- package/types/engine/pdf-images.d.mts +38 -0
- package/types/engine/populations.d.mts +2 -2
- package/types/engine/project-init.d.mts +17 -0
- package/types/engine/prose-config.d.mts +1 -1
- package/types/engine/prose-lint.d.mts +3 -2
- package/types/engine/readability-lint.d.mts +22 -0
- package/types/engine/reckoning-markers.d.mts +4 -2
- package/types/engine/schema-check.d.mts +3 -3
- package/types/engine/site-build.d.mts +38 -28
- package/types/engine/site-config.d.mts +2 -2
- package/types/engine/site-index.d.mts +4 -0
- package/types/engine/site-preview.d.mts +1 -1
- package/types/engine/social-ties.d.mts +8 -2
- package/types/engine/sql-tables.d.mts +13 -2
- package/types/engine/web-wikilinks.d.mts +2 -1
- package/types/engine/wikilinks.d.mts +7 -1
- package/types/hm3/items.d.mts +5 -6
- package/types/labels.d.mts +40 -0
- package/types/manifest.d.mts +3 -3
- package/types/sohl/actors.d.mts +2 -2
- package/types/sohl/being-info.d.mts +3 -43
- package/types/sohl/index.d.mts +1 -1
- package/types/sohl/infobox.d.mts +5 -2
- package/types/sohl/item-fields.d.mts +4 -13
- package/types/stage.d.mts +9 -0
- package/CONTENT.md +0 -3239
package/engine/journals.mjs
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
* documentation entry reuses its document's folder verbatim.
|
|
43
43
|
*
|
|
44
44
|
* Not a standalone script — exports the `Journals` compiler class, imported
|
|
45
|
-
* and driven by `packages/
|
|
45
|
+
* and driven by `packages/package-build/engine/generate.mjs` (via `npm run build:compiledb`).
|
|
46
46
|
*
|
|
47
47
|
* The walk itself — filtering by type, expanding tables, converting
|
|
48
48
|
* wikilinks, writing the JSON and counting errors — belongs to {@link sohl.utils.packs.BasePackCompiler}; this module
|
|
@@ -57,16 +57,20 @@ import {
|
|
|
57
57
|
resolveName,
|
|
58
58
|
defaultStats,
|
|
59
59
|
renderFoundryMarkdown,
|
|
60
|
+
md,
|
|
60
61
|
folderField,
|
|
61
62
|
} from "./helpers.mjs";
|
|
62
63
|
import { BasePackCompiler } from "./base-compiler.mjs";
|
|
63
64
|
import { anchorPageId, resolveReference } from "./wikilinks.mjs";
|
|
64
65
|
import { infoboxesToHtml, linkToUuid } from "./infobox-render.mjs";
|
|
65
66
|
import { noteInfoboxes } from "./infobox-registry.mjs";
|
|
67
|
+
import { reckoningContext } from "./reckoning-markers.mjs";
|
|
66
68
|
import { hasDocEntry, itemDocEntryId } from "./item-docs.mjs";
|
|
67
69
|
import { JOURNAL_TYPES } from "./ids.mjs";
|
|
68
70
|
import { journalHasContent } from "./note-state.mjs";
|
|
69
71
|
import { draftNoticeFor } from "./draft-notice.mjs";
|
|
72
|
+
import { scanCaptions } from "./content-captions.mjs";
|
|
73
|
+
import { separateFootnotes } from "./content-footnotes.mjs";
|
|
70
74
|
|
|
71
75
|
/**
|
|
72
76
|
* Splits a markdown body into pages by top-level H1 headings. Fenced
|
|
@@ -74,7 +78,7 @@ import { draftNoticeFor } from "./draft-notice.mjs";
|
|
|
74
78
|
* a split. Content before the first H1 (if non-empty) becomes a leading
|
|
75
79
|
* page. Each H1 yields a page whose name is the heading
|
|
76
80
|
* text (with any `{#anchor-id}` suffix stripped out and surfaced as
|
|
77
|
-
* `
|
|
81
|
+
* `anchorSlug`).
|
|
78
82
|
*
|
|
79
83
|
* `leadName` names that leading page. A journal note's is "Introduction",
|
|
80
84
|
* because it introduces the pages that follow. An item doc's is the item — a
|
|
@@ -82,10 +86,21 @@ import { draftNoticeFor } from "./draft-notice.mjs";
|
|
|
82
86
|
* calling that page "Introduction" would label the description as a preamble to
|
|
83
87
|
* nothing.
|
|
84
88
|
*
|
|
85
|
-
*
|
|
89
|
+
* @param {string} body - Markdown body to split.
|
|
90
|
+
* @param {string} [leadName] - Name of the page before the first heading.
|
|
91
|
+
* @returns {Array<{name: string, anchorSlug: string|null, level: number,
|
|
92
|
+
* markdown: string}>} Pages in document order.
|
|
86
93
|
*/
|
|
87
94
|
export function splitPages(body, leadName = "Introduction") {
|
|
88
|
-
const
|
|
95
|
+
const { markdown, definitions } = separateFootnotes(body);
|
|
96
|
+
const lines = markdown.split("\n");
|
|
97
|
+
const captions = scanCaptions(markdown).captions;
|
|
98
|
+
const captionStarts = new Map(captions.map((caption) => [caption.line - 1, caption]));
|
|
99
|
+
const captionedHeadings = new Set(
|
|
100
|
+
captions
|
|
101
|
+
.filter((caption) => /^\s*#{1,6}\s/.test(lines[caption.blockStart] ?? ""))
|
|
102
|
+
.map((caption) => caption.blockStart),
|
|
103
|
+
);
|
|
89
104
|
const pages = [];
|
|
90
105
|
const beforeFirstH1 = [];
|
|
91
106
|
let current = null;
|
|
@@ -103,7 +118,7 @@ export function splitPages(body, leadName = "Introduction") {
|
|
|
103
118
|
current = null;
|
|
104
119
|
};
|
|
105
120
|
|
|
106
|
-
for (const line of lines) {
|
|
121
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
107
122
|
if (line.trim().startsWith("```")) {
|
|
108
123
|
inCodeBlock = !inCodeBlock;
|
|
109
124
|
}
|
|
@@ -118,7 +133,18 @@ export function splitPages(body, leadName = "Introduction") {
|
|
|
118
133
|
const rawHeading = headingMatch?.[2]?.trim();
|
|
119
134
|
const anchorMatch = rawHeading?.match(/^(.*?)\s*\{#([^}]+)\}\s*$/);
|
|
120
135
|
const startsPage = headingMatch && (headingMatch[1].length === 1 || anchorMatch);
|
|
121
|
-
|
|
136
|
+
const caption = !inCodeBlock && !inSecret ? captionStarts.get(lineIndex) : null;
|
|
137
|
+
if (caption) {
|
|
138
|
+
closeCurrent();
|
|
139
|
+
current = {
|
|
140
|
+
name: caption.label,
|
|
141
|
+
anchorSlug: caption.id,
|
|
142
|
+
level: 1,
|
|
143
|
+
lines: [line],
|
|
144
|
+
};
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (startsPage && !captionedHeadings.has(lineIndex)) {
|
|
122
148
|
closeCurrent();
|
|
123
149
|
current = {
|
|
124
150
|
name: (anchorMatch ? anchorMatch[1] : rawHeading).trim(),
|
|
@@ -147,6 +173,9 @@ export function splitPages(body, leadName = "Introduction") {
|
|
|
147
173
|
});
|
|
148
174
|
}
|
|
149
175
|
|
|
176
|
+
if (definitions) {
|
|
177
|
+
for (const page of pages) page.markdown += `\n\n${definitions}`;
|
|
178
|
+
}
|
|
150
179
|
return pages;
|
|
151
180
|
}
|
|
152
181
|
|
|
@@ -214,7 +243,7 @@ export function assertUniqueAnchors(rawPages, noteName) {
|
|
|
214
243
|
* An anchored page takes the id its inbound links compute from the note id and
|
|
215
244
|
* the slug, so link and page agree without shared state. Every other page is
|
|
216
245
|
* keyed by its **name**, which is what lets the items pass address an item
|
|
217
|
-
* doc's
|
|
246
|
+
* doc's selected page without having compiled it (see
|
|
218
247
|
* {@link sohl.utils.packs.itemDocPointer}).
|
|
219
248
|
*
|
|
220
249
|
* **It takes no index**. Keying a page by position *and* name,
|
|
@@ -250,13 +279,21 @@ export function journalPageId(entryId, page) {
|
|
|
250
279
|
* documents, in order.
|
|
251
280
|
* @throws {Error} When the note has no content at all, or repeats an anchor.
|
|
252
281
|
*/
|
|
253
|
-
export function buildPages(rawPages, entryId, noteName) {
|
|
282
|
+
export function buildPages(rawPages, entryId, noteName, captions) {
|
|
254
283
|
if (rawPages.length === 0) {
|
|
255
284
|
throw new Error(
|
|
256
285
|
`note "${noteName}" has no Introduction content and no H1 headings — nothing to compile`,
|
|
257
286
|
);
|
|
258
287
|
}
|
|
259
288
|
assertUniquePages(rawPages, noteName);
|
|
289
|
+
const footnoteNumbers = new Map();
|
|
290
|
+
for (const page of rawPages) {
|
|
291
|
+
const env = {};
|
|
292
|
+
md.parse(page.markdown ?? "", env);
|
|
293
|
+
for (const { label } of env.footnotes?.list ?? []) {
|
|
294
|
+
if (!footnoteNumbers.has(label)) footnoteNumbers.set(label, footnoteNumbers.size + 1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
260
297
|
return rawPages.map((page) => {
|
|
261
298
|
const pageId = journalPageId(entryId, page);
|
|
262
299
|
return {
|
|
@@ -266,7 +303,10 @@ export function buildPages(rawPages, entryId, noteName) {
|
|
|
266
303
|
title: { show: true, level: page.level ?? 1 },
|
|
267
304
|
text: {
|
|
268
305
|
format: 1,
|
|
269
|
-
content:
|
|
306
|
+
content:
|
|
307
|
+
page.markdown ?
|
|
308
|
+
renderFoundryMarkdown(page.markdown, captions, footnoteNumbers, pageId)
|
|
309
|
+
: "",
|
|
270
310
|
},
|
|
271
311
|
_key: `!journal.pages!${entryId}.${pageId}`,
|
|
272
312
|
};
|
|
@@ -321,7 +361,7 @@ export function buildJournalEntry({
|
|
|
321
361
|
notice = "",
|
|
322
362
|
}) {
|
|
323
363
|
const rawPages = splitPages(markdown, leadName);
|
|
324
|
-
const pages = buildPages(rawPages, id, name);
|
|
364
|
+
const pages = buildPages(rawPages, id, name, scanCaptions(markdown).captions);
|
|
325
365
|
if (infobox.trim() && pages.length) {
|
|
326
366
|
pages[0].text.content = `${infobox}\n${pages[0].text.content}`;
|
|
327
367
|
}
|
|
@@ -475,6 +515,7 @@ export class Journals extends BasePackCompiler {
|
|
|
475
515
|
// This compile's own router, so the panel's `available` is decided
|
|
476
516
|
// by the pack list this build is being driven by.
|
|
477
517
|
router: this.router,
|
|
518
|
+
dates: reckoningContext(this.linkIndex),
|
|
478
519
|
});
|
|
479
520
|
|
|
480
521
|
return buildJournalEntry({
|
package/engine/macros.mjs
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
* Nothing here evaluates, compiles, or revives anything.
|
|
43
43
|
*
|
|
44
44
|
* Not a standalone script — exports the `Macros` compiler class, imported and
|
|
45
|
-
* driven by `packages/
|
|
45
|
+
* driven by `packages/package-build/engine/generate.mjs` (via `npm run build:compiledb`).
|
|
46
46
|
*
|
|
47
47
|
* The walk itself — filtering by type, expanding tables, converting
|
|
48
48
|
* wikilinks, writing the JSON and counting errors — belongs to {@link sohl.utils.packs.BasePackCompiler}; this module
|
package/engine/map-build.mjs
CHANGED
package/engine/map-dot.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* The drawings of `
|
|
15
|
+
* The drawings of `package-build map`, as DOT.
|
|
16
16
|
*
|
|
17
17
|
* Every drawing is emitted as DOT text and kept beside its rendering, so a
|
|
18
18
|
* picture can be re-rendered, diffed or read without the content index.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
|
2
|
+
|
|
3
|
+
import Handlebars from "handlebars";
|
|
4
|
+
|
|
5
|
+
import { isAddressTuple, renderAddress } from "./address.mjs";
|
|
6
|
+
import { matchAllOutsideCode, replaceOutsideCode } from "./code-fences.mjs";
|
|
7
|
+
import { formatDateInCalendar, parseNoteDate } from "./note-dates.mjs";
|
|
8
|
+
import { numberWords, numberDigits } from "./number-words.mjs";
|
|
9
|
+
|
|
10
|
+
// Hugo shortcodes begin `{{<` or `{{%` and stay in the source for Hugo.
|
|
11
|
+
const EXPRESSION = /(?<![\\{])\{\{(?![{<%])([^{}\n]+)\}\}(?!\})/g;
|
|
12
|
+
|
|
13
|
+
/** Find SQL helper calls, including calls nested inside Boolean expressions. */
|
|
14
|
+
export function sqlQueriesInMarkdown(body, fm = {}) {
|
|
15
|
+
const queries = [];
|
|
16
|
+
const queryValue = (parameter) => {
|
|
17
|
+
if (parameter?.type === "StringLiteral") return parameter.value;
|
|
18
|
+
if (parameter?.type !== "PathExpression") return null;
|
|
19
|
+
return parameter.parts.reduce((value, part) => value?.[part], fm);
|
|
20
|
+
};
|
|
21
|
+
const visit = (node) => {
|
|
22
|
+
if (!node || typeof node !== "object") return;
|
|
23
|
+
if (node.path?.original === "sql") {
|
|
24
|
+
const query = queryValue(node.params?.[0]);
|
|
25
|
+
if (typeof query === "string") queries.push(query);
|
|
26
|
+
}
|
|
27
|
+
for (const parameter of node.params ?? []) visit(parameter);
|
|
28
|
+
};
|
|
29
|
+
for (const match of matchAllOutsideCode(body, EXPRESSION)) {
|
|
30
|
+
try {
|
|
31
|
+
for (const node of Handlebars.parse(match[0]).body) visit(node);
|
|
32
|
+
} catch {
|
|
33
|
+
// The render pass reports the malformed expression at its source position.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return queries;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Expand scalar frontmatter references and registered functions in Markdown prose.
|
|
41
|
+
* @param {string} body
|
|
42
|
+
* @param {{fm?: object, dates?: object, sqlResults?: Map<string, object>, file?: string, bodyLine?: number}} [options]
|
|
43
|
+
*/
|
|
44
|
+
export function renderMarkdownExpressions(
|
|
45
|
+
body,
|
|
46
|
+
{ fm, dates, sqlResults, file, bodyLine = 1 } = {},
|
|
47
|
+
) {
|
|
48
|
+
const findings = [];
|
|
49
|
+
const engine = Handlebars.create();
|
|
50
|
+
const numeric = (name, left, right) => {
|
|
51
|
+
if (
|
|
52
|
+
!["number", "bigint"].includes(typeof left) ||
|
|
53
|
+
!["number", "bigint"].includes(typeof right) ||
|
|
54
|
+
(typeof left === "number" && !Number.isFinite(left)) ||
|
|
55
|
+
(typeof right === "number" && !Number.isFinite(right))
|
|
56
|
+
)
|
|
57
|
+
throw new TypeError(`${name} needs two finite numbers`);
|
|
58
|
+
};
|
|
59
|
+
for (const [name, compare] of Object.entries({
|
|
60
|
+
gt: (a, b) => a > b,
|
|
61
|
+
gte: (a, b) => a >= b,
|
|
62
|
+
lt: (a, b) => a < b,
|
|
63
|
+
lte: (a, b) => a <= b,
|
|
64
|
+
}))
|
|
65
|
+
engine.registerHelper(name, (left, right) => {
|
|
66
|
+
numeric(name, left, right);
|
|
67
|
+
return compare(left, right);
|
|
68
|
+
});
|
|
69
|
+
engine.registerHelper("eq", (left, right) => left === right);
|
|
70
|
+
engine.registerHelper("not", (value) => !value);
|
|
71
|
+
engine.registerHelper("and", (...args) => args.slice(0, -1).every(Boolean));
|
|
72
|
+
engine.registerHelper("or", (...args) => args.slice(0, -1).some(Boolean));
|
|
73
|
+
engine.registerHelper("words", (value) => numberWords(value));
|
|
74
|
+
engine.registerHelper("digits", (value) => numberDigits(value));
|
|
75
|
+
engine.registerHelper("sql", (query) => {
|
|
76
|
+
if (typeof query !== "string" || !query.trim())
|
|
77
|
+
throw new TypeError("sql needs a nonempty query string");
|
|
78
|
+
const result = sqlResults?.get(query);
|
|
79
|
+
if (!result) throw new RangeError(`SQL expression has no prepared result: ${query}`);
|
|
80
|
+
if (result.error) throw new RangeError(`SQL expression failed: ${result.error}`);
|
|
81
|
+
return result.value;
|
|
82
|
+
});
|
|
83
|
+
engine.registerHelper("dateformat", (calendar, authored) => {
|
|
84
|
+
if (
|
|
85
|
+
!(typeof calendar === "string" || isAddressTuple(calendar)) ||
|
|
86
|
+
!["string", "number"].includes(typeof authored)
|
|
87
|
+
)
|
|
88
|
+
throw new TypeError("dateformat needs a calendar Address and a date");
|
|
89
|
+
const parsed = parseNoteDate(authored, { ...dates, allowUnknown: false });
|
|
90
|
+
const error = parsed.findings.find((item) => item.severity === "error");
|
|
91
|
+
if (error || !parsed.date) throw new RangeError(error?.message ?? "date does not resolve");
|
|
92
|
+
const printable = formatDateInCalendar(
|
|
93
|
+
parsed.date,
|
|
94
|
+
isAddressTuple(calendar) ? renderAddress(calendar) : calendar,
|
|
95
|
+
dates,
|
|
96
|
+
);
|
|
97
|
+
if (!printable) throw new RangeError(`date cannot be printed in calendar ${calendar}`);
|
|
98
|
+
return printable.prose ?? printable.text;
|
|
99
|
+
});
|
|
100
|
+
const markdown = replaceOutsideCode(body, EXPRESSION, (source, _inside, offset) => {
|
|
101
|
+
try {
|
|
102
|
+
return engine.compile(source, { strict: true })(fm ?? {});
|
|
103
|
+
} catch (error) {
|
|
104
|
+
const before = String(body).slice(0, offset);
|
|
105
|
+
const lines = before.split("\n");
|
|
106
|
+
findings.push({
|
|
107
|
+
...(file ? { file } : {}),
|
|
108
|
+
line: bodyLine + lines.length - 1,
|
|
109
|
+
column: lines.at(-1).length + 1,
|
|
110
|
+
severity: "error",
|
|
111
|
+
message: `Markdown expression ${source} failed: ${error.message}`,
|
|
112
|
+
});
|
|
113
|
+
return source;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return { markdown, findings };
|
|
117
|
+
}
|
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
resolvePackageUrl,
|
|
62
62
|
} from "./content-address.mjs";
|
|
63
63
|
import { HOMEPAGE_TYPE } from "./homepage.mjs";
|
|
64
|
+
import { isGmNote } from "./note-vocabulary.mjs";
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
67
|
* Written once a fetch completes, so a half-finished cache is never used.
|
|
@@ -236,7 +237,7 @@ export function cachedMetadataIndexes(config) {
|
|
|
236
237
|
if (!cached.length) {
|
|
237
238
|
throw new Error(
|
|
238
239
|
`${rel.id} is a declared dependency but its content index has ` +
|
|
239
|
-
`not been fetched. Run \`
|
|
240
|
+
`not been fetched. Run \`package-build deps fetch\` first.`,
|
|
240
241
|
);
|
|
241
242
|
}
|
|
242
243
|
files.push({ id: rel.id, file: newestIndex(cached) });
|
|
@@ -401,7 +402,7 @@ export function loadForeignIndexes(config, localPackages, bases = PACKAGE_BASE)
|
|
|
401
402
|
// must tolerate that rather than invent an href, exactly as it
|
|
402
403
|
// already tolerates an entry with no `uuid`.
|
|
403
404
|
url:
|
|
404
|
-
!web || !record.address.slug ? undefined
|
|
405
|
+
!web || !record.address.slug || isGmNote(record) ? undefined
|
|
405
406
|
// A package's homepage is its root: the note is the
|
|
406
407
|
// mount's `_index.md`, so `[[thalorna-homepage-root]]`
|
|
407
408
|
// lands on `/thalorna/`, not on a page below it.
|
|
@@ -411,6 +412,7 @@ export function loadForeignIndexes(config, localPackages, bases = PACKAGE_BASE)
|
|
|
411
412
|
doc: record.documentation ?? undefined,
|
|
412
413
|
anchors: foundry?.anchors,
|
|
413
414
|
type: parts.type,
|
|
415
|
+
gm: isGmNote(record),
|
|
414
416
|
// What the note *is*, not only where it lives. A consumer
|
|
415
417
|
// grouping a reference by the family its target declares —
|
|
416
418
|
// an infobox sorting a being's skills — has no other way to
|
|
@@ -426,11 +428,13 @@ export function loadForeignIndexes(config, localPackages, bases = PACKAGE_BASE)
|
|
|
426
428
|
// checks a border across the package boundary from both ends,
|
|
427
429
|
// a consumer's map draws the dependency's places beside its
|
|
428
430
|
// own, and a consumer's pages list what a dependency's
|
|
429
|
-
// affiliation holds and what lies within its regions.
|
|
431
|
+
// affiliation holds and what lies within its regions. A
|
|
432
|
+
// population lets the same checks include dependency figures.
|
|
430
433
|
borders: record.data?.borders ?? undefined,
|
|
431
434
|
routes: record.data?.routes ?? undefined,
|
|
432
435
|
parents: record.data?.parents ?? undefined,
|
|
433
436
|
domains: record.data?.domains ?? undefined,
|
|
437
|
+
population: record.data?.population ?? undefined,
|
|
434
438
|
package: pkg,
|
|
435
439
|
});
|
|
436
440
|
const entry = index.get(key);
|