@heroiclands/package-build 23.13.0 → 24.0.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 +36 -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 +244 -78
- 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 +112 -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 +259 -314
- 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-render.mjs +593 -0
- package/engine/site-root.mjs +22 -13
- package/engine/site-search.mjs +161 -0
- package/engine/site-serve.mjs +273 -0
- package/engine/site-theme-banner.mjs +154 -0
- package/engine/site-theme-urls.mjs +125 -0
- package/engine/site-theme.mjs +926 -0
- 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-render.d.mts +171 -0
- package/types/engine/site-root.d.mts +12 -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,14 @@ 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";
|
|
116
102
|
import { drawSiteMaps, inlineDrawingFigure } from "./site-maps.mjs";
|
|
117
|
-
import { generatedDrawing
|
|
103
|
+
import { generatedDrawing } from "./generated-sections.mjs";
|
|
118
104
|
import { IMAGE_PATTERN } from "./content-images.mjs";
|
|
119
105
|
|
|
120
106
|
const require = createRequire(import.meta.url);
|
|
@@ -196,9 +182,10 @@ function readNote(file, ctx) {
|
|
|
196
182
|
*
|
|
197
183
|
* @param {string} contentBase - Absolute path to the content tree.
|
|
198
184
|
* @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.
|
|
185
|
+
* mount, scheme, only }`. `contentPackage` is the package a note that
|
|
186
|
+
* declares none belongs to; `base` is where the package is served and `mount`
|
|
187
|
+
* is where its content tree sits inside it. `only`, an absolute path, reads
|
|
188
|
+
* that one note and no other.
|
|
202
189
|
* @returns {{pages: object[], addressFindings: object[], fmLinkFindings: object[]}}
|
|
203
190
|
*/
|
|
204
191
|
export function collectContentPages(contentBase, ctx) {
|
|
@@ -227,6 +214,7 @@ export function collectContentPages(contentBase, ctx) {
|
|
|
227
214
|
const dates = reckoningContext({ notes: records });
|
|
228
215
|
|
|
229
216
|
for (const file of siteCorpusFiles(contentBase, ctx)) {
|
|
217
|
+
if (ctx.only && file !== ctx.only) continue;
|
|
230
218
|
const note = readNote(file, ctx);
|
|
231
219
|
if (!note) continue;
|
|
232
220
|
applyComputedBeingAge(note.fm, present, dates);
|
|
@@ -292,16 +280,43 @@ export function collectContentPages(contentBase, ctx) {
|
|
|
292
280
|
// `file.folder`, and the page's `kbfolder`.
|
|
293
281
|
folder: path.basename(path.dirname(file)),
|
|
294
282
|
// Every page is addressed by `(type, shortcode)` at the package
|
|
295
|
-
// root, which takes no content mount.
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
url:
|
|
283
|
+
// root, which takes no content mount.
|
|
284
|
+
//
|
|
285
|
+
// A note `site.sections` names is the page of its section, and is
|
|
286
|
+
// published at the section rather than at its own address.
|
|
287
|
+
url:
|
|
288
|
+
ctx.sectionPages?.has(slug) ?
|
|
289
|
+
`${ctx.base}${ctx.sectionPages.get(slug)}/`
|
|
290
|
+
: `${ctx.base}${slug}/`,
|
|
300
291
|
});
|
|
301
292
|
}
|
|
302
293
|
return { pages, addressFindings, fmLinkFindings };
|
|
303
294
|
}
|
|
304
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Each section `site.sections` names whose page no note in the tree is.
|
|
298
|
+
*
|
|
299
|
+
* The finding names the configuration key rather than a note, since it is the
|
|
300
|
+
* key that has to change; the command locates it in the configuration file.
|
|
301
|
+
*
|
|
302
|
+
* @param {Readonly<Record<string, string>>} sections - `site.sections`.
|
|
303
|
+
* @param {readonly {slug: string}[]} pages - The content pages.
|
|
304
|
+
* @returns {Array<{param: string[], severity: "error", message: string}>} The findings.
|
|
305
|
+
*/
|
|
306
|
+
function unmatchedSectionPages(sections, pages) {
|
|
307
|
+
const addresses = new Set(pages.map((page) => page.slug));
|
|
308
|
+
return Object.entries(sections)
|
|
309
|
+
.filter(([, address]) => !addresses.has(address))
|
|
310
|
+
.map(([name, address]) => ({
|
|
311
|
+
param: ["sections", name],
|
|
312
|
+
severity: "error",
|
|
313
|
+
message:
|
|
314
|
+
`\`site.sections.${name}\` is \`${address}\`, and no note in the content ` +
|
|
315
|
+
`tree has that address, so the section has no page to publish at ` +
|
|
316
|
+
`\`/${name}/\``,
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
|
|
305
320
|
/**
|
|
306
321
|
* The package's homepage notes — the authored page at `/<contentPackage>/`.
|
|
307
322
|
*
|
|
@@ -347,34 +362,10 @@ export function collectHomepages(contentBase, ctx) {
|
|
|
347
362
|
}
|
|
348
363
|
|
|
349
364
|
/**
|
|
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.
|
|
365
|
+
* Each homepage's frontmatter as its page publishes it, and its body.
|
|
360
366
|
*
|
|
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
367
|
* @param {readonly object[]} pages - From {@link collectHomepages}.
|
|
376
|
-
* @param {object} config - The resolved configuration
|
|
377
|
-
* the default title.
|
|
368
|
+
* @param {object} config - The resolved configuration.
|
|
378
369
|
* @param {object} [options] - Options.
|
|
379
370
|
* @param {import("./related-pages.mjs").Related} [options.related] - The
|
|
380
371
|
* homepage's backlinks and mentions. Absent where nothing connects to it,
|
|
@@ -383,15 +374,16 @@ export function collectHomepages(contentBase, ctx) {
|
|
|
383
374
|
* address resolves against the images this package ships.
|
|
384
375
|
* @param {object} [options.foreign] - The dependencies' vendored indexes, so a
|
|
385
376
|
* `data.banner` may name an image a dependency ships.
|
|
386
|
-
* @returns {
|
|
377
|
+
* @returns {Array<{file: string, body: string, data: object}>} One entry per
|
|
378
|
+
* homepage, in walk order.
|
|
387
379
|
*/
|
|
388
|
-
export function
|
|
380
|
+
export function homepageData(pages, config, { related, records, foreign } = {}) {
|
|
389
381
|
const artIndex = assetAddressIndex(records ?? [], { config, foreign });
|
|
390
382
|
// A pathname no asset host serves resolves to `null`, which the theme reads
|
|
391
383
|
// as an unset banner rather than as a URL to nowhere.
|
|
392
384
|
const webSrc = (src) => resolvePathname(src, config)?.web ?? null;
|
|
393
385
|
const artSrc = (value, type, accepts) => artPathname(artIndex, value, type, accepts).pathname;
|
|
394
|
-
|
|
386
|
+
return pages.map((page) => {
|
|
395
387
|
const data = homepageFrontmatter(page.fm, {
|
|
396
388
|
contentPackage: config.contentPackage,
|
|
397
389
|
title: homepageTitle(page.fm, config),
|
|
@@ -399,11 +391,8 @@ export function writeHomepages(outRoot, pages, config, { related, records, forei
|
|
|
399
391
|
});
|
|
400
392
|
if (isPlainObject(data.data)) data.data = { ...data.data };
|
|
401
393
|
resolveArtFields(data, webSrc, artSrc);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
fs.writeFileSync(dest, writeMatter(page.body, encodeAddresses(data)));
|
|
405
|
-
}
|
|
406
|
-
return pages.length;
|
|
394
|
+
return { file: page.file, body: page.body, data };
|
|
395
|
+
});
|
|
407
396
|
}
|
|
408
397
|
|
|
409
398
|
/**
|
|
@@ -442,13 +431,14 @@ export function siteGates(pages, findings, { config, records, sqlTables }) {
|
|
|
442
431
|
homepages: [],
|
|
443
432
|
frontmatterLinks: findings.fmLinkFindings ?? [],
|
|
444
433
|
addressErrors: findings.addressFindings ?? [],
|
|
434
|
+
sections: findings.sectionFindings ?? [],
|
|
445
435
|
staleManifests: [],
|
|
446
436
|
unaddressable: [],
|
|
447
437
|
index: null,
|
|
448
438
|
foreign: null,
|
|
449
439
|
manifests: null,
|
|
450
440
|
};
|
|
451
|
-
if (out.frontmatterLinks.length || out.addressErrors.length) return out;
|
|
441
|
+
if (out.frontmatterLinks.length || out.addressErrors.length || out.sections.length) return out;
|
|
452
442
|
|
|
453
443
|
const content = pages.filter((p) => p.kind === "content");
|
|
454
444
|
// Which packages are *local* is what decides which manifests are foreign,
|
|
@@ -503,6 +493,7 @@ export function emptyGates() {
|
|
|
503
493
|
homepages: [],
|
|
504
494
|
frontmatterLinks: [],
|
|
505
495
|
addressErrors: [],
|
|
496
|
+
sections: [],
|
|
506
497
|
staleManifests: [],
|
|
507
498
|
unaddressable: [],
|
|
508
499
|
index: null,
|
|
@@ -514,7 +505,7 @@ export function emptyGates() {
|
|
|
514
505
|
/**
|
|
515
506
|
* Whether any gate produced a finding.
|
|
516
507
|
* @param {{homepages: unknown[], frontmatterLinks: unknown[],
|
|
517
|
-
* addressErrors: unknown[], staleManifests: unknown[],
|
|
508
|
+
* addressErrors: unknown[], sections?: unknown[], staleManifests: unknown[],
|
|
518
509
|
* unaddressable: unknown[]}} gates - Results of the site checks.
|
|
519
510
|
* @returns {boolean} Whether at least one check found an error.
|
|
520
511
|
*/
|
|
@@ -523,11 +514,38 @@ export function gatesFailed(gates) {
|
|
|
523
514
|
gates.homepages.length ||
|
|
524
515
|
gates.frontmatterLinks.length ||
|
|
525
516
|
gates.addressErrors.length ||
|
|
517
|
+
gates.sections?.length ||
|
|
526
518
|
gates.staleManifests.length ||
|
|
527
519
|
gates.unaddressable.length,
|
|
528
520
|
);
|
|
529
521
|
}
|
|
530
522
|
|
|
523
|
+
/**
|
|
524
|
+
* Whether a site build found anything that stops its pages being written: a
|
|
525
|
+
* gate that fired, or a note whose table, expression, block, caption, heading,
|
|
526
|
+
* footnote, link, image or embed is in error. What drawing the maps found does
|
|
527
|
+
* not stop it.
|
|
528
|
+
*
|
|
529
|
+
* @param {object} result - From {@link buildSite}.
|
|
530
|
+
* @returns {boolean} Whether the build failed.
|
|
531
|
+
*/
|
|
532
|
+
export function siteBuildFailed(result) {
|
|
533
|
+
return (
|
|
534
|
+
gatesFailed(result.gates) ||
|
|
535
|
+
[
|
|
536
|
+
result.tableErrors,
|
|
537
|
+
result.expressionErrors,
|
|
538
|
+
result.secretErrors,
|
|
539
|
+
result.captionErrors,
|
|
540
|
+
result.headingErrors,
|
|
541
|
+
result.footnoteErrors,
|
|
542
|
+
result.wikiErrors,
|
|
543
|
+
result.imageErrors,
|
|
544
|
+
result.embedErrors,
|
|
545
|
+
].some((list) => list?.length)
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
531
549
|
/**
|
|
532
550
|
* The universe a generated table searches, grouped by package.
|
|
533
551
|
*
|
|
@@ -559,39 +577,19 @@ export function tableUniverse(pages) {
|
|
|
559
577
|
/**
|
|
560
578
|
* The frontmatter a page publishes with.
|
|
561
579
|
*
|
|
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>/`.
|
|
580
|
+
* An authored `aliases` is a list of names a reader might call the note. It is
|
|
581
|
+
* dropped: this build emits no redirects.
|
|
582
582
|
*
|
|
583
583
|
* 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
|
|
584
|
+
* declares one, so the note's frontmatter alone would publish a page that does
|
|
585
|
+
* not say which package it belongs to. Writing the derived value keeps a page
|
|
588
586
|
* self-describing and makes sweeping the field out of a content tree
|
|
589
587
|
* output-preserving for a site as it already is for the packs.
|
|
590
588
|
*
|
|
591
589
|
* **`related` is derived, never authored.** What links to a page is a fact
|
|
592
590
|
* about every other page, known only once the whole tree has resolved — see
|
|
593
591
|
* {@link module:engine/related-pages} — so an authored value is dropped the
|
|
594
|
-
* way `aliases` is, and {@link renderPages}
|
|
592
|
+
* way `aliases` is, and {@link renderPages} sets the derived block once it
|
|
595
593
|
* holds the graph.
|
|
596
594
|
*
|
|
597
595
|
* @param {object} page - The page.
|
|
@@ -605,7 +603,7 @@ export function tableUniverse(pages) {
|
|
|
605
603
|
* pathname into the address the website serves. Every artwork field goes
|
|
606
604
|
* through it, so a page's `img:` and its body images name the same file the
|
|
607
605
|
* same way.
|
|
608
|
-
* @returns {object} The frontmatter
|
|
606
|
+
* @returns {object} The frontmatter.
|
|
609
607
|
*/
|
|
610
608
|
export function pageFrontmatter(page, { decorate, webSrc, artSrc }) {
|
|
611
609
|
const { fm, slug } = page;
|
|
@@ -615,12 +613,7 @@ export function pageFrontmatter(page, { decorate, webSrc, artSrc }) {
|
|
|
615
613
|
// Spread after the note's own frontmatter. Guarded because
|
|
616
614
|
// `package: undefined` is not a value YAML can carry.
|
|
617
615
|
...(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.
|
|
616
|
+
// The address, stated site-root relative; `slug` is its last segment.
|
|
624
617
|
slug,
|
|
625
618
|
url: `/${slug}/`,
|
|
626
619
|
title: pageTitle(page),
|
|
@@ -692,37 +685,14 @@ function isPlainObject(value) {
|
|
|
692
685
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
693
686
|
}
|
|
694
687
|
|
|
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. */
|
|
688
|
+
/** Transform one content page. */
|
|
717
689
|
export function renderSitePage(
|
|
718
690
|
page,
|
|
719
691
|
{
|
|
720
692
|
index,
|
|
721
693
|
foreign,
|
|
722
|
-
universe,
|
|
723
694
|
pass = {},
|
|
724
695
|
decorate,
|
|
725
|
-
linkable = (d) => Boolean(d.fm.shortcode),
|
|
726
696
|
sqlTables,
|
|
727
697
|
config,
|
|
728
698
|
artIndex,
|
|
@@ -732,26 +702,13 @@ export function renderSitePage(
|
|
|
732
702
|
runs = runContext(config),
|
|
733
703
|
},
|
|
734
704
|
) {
|
|
735
|
-
const tableErrors = [];
|
|
736
|
-
const expressionErrors = [];
|
|
737
705
|
const secretErrors = [];
|
|
738
706
|
const captionErrors = [];
|
|
739
707
|
const headingErrors = [];
|
|
740
708
|
const footnoteErrors = [];
|
|
741
|
-
const wikiErrors = [];
|
|
742
709
|
const imageErrors = [];
|
|
743
710
|
const embedErrors = [];
|
|
744
|
-
const resolved = [];
|
|
745
711
|
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
712
|
|
|
756
713
|
const seenImages = new Map();
|
|
757
714
|
const webSrc = (src) => {
|
|
@@ -777,24 +734,10 @@ export function renderSitePage(
|
|
|
777
734
|
return src;
|
|
778
735
|
};
|
|
779
736
|
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.
|
|
737
|
+
// The role lookup a captioned item's `map` counter reaches through — see
|
|
738
|
+
// `engine/content-figures.mjs`'s `resolveRole`. It numbers the page's
|
|
739
|
+
// figures once, in expansion, and the render below reads that numbering.
|
|
789
740
|
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
741
|
// The picture's role and pixel size, by the address it resolved to — read
|
|
799
742
|
// before `webSrc` translates it to the page's own host. A body image is
|
|
800
743
|
// free to write the bare, own-package form, so the address is normalized
|
|
@@ -803,11 +746,6 @@ export function renderSitePage(
|
|
|
803
746
|
const key = assetPathnameKey(src, config);
|
|
804
747
|
return key ? artIndex?.byPath?.get(key) : undefined;
|
|
805
748
|
};
|
|
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
749
|
// A generated **From here** section names the drawing the build made, which
|
|
812
750
|
// the asset host does not serve: it is set inline, so its place names stay
|
|
813
751
|
// links. A drawing this build did not make is left for the image pass,
|
|
@@ -820,74 +758,34 @@ export function renderSitePage(
|
|
|
820
758
|
return inlineDrawingFigure(fs.readFileSync(drawing.file, "utf8"), alt);
|
|
821
759
|
});
|
|
822
760
|
|
|
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),
|
|
761
|
+
// The frontmatter's art resolves before the body's pictures, so a picture
|
|
762
|
+
// named in both is counted in that order.
|
|
763
|
+
const data = pageFrontmatter(page, { decorate, webSrc, artSrc });
|
|
764
|
+
const expanded = expandContent(page.body, {
|
|
765
|
+
target: "web",
|
|
766
|
+
index,
|
|
767
|
+
note: { fm: page.fm, file: page.file, source: src, bodyLine: page.bodyLine },
|
|
768
|
+
prepared: {
|
|
769
|
+
sqlTables: sqlTables?.get(page.file),
|
|
770
|
+
pageLists: sqlTables?.pageLists?.get(page.file),
|
|
771
|
+
inline: sqlTables?.inline?.get(page.file),
|
|
772
|
+
},
|
|
857
773
|
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
|
-
}),
|
|
774
|
+
resolveRole,
|
|
775
|
+
figuresByAddress,
|
|
776
|
+
links: {
|
|
777
|
+
foreign: foreign.index,
|
|
778
|
+
assets: artIndex,
|
|
779
|
+
beforeLinks: pass.beforeLinks ? (text) => pass.beforeLinks(text, page) : undefined,
|
|
780
|
+
afterLinks: (text) => renderImageFigures(inlineDrawings(text), webSrc, lookupAsset),
|
|
872
781
|
},
|
|
873
782
|
});
|
|
874
|
-
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
const
|
|
880
|
-
const authoredBody = protectCode(authored, resolve);
|
|
881
|
-
const authoredLinks = resolved.length;
|
|
882
|
-
const resolvedBody =
|
|
883
|
-
generated ? `${authoredBody}\n${protectCode(generated, resolve)}` : authoredBody;
|
|
884
|
-
const figured = renderFigureBlocks(
|
|
885
|
-
renderRuns(resolvedBody, runs, undefined, { target: "site" }).markdown,
|
|
886
|
-
undefined,
|
|
887
|
-
undefined,
|
|
888
|
-
{ resolveRole: (address) => roleByWebSrc.get(address) },
|
|
889
|
-
);
|
|
890
|
-
for (const error of figureScan.errors)
|
|
783
|
+
const { lineMap } = expanded;
|
|
784
|
+
const tableErrors = expanded.findings.tables;
|
|
785
|
+
const expressionErrors = expanded.findings.expressions;
|
|
786
|
+
const wikiErrors = expanded.findings.links;
|
|
787
|
+
const resolvedBody = expanded.markdown;
|
|
788
|
+
for (const error of expanded.findings.captions)
|
|
891
789
|
captionErrors.push({
|
|
892
790
|
file: page.file,
|
|
893
791
|
line: (page.bodyLine ?? 1) + (lineMap[error.line - 1]?.line ?? error.line - 1),
|
|
@@ -926,15 +824,15 @@ export function renderSitePage(
|
|
|
926
824
|
embedErrors.push(error);
|
|
927
825
|
return {
|
|
928
826
|
page,
|
|
929
|
-
// The
|
|
930
|
-
//
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
renderSpans(renderAlerts(renderBlocks(figured.markdown, "web").markdown).markdown)
|
|
934
|
-
.markdown,
|
|
935
|
-
),
|
|
827
|
+
// The expanded body and its numbered figures, for the renderer that
|
|
828
|
+
// writes the whole page.
|
|
829
|
+
markdown: resolvedBody,
|
|
830
|
+
figures: expanded.figures.figures,
|
|
936
831
|
data,
|
|
937
|
-
|
|
832
|
+
// The links a generated section makes join no page's Related card:
|
|
833
|
+
// that card is what the authors link, and the sections already list
|
|
834
|
+
// what they derive.
|
|
835
|
+
resolved: expanded.resolved.slice(0, expanded.authoredLinks),
|
|
938
836
|
tableErrors,
|
|
939
837
|
expressionErrors,
|
|
940
838
|
secretErrors,
|
|
@@ -948,24 +846,20 @@ export function renderSitePage(
|
|
|
948
846
|
}
|
|
949
847
|
|
|
950
848
|
/**
|
|
951
|
-
* Renders
|
|
952
|
-
*
|
|
953
|
-
* The order inside a page is load-bearing and is the same order the pack
|
|
954
|
-
* compilers use:
|
|
849
|
+
* Renders every page.
|
|
955
850
|
*
|
|
956
|
-
*
|
|
957
|
-
*
|
|
958
|
-
*
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
963
|
-
* rather than replacing it.
|
|
851
|
+
* Each page's body is expanded by
|
|
852
|
+
* {@link module:engine/content-expand.expandContent}, the step the pack
|
|
853
|
+
* compilers take too: tables first and outside code-fence protection, then
|
|
854
|
+
* figure numbers and expressions, then, inside protection, the consumer's
|
|
855
|
+
* `beforeLinks` pass and wikilink resolution. A `{@link}` tag may sit in prose
|
|
856
|
+
* a wikilink also touches, so the repository's own rewrite runs before the
|
|
857
|
+
* shared one rather than replacing it.
|
|
964
858
|
*
|
|
965
|
-
* **Every page is rendered before any is
|
|
966
|
-
* known only once every other page has resolved, and it is
|
|
967
|
-
*
|
|
968
|
-
*
|
|
859
|
+
* **Every page is rendered before any is finished.** What links *to* a page is
|
|
860
|
+
* known only once every other page has resolved, and it is set in the page's
|
|
861
|
+
* own front matter, so the render is one pass over the tree and the
|
|
862
|
+
* finishing is a second — with the link graph read off the first, at the one
|
|
969
863
|
* point the resolver answers, and inverted once between them. Nothing is
|
|
970
864
|
* resolved twice.
|
|
971
865
|
*
|
|
@@ -982,14 +876,15 @@ export function renderSitePage(
|
|
|
982
876
|
* @returns {{written: number, byKind: Record<string, number>, tableErrors: object[],
|
|
983
877
|
* wikiErrors: object[], imageErrors: object[], embedErrors: object[],
|
|
984
878
|
* related: Map<string, import("./related-pages.mjs").Related>,
|
|
985
|
-
* maps: number}} `related`
|
|
879
|
+
* maps: number, pages: object[], runs: object}} `related`
|
|
986
880
|
* 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.
|
|
881
|
+
* page's, so the caller can set it on the page this render does not.
|
|
882
|
+
* `maps` counts the pages carrying a map. `pages` holds
|
|
883
|
+
* `{page, markdown, figures, data}` for each page and `runs` the package's
|
|
884
|
+
* marked-text context.
|
|
989
885
|
*/
|
|
990
886
|
export function renderPages(pages, options) {
|
|
991
887
|
const {
|
|
992
|
-
outRoot,
|
|
993
888
|
index,
|
|
994
889
|
foreign,
|
|
995
890
|
universe,
|
|
@@ -1001,8 +896,6 @@ export function renderPages(pages, options) {
|
|
|
1001
896
|
records = [],
|
|
1002
897
|
homepages = [],
|
|
1003
898
|
maps = new Map(),
|
|
1004
|
-
write = true,
|
|
1005
|
-
capture = false,
|
|
1006
899
|
} = options;
|
|
1007
900
|
|
|
1008
901
|
// The address space the art slots and the body's embeds resolve against:
|
|
@@ -1033,7 +926,7 @@ export function renderPages(pages, options) {
|
|
|
1033
926
|
self: { fm: searchableFrontmatter(page.fm, page.pkg), path: page.relPath },
|
|
1034
927
|
});
|
|
1035
928
|
const byId = new Map(
|
|
1036
|
-
scanFigures(markdown)
|
|
929
|
+
scanFigures(markdown, { resolveRole: (address) => embedRole(artIndex, address) })
|
|
1037
930
|
.figures.filter((figure) => figure.id)
|
|
1038
931
|
.map((figure) => [
|
|
1039
932
|
figure.id,
|
|
@@ -1075,6 +968,7 @@ export function renderPages(pages, options) {
|
|
|
1075
968
|
title: pageTitle(page),
|
|
1076
969
|
url: page.url,
|
|
1077
970
|
type: String(page.fm.type),
|
|
971
|
+
...(page.fm.subType ? { subType: String(page.fm.subType) } : {}),
|
|
1078
972
|
...(typeof page.fm.name?.sort === "string" && page.fm.name.sort ?
|
|
1079
973
|
{ sort: page.fm.name.sort }
|
|
1080
974
|
: {}),
|
|
@@ -1087,11 +981,12 @@ export function renderPages(pages, options) {
|
|
|
1087
981
|
}
|
|
1088
982
|
}
|
|
1089
983
|
// What each page renders as, held until the graph is whole.
|
|
1090
|
-
/** @type {Array<{page: object,
|
|
984
|
+
/** @type {Array<{page: object, markdown: string, figures: object[], data: object}>} */
|
|
1091
985
|
const rendered = [];
|
|
986
|
+
const runs = runContext(config, records);
|
|
1092
987
|
|
|
1093
|
-
|
|
1094
|
-
|
|
988
|
+
const renderOne = (page) =>
|
|
989
|
+
renderSitePage(page, {
|
|
1095
990
|
index,
|
|
1096
991
|
foreign,
|
|
1097
992
|
universe,
|
|
@@ -1104,8 +999,10 @@ export function renderPages(pages, options) {
|
|
|
1104
999
|
figuresByAddress,
|
|
1105
1000
|
events,
|
|
1106
1001
|
drawings: maps,
|
|
1107
|
-
runs
|
|
1002
|
+
runs,
|
|
1108
1003
|
});
|
|
1004
|
+
for (const page of pages) {
|
|
1005
|
+
const result = renderOne(page);
|
|
1109
1006
|
tableErrors.push(...result.tableErrors);
|
|
1110
1007
|
expressionErrors.push(...result.expressionErrors);
|
|
1111
1008
|
secretErrors.push(...result.secretErrors);
|
|
@@ -1115,7 +1012,12 @@ export function renderPages(pages, options) {
|
|
|
1115
1012
|
wikiErrors.push(...result.wikiErrors);
|
|
1116
1013
|
imageErrors.push(...result.imageErrors);
|
|
1117
1014
|
embedErrors.push(...result.embedErrors);
|
|
1118
|
-
rendered.push({
|
|
1015
|
+
rendered.push({
|
|
1016
|
+
page,
|
|
1017
|
+
markdown: result.markdown,
|
|
1018
|
+
figures: result.figures,
|
|
1019
|
+
data: result.data,
|
|
1020
|
+
});
|
|
1119
1021
|
for (const hit of result.resolved) if (hit.url) edges.push([page.url, hit.url]);
|
|
1120
1022
|
}
|
|
1121
1023
|
|
|
@@ -1125,17 +1027,10 @@ export function renderPages(pages, options) {
|
|
|
1125
1027
|
const related = relatedPages(edges, entries, deliberate);
|
|
1126
1028
|
|
|
1127
1029
|
let withMap = 0;
|
|
1128
|
-
const
|
|
1129
|
-
for (const { page, body, data } of rendered) {
|
|
1030
|
+
for (const { page, data } of rendered) {
|
|
1130
1031
|
const block = related.get(page.url);
|
|
1131
1032
|
if (block) data.related = block;
|
|
1132
1033
|
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
1034
|
byKind[page.kind] = (byKind[page.kind] ?? 0) + 1;
|
|
1140
1035
|
}
|
|
1141
1036
|
|
|
@@ -1153,7 +1048,21 @@ export function renderPages(pages, options) {
|
|
|
1153
1048
|
embedErrors,
|
|
1154
1049
|
related,
|
|
1155
1050
|
maps: withMap,
|
|
1156
|
-
|
|
1051
|
+
pages: rendered.map(({ page, markdown, figures, data }) => ({
|
|
1052
|
+
page,
|
|
1053
|
+
markdown,
|
|
1054
|
+
figures,
|
|
1055
|
+
data,
|
|
1056
|
+
})),
|
|
1057
|
+
runs,
|
|
1058
|
+
// One page rendered again with everything the whole render resolved
|
|
1059
|
+
// against, its place in the link graph included.
|
|
1060
|
+
renderAgain: (page) => {
|
|
1061
|
+
const result = renderOne(page);
|
|
1062
|
+
const block = related.get(page.url);
|
|
1063
|
+
if (block) result.data.related = block;
|
|
1064
|
+
return result;
|
|
1065
|
+
},
|
|
1157
1066
|
};
|
|
1158
1067
|
}
|
|
1159
1068
|
|
|
@@ -1211,7 +1120,7 @@ export function sitePageDecorator(config, index) {
|
|
|
1211
1120
|
}
|
|
1212
1121
|
|
|
1213
1122
|
/**
|
|
1214
|
-
*
|
|
1123
|
+
* Collects, checks and renders a content tree's pages, and reports what it found.
|
|
1215
1124
|
*
|
|
1216
1125
|
* Returns rather than exits, in every case. A caller — the command, or a test —
|
|
1217
1126
|
* decides what a finding means; the gates below are grouped so it can report
|
|
@@ -1229,8 +1138,13 @@ export function sitePageDecorator(config, index) {
|
|
|
1229
1138
|
* @returns {{gates: object, stats: object|null, tableErrors: object[],
|
|
1230
1139
|
* wikiErrors: object[], imageErrors: object[], embedErrors: object[],
|
|
1231
1140
|
* mapFindings: object[],
|
|
1232
|
-
* manifests: object|null}} `mapFindings` is what drawing the
|
|
1233
|
-
* warnings, never a reason to fail the build.
|
|
1141
|
+
* manifests: object|null, site?: object}} `mapFindings` is what drawing the
|
|
1142
|
+
* maps found — warnings, never a reason to fail the build. `site` holds what a page renderer needs: `base`, `pages` (each content
|
|
1143
|
+
* page's `{page, markdown, figures, data}`), `homepages` (each
|
|
1144
|
+
* `{file, body, data}`), `runs`, `index`, `foreign` and `records`; and
|
|
1145
|
+
* `renderNote(file)`, which reads one content note again and renders it
|
|
1146
|
+
* against the rest of the build, or returns `null` when its address, name
|
|
1147
|
+
* or type changed and the whole site must be built again.
|
|
1234
1148
|
*/
|
|
1235
1149
|
export function buildSite({ config, sqlTables } = {}) {
|
|
1236
1150
|
const resolved = config ?? loadPackConfig();
|
|
@@ -1244,29 +1158,9 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1244
1158
|
// that publishes it, and `prefix` is the content tree's mount within the
|
|
1245
1159
|
// package — the same `prefix` the link manifest records against, so a page
|
|
1246
1160
|
// and its manifest entry cannot disagree.
|
|
1247
|
-
const base =
|
|
1161
|
+
const base = siteBase(resolved);
|
|
1248
1162
|
const mount = `${base}${scheme.prefix}`;
|
|
1249
1163
|
|
|
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
1164
|
const packages = new Set(site.packages.length ? site.packages : [resolved.contentPackage]);
|
|
1271
1165
|
|
|
1272
1166
|
const ctx = {
|
|
@@ -1290,6 +1184,11 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1290
1184
|
base,
|
|
1291
1185
|
mount,
|
|
1292
1186
|
scheme,
|
|
1187
|
+
// The notes `site.sections` names, by address, each with the section
|
|
1188
|
+
// it is the page of.
|
|
1189
|
+
sectionPages: new Map(
|
|
1190
|
+
Object.entries(site.sections ?? {}).map(([name, address]) => [address, name]),
|
|
1191
|
+
),
|
|
1293
1192
|
};
|
|
1294
1193
|
|
|
1295
1194
|
const collected = collectHomepages(resolved.paths.content, ctx);
|
|
@@ -1333,10 +1232,6 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1333
1232
|
};
|
|
1334
1233
|
}
|
|
1335
1234
|
|
|
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
1235
|
// Homepage-only stops here, and stopping is the point: nothing below reads
|
|
1341
1236
|
// the content tree for pages, so `sohl-kethira-basic` and `harn-adventures`
|
|
1342
1237
|
// cannot publish one whatever else their `site:` block declares.
|
|
@@ -1354,15 +1249,22 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1354
1249
|
imageErrors: [],
|
|
1355
1250
|
embedErrors: [],
|
|
1356
1251
|
mapFindings: [],
|
|
1357
|
-
stats: {
|
|
1358
|
-
|
|
1359
|
-
|
|
1252
|
+
stats: { homepages: homepages.length },
|
|
1253
|
+
site: {
|
|
1254
|
+
base,
|
|
1255
|
+
pages: [],
|
|
1256
|
+
homepages: homepageData(homepages, resolved, { records: ctx.records }),
|
|
1257
|
+
runs: runContext(resolved, ctx.records),
|
|
1258
|
+
index: null,
|
|
1259
|
+
foreign: null,
|
|
1260
|
+
records: ctx.records,
|
|
1261
|
+
renderNote: () => null,
|
|
1360
1262
|
},
|
|
1361
1263
|
};
|
|
1362
1264
|
}
|
|
1363
|
-
|
|
1364
1265
|
const content = collectContentPages(resolved.paths.content, ctx);
|
|
1365
1266
|
const { pages } = content;
|
|
1267
|
+
content.sectionFindings = unmatchedSectionPages(site.sections ?? {}, pages);
|
|
1366
1268
|
|
|
1367
1269
|
// The homepage is **indexed but not rendered**. `[[homepage-root|Text]]`
|
|
1368
1270
|
// is an ordinary wikilink and has to resolve to the page the build
|
|
@@ -1429,7 +1331,6 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1429
1331
|
});
|
|
1430
1332
|
|
|
1431
1333
|
const rendered = renderPages(pages, {
|
|
1432
|
-
outRoot: out,
|
|
1433
1334
|
sqlTables,
|
|
1434
1335
|
config: resolved,
|
|
1435
1336
|
records: ctx.records,
|
|
@@ -1443,14 +1344,14 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1443
1344
|
decorate: sitePageDecorator(resolved, gates.index),
|
|
1444
1345
|
});
|
|
1445
1346
|
|
|
1446
|
-
//
|
|
1447
|
-
//
|
|
1448
|
-
//
|
|
1449
|
-
const
|
|
1347
|
+
// The package's front page is not part of the content tree it
|
|
1348
|
+
// introduces. It publishes at `base`, which is where the graph keys its
|
|
1349
|
+
// block.
|
|
1350
|
+
const homepageOptions = {
|
|
1450
1351
|
related: rendered.related.get(base),
|
|
1451
1352
|
records: ctx.records,
|
|
1452
1353
|
foreign: gates.foreign,
|
|
1453
|
-
}
|
|
1354
|
+
};
|
|
1454
1355
|
|
|
1455
1356
|
return {
|
|
1456
1357
|
gates,
|
|
@@ -1466,9 +1367,53 @@ export function buildSite({ config, sqlTables } = {}) {
|
|
|
1466
1367
|
mapFindings: drawn.findings,
|
|
1467
1368
|
stats: {
|
|
1468
1369
|
...rendered.byKind,
|
|
1469
|
-
homepages:
|
|
1370
|
+
homepages: homepages.length,
|
|
1470
1371
|
maps: rendered.maps,
|
|
1471
|
-
|
|
1372
|
+
},
|
|
1373
|
+
site: {
|
|
1374
|
+
base,
|
|
1375
|
+
pages: rendered.pages,
|
|
1376
|
+
homepages: homepageData(homepages, resolved, homepageOptions),
|
|
1377
|
+
runs: rendered.runs,
|
|
1378
|
+
index: gates.index,
|
|
1379
|
+
foreign: gates.foreign,
|
|
1380
|
+
records: ctx.records,
|
|
1381
|
+
renderNote: (file) => {
|
|
1382
|
+
const before = pages.find((page) => page.file === file);
|
|
1383
|
+
const after = collectContentPages(resolved.paths.content, {
|
|
1384
|
+
...ctx,
|
|
1385
|
+
only: file,
|
|
1386
|
+
}).pages[0];
|
|
1387
|
+
// A note with a new address, name or type, or a new
|
|
1388
|
+
// note, changes what other pages resolve to, which
|
|
1389
|
+
// only a whole build answers.
|
|
1390
|
+
if (
|
|
1391
|
+
!before ||
|
|
1392
|
+
!after ||
|
|
1393
|
+
after.url !== before.url ||
|
|
1394
|
+
after.name !== before.name ||
|
|
1395
|
+
String(after.fm.type) !== String(before.fm.type)
|
|
1396
|
+
)
|
|
1397
|
+
return null;
|
|
1398
|
+
const result = rendered.renderAgain(after);
|
|
1399
|
+
return {
|
|
1400
|
+
page: after,
|
|
1401
|
+
markdown: result.markdown,
|
|
1402
|
+
figures: result.figures,
|
|
1403
|
+
data: result.data,
|
|
1404
|
+
findings: [
|
|
1405
|
+
...result.tableErrors,
|
|
1406
|
+
...result.expressionErrors,
|
|
1407
|
+
...result.secretErrors,
|
|
1408
|
+
...result.captionErrors,
|
|
1409
|
+
...result.headingErrors,
|
|
1410
|
+
...result.footnoteErrors,
|
|
1411
|
+
...result.wikiErrors,
|
|
1412
|
+
...result.imageErrors,
|
|
1413
|
+
...result.embedErrors,
|
|
1414
|
+
],
|
|
1415
|
+
};
|
|
1416
|
+
},
|
|
1472
1417
|
},
|
|
1473
1418
|
};
|
|
1474
1419
|
}
|