@heroiclands/package-build 23.12.8 → 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 +84 -0
- package/README.md +3 -2
- package/assets/preview/gitea.ini +12 -7
- package/assets/styles/content-base.css +293 -24
- package/bin/content-commands.mjs +370 -213
- package/bin/package-build-preview.mjs +2 -2
- 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 +110 -89
- package/docs/authoring/links-and-markup.md +469 -97
- package/docs/commands.md +259 -78
- package/docs/configuration.md +211 -103
- package/docs/content-format.md +4 -4
- package/docs/diagnostics.md +44 -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 +677 -319
- package/docs/reference/note-types.md +47 -10
- package/docs/reference/theme-pages.md +178 -108
- package/docs/source-preview.md +16 -2
- package/engine/anchors.mjs +2 -2
- package/engine/asset-shipping.mjs +22 -10
- package/engine/base-compiler.mjs +62 -104
- package/engine/changelog-lint.mjs +1 -1
- package/engine/code-fences.mjs +3 -3
- package/engine/content-address.mjs +1 -1
- package/engine/content-alerts.mjs +8 -4
- package/engine/content-blocks.mjs +29 -14
- package/engine/content-embeds.mjs +1 -1
- package/engine/content-expand.mjs +376 -0
- package/engine/content-figures.mjs +18 -11
- package/engine/content-format.yaml +8 -0
- package/engine/content-glyphs.mjs +213 -0
- package/engine/content-images.mjs +6 -7
- package/engine/content-index.mjs +1 -1
- 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 +376 -43
- package/engine/content-spans.mjs +6 -3
- package/engine/content-styles.mjs +91 -0
- package/engine/content-symbols.mjs +186 -0
- package/engine/content-verse.mjs +738 -0
- package/engine/core-report.mjs +169 -0
- package/engine/core-vocabulary.mjs +208 -0
- package/engine/dictionary-html.mjs +119 -0
- package/engine/dictionary-rows.mjs +15 -4
- package/engine/field-reference.mjs +5 -5
- package/engine/first-use.mjs +17 -27
- 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/key-case-collisions.mjs +136 -0
- package/engine/language-data.mjs +203 -6
- package/engine/language-rows.mjs +8 -2
- package/engine/language-terms.mjs +186 -16
- package/engine/language-words.mjs +12 -0
- package/engine/logographic.mjs +255 -0
- package/engine/map-lexicon.mjs +262 -47
- 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/names.mjs +36 -9
- package/engine/note-matter.mjs +66 -0
- package/engine/note-vocabulary.mjs +187 -3
- package/engine/pdf-build.mjs +69 -133
- package/engine/pdf-render.mjs +205 -58
- 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/read-stdin.mjs +29 -0
- package/engine/run-rows.mjs +2 -2
- package/engine/run-words.mjs +6 -112
- 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 +260 -315
- 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 +52 -525
- 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/sql-tables.mjs +20 -3
- 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 +364 -0
- package/engine/word-rows.mjs +12 -2
- package/engine/word-terms.mjs +38 -4
- package/manifest.mjs +3 -4
- package/package.json +11 -8
- 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 +106 -14
- 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/code-fences.d.mts +3 -3
- 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 +70 -4
- 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/content-verse.d.mts +240 -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/dictionary-rows.d.mts +10 -0
- package/types/engine/field-reference.d.mts +2 -2
- 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/key-case-collisions.d.mts +28 -0
- package/types/engine/language-data.d.mts +0 -2
- package/types/engine/language-terms.d.mts +24 -2
- package/types/engine/logographic.d.mts +45 -0
- package/types/engine/map-lexicon.d.mts +66 -13
- 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/names.d.mts +34 -3
- package/types/engine/note-matter.d.mts +48 -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/read-stdin.d.mts +11 -0
- package/types/engine/run-rows.d.mts +2 -2
- package/types/engine/run-words.d.mts +0 -15
- 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/sql-tables.d.mts +4 -1
- 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 +83 -0
- 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/content-poetry.mjs +0 -240
- 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/content-poetry.d.mts +0 -58
- 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/base-compiler.mjs
CHANGED
|
@@ -68,10 +68,10 @@ import { scanSpans } from "./content-spans.mjs";
|
|
|
68
68
|
import { scanBlocks } from "./content-blocks.mjs";
|
|
69
69
|
import { scanRuns } from "./content-scripts.mjs";
|
|
70
70
|
import { scanHeadingAttributes, withheldSections } from "./heading-attributes.mjs";
|
|
71
|
-
import { scanFigures } from "./content-figures.mjs";
|
|
72
71
|
import { footnoteFindings } from "./content-footnotes.mjs";
|
|
73
72
|
import { checkImages } from "./content-images.mjs";
|
|
74
|
-
import {
|
|
73
|
+
import { embedRole } from "./content-embeds.mjs";
|
|
74
|
+
import { expandContent } from "./content-expand.mjs";
|
|
75
75
|
import { eventNoteIndex } from "./event-fields.mjs";
|
|
76
76
|
import { isGmNote } from "./note-vocabulary.mjs";
|
|
77
77
|
import { cloneAddressState } from "./address-values.mjs";
|
|
@@ -84,8 +84,6 @@ import {
|
|
|
84
84
|
makeFilename,
|
|
85
85
|
resolveImg,
|
|
86
86
|
resolveName,
|
|
87
|
-
convertNoteWikilinks,
|
|
88
|
-
expandNoteTables,
|
|
89
87
|
statsForPack,
|
|
90
88
|
} from "./helpers.mjs";
|
|
91
89
|
// The record accessors only — see `engine/index-records.mjs` for why they live
|
|
@@ -131,25 +129,6 @@ import { annotateAssetReferences, assetReferenceLookup } from "./asset-reference
|
|
|
131
129
|
* about. Counted as errors, never as skips.
|
|
132
130
|
*/
|
|
133
131
|
|
|
134
|
-
/**
|
|
135
|
-
* A `ref` call's Foundry rendering: a same-page wikilink rather than a
|
|
136
|
-
* Markdown link, so the wikilink pass that runs after expressions render
|
|
137
|
-
* addresses it exactly as an authored `[[#anchor|Text]]` is — by the
|
|
138
|
-
* JournalEntryPage the figure's own fence became, whether that page holds
|
|
139
|
-
* only the figure or carries it alongside other prose. A Markdown link's
|
|
140
|
-
* `#anchor` fragment resolves nowhere in Foundry, which addresses a page by
|
|
141
|
-
* UUID and an anchor within a page by that page's own anchor; this is why the
|
|
142
|
-
* two surfaces do not share a renderer.
|
|
143
|
-
*
|
|
144
|
-
* @param {{anchor: string}} target - The figure's own anchor.
|
|
145
|
-
* @param {string} label - The link's text, which may itself carry Markdown.
|
|
146
|
-
* @returns {string} `[[#anchor|label]]`, with a literal `|` in the label
|
|
147
|
-
* escaped so it cannot be read as the wikilink's own separator.
|
|
148
|
-
*/
|
|
149
|
-
function foundryRefLink({ anchor }, label) {
|
|
150
|
-
return `[[#${anchor}|${label.replace(/\|/g, "\\|")}]]`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
132
|
/**
|
|
154
133
|
* The shared walk → filter → expand → convert → build → write → count loop.
|
|
155
134
|
*
|
|
@@ -670,8 +649,9 @@ export class BasePackCompiler {
|
|
|
670
649
|
/**
|
|
671
650
|
* The body {@link BasePackCompiler#buildEntry} receives.
|
|
672
651
|
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
652
|
+
* The body is expanded by {@link module:engine/content-expand.expandContent}
|
|
653
|
+
* for Foundry: generated tables expand before wikilinks are converted, so
|
|
654
|
+
* a cell a table emits is resolved along with the authored links.
|
|
675
655
|
*
|
|
676
656
|
* @param {object} fm - The note's frontmatter.
|
|
677
657
|
* @param {string} body - The note body, as authored.
|
|
@@ -686,6 +666,14 @@ export class BasePackCompiler {
|
|
|
686
666
|
// it made — `errors` or `findings`, never just the first — so nothing
|
|
687
667
|
// here re-scans; it only stops taking `[0]`.
|
|
688
668
|
const findings = [];
|
|
669
|
+
const bodyLine = this.currentNote?.bodyLine ?? 1;
|
|
670
|
+
const bodyColumn = this.currentNote?.bodyColumn ?? 1;
|
|
671
|
+
// A finding's body position, in the file. The parser trims the body,
|
|
672
|
+
// so only its first line is offset by the column the body starts at.
|
|
673
|
+
const inFile = ({ line, column }) => ({
|
|
674
|
+
line: bodyLine + line - 1,
|
|
675
|
+
column: line === 1 && column !== undefined ? bodyColumn + column - 1 : column,
|
|
676
|
+
});
|
|
689
677
|
const syntax = this.corpus?.syntax;
|
|
690
678
|
const scanned = syntax?.(body);
|
|
691
679
|
for (const secretError of [
|
|
@@ -695,41 +683,27 @@ export class BasePackCompiler {
|
|
|
695
683
|
...scanRuns(body, this.corpus?.runs, syntax).errors,
|
|
696
684
|
...markupAnchorFindings(body, syntax),
|
|
697
685
|
]) {
|
|
698
|
-
findings.push({
|
|
699
|
-
message: secretError.message,
|
|
700
|
-
line: (this.currentNote?.bodyLine ?? 1) + secretError.line - 1,
|
|
701
|
-
column:
|
|
702
|
-
secretError.line === 1 ?
|
|
703
|
-
(this.currentNote?.bodyColumn ?? 1)
|
|
704
|
-
: secretError.column,
|
|
705
|
-
});
|
|
686
|
+
findings.push({ message: secretError.message, ...inFile(secretError) });
|
|
706
687
|
}
|
|
707
688
|
// A heading's attribute block, and a `.secret` written where no surface
|
|
708
689
|
// can honour it. Read off the body as authored, before the tables
|
|
709
690
|
// expand, because a heading is a heading whatever a pass does with the
|
|
710
691
|
// prose under it — so a pass that converts nothing reports these too.
|
|
711
692
|
for (const headingError of [
|
|
712
|
-
...scanHeadingAttributes(body,
|
|
713
|
-
...withheldSections(body,
|
|
693
|
+
...scanHeadingAttributes(body, bodyLine).errors,
|
|
694
|
+
...withheldSections(body, bodyLine).errors,
|
|
714
695
|
]) {
|
|
715
696
|
findings.push({
|
|
716
697
|
message: headingError.message,
|
|
717
698
|
line: headingError.line,
|
|
718
699
|
column:
|
|
719
|
-
headingError.line ===
|
|
720
|
-
|
|
700
|
+
headingError.line === bodyLine ?
|
|
701
|
+
bodyColumn + headingError.column - 1
|
|
721
702
|
: headingError.column,
|
|
722
703
|
});
|
|
723
704
|
}
|
|
724
705
|
for (const footnoteError of footnoteFindings(body)) {
|
|
725
|
-
findings.push({
|
|
726
|
-
message: footnoteError.message,
|
|
727
|
-
line: (this.currentNote?.bodyLine ?? 1) + footnoteError.line - 1,
|
|
728
|
-
column:
|
|
729
|
-
footnoteError.line === 1 ?
|
|
730
|
-
(this.currentNote?.bodyColumn ?? 1)
|
|
731
|
-
: footnoteError.column,
|
|
732
|
-
});
|
|
706
|
+
findings.push({ message: footnoteError.message, ...inFile(footnoteError) });
|
|
733
707
|
}
|
|
734
708
|
// An image sharing its paragraph with other text, a bad address or a
|
|
735
709
|
// title with nowhere to draw it — the same findings `lint` reports,
|
|
@@ -737,10 +711,7 @@ export class BasePackCompiler {
|
|
|
737
711
|
// already names rather than rendering the directive as though it were
|
|
738
712
|
// absent. `checkImages` already resolves `bodyLine` and `bodyColumn`
|
|
739
713
|
// internally, so its findings need no further adjustment.
|
|
740
|
-
for (const imageError of checkImages(body, "", {
|
|
741
|
-
bodyLine: this.currentNote?.bodyLine ?? 1,
|
|
742
|
-
bodyColumn: this.currentNote?.bodyColumn ?? 1,
|
|
743
|
-
})) {
|
|
714
|
+
for (const imageError of checkImages(body, "", { bodyLine, bodyColumn })) {
|
|
744
715
|
findings.push({
|
|
745
716
|
message: imageError.message,
|
|
746
717
|
line: imageError.line,
|
|
@@ -752,69 +723,56 @@ export class BasePackCompiler {
|
|
|
752
723
|
return body;
|
|
753
724
|
}
|
|
754
725
|
const name = resolveName(fm);
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
726
|
+
// The note's own position, unset where the caller supplied none, so a
|
|
727
|
+
// link finding drops the field rather than guessing it.
|
|
728
|
+
const { absPath, bodyLine: noteLine, bodyColumn: noteColumn } = this.currentNote ?? {};
|
|
729
|
+
// A `ref`'s cross-note resolution is the web's alone: a journal
|
|
730
|
+
// addresses a page by UUID, which this pass has none of for another
|
|
731
|
+
// note.
|
|
732
|
+
const expanded = expandContent(body, {
|
|
733
|
+
target: "foundry",
|
|
734
|
+
index: this.linkIndex,
|
|
735
|
+
note: { fm, file: absPath, source: name, bodyLine: noteLine, bodyColumn: noteColumn },
|
|
736
|
+
prepared: {
|
|
737
|
+
sqlTables: absPath ? this.sqlTables?.get(absPath) : undefined,
|
|
738
|
+
pageLists: absPath ? this.sqlTables?.pageLists?.get(absPath) : undefined,
|
|
739
|
+
inline: absPath ? this.sqlTables?.inline?.get(absPath) : undefined,
|
|
740
|
+
},
|
|
741
|
+
events: this.eventNotes(),
|
|
742
|
+
// A `ref` to a map reads the number the map's own page carries.
|
|
743
|
+
resolveRole: (address) => embedRole(this.linkIndex, address),
|
|
744
|
+
links: {
|
|
745
|
+
// A `[[#slug]]` self-link addresses the source note, which has
|
|
746
|
+
// no entry in the index — so where its own documents landed
|
|
747
|
+
// has to travel with it.
|
|
748
|
+
pack: this.router?.resolveOrNull(fm, packForType(fm.type).docType),
|
|
749
|
+
docPack: this.router?.resolveOrNull(fm, "JournalEntry"),
|
|
750
|
+
},
|
|
761
751
|
});
|
|
762
|
-
|
|
763
|
-
|
|
752
|
+
// A table the preparation could not answer fails the note on its own,
|
|
753
|
+
// at the table's line.
|
|
754
|
+
const { tables } = expanded.findings;
|
|
755
|
+
if (tables.length) {
|
|
756
|
+
const err = new Error(tables.map((e) => `content table — ${e.reason}`).join("; "));
|
|
757
|
+
if (noteLine !== undefined && tables[0].line !== undefined)
|
|
758
|
+
err.position = { line: noteLine + tables[0].line };
|
|
759
|
+
throw err;
|
|
760
|
+
}
|
|
761
|
+
const { lineMap } = expanded;
|
|
762
|
+
for (const captionError of expanded.findings.captions) {
|
|
763
|
+
const authored = lineMap[captionError.line - 1]?.line ?? captionError.line - 1;
|
|
764
764
|
findings.push({
|
|
765
765
|
message: captionError.message,
|
|
766
|
-
line:
|
|
767
|
-
(bodyLine ?? 1) +
|
|
768
|
-
(lineMap[captionError.line - 1]?.line ?? captionError.line - 1),
|
|
769
|
-
column: captionError.column,
|
|
766
|
+
...inFile({ line: authored + 1, column: captionError.column }),
|
|
770
767
|
});
|
|
771
768
|
}
|
|
772
|
-
|
|
773
|
-
// id, and how a reference renders — a same-page wikilink rather than
|
|
774
|
-
// a Markdown link, so the wikilink pass below addresses it exactly as
|
|
775
|
-
// an authored `[[#anchor|Text]]` is, by the page the figure's own
|
|
776
|
-
// fence became. Cross-note resolution is omitted: a journal addresses
|
|
777
|
-
// a page by UUID, which this pass has none of for another note.
|
|
778
|
-
const figuresById = new Map(
|
|
779
|
-
figureScan.figures
|
|
780
|
-
.filter((figure) => figure.id)
|
|
781
|
-
.map((figure) => [
|
|
782
|
-
figure.id,
|
|
783
|
-
{ label: figure.label, caption: figure.caption, hasCaption: figure.hasCaption },
|
|
784
|
-
]),
|
|
785
|
-
);
|
|
786
|
-
const expressions = renderMarkdownExpressions(tabulated, {
|
|
787
|
-
fm,
|
|
788
|
-
dates: reckoningContext(this.linkIndex),
|
|
789
|
-
sqlResults: absPath ? this.sqlTables?.inline?.get(absPath) : undefined,
|
|
790
|
-
file: absPath,
|
|
791
|
-
bodyLine,
|
|
792
|
-
figures: { get: (id) => figuresById.get(id), link: foundryRefLink },
|
|
793
|
-
events: this.eventNotes(),
|
|
794
|
-
});
|
|
795
|
-
for (const finding of expressions.findings) {
|
|
769
|
+
for (const finding of expanded.findings.expressions) {
|
|
796
770
|
findings.push({ message: finding.message, line: finding.line, column: finding.column });
|
|
797
771
|
}
|
|
798
772
|
this.reportConvertBodyFindings(findings);
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
// Where this note is, so a link that resolves nowhere is reported
|
|
803
|
-
// at a position an author can open rather than by note name.
|
|
804
|
-
file: absPath,
|
|
805
|
-
bodyLine,
|
|
806
|
-
bodyColumn,
|
|
807
|
-
lineMap,
|
|
808
|
-
// A `[[#slug]]` self-link addresses the source note, which has no
|
|
809
|
-
// entry in the index — so where its own documents landed has to
|
|
810
|
-
// travel with it.
|
|
811
|
-
pack: this.router?.resolveOrNull(fm, packForType(fm.type).docType),
|
|
812
|
-
docPack: this.router?.resolveOrNull(fm, "JournalEntry"),
|
|
813
|
-
index: this.linkIndex,
|
|
814
|
-
name,
|
|
815
|
-
});
|
|
816
|
-
this.unresolvedLinks += unresolved.length;
|
|
817
|
-
return markdown;
|
|
773
|
+
if (expanded.linkError) throw expanded.linkError;
|
|
774
|
+
this.unresolvedLinks += expanded.unresolved.length;
|
|
775
|
+
return expanded.markdown;
|
|
818
776
|
}
|
|
819
777
|
|
|
820
778
|
/**
|
|
@@ -553,7 +553,7 @@ function checkTooManyBullets(bullets) {
|
|
|
553
553
|
severity: "error",
|
|
554
554
|
message:
|
|
555
555
|
`changelog-check/too-many-bullets ${bullets.length} top-level bullets is an ` +
|
|
556
|
-
`itemized log, not a release note —
|
|
556
|
+
`itemized log, not a release note — at most ${MAX_TOP_BULLETS}, so a reader sees the shape ` +
|
|
557
557
|
"of the release at a glance",
|
|
558
558
|
},
|
|
559
559
|
];
|
package/engine/code-fences.mjs
CHANGED
|
@@ -104,11 +104,11 @@ function column(space) {
|
|
|
104
104
|
* @param {object} [options]
|
|
105
105
|
* @param {boolean} [options.spans=true] - Include inline code spans. Set false
|
|
106
106
|
* to consider only block-level code.
|
|
107
|
-
* @param {boolean} [options.
|
|
107
|
+
* @param {boolean} [options.verseAsProse=true] - Treat verse bodies as inline prose.
|
|
108
108
|
* @returns {Array<{start: number, end: number}>} Non-overlapping regions, in
|
|
109
109
|
* source order. `end` is exclusive.
|
|
110
110
|
*/
|
|
111
|
-
export function codeRegions(markdown, { spans = true,
|
|
111
|
+
export function codeRegions(markdown, { spans = true, verseAsProse = true } = {}) {
|
|
112
112
|
const src = String(markdown ?? "");
|
|
113
113
|
if (!src) return [];
|
|
114
114
|
const lines = src.split("\n");
|
|
@@ -145,7 +145,7 @@ export function codeRegions(markdown, { spans = true, poetryAsProse = true } = {
|
|
|
145
145
|
let close = i + 1;
|
|
146
146
|
while (close < lines.length && !closer.test(lines[close])) close++;
|
|
147
147
|
// An unclosed fence runs to the end of the document.
|
|
148
|
-
if (
|
|
148
|
+
if (verseAsProse && /^verse(?:\s|$)/.test(fence[3])) {
|
|
149
149
|
regions.push(through(i, i));
|
|
150
150
|
if (close < lines.length) regions.push(through(close, close));
|
|
151
151
|
} else regions.push(through(i, Math.min(close, lines.length - 1)));
|
|
@@ -140,7 +140,7 @@ export function addressSlug(fm) {
|
|
|
140
140
|
* composing a URL prepends where the package is served (`/<package>/`); a
|
|
141
141
|
* consumer composing a manifest entry measures against that same base. Nothing
|
|
142
142
|
* else is prepended: `prefix` says where the content tree *mounts inside the
|
|
143
|
-
* package* —
|
|
143
|
+
* package* — where its pages sit in the source tree — and an address is
|
|
144
144
|
* `(type, shortcode)`, a package-wide identity that takes no mount, so `sohl`
|
|
145
145
|
* publishes `/sohl/affliction-aconite/` from a file written under `kb/`. The
|
|
146
146
|
* `type-` half is what keeps that flat namespace clear of the package's fixed
|
|
@@ -136,9 +136,13 @@ export function scanAlerts(source) {
|
|
|
136
136
|
}
|
|
137
137
|
/** Render alert wrappers while preserving ordinary Markdown and note footnotes.
|
|
138
138
|
* @param {string} source - Authored Markdown.
|
|
139
|
-
* @param {"web"|"foundry"} [target="web"] - Publishing surface
|
|
139
|
+
* @param {"web"|"foundry"|"preview"} [target="web"] - Publishing surface: the
|
|
140
|
+
* preview draws the icon as an inline drawing, since a buffer has no icon font.
|
|
141
|
+
* @param {object} [options]
|
|
142
|
+
* @param {(markdown: string) => string} [options.renderInline] - How a title
|
|
143
|
+
* is rendered; omitted, a title is plain inline Markdown.
|
|
140
144
|
*/
|
|
141
|
-
export function renderAlerts(source, target = "web") {
|
|
145
|
+
export function renderAlerts(source, target = "web", { renderInline } = {}) {
|
|
142
146
|
const { blocks, errors } = scanAlerts(source),
|
|
143
147
|
lines = String(source ?? "").split("\n"),
|
|
144
148
|
output = [];
|
|
@@ -163,9 +167,9 @@ export function renderAlerts(source, target = "web") {
|
|
|
163
167
|
output.push(
|
|
164
168
|
"",
|
|
165
169
|
`<aside ${attrs.join(" ")}>`,
|
|
166
|
-
`<header class="alert-title">${definition.icon} ${titleParser.renderInline(block.title)}</header>`,
|
|
170
|
+
`<header class="alert-title">${target === "preview" ? definition.svg : definition.icon} ${(renderInline ?? titleParser.renderInline.bind(titleParser))(block.title)}</header>`,
|
|
167
171
|
"",
|
|
168
|
-
renderAlerts(block.body, target).markdown,
|
|
172
|
+
renderAlerts(block.body, target, { renderInline }).markdown,
|
|
169
173
|
"",
|
|
170
174
|
"</aside>",
|
|
171
175
|
"",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* **A block's body is left as Markdown, not pre-rendered.** The wrapper is
|
|
39
39
|
* written with a blank line after the opening tag and before the closing one,
|
|
40
40
|
* which is what lets the surrounding renderer — Foundry's single parse of the
|
|
41
|
-
* whole page, or
|
|
41
|
+
* whole page, or the site renderer's of the whole page — read the body as part of its
|
|
42
42
|
* own document rather than as an isolated fragment. A fragment rendered on its
|
|
43
43
|
* own has no access to the note's footnote definitions, which live at the top
|
|
44
44
|
* level outside every block, so a reference inside one would fall through as
|
|
@@ -53,7 +53,7 @@ import crypto from "node:crypto";
|
|
|
53
53
|
import { slugify } from "./content-slug.mjs";
|
|
54
54
|
import MarkdownIt from "markdown-it";
|
|
55
55
|
import { parseExtensionAttributes, refusedAttributes } from "./extension-attributes.mjs";
|
|
56
|
-
import {
|
|
56
|
+
import { scanVerse, renderVerse } from "./content-verse.mjs";
|
|
57
57
|
import { WITHHELD_CLASS, parseHeadingLine, withheldSections } from "./heading-attributes.mjs";
|
|
58
58
|
|
|
59
59
|
/**
|
|
@@ -163,8 +163,8 @@ export function scanBlocks(source) {
|
|
|
163
163
|
parsed.problems.push("block attributes need {…} braces");
|
|
164
164
|
else parsed = parseExtensionAttributes(raw.trim().slice(1, -1));
|
|
165
165
|
}
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
const problems = [...parsed.problems, ...refusedAttributes(parsed.values)];
|
|
167
|
+
for (const message of problems) errors.push({ ...at, message });
|
|
168
168
|
const attributes = { ...parsed.values };
|
|
169
169
|
let title = BLOCK_NAMES[name] ?? "";
|
|
170
170
|
if (name !== "div" && Object.hasOwn(attributes, "title")) {
|
|
@@ -178,7 +178,7 @@ export function scanBlocks(source) {
|
|
|
178
178
|
id: parsed.id,
|
|
179
179
|
classes: parsed.classes,
|
|
180
180
|
attributes,
|
|
181
|
-
rejected:
|
|
181
|
+
rejected: problems.length > 0,
|
|
182
182
|
indent: /^ */.exec(line)[0],
|
|
183
183
|
});
|
|
184
184
|
}
|
|
@@ -200,7 +200,7 @@ export function scanBlocks(source) {
|
|
|
200
200
|
});
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
|
-
return { blocks, errors: [...errors, ...
|
|
203
|
+
return { blocks, errors: [...errors, ...scanVerse(source).errors] };
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
/**
|
|
@@ -237,7 +237,8 @@ function attributeText(block, target, classes) {
|
|
|
237
237
|
* Render named blocks for one publishing surface.
|
|
238
238
|
*
|
|
239
239
|
* `foundry` emits a `<section>` whose heading is its first line; `web` emits a
|
|
240
|
-
* `<details>` the reader opens
|
|
240
|
+
* `<details>` the reader opens, and `preview` the same without the search
|
|
241
|
+
* marker. None carries an inline `style`.
|
|
241
242
|
*
|
|
242
243
|
* The body is left as Markdown between the wrapper's tags, each separated from
|
|
243
244
|
* its tag by a blank line — see the module docs for why that is what lets the
|
|
@@ -247,12 +248,15 @@ function attributeText(block, target, classes) {
|
|
|
247
248
|
* A finding does not stop the well-formed blocks around it from rendering.
|
|
248
249
|
*
|
|
249
250
|
* @param {string} source - Markdown containing named blocks.
|
|
250
|
-
* @param {"foundry"|"web"} target - Publishing surface.
|
|
251
|
+
* @param {"foundry"|"web"|"preview"} target - Publishing surface.
|
|
252
|
+
* @param {object} [options]
|
|
253
|
+
* @param {(markdown: string) => string} [options.renderInline] - How a title
|
|
254
|
+
* is rendered; omitted, a title is plain inline Markdown.
|
|
251
255
|
* @returns {{markdown: string, errors: Array<{line: number, column: number, message: string}>}}
|
|
252
256
|
*/
|
|
253
|
-
export function renderBlocks(source, target) {
|
|
257
|
+
export function renderBlocks(source, target, { renderInline } = {}) {
|
|
254
258
|
const { errors } = scanBlocks(source);
|
|
255
|
-
source =
|
|
259
|
+
source = renderVerse(source).markdown;
|
|
256
260
|
const renderedBlocks = scanBlocks(source).blocks;
|
|
257
261
|
if (!renderedBlocks.length) return { markdown: String(source ?? ""), errors };
|
|
258
262
|
const lines = String(source ?? "").split("\n");
|
|
@@ -268,14 +272,14 @@ export function renderBlocks(source, target) {
|
|
|
268
272
|
// at every depth, never a rendered fragment.
|
|
269
273
|
const held =
|
|
270
274
|
BLOCK_CONTAINERS.includes(block.name) ?
|
|
271
|
-
renderBlocks(block.body, target).markdown
|
|
275
|
+
renderBlocks(block.body, target, { renderInline }).markdown
|
|
272
276
|
: block.body;
|
|
273
277
|
if (block.name === "div") {
|
|
274
278
|
output.push("", `<div ${attributes}>`, "", held, "", "</div>", "");
|
|
275
279
|
cursor = block.end + 1;
|
|
276
280
|
continue;
|
|
277
281
|
}
|
|
278
|
-
const title = titleParser.renderInline(block.title);
|
|
282
|
+
const title = (renderInline ?? titleParser.renderInline.bind(titleParser))(block.title);
|
|
279
283
|
if (target === "foundry") {
|
|
280
284
|
output.push(
|
|
281
285
|
"",
|
|
@@ -287,6 +291,16 @@ export function renderBlocks(source, target) {
|
|
|
287
291
|
"</section>",
|
|
288
292
|
"",
|
|
289
293
|
);
|
|
294
|
+
} else if (target === "preview") {
|
|
295
|
+
output.push(
|
|
296
|
+
"",
|
|
297
|
+
`<details ${attributes}><summary>${title}</summary>`,
|
|
298
|
+
"",
|
|
299
|
+
held,
|
|
300
|
+
"",
|
|
301
|
+
"</details>",
|
|
302
|
+
"",
|
|
303
|
+
);
|
|
290
304
|
} else {
|
|
291
305
|
output.push(
|
|
292
306
|
"",
|
|
@@ -314,7 +328,8 @@ export function renderBlocks(source, target) {
|
|
|
314
328
|
*
|
|
315
329
|
* **A spoiler, not access control.** The section is in the published page, so a
|
|
316
330
|
* reader who opens the element or reads the source reads it. Foundry is the one
|
|
317
|
-
* surface that withholds anything.
|
|
331
|
+
* surface that withholds anything. The disclosure is kept out of the site's
|
|
332
|
+
* search index, as a `secret` block is, so no result quotes it.
|
|
318
333
|
*
|
|
319
334
|
* @param {string} source - A page's Markdown, headings intact.
|
|
320
335
|
* @returns {string} The same Markdown, each withheld section inside a
|
|
@@ -335,7 +350,7 @@ export function renderWithheldSections(source) {
|
|
|
335
350
|
];
|
|
336
351
|
output.push(
|
|
337
352
|
"",
|
|
338
|
-
`<details class="${escapeAttribute(classes.join(" "))}">`,
|
|
353
|
+
`<details class="${escapeAttribute(classes.join(" "))}" data-pagefind-ignore>`,
|
|
339
354
|
`<summary class="${escapeAttribute(WITHHELD_CLASS)}">${BLOCK_NAMES[WITHHELD_CLASS]}</summary>`,
|
|
340
355
|
"",
|
|
341
356
|
...lines.slice(section.start, section.end),
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
*
|
|
36
36
|
* **That is why the rewrite is a source-text pass rather than a markdown-it
|
|
37
37
|
* inline rule.** Two of the four readers have no render pass to put a token in:
|
|
38
|
-
* the website is handed *markdown
|
|
38
|
+
* the website's renderer is handed *markdown*, and the link checker
|
|
39
39
|
* parses nothing at all. A third, the book, reads its staging list out of the
|
|
40
40
|
* source text before it tokenises. A token would therefore serve one reader and
|
|
41
41
|
* leave the other three needing the grammar a second time, which is the drift
|