@heroiclands/package-build 23.13.0 → 24.0.1
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 +50 -0
- package/README.md +2 -1
- package/assets/preview/gitea.ini +12 -7
- package/assets/styles/content-base.css +150 -1
- package/bin/content-commands.mjs +356 -210
- package/bin/package-build.mjs +3 -2
- package/ci/check-banners.mjs +7 -7
- package/config.mjs +21 -6
- package/content-config.mjs +199 -80
- package/docs/api.md +93 -72
- package/docs/authoring/links-and-markup.md +316 -78
- package/docs/commands.md +248 -80
- package/docs/configuration.md +210 -102
- package/docs/content-format.md +2 -2
- package/docs/diagnostics.md +2 -2
- package/docs/getting-started.md +1 -1
- package/docs/guides/icons.md +1 -1
- package/docs/guides/module-project.md +5 -8
- package/docs/guides/release-and-deploy.md +2 -2
- package/docs/guides/site.md +155 -23
- package/docs/guides/system-project.md +1 -1
- package/docs/guides/writing-in-invented-scripts.md +1247 -0
- package/docs/index.md +1 -0
- package/docs/project-setup.md +28 -30
- package/docs/reference/format-details.md +589 -308
- package/docs/reference/note-types.md +40 -10
- package/docs/reference/theme-pages.md +127 -108
- package/docs/source-preview.md +15 -1
- package/engine/asset-shipping.mjs +22 -10
- package/engine/base-compiler.mjs +62 -104
- package/engine/changelog-lint.mjs +1 -1
- package/engine/content-address.mjs +1 -1
- package/engine/content-alerts.mjs +8 -4
- package/engine/content-blocks.mjs +26 -11
- package/engine/content-embeds.mjs +1 -1
- package/engine/content-expand.mjs +376 -0
- package/engine/content-figures.mjs +13 -6
- package/engine/content-format.yaml +8 -0
- package/engine/content-glyphs.mjs +213 -0
- package/engine/content-images.mjs +6 -7
- package/engine/content-links.mjs +3 -3
- package/engine/content-lint.mjs +1 -1
- package/engine/content-render.mjs +453 -0
- package/engine/content-scripts.mjs +326 -27
- package/engine/content-spans.mjs +4 -1
- package/engine/content-styles.mjs +91 -0
- package/engine/content-symbols.mjs +186 -0
- package/engine/content-verse.mjs +6 -18
- package/engine/core-report.mjs +169 -0
- package/engine/core-vocabulary.mjs +208 -0
- package/engine/dictionary-html.mjs +119 -0
- package/engine/field-reference.mjs +1 -1
- package/engine/frontmatter-lint.mjs +1 -1
- package/engine/heading-attributes.mjs +89 -0
- package/engine/helpers.mjs +61 -101
- package/engine/homepage.mjs +17 -29
- package/engine/html-minify.mjs +70 -0
- package/engine/index.mjs +8 -5
- package/engine/language-rows.mjs +8 -2
- package/engine/language-terms.mjs +115 -14
- package/engine/language-words.mjs +12 -0
- package/engine/logographic.mjs +255 -0
- package/engine/map-lexicon.mjs +221 -25
- package/engine/map-places.mjs +5 -4
- package/engine/markdown-expressions.mjs +0 -2
- package/engine/markdownlint-run.mjs +9 -3
- package/engine/markup-validation.mjs +21 -0
- package/engine/metre-declaration.mjs +134 -0
- package/engine/note-matter.mjs +2 -2
- package/engine/note-vocabulary.mjs +187 -3
- package/engine/pdf-build.mjs +68 -133
- package/engine/pdf-render.mjs +102 -44
- package/engine/post-fields.mjs +157 -0
- package/engine/preview-index.mjs +130 -0
- package/engine/preview-source.mjs +450 -0
- package/engine/project-init.mjs +5 -12
- package/engine/prose-config.mjs +3 -3
- package/engine/run-words.mjs +3 -2
- package/engine/script-emphasis-terms.mjs +38 -0
- package/engine/script-emphasis.mjs +295 -0
- package/engine/script-font-delivery.mjs +4 -4
- package/engine/site-build.mjs +265 -313
- package/engine/site-config.mjs +26 -238
- package/engine/site-files.mjs +175 -0
- package/engine/site-index.mjs +9 -4
- package/engine/site-preview.mjs +50 -523
- package/engine/site-reach.mjs +213 -0
- package/engine/site-render.mjs +668 -0
- package/engine/site-root.mjs +90 -39
- package/engine/site-search.mjs +161 -0
- package/engine/site-serve.mjs +274 -0
- package/engine/site-theme-banner.mjs +154 -0
- package/engine/site-theme-urls.mjs +125 -0
- package/engine/site-theme.mjs +939 -0
- package/engine/sql-tables.mjs +9 -6
- package/engine/text-maps.mjs +57 -10
- package/engine/typographer.mjs +248 -0
- package/engine/verse-metre.mjs +357 -0
- package/engine/wikilink-syntax.mjs +29 -47
- package/engine/wikilinks.mjs +758 -279
- package/engine/word-forms.mjs +39 -24
- package/engine/word-rows.mjs +12 -2
- package/engine/word-terms.mjs +29 -4
- package/manifest.mjs +3 -4
- package/package.json +10 -7
- package/{hugo-theme → site-theme}/data/banners.yaml +6 -10
- package/site-theme/layouts/base.hbs +31 -0
- package/site-theme/layouts/blog-list.hbs +30 -0
- package/site-theme/layouts/blog-post.hbs +40 -0
- package/site-theme/layouts/home.hbs +35 -0
- package/site-theme/layouts/list.hbs +20 -0
- package/site-theme/layouts/not-found.hbs +31 -0
- package/site-theme/layouts/page.hbs +7 -0
- package/site-theme/partials/copy-buttons.hbs +37 -0
- package/site-theme/partials/draft-notice.hbs +5 -0
- package/site-theme/partials/exturl.hbs +2 -0
- package/{hugo-theme/layouts/partials/footer.html → site-theme/partials/footer.hbs} +10 -7
- package/site-theme/partials/header.hbs +44 -0
- package/site-theme/partials/hero-banner.hbs +11 -0
- package/site-theme/partials/infobox-entry.hbs +2 -0
- package/site-theme/partials/infobox-section.hbs +31 -0
- package/site-theme/partials/infobox-value.hbs +2 -0
- package/site-theme/partials/infobox.hbs +18 -0
- package/site-theme/partials/list-members.hbs +9 -0
- package/site-theme/partials/nav-links.hbs +5 -0
- package/site-theme/partials/page-body.hbs +48 -0
- package/site-theme/partials/page-list.hbs +14 -0
- package/site-theme/partials/related.hbs +24 -0
- package/site-theme/partials/search-meta.hbs +6 -0
- package/site-theme/partials/search-names.hbs +2 -0
- package/site-theme/partials/search.hbs +52 -0
- package/site-theme/partials/tags.hbs +4 -0
- package/site-theme/partials/toc.hbs +7 -0
- package/{hugo-theme → site-theme}/static/css/style.css +14 -0
- package/stage.mjs +1 -2
- package/types/config.d.mts +8 -4
- package/types/content-config.d.mts +15 -15
- package/types/engine/asset-shipping.d.mts +5 -2
- package/types/engine/base-compiler.d.mts +20 -2
- package/types/engine/content-address.d.mts +1 -1
- package/types/engine/content-alerts.d.mts +8 -2
- package/types/engine/content-blocks.d.mts +11 -4
- package/types/engine/content-expand.d.mts +288 -0
- package/types/engine/content-figures.d.mts +11 -3
- package/types/engine/content-glyphs.d.mts +85 -0
- package/types/engine/content-images.d.mts +6 -7
- package/types/engine/content-links.d.mts +1 -1
- package/types/engine/content-render.d.mts +199 -0
- package/types/engine/content-scripts.d.mts +62 -0
- package/types/engine/content-spans.d.mts +1 -1
- package/types/engine/content-styles.d.mts +33 -0
- package/types/engine/content-symbols.d.mts +35 -0
- package/types/engine/core-report.d.mts +45 -0
- package/types/engine/core-vocabulary.d.mts +53 -0
- package/types/engine/dictionary-html.d.mts +8 -0
- package/types/engine/field-reference.d.mts +1 -1
- package/types/engine/heading-attributes.d.mts +20 -0
- package/types/engine/helpers.d.mts +3 -10
- package/types/engine/homepage.d.mts +11 -24
- package/types/engine/html-minify.d.mts +9 -0
- package/types/engine/index.d.mts +2 -1
- package/types/engine/language-terms.d.mts +24 -2
- package/types/engine/logographic.d.mts +45 -0
- package/types/engine/map-lexicon.d.mts +48 -8
- package/types/engine/map-places.d.mts +8 -0
- package/types/engine/markdown-expressions.d.mts +0 -2
- package/types/engine/metre-declaration.d.mts +39 -0
- package/types/engine/pdf-render.d.mts +6 -2
- package/types/engine/post-fields.d.mts +44 -0
- package/types/engine/preview-index.d.mts +72 -0
- package/types/engine/preview-source.d.mts +51 -0
- package/types/engine/script-emphasis-terms.d.mts +21 -0
- package/types/engine/script-emphasis.d.mts +84 -0
- package/types/engine/script-font-delivery.d.mts +4 -4
- package/types/engine/site-build.d.mts +71 -113
- package/types/engine/site-config.d.mts +14 -134
- package/types/engine/site-files.d.mts +73 -0
- package/types/engine/site-index.d.mts +11 -3
- package/types/engine/site-preview.d.mts +15 -19
- package/types/engine/site-reach.d.mts +76 -0
- package/types/engine/site-render.d.mts +173 -0
- package/types/engine/site-root.d.mts +14 -4
- package/types/engine/site-search.d.mts +54 -0
- package/types/engine/site-serve.d.mts +44 -0
- package/types/engine/site-theme-banner.d.mts +51 -0
- package/types/engine/site-theme-urls.d.mts +60 -0
- package/types/engine/site-theme.d.mts +127 -0
- package/types/engine/text-maps.d.mts +17 -3
- package/types/engine/typographer.d.mts +11 -0
- package/types/engine/verse-metre.d.mts +131 -0
- package/types/engine/wikilink-syntax.d.mts +30 -48
- package/types/engine/wikilinks.d.mts +330 -14
- package/types/engine/word-forms.d.mts +3 -2
- package/types/manifest.d.mts +3 -4
- package/types/stage.d.mts +1 -2
- package/types/vendor/markdownlint.d.mts +8 -0
- package/vendor/markdownlint.mjs +20388 -0
- package/engine/web-wikilinks.mjs +0 -594
- package/hugo-theme/layouts/404.html +0 -85
- package/hugo-theme/layouts/_default/baseof.html +0 -67
- package/hugo-theme/layouts/_default/list.html +0 -139
- package/hugo-theme/layouts/_default/single.html +0 -4
- package/hugo-theme/layouts/blog/list.html +0 -54
- package/hugo-theme/layouts/blog-post/single.html +0 -36
- package/hugo-theme/layouts/index.html +0 -75
- package/hugo-theme/layouts/partials/banner-url.html +0 -141
- package/hugo-theme/layouts/partials/cdn-url.html +0 -36
- package/hugo-theme/layouts/partials/dictionary-lists.html +0 -58
- package/hugo-theme/layouts/partials/draft-notice.html +0 -41
- package/hugo-theme/layouts/partials/draft-state.html +0 -26
- package/hugo-theme/layouts/partials/header.html +0 -35
- package/hugo-theme/layouts/partials/hero-banner.html +0 -85
- package/hugo-theme/layouts/partials/infobox/entry.html +0 -18
- package/hugo-theme/layouts/partials/infobox/items.html +0 -26
- package/hugo-theme/layouts/partials/infobox/section.html +0 -56
- package/hugo-theme/layouts/partials/infobox/value.html +0 -43
- package/hugo-theme/layouts/partials/infobox.html +0 -57
- package/hugo-theme/layouts/partials/page-body.html +0 -152
- package/hugo-theme/layouts/partials/related.html +0 -138
- package/hugo-theme/layouts/partials/search.html +0 -84
- package/hugo-theme/layouts/partials/site-url.html +0 -32
- package/hugo-theme/layouts/partials/tags.html +0 -30
- package/hugo-theme/layouts/partials/toc.html +0 -28
- package/hugo-theme/layouts/partials/warn-missing-banner.html +0 -3
- package/hugo-theme/layouts/shortcodes/cartouche.html +0 -64
- package/hugo-theme/layouts/shortcodes/exturl.html +0 -12
- package/hugo-theme/layouts/shortcodes/glyph.html +0 -43
- package/hugo-theme/layouts/shortcodes/pagelist.html +0 -63
- package/hugo-theme/theme.toml +0 -11
- package/types/engine/web-wikilinks.d.mts +0 -107
- /package/{hugo-theme → site-theme}/data/banner-kinds.yaml +0 -0
- /package/{hugo-theme → site-theme}/static/css/sohl-icons.css +0 -0
- /package/{hugo-theme → site-theme}/static/fonts/game-icons-kb.woff2 +0 -0
- /package/{hugo-theme → site-theme}/static/img/banners/takheperu-hieroglyphs.svg +0 -0
- /package/{hugo-theme → site-theme}/static/img/cartouches/takheperu.svg +0 -0
- /package/{hugo-theme → site-theme}/static/js/inline-spoilers.js +0 -0
package/engine/site-build.mjs
CHANGED
|
@@ -51,37 +51,30 @@
|
|
|
51
51
|
* @module
|
|
52
52
|
*/
|
|
53
53
|
|
|
54
|
-
import {
|
|
54
|
+
import { scanAlerts } from "./content-alerts.mjs";
|
|
55
55
|
import { positionOfYamlPath } from "./diagnostics.mjs";
|
|
56
|
-
import { decodeNoteAddresses, noteAddressContext
|
|
56
|
+
import { decodeNoteAddresses, noteAddressContext } from "./note-addresses.mjs";
|
|
57
57
|
import fs from "node:fs";
|
|
58
58
|
import path from "node:path";
|
|
59
59
|
import { createRequire } from "node:module";
|
|
60
|
-
import { readMatter
|
|
60
|
+
import { readMatter } from "./note-matter.mjs";
|
|
61
61
|
|
|
62
62
|
import { addressSlug } from "./content-address.mjs";
|
|
63
|
-
import {
|
|
64
|
-
import { renderMarkdownExpressions } from "./markdown-expressions.mjs";
|
|
63
|
+
import { expandContent } from "./content-expand.mjs";
|
|
65
64
|
import { eventNoteIndex } from "./event-fields.mjs";
|
|
66
65
|
import { expandContentTables } from "./content-tables.mjs";
|
|
67
|
-
import {
|
|
68
|
-
import {
|
|
69
|
-
import {
|
|
66
|
+
import { scanSpans } from "./content-spans.mjs";
|
|
67
|
+
import { runContext, scanRuns } from "./content-scripts.mjs";
|
|
68
|
+
import { scanBlocks } from "./content-blocks.mjs";
|
|
70
69
|
import { scanHeadingAttributes, withheldSections } from "./heading-attributes.mjs";
|
|
71
|
-
import {
|
|
70
|
+
import { scanFigures } from "./content-figures.mjs";
|
|
72
71
|
import { footnoteFindings } from "./content-footnotes.mjs";
|
|
73
|
-
import {
|
|
72
|
+
import { markupAnchorFindings } from "./anchors.mjs";
|
|
74
73
|
import { checkImages, renderImageFigures } from "./content-images.mjs";
|
|
75
74
|
import { isAddressTuple } from "./address.mjs";
|
|
76
75
|
import { assetPathnameKey, pathnameProblem, resolvePathname } from "./pathnames.mjs";
|
|
77
|
-
import {
|
|
78
|
-
|
|
79
|
-
figureIndexKeys,
|
|
80
|
-
resolveCrossNoteFigures,
|
|
81
|
-
resolveInfoboxRef,
|
|
82
|
-
wikiContext,
|
|
83
|
-
} from "./site-index.mjs";
|
|
84
|
-
import { frontmatterWikilinks, resolveWebWikilinks } from "./web-wikilinks.mjs";
|
|
76
|
+
import { buildSiteIndex, figureIndexKeys, resolveInfoboxRef } from "./site-index.mjs";
|
|
77
|
+
import { frontmatterWikilinks } from "./wikilinks.mjs";
|
|
85
78
|
import { loadForeignIndexes, noContentIndexPackages } from "./metadata-index.mjs";
|
|
86
79
|
import { noteInfoboxes } from "./infobox-registry.mjs";
|
|
87
80
|
import { formatUnaddressableFinding, unaddressableForeignPackages } from "./metadata-index.mjs";
|
|
@@ -100,21 +93,15 @@ import { isNoteRecord, noteFile } from "./index-records.mjs";
|
|
|
100
93
|
// The one statement of what an empty body means, shared with the index.
|
|
101
94
|
import { isStubNote } from "./note-state.mjs";
|
|
102
95
|
import { isGmNote } from "./note-vocabulary.mjs";
|
|
103
|
-
import { ART_SLOTS, artPathname, assetAddressIndex
|
|
96
|
+
import { ART_SLOTS, artPathname, assetAddressIndex } from "./art-fields.mjs";
|
|
104
97
|
import { embedRole } from "./content-embeds.mjs";
|
|
105
|
-
import {
|
|
106
|
-
|
|
107
|
-
checkHomepageCount,
|
|
108
|
-
homepageFrontmatter,
|
|
109
|
-
homepageTitle,
|
|
110
|
-
isHomepage,
|
|
111
|
-
} from "./homepage.mjs";
|
|
112
|
-
import { publishesContentPages } from "../content-config.mjs";
|
|
113
|
-
import { HUGO_CONTENT } from "./site-config.mjs";
|
|
98
|
+
import { checkHomepageCount, homepageFrontmatter, homepageTitle, isHomepage } from "./homepage.mjs";
|
|
99
|
+
import { publishesContentPages, siteBase } from "../content-config.mjs";
|
|
114
100
|
import { seeAlsoPages } from "./see-also.mjs";
|
|
115
101
|
import { homepageLinkTargets, relatedPages } from "./related-pages.mjs";
|
|
102
|
+
import { GENERATED_MARK } from "./site-reach.mjs";
|
|
116
103
|
import { drawSiteMaps, inlineDrawingFigure } from "./site-maps.mjs";
|
|
117
|
-
import { generatedDrawing
|
|
104
|
+
import { generatedDrawing } from "./generated-sections.mjs";
|
|
118
105
|
import { IMAGE_PATTERN } from "./content-images.mjs";
|
|
119
106
|
|
|
120
107
|
const require = createRequire(import.meta.url);
|
|
@@ -196,9 +183,10 @@ function readNote(file, ctx) {
|
|
|
196
183
|
*
|
|
197
184
|
* @param {string} contentBase - Absolute path to the content tree.
|
|
198
185
|
* @param {object} ctx - `{ packages, contentPackage, skipDirectories, base,
|
|
199
|
-
* mount, scheme }`. `contentPackage` is the package a note that
|
|
200
|
-
* belongs to; `base` is where the package is served and `mount`
|
|
201
|
-
* content tree sits inside it.
|
|
186
|
+
* mount, scheme, only }`. `contentPackage` is the package a note that
|
|
187
|
+
* declares none belongs to; `base` is where the package is served and `mount`
|
|
188
|
+
* is where its content tree sits inside it. `only`, an absolute path, reads
|
|
189
|
+
* that one note and no other.
|
|
202
190
|
* @returns {{pages: object[], addressFindings: object[], fmLinkFindings: object[]}}
|
|
203
191
|
*/
|
|
204
192
|
export function collectContentPages(contentBase, ctx) {
|
|
@@ -227,6 +215,7 @@ export function collectContentPages(contentBase, ctx) {
|
|
|
227
215
|
const dates = reckoningContext({ notes: records });
|
|
228
216
|
|
|
229
217
|
for (const file of siteCorpusFiles(contentBase, ctx)) {
|
|
218
|
+
if (ctx.only && file !== ctx.only) continue;
|
|
230
219
|
const note = readNote(file, ctx);
|
|
231
220
|
if (!note) continue;
|
|
232
221
|
applyComputedBeingAge(note.fm, present, dates);
|
|
@@ -292,16 +281,43 @@ export function collectContentPages(contentBase, ctx) {
|
|
|
292
281
|
// `file.folder`, and the page's `kbfolder`.
|
|
293
282
|
folder: path.basename(path.dirname(file)),
|
|
294
283
|
// Every page is addressed by `(type, shortcode)` at the package
|
|
295
|
-
// root, which takes no content mount.
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
url:
|
|
284
|
+
// root, which takes no content mount.
|
|
285
|
+
//
|
|
286
|
+
// A note `site.sections` names is the page of its section, and is
|
|
287
|
+
// published at the section rather than at its own address.
|
|
288
|
+
url:
|
|
289
|
+
ctx.sectionPages?.has(slug) ?
|
|
290
|
+
`${ctx.base}${ctx.sectionPages.get(slug)}/`
|
|
291
|
+
: `${ctx.base}${slug}/`,
|
|
300
292
|
});
|
|
301
293
|
}
|
|
302
294
|
return { pages, addressFindings, fmLinkFindings };
|
|
303
295
|
}
|
|
304
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Each section `site.sections` names whose page no note in the tree is.
|
|
299
|
+
*
|
|
300
|
+
* The finding names the configuration key rather than a note, since it is the
|
|
301
|
+
* key that has to change; the command locates it in the configuration file.
|
|
302
|
+
*
|
|
303
|
+
* @param {Readonly<Record<string, string>>} sections - `site.sections`.
|
|
304
|
+
* @param {readonly {slug: string}[]} pages - The content pages.
|
|
305
|
+
* @returns {Array<{param: string[], severity: "error", message: string}>} The findings.
|
|
306
|
+
*/
|
|
307
|
+
function unmatchedSectionPages(sections, pages) {
|
|
308
|
+
const addresses = new Set(pages.map((page) => page.slug));
|
|
309
|
+
return Object.entries(sections)
|
|
310
|
+
.filter(([, address]) => !addresses.has(address))
|
|
311
|
+
.map(([name, address]) => ({
|
|
312
|
+
param: ["sections", name],
|
|
313
|
+
severity: "error",
|
|
314
|
+
message:
|
|
315
|
+
`\`site.sections.${name}\` is \`${address}\`, and no note in the content ` +
|
|
316
|
+
`tree has that address, so the section has no page to publish at ` +
|
|
317
|
+
`\`/${name}/\``,
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
|
|
305
321
|
/**
|
|
306
322
|
* The package's homepage notes — the authored page at `/<contentPackage>/`.
|
|
307
323
|
*
|
|
@@ -347,34 +363,10 @@ export function collectHomepages(contentBase, ctx) {
|
|
|
347
363
|
}
|
|
348
364
|
|
|
349
365
|
/**
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
* Its own writer, deliberately small. A homepage is authored markdown published
|
|
353
|
-
* verbatim — no table expansion and no link resolution — so routing it through
|
|
354
|
-
* {@link renderPages} would give it a pipeline it does not use.
|
|
355
|
-
*
|
|
356
|
-
* **Verbatim is the answer, not a gap.** A homepage's links could not be
|
|
357
|
-
* *resolved* here, so they are **checked** instead:
|
|
358
|
-
* {@link auditHomepageLinks} reads the body's markdown links, and reports a
|
|
359
|
-
* wikilink on the page rather than resolving one.
|
|
366
|
+
* Each homepage's frontmatter as its page publishes it, and its body.
|
|
360
367
|
*
|
|
361
|
-
* **Its destination is fixed**: {@link HOMEPAGE_DESTINATION}, at the package's
|
|
362
|
-
* site root, which Hugo renders as the `home` kind at `/<package>/`. The page
|
|
363
|
-
* states no `url` — the home kind has none to state — and the mount below it
|
|
364
|
-
* holds pages and nothing else, so this is the only `_index.md` in the tree.
|
|
365
|
-
*
|
|
366
|
-
* **Verbatim, but not unconnected.** The homepage is a page like any other on
|
|
367
|
-
* both sides of the link graph: a content page reaches it through
|
|
368
|
-
* `[[homepage-root|Text]]`, and its own markdown links name content pages. So
|
|
369
|
-
* it carries the same `related` block every content page does — handed in by
|
|
370
|
-
* the caller, since the graph is read off the content render and a tree with
|
|
371
|
-
* only a homepage has no such graph.
|
|
372
|
-
*
|
|
373
|
-
* @param {string} outRoot - The package's site root — the content mount's
|
|
374
|
-
* root, `build/hugo/content`, one level above the mount itself.
|
|
375
368
|
* @param {readonly object[]} pages - From {@link collectHomepages}.
|
|
376
|
-
* @param {object} config - The resolved configuration
|
|
377
|
-
* the default title.
|
|
369
|
+
* @param {object} config - The resolved configuration.
|
|
378
370
|
* @param {object} [options] - Options.
|
|
379
371
|
* @param {import("./related-pages.mjs").Related} [options.related] - The
|
|
380
372
|
* homepage's backlinks and mentions. Absent where nothing connects to it,
|
|
@@ -383,15 +375,16 @@ export function collectHomepages(contentBase, ctx) {
|
|
|
383
375
|
* address resolves against the images this package ships.
|
|
384
376
|
* @param {object} [options.foreign] - The dependencies' vendored indexes, so a
|
|
385
377
|
* `data.banner` may name an image a dependency ships.
|
|
386
|
-
* @returns {
|
|
378
|
+
* @returns {Array<{file: string, body: string, data: object}>} One entry per
|
|
379
|
+
* homepage, in walk order.
|
|
387
380
|
*/
|
|
388
|
-
export function
|
|
381
|
+
export function homepageData(pages, config, { related, records, foreign } = {}) {
|
|
389
382
|
const artIndex = assetAddressIndex(records ?? [], { config, foreign });
|
|
390
383
|
// A pathname no asset host serves resolves to `null`, which the theme reads
|
|
391
384
|
// as an unset banner rather than as a URL to nowhere.
|
|
392
385
|
const webSrc = (src) => resolvePathname(src, config)?.web ?? null;
|
|
393
386
|
const artSrc = (value, type, accepts) => artPathname(artIndex, value, type, accepts).pathname;
|
|
394
|
-
|
|
387
|
+
return pages.map((page) => {
|
|
395
388
|
const data = homepageFrontmatter(page.fm, {
|
|
396
389
|
contentPackage: config.contentPackage,
|
|
397
390
|
title: homepageTitle(page.fm, config),
|
|
@@ -399,11 +392,8 @@ export function writeHomepages(outRoot, pages, config, { related, records, forei
|
|
|
399
392
|
});
|
|
400
393
|
if (isPlainObject(data.data)) data.data = { ...data.data };
|
|
401
394
|
resolveArtFields(data, webSrc, artSrc);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
fs.writeFileSync(dest, writeMatter(page.body, encodeAddresses(data)));
|
|
405
|
-
}
|
|
406
|
-
return pages.length;
|
|
395
|
+
return { file: page.file, body: page.body, data };
|
|
396
|
+
});
|
|
407
397
|
}
|
|
408
398
|
|
|
409
399
|
/**
|
|
@@ -442,13 +432,14 @@ export function siteGates(pages, findings, { config, records, sqlTables }) {
|
|
|
442
432
|
homepages: [],
|
|
443
433
|
frontmatterLinks: findings.fmLinkFindings ?? [],
|
|
444
434
|
addressErrors: findings.addressFindings ?? [],
|
|
435
|
+
sections: findings.sectionFindings ?? [],
|
|
445
436
|
staleManifests: [],
|
|
446
437
|
unaddressable: [],
|
|
447
438
|
index: null,
|
|
448
439
|
foreign: null,
|
|
449
440
|
manifests: null,
|
|
450
441
|
};
|
|
451
|
-
if (out.frontmatterLinks.length || out.addressErrors.length) return out;
|
|
442
|
+
if (out.frontmatterLinks.length || out.addressErrors.length || out.sections.length) return out;
|
|
452
443
|
|
|
453
444
|
const content = pages.filter((p) => p.kind === "content");
|
|
454
445
|
// Which packages are *local* is what decides which manifests are foreign,
|
|
@@ -503,6 +494,7 @@ export function emptyGates() {
|
|
|
503
494
|
homepages: [],
|
|
504
495
|
frontmatterLinks: [],
|
|
505
496
|
addressErrors: [],
|
|
497
|
+
sections: [],
|
|
506
498
|
staleManifests: [],
|
|
507
499
|
unaddressable: [],
|
|
508
500
|
index: null,
|
|
@@ -514,7 +506,7 @@ export function emptyGates() {
|
|
|
514
506
|
/**
|
|
515
507
|
* Whether any gate produced a finding.
|
|
516
508
|
* @param {{homepages: unknown[], frontmatterLinks: unknown[],
|
|
517
|
-
* addressErrors: unknown[], staleManifests: unknown[],
|
|
509
|
+
* addressErrors: unknown[], sections?: unknown[], staleManifests: unknown[],
|
|
518
510
|
* unaddressable: unknown[]}} gates - Results of the site checks.
|
|
519
511
|
* @returns {boolean} Whether at least one check found an error.
|
|
520
512
|
*/
|
|
@@ -523,11 +515,38 @@ export function gatesFailed(gates) {
|
|
|
523
515
|
gates.homepages.length ||
|
|
524
516
|
gates.frontmatterLinks.length ||
|
|
525
517
|
gates.addressErrors.length ||
|
|
518
|
+
gates.sections?.length ||
|
|
526
519
|
gates.staleManifests.length ||
|
|
527
520
|
gates.unaddressable.length,
|
|
528
521
|
);
|
|
529
522
|
}
|
|
530
523
|
|
|
524
|
+
/**
|
|
525
|
+
* Whether a site build found anything that stops its pages being written: a
|
|
526
|
+
* gate that fired, or a note whose table, expression, block, caption, heading,
|
|
527
|
+
* footnote, link, image or embed is in error. What drawing the maps found does
|
|
528
|
+
* not stop it.
|
|
529
|
+
*
|
|
530
|
+
* @param {object} result - From {@link buildSite}.
|
|
531
|
+
* @returns {boolean} Whether the build failed.
|
|
532
|
+
*/
|
|
533
|
+
export function siteBuildFailed(result) {
|
|
534
|
+
return (
|
|
535
|
+
gatesFailed(result.gates) ||
|
|
536
|
+
[
|
|
537
|
+
result.tableErrors,
|
|
538
|
+
result.expressionErrors,
|
|
539
|
+
result.secretErrors,
|
|
540
|
+
result.captionErrors,
|
|
541
|
+
result.headingErrors,
|
|
542
|
+
result.footnoteErrors,
|
|
543
|
+
result.wikiErrors,
|
|
544
|
+
result.imageErrors,
|
|
545
|
+
result.embedErrors,
|
|
546
|
+
].some((list) => list?.length)
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
|
|
531
550
|
/**
|
|
532
551
|
* The universe a generated table searches, grouped by package.
|
|
533
552
|
*
|
|
@@ -559,39 +578,19 @@ export function tableUniverse(pages) {
|
|
|
559
578
|
/**
|
|
560
579
|
* The frontmatter a page publishes with.
|
|
561
580
|
*
|
|
562
|
-
* An authored `aliases` is a list of names a reader might call the note.
|
|
563
|
-
*
|
|
564
|
-
* redirect stub at each name. They are dropped, and this build emits no
|
|
565
|
-
* redirects of its own.
|
|
566
|
-
*
|
|
567
|
-
* A content page states its own **`url`**, which is its address rather than its
|
|
568
|
-
* path. It is written flat under the content mount, so Hugo would
|
|
569
|
-
* otherwise publish it at `<mount><type>-<shortcode>/` rather than at the
|
|
570
|
-
* package-wide address the link manifest records — the same address, one
|
|
571
|
-
* segment too deep. So the address is stated and the mount does not reach it.
|
|
572
|
-
*
|
|
573
|
-
* **It is stated relative to the site root, and so carries no package base**.
|
|
574
|
-
* Hugo resolves a `url` against `baseURL`, whose path is already where
|
|
575
|
-
* the package is served — a consumer's Hugo site *is* its package — so writing
|
|
576
|
-
* `page.url`, which carries the base for every href this build renders, wrote
|
|
577
|
-
* that base a second time and published every content page a segment too deep
|
|
578
|
-
* (`/sohl/sohl/doc-rulesintro/`). The two are separate quantities: the page
|
|
579
|
-
* states `/<slug>/`, and everything that points *at* the page — the address
|
|
580
|
-
* index a wikilink resolves through, and the link manifest — composes
|
|
581
|
-
* `<base><slug>/`.
|
|
581
|
+
* An authored `aliases` is a list of names a reader might call the note. It is
|
|
582
|
+
* dropped: this build emits no redirects.
|
|
582
583
|
*
|
|
583
584
|
* A content page carries the package the build **derived**. No note
|
|
584
|
-
* declares one, so the note's frontmatter alone
|
|
585
|
-
*
|
|
586
|
-
* emitted page is what a theme reads: `breadcrumbs.html` reads
|
|
587
|
-
* `.Params.package`. Writing the derived value keeps a page
|
|
585
|
+
* declares one, so the note's frontmatter alone would publish a page that does
|
|
586
|
+
* not say which package it belongs to. Writing the derived value keeps a page
|
|
588
587
|
* self-describing and makes sweeping the field out of a content tree
|
|
589
588
|
* output-preserving for a site as it already is for the packs.
|
|
590
589
|
*
|
|
591
590
|
* **`related` is derived, never authored.** What links to a page is a fact
|
|
592
591
|
* about every other page, known only once the whole tree has resolved — see
|
|
593
592
|
* {@link module:engine/related-pages} — so an authored value is dropped the
|
|
594
|
-
* way `aliases` is, and {@link renderPages}
|
|
593
|
+
* way `aliases` is, and {@link renderPages} sets the derived block once it
|
|
595
594
|
* holds the graph.
|
|
596
595
|
*
|
|
597
596
|
* @param {object} page - The page.
|
|
@@ -605,7 +604,7 @@ export function tableUniverse(pages) {
|
|
|
605
604
|
* pathname into the address the website serves. Every artwork field goes
|
|
606
605
|
* through it, so a page's `img:` and its body images name the same file the
|
|
607
606
|
* same way.
|
|
608
|
-
* @returns {object} The frontmatter
|
|
607
|
+
* @returns {object} The frontmatter.
|
|
609
608
|
*/
|
|
610
609
|
export function pageFrontmatter(page, { decorate, webSrc, artSrc }) {
|
|
611
610
|
const { fm, slug } = page;
|
|
@@ -615,12 +614,7 @@ export function pageFrontmatter(page, { decorate, webSrc, artSrc }) {
|
|
|
615
614
|
// Spread after the note's own frontmatter. Guarded because
|
|
616
615
|
// `package: undefined` is not a value YAML can carry.
|
|
617
616
|
...(page.pkg ? { package: page.pkg } : {}),
|
|
618
|
-
// The address, stated
|
|
619
|
-
// the site's own base to it. `slug` is written beside it
|
|
620
|
-
// because it is the last segment of that address and Hugo's own key
|
|
621
|
-
// for one; it decides nothing while `url` is present, but a page
|
|
622
|
-
// that carried only `url` would report a slug Hugo had inferred
|
|
623
|
-
// from the filename.
|
|
617
|
+
// The address, stated site-root relative; `slug` is its last segment.
|
|
624
618
|
slug,
|
|
625
619
|
url: `/${slug}/`,
|
|
626
620
|
title: pageTitle(page),
|
|
@@ -692,37 +686,14 @@ function isPlainObject(value) {
|
|
|
692
686
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
693
687
|
}
|
|
694
688
|
|
|
695
|
-
/**
|
|
696
|
-
* Where a page is written, relative to the output root.
|
|
697
|
-
*
|
|
698
|
-
* **Flat, under the mount, named by its address**. A content page's URL
|
|
699
|
-
* is its address — `/<package>/<type>-<shortcode>/` — and the file is named
|
|
700
|
-
* the same way, so the two agree. No page creates a directory: a section is
|
|
701
|
-
* a Hugo listing over a content directory, the `section` kind is disabled on
|
|
702
|
-
* every site, and a directory chosen only to satisfy a rendering engine's idea
|
|
703
|
-
* of what a section is has no business in the note format.
|
|
704
|
-
*
|
|
705
|
-
* The name is the *whole* address, so two types cannot fight over one file: a
|
|
706
|
-
* `doc` note's `subType` may be spelled the same as another note's `type`, and
|
|
707
|
-
* `doc-gear.md` and `weapongear-gear.md` are distinct.
|
|
708
|
-
*
|
|
709
|
-
* @param {{slug: string}} page - The page.
|
|
710
|
-
* @returns {string} The file, relative to the mount.
|
|
711
|
-
*/
|
|
712
|
-
export function pageDestination(page) {
|
|
713
|
-
return `${page.slug}.md`;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
/** Transform one content page without writing the site mount. */
|
|
689
|
+
/** Transform one content page. */
|
|
717
690
|
export function renderSitePage(
|
|
718
691
|
page,
|
|
719
692
|
{
|
|
720
693
|
index,
|
|
721
694
|
foreign,
|
|
722
|
-
universe,
|
|
723
695
|
pass = {},
|
|
724
696
|
decorate,
|
|
725
|
-
linkable = (d) => Boolean(d.fm.shortcode),
|
|
726
697
|
sqlTables,
|
|
727
698
|
config,
|
|
728
699
|
artIndex,
|
|
@@ -732,26 +703,13 @@ export function renderSitePage(
|
|
|
732
703
|
runs = runContext(config),
|
|
733
704
|
},
|
|
734
705
|
) {
|
|
735
|
-
const tableErrors = [];
|
|
736
|
-
const expressionErrors = [];
|
|
737
706
|
const secretErrors = [];
|
|
738
707
|
const captionErrors = [];
|
|
739
708
|
const headingErrors = [];
|
|
740
709
|
const footnoteErrors = [];
|
|
741
|
-
const wikiErrors = [];
|
|
742
710
|
const imageErrors = [];
|
|
743
711
|
const embedErrors = [];
|
|
744
|
-
const resolved = [];
|
|
745
712
|
const src = page.relPath ?? page.base;
|
|
746
|
-
const ctx = wikiContext(index, {
|
|
747
|
-
src,
|
|
748
|
-
file: page.file,
|
|
749
|
-
type: page.fm.type ?? null,
|
|
750
|
-
errors: wikiErrors,
|
|
751
|
-
foreignIndex: foreign.index,
|
|
752
|
-
assets: artIndex,
|
|
753
|
-
resolved,
|
|
754
|
-
});
|
|
755
713
|
|
|
756
714
|
const seenImages = new Map();
|
|
757
715
|
const webSrc = (src) => {
|
|
@@ -777,24 +735,10 @@ export function renderSitePage(
|
|
|
777
735
|
return src;
|
|
778
736
|
};
|
|
779
737
|
const artSrc = (value, type, accepts) => artPathname(artIndex, value, type, accepts).pathname;
|
|
780
|
-
// The role lookup a captioned item's `map` counter reaches through —
|
|
781
|
-
//
|
|
782
|
-
//
|
|
783
|
-
// an ordinary image; `roleByWebSrc` is the same lookup keyed by the web
|
|
784
|
-
// address each picture that reaches `renderImageFigures` resolves to,
|
|
785
|
-
// recorded as it is resolved rather than built across the whole corpus —
|
|
786
|
-
// `webSrc` itself reports an image's own problems as a side effect, which
|
|
787
|
-
// a speculative call over every asset would misfire for one this page
|
|
788
|
-
// never names.
|
|
738
|
+
// The role lookup a captioned item's `map` counter reaches through — see
|
|
739
|
+
// `engine/content-figures.mjs`'s `resolveRole`. It numbers the page's
|
|
740
|
+
// figures once, in expansion, and the render below reads that numbering.
|
|
789
741
|
const resolveRole = (address) => embedRole(artIndex, address);
|
|
790
|
-
const pathRoles = pathnameRoles(artIndex);
|
|
791
|
-
const roleByWebSrc = new Map();
|
|
792
|
-
const webSrcWithRole = (src) => {
|
|
793
|
-
const result = webSrc(src);
|
|
794
|
-
const role = pathRoles.get(src);
|
|
795
|
-
if (role) roleByWebSrc.set(result, role);
|
|
796
|
-
return result;
|
|
797
|
-
};
|
|
798
742
|
// The picture's role and pixel size, by the address it resolved to — read
|
|
799
743
|
// before `webSrc` translates it to the page's own host. A body image is
|
|
800
744
|
// free to write the bare, own-package form, so the address is normalized
|
|
@@ -803,11 +747,6 @@ export function renderSitePage(
|
|
|
803
747
|
const key = assetPathnameKey(src, config);
|
|
804
748
|
return key ? artIndex?.byPath?.get(key) : undefined;
|
|
805
749
|
};
|
|
806
|
-
const resolve = (text) => {
|
|
807
|
-
let transformed = pass.beforeLinks ? pass.beforeLinks(text, page) : text;
|
|
808
|
-
transformed = resolveWebWikilinks(transformed, ctx);
|
|
809
|
-
return renderImageFigures(inlineDrawings(transformed), webSrcWithRole, lookupAsset);
|
|
810
|
-
};
|
|
811
750
|
// A generated **From here** section names the drawing the build made, which
|
|
812
751
|
// the asset host does not serve: it is set inline, so its place names stay
|
|
813
752
|
// links. A drawing this build did not make is left for the image pass,
|
|
@@ -820,74 +759,40 @@ export function renderSitePage(
|
|
|
820
759
|
return inlineDrawingFigure(fs.readFileSync(drawing.file, "utf8"), alt);
|
|
821
760
|
});
|
|
822
761
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
);
|
|
836
|
-
// What the `ref` expression helper needs beyond the label: the caption
|
|
837
|
-
// text and whether one was authored, by the same id `ctx.captionLabels`
|
|
838
|
-
// keys on.
|
|
839
|
-
const figuresById = new Map(
|
|
840
|
-
figureScan.figures
|
|
841
|
-
.filter((figure) => figure.id)
|
|
842
|
-
.map((figure) => [
|
|
843
|
-
figure.id,
|
|
844
|
-
{ label: figure.label, caption: figure.caption, hasCaption: figure.hasCaption },
|
|
845
|
-
]),
|
|
846
|
-
);
|
|
847
|
-
// This page's own anchors, so a `[[#slug]]` self-link is checked against
|
|
848
|
-
// what the page actually declares rather than trusted unconditionally.
|
|
849
|
-
ctx.anchors = new Set(collectAnchors(markdown).map((anchor) => anchor.slug));
|
|
850
|
-
ctx.eventAnchors = new Set(eventAnchors(page.fm).map((anchor) => anchor.slug));
|
|
851
|
-
const expressions = renderMarkdownExpressions(markGenerated(markdown, generatedFrom), {
|
|
852
|
-
fm: page.fm,
|
|
853
|
-
dates: index.dateContext,
|
|
854
|
-
file: page.file,
|
|
855
|
-
bodyLine: page.bodyLine,
|
|
856
|
-
sqlResults: sqlTables?.inline?.get(page.file),
|
|
762
|
+
// The frontmatter's art resolves before the body's pictures, so a picture
|
|
763
|
+
// named in both is counted in that order.
|
|
764
|
+
const data = pageFrontmatter(page, { decorate, webSrc, artSrc });
|
|
765
|
+
const expanded = expandContent(page.body, {
|
|
766
|
+
target: "web",
|
|
767
|
+
index,
|
|
768
|
+
note: { fm: page.fm, file: page.file, source: src, bodyLine: page.bodyLine },
|
|
769
|
+
prepared: {
|
|
770
|
+
sqlTables: sqlTables?.get(page.file),
|
|
771
|
+
pageLists: sqlTables?.pageLists?.get(page.file),
|
|
772
|
+
inline: sqlTables?.inline?.get(page.file),
|
|
773
|
+
},
|
|
857
774
|
events,
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
contentTypes: index.contentTypes,
|
|
866
|
-
packages: index.packages,
|
|
867
|
-
noIndexPackages: index.noIndexPackages,
|
|
868
|
-
contentPackage: index.contentPackage,
|
|
869
|
-
siteIndexMap: index.index,
|
|
870
|
-
figuresByAddress,
|
|
871
|
-
}),
|
|
775
|
+
resolveRole,
|
|
776
|
+
figuresByAddress,
|
|
777
|
+
links: {
|
|
778
|
+
foreign: foreign.index,
|
|
779
|
+
assets: artIndex,
|
|
780
|
+
beforeLinks: pass.beforeLinks ? (text) => pass.beforeLinks(text, page) : undefined,
|
|
781
|
+
afterLinks: (text) => renderImageFigures(inlineDrawings(text), webSrc, lookupAsset),
|
|
872
782
|
},
|
|
873
783
|
});
|
|
874
|
-
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
//
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
const authoredLinks = resolved.length;
|
|
784
|
+
const { lineMap } = expanded;
|
|
785
|
+
const tableErrors = expanded.findings.tables;
|
|
786
|
+
const expressionErrors = expanded.findings.expressions;
|
|
787
|
+
const wikiErrors = expanded.findings.links;
|
|
788
|
+
// The author's text and the sections the build generated after it are
|
|
789
|
+
// told apart by a comment, so the site writer can leave the generated
|
|
790
|
+
// links out of what makes a page reachable.
|
|
882
791
|
const resolvedBody =
|
|
883
|
-
generated ?
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
undefined,
|
|
888
|
-
{ resolveRole: (address) => roleByWebSrc.get(address) },
|
|
889
|
-
);
|
|
890
|
-
for (const error of figureScan.errors)
|
|
792
|
+
expanded.generated ?
|
|
793
|
+
`${expanded.authored}\n\n${GENERATED_MARK}\n\n${expanded.generated}`
|
|
794
|
+
: expanded.markdown;
|
|
795
|
+
for (const error of expanded.findings.captions)
|
|
891
796
|
captionErrors.push({
|
|
892
797
|
file: page.file,
|
|
893
798
|
line: (page.bodyLine ?? 1) + (lineMap[error.line - 1]?.line ?? error.line - 1),
|
|
@@ -926,15 +831,15 @@ export function renderSitePage(
|
|
|
926
831
|
embedErrors.push(error);
|
|
927
832
|
return {
|
|
928
833
|
page,
|
|
929
|
-
// The
|
|
930
|
-
//
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
renderSpans(renderAlerts(renderBlocks(figured.markdown, "web").markdown).markdown)
|
|
934
|
-
.markdown,
|
|
935
|
-
),
|
|
834
|
+
// The expanded body and its numbered figures, for the renderer that
|
|
835
|
+
// writes the whole page.
|
|
836
|
+
markdown: resolvedBody,
|
|
837
|
+
figures: expanded.figures.figures,
|
|
936
838
|
data,
|
|
937
|
-
|
|
839
|
+
// The links a generated section makes join no page's Related card:
|
|
840
|
+
// that card is what the authors link, and the sections already list
|
|
841
|
+
// what they derive.
|
|
842
|
+
resolved: expanded.resolved.slice(0, expanded.authoredLinks),
|
|
938
843
|
tableErrors,
|
|
939
844
|
expressionErrors,
|
|
940
845
|
secretErrors,
|
|
@@ -948,24 +853,20 @@ export function renderSitePage(
|
|
|
948
853
|
}
|
|
949
854
|
|
|
950
855
|
/**
|
|
951
|
-
* Renders
|
|
952
|
-
*
|
|
953
|
-
* The order inside a page is load-bearing and is the same order the pack
|
|
954
|
-
* compilers use:
|
|
856
|
+
* Renders every page.
|
|
955
857
|
*
|
|
956
|
-
*
|
|
957
|
-
*
|
|
958
|
-
*
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
963
|
-
* rather than replacing it.
|
|
858
|
+
* Each page's body is expanded by
|
|
859
|
+
* {@link module:engine/content-expand.expandContent}, the step the pack
|
|
860
|
+
* compilers take too: tables first and outside code-fence protection, then
|
|
861
|
+
* figure numbers and expressions, then, inside protection, the consumer's
|
|
862
|
+
* `beforeLinks` pass and wikilink resolution. A `{@link}` tag may sit in prose
|
|
863
|
+
* a wikilink also touches, so the repository's own rewrite runs before the
|
|
864
|
+
* shared one rather than replacing it.
|
|
964
865
|
*
|
|
965
|
-
* **Every page is rendered before any is
|
|
966
|
-
* known only once every other page has resolved, and it is
|
|
967
|
-
*
|
|
968
|
-
*
|
|
866
|
+
* **Every page is rendered before any is finished.** What links *to* a page is
|
|
867
|
+
* known only once every other page has resolved, and it is set in the page's
|
|
868
|
+
* own front matter, so the render is one pass over the tree and the
|
|
869
|
+
* finishing is a second — with the link graph read off the first, at the one
|
|
969
870
|
* point the resolver answers, and inverted once between them. Nothing is
|
|
970
871
|
* resolved twice.
|
|
971
872
|
*
|
|
@@ -982,14 +883,15 @@ export function renderSitePage(
|
|
|
982
883
|
* @returns {{written: number, byKind: Record<string, number>, tableErrors: object[],
|
|
983
884
|
* wikiErrors: object[], imageErrors: object[], embedErrors: object[],
|
|
984
885
|
* related: Map<string, import("./related-pages.mjs").Related>,
|
|
985
|
-
* maps: number}} `related`
|
|
886
|
+
* maps: number, pages: object[], runs: object}} `related`
|
|
986
887
|
* is keyed by page URL, and holds the homepage's block beside every content
|
|
987
|
-
* page's, so the caller can
|
|
988
|
-
* `maps` counts the pages carrying a map.
|
|
888
|
+
* page's, so the caller can set it on the page this render does not.
|
|
889
|
+
* `maps` counts the pages carrying a map. `pages` holds
|
|
890
|
+
* `{page, markdown, figures, data}` for each page and `runs` the package's
|
|
891
|
+
* marked-text context.
|
|
989
892
|
*/
|
|
990
893
|
export function renderPages(pages, options) {
|
|
991
894
|
const {
|
|
992
|
-
outRoot,
|
|
993
895
|
index,
|
|
994
896
|
foreign,
|
|
995
897
|
universe,
|
|
@@ -1001,8 +903,6 @@ export function renderPages(pages, options) {
|
|
|
1001
903
|
records = [],
|
|
1002
904
|
homepages = [],
|
|
1003
905
|
maps = new Map(),
|
|
1004
|
-
write = true,
|
|
1005
|
-
capture = false,
|
|
1006
906
|
} = options;
|
|
1007
907
|
|
|
1008
908
|
// The address space the art slots and the body's embeds resolve against:
|
|
@@ -1033,7 +933,7 @@ export function renderPages(pages, options) {
|
|
|
1033
933
|
self: { fm: searchableFrontmatter(page.fm, page.pkg), path: page.relPath },
|
|
1034
934
|
});
|
|
1035
935
|
const byId = new Map(
|
|
1036
|
-
scanFigures(markdown)
|
|
936
|
+
scanFigures(markdown, { resolveRole: (address) => embedRole(artIndex, address) })
|
|
1037
937
|
.figures.filter((figure) => figure.id)
|
|
1038
938
|
.map((figure) => [
|
|
1039
939
|
figure.id,
|
|
@@ -1075,6 +975,7 @@ export function renderPages(pages, options) {
|
|
|
1075
975
|
title: pageTitle(page),
|
|
1076
976
|
url: page.url,
|
|
1077
977
|
type: String(page.fm.type),
|
|
978
|
+
...(page.fm.subType ? { subType: String(page.fm.subType) } : {}),
|
|
1078
979
|
...(typeof page.fm.name?.sort === "string" && page.fm.name.sort ?
|
|
1079
980
|
{ sort: page.fm.name.sort }
|
|
1080
981
|
: {}),
|
|
@@ -1087,11 +988,12 @@ export function renderPages(pages, options) {
|
|
|
1087
988
|
}
|
|
1088
989
|
}
|
|
1089
990
|
// What each page renders as, held until the graph is whole.
|
|
1090
|
-
/** @type {Array<{page: object,
|
|
991
|
+
/** @type {Array<{page: object, markdown: string, figures: object[], data: object}>} */
|
|
1091
992
|
const rendered = [];
|
|
993
|
+
const runs = runContext(config, records);
|
|
1092
994
|
|
|
1093
|
-
|
|
1094
|
-
|
|
995
|
+
const renderOne = (page) =>
|
|
996
|
+
renderSitePage(page, {
|
|
1095
997
|
index,
|
|
1096
998
|
foreign,
|
|
1097
999
|
universe,
|
|
@@ -1104,8 +1006,10 @@ export function renderPages(pages, options) {
|
|
|
1104
1006
|
figuresByAddress,
|
|
1105
1007
|
events,
|
|
1106
1008
|
drawings: maps,
|
|
1107
|
-
runs
|
|
1009
|
+
runs,
|
|
1108
1010
|
});
|
|
1011
|
+
for (const page of pages) {
|
|
1012
|
+
const result = renderOne(page);
|
|
1109
1013
|
tableErrors.push(...result.tableErrors);
|
|
1110
1014
|
expressionErrors.push(...result.expressionErrors);
|
|
1111
1015
|
secretErrors.push(...result.secretErrors);
|
|
@@ -1115,7 +1019,12 @@ export function renderPages(pages, options) {
|
|
|
1115
1019
|
wikiErrors.push(...result.wikiErrors);
|
|
1116
1020
|
imageErrors.push(...result.imageErrors);
|
|
1117
1021
|
embedErrors.push(...result.embedErrors);
|
|
1118
|
-
rendered.push({
|
|
1022
|
+
rendered.push({
|
|
1023
|
+
page,
|
|
1024
|
+
markdown: result.markdown,
|
|
1025
|
+
figures: result.figures,
|
|
1026
|
+
data: result.data,
|
|
1027
|
+
});
|
|
1119
1028
|
for (const hit of result.resolved) if (hit.url) edges.push([page.url, hit.url]);
|
|
1120
1029
|
}
|
|
1121
1030
|
|
|
@@ -1125,17 +1034,10 @@ export function renderPages(pages, options) {
|
|
|
1125
1034
|
const related = relatedPages(edges, entries, deliberate);
|
|
1126
1035
|
|
|
1127
1036
|
let withMap = 0;
|
|
1128
|
-
const
|
|
1129
|
-
for (const { page, body, data } of rendered) {
|
|
1037
|
+
for (const { page, data } of rendered) {
|
|
1130
1038
|
const block = related.get(page.url);
|
|
1131
1039
|
if (block) data.related = block;
|
|
1132
1040
|
if (/\{#fromhere\}/.test(sqlTables?.get(page.file)?.generated ?? "")) withMap += 1;
|
|
1133
|
-
if (write) {
|
|
1134
|
-
const dest = path.join(outRoot, pageDestination(page));
|
|
1135
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
1136
|
-
fs.writeFileSync(dest, writeMatter(body, encodeAddresses(data)));
|
|
1137
|
-
}
|
|
1138
|
-
if (outputs) outputs.set(page.file, { markdown: body, frontmatter: data });
|
|
1139
1041
|
byKind[page.kind] = (byKind[page.kind] ?? 0) + 1;
|
|
1140
1042
|
}
|
|
1141
1043
|
|
|
@@ -1153,7 +1055,21 @@ export function renderPages(pages, options) {
|
|
|
1153
1055
|
embedErrors,
|
|
1154
1056
|
related,
|
|
1155
1057
|
maps: withMap,
|
|
1156
|
-
|
|
1058
|
+
pages: rendered.map(({ page, markdown, figures, data }) => ({
|
|
1059
|
+
page,
|
|
1060
|
+
markdown,
|
|
1061
|
+
figures,
|
|
1062
|
+
data,
|
|
1063
|
+
})),
|
|
1064
|
+
runs,
|
|
1065
|
+
// One page rendered again with everything the whole render resolved
|
|
1066
|
+
// against, its place in the link graph included.
|
|
1067
|
+
renderAgain: (page) => {
|
|
1068
|
+
const result = renderOne(page);
|
|
1069
|
+
const block = related.get(page.url);
|
|
1070
|
+
if (block) result.data.related = block;
|
|
1071
|
+
return result;
|
|
1072
|
+
},
|
|
1157
1073
|
};
|
|
1158
1074
|
}
|
|
1159
1075
|
|
|
@@ -1211,7 +1127,7 @@ export function sitePageDecorator(config, index) {
|
|
|
1211
1127
|
}
|
|
1212
1128
|
|
|
1213
1129
|
/**
|
|
1214
|
-
*
|
|
1130
|
+
* Collects, checks and renders a content tree's pages, and reports what it found.
|
|
1215
1131
|
*
|
|
1216
1132
|
* Returns rather than exits, in every case. A caller — the command, or a test —
|
|
1217
1133
|
* decides what a finding means; the gates below are grouped so it can report
|
|
@@ -1229,8 +1145,13 @@ export function sitePageDecorator(config, index) {
|
|
|
1229
1145
|
* @returns {{gates: object, stats: object|null, tableErrors: object[],
|
|
1230
1146
|
* wikiErrors: object[], imageErrors: object[], embedErrors: object[],
|
|
1231
1147
|
* mapFindings: object[],
|
|
1232
|
-
* manifests: object|null}} `mapFindings` is what drawing the
|
|
1233
|
-
* warnings, never a reason to fail the build.
|
|
1148
|
+
* manifests: object|null, site?: object}} `mapFindings` is what drawing the
|
|
1149
|
+
* maps found — warnings, never a reason to fail the build. `site` holds what a page renderer needs: `base`, `pages` (each content
|
|
1150
|
+
* page's `{page, markdown, figures, data}`), `homepages` (each
|
|
1151
|
+
* `{file, body, data}`), `runs`, `index`, `foreign` and `records`; and
|
|
1152
|
+
* `renderNote(file)`, which reads one content note again and renders it
|
|
1153
|
+
* against the rest of the build, or returns `null` when its address, name
|
|
1154
|
+
* or type changed and the whole site must be built again.
|
|
1234
1155
|
*/
|
|
1235
1156
|
export function buildSite({ config, sqlTables } = {}) {
|
|
1236
1157
|
const resolved = config ?? loadPackConfig();
|
|
@@ -1244,29 +1165,9 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1244
1165
|
// that publishes it, and `prefix` is the content tree's mount within the
|
|
1245
1166
|
// package — the same `prefix` the link manifest records against, so a page
|
|
1246
1167
|
// and its manifest entry cannot disagree.
|
|
1247
|
-
const base =
|
|
1168
|
+
const base = siteBase(resolved);
|
|
1248
1169
|
const mount = `${base}${scheme.prefix}`;
|
|
1249
1170
|
|
|
1250
|
-
// The Hugo content tree mirrors that mount: the homepage is written at
|
|
1251
|
-
// `<out>/_index.md` and every page under `<out>/<prefix>/`.
|
|
1252
|
-
// The root is fixed — `build/hugo/content`, beside the generated
|
|
1253
|
-
// `hugo.toml` — and resolved against the repository root for the same
|
|
1254
|
-
// reason every configured path is, so the build reads and writes the same
|
|
1255
|
-
// places whatever directory it was launched from. It is wiped on every
|
|
1256
|
-
// run, which is safe precisely because it is not configurable: nothing an
|
|
1257
|
-
// author writes can point it at the working tree.
|
|
1258
|
-
const outBase = path.join(resolved.rootDir, HUGO_CONTENT);
|
|
1259
|
-
const out =
|
|
1260
|
-
publishesContent ?
|
|
1261
|
-
path.join(outBase, scheme.prefix.replace(/\/$/, ""))
|
|
1262
|
-
// A tree containing only the homepage has no content mount, so its root is
|
|
1263
|
-
// the output root.
|
|
1264
|
-
: outBase;
|
|
1265
|
-
// The homepage publishes at `/<contentPackage>/`, so its file goes at the
|
|
1266
|
-
// package's own root — one level above the content mount, and the same
|
|
1267
|
-
// directory when the tree contains only a homepage.
|
|
1268
|
-
const homeRoot = publishesContent ? outBase : out;
|
|
1269
|
-
|
|
1270
1171
|
const packages = new Set(site.packages.length ? site.packages : [resolved.contentPackage]);
|
|
1271
1172
|
|
|
1272
1173
|
const ctx = {
|
|
@@ -1290,6 +1191,11 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1290
1191
|
base,
|
|
1291
1192
|
mount,
|
|
1292
1193
|
scheme,
|
|
1194
|
+
// The notes `site.sections` names, by address, each with the section
|
|
1195
|
+
// it is the page of.
|
|
1196
|
+
sectionPages: new Map(
|
|
1197
|
+
Object.entries(site.sections ?? {}).map(([name, address]) => [address, name]),
|
|
1198
|
+
),
|
|
1293
1199
|
};
|
|
1294
1200
|
|
|
1295
1201
|
const collected = collectHomepages(resolved.paths.content, ctx);
|
|
@@ -1333,10 +1239,6 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1333
1239
|
};
|
|
1334
1240
|
}
|
|
1335
1241
|
|
|
1336
|
-
// The whole tree is a build artifact, regenerated every run: a page whose
|
|
1337
|
-
// note was deleted or moved would otherwise linger and keep publishing.
|
|
1338
|
-
fs.rmSync(outBase, { recursive: true, force: true });
|
|
1339
|
-
|
|
1340
1242
|
// Homepage-only stops here, and stopping is the point: nothing below reads
|
|
1341
1243
|
// the content tree for pages, so `sohl-kethira-basic` and `harn-adventures`
|
|
1342
1244
|
// cannot publish one whatever else their `site:` block declares.
|
|
@@ -1354,15 +1256,22 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1354
1256
|
imageErrors: [],
|
|
1355
1257
|
embedErrors: [],
|
|
1356
1258
|
mapFindings: [],
|
|
1357
|
-
stats: {
|
|
1358
|
-
|
|
1359
|
-
|
|
1259
|
+
stats: { homepages: homepages.length },
|
|
1260
|
+
site: {
|
|
1261
|
+
base,
|
|
1262
|
+
pages: [],
|
|
1263
|
+
homepages: homepageData(homepages, resolved, { records: ctx.records }),
|
|
1264
|
+
runs: runContext(resolved, ctx.records),
|
|
1265
|
+
index: null,
|
|
1266
|
+
foreign: null,
|
|
1267
|
+
records: ctx.records,
|
|
1268
|
+
renderNote: () => null,
|
|
1360
1269
|
},
|
|
1361
1270
|
};
|
|
1362
1271
|
}
|
|
1363
|
-
|
|
1364
1272
|
const content = collectContentPages(resolved.paths.content, ctx);
|
|
1365
1273
|
const { pages } = content;
|
|
1274
|
+
content.sectionFindings = unmatchedSectionPages(site.sections ?? {}, pages);
|
|
1366
1275
|
|
|
1367
1276
|
// The homepage is **indexed but not rendered**. `[[homepage-root|Text]]`
|
|
1368
1277
|
// is an ordinary wikilink and has to resolve to the page the build
|
|
@@ -1429,7 +1338,6 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1429
1338
|
});
|
|
1430
1339
|
|
|
1431
1340
|
const rendered = renderPages(pages, {
|
|
1432
|
-
outRoot: out,
|
|
1433
1341
|
sqlTables,
|
|
1434
1342
|
config: resolved,
|
|
1435
1343
|
records: ctx.records,
|
|
@@ -1443,14 +1351,14 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1443
1351
|
decorate: sitePageDecorator(resolved, gates.index),
|
|
1444
1352
|
});
|
|
1445
1353
|
|
|
1446
|
-
//
|
|
1447
|
-
//
|
|
1448
|
-
//
|
|
1449
|
-
const
|
|
1354
|
+
// The package's front page is not part of the content tree it
|
|
1355
|
+
// introduces. It publishes at `base`, which is where the graph keys its
|
|
1356
|
+
// block.
|
|
1357
|
+
const homepageOptions = {
|
|
1450
1358
|
related: rendered.related.get(base),
|
|
1451
1359
|
records: ctx.records,
|
|
1452
1360
|
foreign: gates.foreign,
|
|
1453
|
-
}
|
|
1361
|
+
};
|
|
1454
1362
|
|
|
1455
1363
|
return {
|
|
1456
1364
|
gates,
|
|
@@ -1466,9 +1374,53 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1466
1374
|
mapFindings: drawn.findings,
|
|
1467
1375
|
stats: {
|
|
1468
1376
|
...rendered.byKind,
|
|
1469
|
-
homepages:
|
|
1377
|
+
homepages: homepages.length,
|
|
1470
1378
|
maps: rendered.maps,
|
|
1471
|
-
|
|
1379
|
+
},
|
|
1380
|
+
site: {
|
|
1381
|
+
base,
|
|
1382
|
+
pages: rendered.pages,
|
|
1383
|
+
homepages: homepageData(homepages, resolved, homepageOptions),
|
|
1384
|
+
runs: rendered.runs,
|
|
1385
|
+
index: gates.index,
|
|
1386
|
+
foreign: gates.foreign,
|
|
1387
|
+
records: ctx.records,
|
|
1388
|
+
renderNote: (file) => {
|
|
1389
|
+
const before = pages.find((page) => page.file === file);
|
|
1390
|
+
const after = collectContentPages(resolved.paths.content, {
|
|
1391
|
+
...ctx,
|
|
1392
|
+
only: file,
|
|
1393
|
+
}).pages[0];
|
|
1394
|
+
// A note with a new address, name or type, or a new
|
|
1395
|
+
// note, changes what other pages resolve to, which
|
|
1396
|
+
// only a whole build answers.
|
|
1397
|
+
if (
|
|
1398
|
+
!before ||
|
|
1399
|
+
!after ||
|
|
1400
|
+
after.url !== before.url ||
|
|
1401
|
+
after.name !== before.name ||
|
|
1402
|
+
String(after.fm.type) !== String(before.fm.type)
|
|
1403
|
+
)
|
|
1404
|
+
return null;
|
|
1405
|
+
const result = rendered.renderAgain(after);
|
|
1406
|
+
return {
|
|
1407
|
+
page: after,
|
|
1408
|
+
markdown: result.markdown,
|
|
1409
|
+
figures: result.figures,
|
|
1410
|
+
data: result.data,
|
|
1411
|
+
findings: [
|
|
1412
|
+
...result.tableErrors,
|
|
1413
|
+
...result.expressionErrors,
|
|
1414
|
+
...result.secretErrors,
|
|
1415
|
+
...result.captionErrors,
|
|
1416
|
+
...result.headingErrors,
|
|
1417
|
+
...result.footnoteErrors,
|
|
1418
|
+
...result.wikiErrors,
|
|
1419
|
+
...result.imageErrors,
|
|
1420
|
+
...result.embedErrors,
|
|
1421
|
+
],
|
|
1422
|
+
};
|
|
1423
|
+
},
|
|
1472
1424
|
},
|
|
1473
1425
|
};
|
|
1474
1426
|
}
|