@heroiclands/package-build 25.5.4 → 25.6.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 +40 -0
- package/README.md +7 -0
- package/assets/styles/content-base.css +1 -1
- package/bin/content-commands.mjs +32 -8
- package/docs/api.md +81 -32
- package/docs/authoring/frontmatter.md +1 -1
- package/docs/authoring/links-and-markup.md +59 -10
- package/docs/commands.md +60 -17
- package/docs/configuration.md +3 -2
- package/docs/content-format.md +50 -1
- package/docs/diagnostics.md +17 -0
- package/docs/guides/module-project.md +11 -3
- package/docs/guides/reference-pages.md +4 -1
- package/docs/guides/site.md +13 -3
- package/docs/guides/writing-in-invented-scripts.md +5 -2
- package/docs/reference/format-details.md +233 -138
- package/docs/reference/note-types.md +165 -152
- package/docs/reference/theme-pages.md +1 -0
- package/e2e.mjs +1 -1
- package/engine/base-compiler.mjs +2 -0
- package/engine/compile-corpus.mjs +6 -2
- package/engine/content-charset.mjs +1 -1
- package/engine/content-format.yaml +9 -7
- package/engine/content-html.mjs +3 -2
- package/engine/content-index.mjs +23 -0
- package/engine/content-links.mjs +8 -0
- package/engine/content-render.mjs +14 -2
- package/engine/content-scripts.mjs +44 -6
- package/engine/content-session.mjs +11 -7
- package/engine/content-spans.mjs +3 -4
- package/engine/content-tables.mjs +12 -5
- package/engine/content-verse.mjs +52 -10
- package/engine/core-report.mjs +27 -18
- package/engine/culture-reach.mjs +3 -0
- package/engine/declared-sections.mjs +100 -0
- package/engine/derived-sections.mjs +36 -2
- package/engine/dictionary-rows.mjs +2 -1
- package/engine/generated-sections.mjs +2 -0
- package/engine/holdings.mjs +107 -3
- package/engine/index-records.mjs +3 -1
- package/engine/index.mjs +1 -1
- package/engine/language-data.mjs +0 -1
- package/engine/language-names.mjs +53 -23
- package/engine/language-rows.mjs +1 -3
- package/engine/language-terms.mjs +7 -10
- package/engine/map-dot.mjs +22 -0
- package/engine/markdown-blocks.mjs +23 -8
- package/engine/markdownlint-run.mjs +1 -1
- package/engine/markup-validation.mjs +2 -0
- package/engine/metadata-index.mjs +1 -0
- package/engine/note-addresses.mjs +16 -0
- package/engine/note-vocabulary.mjs +29 -13
- package/engine/npc-generate.mjs +52 -7
- package/engine/office-holders.mjs +76 -0
- package/engine/page-breaks.mjs +143 -0
- package/engine/pdf-build.mjs +12 -4
- package/engine/pdf-render.mjs +12 -2
- package/engine/place-kinds.mjs +106 -0
- package/engine/preview-index.mjs +4 -2
- package/engine/settlement-tables.mjs +410 -675
- package/engine/settlement-terms.mjs +118 -0
- package/engine/site-build.mjs +14 -7
- package/engine/site-preview.mjs +3 -0
- package/engine/site-render.mjs +18 -4
- package/engine/site-root.mjs +10 -1
- package/engine/site-serve-http.mjs +151 -0
- package/engine/site-serve.mjs +196 -126
- package/engine/sql-tables.mjs +101 -7
- package/engine/standing-terms.mjs +9 -5
- package/engine/standings.mjs +83 -31
- package/engine/unique-items.mjs +85 -0
- package/engine/verse-metre.mjs +50 -5
- package/engine/verse-regions.mjs +16 -0
- package/package.json +9 -4
- package/site-theme/data/banner-kinds.yaml +2 -0
- package/sohl/actors.mjs +4 -3
- package/types/e2e.d.mts +1 -1
- package/types/engine/content-html.d.mts +2 -1
- package/types/engine/content-scripts.d.mts +9 -1
- package/types/engine/content-verse.d.mts +11 -2
- package/types/engine/core-report.d.mts +6 -2
- package/types/engine/culture-reach.d.mts +3 -0
- package/types/engine/declared-sections.d.mts +30 -0
- package/types/engine/derived-sections.d.mts +5 -1
- package/types/engine/holdings.d.mts +34 -2
- package/types/engine/index-records.d.mts +2 -1
- package/types/engine/language-names.d.mts +18 -6
- package/types/engine/language-terms.d.mts +5 -1
- package/types/engine/markdown-blocks.d.mts +5 -1
- package/types/engine/npc-generate.d.mts +3 -0
- package/types/engine/office-holders.d.mts +33 -0
- package/types/engine/page-breaks.d.mts +38 -0
- package/types/engine/pdf-render.d.mts +2 -0
- package/types/engine/place-kinds.d.mts +18 -0
- package/types/engine/settlement-tables.d.mts +25 -158
- package/types/engine/settlement-terms.d.mts +53 -0
- package/types/engine/site-build.d.mts +3 -1
- package/types/engine/site-render.d.mts +6 -2
- package/types/engine/site-root.d.mts +3 -1
- package/types/engine/site-serve-http.d.mts +1 -0
- package/types/engine/site-serve.d.mts +12 -6
- package/types/engine/sql-tables.d.mts +35 -3
- package/types/engine/standing-terms.d.mts +3 -2
- package/types/engine/standings.d.mts +14 -2
- package/types/engine/unique-items.d.mts +12 -0
- package/types/engine/verse-metre.d.mts +2 -1
- package/types/engine/verse-regions.d.mts +8 -0
- package/vendor/markdownlint.mjs +855 -569
|
@@ -218,6 +218,7 @@ something to show:
|
|
|
218
218
|
| Within | `within` | a place | the places whose `data.parents` names it, a paragraph to each kind of place |
|
|
219
219
|
| Governed by | `governedby` | a place | the affiliation its `data.government` names, with that affiliation's kind |
|
|
220
220
|
| Governed places | `governedplaces` | an affiliation | the places whose `data.government` names it, a paragraph to each kind of place |
|
|
221
|
+
| Office holders | `officeholders` | an affiliation | each office a living being holds, one line each, with its holders |
|
|
221
222
|
| Chronology | `chronology` | a place | the [event views](format-details.md#event-views) |
|
|
222
223
|
| Events | `events` | a being, an affiliation or a people | the event views |
|
|
223
224
|
| Accounts | `accounts` | an affiliation, a place or a people | the event views |
|
package/e2e.mjs
CHANGED
|
@@ -799,7 +799,7 @@ export function suiteExecutables(command) {
|
|
|
799
799
|
}
|
|
800
800
|
|
|
801
801
|
/** The Chrome for Testing build shared by local and CI Cypress runs. */
|
|
802
|
-
export const CHROME_BUILD_ID = "
|
|
802
|
+
export const CHROME_BUILD_ID = "159.0.8136.0";
|
|
803
803
|
|
|
804
804
|
/**
|
|
805
805
|
* The browser cache survives reseeding and container teardown.
|
package/engine/base-compiler.mjs
CHANGED
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
* @module
|
|
62
62
|
*/
|
|
63
63
|
|
|
64
|
+
import { scanPageBreaks } from "./page-breaks.mjs";
|
|
64
65
|
import { scanAlerts } from "./content-alerts.mjs";
|
|
65
66
|
import { authoredFrontmatter } from "./index-records.mjs";
|
|
66
67
|
import { markupAnchorFindings } from "./anchors.mjs";
|
|
@@ -697,6 +698,7 @@ export class BasePackCompiler {
|
|
|
697
698
|
for (const secretError of [
|
|
698
699
|
...(scanned?.blocks ?? scanBlocks(body)).errors,
|
|
699
700
|
...(scanned?.alerts ?? scanAlerts(body)).errors,
|
|
701
|
+
...scanPageBreaks(body).errors,
|
|
700
702
|
...(scanned?.spans ?? scanSpans(body)).errors,
|
|
701
703
|
...scanRuns(body, this.corpus?.runs, syntax).errors,
|
|
702
704
|
...markupAnchorFindings(body, syntax),
|
|
@@ -42,6 +42,7 @@ import { indexRecordsFor } from "./content-index.mjs";
|
|
|
42
42
|
import { buildContentLinkIndex } from "./helpers.mjs";
|
|
43
43
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
44
44
|
import { runContext } from "./content-scripts.mjs";
|
|
45
|
+
import { resolveReference } from "./wikilinks.mjs";
|
|
45
46
|
import { prepareTreeSqlTables } from "./sql-tables.mjs";
|
|
46
47
|
|
|
47
48
|
/**
|
|
@@ -99,13 +100,16 @@ export async function buildCompileCorpus({
|
|
|
99
100
|
const corpusOptions = { config: resolved, records };
|
|
100
101
|
const sqlOptions = { ...corpusOptions, skipDirectories, parsed };
|
|
101
102
|
const sqlTables = await prepareTreeSqlTables(contentBase, sqlOptions);
|
|
103
|
+
const linkIndex = buildContentLinkIndex(contentBase, router, { ...corpusOptions, sqlTables });
|
|
102
104
|
return {
|
|
103
105
|
currentDate: resolved.currentDate,
|
|
104
|
-
runs: runContext(resolved, records
|
|
106
|
+
runs: runContext(resolved, records, {
|
|
107
|
+
referenceName: (target) => resolveReference(linkIndex, target)?.name,
|
|
108
|
+
}),
|
|
105
109
|
records,
|
|
106
110
|
parsed,
|
|
107
111
|
problems: collected,
|
|
108
|
-
linkIndex
|
|
112
|
+
linkIndex,
|
|
109
113
|
sqlTables,
|
|
110
114
|
};
|
|
111
115
|
}
|
|
@@ -177,7 +177,7 @@ export const NOTATION = Object.freeze(
|
|
|
177
177
|
*/
|
|
178
178
|
const SCRIPT_BLOCKS = Object.freeze([
|
|
179
179
|
[0xf0000, 0xf2fff], // plane 15: Pirzath, Thuravark, Semaron, the reserved alphabets, Teng Chou
|
|
180
|
-
[0xf3000,
|
|
180
|
+
[0xf3000, 0xf4fff], // Ch'alish P'ilam Common and Ceremonial
|
|
181
181
|
[0x1e960, 0x1ec6f], // Zethu overflow, Qalezu, Zethu
|
|
182
182
|
[0x1ecc0, 0x1ecff], // Qalezu catch-before forms
|
|
183
183
|
[0x1ed50, 0x1ed7f], // Veldikko
|
|
@@ -96,6 +96,7 @@ types:
|
|
|
96
96
|
- durability
|
|
97
97
|
- made
|
|
98
98
|
- lost
|
|
99
|
+
- unique
|
|
99
100
|
subTypes: []
|
|
100
101
|
armorlocation:
|
|
101
102
|
data:
|
|
@@ -114,6 +115,7 @@ types:
|
|
|
114
115
|
- durability
|
|
115
116
|
- made
|
|
116
117
|
- lost
|
|
118
|
+
- unique
|
|
117
119
|
- quantity
|
|
118
120
|
- potency
|
|
119
121
|
- strength
|
|
@@ -130,6 +132,7 @@ types:
|
|
|
130
132
|
- durability
|
|
131
133
|
- made
|
|
132
134
|
- lost
|
|
135
|
+
- unique
|
|
133
136
|
- capacity
|
|
134
137
|
subTypes: []
|
|
135
138
|
miscgear:
|
|
@@ -141,6 +144,7 @@ types:
|
|
|
141
144
|
- durability
|
|
142
145
|
- made
|
|
143
146
|
- lost
|
|
147
|
+
- unique
|
|
144
148
|
- quantity
|
|
145
149
|
subTypes: []
|
|
146
150
|
mystery:
|
|
@@ -189,6 +193,7 @@ types:
|
|
|
189
193
|
- durability
|
|
190
194
|
- made
|
|
191
195
|
- lost
|
|
196
|
+
- unique
|
|
192
197
|
- quantity
|
|
193
198
|
subTypes:
|
|
194
199
|
- none
|
|
@@ -268,6 +273,7 @@ types:
|
|
|
268
273
|
- durability
|
|
269
274
|
- made
|
|
270
275
|
- lost
|
|
276
|
+
- unique
|
|
271
277
|
subTypes: []
|
|
272
278
|
lore:
|
|
273
279
|
data:
|
|
@@ -323,6 +329,7 @@ types:
|
|
|
323
329
|
- culture
|
|
324
330
|
- population
|
|
325
331
|
- government
|
|
332
|
+
- settlements
|
|
326
333
|
- market
|
|
327
334
|
- borders
|
|
328
335
|
- routes
|
|
@@ -343,10 +350,12 @@ types:
|
|
|
343
350
|
subTypes:
|
|
344
351
|
- world
|
|
345
352
|
- region
|
|
353
|
+
- district
|
|
346
354
|
- settlement
|
|
347
355
|
- site
|
|
348
356
|
- structure
|
|
349
357
|
- feature
|
|
358
|
+
- route
|
|
350
359
|
- celestial
|
|
351
360
|
scenario:
|
|
352
361
|
data:
|
|
@@ -355,7 +364,6 @@ types:
|
|
|
355
364
|
- cast
|
|
356
365
|
- factions
|
|
357
366
|
- follows
|
|
358
|
-
- status
|
|
359
367
|
- party.size
|
|
360
368
|
- party.archetypes
|
|
361
369
|
subTypes:
|
|
@@ -740,12 +748,6 @@ vocabularies:
|
|
|
740
748
|
- "4"
|
|
741
749
|
- "5"
|
|
742
750
|
- "6"
|
|
743
|
-
holdingModel:
|
|
744
|
-
- held
|
|
745
|
-
- endowed
|
|
746
|
-
- registered
|
|
747
|
-
- kin
|
|
748
|
-
- polis
|
|
749
751
|
bearing:
|
|
750
752
|
- N
|
|
751
753
|
- NE
|
package/engine/content-html.mjs
CHANGED
|
@@ -64,7 +64,8 @@ import { positionInBody, splitFrontmatter } from "./diagnostics.mjs";
|
|
|
64
64
|
* markdown's own angle brackets out of it: an autolink is `<https://…>`, and
|
|
65
65
|
* `https` is followed by `:` rather than whitespace or `>`, so the pattern
|
|
66
66
|
* stops. A comparison written in prose — `a < b` — has no name after the
|
|
67
|
-
* bracket at all.
|
|
67
|
+
* bracket at all. A doubled `<<` opens a quotation, so its second bracket
|
|
68
|
+
* never starts an HTML tag.
|
|
68
69
|
*
|
|
69
70
|
* Attributes are consumed as "anything but a bracket", deliberately loosely: a
|
|
70
71
|
* finding names the tag, and a pattern that tried to parse attribute syntax
|
|
@@ -73,7 +74,7 @@ import { positionInBody, splitFrontmatter } from "./diagnostics.mjs";
|
|
|
73
74
|
*
|
|
74
75
|
* @type {RegExp}
|
|
75
76
|
*/
|
|
76
|
-
export const HTML_TAG =
|
|
77
|
+
export const HTML_TAG = /(?<!<)<\/?[A-Za-z][A-Za-z0-9-]*(?:\s[^<>]*)?\/?>/g;
|
|
77
78
|
|
|
78
79
|
/**
|
|
79
80
|
* What a note carrying raw HTML is told.
|
package/engine/content-index.mjs
CHANGED
|
@@ -141,6 +141,8 @@ import { EXPRESSION, renderMarkdownExpressions } from "./markdown-expressions.mj
|
|
|
141
141
|
import { scanFigures } from "./content-figures.mjs";
|
|
142
142
|
import { eventNoteIndex } from "./event-fields.mjs";
|
|
143
143
|
import { derivedSections } from "./derived-sections.mjs";
|
|
144
|
+
import { officeBodyKey, officeHolders } from "./office-holders.mjs";
|
|
145
|
+
import { inheritDistrictGovernments } from "./holdings.mjs";
|
|
144
146
|
// One reader for a note's anchors, shared with the link checker and with the
|
|
145
147
|
// builds that emit a link. Re-exported because this is where callers
|
|
146
148
|
// have always addressed it.
|
|
@@ -1311,6 +1313,8 @@ export function collectContentIndex(
|
|
|
1311
1313
|
parsed,
|
|
1312
1314
|
});
|
|
1313
1315
|
resolveCultures(records, { contentPackage, contentBase, foreignRecord, problems, parsed });
|
|
1316
|
+
// A district stating no government falls under its parent's.
|
|
1317
|
+
inheritDistrictGovernments(records, { foreignRecord });
|
|
1314
1318
|
const cultureNotes = records
|
|
1315
1319
|
.filter((record) => isNoteRecord(record) && record.type === "lore")
|
|
1316
1320
|
.map((record) => ({
|
|
@@ -1333,6 +1337,24 @@ export function collectContentIndex(
|
|
|
1333
1337
|
for (const [note, inherited] of inheritance.values)
|
|
1334
1338
|
if (Object.keys(inherited).length) note.fm.data = { ...note.fm.data, ...inherited };
|
|
1335
1339
|
|
|
1340
|
+
// An office's holders are the living beings naming it, read once every
|
|
1341
|
+
// being is recorded. A public body lists only public beings, as its page
|
|
1342
|
+
// does.
|
|
1343
|
+
phase("index office holders");
|
|
1344
|
+
const publicHolders = officeHolders(records.filter((record) => !isGmNote(record)));
|
|
1345
|
+
const allHolders = officeHolders(records);
|
|
1346
|
+
for (const record of records) {
|
|
1347
|
+
if (!isNoteRecord(record) || record.type !== "affiliation") continue;
|
|
1348
|
+
const held = (isGmNote(record) ? allHolders : publicHolders).get(officeBodyKey(record));
|
|
1349
|
+
if (!held) continue;
|
|
1350
|
+
record.officeHolders = [...held].map(([office, holders]) => ({
|
|
1351
|
+
office,
|
|
1352
|
+
holders: holders.map((being) =>
|
|
1353
|
+
readCanonicalKey(canonicalKey(being.package, "note", "being", being.shortcode)),
|
|
1354
|
+
),
|
|
1355
|
+
}));
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1336
1358
|
// Automatic lexical sections require only the recorded frontmatter. Their
|
|
1337
1359
|
// published anchors and journal targets therefore use the same pure body
|
|
1338
1360
|
// projection as the surfaces, without making the index emitter async.
|
|
@@ -1924,6 +1946,7 @@ export function emitContentIndex({ contentBase, outDir, config, publication } =
|
|
|
1924
1946
|
record.data?.events?.length,
|
|
1925
1947
|
) ||
|
|
1926
1948
|
derivedSections(notes, {
|
|
1949
|
+
rawOf: (file) => parsed.get(file)?.raw,
|
|
1927
1950
|
fileOf: (record) => noteFile(tree, record),
|
|
1928
1951
|
config: resolved,
|
|
1929
1952
|
foreignIndex: loadForeignIndexes(resolved, [contentPackage]).index,
|
package/engine/content-links.mjs
CHANGED
|
@@ -56,6 +56,7 @@ import { readWikilink } from "./wikilink-syntax.mjs";
|
|
|
56
56
|
import { encodeAddresses } from "./address-values.mjs";
|
|
57
57
|
import fs from "node:fs";
|
|
58
58
|
import { cultureRecordProblems, cultureInheritedFields } from "./culture-parents.mjs";
|
|
59
|
+
import { governmentInherited } from "./holdings.mjs";
|
|
59
60
|
import path from "node:path";
|
|
60
61
|
|
|
61
62
|
import { matchAllOutsideCode } from "./code-fences.mjs";
|
|
@@ -248,6 +249,13 @@ export function buildLinkIndex(
|
|
|
248
249
|
if (typeof record.languageCode === "string") note.languageCode = record.languageCode;
|
|
249
250
|
const hierarchyProblems = cultureRecordProblems(record);
|
|
250
251
|
note.inheritedCultureFields = cultureInheritedFields(record);
|
|
252
|
+
// A district's government taken from its parent is the parent's
|
|
253
|
+
// authored value, checked where it is written, not here.
|
|
254
|
+
if (governmentInherited(record))
|
|
255
|
+
note.inheritedCultureFields = new Set([
|
|
256
|
+
...(note.inheritedCultureFields ?? []),
|
|
257
|
+
"government",
|
|
258
|
+
]);
|
|
251
259
|
if (hierarchyProblems)
|
|
252
260
|
note.reportedCultureParents = new Set(
|
|
253
261
|
hierarchyProblems
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* | quotes, dashes and ellipses | as written | typographic | as written | as written, set by Typst |
|
|
29
29
|
* | raw HTML in a note | passed through | passed through | shown as text | shown as text |
|
|
30
30
|
* | a name's first use | bold | bold | bold, with an index | bold |
|
|
31
|
+
* | `--- {.page-break}` | omitted | omitted | omitted | a weak page break |
|
|
31
32
|
* | the output | HTML | HTML | HTML | the token stream, for the Typst stage |
|
|
32
33
|
*
|
|
33
34
|
* A target differs from the others only in that table. The shared plugins are
|
|
@@ -39,6 +40,8 @@
|
|
|
39
40
|
*
|
|
40
41
|
* A note reaches {@link Renderer#render} with its index-dependent constructs
|
|
41
42
|
* already resolved by {@link module:engine/content-expand.expandContent}.
|
|
43
|
+
* Valid authored page breaks are omitted before these stages.
|
|
44
|
+
*
|
|
42
45
|
* {@link Renderer#prepare} then writes the constructs markdown-it has no
|
|
43
46
|
* syntax for as HTML between blank lines, in this order:
|
|
44
47
|
*
|
|
@@ -64,6 +67,7 @@
|
|
|
64
67
|
* @module
|
|
65
68
|
*/
|
|
66
69
|
|
|
70
|
+
import { omitPageBreaks, pageBreakPlugin } from "./page-breaks.mjs";
|
|
67
71
|
import MarkdownIt from "markdown-it";
|
|
68
72
|
import footnotePlugin from "markdown-it-footnote";
|
|
69
73
|
import deflistPlugin from "markdown-it-deflist";
|
|
@@ -122,7 +126,14 @@ function parseVerseContent(token, md, env, region) {
|
|
|
122
126
|
const inline = token.type === "verse_inline";
|
|
123
127
|
const paired = inline ? undefined : env.verses?.get(body);
|
|
124
128
|
const footnotes = new Set(Object.keys(env.footnotes?.refs ?? {}).map((key) => key.slice(1)));
|
|
125
|
-
const model =
|
|
129
|
+
const model =
|
|
130
|
+
paired?.model ??
|
|
131
|
+
region.model ??
|
|
132
|
+
parseVerse(region.logicalBody ?? body, {
|
|
133
|
+
inline,
|
|
134
|
+
footnotes,
|
|
135
|
+
sourceOffsets: region.bodyOffsets,
|
|
136
|
+
});
|
|
126
137
|
token.meta ??= {};
|
|
127
138
|
token.meta.verse = region;
|
|
128
139
|
token.meta.versePaired = paired;
|
|
@@ -395,6 +406,7 @@ export function createRenderer(target, options = {}) {
|
|
|
395
406
|
|
|
396
407
|
use("footnotes", footnotePlugin);
|
|
397
408
|
if (book) {
|
|
409
|
+
use("page-breaks", pageBreakPlugin);
|
|
398
410
|
use("alerts", alertMarkdownPlugin);
|
|
399
411
|
use("spans", spanMarkdownPlugin, { verseInline: quotedVerse });
|
|
400
412
|
use("verse-div", verseDivPlugin);
|
|
@@ -442,7 +454,7 @@ export function createRenderer(target, options = {}) {
|
|
|
442
454
|
|
|
443
455
|
/** Write the constructs markdown-it has no syntax for as HTML. */
|
|
444
456
|
const prepare = (markdown, env = {}) => {
|
|
445
|
-
let text = String(markdown ?? "");
|
|
457
|
+
let text = omitPageBreaks(String(markdown ?? ""));
|
|
446
458
|
if (preview) text = sourceMalformedAlerts(text);
|
|
447
459
|
let verses = env.verses;
|
|
448
460
|
if (env.runs) {
|
|
@@ -74,6 +74,9 @@ import { plainRomanVerse } from "./roman-verse.mjs";
|
|
|
74
74
|
import { encodeText } from "./encoding.mjs";
|
|
75
75
|
import { WIKILINK } from "./wikilink-syntax.mjs";
|
|
76
76
|
import { matchAllOutsideCode } from "./code-fences.mjs";
|
|
77
|
+
import { noteAddressOf } from "./word-rows.mjs";
|
|
78
|
+
import { isNoteRecord } from "./index-records.mjs";
|
|
79
|
+
import { parseAddress, isAddressTuple, renderAddress } from "./address.mjs";
|
|
77
80
|
import { parseExtensionAttributes } from "./extension-attributes.mjs";
|
|
78
81
|
|
|
79
82
|
/** The keys this module reads, which no construct passes through as written. */
|
|
@@ -118,6 +121,7 @@ const CONFIG = "package-build.config.yaml";
|
|
|
118
121
|
* @property {ReadonlyMap<string, import("./verse-metre.mjs").MetreBook>} [metreBooks] -
|
|
119
122
|
* What each configured language's verse is counted by, from
|
|
120
123
|
* {@link module:engine/verse-metre.metreBooksFromRecords}.
|
|
124
|
+
* @property {(target: string) => string|undefined} [referenceName] - An indexed wikilink target's visible name.
|
|
121
125
|
* @property {ReadonlyMap<string, import("./roman-characters.mjs").RomanInventory>} [romanInventories] -
|
|
122
126
|
* Exact Roman source inventories by configured alias and BCP 47 tag.
|
|
123
127
|
* @property {ReadonlyMap<string, import("./script-emphasis.mjs").Emphasis>} [emphases] -
|
|
@@ -164,12 +168,16 @@ const withEncodings = new WeakMap();
|
|
|
164
168
|
* resolved configuration.
|
|
165
169
|
* @param {Iterable<Record<string, any>>} [records] - The content index's
|
|
166
170
|
* records, whose language notes supply lexical writings and script choices.
|
|
171
|
+
* @param {{referenceName?: (target: string) => string|undefined}} [options] -
|
|
172
|
+
* The rendering or validation index's own target-name lookup.
|
|
167
173
|
* @returns {RunContext} Its languages, the faces installed where it sits, and
|
|
168
174
|
* the encodings. A fonts package that cannot be read gives no faces; a run naming
|
|
169
175
|
* one is reported.
|
|
170
176
|
*/
|
|
171
|
-
export function runContext(config, records) {
|
|
172
|
-
|
|
177
|
+
export function runContext(config, records, { referenceName: indexedName } = {}) {
|
|
178
|
+
const withReferences = (context) =>
|
|
179
|
+
indexedName ? Object.freeze({ ...context, referenceName: indexedName }) : context;
|
|
180
|
+
if (!config) return withReferences({});
|
|
173
181
|
let base = contexts.get(config);
|
|
174
182
|
if (!base) {
|
|
175
183
|
let faces;
|
|
@@ -192,14 +200,41 @@ export function runContext(config, records) {
|
|
|
192
200
|
});
|
|
193
201
|
contexts.set(config, base);
|
|
194
202
|
}
|
|
195
|
-
if (!records || typeof records !== "object") return base;
|
|
203
|
+
if (!records || typeof records !== "object") return withReferences(base);
|
|
196
204
|
let byRecords = withEncodings.get(config);
|
|
197
205
|
if (!byRecords) withEncodings.set(config, (byRecords = new WeakMap()));
|
|
198
206
|
const known = byRecords.get(records);
|
|
199
|
-
if (known) return known;
|
|
207
|
+
if (known) return withReferences(known);
|
|
200
208
|
const loaded = encodingsFromRecords(records, config, base.fonts);
|
|
201
209
|
const encodings = loaded.encodings;
|
|
202
210
|
const languageRecords = languageRecordsByName(records, config);
|
|
211
|
+
const referenceNames = new Map();
|
|
212
|
+
for (const record of records) {
|
|
213
|
+
if (!isNoteRecord(record)) continue;
|
|
214
|
+
const name = record.name?.full ?? record.name;
|
|
215
|
+
if (typeof name !== "string") continue;
|
|
216
|
+
const note = noteAddressOf(record);
|
|
217
|
+
if (note) referenceNames.set(note, name);
|
|
218
|
+
const canonical = record.address?.canonical;
|
|
219
|
+
if (canonical)
|
|
220
|
+
referenceNames.set(
|
|
221
|
+
isAddressTuple(canonical) ? renderAddress(canonical) : canonical,
|
|
222
|
+
name,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
const referenceTypes = new Set([...records].map((record) => record.type));
|
|
226
|
+
const referenceName = (target) => {
|
|
227
|
+
const address = parseAddress(
|
|
228
|
+
target,
|
|
229
|
+
{
|
|
230
|
+
package: config.contentPackage,
|
|
231
|
+
system: "note",
|
|
232
|
+
types: referenceTypes,
|
|
233
|
+
},
|
|
234
|
+
{ declared: true },
|
|
235
|
+
);
|
|
236
|
+
return isAddressTuple(address) ? referenceNames.get(renderAddress(address)) : undefined;
|
|
237
|
+
};
|
|
203
238
|
const context = Object.freeze({
|
|
204
239
|
...base,
|
|
205
240
|
encodings,
|
|
@@ -211,12 +246,13 @@ export function runContext(config, records) {
|
|
|
211
246
|
wordBooks: wordBooksFromRecords(records, config),
|
|
212
247
|
metres: metresFromRecords(records),
|
|
213
248
|
metreBooks: metreBooksFromRecords(records, config),
|
|
249
|
+
referenceName,
|
|
214
250
|
emphases: emphasesFromRecords(records),
|
|
215
251
|
languageEmphases: languageEmphasesOf(languageRecords),
|
|
216
252
|
directions: directionsFromRecords(records),
|
|
217
253
|
});
|
|
218
254
|
byRecords.set(records, context);
|
|
219
|
-
return context;
|
|
255
|
+
return withReferences(context);
|
|
220
256
|
}
|
|
221
257
|
|
|
222
258
|
/**
|
|
@@ -2056,6 +2092,7 @@ export function scanRuns(source, ctx = {}, syntax) {
|
|
|
2056
2092
|
languages: ctx.languages,
|
|
2057
2093
|
metreBooks: ctx.metreBooks,
|
|
2058
2094
|
lexicons: ctx.lexicons,
|
|
2095
|
+
referenceName: ctx.referenceName,
|
|
2059
2096
|
attributeColumns,
|
|
2060
2097
|
});
|
|
2061
2098
|
const merged = (found, extra) =>
|
|
@@ -2715,7 +2752,8 @@ export function renderRuns(source, ctx = {}, syntax, options = {}) {
|
|
|
2715
2752
|
...(detail.face?.direction === "rtl" ? { dir: "rtl" } : {}),
|
|
2716
2753
|
...(search ? { "data-pagefind-ignore": "true" } : {}),
|
|
2717
2754
|
};
|
|
2718
|
-
const
|
|
2755
|
+
const prefix = poem.inline ? "" : block.body.slice(0, block.bodyOffsets[0]);
|
|
2756
|
+
const marker = `${prefix}<!--verse-pair:${verses.size + 1}-->${poem.inline ? "" : "\n"}`;
|
|
2719
2757
|
const body = marker + block.body;
|
|
2720
2758
|
verses.set(body, { model, classes, attributes, inline: poem.inline });
|
|
2721
2759
|
pairedEdits.push({ from: block.bodyStart, to: block.bodyStart, text: marker });
|
|
@@ -25,6 +25,7 @@ import { captureContentSources } from "./content-sources.mjs";
|
|
|
25
25
|
import { buildLinkIndex } from "./content-links.mjs";
|
|
26
26
|
import { buildContentLinkIndex } from "./helpers.mjs";
|
|
27
27
|
import { runContext } from "./content-scripts.mjs";
|
|
28
|
+
import { resolveReference } from "./wikilinks.mjs";
|
|
28
29
|
import { loadForeignIndexes } from "./metadata-index.mjs";
|
|
29
30
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
30
31
|
import { routerFor } from "./pack-router.mjs";
|
|
@@ -273,19 +274,22 @@ export async function createContentSession({
|
|
|
273
274
|
async compileCorpus() {
|
|
274
275
|
return memo("compile", async () => {
|
|
275
276
|
const sqlTables = await session.sqlTables("all");
|
|
277
|
+
const linkIndex = buildContentLinkIndex(tree, router, {
|
|
278
|
+
config,
|
|
279
|
+
records,
|
|
280
|
+
sqlTables,
|
|
281
|
+
foreign,
|
|
282
|
+
});
|
|
276
283
|
return {
|
|
277
|
-
runs: runContext(config, records
|
|
284
|
+
runs: runContext(config, records, {
|
|
285
|
+
referenceName: (target) => resolveReference(linkIndex, target)?.name,
|
|
286
|
+
}),
|
|
278
287
|
records,
|
|
279
288
|
parsed,
|
|
280
289
|
problems,
|
|
281
290
|
sqlTables,
|
|
282
291
|
syntax,
|
|
283
|
-
linkIndex
|
|
284
|
-
config,
|
|
285
|
-
records,
|
|
286
|
-
sqlTables,
|
|
287
|
-
foreign,
|
|
288
|
-
}),
|
|
292
|
+
linkIndex,
|
|
289
293
|
};
|
|
290
294
|
});
|
|
291
295
|
},
|
package/engine/content-spans.mjs
CHANGED
|
@@ -318,12 +318,11 @@ export function scanSpans(source) {
|
|
|
318
318
|
return mapping.absolute + pos - mapping.local;
|
|
319
319
|
});
|
|
320
320
|
}
|
|
321
|
-
// Verse
|
|
322
|
-
// normally treats every code fence as literal text.
|
|
321
|
+
// Verse divs retain inline syntax while their container prefixes stay layout.
|
|
323
322
|
for (const poem of scanVerse(text, { checkWorks: false }).blocks) {
|
|
324
323
|
const children = [];
|
|
325
|
-
parser.inline.parse(poem.
|
|
326
|
-
collect(children, (pos) => poem.bodyStart + pos);
|
|
324
|
+
parser.inline.parse(poem.logicalBody, parser, env, children);
|
|
325
|
+
collect(children, (pos) => poem.bodyStart + poem.bodyOffsets[pos]);
|
|
327
326
|
}
|
|
328
327
|
const raw = text.split("");
|
|
329
328
|
for (const region of codeRegions(text))
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { FENCE_LINE } from "./code-fences.mjs";
|
|
15
15
|
import { PAGE_LIST_LANGUAGE } from "./page-lists.mjs";
|
|
16
16
|
import { appendGeneratedSections } from "./generated-sections.mjs";
|
|
17
|
+
import { insertDeclaredSections } from "./declared-sections.mjs";
|
|
17
18
|
import { insertLexicalSections } from "./lexical-sections.mjs";
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -129,8 +130,8 @@ export function expandContentTables(markdown, { source = "", sqlTables, pageList
|
|
|
129
130
|
source,
|
|
130
131
|
directive: block.join("\n"),
|
|
131
132
|
reason: failure,
|
|
132
|
-
line: i,
|
|
133
|
-
column: indent.length + 1,
|
|
133
|
+
line: prepared?.line ?? i,
|
|
134
|
+
column: prepared?.column ?? indent.length + 1,
|
|
134
135
|
});
|
|
135
136
|
block.forEach((value, offset) => emit(value, i + offset));
|
|
136
137
|
i = close;
|
|
@@ -149,11 +150,17 @@ export function expandContentTables(markdown, { source = "", sqlTables, pageList
|
|
|
149
150
|
if (close + 1 < lines.length && lines[close + 1].trim()) emit("", i, true);
|
|
150
151
|
i = close;
|
|
151
152
|
}
|
|
153
|
+
const declared = insertDeclaredSections(out.join("\n"), sqlTables?.declared, {
|
|
154
|
+
authored,
|
|
155
|
+
lineMap,
|
|
156
|
+
generatedFrom,
|
|
157
|
+
syntax: sqlTables?.syntax,
|
|
158
|
+
});
|
|
152
159
|
return {
|
|
153
|
-
...insertLexicalSections(
|
|
160
|
+
...insertLexicalSections(declared.markdown, sqlTables?.lexical, {
|
|
154
161
|
authored,
|
|
155
|
-
lineMap,
|
|
156
|
-
generatedFrom,
|
|
162
|
+
lineMap: declared.lineMap,
|
|
163
|
+
generatedFrom: declared.generatedFrom,
|
|
157
164
|
syntax: sqlTables?.syntax,
|
|
158
165
|
}),
|
|
159
166
|
errors,
|
package/engine/content-verse.mjs
CHANGED
|
@@ -140,7 +140,7 @@ export function scanVerse(source, { checkWorks = true } = {}) {
|
|
|
140
140
|
for (const message of [...region.problems, ...refusedAttributes(region.attributes)])
|
|
141
141
|
errors.push({ ...at, message });
|
|
142
142
|
if (!region.closed) errors.push({ ...at, message: "verse div needs a closing ::: line" });
|
|
143
|
-
if (!region.
|
|
143
|
+
if (!region.logicalBody.trim()) errors.push({ ...at, message: "verse div is empty" });
|
|
144
144
|
for (const key of Object.keys(region.attributes))
|
|
145
145
|
if (!VERSE_KEYS.includes(key))
|
|
146
146
|
errors.push({
|
|
@@ -150,7 +150,10 @@ export function scanVerse(source, { checkWorks = true } = {}) {
|
|
|
150
150
|
});
|
|
151
151
|
if (region.attributes.work !== undefined && !region.attributes.work.trim())
|
|
152
152
|
errors.push({ ...at, message: "work= needs a nonempty page-local key" });
|
|
153
|
-
const model = parseVerse(region.
|
|
153
|
+
const model = parseVerse(region.logicalBody, {
|
|
154
|
+
footnotes: definedFootnotes,
|
|
155
|
+
sourceOffsets: region.bodyOffsets,
|
|
156
|
+
});
|
|
154
157
|
for (const finding of model.errors) {
|
|
155
158
|
const preceding = text.slice(0, region.bodyStart + finding.offset);
|
|
156
159
|
errors.push({
|
|
@@ -254,6 +257,8 @@ function keyColumn(opening, key) {
|
|
|
254
257
|
* @property {string[]} classes - Authored presentation classes.
|
|
255
258
|
* @property {string} indent - Opening div's leading spaces.
|
|
256
259
|
* @property {string} body - Authored verse body.
|
|
260
|
+
* @property {string} logicalBody - Body without Markdown container prefixes.
|
|
261
|
+
* @property {number[]} bodyOffsets - Logical UTF-16 offsets mapped to authored body offsets, including the end.
|
|
257
262
|
* @property {VerseModel} model - Logical structure relative to the body.
|
|
258
263
|
*/
|
|
259
264
|
|
|
@@ -291,10 +296,10 @@ function cantoLine(line) {
|
|
|
291
296
|
* Offsets are UTF-16 positions relative to body. Delimiters and indentation are
|
|
292
297
|
* layout ranges; source newlines inside a logical line contribute one space.
|
|
293
298
|
* @param {string} body - Authored verse body.
|
|
294
|
-
* @param {{inline?: boolean, footnotes?: ReadonlySet<string
|
|
299
|
+
* @param {{inline?: boolean, footnotes?: ReadonlySet<string>, sourceOffsets?: number[]}} [options] - Inline quotations have no cantos; footnotes are same-note definition labels; sourceOffsets maps projected body positions to authored offsets, including the end.
|
|
295
300
|
* @returns {VerseModel} The shared structure with offsets relative to the body.
|
|
296
301
|
*/
|
|
297
|
-
export function parseVerse(body, { inline = false, footnotes = new Set() } = {}) {
|
|
302
|
+
export function parseVerse(body, { inline = false, footnotes = new Set(), sourceOffsets } = {}) {
|
|
298
303
|
const source = String(body ?? "");
|
|
299
304
|
const groups = [],
|
|
300
305
|
boundaries = [],
|
|
@@ -594,6 +599,35 @@ export function parseVerse(body, { inline = false, footnotes = new Set() } = {})
|
|
|
594
599
|
for (const group of groups)
|
|
595
600
|
if (!group.cantoBreak)
|
|
596
601
|
group.label = multi ? `${group.canto}:${group.stanza}` : String(group.stanza);
|
|
602
|
+
if (sourceOffsets) {
|
|
603
|
+
const position = (offset) => sourceOffsets[offset];
|
|
604
|
+
const bounds = (item) => {
|
|
605
|
+
item.from = position(item.from);
|
|
606
|
+
item.to = position(item.to);
|
|
607
|
+
};
|
|
608
|
+
for (const line of new Set([
|
|
609
|
+
...cantos.flatMap((canto) => canto.stanzas.flatMap((stanza) => stanza.lines)),
|
|
610
|
+
...pendingNotes,
|
|
611
|
+
])) {
|
|
612
|
+
bounds(line);
|
|
613
|
+
line.offsets = line.offsets.map(position);
|
|
614
|
+
}
|
|
615
|
+
for (const canto of cantos) for (const stanza of canto.stanzas) bounds(stanza);
|
|
616
|
+
for (const group of groups) bounds(group);
|
|
617
|
+
for (const boundary of boundaries) boundary.sourceOffset = position(boundary.sourceOffset);
|
|
618
|
+
for (const range of layout) {
|
|
619
|
+
range[0] = position(range[0]);
|
|
620
|
+
range[1] = position(range[1]);
|
|
621
|
+
}
|
|
622
|
+
for (const finding of errors) finding.offset = position(finding.offset);
|
|
623
|
+
// Container bytes remain layout in projections that read the authored body.
|
|
624
|
+
let previous = 0;
|
|
625
|
+
for (const offset of sourceOffsets) {
|
|
626
|
+
if (offset > previous) layout.push([previous, offset]);
|
|
627
|
+
previous = offset + 1;
|
|
628
|
+
}
|
|
629
|
+
layout.sort((a, b) => a[0] - b[0] || b[1] - a[1]);
|
|
630
|
+
}
|
|
597
631
|
return { cantos, groups, boundaries, layout, errors, notes: pendingNotes };
|
|
598
632
|
}
|
|
599
633
|
|
|
@@ -816,16 +850,24 @@ export function renderVerse(source, { verses = new Map() } = {}) {
|
|
|
816
850
|
!lines.slice(0, block.start).some((line) => /^ *(?:[-+*]|\d+[.)]) +/.test(line))
|
|
817
851
|
)
|
|
818
852
|
indent = "";
|
|
853
|
+
const openingPrefix = lines[block.start].slice(0, block.column - 1);
|
|
854
|
+
// A list marker belongs only to the opening line; its continuation is spaces.
|
|
855
|
+
const prefix = openingPrefix.replace(/(?:[-+*]|\d+[.)])[ \t]+/g, (mark) =>
|
|
856
|
+
" ".repeat(mark.length),
|
|
857
|
+
);
|
|
858
|
+
const container = prefix.includes(">") || openingPrefix !== prefix;
|
|
859
|
+
const continuation = container ? prefix : indent;
|
|
860
|
+
const blank = continuation.trimEnd();
|
|
819
861
|
output.push(
|
|
820
862
|
"",
|
|
821
|
-
`${indent}<div ${attrs.join(" ")}>`,
|
|
822
|
-
|
|
863
|
+
`${container ? openingPrefix : indent}<div ${attrs.join(" ")}>`,
|
|
864
|
+
blank,
|
|
823
865
|
...verseMarkdown(block.body, { labelled, model, presentation: transported })
|
|
824
866
|
.split("\n")
|
|
825
|
-
.map((line) => (line ?
|
|
826
|
-
|
|
827
|
-
`${
|
|
828
|
-
|
|
867
|
+
.map((line) => (line ? continuation + line : blank)),
|
|
868
|
+
blank,
|
|
869
|
+
`${continuation}</div>`,
|
|
870
|
+
blank,
|
|
829
871
|
);
|
|
830
872
|
cursor = block.end + 1;
|
|
831
873
|
}
|