@heroiclands/package-build 23.12.4 → 23.12.6
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 +28 -0
- package/content-config.mjs +26 -0
- package/docs/api.md +9 -5
- package/docs/authoring/dates-and-calendars.md +29 -1
- package/docs/authoring/frontmatter.md +1 -1
- package/docs/authoring/links-and-markup.md +7 -7
- package/docs/configuration.md +30 -4
- package/docs/guides/book.md +12 -12
- package/docs/guides/module-project.md +1 -1
- package/docs/reference/format-details.md +81 -24
- package/docs/reference/note-types.md +10 -11
- package/engine/content-format.yaml +0 -2
- package/engine/content-index.mjs +23 -41
- package/engine/content-links.mjs +3 -0
- package/engine/event-fields.mjs +24 -1
- package/engine/event-rows.mjs +82 -2
- package/engine/event-views.mjs +74 -17
- package/engine/frontmatter-lint.mjs +11 -0
- package/engine/generated-sections.mjs +8 -1
- package/engine/holdings.mjs +8 -2
- package/engine/index-records.mjs +1 -0
- package/engine/infobox-render.mjs +16 -5
- package/engine/infobox.mjs +27 -10
- package/engine/literature-works.mjs +8 -2
- package/engine/markdown-expressions.mjs +72 -17
- package/engine/metadata-index.mjs +3 -0
- package/engine/name-sort.mjs +91 -0
- package/engine/note-calendars.mjs +314 -0
- package/engine/note-frontmatter.mjs +1 -1
- package/engine/note-vocabulary.mjs +12 -17
- package/engine/page-lists.mjs +3 -2
- package/engine/pdf-index.mjs +11 -4
- package/engine/pdf-pages.mjs +2 -1
- package/engine/pdf-render.mjs +43 -13
- package/engine/pdf-toc.mjs +4 -3
- package/engine/reckoning-markers.mjs +63 -3
- package/engine/related-pages.mjs +12 -2
- package/engine/site-build.mjs +3 -0
- package/engine/sql-tables.mjs +28 -7
- package/package.json +1 -1
- package/types/content-config.d.mts +12 -0
- package/types/engine/content-index.d.mts +9 -32
- package/types/engine/event-fields.d.mts +11 -0
- package/types/engine/event-rows.d.mts +19 -1
- package/types/engine/event-views.d.mts +8 -3
- package/types/engine/generated-sections.d.mts +6 -0
- package/types/engine/holdings.d.mts +7 -1
- package/types/engine/infobox.d.mts +11 -0
- package/types/engine/literature-works.d.mts +5 -0
- package/types/engine/name-sort.d.mts +49 -0
- package/types/engine/note-calendars.d.mts +60 -0
- package/types/engine/pdf-render.d.mts +8 -0
- package/types/engine/pdf-toc.d.mts +1 -1
- package/types/engine/reckoning-markers.d.mts +32 -1
- package/types/engine/related-pages.d.mts +5 -0
|
@@ -209,13 +209,69 @@ export function resolveReckoningMarkers(index, daysPerYear) {
|
|
|
209
209
|
return { eras, markers, findings };
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
/**
|
|
213
|
+
* The key a note's resolved calendar is filed under: its type and shortcode.
|
|
214
|
+
*
|
|
215
|
+
* @param {object|undefined} fm - A note's frontmatter or record.
|
|
216
|
+
* @returns {string|undefined} The key.
|
|
217
|
+
*/
|
|
218
|
+
export function noteCalendarKey(fm) {
|
|
219
|
+
return fm?.type && fm?.shortcode ? `${fm.type}\u0000${fm.shortcode}` : undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* A record's `resolvedCalendar` as its canonical Address string: an Address
|
|
224
|
+
* tuple, as a record in memory carries it, or the string an encoded record
|
|
225
|
+
* carries.
|
|
226
|
+
*
|
|
227
|
+
* @param {unknown} value - The value.
|
|
228
|
+
* @returns {string|undefined} The Address, or `undefined` for none.
|
|
229
|
+
*/
|
|
230
|
+
export function calendarAddress(value) {
|
|
231
|
+
if (typeof value === "string") return value || undefined;
|
|
232
|
+
return value && typeof value === "object" && "shortcode" in value ?
|
|
233
|
+
renderAddress(/** @type {any} */ (value))
|
|
234
|
+
: undefined;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Each note's resolved calendar, by {@link noteCalendarKey}, from the
|
|
239
|
+
* `resolvedCalendar` the content index writes on a record.
|
|
240
|
+
*
|
|
241
|
+
* @param {Iterable<object>|undefined} notes - Index records, or
|
|
242
|
+
* `{ fm, resolvedCalendar }` entries.
|
|
243
|
+
* @returns {Map<string, string>} Note key to the calendar's canonical Address.
|
|
244
|
+
*/
|
|
245
|
+
export function noteCalendarMap(notes) {
|
|
246
|
+
const out = new Map();
|
|
247
|
+
for (const note of notes ?? []) {
|
|
248
|
+
const calendar = calendarAddress(note?.resolvedCalendar);
|
|
249
|
+
if (!calendar) continue;
|
|
250
|
+
const key = noteCalendarKey(note?.fm ?? note?.frontmatter ?? note);
|
|
251
|
+
if (key && !out.has(key)) out.set(key, calendar);
|
|
252
|
+
}
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
255
|
+
|
|
212
256
|
/** One resolved date context per corpus index. */
|
|
213
257
|
const contexts = new WeakMap();
|
|
214
258
|
|
|
215
|
-
/**
|
|
259
|
+
/**
|
|
260
|
+
* Read the world's year length and resolve every calendar era in one corpus.
|
|
261
|
+
*
|
|
262
|
+
* The context also carries `noteCalendars`, each note's resolved calendar by
|
|
263
|
+
* note, read from the `resolvedCalendar` the content index writes on a record;
|
|
264
|
+
* {@link module:engine/note-calendars.noteCalendar} reads it.
|
|
265
|
+
*/
|
|
216
266
|
export function reckoningContext(index) {
|
|
217
267
|
if (!index || typeof index !== "object")
|
|
218
|
-
return {
|
|
268
|
+
return {
|
|
269
|
+
eras: new Map(),
|
|
270
|
+
markers: new Map(),
|
|
271
|
+
daysPerYear: undefined,
|
|
272
|
+
findings: [],
|
|
273
|
+
noteCalendars: new Map(),
|
|
274
|
+
};
|
|
219
275
|
const cached = contexts.get(index);
|
|
220
276
|
if (cached) return cached;
|
|
221
277
|
const world = (index.notes ?? []).find((note) => {
|
|
@@ -224,7 +280,11 @@ export function reckoningContext(index) {
|
|
|
224
280
|
});
|
|
225
281
|
const fm = world?.fm ?? world?.frontmatter ?? world;
|
|
226
282
|
const daysPerYear = fm?.data?.year?.days;
|
|
227
|
-
const context = {
|
|
283
|
+
const context = {
|
|
284
|
+
...resolveReckoningMarkers(index, daysPerYear),
|
|
285
|
+
daysPerYear,
|
|
286
|
+
noteCalendars: noteCalendarMap(index.notes),
|
|
287
|
+
};
|
|
228
288
|
contexts.set(index, context);
|
|
229
289
|
return context;
|
|
230
290
|
}
|
package/engine/related-pages.mjs
CHANGED
|
@@ -49,6 +49,8 @@ import { homepageAddresses } from "./homepage.mjs";
|
|
|
49
49
|
* site-root-relative form the page states for itself.
|
|
50
50
|
* @property {string} [gloss] Why the author recommends this page.
|
|
51
51
|
* @property {string} type The note's `type`, which the theme groups by.
|
|
52
|
+
* @property {string} [sort] The note's `name.sort`. It orders the list in place
|
|
53
|
+
* of `title` and is not published.
|
|
52
54
|
*/
|
|
53
55
|
|
|
54
56
|
/**
|
|
@@ -73,7 +75,10 @@ import { homepageAddresses } from "./homepage.mjs";
|
|
|
73
75
|
* @returns {number}
|
|
74
76
|
*/
|
|
75
77
|
function byTypeThenTitle(a, b) {
|
|
76
|
-
return
|
|
78
|
+
return (
|
|
79
|
+
a.type.localeCompare(b.type, "en") ||
|
|
80
|
+
(a.sort ?? a.title).localeCompare(b.sort ?? b.title, "en")
|
|
81
|
+
);
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
/**
|
|
@@ -118,7 +123,12 @@ export function relatedPages(edges, entries, deliberate = new Map()) {
|
|
|
118
123
|
const list = (urls) =>
|
|
119
124
|
[...urls]
|
|
120
125
|
.map((url) => /** @type {RelatedEntry} */ (entries.get(url)))
|
|
121
|
-
.sort(byTypeThenTitle)
|
|
126
|
+
.sort(byTypeThenTitle)
|
|
127
|
+
.map((entry) => {
|
|
128
|
+
if (entry.sort === undefined) return entry;
|
|
129
|
+
const { sort, ...published } = entry;
|
|
130
|
+
return published;
|
|
131
|
+
});
|
|
122
132
|
|
|
123
133
|
const out = new Map();
|
|
124
134
|
for (const [url, { backlinks, mentions }] of graph) {
|
package/engine/site-build.mjs
CHANGED
|
@@ -1069,6 +1069,9 @@ export function renderPages(pages, options) {
|
|
|
1069
1069
|
title: pageTitle(page),
|
|
1070
1070
|
url: page.url,
|
|
1071
1071
|
type: String(page.fm.type),
|
|
1072
|
+
...(typeof page.fm.name?.sort === "string" && page.fm.name.sort ?
|
|
1073
|
+
{ sort: page.fm.name.sort }
|
|
1074
|
+
: {}),
|
|
1072
1075
|
});
|
|
1073
1076
|
}
|
|
1074
1077
|
for (const home of homepages) {
|
package/engine/sql-tables.mjs
CHANGED
|
@@ -31,7 +31,8 @@ import { collectAnchors } from "./anchors.mjs";
|
|
|
31
31
|
import { expandContentTables } from "./content-tables.mjs";
|
|
32
32
|
import { NOTE_VOCABULARY, isGmNote } from "./note-vocabulary.mjs";
|
|
33
33
|
import { encodeAddresses, flattenPublishedAddresses } from "./address-values.mjs";
|
|
34
|
-
import { EVENT_COLUMNS, EVENT_SORT_COLUMNS, eventRows } from "./event-rows.mjs";
|
|
34
|
+
import { EVENT_COLUMNS, EVENT_SORT_COLUMNS, eventCalendars, eventRows } from "./event-rows.mjs";
|
|
35
|
+
import { reckoningContext } from "./reckoning-markers.mjs";
|
|
35
36
|
import { NAME_COLUMNS, namesRows } from "./name-rows.mjs";
|
|
36
37
|
import {
|
|
37
38
|
GLOSSARY_COLUMNS,
|
|
@@ -66,6 +67,7 @@ import { parseMarkdownFile } from "./helpers.mjs";
|
|
|
66
67
|
import { sqlQueriesInMarkdown } from "./markdown-expressions.mjs";
|
|
67
68
|
// The record accessors only — see `engine/index-records.mjs`.
|
|
68
69
|
import { authoredFrontmatter, isNoteRecord, noteFile, sortKeysDeep } from "./index-records.mjs";
|
|
70
|
+
import { nameSortOf } from "./name-sort.mjs";
|
|
69
71
|
|
|
70
72
|
/** Rendered in a cell whose value is absent. */
|
|
71
73
|
const EMPTY_CELL = "—";
|
|
@@ -438,11 +440,25 @@ export async function openNotesDatabase(
|
|
|
438
440
|
*/
|
|
439
441
|
const WITH_STUBS_SCHEMA = "__with_stubs";
|
|
440
442
|
|
|
443
|
+
/**
|
|
444
|
+
* The record with its `nameSort` stated: the declared `name.sort`, otherwise
|
|
445
|
+
* `name.full`. A record with no name has none.
|
|
446
|
+
*
|
|
447
|
+
* @param {object} record - An index record.
|
|
448
|
+
* @returns {object} The record, with `nameSort` where it has a name.
|
|
449
|
+
*/
|
|
450
|
+
function withNameSort(record) {
|
|
451
|
+
const key = nameSortOf(record);
|
|
452
|
+
return key === undefined ? record : { ...record, nameSort: key };
|
|
453
|
+
}
|
|
454
|
+
|
|
441
455
|
/**
|
|
442
456
|
* Create the two relations an index is read through, and the ladder one of them
|
|
443
457
|
* derives.
|
|
444
458
|
*
|
|
445
|
-
* **`entries` is every row**, stubs included, plus a derived `
|
|
459
|
+
* **`entries` is every row**, stubs included, plus a derived `nameSort` column
|
|
460
|
+
* (the note's `name.sort`, otherwise its `name.full`) and a derived `state`
|
|
461
|
+
* column:
|
|
446
462
|
*
|
|
447
463
|
* ```text
|
|
448
464
|
* address IS NULL → stub
|
|
@@ -484,7 +500,7 @@ async function createRelations(connection, file, records, prefix = "", audience
|
|
|
484
500
|
records
|
|
485
501
|
.map((record) =>
|
|
486
502
|
JSON.stringify({
|
|
487
|
-
record: sortKeysDeep(record),
|
|
503
|
+
record: sortKeysDeep(withNameSort(record)),
|
|
488
504
|
visible: audience !== "public" || !isGmNote(record),
|
|
489
505
|
}),
|
|
490
506
|
)
|
|
@@ -535,9 +551,8 @@ async function createRelations(connection, file, records, prefix = "", audience
|
|
|
535
551
|
* @returns {Promise<void>}
|
|
536
552
|
*/
|
|
537
553
|
async function createEventRelation(connection, file, records, prefix, audience) {
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
);
|
|
554
|
+
const visible = audience === "public" ? records.filter((record) => !isGmNote(record)) : records;
|
|
555
|
+
const rows = eventRows(visible, reckoningContext({ notes: records }), eventCalendars(records));
|
|
541
556
|
if (!rows.length) {
|
|
542
557
|
const columns = EVENT_COLUMNS.map((column) => {
|
|
543
558
|
const type = EVENT_SORT_COLUMNS.includes(column) ? "DOUBLE" : "VARCHAR";
|
|
@@ -549,7 +564,13 @@ async function createEventRelation(connection, file, records, prefix, audience)
|
|
|
549
564
|
return;
|
|
550
565
|
}
|
|
551
566
|
fs.writeFileSync(file, rows.map((row) => JSON.stringify(row)).join("\n"));
|
|
552
|
-
|
|
567
|
+
// `whenIn` and `untilIn` are keyed by calendar Address, and read as structs
|
|
568
|
+
// whatever their number of keys, so `whenIn['<calendar>']` always binds.
|
|
569
|
+
await createTableFromFile(
|
|
570
|
+
connection,
|
|
571
|
+
`${prefix}events`,
|
|
572
|
+
readJsonAuto(file).replace(/\)$/, ", map_inference_threshold=-1)"),
|
|
573
|
+
);
|
|
553
574
|
}
|
|
554
575
|
|
|
555
576
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "23.12.
|
|
3
|
+
"version": "23.12.6",
|
|
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",
|
|
@@ -637,6 +637,13 @@ export type ContentBuildConfigInput = {
|
|
|
637
637
|
* Setting-wide neutral current date; defaults to "720.1".
|
|
638
638
|
*/
|
|
639
639
|
currentDate?: string | undefined;
|
|
640
|
+
/**
|
|
641
|
+
* Shortcode of the `lore` note with
|
|
642
|
+
* `subType: calendar` that prints the
|
|
643
|
+
* dates of a note no other rule gives a
|
|
644
|
+
* calendar.
|
|
645
|
+
*/
|
|
646
|
+
defaultCalendar?: string | undefined;
|
|
640
647
|
/**
|
|
641
648
|
* Content package name — the address
|
|
642
649
|
* namespace every note in this
|
|
@@ -761,6 +768,11 @@ export type ContentBuildConfig = {
|
|
|
761
768
|
* Setting-wide neutral current date.
|
|
762
769
|
*/
|
|
763
770
|
currentDate: string;
|
|
771
|
+
/**
|
|
772
|
+
* The calendar of last resort's
|
|
773
|
+
* shortcode, `null` when none is named.
|
|
774
|
+
*/
|
|
775
|
+
defaultCalendar: string | null;
|
|
764
776
|
rootDir: string;
|
|
765
777
|
contentPackage: string;
|
|
766
778
|
/**
|
|
@@ -31,36 +31,6 @@ export function noteAddress(frontmatter: Record<string, any>, contentPackage: st
|
|
|
31
31
|
slug: string;
|
|
32
32
|
canonical: string;
|
|
33
33
|
} | null;
|
|
34
|
-
/**
|
|
35
|
-
* A note's display name reduced to printable 7-bit ASCII.
|
|
36
|
-
*
|
|
37
|
-
* Content names carry the setting's orthography — `Kûrbúl Helm`, `Hârn`,
|
|
38
|
-
* `Kèthîra` — and nobody types them. A reader searching the index, or an editor
|
|
39
|
-
* completing a wikilink, needs a form that matches what a keyboard produces, so
|
|
40
|
-
* the record states one rather than leaving every consumer to invent it (and to
|
|
41
|
-
* invent a *different* one, which is how two searches over the same data come
|
|
42
|
-
* to disagree).
|
|
43
|
-
*
|
|
44
|
-
* **Transliterated, not stripped.** `unidecode` — the same table
|
|
45
|
-
* {@link slugify} already runs, so an ASCII name and a slug can never disagree
|
|
46
|
-
* about a character — carries a letter across rather than deleting it:
|
|
47
|
-
* diacritics fold (`â`→`a`, `è`→`e`), ligatures expand (`æ`→`ae`, `Œ`→`OE`,
|
|
48
|
-
* `ß`→`ss`), the runic letters spell out (`þ`→`th`, `Þ`→`Th`, `ð`→`d`), and
|
|
49
|
-
* even a vulgar fraction becomes readable (`¾`→`3/4`). Deleting them instead
|
|
50
|
-
* would collapse `Kûrbúl` to `Krbl`, which is worse than the original.
|
|
51
|
-
*
|
|
52
|
-
* Anything still outside printable ASCII after that becomes a space, and runs
|
|
53
|
-
* of whitespace collapse — a space rather than nothing, so a character that
|
|
54
|
-
* transliterates away cannot silently weld two words together.
|
|
55
|
-
*
|
|
56
|
-
* The value is emitted even when it equals the name, so a consumer matching on
|
|
57
|
-
* it never has to branch on whether the name happened to be ASCII already.
|
|
58
|
-
*
|
|
59
|
-
* @param {unknown} name - The note's `name.full`.
|
|
60
|
-
* @returns {string|null} The ASCII form, or `null` when there is no name, or
|
|
61
|
-
* nothing printable survives.
|
|
62
|
-
*/
|
|
63
|
-
export function asciiName(name: unknown): string | null;
|
|
64
34
|
/**
|
|
65
35
|
* A note's `name.aliases` reduced to printable 7-bit ASCII, in order.
|
|
66
36
|
*
|
|
@@ -235,6 +205,10 @@ export function resolveCultures(records: Array<Record<string, any>>, { contentPa
|
|
|
235
205
|
* reader receives each as a finding, and the emitter throws once, listing
|
|
236
206
|
* every one.
|
|
237
207
|
* @param {string} [options.currentDate="720.1"] - Setting-wide neutral current date.
|
|
208
|
+
* @param {string} [options.defaultCalendar] - The calendar of last resort, a
|
|
209
|
+
* calendar note's shortcode; see {@link module:engine/note-calendars}.
|
|
210
|
+
* @param {string} [options.configFile] - The configuration file, where an
|
|
211
|
+
* unknown `defaultCalendar` is reported.
|
|
238
212
|
* @param {(key: string) => ({culture?: unknown}|undefined)} [options.foreignRecord] -
|
|
239
213
|
* Another package's entry for a canonical address, read for a parent that
|
|
240
214
|
* package publishes; see {@link resolveCultures}.
|
|
@@ -247,7 +221,7 @@ export function resolveCultures(records: Array<Record<string, any>>, { contentPa
|
|
|
247
221
|
* @returns {Array<Record<string, any>>} The records, in a total order that does
|
|
248
222
|
* not depend on directory-read order.
|
|
249
223
|
*/
|
|
250
|
-
export function collectContentIndex(contentBase: string, { contentPackage, currentDate, skipDirectories, assetsBase, manifest, problems, addressContext, foreignRecord, parsed, sources, syntax, nameTypes, }: {
|
|
224
|
+
export function collectContentIndex(contentBase: string, { contentPackage, currentDate, defaultCalendar, configFile, skipDirectories, assetsBase, manifest, problems, addressContext, foreignRecord, parsed, sources, syntax, nameTypes, }: {
|
|
251
225
|
contentPackage: string;
|
|
252
226
|
skipDirectories: readonly string[];
|
|
253
227
|
assetsBase?: string | undefined;
|
|
@@ -255,6 +229,8 @@ export function collectContentIndex(contentBase: string, { contentPackage, curre
|
|
|
255
229
|
addressContext?: object | undefined;
|
|
256
230
|
problems?: object[] | undefined;
|
|
257
231
|
currentDate?: string | undefined;
|
|
232
|
+
defaultCalendar?: string | undefined;
|
|
233
|
+
configFile?: string | undefined;
|
|
258
234
|
foreignRecord?: ((key: string) => ({
|
|
259
235
|
culture?: unknown;
|
|
260
236
|
} | undefined)) | undefined;
|
|
@@ -449,6 +425,7 @@ export type PreparedContentIndex = {
|
|
|
449
425
|
*/
|
|
450
426
|
syntax?: import("./content-session.mjs").ContentSyntax | undefined;
|
|
451
427
|
};
|
|
428
|
+
import { asciiName } from "./name-sort.mjs";
|
|
452
429
|
import { collectAnchors } from "./anchors.mjs";
|
|
453
430
|
import { authoredFrontmatter } from "./index-records.mjs";
|
|
454
431
|
import { DERIVED_KEYS } from "./index-records.mjs";
|
|
@@ -458,4 +435,4 @@ import { isStub } from "./index-records.mjs";
|
|
|
458
435
|
import { noteFile } from "./index-records.mjs";
|
|
459
436
|
import { recordPath } from "./index-records.mjs";
|
|
460
437
|
import { sortKeysDeep } from "./index-records.mjs";
|
|
461
|
-
export { collectAnchors, authoredFrontmatter, DERIVED_KEYS, isAssetRecord, isNoteRecord, isStub, noteFile, recordPath, sortKeysDeep };
|
|
438
|
+
export { asciiName, collectAnchors, authoredFrontmatter, DERIVED_KEYS, isAssetRecord, isNoteRecord, isStub, noteFile, recordPath, sortKeysDeep };
|
|
@@ -46,6 +46,17 @@ export function eventNoteIndex(records: Iterable<Record<string, any>>, { content
|
|
|
46
46
|
* @returns {string} The printed date.
|
|
47
47
|
*/
|
|
48
48
|
export function printEventDate(written: unknown, date: object | null, dates?: object): string;
|
|
49
|
+
/**
|
|
50
|
+
* A date as `dateformat` prints it in a calendar, where one is named and
|
|
51
|
+
* prints it; otherwise as {@link printEventDate} prints it.
|
|
52
|
+
*
|
|
53
|
+
* @param {unknown} written - The date as the event writes it.
|
|
54
|
+
* @param {object|null} date - The resolved date.
|
|
55
|
+
* @param {object} [dates] - The reckoning context.
|
|
56
|
+
* @param {string} [calendar] - The calendar's Address or shortcode.
|
|
57
|
+
* @returns {string} The printed date; the authored text when nothing resolves.
|
|
58
|
+
*/
|
|
59
|
+
export function printEventDateIn(written: unknown, date: object | null, dates?: object, calendar?: string): string;
|
|
49
60
|
/**
|
|
50
61
|
* One field of one event, as the text a reference prints, or why it cannot.
|
|
51
62
|
*
|
|
@@ -1,12 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every calendar a set of records can print an event's date in: each calendar
|
|
3
|
+
* note's canonical Address, and every calendar a note resolves to.
|
|
4
|
+
*
|
|
5
|
+
* @param {Iterable<Record<string, any>>} records - Index records, every
|
|
6
|
+
* Address a string.
|
|
7
|
+
* @returns {string[]} The calendars, sorted.
|
|
8
|
+
*/
|
|
9
|
+
export function eventCalendars(records: Iterable<Record<string, any>>): string[];
|
|
1
10
|
/**
|
|
2
11
|
* The `events` rows of a set of index records.
|
|
3
12
|
*
|
|
4
13
|
* @param {Iterable<Record<string, any>>} records - Index records, every
|
|
5
14
|
* Address a string, already narrowed to the audience being published to.
|
|
15
|
+
* @param {object} [dates] - The reckoning context the dates are printed in.
|
|
16
|
+
* @param {readonly string[]} [calendars] - The calendars `whenIn` and
|
|
17
|
+
* `untilIn` are keyed by: {@link eventCalendars} of every record of the
|
|
18
|
+
* corpus, whatever the audience, so every surface's relation has the same
|
|
19
|
+
* keys. Absent, those of `records`.
|
|
6
20
|
* @returns {object[]} One row per event entry, in record then entry order.
|
|
7
21
|
*/
|
|
8
|
-
export function eventRows(records: Iterable<Record<string, any
|
|
22
|
+
export function eventRows(records: Iterable<Record<string, any>>, dates?: object, calendars?: readonly string[]): object[];
|
|
9
23
|
/** The sort keys a row carries, after the event's own keys. */
|
|
10
24
|
export const EVENT_SORT_COLUMNS: readonly string[];
|
|
25
|
+
/** The printed dates a row carries, after the sort keys. */
|
|
26
|
+
export const EVENT_TEXT_COLUMNS: readonly string[];
|
|
27
|
+
/** The dates a row carries in every calendar, after the printed ones. */
|
|
28
|
+
export const EVENT_CALENDAR_COLUMNS: readonly string[];
|
|
11
29
|
/** Every column of the `events` relation, in order. */
|
|
12
30
|
export const EVENT_COLUMNS: any;
|
|
@@ -34,18 +34,23 @@ export function eventViewSections(db: object, records: Iterable<object>, fileOf:
|
|
|
34
34
|
*
|
|
35
35
|
* @type {ReadonlyArray<Readonly<{slug: string, heading: string,
|
|
36
36
|
* applies: (record: object) => boolean,
|
|
37
|
-
* query: (self: string[], shape: CorpusShape) => {fence: string, own?: string}|null,
|
|
38
|
-
* owners?: (db: object, selves: string[][], shape: CorpusShape) => Promise<Set<string
|
|
37
|
+
* query: (self: string[], shape: CorpusShape, columns?: {felt?: boolean, calendar?: string}) => {fence: string, own?: string}|null,
|
|
38
|
+
* owners?: (db: object, selves: string[][], shape: CorpusShape) => Promise<Set<string>>,
|
|
39
|
+
* felt?: (db: object, selves: string[][], shape: CorpusShape) => Promise<Set<string>>}>>}
|
|
39
40
|
*/
|
|
40
41
|
export const EVENT_VIEWS: ReadonlyArray<Readonly<{
|
|
41
42
|
slug: string;
|
|
42
43
|
heading: string;
|
|
43
44
|
applies: (record: object) => boolean;
|
|
44
|
-
query: (self: string[], shape: CorpusShape
|
|
45
|
+
query: (self: string[], shape: CorpusShape, columns?: {
|
|
46
|
+
felt?: boolean;
|
|
47
|
+
calendar?: string;
|
|
48
|
+
}) => {
|
|
45
49
|
fence: string;
|
|
46
50
|
own?: string;
|
|
47
51
|
} | null;
|
|
48
52
|
owners?: (db: object, selves: string[][], shape: CorpusShape) => Promise<Set<string>>;
|
|
53
|
+
felt?: (db: object, selves: string[][], shape: CorpusShape) => Promise<Set<string>>;
|
|
49
54
|
}>>;
|
|
50
55
|
export type CorpusShape = {
|
|
51
56
|
/**
|
|
@@ -110,6 +110,12 @@ export function splitGenerated(markdown: string): {
|
|
|
110
110
|
*
|
|
111
111
|
* @module
|
|
112
112
|
*/
|
|
113
|
+
/**
|
|
114
|
+
* The anchor of the Chronology section, which the book sets across the page.
|
|
115
|
+
*
|
|
116
|
+
* @type {string}
|
|
117
|
+
*/
|
|
118
|
+
export const CHRONOLOGY_SLUG: string;
|
|
113
119
|
/**
|
|
114
120
|
* Every generated section, in the order it follows the author's text: where a
|
|
115
121
|
* place sits and who governs it, then what happened, then what was written
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
/** @typedef {{title:string,url?:string,address:string,type:string,subType?:string}} HoldingsEntry */
|
|
1
|
+
/** @typedef {{title:string,sort?:string,url?:string,address:string,type:string,subType?:string}} HoldingsEntry */
|
|
2
2
|
/**
|
|
3
3
|
* @typedef {object} HoldingsNode
|
|
4
4
|
* @property {string} shortcode
|
|
5
5
|
* @property {"place"|"affiliation"} type
|
|
6
6
|
* @property {string} [subType]
|
|
7
7
|
* @property {string} title
|
|
8
|
+
* @property {string} [sort] The note's `name.sort`; the list files by it in place of `title`.
|
|
8
9
|
* @property {string} [url]
|
|
9
10
|
* @property {string} [package] Owning content package.
|
|
10
11
|
* @property {string} [system] Own document system.
|
|
@@ -60,6 +61,7 @@ export function holdingsPages(nodes: Iterable<HoldingsNode | null>, { government
|
|
|
60
61
|
export function checkGovernment(note: object): object[];
|
|
61
62
|
export type HoldingsEntry = {
|
|
62
63
|
title: string;
|
|
64
|
+
sort?: string;
|
|
63
65
|
url?: string;
|
|
64
66
|
address: string;
|
|
65
67
|
type: string;
|
|
@@ -70,6 +72,10 @@ export type HoldingsNode = {
|
|
|
70
72
|
type: "place" | "affiliation";
|
|
71
73
|
subType?: string | undefined;
|
|
72
74
|
title: string;
|
|
75
|
+
/**
|
|
76
|
+
* The note's `name.sort`; the list files by it in place of `title`.
|
|
77
|
+
*/
|
|
78
|
+
sort?: string | undefined;
|
|
73
79
|
url?: string | undefined;
|
|
74
80
|
/**
|
|
75
81
|
* Owning content package.
|
|
@@ -94,6 +94,17 @@ export function hasRenderableValue(kind: string, value: unknown): boolean;
|
|
|
94
94
|
* @returns {string} One of {@link INFOBOX_VALUE_KINDS}.
|
|
95
95
|
*/
|
|
96
96
|
export function valueKindOf(field: object, value: unknown): string;
|
|
97
|
+
/**
|
|
98
|
+
* A number as a reader reads it in a profile: under a million in full with
|
|
99
|
+
* thousands separators (`850`, `12,500`), and from a million up in words with
|
|
100
|
+
* at most one decimal (`11 million`, `1.2 million`, `3 billion`). Every figure
|
|
101
|
+
* the setting gives at that scale is an estimate, and the words say so.
|
|
102
|
+
* Decimals below a million keep the digits they were written with.
|
|
103
|
+
*
|
|
104
|
+
* @param {number} value - The number.
|
|
105
|
+
* @returns {string} The text the row shows.
|
|
106
|
+
*/
|
|
107
|
+
export function formatQuantity(value: number): string;
|
|
97
108
|
/**
|
|
98
109
|
* One row, with its unit on it.
|
|
99
110
|
*
|
|
@@ -38,6 +38,7 @@ export function worksPages(nodes: Iterable<WorksNode | null>, { types }: {
|
|
|
38
38
|
}>;
|
|
39
39
|
export type WorkEntry = {
|
|
40
40
|
title: string;
|
|
41
|
+
sort?: string;
|
|
41
42
|
url?: string;
|
|
42
43
|
address: string;
|
|
43
44
|
form?: string;
|
|
@@ -48,6 +49,10 @@ export type WorksNode = {
|
|
|
48
49
|
*/
|
|
49
50
|
key: string;
|
|
50
51
|
title: string;
|
|
52
|
+
/**
|
|
53
|
+
* The note's `name.sort`; the list files by it in place of `title`.
|
|
54
|
+
*/
|
|
55
|
+
sort?: string | undefined;
|
|
51
56
|
/**
|
|
52
57
|
* Absent for a note that publishes no page.
|
|
53
58
|
*/
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A note's display name reduced to printable 7-bit ASCII.
|
|
3
|
+
*
|
|
4
|
+
* Content names carry the setting's orthography — `Kûrbúl Helm`, `Hârn`,
|
|
5
|
+
* `Kèthîra` — and nobody types them. A reader searching the index, or an editor
|
|
6
|
+
* completing a wikilink, needs a form that matches what a keyboard produces, so
|
|
7
|
+
* the record states one rather than leaving every consumer to invent it (and to
|
|
8
|
+
* invent a *different* one, which is how two searches over the same data come
|
|
9
|
+
* to disagree).
|
|
10
|
+
*
|
|
11
|
+
* **Transliterated, not stripped.** `unidecode` — the same table
|
|
12
|
+
* {@link slugify} already runs, so an ASCII name and a slug can never disagree
|
|
13
|
+
* about a character — carries a letter across rather than deleting it:
|
|
14
|
+
* diacritics fold (`â`→`a`, `è`→`e`), ligatures expand (`æ`→`ae`, `Œ`→`OE`,
|
|
15
|
+
* `ß`→`ss`), the runic letters spell out (`þ`→`th`, `Þ`→`Th`, `ð`→`d`), and
|
|
16
|
+
* even a vulgar fraction becomes readable (`¾`→`3/4`). Deleting them instead
|
|
17
|
+
* would collapse `Kûrbúl` to `Krbl`, which is worse than the original.
|
|
18
|
+
*
|
|
19
|
+
* Anything still outside printable ASCII after that becomes a space, and runs
|
|
20
|
+
* of whitespace collapse — a space rather than nothing, so a character that
|
|
21
|
+
* transliterates away cannot silently weld two words together.
|
|
22
|
+
*
|
|
23
|
+
* The value is emitted even when it equals the name, so a consumer matching on
|
|
24
|
+
* it never has to branch on whether the name happened to be ASCII already.
|
|
25
|
+
*
|
|
26
|
+
* @param {unknown} name - The note's `name.full`.
|
|
27
|
+
* @returns {string|null} The ASCII form, or `null` when there is no name, or
|
|
28
|
+
* nothing printable survives.
|
|
29
|
+
*/
|
|
30
|
+
export function asciiName(name: unknown): string | null;
|
|
31
|
+
/**
|
|
32
|
+
* The name a note files under: its `name.sort` when it declares one, otherwise
|
|
33
|
+
* its `name.full`.
|
|
34
|
+
*
|
|
35
|
+
* @param {unknown} record - An index record or a note's frontmatter.
|
|
36
|
+
* @returns {string|undefined} The unfolded key, or `undefined` for a record
|
|
37
|
+
* with no name.
|
|
38
|
+
*/
|
|
39
|
+
export function nameSortOf(record: unknown): string | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* The key a name-ordered list compares notes on: {@link nameSortOf} folded to
|
|
42
|
+
* printable ASCII by {@link asciiName}, so an accented sort key files beside
|
|
43
|
+
* its base letters. Case is left for the caller, which decides whether its
|
|
44
|
+
* comparison ignores it.
|
|
45
|
+
*
|
|
46
|
+
* @param {unknown} record - An index record or a note's frontmatter.
|
|
47
|
+
* @returns {string} The key, or "" for a record with no name.
|
|
48
|
+
*/
|
|
49
|
+
export function nameSortKey(record: unknown): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write each note's resolved calendar into its record, as `resolvedCalendar`.
|
|
3
|
+
*
|
|
4
|
+
* Reads each note's authored `data.calendar`, `data.culture` and
|
|
5
|
+
* `data.parents`, so it runs before the index writes a resolved culture over
|
|
6
|
+
* `data.culture`. A culture or parent in another package contributes the
|
|
7
|
+
* calendar its published entry states in `data.calendar`, and nothing else.
|
|
8
|
+
*
|
|
9
|
+
* @param {Array<Record<string, any>>} records - Index records, every Address a
|
|
10
|
+
* tuple. Records that are not notes are passed over.
|
|
11
|
+
* @param {object} options - Options.
|
|
12
|
+
* @param {string} options.contentPackage - The package the records belong to.
|
|
13
|
+
* @param {string} [options.defaultCalendar] - The configured calendar of last
|
|
14
|
+
* resort, a calendar note's shortcode.
|
|
15
|
+
* @param {string} [options.configFile] - The configuration file, where an
|
|
16
|
+
* unknown `defaultCalendar` is reported.
|
|
17
|
+
* @param {string} [options.contentBase] - The content tree, for opening a note
|
|
18
|
+
* to locate a finding.
|
|
19
|
+
* @param {(key: string) => Record<string, any>|undefined} [options.foreignRecord] -
|
|
20
|
+
* Another package's entry for a canonical address.
|
|
21
|
+
* @param {object[]} [options.problems] - Receives each finding. Omitted, the
|
|
22
|
+
* findings are collected and one Error lists them all.
|
|
23
|
+
* @param {Map<string, {raw: string}>} [options.parsed] - Notes already read, keyed by file.
|
|
24
|
+
* @returns {Array<Record<string, any>>} The same records.
|
|
25
|
+
* @throws {Error} When `problems` is omitted and anything is reported.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveNoteCalendars(records: Array<Record<string, any>>, { contentPackage, defaultCalendar, configFile, contentBase, foreignRecord, problems, parsed }: {
|
|
28
|
+
contentPackage: string;
|
|
29
|
+
defaultCalendar?: string | undefined;
|
|
30
|
+
configFile?: string | undefined;
|
|
31
|
+
contentBase?: string | undefined;
|
|
32
|
+
foreignRecord?: ((key: string) => Record<string, any> | undefined) | undefined;
|
|
33
|
+
problems?: object[] | undefined;
|
|
34
|
+
parsed?: Map<string, {
|
|
35
|
+
raw: string;
|
|
36
|
+
}> | undefined;
|
|
37
|
+
}): Array<Record<string, any>>;
|
|
38
|
+
/**
|
|
39
|
+
* The configuration file in a package root, where a configured value is
|
|
40
|
+
* reported.
|
|
41
|
+
*
|
|
42
|
+
* @param {string|undefined} rootDir - The package root.
|
|
43
|
+
* @param {readonly string[]} names - The file names a configuration may take.
|
|
44
|
+
* @returns {string|undefined} The first that exists.
|
|
45
|
+
*/
|
|
46
|
+
export function configFileIn(rootDir: string | undefined, names: readonly string[]): string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* The calendar a note's dates print in: the one the content index resolved for
|
|
49
|
+
* it, where the reckoning context carries it, else the note's own
|
|
50
|
+
* `data.calendar`.
|
|
51
|
+
*
|
|
52
|
+
* @param {object|undefined} fm - The note's frontmatter.
|
|
53
|
+
* @param {{noteCalendars?: Map<string, string>}} [dates] - The reckoning context.
|
|
54
|
+
* @returns {string|undefined} The calendar's Address or shortcode.
|
|
55
|
+
*/
|
|
56
|
+
export function noteCalendar(fm: object | undefined, dates?: {
|
|
57
|
+
noteCalendars?: Map<string, string>;
|
|
58
|
+
}): string | undefined;
|
|
59
|
+
/** The configuration key naming the calendar of last resort. */
|
|
60
|
+
export const DEFAULT_CALENDAR_KEY: "defaultCalendar";
|
|
@@ -169,6 +169,10 @@ export function absolutePageUrl(url: string, site?: string): string;
|
|
|
169
169
|
* carried, then the book and the page. A book with an abbreviation reads
|
|
170
170
|
* `Name (Aelw. p12)`; one without reads `Name (_Book title_, p. 12)`.
|
|
171
171
|
*
|
|
172
|
+
* A link the text already closes a bracket on, as in `(see Name)`, takes the
|
|
173
|
+
* reference inside that bracket after a comma, `(see Name, Aelw. p12)`, so the
|
|
174
|
+
* brackets never nest.
|
|
175
|
+
*
|
|
172
176
|
* The space before the page number, or between the abbreviation and the page,
|
|
173
177
|
* does not break, so a number never starts a line of its own.
|
|
174
178
|
*
|
|
@@ -176,12 +180,16 @@ export function absolutePageUrl(url: string, site?: string): string;
|
|
|
176
180
|
* @param {{title: string, abbreviation?: string, page: string|number}} away -
|
|
177
181
|
* The home book's title, its abbreviation when it declares one, and the page
|
|
178
182
|
* the entry starts on.
|
|
183
|
+
* @param {{bracketed?: boolean}} [opts] - `bracketed` when the text closes a
|
|
184
|
+
* bracket right after the link.
|
|
179
185
|
* @returns {string} Typst markup.
|
|
180
186
|
*/
|
|
181
187
|
export function crossBookReference(inner: string, away: {
|
|
182
188
|
title: string;
|
|
183
189
|
abbreviation?: string;
|
|
184
190
|
page: string | number;
|
|
191
|
+
}, { bracketed }?: {
|
|
192
|
+
bracketed?: boolean;
|
|
185
193
|
}): string;
|
|
186
194
|
/**
|
|
187
195
|
* The Typst definitions the book's page furniture is drawn with.
|
|
@@ -72,7 +72,7 @@ export function runTreeFilters(nodes: object[], db: {
|
|
|
72
72
|
* without a renderer or a PDF. Only how the result *looks* needs eyes.
|
|
73
73
|
*
|
|
74
74
|
* **Notes are sorted, prose is not.** A section's entries come out in
|
|
75
|
-
*
|
|
75
|
+
* name-sort order (`name.sort`, else `name.full`, folded to ASCII), so a
|
|
76
76
|
* circumflex sorts with its letter instead of after `Z` as a raw codepoint
|
|
77
77
|
* comparison would put it. Prose keeps the position it was written in, because
|
|
78
78
|
* its place in the sequence is the author's statement.
|