@heroiclands/package-build 24.0.1 → 24.0.3
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 +32 -0
- package/assets/preview/gitea.ini +1 -1
- package/assets/styles/content-base.css +33 -0
- package/bin/content-commands.mjs +17 -17
- package/content-config.mjs +1 -52
- package/docs/api.md +20 -13
- package/docs/authoring/links-and-markup.md +165 -17
- package/docs/commands.md +20 -20
- package/docs/configuration.md +29 -69
- package/docs/content-format.md +4 -2
- package/docs/guides/site.md +3 -1
- package/docs/guides/writing-in-invented-scripts.md +68 -6
- package/docs/reference/format-details.md +160 -118
- package/docs/reference/note-types.md +49 -29
- package/docs/source-preview.md +2 -2
- package/engine/content-blocks.mjs +6 -2
- package/engine/content-charset.mjs +1 -0
- package/engine/content-format.yaml +2 -0
- package/engine/content-index.mjs +5 -1
- package/engine/content-render.mjs +2 -1
- package/engine/content-scripts.mjs +268 -21
- package/engine/content-scroll.mjs +389 -0
- package/engine/dictionary-layout.mjs +16 -3
- package/engine/font-outlines.mjs +394 -0
- package/engine/foreign-catalog.mjs +11 -122
- package/engine/language-data.mjs +154 -63
- package/engine/language-rows.mjs +35 -9
- package/engine/language-terms.mjs +39 -9
- package/engine/map-lexicon.mjs +31 -6
- package/engine/note-vocabulary.mjs +58 -6
- package/engine/pdf-build.mjs +9 -178
- package/engine/pdf-fonts.mjs +1 -1
- package/engine/pdf-render.mjs +52 -11
- package/engine/run-rows.mjs +45 -3
- package/engine/script-direction.mjs +67 -0
- package/engine/script-emphasis-terms.mjs +11 -3
- package/engine/script-font-delivery.mjs +42 -9
- package/engine/script-fonts.mjs +7 -0
- package/engine/site-reach.mjs +23 -0
- package/engine/site-render.mjs +18 -3
- package/engine/verse-metre.mjs +1 -0
- package/engine/written-in.mjs +208 -0
- package/package.json +2 -1
- package/site-theme/static/css/style.css +7 -0
- package/types/content-config.d.mts +1 -19
- package/types/engine/content-scripts.d.mts +31 -6
- package/types/engine/content-scroll.d.mts +179 -0
- package/types/engine/dictionary-layout.d.mts +5 -0
- package/types/engine/font-outlines.d.mts +67 -0
- package/types/engine/foreign-catalog.d.mts +0 -38
- package/types/engine/language-data.d.mts +13 -0
- package/types/engine/language-rows.d.mts +5 -4
- package/types/engine/language-terms.d.mts +7 -0
- package/types/engine/map-lexicon.d.mts +4 -3
- package/types/engine/pdf-build.d.mts +5 -27
- package/types/engine/pdf-fonts.d.mts +10 -0
- package/types/engine/run-rows.d.mts +28 -6
- package/types/engine/script-direction.d.mts +10 -0
- package/types/engine/script-emphasis-terms.d.mts +10 -3
- package/types/engine/script-font-delivery.d.mts +5 -3
- package/types/engine/script-fonts.d.mts +9 -0
- package/types/engine/site-reach.d.mts +11 -0
- package/types/engine/written-in.d.mts +76 -0
- package/engine/asset-replacement.mjs +0 -93
- package/types/engine/asset-replacement.d.mts +0 -22
package/engine/script-fonts.mjs
CHANGED
|
@@ -68,6 +68,9 @@ export const FONTS_MANIFEST = "fonts.json";
|
|
|
68
68
|
* @property {string} direction - `ltr` or `rtl`.
|
|
69
69
|
* @property {string[]} features - OpenType features it turns on.
|
|
70
70
|
* @property {ReadonlySet<number>} draws - Every code point it draws.
|
|
71
|
+
* @property {string|undefined} file - The absolute path of its TrueType file,
|
|
72
|
+
* which the book draws a `scroll` fence's outlines from; `undefined` when the
|
|
73
|
+
* manifest lists none.
|
|
71
74
|
*/
|
|
72
75
|
|
|
73
76
|
/**
|
|
@@ -164,6 +167,10 @@ export function resolveScriptFonts(rootDir) {
|
|
|
164
167
|
direction: entry.direction === "rtl" ? "rtl" : "ltr",
|
|
165
168
|
features: Object.freeze([...(entry.features ?? [])]),
|
|
166
169
|
draws,
|
|
170
|
+
file:
|
|
171
|
+
typeof entry.files?.ttf === "string" ?
|
|
172
|
+
path.join(root, entry.files.ttf)
|
|
173
|
+
: undefined,
|
|
167
174
|
}),
|
|
168
175
|
);
|
|
169
176
|
}
|
package/engine/site-reach.mjs
CHANGED
|
@@ -211,3 +211,26 @@ export function pruneGenerated(html, outside) {
|
|
|
211
211
|
});
|
|
212
212
|
return kept.join("");
|
|
213
213
|
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Entries' links set as plain text where they lead outside a set of pages.
|
|
217
|
+
* Each link is the only child of a span carrying the class.
|
|
218
|
+
*
|
|
219
|
+
* @param {string} html - A page body.
|
|
220
|
+
* @param {(href: string) => boolean} outside - Whether an `href` names a page
|
|
221
|
+
* of the site that is not to be linked.
|
|
222
|
+
* @param {string} className - The class the entries' links carry.
|
|
223
|
+
* @returns {string} The body with each such `<a>` replaced by its content.
|
|
224
|
+
*/
|
|
225
|
+
export function unlinkEntries(html, outside, className) {
|
|
226
|
+
const marked = new RegExp(
|
|
227
|
+
`<span\\b[^>]*?\\sclass=(?:"[^"]*\\b${className}\\b[^"]*"|'[^']*\\b${className}\\b[^']*')[^>]*>(<a\\b[^>]*>[\\s\\S]*?</a>)</span>`,
|
|
228
|
+
"gi",
|
|
229
|
+
);
|
|
230
|
+
return String(html ?? "").replace(marked, (span, anchor) => {
|
|
231
|
+
const match = new RegExp(ANCHOR.source, "i").exec(anchor);
|
|
232
|
+
const href = match ? (match[1] ?? match[2] ?? match[3] ?? "").replace(/&/g, "&") : "";
|
|
233
|
+
if (href === "" || !outside(href)) return span;
|
|
234
|
+
return anchor.replace(/^<a\b[^>]*>/, "").replace(/<\/a>$/, "");
|
|
235
|
+
});
|
|
236
|
+
}
|
package/engine/site-render.mjs
CHANGED
|
@@ -69,6 +69,7 @@ import fs from "node:fs";
|
|
|
69
69
|
import path from "node:path";
|
|
70
70
|
import { fileURLToPath } from "node:url";
|
|
71
71
|
|
|
72
|
+
import { LEXICAL_LINK_CLASS } from "./dictionary-layout.mjs";
|
|
72
73
|
import { createRenderer } from "./content-render.mjs";
|
|
73
74
|
import { resolvePathname } from "./pathnames.mjs";
|
|
74
75
|
import { minifyHtml } from "./html-minify.mjs";
|
|
@@ -82,7 +83,13 @@ import {
|
|
|
82
83
|
import { buildSite, gatesFailed } from "./site-build.mjs";
|
|
83
84
|
import { readCachedNavigation, siteSettings } from "./site-config.mjs";
|
|
84
85
|
import { atomFeed, robotsTxt, sitemapIndexXml, sitemapXml } from "./site-files.mjs";
|
|
85
|
-
import {
|
|
86
|
+
import {
|
|
87
|
+
pageAddress,
|
|
88
|
+
pruneGenerated,
|
|
89
|
+
reachablePages,
|
|
90
|
+
splitContent,
|
|
91
|
+
unlinkEntries,
|
|
92
|
+
} from "./site-reach.mjs";
|
|
86
93
|
import { prepareTreeSqlTables } from "./sql-tables.mjs";
|
|
87
94
|
import { placeGeography, searchMetadata } from "./site-search.mjs";
|
|
88
95
|
import {
|
|
@@ -369,7 +376,12 @@ export function writeSite({ site, pages, out, report, staticDir = SITE_STATIC_DI
|
|
|
369
376
|
},
|
|
370
377
|
report: sink,
|
|
371
378
|
});
|
|
372
|
-
const authoredOf = (page) =>
|
|
379
|
+
const authoredOf = (page) =>
|
|
380
|
+
unlinkEntries(
|
|
381
|
+
splitContent(page.content).authored,
|
|
382
|
+
(url) => pageOfUrl(url) !== undefined,
|
|
383
|
+
LEXICAL_LINK_CLASS,
|
|
384
|
+
);
|
|
373
385
|
const first = makeTheme({
|
|
374
386
|
visible: (url) => pageOfUrl(url) === undefined,
|
|
375
387
|
contentOf: authoredOf,
|
|
@@ -405,7 +417,10 @@ export function writeSite({ site, pages, out, report, staticDir = SITE_STATIC_DI
|
|
|
405
417
|
const contentOf = (page) => {
|
|
406
418
|
if (page.content === undefined) return page.content;
|
|
407
419
|
const { authored, generated } = splitContent(page.content);
|
|
408
|
-
return
|
|
420
|
+
return (
|
|
421
|
+
unlinkEntries(authored, outside, LEXICAL_LINK_CLASS) +
|
|
422
|
+
pruneGenerated(generated, outside)
|
|
423
|
+
);
|
|
409
424
|
};
|
|
410
425
|
const theme = makeTheme({ visible: (url) => !outside(url), contentOf, report });
|
|
411
426
|
|
package/engine/verse-metre.mjs
CHANGED
|
@@ -129,6 +129,7 @@ export function stressIndex(word, rule, pairs) {
|
|
|
129
129
|
const count = nuclei.length;
|
|
130
130
|
if (count <= 1) return 0;
|
|
131
131
|
if (count === 2) return rule.two === "last" ? 1 : 0;
|
|
132
|
+
if (rule.longer === "first") return 0;
|
|
132
133
|
if (rule.longer === "antepenultimate") return count - 3;
|
|
133
134
|
if (rule.longer === "final") return count - 1;
|
|
134
135
|
return count - 2;
|
|
@@ -0,0 +1,208 @@
|
|
|
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 script a language is written in.** A language note — a `skill` of
|
|
16
|
+
* `subType: language` — names the map and the face its text is written in:
|
|
17
|
+
*
|
|
18
|
+
* ```yaml
|
|
19
|
+
* data:
|
|
20
|
+
* writtenIn: { map: semaron, font: semaron-kuriale }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* A run marked with the language's name takes them where it states neither:
|
|
24
|
+
* `[Zaron tholmeza]{lang=vyl}` is mapped by `semaron` and set in
|
|
25
|
+
* `semaron-kuriale`. A run that states one of the two takes the other from the
|
|
26
|
+
* default only where they agree, so the run is never set in a face its map is
|
|
27
|
+
* not drawn in:
|
|
28
|
+
*
|
|
29
|
+
* - **`map=` alone** takes the default `font` where that map is drawn in it.
|
|
30
|
+
* - **`font=` alone** takes the default `map` where the map is drawn in that
|
|
31
|
+
* face.
|
|
32
|
+
* - **Both** take nothing.
|
|
33
|
+
*
|
|
34
|
+
* The default belongs to the run's own language, after a div or fence around
|
|
35
|
+
* it has given it its keys: a span marked `lang=en` inside a div of a language
|
|
36
|
+
* with a default takes no face from it.
|
|
37
|
+
*
|
|
38
|
+
* @module
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
42
|
+
import { nearest } from "./near-miss.mjs";
|
|
43
|
+
import { compileMap } from "./text-maps.mjs";
|
|
44
|
+
import { LANGUAGE_SUBTYPE } from "./word-terms.mjs";
|
|
45
|
+
|
|
46
|
+
/** The `data` key a language note names its script in. */
|
|
47
|
+
export const WRITTEN_IN_KEY = "writtenIn";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A language's script.
|
|
51
|
+
*
|
|
52
|
+
* @typedef {object} WrittenIn
|
|
53
|
+
* @property {string} map - The `data.map.name` of the mapping note.
|
|
54
|
+
* @property {string} font - The slug of the face, one the map is drawn in.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/** Whether a value is a plain map. */
|
|
58
|
+
const isMap = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Whether a map is drawn in a face: it lists the face, or lists none.
|
|
62
|
+
*
|
|
63
|
+
* @param {import("./text-maps.mjs").TextMap} map - The map.
|
|
64
|
+
* @param {string} font - The face's slug.
|
|
65
|
+
* @returns {boolean} Whether the map may set text in it.
|
|
66
|
+
*/
|
|
67
|
+
export function drawnIn(map, font) {
|
|
68
|
+
return !map.fonts.size || map.fonts.has(font);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A list of names as prose: `a`, `a` and `b`, `a`, `b` and `c`. */
|
|
72
|
+
function said(names) {
|
|
73
|
+
const quoted = names.map((name) => `\`${name}\``);
|
|
74
|
+
return quoted.length < 2 ?
|
|
75
|
+
quoted.join("")
|
|
76
|
+
: `${quoted.slice(0, -1).join(", ")} and ${quoted.at(-1)}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The script each configured language's note names, where the map is known
|
|
81
|
+
* and is drawn in the face. A default that names an unknown map or a face its
|
|
82
|
+
* map is not drawn in is the note's finding, and fills no run.
|
|
83
|
+
*
|
|
84
|
+
* @param {ReadonlyMap<string, Record<string, any>>} byName - Each configured
|
|
85
|
+
* language's note record, by language name, from
|
|
86
|
+
* {@link module:engine/map-lexicon.languageRecordsByName}.
|
|
87
|
+
* @param {ReadonlyMap<string, import("./text-maps.mjs").TextMap>} maps - The
|
|
88
|
+
* maps by name.
|
|
89
|
+
* @returns {Map<string, WrittenIn>} The scripts, by language name.
|
|
90
|
+
*/
|
|
91
|
+
export function writtenInOf(byName, maps) {
|
|
92
|
+
const out = new Map();
|
|
93
|
+
for (const [name, record] of byName ?? []) {
|
|
94
|
+
const value = record?.data?.[WRITTEN_IN_KEY];
|
|
95
|
+
if (!isMap(value) || typeof value.map !== "string" || typeof value.font !== "string")
|
|
96
|
+
continue;
|
|
97
|
+
const map = maps?.get(value.map);
|
|
98
|
+
if (!map || !drawnIn(map, value.font)) continue;
|
|
99
|
+
out.set(name, Object.freeze({ map: value.map, font: value.font }));
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The keys a run takes from its language's script: each of `map` and `font`
|
|
106
|
+
* the run does not state, where it agrees with the one the run does.
|
|
107
|
+
*
|
|
108
|
+
* @param {{lang?: string, map?: string, font?: string}} keys - The run's keys,
|
|
109
|
+
* its own and those it inherits.
|
|
110
|
+
* @param {ReadonlyMap<string, WrittenIn>|undefined} scripts - Each language's
|
|
111
|
+
* script, from {@link writtenInOf}.
|
|
112
|
+
* @param {ReadonlyMap<string, import("./text-maps.mjs").TextMap>|undefined} maps -
|
|
113
|
+
* The maps by name.
|
|
114
|
+
* @returns {{map?: string, font?: string}} What the run takes; empty when it
|
|
115
|
+
* takes nothing.
|
|
116
|
+
*/
|
|
117
|
+
export function fromWrittenIn(keys, scripts, maps) {
|
|
118
|
+
if (keys.lang === undefined || (keys.map !== undefined && keys.font !== undefined)) return {};
|
|
119
|
+
const script = scripts?.get(keys.lang);
|
|
120
|
+
if (!script) return {};
|
|
121
|
+
if (keys.map === undefined && keys.font === undefined) return { ...script };
|
|
122
|
+
if (keys.map === undefined) {
|
|
123
|
+
const map = maps?.get(script.map);
|
|
124
|
+
return map && drawnIn(map, /** @type {string} */ (keys.font)) ? { map: script.map } : {};
|
|
125
|
+
}
|
|
126
|
+
const map = maps?.get(keys.map);
|
|
127
|
+
return map && drawnIn(map, script.font) ? { font: script.font } : {};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The maps each lint index's notes declare, read once. */
|
|
131
|
+
const declaredMaps = new WeakMap();
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Each map the index's notes declare, by name; the first note to declare a
|
|
135
|
+
* name keeps it.
|
|
136
|
+
*
|
|
137
|
+
* @param {{notes?: Iterable<{fm?: Record<string, any>}>}} index - The lint index.
|
|
138
|
+
* @returns {Map<string, import("./text-maps.mjs").TextMap>} The maps.
|
|
139
|
+
*/
|
|
140
|
+
function mapsIn(index) {
|
|
141
|
+
const known = declaredMaps.get(index);
|
|
142
|
+
if (known) return known;
|
|
143
|
+
const maps = new Map();
|
|
144
|
+
for (const note of index.notes ?? []) {
|
|
145
|
+
const map = compileMap(note?.fm?.data?.map);
|
|
146
|
+
if (map && !maps.has(map.name)) maps.set(map.name, map);
|
|
147
|
+
}
|
|
148
|
+
declaredMaps.set(index, maps);
|
|
149
|
+
return maps;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check a note's `data.writtenIn`: that the note is a language, that `map`
|
|
154
|
+
* names a map a mapping note declares, and that the map is drawn in `font`.
|
|
155
|
+
* The data-key check owns the keys and their kinds.
|
|
156
|
+
*
|
|
157
|
+
* @param {{file: string, raw?: string, fm?: Record<string, any>}} note - The note.
|
|
158
|
+
* @param {{index?: {notes?: Iterable<object>}}} [context] - The lint index;
|
|
159
|
+
* without one, only the note's `subType` is checked.
|
|
160
|
+
* @returns {object[]} The findings, each at its key or value.
|
|
161
|
+
*/
|
|
162
|
+
export function checkWrittenIn(note, { index } = {}) {
|
|
163
|
+
const value = note?.fm?.data?.[WRITTEN_IN_KEY];
|
|
164
|
+
if (value === undefined || value === null) return [];
|
|
165
|
+
const raw = note.raw ?? "";
|
|
166
|
+
/**
|
|
167
|
+
* @param {Array<string|number>} keyPath - Where the fault is.
|
|
168
|
+
* @param {string} message - What is wrong.
|
|
169
|
+
* @param {boolean} [key] - Locate the key rather than its value.
|
|
170
|
+
*/
|
|
171
|
+
const finding = (keyPath, message, key = false) => ({
|
|
172
|
+
file: note.file,
|
|
173
|
+
...positionOfFrontmatterPath(raw, ["data", WRITTEN_IN_KEY, ...keyPath], { key }),
|
|
174
|
+
severity: "error",
|
|
175
|
+
message,
|
|
176
|
+
});
|
|
177
|
+
if (String(note.fm?.subType ?? "").toLowerCase() !== LANGUAGE_SUBTYPE)
|
|
178
|
+
return [
|
|
179
|
+
finding(
|
|
180
|
+
[],
|
|
181
|
+
`\`data.${WRITTEN_IN_KEY}\` names the script a language is written in, and this ` +
|
|
182
|
+
`note's \`subType\` is not \`${LANGUAGE_SUBTYPE}\``,
|
|
183
|
+
true,
|
|
184
|
+
),
|
|
185
|
+
];
|
|
186
|
+
if (!index || !isMap(value) || typeof value.map !== "string") return [];
|
|
187
|
+
const maps = mapsIn(index);
|
|
188
|
+
const map = maps.get(value.map);
|
|
189
|
+
if (!map) {
|
|
190
|
+
const near = nearest(value.map, [...maps.keys()]);
|
|
191
|
+
return [
|
|
192
|
+
finding(
|
|
193
|
+
["map"],
|
|
194
|
+
`\`data.${WRITTEN_IN_KEY}.map\` is \`${value.map}\`, which no mapping note's ` +
|
|
195
|
+
"`data.map.name` declares" +
|
|
196
|
+
(near ? `; did you mean \`${near}\`?` : ""),
|
|
197
|
+
),
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
if (typeof value.font !== "string" || drawnIn(map, value.font)) return [];
|
|
201
|
+
return [
|
|
202
|
+
finding(
|
|
203
|
+
["font"],
|
|
204
|
+
`\`data.${WRITTEN_IN_KEY}.font\` is \`${value.font}\`, which the map \`${map.name}\` ` +
|
|
205
|
+
`is not drawn in; it is drawn in ${said([...map.fonts.keys()])}`,
|
|
206
|
+
),
|
|
207
|
+
];
|
|
208
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "24.0.
|
|
3
|
+
"version": "24.0.3",
|
|
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",
|
|
@@ -184,6 +184,7 @@
|
|
|
184
184
|
},
|
|
185
185
|
"devDependencies": {
|
|
186
186
|
"@changesets/cli": "^3.0.0",
|
|
187
|
+
"@heroiclands/fonts": "^0.1.5",
|
|
187
188
|
"@types/node": "^26.2.0",
|
|
188
189
|
"esbuild": "^0.28.2",
|
|
189
190
|
"markdownlint": "^0.42.0",
|
|
@@ -3114,6 +3114,13 @@ summary.info-sidebar-title::after {
|
|
|
3114
3114
|
and `mono` set the site's own serif and mono, and the reader's sans. */
|
|
3115
3115
|
.script-block { margin:1rem 0; line-height:1.7; }
|
|
3116
3116
|
.script-block[dir="rtl"], .verse[dir="rtl"] { text-align:start; }
|
|
3117
|
+
/* A scroll: columns top to bottom, read from the right; the line height is the
|
|
3118
|
+
column pitch, one em and half an em between columns. A panel wider than the
|
|
3119
|
+
page scrolls sideways from the right. */
|
|
3120
|
+
.single-content .scroll { writing-mode:vertical-rl; line-height:1.5; inline-size:max-content; max-block-size:100%; overflow-x:auto; overflow-y:hidden; margin:1rem 0; }
|
|
3121
|
+
.single-content .scroll[data-rows] { inline-size:calc(var(--scroll-rows) * 1em); }
|
|
3122
|
+
.single-content .scroll[data-cols] { block-size:calc(var(--scroll-cols) * 1.5em); }
|
|
3123
|
+
.single-content .scroll p { margin:0; text-indent:0; }
|
|
3117
3124
|
.font-serif { font-family:var(--font-body); }
|
|
3118
3125
|
.font-sans { font-family:sans-serif; }
|
|
3119
3126
|
.font-mono { font-family:var(--font-mono); }
|
|
@@ -313,8 +313,7 @@ export type PathsInput = {
|
|
|
313
313
|
unpack?: string | undefined;
|
|
314
314
|
/**
|
|
315
315
|
* Where a dependency declaring
|
|
316
|
-
* `itemCatalog: true`
|
|
317
|
-
* `assetArchive: true` is unpacked.
|
|
316
|
+
* `itemCatalog: true` is unpacked.
|
|
318
317
|
* Inbound, and fetched rather than
|
|
319
318
|
* committed.
|
|
320
319
|
*/
|
|
@@ -486,13 +485,6 @@ export type RelationshipSpec = {
|
|
|
486
485
|
* does not hold. Needs a `manifest`.
|
|
487
486
|
*/
|
|
488
487
|
itemCatalog?: boolean | undefined;
|
|
489
|
-
/**
|
|
490
|
-
* Whether `deps fetch` unpacks this
|
|
491
|
-
* dependency's release archive for its
|
|
492
|
-
* asset bytes alone, building no item
|
|
493
|
-
* catalogue from it. Needs a `manifest`.
|
|
494
|
-
*/
|
|
495
|
-
assetArchive?: boolean | undefined;
|
|
496
488
|
/**
|
|
497
489
|
* Whether `deps fetch` fetches this
|
|
498
490
|
* dependency's content index. Default
|
|
@@ -504,16 +496,6 @@ export type RelationshipSpec = {
|
|
|
504
496
|
* index.
|
|
505
497
|
*/
|
|
506
498
|
contentIndex?: boolean | undefined;
|
|
507
|
-
/**
|
|
508
|
-
* Whether this dependency's asset
|
|
509
|
-
* tree answers this package's own asset
|
|
510
|
-
* addresses ahead of its local record.
|
|
511
|
-
* Default `false`. Refuses `contentIndex:
|
|
512
|
-
* false` on the same entry, since a
|
|
513
|
-
* replacement with no fetched index to
|
|
514
|
-
* check against resolves nothing.
|
|
515
|
-
*/
|
|
516
|
-
assetReplacement?: boolean | undefined;
|
|
517
499
|
};
|
|
518
500
|
/**
|
|
519
501
|
* How a generated documentation page is framed in the repository publishing it.
|
|
@@ -63,6 +63,10 @@ export function scanRuns(source: string, ctx?: RunContext, syntax?: import("./co
|
|
|
63
63
|
* - **A div or fence in a right-to-left face** carries `dir=rtl`. No direction
|
|
64
64
|
* mark, `<bdo>` or `<bdi>` is ever written: the code points carry their own
|
|
65
65
|
* direction.
|
|
66
|
+
* - **A scroll fence's body** is rewritten to one line per paragraph, its
|
|
67
|
+
* lines joined by what the map writes for a space, and left unescaped,
|
|
68
|
+
* since the body is raw. Its layout, each character with the source text it
|
|
69
|
+
* was written from, is returned for the book, which draws it.
|
|
66
70
|
*
|
|
67
71
|
* A run with a finding is left as written; {@link scanRuns} reports it.
|
|
68
72
|
*
|
|
@@ -72,8 +76,9 @@ export function scanRuns(source: string, ctx?: RunContext, syntax?: import("./co
|
|
|
72
76
|
* scanner projections.
|
|
73
77
|
* @param {{target?: "site"|"foundry"|"book"}} [options] - The target the
|
|
74
78
|
* Markdown is rendered for; only `site` writes the search marker.
|
|
75
|
-
* @returns {{markdown: string, errors: Array<{line: number, column: number, message: string}>}}
|
|
76
|
-
* The rewritten Markdown,
|
|
79
|
+
* @returns {{markdown: string, errors: Array<{line: number, column: number, message: string}>, scrolls: Map<string, import("./content-scroll.mjs").ScrollLayout>}}
|
|
80
|
+
* The rewritten Markdown, the findings of {@link scanRuns}, and each
|
|
81
|
+
* rewritten scroll fence's layout, by its rewritten body.
|
|
77
82
|
*/
|
|
78
83
|
export function renderRuns(source: string, ctx?: RunContext, syntax?: import("./content-session.mjs").ContentSyntax, options?: {
|
|
79
84
|
target?: "site" | "foundry" | "book";
|
|
@@ -84,6 +89,7 @@ export function renderRuns(source: string, ctx?: RunContext, syntax?: import("./
|
|
|
84
89
|
column: number;
|
|
85
90
|
message: string;
|
|
86
91
|
}>;
|
|
92
|
+
scrolls: Map<string, import("./content-scroll.mjs").ScrollLayout>;
|
|
87
93
|
};
|
|
88
94
|
/** The keys this module reads, which no construct passes through as written. */
|
|
89
95
|
export const RUN_KEYS: readonly string[];
|
|
@@ -184,6 +190,11 @@ export type RunDetail = {
|
|
|
184
190
|
* - The emphasis it holds, by absolute offset.
|
|
185
191
|
*/
|
|
186
192
|
emphasis: Delimiter[];
|
|
193
|
+
/**
|
|
194
|
+
* - A scroll fence whose opening has a finding,
|
|
195
|
+
* which is left as written.
|
|
196
|
+
*/
|
|
197
|
+
unrolled?: boolean | undefined;
|
|
187
198
|
};
|
|
188
199
|
/**
|
|
189
200
|
* What a run is checked against.
|
|
@@ -208,6 +219,12 @@ export type RunContext = {
|
|
|
208
219
|
* {@link module :engine/text-maps.mapsFromRecords}.
|
|
209
220
|
*/
|
|
210
221
|
maps?: ReadonlyMap<string, import("./text-maps.mjs").TextMap> | undefined;
|
|
222
|
+
/**
|
|
223
|
+
* -
|
|
224
|
+
* The script each configured language's note names, by language name, from
|
|
225
|
+
* {@link module :engine/written-in.writtenInOf}.
|
|
226
|
+
*/
|
|
227
|
+
writtenIn?: ReadonlyMap<string, import("./written-in.mjs").WrittenIn> | undefined;
|
|
211
228
|
/**
|
|
212
229
|
* -
|
|
213
230
|
* Each configured language's lexicon by name, from
|
|
@@ -243,17 +260,25 @@ export type RunContext = {
|
|
|
243
260
|
* from {@link module :engine/script-emphasis.languageEmphasesOf}.
|
|
244
261
|
*/
|
|
245
262
|
languageEmphases?: ReadonlyMap<string, import("./script-emphasis.mjs").LanguageEmphasis> | undefined;
|
|
263
|
+
/**
|
|
264
|
+
* - Each face whose
|
|
265
|
+
* script is written in columns, from the script notes, by
|
|
266
|
+
* {@link module :engine/script-direction.directionsFromRecords}.
|
|
267
|
+
*/
|
|
268
|
+
directions?: ReadonlyMap<string, "vertical-rl"> | undefined;
|
|
246
269
|
};
|
|
247
270
|
/**
|
|
248
|
-
* One span, div or
|
|
249
|
-
* `lang`, `font` and `map` are the ones it is set with: its own,
|
|
250
|
-
* innermost div or fence around it that states one
|
|
271
|
+
* One span, div, verse fence or scroll fence carrying `lang=`, `font=` or
|
|
272
|
+
* `map=`. Its `lang`, `font` and `map` are the ones it is set with: its own,
|
|
273
|
+
* else the innermost div or fence around it that states one, else its
|
|
274
|
+
* language's script. A scroll fence that still names no face takes the face of
|
|
275
|
+
* its map where the map is drawn in one.
|
|
251
276
|
*/
|
|
252
277
|
export type Run = {
|
|
253
278
|
/**
|
|
254
279
|
* - Which construct.
|
|
255
280
|
*/
|
|
256
|
-
kind: "span" | "block" | "verse";
|
|
281
|
+
kind: "span" | "block" | "verse" | "scroll";
|
|
257
282
|
/**
|
|
258
283
|
* - The language as written.
|
|
259
284
|
*/
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read every `scroll` fence in a source, and every fault in its opening.
|
|
3
|
+
*
|
|
4
|
+
* A fence inside a block quote or an HTML block is not read, and neither is a
|
|
5
|
+
* fence quoted inside another fence.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} source - Markdown.
|
|
8
|
+
* @returns {{blocks: Scroll[], errors: Array<{line: number, column: number, message: string}>}}
|
|
9
|
+
* The fences, and each finding at its 1-based position.
|
|
10
|
+
*/
|
|
11
|
+
export function scanScrolls(source: string): {
|
|
12
|
+
blocks: Scroll[];
|
|
13
|
+
errors: Array<{
|
|
14
|
+
line: number;
|
|
15
|
+
column: number;
|
|
16
|
+
message: string;
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A scroll's paragraphs: each run of nonblank lines, its lines trimmed and
|
|
21
|
+
* joined by a space.
|
|
22
|
+
*
|
|
23
|
+
* @param {string[]} lines - The body's lines.
|
|
24
|
+
* @returns {string[]} The paragraphs, in order.
|
|
25
|
+
*/
|
|
26
|
+
export function scrollParagraphs(lines: string[]): string[];
|
|
27
|
+
/**
|
|
28
|
+
* Replace each well-formed `scroll` fence with the HTML block the site, the
|
|
29
|
+
* preview and Foundry show: a `<div class="scroll">` holding a `<p>` for each
|
|
30
|
+
* paragraph, its size carried as `--scroll-rows` and `--scroll-cols`. A fence
|
|
31
|
+
* with a finding in its opening is left as a code fence.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} source - Markdown, its marked text already rewritten.
|
|
34
|
+
* @returns {{markdown: string}} The Markdown.
|
|
35
|
+
*/
|
|
36
|
+
export function renderScroll(source: string): {
|
|
37
|
+
markdown: string;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* One character of a scroll as the book draws it.
|
|
41
|
+
*
|
|
42
|
+
* @typedef {object} ScrollCell
|
|
43
|
+
* @property {string} text - The character, one code point.
|
|
44
|
+
* @property {string} roman - The source text it was written from: the text of
|
|
45
|
+
* the piece of the map it begins, with the text of any piece before it that
|
|
46
|
+
* wrote nothing; `""` for a later character of the same piece.
|
|
47
|
+
* @property {"rubric"|"overline"} [emphasis] - The convention it is set by.
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* A scroll's text, as the book lays it out.
|
|
51
|
+
*
|
|
52
|
+
* @typedef {object} ScrollLayout
|
|
53
|
+
* @property {ScrollCell[][]} paragraphs - Each paragraph's characters.
|
|
54
|
+
* @property {import("./script-fonts.mjs").Face|undefined} face - The face it
|
|
55
|
+
* is drawn in.
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* Lay a scroll's paragraphs into columns and the columns into panels.
|
|
59
|
+
*
|
|
60
|
+
* Each paragraph starts a fresh column, filled from the top, `rows` characters
|
|
61
|
+
* to a column; the columns are read from the right and grouped `cols` to a
|
|
62
|
+
* panel. Without `rows`, a column is as long as the longest paragraph, up to
|
|
63
|
+
* {@link BOOK_SCROLL_ROWS}; without `cols`, each column is a panel of its own,
|
|
64
|
+
* so the book sets as many beside one another as fit its measure.
|
|
65
|
+
*
|
|
66
|
+
* @param {ScrollCell[][]} paragraphs - The characters of each paragraph.
|
|
67
|
+
* @param {{rows?: number, cols?: number}} size - The fence's size.
|
|
68
|
+
* @returns {{rows: number, panels: ScrollCell[][][]}} The column length, and
|
|
69
|
+
* each panel's columns, the first column the rightmost.
|
|
70
|
+
*/
|
|
71
|
+
export function scrollPanels(paragraphs: ScrollCell[][], { rows, cols }?: {
|
|
72
|
+
rows?: number;
|
|
73
|
+
cols?: number;
|
|
74
|
+
}): {
|
|
75
|
+
rows: number;
|
|
76
|
+
panels: ScrollCell[][][];
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* The roman text a panel was written from, as its alt text: each paragraph's
|
|
80
|
+
* source text, a space between paragraphs, its spacing collapsed.
|
|
81
|
+
*
|
|
82
|
+
* @param {ScrollCell[][]} panel - The panel's columns.
|
|
83
|
+
* @param {ScrollCell[][]} paragraphs - The scroll's paragraphs, so a column
|
|
84
|
+
* that begins a paragraph is told from one that continues it.
|
|
85
|
+
* @returns {string} The text.
|
|
86
|
+
*/
|
|
87
|
+
export function panelRoman(panel: ScrollCell[][], paragraphs: ScrollCell[][]): string;
|
|
88
|
+
/**
|
|
89
|
+
* The keys a scroll fence's attribute block takes. `lang`, `font` and `map`
|
|
90
|
+
* are read by `scanRuns`; `data-pagefind-ignore` is written on the site by its
|
|
91
|
+
* rewrite of a fence set in a face, and reaches the element as written.
|
|
92
|
+
*/
|
|
93
|
+
export const SCROLL_KEYS: readonly string[];
|
|
94
|
+
/**
|
|
95
|
+
* The distance from one column to the next, in ems: the character's one em
|
|
96
|
+
* and a gap of half an em.
|
|
97
|
+
*/
|
|
98
|
+
export const SCROLL_PITCH: 1.5;
|
|
99
|
+
/**
|
|
100
|
+
* The longest column the book sets when a fence states no `rows=`: each
|
|
101
|
+
* paragraph is one column as long as the longest, up to this many characters.
|
|
102
|
+
*/
|
|
103
|
+
export const BOOK_SCROLL_ROWS: 40;
|
|
104
|
+
/** The class every scroll carries. */
|
|
105
|
+
export const SCROLL_CLASS: "scroll";
|
|
106
|
+
/**
|
|
107
|
+
* One `scroll` fence.
|
|
108
|
+
*/
|
|
109
|
+
export type Scroll = {
|
|
110
|
+
/**
|
|
111
|
+
* - The 0-based line of its opening.
|
|
112
|
+
*/
|
|
113
|
+
start: number;
|
|
114
|
+
/**
|
|
115
|
+
* - The 0-based line of its closing.
|
|
116
|
+
*/
|
|
117
|
+
end: number;
|
|
118
|
+
/**
|
|
119
|
+
* - Its fence marker.
|
|
120
|
+
*/
|
|
121
|
+
fence: string;
|
|
122
|
+
/**
|
|
123
|
+
* - The opening line's indentation.
|
|
124
|
+
*/
|
|
125
|
+
indent: string;
|
|
126
|
+
/**
|
|
127
|
+
* - Its `key=value` attributes.
|
|
128
|
+
*/
|
|
129
|
+
attributes: Record<string, string>;
|
|
130
|
+
/**
|
|
131
|
+
* - Its classes.
|
|
132
|
+
*/
|
|
133
|
+
classes: string[];
|
|
134
|
+
/**
|
|
135
|
+
* - Its `rows=`, where it is valid.
|
|
136
|
+
*/
|
|
137
|
+
rows: number | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* - Its `cols=`, where it is valid.
|
|
140
|
+
*/
|
|
141
|
+
cols: number | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* - Whether its opening has a finding.
|
|
144
|
+
*/
|
|
145
|
+
invalid: boolean;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* One character of a scroll as the book draws it.
|
|
149
|
+
*/
|
|
150
|
+
export type ScrollCell = {
|
|
151
|
+
/**
|
|
152
|
+
* - The character, one code point.
|
|
153
|
+
*/
|
|
154
|
+
text: string;
|
|
155
|
+
/**
|
|
156
|
+
* - The source text it was written from: the text of
|
|
157
|
+
* the piece of the map it begins, with the text of any piece before it that
|
|
158
|
+
* wrote nothing; `""` for a later character of the same piece.
|
|
159
|
+
*/
|
|
160
|
+
roman: string;
|
|
161
|
+
/**
|
|
162
|
+
* - The convention it is set by.
|
|
163
|
+
*/
|
|
164
|
+
emphasis?: "rubric" | "overline" | undefined;
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* A scroll's text, as the book lays it out.
|
|
168
|
+
*/
|
|
169
|
+
export type ScrollLayout = {
|
|
170
|
+
/**
|
|
171
|
+
* - Each paragraph's characters.
|
|
172
|
+
*/
|
|
173
|
+
paragraphs: ScrollCell[][];
|
|
174
|
+
/**
|
|
175
|
+
* - The face it
|
|
176
|
+
* is drawn in.
|
|
177
|
+
*/
|
|
178
|
+
face: import("./script-fonts.mjs").Face | undefined;
|
|
179
|
+
};
|
|
@@ -60,6 +60,11 @@ export const LAYOUT_REQUIRED_COLUMNS: Readonly<{
|
|
|
60
60
|
export const COMMON_MARK: "\u2022";
|
|
61
61
|
/** The word-entry span class whose Foundry rendering is a native TOC heading. */
|
|
62
62
|
export const DICTIONARY_ENTRY_CLASS: "dictionary-entry";
|
|
63
|
+
/**
|
|
64
|
+
* The class on every link an entry writes to another note, so a site can set
|
|
65
|
+
* the link as plain text where its target is not a page a reader can reach.
|
|
66
|
+
*/
|
|
67
|
+
export const LEXICAL_LINK_CLASS: "lexical-ref";
|
|
63
68
|
export type LayoutOptions = {
|
|
64
69
|
/**
|
|
65
70
|
* - Whether an Address can be
|