@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/pdf-render.mjs
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
* ## Links, and the one rule that decides them
|
|
37
37
|
*
|
|
38
38
|
* A wikilink is already resolved to a URL before this module sees it — by the
|
|
39
|
-
* same {@link module:engine/
|
|
39
|
+
* same {@link module:engine/wikilinks} resolver the site uses, so the two
|
|
40
40
|
* surfaces cannot disagree about where a link points. What differs is what a
|
|
41
41
|
* *book* does with the answer:
|
|
42
42
|
*
|
|
@@ -61,19 +61,10 @@
|
|
|
61
61
|
* @module
|
|
62
62
|
*/
|
|
63
63
|
|
|
64
|
-
import
|
|
65
|
-
import footnotePlugin from "markdown-it-footnote";
|
|
66
|
-
import deflistPlugin from "markdown-it-deflist";
|
|
67
|
-
|
|
64
|
+
import { createRenderer } from "./content-render.mjs";
|
|
68
65
|
import { bookDraftNoticePreamble } from "./draft-notice.mjs";
|
|
69
|
-
import {
|
|
70
|
-
import {
|
|
71
|
-
IMAGE_CLASSES,
|
|
72
|
-
IMAGE_FLOATS,
|
|
73
|
-
IMAGE_PATTERN,
|
|
74
|
-
checkImages,
|
|
75
|
-
imagePlugin,
|
|
76
|
-
} from "./content-images.mjs";
|
|
66
|
+
import { ICON_PATTERN, ICON_SIZES } from "./content-icons.mjs";
|
|
67
|
+
import { IMAGE_CLASSES, IMAGE_FLOATS, IMAGE_PATTERN, checkImages } from "./content-images.mjs";
|
|
77
68
|
import { bookImageWidthIn } from "./pdf-images.mjs";
|
|
78
69
|
import { CHRONOLOGY_SLUG } from "./generated-sections.mjs";
|
|
79
70
|
|
|
@@ -89,15 +80,25 @@ export const BOOK_IMAGE_WIDTHS = Object.freeze({
|
|
|
89
80
|
import { markupAnchorFindings } from "./anchors.mjs";
|
|
90
81
|
import { indexChapter, indexMarker, indexPreamble } from "./pdf-index.mjs";
|
|
91
82
|
import { slugify } from "./content-slug.mjs";
|
|
92
|
-
import {
|
|
93
|
-
import {
|
|
83
|
+
import { scanAlerts, ALERT_TYPES } from "./content-alerts.mjs";
|
|
84
|
+
import { scanSpans } from "./content-spans.mjs";
|
|
94
85
|
import { GENERIC_FONTS, renderRuns, scanRuns } from "./content-scripts.mjs";
|
|
95
86
|
import { scanFigures } from "./content-figures.mjs";
|
|
96
87
|
import { BLOCK_CONTAINERS, scanBlocks, BLOCK_NAMES } from "./content-blocks.mjs";
|
|
97
|
-
import {
|
|
88
|
+
import {
|
|
89
|
+
COLUMNS_CLASS,
|
|
90
|
+
STANZA_BREAK,
|
|
91
|
+
lineNumber,
|
|
92
|
+
rightToLeft,
|
|
93
|
+
scanVerse,
|
|
94
|
+
verseColumns,
|
|
95
|
+
verseStanzas,
|
|
96
|
+
} from "./content-verse.mjs";
|
|
98
97
|
import { matchAllOutsideCode, replaceOutsideCode } from "./code-fences.mjs";
|
|
99
98
|
import { HTML_TAG, htmlMessage } from "./content-html.mjs";
|
|
100
99
|
import { EXPRESSION } from "./markdown-expressions.mjs";
|
|
100
|
+
import { inlineText } from "./name-markup.mjs";
|
|
101
|
+
import { cartouchePathname, glyphPathname, humanizeSlug, typstLength } from "./content-glyphs.mjs";
|
|
101
102
|
import { WIKILINK } from "./wikilink-syntax.mjs";
|
|
102
103
|
import { EMBED_PATTERN } from "./content-embeds.mjs";
|
|
103
104
|
import { positionInBody } from "./diagnostics.mjs";
|
|
@@ -188,43 +189,22 @@ export function labelFor(anchor) {
|
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
/**
|
|
191
|
-
*
|
|
192
|
+
* The shared parser configured for the book: the `book` target of
|
|
193
|
+
* {@link module:engine/content-render.createRenderer}, which parses and leaves
|
|
194
|
+
* the output to this module's Typst stage.
|
|
192
195
|
*
|
|
193
196
|
* `html: false` is the load-bearing setting: raw HTML in a note has no route to
|
|
194
197
|
* Typst at all, which is why {@link module:engine/content-html} reports it. With
|
|
195
198
|
* HTML disabled markdown-it emits the tag as text, so it arrives in the book
|
|
196
|
-
* visibly wrong rather than invisibly missing.
|
|
199
|
+
* visibly wrong rather than invisibly missing. The one tag it reads is the
|
|
200
|
+
* emphasis a script run's convention sets, which the run's rewrite writes and
|
|
201
|
+
* the book prints in its rubric ink or under an overline.
|
|
197
202
|
*
|
|
198
203
|
* @param {object} [registry] - The icon registry.
|
|
199
204
|
* @returns {object} A markdown-it instance.
|
|
200
205
|
*/
|
|
201
206
|
export function createParser(registry) {
|
|
202
|
-
|
|
203
|
-
md.use(footnotePlugin);
|
|
204
|
-
md.use(alertMarkdownPlugin);
|
|
205
|
-
md.use(spanMarkdownPlugin);
|
|
206
|
-
// Verse is held inside a literal fence. Parse its inline content before
|
|
207
|
-
// footnote_tail collects definitions, using the surrounding note's env.
|
|
208
|
-
md.core.ruler.after("inline", "poetry_inline", (state) => {
|
|
209
|
-
for (const token of state.tokens) {
|
|
210
|
-
if (token.type !== "fence" || !/^poetry(?:\s|$)/.test(token.info?.trim() || ""))
|
|
211
|
-
continue;
|
|
212
|
-
token.meta ??= {};
|
|
213
|
-
token.meta.poetryStanzas = poetryStanzas(token.content).map((stanza) =>
|
|
214
|
-
stanza.map(({ text, level }) => {
|
|
215
|
-
const children = [];
|
|
216
|
-
state.md.inline.parse(inlineVerse(text), state.md, state.env, children);
|
|
217
|
-
return { children, level };
|
|
218
|
-
}),
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
md.use(deflistPlugin);
|
|
223
|
-
md.use(iconPlugin(registry));
|
|
224
|
-
// The same plugin the HTML surfaces use, so one directive is read once and
|
|
225
|
-
// three renderers read the same `meta` off the same token.
|
|
226
|
-
md.use(imagePlugin());
|
|
227
|
-
return md;
|
|
207
|
+
return createRenderer("book", { icons: registry }).md;
|
|
228
208
|
}
|
|
229
209
|
|
|
230
210
|
/**
|
|
@@ -772,20 +752,24 @@ function renderBlock(tokens, i, out, ctx) {
|
|
|
772
752
|
}
|
|
773
753
|
case "fence":
|
|
774
754
|
case "code_block": {
|
|
775
|
-
if (token.type === "fence" && /^
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
755
|
+
if (token.type === "fence" && /^verse(?:\s|$)/.test(token.info?.trim() || "")) {
|
|
756
|
+
const opening = `${token.markup}${token.info}`;
|
|
757
|
+
const poem = scanVerse(`${opening}\n${token.content}${token.markup}`).blocks[0];
|
|
758
|
+
const step = Number(poem?.attributes.numstep ?? 0) || 0;
|
|
759
|
+
const content =
|
|
760
|
+
poem?.classes.includes(COLUMNS_CLASS) ?
|
|
761
|
+
verseGrid(token, poem, opening, step, ctx)
|
|
762
|
+
: step ? numberedVerse(token, step, ctx)
|
|
763
|
+
: token.meta.verseStanzas
|
|
764
|
+
.map((stanza) =>
|
|
765
|
+
stanza
|
|
766
|
+
.map(({ children, level }) => {
|
|
767
|
+
const verse = renderInline({ children }, ctx);
|
|
768
|
+
return `${level ? `#h(${level * 1.25}em)` : ""}${verse}`;
|
|
769
|
+
})
|
|
770
|
+
.join(" \\\n"),
|
|
771
|
+
)
|
|
772
|
+
.join("\n\n");
|
|
789
773
|
const anchor = poem?.id ? ` <${sectionLabel(ctx.anchorPrefix, poem.id)}>` : "";
|
|
790
774
|
out.push(
|
|
791
775
|
`\n#block(width: 100%)[${scriptWrapped(content, poem?.classes, poem?.attributes)}]${anchor}\n`,
|
|
@@ -1251,6 +1235,82 @@ function typstLanguage(tag) {
|
|
|
1251
1235
|
/** The classes that set a run in a font: a face's run carries `script`. */
|
|
1252
1236
|
const FONT_CLASSES = Object.freeze(["script", ...Object.values(GENERIC_FONTS)]);
|
|
1253
1237
|
|
|
1238
|
+
/** A line number in the book: muted, as on the page. */
|
|
1239
|
+
const typstNumber = (number) => (number ? `[#text(fill: luma(45%))[${number}]]` : "[]");
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* A numbered verse without columns: a grid of each line's number and the
|
|
1243
|
+
* line, with a row of space between stanzas.
|
|
1244
|
+
*
|
|
1245
|
+
* @param {object} token - The verse's fence token.
|
|
1246
|
+
* @param {number} step - Its `numstep`.
|
|
1247
|
+
* @param {object} ctx - Render context.
|
|
1248
|
+
* @returns {string} Typst markup.
|
|
1249
|
+
*/
|
|
1250
|
+
function numberedVerse(token, step, ctx) {
|
|
1251
|
+
let count = 0;
|
|
1252
|
+
const stanzas = token.meta.verseStanzas.map((stanza) =>
|
|
1253
|
+
stanza
|
|
1254
|
+
.map(({ children, level }) => {
|
|
1255
|
+
const verse = renderInline({ children }, ctx);
|
|
1256
|
+
return ` ${typstNumber(lineNumber(++count, step))}, [${level ? `#h(${level * 1.25}em)` : ""}${verse}],`;
|
|
1257
|
+
})
|
|
1258
|
+
.join("\n"),
|
|
1259
|
+
);
|
|
1260
|
+
return [
|
|
1261
|
+
"#grid(columns: (2.5em, 1fr), column-gutter: 1em, row-gutter: 0.35em,",
|
|
1262
|
+
stanzas.join("\n grid.cell(colspan: 2)[#v(0.5em)],\n"),
|
|
1263
|
+
")",
|
|
1264
|
+
].join("\n");
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* A verse in columns: a grid with a row per verse line, its number first when
|
|
1269
|
+
* the verse is numbered, then each column's entries stacked, the first in the
|
|
1270
|
+
* normal tone and each later one grey. A part whose text runs right to left
|
|
1271
|
+
* is set right to left inside its cell, and the grid keeps the page's
|
|
1272
|
+
* direction, so the columns stay in order. A `***` line is a row of space
|
|
1273
|
+
* across the grid.
|
|
1274
|
+
*
|
|
1275
|
+
* @param {object} token - The expanded verse's fence token.
|
|
1276
|
+
* @param {object} poem - The verse, from `scanVerse`.
|
|
1277
|
+
* @param {string} opening - Its opening line.
|
|
1278
|
+
* @param {number} step - Its `numstep`.
|
|
1279
|
+
* @param {object} ctx - Render context.
|
|
1280
|
+
* @returns {string} Typst markup.
|
|
1281
|
+
*/
|
|
1282
|
+
function verseGrid(token, poem, opening, step, ctx) {
|
|
1283
|
+
const counts = verseColumns(poem, opening).map((column) => column.entries.length);
|
|
1284
|
+
const width = counts.length + (step ? 1 : 0);
|
|
1285
|
+
const columns = [...(step ? ["2.5em"] : []), ...counts.map(() => "1fr")];
|
|
1286
|
+
const raw = verseStanzas(token.content);
|
|
1287
|
+
const rows = [];
|
|
1288
|
+
let count = 0;
|
|
1289
|
+
token.meta.verseStanzas.forEach((group, at) => {
|
|
1290
|
+
if (raw[at].length === 1 && STANZA_BREAK.test(raw[at][0].text)) {
|
|
1291
|
+
rows.push(` grid.cell(colspan: ${width})[#v(0.5em)],`);
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
const parts = group.map(({ children }, k) => {
|
|
1295
|
+
const part = renderInline({ children }, ctx);
|
|
1296
|
+
return rightToLeft(raw[at][k].text) ? `#set text(dir: rtl)\n${part}` : part;
|
|
1297
|
+
});
|
|
1298
|
+
let next = 0;
|
|
1299
|
+
const cells = counts.map((n) => {
|
|
1300
|
+
const stacked = parts.slice(next, (next += n));
|
|
1301
|
+
if (stacked.length === 1) return `[${stacked[0]}]`;
|
|
1302
|
+
return `[#stack(dir: ttb, spacing: 0.3em, ${stacked.map((part, k) => (k ? `[#text(fill: luma(45%))[${part}]]` : `[${part}]`)).join(", ")})]`;
|
|
1303
|
+
});
|
|
1304
|
+
const number = step ? [typstNumber(lineNumber(++count, step))] : [];
|
|
1305
|
+
rows.push(` ${[...number, ...cells].join(", ")},`);
|
|
1306
|
+
});
|
|
1307
|
+
return [
|
|
1308
|
+
`#grid(columns: (${columns.join(", ")}${columns.length === 1 ? "," : ""}), column-gutter: 1.5em, row-gutter: 0.65em,`,
|
|
1309
|
+
...rows,
|
|
1310
|
+
")",
|
|
1311
|
+
].join("\n");
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1254
1314
|
/**
|
|
1255
1315
|
* Wrap a block's Typst in its font, its language and its direction.
|
|
1256
1316
|
*
|
|
@@ -1296,6 +1356,12 @@ function renderInline(token, ctx) {
|
|
|
1296
1356
|
out.push(inlineRaw(child.content));
|
|
1297
1357
|
break;
|
|
1298
1358
|
case "span_open": {
|
|
1359
|
+
const sign = renderGlyph(children, i, ctx) ?? renderCartouche(children, i, ctx);
|
|
1360
|
+
if (sign) {
|
|
1361
|
+
out.push(sign.typst);
|
|
1362
|
+
i = sign.close;
|
|
1363
|
+
break;
|
|
1364
|
+
}
|
|
1299
1365
|
// Inline secrecy has no print presentation; other classes and ids survive.
|
|
1300
1366
|
const classes = (child.attrGet?.("class") ?? "")
|
|
1301
1367
|
.split(/\s+/)
|
|
@@ -1330,6 +1396,16 @@ function renderInline(token, ctx) {
|
|
|
1330
1396
|
out.push(`]${id ? ` <${sectionLabel(ctx.anchorPrefix, id)}>` : ""}`);
|
|
1331
1397
|
break;
|
|
1332
1398
|
}
|
|
1399
|
+
case "script_emphasis_open":
|
|
1400
|
+
out.push(
|
|
1401
|
+
child.meta?.convention === "rubric" ?
|
|
1402
|
+
"#text(fill: book-rubric)["
|
|
1403
|
+
: "#overline[",
|
|
1404
|
+
);
|
|
1405
|
+
break;
|
|
1406
|
+
case "script_emphasis_close":
|
|
1407
|
+
out.push("]");
|
|
1408
|
+
break;
|
|
1333
1409
|
case "strong_open":
|
|
1334
1410
|
out.push("#strong[");
|
|
1335
1411
|
break;
|
|
@@ -1384,6 +1460,74 @@ function renderInline(token, ctx) {
|
|
|
1384
1460
|
return out.join("");
|
|
1385
1461
|
}
|
|
1386
1462
|
|
|
1463
|
+
/**
|
|
1464
|
+
* A glyph span as the picture the book carries, or as its label when the
|
|
1465
|
+
* package ships no file for it.
|
|
1466
|
+
*
|
|
1467
|
+
* @param {object[]} children - Inline children.
|
|
1468
|
+
* @param {number} i - The index of a `span_open`.
|
|
1469
|
+
* @param {object} ctx - Render context.
|
|
1470
|
+
* @returns {{typst: string, close: number}|undefined} The Typst and the index
|
|
1471
|
+
* of the closing token; `undefined` when the span is not a well-formed glyph.
|
|
1472
|
+
*/
|
|
1473
|
+
function renderGlyph(children, i, ctx) {
|
|
1474
|
+
const open = children[i];
|
|
1475
|
+
const classes = (open.attrGet?.("class") ?? "").split(/\s+/);
|
|
1476
|
+
const slug = open.attrGet?.("slug");
|
|
1477
|
+
if (!classes.includes("glyph") || !slug || open.meta?.problems?.length) return undefined;
|
|
1478
|
+
const close = childMatching(children, i, "span_open", "span_close");
|
|
1479
|
+
const label = inlineText(children.slice(i + 1, close)).trim() || humanizeSlug(slug);
|
|
1480
|
+
const staged = ctx.images.get(glyphPathname(slug));
|
|
1481
|
+
if (!staged) return { typst: escapeTypst(label), close };
|
|
1482
|
+
const picture = `image("${escapeTypstString(staged)}", height: 1.8em)`;
|
|
1483
|
+
const frame = open.attrGet?.("frame");
|
|
1484
|
+
const typst =
|
|
1485
|
+
frame === "tablet" ?
|
|
1486
|
+
`#box(fill: rgb("#ece1c8"), inset: (x: 0.3em, y: 0.15em), radius: 0.2em, baseline: 25%, ${picture})`
|
|
1487
|
+
: open.attrGet?.("size") === "block" ? `#align(center, ${picture})`
|
|
1488
|
+
: `#box(baseline: 25%, ${picture})`;
|
|
1489
|
+
return { typst, close };
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
/**
|
|
1493
|
+
* A cartouche span as the figure the book prints. Print cannot wrap text
|
|
1494
|
+
* around it, so `align` places the figure between the lines of its sentence.
|
|
1495
|
+
*
|
|
1496
|
+
* @param {object[]} children - Inline children.
|
|
1497
|
+
* @param {number} i - The index of a `span_open`.
|
|
1498
|
+
* @param {object} ctx - Render context.
|
|
1499
|
+
* @returns {{typst: string, close: number}|undefined} The Typst and the index
|
|
1500
|
+
* of the closing token; `undefined` when the span is not a well-formed
|
|
1501
|
+
* cartouche.
|
|
1502
|
+
*/
|
|
1503
|
+
function renderCartouche(children, i, ctx) {
|
|
1504
|
+
const open = children[i];
|
|
1505
|
+
const classes = (open.attrGet?.("class") ?? "").split(/\s+/);
|
|
1506
|
+
const slug = open.attrGet?.("slug");
|
|
1507
|
+
if (!classes.includes("cartouche") || !slug || open.meta?.problems?.length) return undefined;
|
|
1508
|
+
const close = childMatching(children, i, "span_open", "span_close");
|
|
1509
|
+
const align = open.attrGet?.("align");
|
|
1510
|
+
const height = open.attrGet?.("height");
|
|
1511
|
+
const caption = open.attrGet?.("caption");
|
|
1512
|
+
const alt = inlineText(children.slice(i + 1, close)).trim();
|
|
1513
|
+
const staged = ctx.images.get(cartouchePathname(slug));
|
|
1514
|
+
const label = caption ? escapeTypst(caption) : "";
|
|
1515
|
+
const text = (content) => `#text(size: 7.6pt, style: "italic", fill: luma(45%))[${content}]`;
|
|
1516
|
+
if (!staged) {
|
|
1517
|
+
const shown = label || escapeTypst(alt || humanizeSlug(slug));
|
|
1518
|
+
return { typst: `\n#align(center, ${text(shown)})\n`, close };
|
|
1519
|
+
}
|
|
1520
|
+
const size =
|
|
1521
|
+
height ? typstLength(height)
|
|
1522
|
+
: align ? "7em"
|
|
1523
|
+
: "10em";
|
|
1524
|
+
const picture = `image("${escapeTypstString(staged)}", height: ${size})`;
|
|
1525
|
+
return {
|
|
1526
|
+
typst: `\n#align(${align ?? "center"}, block(breakable: false)[#${picture}${label ? ` \\ ${text(label)}` : ""}])\n`,
|
|
1527
|
+
close,
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1387
1531
|
/**
|
|
1388
1532
|
* The index of the inline token closing the one at `i`.
|
|
1389
1533
|
*
|
|
@@ -1801,6 +1945,9 @@ export function bookTypstPreamble(runs = {}, families = {}) {
|
|
|
1801
1945
|
// run to the trimmed edge of the paper.
|
|
1802
1946
|
"#let book-title-measure = book-text-width - 2.5cm",
|
|
1803
1947
|
'#let book-ink = rgb("#241f1a")',
|
|
1948
|
+
// A rubric script's emphasis: a vermilion that holds 4.5:1 on the paper
|
|
1949
|
+
// and stays apart from the ink in lightness.
|
|
1950
|
+
'#let book-rubric = rgb("#9c281b")',
|
|
1804
1951
|
'#let book-paper = rgb("#f4efe4")',
|
|
1805
1952
|
'#let book-accent = rgb("#7c3b1e")',
|
|
1806
1953
|
'#let book-head = rgb("#5e2b14")',
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* **A blog post** is a `doc` of `subType: post`. It states the day it was
|
|
16
|
+
* published in `data.date`, as `YYYY-MM-DD`, and may name its writer in
|
|
17
|
+
* `data.author`; a post that names none carries the site's byline, the first
|
|
18
|
+
* entry of `authors`. Its `tags` are the top-level ones any note carries.
|
|
19
|
+
*
|
|
20
|
+
* Both fields belong to a post alone. {@link checkPostFields} requires the date
|
|
21
|
+
* on a post, refuses either field on any other `doc`, and refuses a date that
|
|
22
|
+
* is not a calendar day or an author that is blank.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { positionInFrontmatter, positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
28
|
+
|
|
29
|
+
/** The `doc` subType a post is. */
|
|
30
|
+
export const POST_SUBTYPE = "post";
|
|
31
|
+
|
|
32
|
+
/** A calendar day as a post states it. */
|
|
33
|
+
const DAY = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether a note's frontmatter, or a page's parameters, are a post's.
|
|
37
|
+
*
|
|
38
|
+
* @param {{type?: unknown, subType?: unknown}|null|undefined} fm - The frontmatter.
|
|
39
|
+
* @returns {boolean} Whether it is a `doc` of `subType: post`.
|
|
40
|
+
*/
|
|
41
|
+
export function isPost(fm) {
|
|
42
|
+
return (
|
|
43
|
+
String(fm?.type ?? "").toLowerCase() === "doc" &&
|
|
44
|
+
String(fm?.subType ?? "").toLowerCase() === POST_SUBTYPE
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whether a string is a calendar day, `YYYY-MM-DD`, that exists.
|
|
50
|
+
*
|
|
51
|
+
* @param {unknown} value - The value.
|
|
52
|
+
* @returns {boolean} Whether it is one.
|
|
53
|
+
*/
|
|
54
|
+
export function isCalendarDay(value) {
|
|
55
|
+
if (typeof value !== "string") return false;
|
|
56
|
+
const match = DAY.exec(value);
|
|
57
|
+
if (!match) return false;
|
|
58
|
+
const [year, month, day] = match.slice(1).map(Number);
|
|
59
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
60
|
+
return (
|
|
61
|
+
date.getUTCFullYear() === year &&
|
|
62
|
+
date.getUTCMonth() === month - 1 &&
|
|
63
|
+
date.getUTCDate() === day
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** `data.date`, as the vocabulary declares it. */
|
|
68
|
+
export const POST_DATE_FIELD = Object.freeze({
|
|
69
|
+
name: "date",
|
|
70
|
+
shape: "string",
|
|
71
|
+
kind: "string",
|
|
72
|
+
describe:
|
|
73
|
+
"The day a post was published, as `YYYY-MM-DD`. Required on a `post`; no other " +
|
|
74
|
+
"subType states it. Posts are listed and fed newest first by it.",
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
/** `data.author`, as the vocabulary declares it. */
|
|
78
|
+
export const POST_AUTHOR_FIELD = Object.freeze({
|
|
79
|
+
name: "author",
|
|
80
|
+
shape: "string",
|
|
81
|
+
kind: "string",
|
|
82
|
+
describe:
|
|
83
|
+
"Who wrote a post, as the byline prints them. A post that names none carries the " +
|
|
84
|
+
"site's byline, the first entry of `authors`; no other subType states it.",
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Scope `data.date` and `data.author` to posts, and check their values.
|
|
89
|
+
*
|
|
90
|
+
* A post with no date is an error at its `subType:` line; a date that is not a
|
|
91
|
+
* calendar day, or an author that is blank, is an error at the value. Either
|
|
92
|
+
* field on a `doc` that is not a post is an error at the key.
|
|
93
|
+
*
|
|
94
|
+
* @param {object} note - The note, as the link index hands it over.
|
|
95
|
+
* @returns {object[]} Findings.
|
|
96
|
+
*/
|
|
97
|
+
export function checkPostFields(note) {
|
|
98
|
+
const type = String(note?.type ?? note?.fm?.type ?? "").toLowerCase();
|
|
99
|
+
if (type !== "doc") return [];
|
|
100
|
+
const raw = note.raw ?? "";
|
|
101
|
+
const data = note?.fm?.data;
|
|
102
|
+
const has = (key) =>
|
|
103
|
+
data !== null &&
|
|
104
|
+
typeof data === "object" &&
|
|
105
|
+
!Array.isArray(data) &&
|
|
106
|
+
data[key] !== undefined &&
|
|
107
|
+
data[key] !== null;
|
|
108
|
+
const finding = (keyPath, message, key = false) => ({
|
|
109
|
+
file: note.file,
|
|
110
|
+
...positionOfFrontmatterPath(raw, keyPath, { key }),
|
|
111
|
+
severity: "error",
|
|
112
|
+
message,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!isPost({ type, subType: note?.fm?.subType })) {
|
|
116
|
+
return ["date", "author"]
|
|
117
|
+
.filter(has)
|
|
118
|
+
.map((key) =>
|
|
119
|
+
finding(
|
|
120
|
+
["data", key],
|
|
121
|
+
`\`data.${key}\` is stated by a post, and this note's \`subType\` is not ` +
|
|
122
|
+
`\`${POST_SUBTYPE}\``,
|
|
123
|
+
true,
|
|
124
|
+
),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const findings = [];
|
|
129
|
+
if (!has("date")) {
|
|
130
|
+
findings.push({
|
|
131
|
+
file: note.file,
|
|
132
|
+
...positionInFrontmatter(raw, "subType", undefined, { topLevel: true }),
|
|
133
|
+
severity: "error",
|
|
134
|
+
message:
|
|
135
|
+
"a post is listed and fed by the day it was published, so it must state " +
|
|
136
|
+
"`data.date`, as `YYYY-MM-DD`",
|
|
137
|
+
});
|
|
138
|
+
} else if (typeof data.date === "string" && !isCalendarDay(data.date)) {
|
|
139
|
+
findings.push(
|
|
140
|
+
finding(
|
|
141
|
+
["data", "date"],
|
|
142
|
+
`\`data.date\` is \`${String(data.date)}\`, which is not a calendar day written ` +
|
|
143
|
+
"`YYYY-MM-DD`",
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (has("author") && typeof data.author === "string" && !data.author.trim()) {
|
|
148
|
+
findings.push(
|
|
149
|
+
finding(
|
|
150
|
+
["data", "author"],
|
|
151
|
+
"`data.author` names who wrote the post; leave it out for the site's byline " +
|
|
152
|
+
"rather than writing it blank",
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
return findings;
|
|
157
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* **What an editor with a content index gives the preview.** {@link previewIndex}
|
|
16
|
+
* turns a package's configuration and its content-index records into the one
|
|
17
|
+
* object {@link module:engine/site-preview.renderPreview} takes as `index`. A
|
|
18
|
+
* preview given it resolves links, embeds, marked text, icons and figure
|
|
19
|
+
* numbers as the website does; a preview without one shows the buffer's source.
|
|
20
|
+
*
|
|
21
|
+
* Build it once per index and reuse it for every render: it holds the link
|
|
22
|
+
* index, the asset address space, the languages' maps and the events of every
|
|
23
|
+
* note.
|
|
24
|
+
*
|
|
25
|
+
* @module
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { addressSlug } from "./content-address.mjs";
|
|
29
|
+
import { expandContent } from "./content-expand.mjs";
|
|
30
|
+
import { embedRole } from "./content-embeds.mjs";
|
|
31
|
+
import { renderImageFigures } from "./content-images.mjs";
|
|
32
|
+
import { runContext } from "./content-scripts.mjs";
|
|
33
|
+
import { eventNoteIndex } from "./event-fields.mjs";
|
|
34
|
+
import { isHomepage } from "./homepage.mjs";
|
|
35
|
+
import { authoredFrontmatter, isNoteRecord, isStub } from "./index-records.mjs";
|
|
36
|
+
import { noContentIndexPackages } from "./metadata-index.mjs";
|
|
37
|
+
import { isGmNote } from "./note-vocabulary.mjs";
|
|
38
|
+
import { assetAddressIndex } from "./art-fields.mjs";
|
|
39
|
+
import { assetPathnameKey, pathnameProblem, resolvePathname } from "./pathnames.mjs";
|
|
40
|
+
import { buildSiteIndex } from "./site-index.mjs";
|
|
41
|
+
import { siteBase } from "../content-config.mjs";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* What a preview reads to render with an index.
|
|
45
|
+
*
|
|
46
|
+
* @typedef {object} PreviewIndex
|
|
47
|
+
* @property {object} runs - The package's languages, faces and maps.
|
|
48
|
+
* @property {object|undefined} icons - The package's icon registry.
|
|
49
|
+
* @property {(src: string) => ({role?: string, width?: number|"", height?: number|""}|undefined)} lookupAsset -
|
|
50
|
+
* From a picture's address to its asset.
|
|
51
|
+
* @property {(src: string) => string} resolveSrc - From a picture's authored
|
|
52
|
+
* pathname to the address the website serves it at.
|
|
53
|
+
* @property {(markdown: string, note: {fm: object, file?: string}) => {markdown: string, figures: object[]}} expand -
|
|
54
|
+
* Expand a buffer's body as the website expands a page: links, embeds,
|
|
55
|
+
* expressions and figure numbers resolved. A query, which a buffer cannot
|
|
56
|
+
* answer, is left as written.
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build the object a preview with an index renders from.
|
|
61
|
+
*
|
|
62
|
+
* @param {object} options
|
|
63
|
+
* @param {object} options.config - The resolved package configuration.
|
|
64
|
+
* @param {object[]} options.records - The content index's records: every note
|
|
65
|
+
* and every asset.
|
|
66
|
+
* @param {object} [options.foreign] - The dependencies' indexes, as
|
|
67
|
+
* {@link module:engine/metadata-index.loadForeignIndexes} returns them.
|
|
68
|
+
* Omitted, the package links only to itself.
|
|
69
|
+
* @returns {PreviewIndex} The index.
|
|
70
|
+
*/
|
|
71
|
+
export function previewIndex({ config, records, foreign }) {
|
|
72
|
+
const base = siteBase(config);
|
|
73
|
+
const entries = [];
|
|
74
|
+
for (const record of records) {
|
|
75
|
+
if (!isNoteRecord(record) || isStub(record)) continue;
|
|
76
|
+
const fm = authoredFrontmatter(record);
|
|
77
|
+
if (isGmNote(fm) || typeof fm.shortcode !== "string" || !fm.shortcode) continue;
|
|
78
|
+
entries.push({
|
|
79
|
+
kind: "content",
|
|
80
|
+
fm,
|
|
81
|
+
body: "",
|
|
82
|
+
anchors: record.anchors ?? [],
|
|
83
|
+
pkg: record.package ?? config.contentPackage,
|
|
84
|
+
name: fm.name?.full ?? fm.shortcode,
|
|
85
|
+
url: isHomepage(fm) ? base : `${base}${addressSlug(fm)}/`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const site = buildSiteIndex(entries, {
|
|
89
|
+
package: config.contentPackage,
|
|
90
|
+
currentDate: config.currentDate,
|
|
91
|
+
foreignIndex: foreign?.index,
|
|
92
|
+
foreignReferences: foreign?.references,
|
|
93
|
+
records,
|
|
94
|
+
noIndexPackages: noContentIndexPackages(config),
|
|
95
|
+
});
|
|
96
|
+
const assets = assetAddressIndex(records, { config, foreign, types: site.contentTypes });
|
|
97
|
+
const events = eventNoteIndex(records, {
|
|
98
|
+
contentPackage: site.contentPackage,
|
|
99
|
+
foreignIndex: foreign?.index,
|
|
100
|
+
dates: site.dateContext,
|
|
101
|
+
});
|
|
102
|
+
const resolveRole = (address) => embedRole(assets, address);
|
|
103
|
+
const webSrc = (src) =>
|
|
104
|
+
pathnameProblem(src) ? src : (resolvePathname(src, config)?.web ?? src);
|
|
105
|
+
const lookupAsset = (src) => {
|
|
106
|
+
const key = assetPathnameKey(src, config);
|
|
107
|
+
return key ? assets?.byPath?.get(key) : undefined;
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
runs: runContext(config, records),
|
|
111
|
+
icons: config.icons,
|
|
112
|
+
lookupAsset,
|
|
113
|
+
resolveSrc: webSrc,
|
|
114
|
+
expand: (markdown, { fm, file }) => {
|
|
115
|
+
const expanded = expandContent(markdown, {
|
|
116
|
+
target: "web",
|
|
117
|
+
index: site,
|
|
118
|
+
note: { fm, file, source: file || "<buffer>" },
|
|
119
|
+
events,
|
|
120
|
+
resolveRole,
|
|
121
|
+
links: {
|
|
122
|
+
foreign: foreign?.index,
|
|
123
|
+
assets,
|
|
124
|
+
afterLinks: (block) => renderImageFigures(block, webSrc, lookupAsset),
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
return { markdown: expanded.markdown, figures: expanded.figures.figures };
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|