@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/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,14 @@ 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
88
|
import {
|
|
98
89
|
COLUMNS_CLASS,
|
|
99
90
|
STANZA_BREAK,
|
|
100
|
-
inlineVerse,
|
|
101
91
|
lineNumber,
|
|
102
92
|
rightToLeft,
|
|
103
93
|
scanVerse,
|
|
@@ -107,6 +97,8 @@ import {
|
|
|
107
97
|
import { matchAllOutsideCode, replaceOutsideCode } from "./code-fences.mjs";
|
|
108
98
|
import { HTML_TAG, htmlMessage } from "./content-html.mjs";
|
|
109
99
|
import { EXPRESSION } from "./markdown-expressions.mjs";
|
|
100
|
+
import { inlineText } from "./name-markup.mjs";
|
|
101
|
+
import { cartouchePathname, glyphPathname, humanizeSlug, typstLength } from "./content-glyphs.mjs";
|
|
110
102
|
import { WIKILINK } from "./wikilink-syntax.mjs";
|
|
111
103
|
import { EMBED_PATTERN } from "./content-embeds.mjs";
|
|
112
104
|
import { positionInBody } from "./diagnostics.mjs";
|
|
@@ -197,43 +189,22 @@ export function labelFor(anchor) {
|
|
|
197
189
|
}
|
|
198
190
|
|
|
199
191
|
/**
|
|
200
|
-
*
|
|
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.
|
|
201
195
|
*
|
|
202
196
|
* `html: false` is the load-bearing setting: raw HTML in a note has no route to
|
|
203
197
|
* Typst at all, which is why {@link module:engine/content-html} reports it. With
|
|
204
198
|
* HTML disabled markdown-it emits the tag as text, so it arrives in the book
|
|
205
|
-
* 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.
|
|
206
202
|
*
|
|
207
203
|
* @param {object} [registry] - The icon registry.
|
|
208
204
|
* @returns {object} A markdown-it instance.
|
|
209
205
|
*/
|
|
210
206
|
export function createParser(registry) {
|
|
211
|
-
|
|
212
|
-
md.use(footnotePlugin);
|
|
213
|
-
md.use(alertMarkdownPlugin);
|
|
214
|
-
md.use(spanMarkdownPlugin);
|
|
215
|
-
// Verse is held inside a literal fence. Parse its inline content before
|
|
216
|
-
// footnote_tail collects definitions, using the surrounding note's env.
|
|
217
|
-
md.core.ruler.after("inline", "verse_inline", (state) => {
|
|
218
|
-
for (const token of state.tokens) {
|
|
219
|
-
if (token.type !== "fence" || !/^verse(?:\s|$)/.test(token.info?.trim() || ""))
|
|
220
|
-
continue;
|
|
221
|
-
token.meta ??= {};
|
|
222
|
-
token.meta.verseStanzas = verseStanzas(token.content).map((stanza) =>
|
|
223
|
-
stanza.map(({ text, level }) => {
|
|
224
|
-
const children = [];
|
|
225
|
-
state.md.inline.parse(inlineVerse(text), state.md, state.env, children);
|
|
226
|
-
return { children, level };
|
|
227
|
-
}),
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
md.use(deflistPlugin);
|
|
232
|
-
md.use(iconPlugin(registry));
|
|
233
|
-
// The same plugin the HTML surfaces use, so one directive is read once and
|
|
234
|
-
// three renderers read the same `meta` off the same token.
|
|
235
|
-
md.use(imagePlugin());
|
|
236
|
-
return md;
|
|
207
|
+
return createRenderer("book", { icons: registry }).md;
|
|
237
208
|
}
|
|
238
209
|
|
|
239
210
|
/**
|
|
@@ -1385,6 +1356,12 @@ function renderInline(token, ctx) {
|
|
|
1385
1356
|
out.push(inlineRaw(child.content));
|
|
1386
1357
|
break;
|
|
1387
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
|
+
}
|
|
1388
1365
|
// Inline secrecy has no print presentation; other classes and ids survive.
|
|
1389
1366
|
const classes = (child.attrGet?.("class") ?? "")
|
|
1390
1367
|
.split(/\s+/)
|
|
@@ -1419,6 +1396,16 @@ function renderInline(token, ctx) {
|
|
|
1419
1396
|
out.push(`]${id ? ` <${sectionLabel(ctx.anchorPrefix, id)}>` : ""}`);
|
|
1420
1397
|
break;
|
|
1421
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;
|
|
1422
1409
|
case "strong_open":
|
|
1423
1410
|
out.push("#strong[");
|
|
1424
1411
|
break;
|
|
@@ -1473,6 +1460,74 @@ function renderInline(token, ctx) {
|
|
|
1473
1460
|
return out.join("");
|
|
1474
1461
|
}
|
|
1475
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
|
+
|
|
1476
1531
|
/**
|
|
1477
1532
|
* The index of the inline token closing the one at `i`.
|
|
1478
1533
|
*
|
|
@@ -1890,6 +1945,9 @@ export function bookTypstPreamble(runs = {}, families = {}) {
|
|
|
1890
1945
|
// run to the trimmed edge of the paper.
|
|
1891
1946
|
"#let book-title-measure = book-text-width - 2.5cm",
|
|
1892
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")',
|
|
1893
1951
|
'#let book-paper = rgb("#f4efe4")',
|
|
1894
1952
|
'#let book-accent = rgb("#7c3b1e")',
|
|
1895
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
|
+
}
|