@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
|
@@ -0,0 +1,295 @@
|
|
|
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
|
+
* **How a script marks emphasis.** A script note — a `skill` of
|
|
16
|
+
* `subType: script` — names the faces it is written in and its scribal
|
|
17
|
+
* convention for a stressed word:
|
|
18
|
+
*
|
|
19
|
+
* ```yaml
|
|
20
|
+
* data:
|
|
21
|
+
* script:
|
|
22
|
+
* fonts: [semaron-kuriale]
|
|
23
|
+
* emphasis: rubric
|
|
24
|
+
* strongEmphasis: overline
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* - **`fonts`** lists installed faces by the slug `font=` names them by. A
|
|
28
|
+
* face belongs to one script.
|
|
29
|
+
* - **`emphasis`** is `rubric` (red text) or `overline` (a line above the
|
|
30
|
+
* word), and sets both `*…*` and `**…**`.
|
|
31
|
+
* - **`strongEmphasis`**, where stated, sets `**…**` alone.
|
|
32
|
+
*
|
|
33
|
+
* Inside a run in one of those faces, `*…*` and `**…**` stay an `<em>` and a
|
|
34
|
+
* `<strong>` on the site and in Foundry, carrying {@link module:engine/script-emphasis-terms.EMPHASIS_CLASSES}'s
|
|
35
|
+
* class, and the book sets them in its rubric ink or under an overline. A face
|
|
36
|
+
* no script note lists sets its emphasis plain: the element with no class, in
|
|
37
|
+
* the face's Regular. See {@link module:engine/content-scripts.renderRuns}.
|
|
38
|
+
*
|
|
39
|
+
* @module
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
43
|
+
import { isNoteRecord } from "./index-records.mjs";
|
|
44
|
+
import { nearest } from "./near-miss.mjs";
|
|
45
|
+
import { FONTS_PACKAGE } from "./script-fonts.mjs";
|
|
46
|
+
import {
|
|
47
|
+
EMPHASIS_CONVENTIONS,
|
|
48
|
+
LANGUAGE_EMPHASES,
|
|
49
|
+
SCRIPT_SUBTYPE,
|
|
50
|
+
} from "./script-emphasis-terms.mjs";
|
|
51
|
+
import { GENERIC_FAMILIES } from "./text-maps.mjs";
|
|
52
|
+
import { LANGUAGE_SUBTYPE } from "./word-terms.mjs";
|
|
53
|
+
|
|
54
|
+
export {
|
|
55
|
+
EMPHASIS_CLASSES,
|
|
56
|
+
EMPHASIS_CONVENTIONS,
|
|
57
|
+
LANGUAGE_EMPHASES,
|
|
58
|
+
} from "./script-emphasis-terms.mjs";
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* One face's conventions, one for each level of emphasis.
|
|
62
|
+
*
|
|
63
|
+
* @typedef {object} Emphasis
|
|
64
|
+
* @property {"rubric"|"overline"} em - What `*…*` is set by.
|
|
65
|
+
* @property {"rubric"|"overline"} strong - What `**…**` is set by.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A convention a language note states.
|
|
70
|
+
*
|
|
71
|
+
* @typedef {"words"|"rubric"|"overline"} LanguageEmphasis
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/** Whether a value is a plain map. */
|
|
75
|
+
const isMap = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
76
|
+
|
|
77
|
+
/** Whether a value is one of the conventions. */
|
|
78
|
+
const isConvention = (value) => EMPHASIS_CONVENTIONS.includes(value);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Each face's conventions, from the script notes among the index records. The
|
|
82
|
+
* first note to list a face keeps it; a second is the lint's to report, and a
|
|
83
|
+
* note whose convention is unknown gives none.
|
|
84
|
+
*
|
|
85
|
+
* @param {Iterable<Record<string, any>>|undefined} records - Index records.
|
|
86
|
+
* @returns {Map<string, Emphasis>} The conventions, by face slug.
|
|
87
|
+
*/
|
|
88
|
+
export function emphasesFromRecords(records) {
|
|
89
|
+
const out = new Map();
|
|
90
|
+
for (const record of records ?? []) {
|
|
91
|
+
if (!record || !isNoteRecord(record)) continue;
|
|
92
|
+
if (
|
|
93
|
+
record.type !== "skill" ||
|
|
94
|
+
String(record.subType ?? "").toLowerCase() !== SCRIPT_SUBTYPE
|
|
95
|
+
)
|
|
96
|
+
continue;
|
|
97
|
+
const script = record.data?.script;
|
|
98
|
+
if (!isMap(script) || !Array.isArray(script.fonts) || !isConvention(script.emphasis))
|
|
99
|
+
continue;
|
|
100
|
+
const strong =
|
|
101
|
+
script.strongEmphasis === undefined || script.strongEmphasis === null ?
|
|
102
|
+
script.emphasis
|
|
103
|
+
: script.strongEmphasis;
|
|
104
|
+
if (!isConvention(strong)) continue;
|
|
105
|
+
const emphasis = Object.freeze({ em: script.emphasis, strong });
|
|
106
|
+
for (const slug of script.fonts)
|
|
107
|
+
if (typeof slug === "string" && !out.has(slug)) out.set(slug, emphasis);
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The convention each language's note states.
|
|
114
|
+
*
|
|
115
|
+
* @param {ReadonlyMap<string, Record<string, any>>} byName - Each configured
|
|
116
|
+
* language's note record, by language name, from
|
|
117
|
+
* {@link module:engine/map-lexicon.languageRecordsByName}.
|
|
118
|
+
* @returns {Map<string, LanguageEmphasis>} The conventions, by language name;
|
|
119
|
+
* a language whose note states none, or one the build does not know, is
|
|
120
|
+
* absent.
|
|
121
|
+
*/
|
|
122
|
+
export function languageEmphasesOf(byName) {
|
|
123
|
+
const out = new Map();
|
|
124
|
+
for (const [name, record] of byName ?? [])
|
|
125
|
+
if (LANGUAGE_EMPHASES.includes(record?.data?.emphasis)) out.set(name, record.data.emphasis);
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Refuse `data.emphasis` on a skill that is not a language, at its key.
|
|
131
|
+
*
|
|
132
|
+
* @param {{file: string, raw?: string, fm?: Record<string, any>}} note - The note.
|
|
133
|
+
* @returns {object[]} The findings.
|
|
134
|
+
*/
|
|
135
|
+
export function checkLanguageEmphasis(note) {
|
|
136
|
+
const value = note?.fm?.data?.emphasis;
|
|
137
|
+
if (value === undefined || value === null) return [];
|
|
138
|
+
if (String(note.fm?.subType ?? "").toLowerCase() === LANGUAGE_SUBTYPE) return [];
|
|
139
|
+
return [
|
|
140
|
+
{
|
|
141
|
+
file: note.file,
|
|
142
|
+
...positionOfFrontmatterPath(note.raw ?? "", ["data", "emphasis"], { key: true }),
|
|
143
|
+
severity: "error",
|
|
144
|
+
message:
|
|
145
|
+
"`data.emphasis` states how a language marks emphasis, and this note's " +
|
|
146
|
+
`\`subType\` is not \`${LANGUAGE_SUBTYPE}\``,
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** The notes listing each face, read once per lint index. */
|
|
152
|
+
const listedFaces = new WeakMap();
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Each face the index's script notes list, and the notes listing it.
|
|
156
|
+
*
|
|
157
|
+
* @param {{notes?: Iterable<{file: string, fm?: Record<string, any>}>}} index - The lint index.
|
|
158
|
+
* @returns {Map<string, string[]>} Slug → the files that list it.
|
|
159
|
+
*/
|
|
160
|
+
function facesIn(index) {
|
|
161
|
+
const known = listedFaces.get(index);
|
|
162
|
+
if (known) return known;
|
|
163
|
+
const faces = new Map();
|
|
164
|
+
for (const note of index.notes ?? []) {
|
|
165
|
+
const fm = note?.fm;
|
|
166
|
+
if (fm?.type !== "skill" || String(fm.subType ?? "").toLowerCase() !== SCRIPT_SUBTYPE)
|
|
167
|
+
continue;
|
|
168
|
+
const fonts = fm.data?.script?.fonts;
|
|
169
|
+
if (!Array.isArray(fonts)) continue;
|
|
170
|
+
for (const slug of new Set(fonts.filter((value) => typeof value === "string"))) {
|
|
171
|
+
if (!faces.has(slug)) faces.set(slug, []);
|
|
172
|
+
faces.get(slug).push(note.file);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
listedFaces.set(index, faces);
|
|
176
|
+
return faces;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Check a note's `data.script`: that the note is a script, and that its
|
|
181
|
+
* `fonts` is not empty, names no generic family, lists no face twice, and
|
|
182
|
+
* lists no face another script note lists. The data-key check owns every key,
|
|
183
|
+
* its kind and the conventions; whether a face is installed is
|
|
184
|
+
* {@link checkScriptNoteFonts}'s.
|
|
185
|
+
*
|
|
186
|
+
* @param {{file: string, raw?: string, fm?: Record<string, any>}} note - The note.
|
|
187
|
+
* @param {{index?: {notes?: Iterable<object>}}} [context] - The lint index.
|
|
188
|
+
* @returns {object[]} The findings, each at its key or entry.
|
|
189
|
+
*/
|
|
190
|
+
export function checkScriptField(note, { index } = {}) {
|
|
191
|
+
const value = note?.fm?.data?.script;
|
|
192
|
+
if (value === undefined || value === null) return [];
|
|
193
|
+
const raw = note.raw ?? "";
|
|
194
|
+
/**
|
|
195
|
+
* @param {Array<string|number>} keyPath - Where the fault is.
|
|
196
|
+
* @param {string} message - What is wrong.
|
|
197
|
+
* @param {boolean} [key] - Locate the key rather than its value.
|
|
198
|
+
*/
|
|
199
|
+
const finding = (keyPath, message, key = false) => ({
|
|
200
|
+
file: note.file,
|
|
201
|
+
...positionOfFrontmatterPath(raw, ["data", "script", ...keyPath], { key }),
|
|
202
|
+
severity: "error",
|
|
203
|
+
message,
|
|
204
|
+
});
|
|
205
|
+
if (String(note.fm?.subType ?? "").toLowerCase() !== SCRIPT_SUBTYPE)
|
|
206
|
+
return [
|
|
207
|
+
finding(
|
|
208
|
+
[],
|
|
209
|
+
"`data.script` states a script's faces and emphasis, and this note's " +
|
|
210
|
+
`\`subType\` is not \`${SCRIPT_SUBTYPE}\``,
|
|
211
|
+
true,
|
|
212
|
+
),
|
|
213
|
+
];
|
|
214
|
+
if (!isMap(value) || !Array.isArray(value.fonts)) return [];
|
|
215
|
+
const { fonts } = value;
|
|
216
|
+
if (!fonts.length)
|
|
217
|
+
return [
|
|
218
|
+
finding(
|
|
219
|
+
["fonts"],
|
|
220
|
+
"`data.script.fonts` is empty; a script lists each face it is written in",
|
|
221
|
+
),
|
|
222
|
+
];
|
|
223
|
+
const findings = [];
|
|
224
|
+
const first = new Map();
|
|
225
|
+
fonts.forEach((slug, at) => {
|
|
226
|
+
if (typeof slug !== "string") return;
|
|
227
|
+
if (GENERIC_FAMILIES.includes(slug)) {
|
|
228
|
+
findings.push(
|
|
229
|
+
finding(
|
|
230
|
+
["fonts", at],
|
|
231
|
+
`\`data.script.fonts[${at}]\` is \`${slug}\`, a generic family; a script lists the faces it is written in`,
|
|
232
|
+
),
|
|
233
|
+
);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (first.has(slug)) {
|
|
237
|
+
findings.push(
|
|
238
|
+
finding(
|
|
239
|
+
["fonts", at],
|
|
240
|
+
`\`data.script.fonts[${at}]\` lists the font \`${slug}\` a second time; the first is ` +
|
|
241
|
+
`\`data.script.fonts[${first.get(slug)}]\``,
|
|
242
|
+
),
|
|
243
|
+
);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
first.set(slug, at);
|
|
247
|
+
if (!index) return;
|
|
248
|
+
const others = (facesIn(index).get(slug) ?? []).filter((file) => file !== note.file);
|
|
249
|
+
if (others.length)
|
|
250
|
+
findings.push(
|
|
251
|
+
finding(
|
|
252
|
+
["fonts", at],
|
|
253
|
+
`\`data.script.fonts[${at}]\` is \`${slug}\`, which ${others.join(", ")} also lists; a face belongs to one script`,
|
|
254
|
+
),
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
return findings;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Each face a script note lists that is not installed, at its entry.
|
|
262
|
+
*
|
|
263
|
+
* @param {{file: string, raw?: string, fm?: Record<string, any>}} note - The note.
|
|
264
|
+
* @param {{faces?: ReadonlyMap<string, unknown>}} [ctx] - The installed faces,
|
|
265
|
+
* from {@link module:engine/content-scripts.runContext}.
|
|
266
|
+
* @returns {object[]} The findings.
|
|
267
|
+
*/
|
|
268
|
+
export function checkScriptNoteFonts(note, ctx) {
|
|
269
|
+
const fm = note?.fm;
|
|
270
|
+
if (String(fm?.subType ?? "").toLowerCase() !== SCRIPT_SUBTYPE) return [];
|
|
271
|
+
const fonts = fm?.data?.script?.fonts;
|
|
272
|
+
if (!Array.isArray(fonts)) return [];
|
|
273
|
+
const faces = ctx?.faces ?? new Map();
|
|
274
|
+
const raw = note.raw ?? "";
|
|
275
|
+
const findings = [];
|
|
276
|
+
const seen = new Set();
|
|
277
|
+
fonts.forEach((slug, at) => {
|
|
278
|
+
if (typeof slug !== "string" || seen.has(slug)) return;
|
|
279
|
+
seen.add(slug);
|
|
280
|
+
if (GENERIC_FAMILIES.includes(slug) || faces.has(slug)) return;
|
|
281
|
+
const near = nearest(slug, [...faces.keys()]);
|
|
282
|
+
findings.push({
|
|
283
|
+
file: note.file,
|
|
284
|
+
...positionOfFrontmatterPath(raw, ["data", "script", "fonts", at]),
|
|
285
|
+
severity: "error",
|
|
286
|
+
message:
|
|
287
|
+
`\`${slug}\` is not a font: \`data.script.fonts[${at}]\` names ` +
|
|
288
|
+
(faces.size ? "an installed face" : (
|
|
289
|
+
`a face of ${FONTS_PACKAGE}, which this package has not installed`
|
|
290
|
+
)) +
|
|
291
|
+
(near ? `; did you mean \`${near}\`?` : ""),
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
return findings;
|
|
295
|
+
}
|
|
@@ -39,8 +39,8 @@ import { FONTS_MANIFEST, resolveScriptFonts } from "./script-fonts.mjs";
|
|
|
39
39
|
export const STAGED_FONTS_DIR = "fonts/heroiclands";
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* Where the files land in the
|
|
43
|
-
*
|
|
42
|
+
* Where the files land in the site's static tree, beneath a directory per
|
|
43
|
+
* version.
|
|
44
44
|
*
|
|
45
45
|
* @type {string}
|
|
46
46
|
*/
|
|
@@ -279,10 +279,10 @@ export function siteFontsParam(config) {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
/**
|
|
282
|
-
* The directory under the
|
|
282
|
+
* The directory under the site's static tree that holds this version's fonts.
|
|
283
283
|
*
|
|
284
284
|
* @param {string} version - The fonts package's version.
|
|
285
|
-
* @returns {string} The POSIX path, relative to
|
|
285
|
+
* @returns {string} The POSIX path, relative to the static tree.
|
|
286
286
|
*/
|
|
287
287
|
export function siteFontsDestination(version) {
|
|
288
288
|
return `${SITE_FONTS_DIR}/${version}`;
|