@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
|
@@ -216,6 +216,95 @@ export function headingAttributesPlugin(md) {
|
|
|
216
216
|
});
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
/**
|
|
220
|
+
* The id a heading's text gives it on the web: GitHub's form, lower case, each
|
|
221
|
+
* space or hyphen a hyphen, every character that is not a letter, a digit or
|
|
222
|
+
* an underscore dropped.
|
|
223
|
+
*
|
|
224
|
+
* @param {string} text - The heading's text, without its markup.
|
|
225
|
+
* @returns {string} The id, or `heading` when nothing is left of the text.
|
|
226
|
+
*/
|
|
227
|
+
export function headingId(text) {
|
|
228
|
+
let id = "";
|
|
229
|
+
for (const char of String(text ?? "").trim()) {
|
|
230
|
+
if (char === " " || char === "-") id += "-";
|
|
231
|
+
else if (char === "_" || /[\p{L}\p{Nd}]/u.test(char)) id += char.toLowerCase();
|
|
232
|
+
}
|
|
233
|
+
return id || "heading";
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** The tokens whose content is a heading's text. */
|
|
237
|
+
const TEXT_TOKENS = new Set(["text", "code_inline", "typographic"]);
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The text a heading's id is made from.
|
|
241
|
+
*
|
|
242
|
+
* Its words, code and punctuation; a line break as a newline; no markup. An
|
|
243
|
+
* element inside the heading — emphasis, a link, an image — gives only its
|
|
244
|
+
* first piece of text, which ends at the first punctuation the page sets
|
|
245
|
+
* typographically, so `[Nkaru'thar](…)` gives `Nkaru`.
|
|
246
|
+
*
|
|
247
|
+
* @param {object[]} children - The heading's inline tokens.
|
|
248
|
+
* @returns {string} The text.
|
|
249
|
+
*/
|
|
250
|
+
function headingText(children) {
|
|
251
|
+
const list = children ?? [];
|
|
252
|
+
/** The first piece of text inside the element opening at `index`. */
|
|
253
|
+
const first = (index) => {
|
|
254
|
+
const token = list[index];
|
|
255
|
+
if (!token || token.nesting === -1) return "";
|
|
256
|
+
if (TEXT_TOKENS.has(token.type)) return token.content;
|
|
257
|
+
if (token.type === "image") return headingText(token.children?.slice(0, 1));
|
|
258
|
+
if (token.nesting === 1) return first(index + 1);
|
|
259
|
+
return "";
|
|
260
|
+
};
|
|
261
|
+
let text = "";
|
|
262
|
+
for (let i = 0; i < list.length; i++) {
|
|
263
|
+
const token = list[i];
|
|
264
|
+
if (TEXT_TOKENS.has(token.type)) text += token.content;
|
|
265
|
+
else if (token.type === "softbreak" || token.type === "hardbreak") text += "\n";
|
|
266
|
+
else if (token.type === "image") text += headingText(token.children?.slice(0, 1));
|
|
267
|
+
else if (token.nesting === 1) {
|
|
268
|
+
text += first(i + 1);
|
|
269
|
+
for (let depth = 1; depth > 0 && i + 1 < list.length;) {
|
|
270
|
+
i++;
|
|
271
|
+
depth += list[i].nesting;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return text;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Give every heading an id, so a page's contents list and a reader's link can
|
|
280
|
+
* name any section.
|
|
281
|
+
*
|
|
282
|
+
* An authored `{#id}` is kept, and every other heading takes {@link headingId}
|
|
283
|
+
* of its text. A repeated id takes `-1`, `-2` and so on, counted over every id
|
|
284
|
+
* on the page that came before it.
|
|
285
|
+
*
|
|
286
|
+
* @param {import("markdown-it")} md - Markdown parser to extend.
|
|
287
|
+
*/
|
|
288
|
+
export function headingIdsPlugin(md) {
|
|
289
|
+
md.core.ruler.push("heading_ids", (state) => {
|
|
290
|
+
const taken = new Set();
|
|
291
|
+
const tokens = state.tokens;
|
|
292
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
293
|
+
if (tokens[i].type !== "heading_open") continue;
|
|
294
|
+
const authored = tokens[i].attrGet("id");
|
|
295
|
+
if (authored) {
|
|
296
|
+
taken.add(authored);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const base = headingId(headingText(tokens[i + 1]?.children));
|
|
300
|
+
let id = base;
|
|
301
|
+
for (let n = 1; taken.has(id); n++) id = `${base}-${n}`;
|
|
302
|
+
taken.add(id);
|
|
303
|
+
tokens[i].attrSet("id", id);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
219
308
|
/**
|
|
220
309
|
* Parse one line as a heading, and say whether it starts a journal page.
|
|
221
310
|
*
|
package/engine/helpers.mjs
CHANGED
|
@@ -25,8 +25,7 @@
|
|
|
25
25
|
* journals.mjs, actors.mjs).
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
import {
|
|
29
|
-
import { renderRuns } from "./content-scripts.mjs";
|
|
28
|
+
import { createRenderer } from "./content-render.mjs";
|
|
30
29
|
import { buildReferenceTargets } from "./reference-targets.mjs";
|
|
31
30
|
import { standingsDigest } from "./standings.mjs";
|
|
32
31
|
|
|
@@ -39,12 +38,7 @@ import crypto from "crypto";
|
|
|
39
38
|
import path from "path";
|
|
40
39
|
import yaml from "yaml";
|
|
41
40
|
import unidecode from "unidecode";
|
|
42
|
-
import
|
|
43
|
-
import footnotePlugin from "markdown-it-footnote";
|
|
44
|
-
import deflistPlugin from "markdown-it-deflist";
|
|
45
|
-
import { dictionaryListPlugin } from "./definition-list.mjs";
|
|
46
|
-
import { iconPlugin } from "./content-icons.mjs";
|
|
47
|
-
import { imagePlugin, imagesIn } from "./content-images.mjs";
|
|
41
|
+
import { imagesIn } from "./content-images.mjs";
|
|
48
42
|
import { embedRole, resolveEmbeds } from "./content-embeds.mjs";
|
|
49
43
|
import { collectAssetRecords } from "./asset-index.mjs";
|
|
50
44
|
import { assetImageInfoByPathname } from "./art-fields.mjs";
|
|
@@ -67,19 +61,15 @@ import { loadForeignIndexes, noContentIndexPackages } from "./metadata-index.mjs
|
|
|
67
61
|
// a record needs none of that.
|
|
68
62
|
import { authoredFrontmatter, isAssetRecord, isNoteRecord, noteFile } from "./index-records.mjs";
|
|
69
63
|
import { buildWikilinkIndex, convertWikilinks } from "./wikilinks.mjs";
|
|
70
|
-
// One vocabulary of link findings, and one message per class, so
|
|
71
|
-
//
|
|
64
|
+
// One vocabulary of link findings, and one message per class, so no two
|
|
65
|
+
// builds word the same defect differently.
|
|
72
66
|
import { linkFindingMessage } from "./wikilink-syntax.mjs";
|
|
73
67
|
// The declared tag vocabulary, which is where `draft` is stated. Read
|
|
74
68
|
// from there rather than respelt, so the tag and its one reader cannot drift.
|
|
75
69
|
import { isGmNote, isDraftNote } from "./note-vocabulary.mjs";
|
|
76
70
|
import { DERIVED_PACKED_TYPES, NEVER_PACKED_TYPES } from "./note-claims.mjs";
|
|
77
71
|
import { expandContentTables } from "./content-tables.mjs";
|
|
78
|
-
import {
|
|
79
|
-
import { dictionaryEntryPlugin } from "./dictionary-layout.mjs";
|
|
80
|
-
import { renderBlocks } from "./content-blocks.mjs";
|
|
81
|
-
import { headingAttributesPlugin } from "./heading-attributes.mjs";
|
|
82
|
-
import { renderFigureBlocks, scanFigures } from "./content-figures.mjs";
|
|
72
|
+
import { scanFigures } from "./content-figures.mjs";
|
|
83
73
|
import { positionInBody, splitFrontmatter } from "./diagnostics.mjs";
|
|
84
74
|
// The pure `sohl:` frontmatter readers live in a leaf module so the item-type
|
|
85
75
|
// registry can import them without reaching back through this one.
|
|
@@ -100,72 +90,68 @@ export {
|
|
|
100
90
|
let localAssetImageInfoCache;
|
|
101
91
|
|
|
102
92
|
/**
|
|
103
|
-
* The
|
|
93
|
+
* The Foundry renderer: the shared configuration of
|
|
94
|
+
* {@link module:engine/content-render.createRenderer} for the `foundry`
|
|
95
|
+
* target.
|
|
104
96
|
*
|
|
105
97
|
* `html: true` is long-standing and load-bearing — notes carry raw blocks — and
|
|
106
98
|
* it is also why {@link module:engine/content-icons} exists rather than an
|
|
107
99
|
* instruction to write `<i class="fa-solid …">` by hand: that would render on
|
|
108
100
|
* the two HTML surfaces and be silently dropped by the third.
|
|
101
|
+
*
|
|
102
|
+
* The icon registry and the image resolvers are read per render, not at import:
|
|
103
|
+
* this constant is built before any configuration is read, and a package's own
|
|
104
|
+
* icons live in the configuration. A tree with none — or a caller with no
|
|
105
|
+
* configuration to find — falls back to the shipped table and to the authored
|
|
106
|
+
* pathname.
|
|
109
107
|
*/
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
.use(headingAttributesPlugin)
|
|
119
|
-
.use(
|
|
120
|
-
// Resolved per render, not at import: this constant is built before any
|
|
121
|
-
// configuration is read, and a package's own icons live in the
|
|
122
|
-
// configuration. A tree with none — or a caller with no configuration to
|
|
123
|
-
// find — falls back to the shipped table.
|
|
124
|
-
iconPlugin(() => {
|
|
125
|
-
try {
|
|
126
|
-
return loadPackConfig().icons;
|
|
127
|
-
} catch {
|
|
128
|
-
return undefined;
|
|
129
|
-
}
|
|
130
|
-
}),
|
|
131
|
-
)
|
|
108
|
+
const renderer = createRenderer("foundry", {
|
|
109
|
+
icons: () => {
|
|
110
|
+
try {
|
|
111
|
+
return loadPackConfig().icons;
|
|
112
|
+
} catch {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
},
|
|
132
116
|
// The width and position an image states, honoured as a figure. The
|
|
133
117
|
// vocabularies are closed and need no configuration; the address does, and
|
|
134
118
|
// is resolved per render for the reason the icon registry is — Foundry
|
|
135
119
|
// serves a file from inside the install, so a body image's address is
|
|
136
120
|
// translated by the same rule `img:` follows.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
121
|
+
images: {
|
|
122
|
+
resolveSrc: (src) => {
|
|
123
|
+
// **Reported upstream, never here.** A renderer has no channel to
|
|
124
|
+
// report through, and this one runs inside the very passes whose
|
|
125
|
+
// job is to collect findings — a throw would take the whole lint
|
|
126
|
+
// down and lose every other finding in the tree. So nothing reaches
|
|
127
|
+
// this point unreported: {@link convertNoteWikilinks} refuses a body
|
|
128
|
+
// image with no address inside the install before a compiler renders
|
|
129
|
+
// one, and the address passes report the same pathname with a line
|
|
130
|
+
// and a column. What is left here is a fallback for a caller with no
|
|
131
|
+
// configuration to resolve against, where the authored pathname is
|
|
132
|
+
// the most honest thing to emit.
|
|
133
|
+
try {
|
|
134
|
+
return resolveImg(src, loadPackConfig()) ?? src;
|
|
135
|
+
} catch {
|
|
136
|
+
return src;
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
lookupAsset: (src) => {
|
|
140
|
+
// A body image is free to write the bare, own-package form,
|
|
141
|
+
// so the address is normalized to the pathname the cache
|
|
142
|
+
// below keys by before it is looked up.
|
|
143
|
+
try {
|
|
144
|
+
const key = assetPathnameKey(src, loadPackConfig());
|
|
145
|
+
return key ? localAssetImageInfo().get(key) : undefined;
|
|
146
|
+
} catch {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
/** The Foundry renderer's parser, for callers that parse or render a fragment. */
|
|
154
|
+
export const md = renderer.md;
|
|
169
155
|
|
|
170
156
|
/**
|
|
171
157
|
* The pictures this package's own tree ships, by the pathname their address
|
|
@@ -194,25 +180,6 @@ function localAssetImageInfo() {
|
|
|
194
180
|
return localAssetImageInfoCache;
|
|
195
181
|
}
|
|
196
182
|
|
|
197
|
-
md.renderer.rules.footnote_block_open = () =>
|
|
198
|
-
'<section class="footnotes"><h2>Footnotes</h2><ol class="footnotes-list">\n';
|
|
199
|
-
md.renderer.rules.footnote_caption = (tokens, idx, _options, env) => {
|
|
200
|
-
const meta = tokens[idx].meta;
|
|
201
|
-
const number = env.footnoteNumbers?.get(meta.label) ?? meta.id + 1;
|
|
202
|
-
return `[${number}${meta.subId > 0 ? `:${meta.subId}` : ""}]`;
|
|
203
|
-
};
|
|
204
|
-
md.renderer.rules.footnote_anchor_name = (tokens, idx, _options, env) => {
|
|
205
|
-
const meta = tokens[idx].meta;
|
|
206
|
-
const number = env.footnoteNumbers?.get(meta.label) ?? meta.id + 1;
|
|
207
|
-
return `${env.docId ? `-${env.docId}-` : ""}${number}`;
|
|
208
|
-
};
|
|
209
|
-
md.renderer.rules.footnote_open = (tokens, idx, options, env, renderer) => {
|
|
210
|
-
const meta = tokens[idx].meta;
|
|
211
|
-
const number = env.footnoteNumbers?.get(meta.label) ?? meta.id + 1;
|
|
212
|
-
const id = renderer.rules.footnote_anchor_name(tokens, idx, options, env, renderer);
|
|
213
|
-
return `<li id="fn${id}" class="footnote-item" value="${number}">`;
|
|
214
|
-
};
|
|
215
|
-
|
|
216
183
|
/**
|
|
217
184
|
* Render a note body with Foundry's named-block markup.
|
|
218
185
|
*
|
|
@@ -232,12 +199,7 @@ md.renderer.rules.footnote_open = (tokens, idx, options, env, renderer) => {
|
|
|
232
199
|
* @returns {string} The rendered HTML.
|
|
233
200
|
*/
|
|
234
201
|
export function renderFoundryMarkdown(body, figures, footnoteNumbers, docId, resolveRole, runs) {
|
|
235
|
-
|
|
236
|
-
const figured = renderFigureBlocks(marked, (block) => md.render(block), figures, {
|
|
237
|
-
resolveRole,
|
|
238
|
-
});
|
|
239
|
-
const blocks = renderBlocks(figured.markdown, "foundry");
|
|
240
|
-
return md.render(renderAlerts(blocks.markdown, "foundry").markdown, { footnoteNumbers, docId });
|
|
202
|
+
return renderer.render(body, { runs, figures, resolveRole, footnoteNumbers, docId });
|
|
241
203
|
}
|
|
242
204
|
|
|
243
205
|
/**
|
|
@@ -983,7 +945,7 @@ export function buildContentLinkIndex(
|
|
|
983
945
|
* @param {number} [ctx.bodyLine] - 1-based file line of the body's first line.
|
|
984
946
|
* @param {number} [ctx.bodyColumn] - 1-based file column of the same character.
|
|
985
947
|
* @param {Array<{line: number, generated: boolean}>} [ctx.lineMap] - Which
|
|
986
|
-
* authored line each body line came from, from {@link
|
|
948
|
+
* authored line each body line came from, from {@link module:engine/content-tables.expandContentTables}.
|
|
987
949
|
* @returns {{markdown: string, unresolved: Array<object>}}
|
|
988
950
|
* @throws {Error} On any link that does not resolve — an unlabelled one, a
|
|
989
951
|
* target that is not an address, or an address nothing publishes — and on a
|
|
@@ -1086,11 +1048,9 @@ export function convertNoteWikilinks(
|
|
|
1086
1048
|
}
|
|
1087
1049
|
|
|
1088
1050
|
for (const u of result.unresolved) {
|
|
1089
|
-
// Every class fails, and every class is worded by the shared table
|
|
1090
|
-
//
|
|
1091
|
-
//
|
|
1092
|
-
// two different things about the same mistake — and a class the pack
|
|
1093
|
-
// build alone knew how to describe is how they came apart before.
|
|
1051
|
+
// Every class fails, and every class is worded by the shared table,
|
|
1052
|
+
// so an author who ran the pack build first and the link checker second
|
|
1053
|
+
// is told one thing about one mistake.
|
|
1094
1054
|
//
|
|
1095
1055
|
// The note's name is appended rather than woven in: the message is the
|
|
1096
1056
|
// defect, the name is the context this build can add.
|
package/engine/homepage.mjs
CHANGED
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
* module, which is most of the packages that need a homepage and nothing else.
|
|
44
44
|
*
|
|
45
45
|
* **Published at the package root, addressed like every other note.** A
|
|
46
|
-
* homepage is
|
|
47
|
-
*
|
|
46
|
+
* homepage is rendered at `/<package>/` — the address a reader expects a
|
|
47
|
+
* package's front page at,
|
|
48
48
|
* and the one `package.json`'s `homepage` states. It still declares a
|
|
49
49
|
* `shortcode` — conventionally {@link HOMEPAGE_SHORTCODE} — because that is what
|
|
50
50
|
* a link is written with: `[[homepage-root|Text]]` is an ordinary wikilink, and
|
|
@@ -96,19 +96,6 @@ export const HOMEPAGE_FIELDS = Object.freeze([]);
|
|
|
96
96
|
*/
|
|
97
97
|
export const HOMEPAGE_SHORTCODE = "root";
|
|
98
98
|
|
|
99
|
-
/**
|
|
100
|
-
* The file a homepage is written to, relative to the package's site root.
|
|
101
|
-
*
|
|
102
|
-
* The mount's own `_index.md`: Hugo renders it as the `home` kind, at
|
|
103
|
-
* `baseURL` — which is `/<package>/`, the package's own address. One fixed
|
|
104
|
-
* destination rather than one derived from the note's address, because the
|
|
105
|
-
* homepage's address *is* the package root; the note's `shortcode` names the
|
|
106
|
-
* page in links and decides no file.
|
|
107
|
-
*
|
|
108
|
-
* @type {string}
|
|
109
|
-
*/
|
|
110
|
-
export const HOMEPAGE_DESTINATION = "_index.md";
|
|
111
|
-
|
|
112
99
|
/**
|
|
113
100
|
* Whether a note's frontmatter declares the homepage type.
|
|
114
101
|
*
|
|
@@ -337,8 +324,10 @@ function positionOfType(file) {
|
|
|
337
324
|
* authored `name.full` wins, because a front page may greet a reader
|
|
338
325
|
* differently from a package browser.
|
|
339
326
|
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
327
|
+
* A package with no manifest title — a `documentation` package has no manifest
|
|
328
|
+
* at all — takes `site.title`, the name its site is known by. It falls back to
|
|
329
|
+
* `contentPackage` last, so a package that states neither still yields a
|
|
330
|
+
* titled page rather than a blank heading.
|
|
342
331
|
*
|
|
343
332
|
* @param {object|null|undefined} fm - The note's frontmatter.
|
|
344
333
|
* @param {object} config - The resolved configuration.
|
|
@@ -351,7 +340,9 @@ export function homepageTitle(fm, config) {
|
|
|
351
340
|
config?.packageBuild?.manifest
|
|
352
341
|
);
|
|
353
342
|
const title = manifest?.title;
|
|
354
|
-
|
|
343
|
+
if (typeof title === "string" && title.trim()) return title;
|
|
344
|
+
const site = config?.site?.title;
|
|
345
|
+
return typeof site === "string" && site.trim() ? site : config.contentPackage;
|
|
355
346
|
}
|
|
356
347
|
|
|
357
348
|
/**
|
|
@@ -359,20 +350,17 @@ export function homepageTitle(fm, config) {
|
|
|
359
350
|
*
|
|
360
351
|
* The note's own, plus the two derived values every emitted page carries: the
|
|
361
352
|
* resolved `title`, and the package the build derived — no note declares one
|
|
362
|
-
* and the theme's
|
|
353
|
+
* and the theme's breadcrumbs read.
|
|
363
354
|
*
|
|
364
|
-
* **No `url` and no `slug`.**
|
|
365
|
-
*
|
|
366
|
-
* address to state; a content page states one because its file sits under the
|
|
367
|
-
* mount and its address does not.
|
|
355
|
+
* **No `url` and no `slug`.** The homepage publishes at the package root, so
|
|
356
|
+
* the page has no address to state.
|
|
368
357
|
*
|
|
369
|
-
* An authored `aliases` is dropped
|
|
370
|
-
*
|
|
371
|
-
* redirect stub at each one.
|
|
358
|
+
* An authored `aliases` is dropped, as it is on every other page: this build
|
|
359
|
+
* emits no redirects.
|
|
372
360
|
*
|
|
373
361
|
* `related` is derived like every other page's — what links to the homepage
|
|
374
362
|
* is a fact about the rest of the tree — so an authored value is dropped and
|
|
375
|
-
* the caller's block, where it has one, is
|
|
363
|
+
* the caller's block, where it has one, is set in its place.
|
|
376
364
|
*
|
|
377
365
|
* @param {object} fm - The note's frontmatter.
|
|
378
366
|
* @param {object} options - Options.
|
|
@@ -380,7 +368,7 @@ export function homepageTitle(fm, config) {
|
|
|
380
368
|
* @param {string} options.title - The resolved title.
|
|
381
369
|
* @param {object} [options.related] - The homepage's backlinks and mentions,
|
|
382
370
|
* where the site build found either.
|
|
383
|
-
* @returns {object} The frontmatter
|
|
371
|
+
* @returns {object} The frontmatter.
|
|
384
372
|
*/
|
|
385
373
|
export function homepageFrontmatter(fm, { contentPackage, title, related }) {
|
|
386
374
|
const data = { ...fm, package: contentPackage, title };
|
|
@@ -395,7 +383,7 @@ export function homepageFrontmatter(fm, { contentPackage, title, related }) {
|
|
|
395
383
|
*
|
|
396
384
|
* Reference-style links are deliberately not matched: a homepage is published
|
|
397
385
|
* verbatim, so a `[x][y]` whose definition sits in the body reaches the reader
|
|
398
|
-
* as
|
|
386
|
+
* as authored, and one whose definition is missing is a defect the
|
|
399
387
|
* rendered page shows for itself.
|
|
400
388
|
*
|
|
401
389
|
* @type {RegExp}
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
* **Minified site pages.** {@link minifyHtml} drops what a browser does not
|
|
16
|
+
* read: comments, and every run of white space but one character.
|
|
17
|
+
*
|
|
18
|
+
* What a reader sees is unchanged. A run of white space between two words or
|
|
19
|
+
* two tags still separates them, as one space; it is only made shorter. The
|
|
20
|
+
* places where white space is the content are kept exactly as written: a
|
|
21
|
+
* `<pre>`, a `<textarea>`, a `<script>` or `<style>`, and a verse's lines,
|
|
22
|
+
* which set their spacing as authored. A tag is kept as written too, its
|
|
23
|
+
* attribute values included.
|
|
24
|
+
*
|
|
25
|
+
* @module
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** The elements whose content is written out as it stands. */
|
|
29
|
+
const VERBATIM = /^<(pre|textarea|script|style)\b/i;
|
|
30
|
+
|
|
31
|
+
/** A verse's spans, whose white space is set as authored. */
|
|
32
|
+
const VERSE_SPAN = /\bclass\s*=\s*"[^"]*\bverse-(?:line|column|part)\b/i;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Minify a page.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} html - A complete document.
|
|
38
|
+
* @returns {string} The same document without comments and with each run of
|
|
39
|
+
* white space outside the verbatim elements cut to one character: a newline
|
|
40
|
+
* where the run held one, a space otherwise.
|
|
41
|
+
*/
|
|
42
|
+
export function minifyHtml(html) {
|
|
43
|
+
const source = String(html ?? "");
|
|
44
|
+
const pieces =
|
|
45
|
+
/<!--[\s\S]*?-->|<(pre|textarea|script|style)\b[^>]*>[\s\S]*?<\/\1\s*>|<[^>]*>|[^<]+|</gi;
|
|
46
|
+
const verse = [];
|
|
47
|
+
let out = "";
|
|
48
|
+
for (const [piece] of source.matchAll(pieces)) {
|
|
49
|
+
if (piece.startsWith("<!--")) {
|
|
50
|
+
// A conditional comment is read by the browser it names.
|
|
51
|
+
if (piece.startsWith("<!--[if")) out += piece;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (piece.startsWith("<")) {
|
|
55
|
+
if (VERBATIM.test(piece)) {
|
|
56
|
+
out += piece;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (/^<span\b/i.test(piece)) verse.push(verse.at(-1) || VERSE_SPAN.test(piece));
|
|
60
|
+
else if (/^<\/span\s*>/i.test(piece)) verse.pop();
|
|
61
|
+
out += piece;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
out +=
|
|
65
|
+
verse.at(-1) ? piece : (
|
|
66
|
+
piece.replace(/\s+/g, (run) => (run.includes("\n") ? "\n" : " "))
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return out.replace(/^\s+/, "");
|
|
70
|
+
}
|
package/engine/index.mjs
CHANGED
|
@@ -216,6 +216,12 @@ export * as siteBuild from "./site-build.mjs";
|
|
|
216
216
|
/** In-memory page rendering for an editor preview. */
|
|
217
217
|
export * as sitePreview from "./site-preview.mjs";
|
|
218
218
|
|
|
219
|
+
/** What an editor with a content index gives the preview, so it renders as the site does. */
|
|
220
|
+
export * as previewIndex from "./preview-index.mjs";
|
|
221
|
+
|
|
222
|
+
/** The one markdown-it configuration every target renders through. */
|
|
223
|
+
export * as contentRender from "./content-render.mjs";
|
|
224
|
+
|
|
219
225
|
/** Address rules every content tree is linted against: shape, uniqueness, alias. */
|
|
220
226
|
export * as contentLint from "./content-lint.mjs";
|
|
221
227
|
|
|
@@ -244,9 +250,6 @@ export * as pathnames from "./pathnames.mjs";
|
|
|
244
250
|
/** Resolving every link in a tree, and the ones that land nowhere. */
|
|
245
251
|
export * as contentLinks from "./content-links.mjs";
|
|
246
252
|
|
|
247
|
-
/** Wikilinks resolved to a **web URL** — the site half of the pair below. */
|
|
248
|
-
export * as webWikilinks from "./web-wikilinks.mjs";
|
|
249
|
-
|
|
250
253
|
/** Prepared SQL content tables, expanded into Markdown at compile time. */
|
|
251
254
|
export * as contentTables from "./content-tables.mjs";
|
|
252
255
|
|
|
@@ -262,10 +265,10 @@ export * as documentSubtypes from "./document-subtypes.mjs";
|
|
|
262
265
|
/** Which types carry documentation of their own, and where it is addressed. */
|
|
263
266
|
export * as itemDocs from "./item-docs.mjs";
|
|
264
267
|
|
|
265
|
-
/**
|
|
268
|
+
/** The link resolver: the pack index, the views, and each target's link form. */
|
|
266
269
|
export * as wikilinks from "./wikilinks.mjs";
|
|
267
270
|
|
|
268
|
-
/** What a `[[…]]` is, before
|
|
271
|
+
/** What a `[[…]]` is, before the resolver decides where it points. */
|
|
269
272
|
export * as wikilinkSyntax from "./wikilink-syntax.mjs";
|
|
270
273
|
|
|
271
274
|
/** The address index a site build resolves its wikilinks against. */
|