@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
|
@@ -0,0 +1,364 @@
|
|
|
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
|
+
* **The regular forms a language's own rules derive from its lexicon.** Each
|
|
16
|
+
* rule a language note states in machine-readable form gives a lexicon word
|
|
17
|
+
* further forms, and nothing else does:
|
|
18
|
+
*
|
|
19
|
+
* - **the noun plural**: `data.grammar.plural`, or the word's own `plural`;
|
|
20
|
+
* - **the verb pattern**: a verb's citation form with one ending of
|
|
21
|
+
* `data.grammar.verbs.persons`, which takes the place of a closing
|
|
22
|
+
* `data.grammar.verbs.replaces` where the pattern states one (see
|
|
23
|
+
* {@link module:engine/verb-pattern.verbForm});
|
|
24
|
+
* - **a replacing suffix**: a suffix of `data.affixes` that states `replaces`,
|
|
25
|
+
* or its `front` form, on a word whose class base its `attachesTo` names,
|
|
26
|
+
* or on any word where it states none: `vowel` takes the place of a closing
|
|
27
|
+
* vowel (a letter whose base is `a`, `e`, `i`, `o` or `u`), letters take the
|
|
28
|
+
* place of those letters, and a stem closing otherwise takes the suffix
|
|
29
|
+
* whole. An affix that states no `replaces` derives nothing here;
|
|
30
|
+
* - **agreement**: a word whose class base `data.grammar.agreement.classes`
|
|
31
|
+
* names, closing on one of `data.grammar.agreement.endings` (the longest
|
|
32
|
+
* that leaves a stem), also takes each other ending in its place, and each
|
|
33
|
+
* of those forms and the word itself takes the plural the
|
|
34
|
+
* `data.grammar.plural` rule gives;
|
|
35
|
+
* - **participles**: a verb closing on one of `data.grammar.participles.root`
|
|
36
|
+
* (the longest that leaves a root) drops it, and each suffix of `data.affixes`
|
|
37
|
+
* that `data.grammar.participles.affixes` names, by its `form`, attaches to
|
|
38
|
+
* the root as its `replaces` says, or whole where it states none. A
|
|
39
|
+
* participle closing on an agreement ending agrees.
|
|
40
|
+
*
|
|
41
|
+
* A word's `variants` are known forms as written, and each takes the rules
|
|
42
|
+
* its word takes: the plural rule, the verb pattern, the suffixes, agreement
|
|
43
|
+
* and participles. A word's own stated `plural` belongs to its citation form
|
|
44
|
+
* alone.
|
|
45
|
+
*
|
|
46
|
+
* The mapped-run word check ({@link module:engine/run-words}) and
|
|
47
|
+
* `package-build lexicon --derived` both read {@link derivedForms}, so what
|
|
48
|
+
* the command lists is what the check accepts.
|
|
49
|
+
*
|
|
50
|
+
* @module
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
import { pluralByRule, pluralOf } from "./dictionary-rows.mjs";
|
|
54
|
+
import { replaceClosing, verbForm, verbPattern } from "./verb-pattern.mjs";
|
|
55
|
+
import { parseWordClass } from "./word-classes.mjs";
|
|
56
|
+
import { WORD } from "./word-pattern.mjs";
|
|
57
|
+
|
|
58
|
+
/** Whether a value is a plain map. */
|
|
59
|
+
const isMap = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
60
|
+
|
|
61
|
+
/** A list value, or an empty one. */
|
|
62
|
+
const listOf = (value) => (Array.isArray(value) ? value : []);
|
|
63
|
+
|
|
64
|
+
/** The characters a compound is split at. */
|
|
65
|
+
const JOINS = /[–-]/u;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A word as it is compared: NFC, lowercase, `’` read as `'`.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} word - The word.
|
|
71
|
+
* @returns {string} Its folded form.
|
|
72
|
+
*/
|
|
73
|
+
export function foldWord(word) {
|
|
74
|
+
return word.normalize("NFC").toLowerCase().replace(/’/gu, "'");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Whether a character is a vowel: its base letter is a, e, i, o or u. */
|
|
78
|
+
const isVowel = (char) => /^[aeiou]/iu.test(char.normalize("NFD"));
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Add a text's words to a set, each whole and each part of a compound.
|
|
82
|
+
*
|
|
83
|
+
* @param {Set<string>} set - The set.
|
|
84
|
+
* @param {string} text - A form or a name.
|
|
85
|
+
*/
|
|
86
|
+
export function addWords(set, text) {
|
|
87
|
+
for (const [word] of text.matchAll(WORD)) {
|
|
88
|
+
const folded = foldWord(word);
|
|
89
|
+
set.add(folded);
|
|
90
|
+
for (const part of folded.split(JOINS)) if (part) set.add(part);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The form a suffix makes of a stem.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} stem - The stem, folded.
|
|
98
|
+
* @param {string} suffix - The suffix, folded, with no hyphen.
|
|
99
|
+
* @param {string|undefined} replaces - The affix's `replaces`; none adds the
|
|
100
|
+
* suffix whole.
|
|
101
|
+
* @returns {string} The derived form.
|
|
102
|
+
*/
|
|
103
|
+
function attach(stem, suffix, replaces) {
|
|
104
|
+
if (replaces === "vowel") {
|
|
105
|
+
const last = Array.from(stem).at(-1);
|
|
106
|
+
return last !== undefined && isVowel(last) ?
|
|
107
|
+
stem.slice(0, -last.length) + suffix
|
|
108
|
+
: stem + suffix;
|
|
109
|
+
}
|
|
110
|
+
return replaceClosing(stem, suffix, replaces === undefined ? undefined : foldWord(replaces));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* An affix's suffixes: its `form` and its `front` form, each written `-x`,
|
|
115
|
+
* folded and without the hyphen.
|
|
116
|
+
*
|
|
117
|
+
* @param {Record<string, any>} affix - One `data.affixes` entry.
|
|
118
|
+
* @returns {string[]} The suffixes.
|
|
119
|
+
*/
|
|
120
|
+
function suffixesOf(affix) {
|
|
121
|
+
const out = [];
|
|
122
|
+
for (const written of [affix.form, affix.front]) {
|
|
123
|
+
if (typeof written !== "string") continue;
|
|
124
|
+
const form = foldWord(written.trim());
|
|
125
|
+
if (!/^[-–]/u.test(form) || /[-–]$/u.test(form)) continue;
|
|
126
|
+
const suffix = form.replace(/^[-–]+/u, "");
|
|
127
|
+
if (suffix) out.push(suffix);
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The suffixes a language's affixes state `replaces` for, each with the class
|
|
134
|
+
* bases its `attachesTo` names.
|
|
135
|
+
*
|
|
136
|
+
* @param {Record<string, any>} data - The language note's `data`.
|
|
137
|
+
* @returns {Array<{suffix: string, replaces: string, classes: ReadonlySet<string>|undefined}>}
|
|
138
|
+
* The suffixes, folded; `classes` is `undefined` where the affix attaches to
|
|
139
|
+
* every class.
|
|
140
|
+
*/
|
|
141
|
+
function replacingSuffixes(data) {
|
|
142
|
+
const out = [];
|
|
143
|
+
for (const affix of listOf(data.affixes)) {
|
|
144
|
+
if (!isMap(affix) || typeof affix.replaces !== "string") continue;
|
|
145
|
+
const classes =
|
|
146
|
+
Array.isArray(affix.attachesTo) ?
|
|
147
|
+
new Set(
|
|
148
|
+
affix.attachesTo
|
|
149
|
+
.filter((item) => typeof item === "string")
|
|
150
|
+
.map((item) => item.trim()),
|
|
151
|
+
)
|
|
152
|
+
: undefined;
|
|
153
|
+
for (const suffix of suffixesOf(affix))
|
|
154
|
+
out.push({ suffix, replaces: affix.replaces, classes });
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The strings of a list, trimmed and folded, keeping those with letters.
|
|
161
|
+
*
|
|
162
|
+
* @param {unknown} value - The list.
|
|
163
|
+
* @returns {string[]} The strings.
|
|
164
|
+
*/
|
|
165
|
+
const foldedStrings = (value) =>
|
|
166
|
+
listOf(value)
|
|
167
|
+
.filter((item) => typeof item === "string" && item.trim())
|
|
168
|
+
.map((item) => foldWord(item.trim()));
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* A language's agreement rule, as derivation reads it.
|
|
172
|
+
*
|
|
173
|
+
* @typedef {object} Agreement
|
|
174
|
+
* @property {ReadonlySet<string>} classes - The class bases that agree.
|
|
175
|
+
* @property {readonly string[]} endings - The endings, folded, longest first.
|
|
176
|
+
*/
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Read a language's `data.grammar.agreement`.
|
|
180
|
+
*
|
|
181
|
+
* @param {unknown} value - The authored value.
|
|
182
|
+
* @returns {Agreement|undefined} The rule, or `undefined` where none is stated
|
|
183
|
+
* in full.
|
|
184
|
+
*/
|
|
185
|
+
function agreementOf(value) {
|
|
186
|
+
if (!isMap(value)) return undefined;
|
|
187
|
+
const classes = listOf(value.classes).filter((item) => typeof item === "string");
|
|
188
|
+
const endings = [
|
|
189
|
+
...new Set(
|
|
190
|
+
listOf(value.endings)
|
|
191
|
+
.filter((item) => typeof item === "string")
|
|
192
|
+
.map((item) => foldWord(item.trim())),
|
|
193
|
+
),
|
|
194
|
+
];
|
|
195
|
+
if (!classes.length || endings.length < 2) return undefined;
|
|
196
|
+
return {
|
|
197
|
+
classes: new Set(classes.map((item) => item.trim())),
|
|
198
|
+
endings: endings.sort((a, b) => b.length - a.length),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The forms an agreeing word takes beside its own.
|
|
204
|
+
*
|
|
205
|
+
* @param {string} form - The word, folded.
|
|
206
|
+
* @param {Agreement} agreement - The rule.
|
|
207
|
+
* @param {Record<string, any>|undefined} grammar - The language's grammar,
|
|
208
|
+
* for its plural rule.
|
|
209
|
+
* @returns {string[]} The forms; none where the word closes on no ending.
|
|
210
|
+
*/
|
|
211
|
+
function agreeingForms(form, agreement, grammar) {
|
|
212
|
+
const ending = agreement.endings.find((one) => form.length > one.length && form.endsWith(one));
|
|
213
|
+
if (ending === undefined) return [];
|
|
214
|
+
const stem = form.slice(0, form.length - ending.length);
|
|
215
|
+
const singulars = agreement.endings.map((one) => stem + one);
|
|
216
|
+
const out = singulars.filter((one) => one !== form);
|
|
217
|
+
for (const one of singulars) {
|
|
218
|
+
const plural = pluralByRule(one, grammar);
|
|
219
|
+
if (plural) out.push(foldWord(plural));
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* A language's participle rule, as derivation reads it.
|
|
226
|
+
*
|
|
227
|
+
* @typedef {object} Participles
|
|
228
|
+
* @property {readonly string[]} root - The root endings, folded, longest
|
|
229
|
+
* first.
|
|
230
|
+
* @property {Array<{suffix: string, replaces: string|undefined}>} suffixes -
|
|
231
|
+
* The participle suffixes, folded.
|
|
232
|
+
*/
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Read a language's `data.grammar.participles` against its affixes.
|
|
236
|
+
*
|
|
237
|
+
* @param {unknown} value - The authored value.
|
|
238
|
+
* @param {Record<string, any>} data - The language note's `data`.
|
|
239
|
+
* @returns {Participles|undefined} The rule, or `undefined` where it names no
|
|
240
|
+
* root ending or no suffix the affixes state.
|
|
241
|
+
*/
|
|
242
|
+
function participlesOf(value, data) {
|
|
243
|
+
if (!isMap(value)) return undefined;
|
|
244
|
+
const root = [...new Set(foldedStrings(value.root))].sort((a, b) => b.length - a.length);
|
|
245
|
+
const named = new Set(
|
|
246
|
+
listOf(value.affixes)
|
|
247
|
+
.filter((item) => typeof item === "string")
|
|
248
|
+
.map((item) => item.trim()),
|
|
249
|
+
);
|
|
250
|
+
const suffixes = [];
|
|
251
|
+
for (const affix of listOf(data.affixes)) {
|
|
252
|
+
if (!isMap(affix) || typeof affix.form !== "string" || !named.has(affix.form.trim()))
|
|
253
|
+
continue;
|
|
254
|
+
const replaces = typeof affix.replaces === "string" ? affix.replaces : undefined;
|
|
255
|
+
for (const suffix of suffixesOf(affix)) suffixes.push({ suffix, replaces });
|
|
256
|
+
}
|
|
257
|
+
if (!root.length || !suffixes.length) return undefined;
|
|
258
|
+
return { root, suffixes };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The participles of a verb.
|
|
263
|
+
*
|
|
264
|
+
* @param {string} citation - The citation form, folded.
|
|
265
|
+
* @param {Participles} participles - The rule.
|
|
266
|
+
* @returns {string[]} The participles; none where the verb closes on no root
|
|
267
|
+
* ending.
|
|
268
|
+
*/
|
|
269
|
+
function participleForms(citation, participles) {
|
|
270
|
+
const ending = participles.root.find(
|
|
271
|
+
(one) => citation.length > one.length && citation.endsWith(one),
|
|
272
|
+
);
|
|
273
|
+
if (ending === undefined) return [];
|
|
274
|
+
const root = citation.slice(0, citation.length - ending.length);
|
|
275
|
+
return participles.suffixes.map(({ suffix, replaces }) => attach(root, suffix, replaces));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* One form a rule derives from a lexicon word.
|
|
280
|
+
*
|
|
281
|
+
* @typedef {object} DerivedForm
|
|
282
|
+
* @property {string} form - The form, folded.
|
|
283
|
+
* @property {Record<string, any>} word - The `data.words` entry it comes from.
|
|
284
|
+
*/
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Every form the language's rules derive from its lexicon words, and each
|
|
288
|
+
* declared variant of a word, in `data.words` order, each once, leaving out a
|
|
289
|
+
* form that is a lexicon word.
|
|
290
|
+
*
|
|
291
|
+
* @param {Record<string, any>|undefined} data - The language note's `data`.
|
|
292
|
+
* @returns {DerivedForm[]} The forms.
|
|
293
|
+
*/
|
|
294
|
+
export function derivedForms(data) {
|
|
295
|
+
const words = listOf(data?.words).filter(
|
|
296
|
+
(word) => isMap(word) && typeof word.form === "string",
|
|
297
|
+
);
|
|
298
|
+
const grammar = isMap(data?.grammar) ? data.grammar : undefined;
|
|
299
|
+
const verbs = verbPattern(grammar?.verbs);
|
|
300
|
+
const replaces = verbs?.replaces === undefined ? undefined : foldWord(verbs.replaces);
|
|
301
|
+
const suffixes = replacingSuffixes(data ?? {});
|
|
302
|
+
const agreement = agreementOf(grammar?.agreement);
|
|
303
|
+
const participles = participlesOf(grammar?.participles, data ?? {});
|
|
304
|
+
const seen = new Set(words.map((word) => foldWord(word.form)));
|
|
305
|
+
const out = [];
|
|
306
|
+
for (const word of words) {
|
|
307
|
+
const add = (form) => {
|
|
308
|
+
const folded = foldWord(form);
|
|
309
|
+
if (!folded || seen.has(folded)) return;
|
|
310
|
+
seen.add(folded);
|
|
311
|
+
out.push({ form: folded, word });
|
|
312
|
+
};
|
|
313
|
+
const base = parseWordClass(word.class).base;
|
|
314
|
+
const variants = listOf(word.variants).filter(
|
|
315
|
+
(variant) => typeof variant === "string" && variant.trim() !== "",
|
|
316
|
+
);
|
|
317
|
+
for (const [at, written] of [word.form, ...variants].entries()) {
|
|
318
|
+
const folded = foldWord(written);
|
|
319
|
+
if (at > 0) add(written);
|
|
320
|
+
const plural =
|
|
321
|
+
at === 0 ? pluralOf(word, grammar)
|
|
322
|
+
: base === "n" ? pluralByRule(written, grammar)
|
|
323
|
+
: null;
|
|
324
|
+
if (plural) add(plural);
|
|
325
|
+
if (verbs && base === "v")
|
|
326
|
+
for (const { ending } of verbs.endings)
|
|
327
|
+
add(verbForm(folded, foldWord(ending), replaces));
|
|
328
|
+
const attaching = suffixes.filter(
|
|
329
|
+
({ classes }) => classes === undefined || (base !== undefined && classes.has(base)),
|
|
330
|
+
);
|
|
331
|
+
if (attaching.length)
|
|
332
|
+
for (const [stem] of written.matchAll(WORD)) {
|
|
333
|
+
const one = foldWord(stem);
|
|
334
|
+
for (const { suffix, replaces: closing } of attaching)
|
|
335
|
+
add(attach(one, suffix, closing));
|
|
336
|
+
}
|
|
337
|
+
if (agreement && base !== undefined && agreement.classes.has(base))
|
|
338
|
+
for (const form of agreeingForms(folded, agreement, grammar)) add(form);
|
|
339
|
+
if (participles && base === "v")
|
|
340
|
+
for (const participle of participleForms(folded, participles)) {
|
|
341
|
+
add(participle);
|
|
342
|
+
if (agreement)
|
|
343
|
+
for (const form of agreeingForms(participle, agreement, grammar)) add(form);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return out;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* The words a language note accepts of its own: its forms, and what its rules
|
|
352
|
+
* derive from them (see {@link derivedForms}), each whole and each part of a
|
|
353
|
+
* compound.
|
|
354
|
+
*
|
|
355
|
+
* @param {Record<string, any>} data - The language note's `data`.
|
|
356
|
+
* @returns {Set<string>} The words, folded.
|
|
357
|
+
*/
|
|
358
|
+
export function lexiconWords(data) {
|
|
359
|
+
const accepted = new Set();
|
|
360
|
+
for (const word of listOf(data?.words))
|
|
361
|
+
if (isMap(word) && typeof word.form === "string") addWords(accepted, word.form);
|
|
362
|
+
for (const { form } of derivedForms(data)) addWords(accepted, form);
|
|
363
|
+
return accepted;
|
|
364
|
+
}
|
package/engine/word-rows.mjs
CHANGED
|
@@ -69,8 +69,18 @@ export const WORD_COLUMNS = Object.freeze([
|
|
|
69
69
|
/** Every column of the `glossary` relation, in order. */
|
|
70
70
|
export const GLOSSARY_COLUMNS = Object.freeze(["note", "position", ...GLOSSARY_ENTRY_KEYS]);
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* The affix's own keys an affix row carries: all but `native`, its writings
|
|
74
|
+
* under a logographic map, which `package-build lexicon` resolves.
|
|
75
|
+
*/
|
|
76
|
+
const AFFIX_ROW_KEYS = Object.freeze(AFFIX_KEYS.filter((key) => key !== "native"));
|
|
77
|
+
|
|
72
78
|
/** Every column of the `affixes` relation, in order. */
|
|
73
|
-
export const AFFIX_COLUMNS = Object.freeze([
|
|
79
|
+
export const AFFIX_COLUMNS = Object.freeze([
|
|
80
|
+
...WORD_IDENTITY_COLUMNS,
|
|
81
|
+
...AFFIX_ROW_KEYS,
|
|
82
|
+
"written",
|
|
83
|
+
]);
|
|
74
84
|
|
|
75
85
|
/** Every column of the `fields` relation, in order. */
|
|
76
86
|
export const FIELD_COLUMNS = Object.freeze([...WORD_IDENTITY_COLUMNS, ...FIELD_KEYS]);
|
|
@@ -322,7 +332,7 @@ function languageListRows(records, list, keys) {
|
|
|
322
332
|
* @returns {object[]} One row per affix, in record then entry order.
|
|
323
333
|
*/
|
|
324
334
|
export function affixRows(records) {
|
|
325
|
-
return languageListRows(records, "affixes",
|
|
335
|
+
return languageListRows(records, "affixes", AFFIX_ROW_KEYS).map((row) => ({
|
|
326
336
|
...row,
|
|
327
337
|
written: row.zero === true ? ZERO_AFFIX : row.form,
|
|
328
338
|
}));
|
package/engine/word-terms.mjs
CHANGED
|
@@ -126,7 +126,8 @@ export const WORD_NATIVE_FIELDS = Object.freeze([
|
|
|
126
126
|
required: true,
|
|
127
127
|
shape: "the word in the script, as the map's characters",
|
|
128
128
|
describe:
|
|
129
|
-
"The word as the script writes it, where the map's rows would write it otherwise
|
|
129
|
+
"The word as the script writes it, where the map's rows would write it otherwise, " +
|
|
130
|
+
"or as a logographic map writes it.",
|
|
130
131
|
}),
|
|
131
132
|
]);
|
|
132
133
|
|
|
@@ -150,7 +151,7 @@ export const WORD_FIELDS = Object.freeze([
|
|
|
150
151
|
entries: Object.freeze({ kind: "string", shape: "a form, bare" }),
|
|
151
152
|
describe:
|
|
152
153
|
"The word's other forms, such as a long form beside a short one; a marked use of " +
|
|
153
|
-
"one cites the word.",
|
|
154
|
+
"one cites the word, and a mapped run accepts it and the forms the language's rules derive from it.",
|
|
154
155
|
}),
|
|
155
156
|
Object.freeze({
|
|
156
157
|
name: "name_sort",
|
|
@@ -191,6 +192,15 @@ export const WORD_FIELDS = Object.freeze([
|
|
|
191
192
|
describe:
|
|
192
193
|
"The meaning, in brief; a list states one sense per entry, which a dictionary numbers.",
|
|
193
194
|
}),
|
|
195
|
+
Object.freeze({
|
|
196
|
+
name: "concept",
|
|
197
|
+
kind: "string-or-list",
|
|
198
|
+
shape: "a core concept's id, or a list of them",
|
|
199
|
+
entries: Object.freeze({ kind: "string", shape: "a core concept's id" }),
|
|
200
|
+
describe:
|
|
201
|
+
"The core concepts the word serves, each an `id` of a `data.concepts` list. The " +
|
|
202
|
+
"core report counts the word toward them.",
|
|
203
|
+
}),
|
|
194
204
|
Object.freeze({
|
|
195
205
|
name: "source",
|
|
196
206
|
kind: "map",
|
|
@@ -250,8 +260,10 @@ export const WORD_FIELDS = Object.freeze([
|
|
|
250
260
|
fields: WORD_NATIVE_FIELDS,
|
|
251
261
|
}),
|
|
252
262
|
describe:
|
|
253
|
-
"The word's native form under a map
|
|
254
|
-
"
|
|
263
|
+
"The word's native form under a map. Under a map that spells by its rows it is " +
|
|
264
|
+
"stated only where the rows spell the word differently, and the word's derived " +
|
|
265
|
+
"forms are spelled by the rows; under a logographic map it is the word's writing, " +
|
|
266
|
+
"which its derived forms and the words built on it are written from.",
|
|
255
267
|
}),
|
|
256
268
|
]);
|
|
257
269
|
|
|
@@ -368,6 +380,15 @@ export const AFFIX_FIELDS = Object.freeze([
|
|
|
368
380
|
"What it takes the place of at the close of the stem: `vowel` for any closing " +
|
|
369
381
|
"vowel, or the letters it replaces; added to the whole stem where absent.",
|
|
370
382
|
}),
|
|
383
|
+
Object.freeze({
|
|
384
|
+
name: "attachesTo",
|
|
385
|
+
kind: "list",
|
|
386
|
+
shape: "a list of word classes, such as `[n, adj]`",
|
|
387
|
+
entries: Object.freeze({ kind: "string", shape: "a word class, such as `n`" }),
|
|
388
|
+
describe:
|
|
389
|
+
"The word classes it attaches to, by their base; every class where absent. A " +
|
|
390
|
+
"suffix stating `replaces` derives forms of these classes' words alone.",
|
|
391
|
+
}),
|
|
371
392
|
Object.freeze({
|
|
372
393
|
name: "lengthens",
|
|
373
394
|
kind: "boolean",
|
|
@@ -402,6 +423,19 @@ export const AFFIX_FIELDS = Object.freeze([
|
|
|
402
423
|
shape: "a tone pattern, such as `L`",
|
|
403
424
|
describe: "The tone pattern the affix carries, written as a word's `tones` is.",
|
|
404
425
|
}),
|
|
426
|
+
Object.freeze({
|
|
427
|
+
name: "native",
|
|
428
|
+
kind: "list",
|
|
429
|
+
shape: "a list of `{ map, font?, text }`",
|
|
430
|
+
entries: Object.freeze({
|
|
431
|
+
kind: "map",
|
|
432
|
+
shape: "a map — `{ map, font?, text }`",
|
|
433
|
+
fields: WORD_NATIVE_FIELDS,
|
|
434
|
+
}),
|
|
435
|
+
describe:
|
|
436
|
+
"The affix's writing under a logographic map, which a word whose `built` names the " +
|
|
437
|
+
"affix is written with.",
|
|
438
|
+
}),
|
|
405
439
|
]);
|
|
406
440
|
|
|
407
441
|
/** The keys of {@link WORD_FIELD_FIELDS}, in declared order. */
|
package/manifest.mjs
CHANGED
|
@@ -121,10 +121,9 @@ export const HOMEPAGE_ORIGIN = "https://www.heroiclands.org";
|
|
|
121
121
|
* to drift — and the one that drifts is this one, because nothing fetches `url`
|
|
122
122
|
* the way Foundry fetches `manifest`, so a wrong value is never reported.
|
|
123
123
|
*
|
|
124
|
-
*
|
|
125
|
-
* source: it
|
|
126
|
-
*
|
|
127
|
-
* repository keeps its site configuration.
|
|
124
|
+
* `package.json`'s `homepage` says the same thing, and is deliberately not the
|
|
125
|
+
* source: reading it would mean the packaging half depending on the site's
|
|
126
|
+
* configuration.
|
|
128
127
|
*
|
|
129
128
|
* @param {string} contentPackage - The package's content-package name.
|
|
130
129
|
* @returns {string} The homepage URL, with its trailing slash.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "24.0.0",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -123,17 +123,18 @@
|
|
|
123
123
|
"engine",
|
|
124
124
|
"githooks",
|
|
125
125
|
"hm3",
|
|
126
|
-
"hugo-theme",
|
|
127
126
|
"index.mjs",
|
|
128
127
|
"lang.mjs",
|
|
129
128
|
"manifest.mjs",
|
|
130
129
|
"markdownlint-config.mjs",
|
|
131
130
|
"prettier-config.mjs",
|
|
132
131
|
"release.mjs",
|
|
132
|
+
"site-theme",
|
|
133
133
|
"sohl",
|
|
134
134
|
"stage.mjs",
|
|
135
135
|
"templates.mjs",
|
|
136
|
-
"types"
|
|
136
|
+
"types",
|
|
137
|
+
"vendor"
|
|
137
138
|
],
|
|
138
139
|
"dependencies": {
|
|
139
140
|
"@duckdb/node-api": "^1.5.5-r.4",
|
|
@@ -151,16 +152,16 @@
|
|
|
151
152
|
"fflate": "^0.8.3",
|
|
152
153
|
"flesch": "^2.0.1",
|
|
153
154
|
"glob": "^13.0.6",
|
|
154
|
-
"gray-matter": "^4.0.3",
|
|
155
155
|
"handlebars": "^4.7.10",
|
|
156
|
+
"highlight.js": "^11.12.0",
|
|
156
157
|
"ignore": "^7.0.12",
|
|
158
|
+
"js-yaml": "^5.5.0",
|
|
157
159
|
"jsonc-parser": "^3.3.1",
|
|
158
160
|
"loglevel": "^1.9.2",
|
|
159
161
|
"loglevel-plugin-prefix": "^0.8.4",
|
|
160
162
|
"markdown-it": "^15.0.0",
|
|
161
163
|
"markdown-it-deflist": "^4.0.0",
|
|
162
164
|
"markdown-it-footnote": "^4.0.0",
|
|
163
|
-
"markdownlint": "^0.42.0",
|
|
164
165
|
"minimatch": "^10.2.6",
|
|
165
166
|
"pagefind": "^1.5.2",
|
|
166
167
|
"prettier": "^3.9.6",
|
|
@@ -173,7 +174,6 @@
|
|
|
173
174
|
"retext-simplify": "^8.0.0",
|
|
174
175
|
"retext-stringify": "^4.0.0",
|
|
175
176
|
"sharp": "^0.35.4",
|
|
176
|
-
"smol-toml": "^1.8.0",
|
|
177
177
|
"ssh2-sftp-client": "^12.1.1",
|
|
178
178
|
"syllable": "^5.0.1",
|
|
179
179
|
"typescript": "^6.0.3",
|
|
@@ -185,6 +185,8 @@
|
|
|
185
185
|
"devDependencies": {
|
|
186
186
|
"@changesets/cli": "^3.0.0",
|
|
187
187
|
"@types/node": "^26.2.0",
|
|
188
|
+
"esbuild": "^0.28.2",
|
|
189
|
+
"markdownlint": "^0.42.0",
|
|
188
190
|
"npm-run-all": "^4.1.5",
|
|
189
191
|
"vitest": "^5.0.0"
|
|
190
192
|
},
|
|
@@ -193,7 +195,8 @@
|
|
|
193
195
|
"test:watch": "vitest",
|
|
194
196
|
"build": "node bin/package-build.mjs ci",
|
|
195
197
|
"deploy:npm": "node ci/publish-npm.mjs",
|
|
196
|
-
"prepack": "run-s build:types build:asset-index",
|
|
198
|
+
"prepack": "run-s build:vendor build:types build:asset-index",
|
|
199
|
+
"build:vendor": "node scripts/vendor-markdownlint.mjs",
|
|
197
200
|
"build:types": "tsc -p tsconfig.dts.json",
|
|
198
201
|
"build:asset-index": "node ci/emit-asset-index.mjs",
|
|
199
202
|
"format": "node ci/prettier.mjs --write",
|
|
@@ -210,7 +213,7 @@
|
|
|
210
213
|
"lint:content-format:fields": "node bin/package-build.mjs content-format fields --fields sohl && node bin/package-build.mjs content-format fields --fields hm3",
|
|
211
214
|
"changeset": "changeset",
|
|
212
215
|
"changeset:version": "changeset version && node bin/package-build.mjs changelog group && npm install --package-lock-only",
|
|
213
|
-
"prepare": "git config core.hooksPath .githooks || true",
|
|
216
|
+
"prepare": "(git config core.hooksPath .githooks || true) && (test ! -e scripts/vendor-markdownlint.mjs || npm run build:vendor)",
|
|
214
217
|
"build:deps": "node bin/package-build.mjs deps fetch"
|
|
215
218
|
},
|
|
216
219
|
"engines": {
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# Which banner images exist in the shared artwork set.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
3
|
+
# The hero banner resolves a page's hero image to a path under
|
|
4
4
|
# `images/banners/`, and that path is emitted into a `background-image` with no
|
|
5
5
|
# way to test it: the artwork is served from the consuming site's
|
|
6
|
-
# `params.cdnBaseURL`, and
|
|
7
|
-
# So a banner that was never drawn would 404 in silence — the band
|
|
8
|
-
# with its title, palette and gradient intact and a dead image URL
|
|
9
|
-
# and nothing fails: not the build, not
|
|
6
|
+
# `params.cdnBaseURL`, and the build cannot ask a remote host whether a URL
|
|
7
|
+
# exists. So a banner that was never drawn would 404 in silence — the band
|
|
8
|
+
# renders with its title, palette and gradient intact and a dead image URL
|
|
9
|
+
# behind it, and nothing fails: not the build, not the deploy guard.
|
|
10
10
|
#
|
|
11
11
|
# This file is the declaration that replaces that missing test. A name listed
|
|
12
12
|
# here is asserted to exist; a resolved banner whose name is absent falls back
|
|
13
|
-
# to `default.webp` and
|
|
13
|
+
# to `default.webp` and the build warns once for it.
|
|
14
14
|
#
|
|
15
15
|
# It speaks for the names this theme resolves itself — a hand-authored
|
|
16
16
|
# `banner:` and the subtype defaults below. A note's own `data.banner` arrives
|
|
@@ -21,10 +21,6 @@
|
|
|
21
21
|
# (`ci/check-banners.mjs`, part of `npm run lint`) fetches every name below
|
|
22
22
|
# from a real asset host and fails on any non-200. Add a name here only after
|
|
23
23
|
# the file is published, or the check will say so.
|
|
24
|
-
#
|
|
25
|
-
# A consumer publishing its own artwork set overrides the whole list by
|
|
26
|
-
# shipping its own `data/banners.yaml` — Hugo reads the site's data directory
|
|
27
|
-
# in preference to the theme's.
|
|
28
24
|
|
|
29
25
|
# The asset root every name below is resolved under, and the name used when a
|
|
30
26
|
# resolved banner is not in `available`. Both are relative to the consuming
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{{!-- SPDX-License-Identifier: GPL-3.0-or-later --}}
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
<title>{{head.title}}</title>
|
|
8
|
+
<meta name="description" content="{{head.description}}">
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Lora:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600&display=swap" rel="stylesheet">
|
|
12
|
+
<link rel="stylesheet" href="{{site.urls.style}}">
|
|
13
|
+
{{#if site.urls.scriptFonts}}
|
|
14
|
+
<link rel="stylesheet" href="{{site.urls.scriptFonts}}">
|
|
15
|
+
{{/if}}
|
|
16
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.3.1/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer">
|
|
17
|
+
<link rel="stylesheet" href="{{site.urls.icons}}">
|
|
18
|
+
<link rel="stylesheet" href="{{site.urls.code}}">
|
|
19
|
+
{{#if site.feed}}
|
|
20
|
+
<link rel="alternate" type="application/atom+xml" title="{{site.feed.title}}" href="{{site.feed.url}}">
|
|
21
|
+
{{/if}}
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
{{> header site}}
|
|
25
|
+
<main>
|
|
26
|
+
{{{body}}}
|
|
27
|
+
</main>
|
|
28
|
+
{{> footer site}}
|
|
29
|
+
{{> copy-buttons}}
|
|
30
|
+
</body>
|
|
31
|
+
</html>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{{!-- SPDX-License-Identifier: GPL-3.0-or-later --}}
|
|
2
|
+
{{#if hero}}
|
|
3
|
+
{{> hero-banner hero}}
|
|
4
|
+
{{/if}}
|
|
5
|
+
<article class="single">
|
|
6
|
+
<div class="single-content">
|
|
7
|
+
{{#if body}}
|
|
8
|
+
<div class="single-body">
|
|
9
|
+
{{{body}}}
|
|
10
|
+
</div>
|
|
11
|
+
{{/if}}
|
|
12
|
+
{{#if posts}}
|
|
13
|
+
<ul class="blog-list">
|
|
14
|
+
{{#each posts}}
|
|
15
|
+
<li class="blog-list-item">
|
|
16
|
+
{{#if date}}
|
|
17
|
+
<time class="blog-list-date" datetime="{{date.iso}}">{{date.text}}</time>
|
|
18
|
+
{{/if}}
|
|
19
|
+
<a class="blog-list-title" href="{{url}}">{{title}}</a>
|
|
20
|
+
{{#if preview}}
|
|
21
|
+
<p class="blog-list-desc">{{preview}}</p>
|
|
22
|
+
{{/if}}
|
|
23
|
+
</li>
|
|
24
|
+
{{/each}}
|
|
25
|
+
</ul>
|
|
26
|
+
{{else}}
|
|
27
|
+
<p class="page-list-empty">No posts yet.</p>
|
|
28
|
+
{{/if}}
|
|
29
|
+
</div>
|
|
30
|
+
</article>
|