@heroiclands/package-build 22.7.0 → 22.8.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 +33 -0
- package/content-config.mjs +78 -0
- package/docs/api.md +22 -0
- package/docs/configuration.md +57 -3
- package/docs/content-format.md +28 -2
- package/engine/calendars.mjs +132 -0
- package/engine/content-format-check.mjs +10 -1
- package/engine/draft-notice.mjs +45 -13
- package/engine/frontmatter-lint.mjs +26 -0
- package/engine/index.mjs +6 -0
- package/engine/infobox.mjs +7 -2
- package/engine/note-dates.mjs +344 -0
- package/engine/note-vocabulary.mjs +26 -2
- package/engine/pdf-render.mjs +10 -0
- package/engine/retired-fields.mjs +58 -8
- package/package.json +1 -1
- package/types/content-config.d.mts +10 -0
- package/types/engine/calendars.d.mts +115 -0
- package/types/engine/draft-notice.d.mts +26 -4
- package/types/engine/index.d.mts +2 -0
- package/types/engine/note-dates.d.mts +70 -0
- package/types/engine/retired-fields.d.mts +23 -2
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The year on the canonical axis that a written year lands on.
|
|
3
|
+
*
|
|
4
|
+
* The whole of the conversion. A reckoning differs from its neighbours in
|
|
5
|
+
* where its year 1 sits and in nothing else, so an era states one number and
|
|
6
|
+
* this states what to do with it.
|
|
7
|
+
*
|
|
8
|
+
* The two branches are the year-zero hole: the authored numbering has no year
|
|
9
|
+
* 0, so a positive year is one place closer to the epoch than its magnitude
|
|
10
|
+
* suggests and a negative year is exactly its magnitude below it.
|
|
11
|
+
*
|
|
12
|
+
* @param {number} year - The year as authored, signed, within its own
|
|
13
|
+
* reckoning. Never 0 — the authored numbering has no such year.
|
|
14
|
+
* @param {number} [epoch] - Where that reckoning's year 1 sits on the axis.
|
|
15
|
+
* Omitted, the axis itself.
|
|
16
|
+
* @returns {number} The signed year on the axis — authored `-1` is `0`.
|
|
17
|
+
*/
|
|
18
|
+
export function canonicalYear(year: number, epoch?: number): number;
|
|
19
|
+
/**
|
|
20
|
+
* The total-order key a date sorts on.
|
|
21
|
+
*
|
|
22
|
+
* Month and day fold into the fraction, so a mixed-precision list sorts stably
|
|
23
|
+
* against a single number instead of a three-key comparator. The arithmetic is
|
|
24
|
+
* done in ten-thousandths and divided once, because `year + 6 / 100 + 19 /
|
|
25
|
+
* 10000` accumulates a rounding error and `689.0619` is a value a reader
|
|
26
|
+
* compares against by eye.
|
|
27
|
+
*
|
|
28
|
+
* A coarser value sorts **before** the finer values inside it: `689` precedes
|
|
29
|
+
* `689/1`, which is the ordering a reader expects of a year whose month nobody
|
|
30
|
+
* wrote.
|
|
31
|
+
*
|
|
32
|
+
* @param {number} year - The year on the canonical axis.
|
|
33
|
+
* @param {number|null} month - The month, or `null`.
|
|
34
|
+
* @param {number|null} day - The day, or `null`.
|
|
35
|
+
* @returns {number} The sort key.
|
|
36
|
+
*/
|
|
37
|
+
export function dateSortKey(year: number, month: number | null, day: number | null): number;
|
|
38
|
+
/**
|
|
39
|
+
* The bounds a package states for a month, or `null` where it states none.
|
|
40
|
+
*
|
|
41
|
+
* Separate from the check that reads it so a caller can ask what a package
|
|
42
|
+
* declares without phrasing a finding about it.
|
|
43
|
+
*
|
|
44
|
+
* @param {{months?: number|null, monthDays?: number|null}} [calendar] - The
|
|
45
|
+
* resolved `calendar:` block.
|
|
46
|
+
* @returns {{months: number|null, monthDays: number|null}} What it declares.
|
|
47
|
+
*/
|
|
48
|
+
export function calendarStructure(calendar?: {
|
|
49
|
+
months?: number | null;
|
|
50
|
+
monthDays?: number | null;
|
|
51
|
+
}): {
|
|
52
|
+
months: number | null;
|
|
53
|
+
monthDays: number | null;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* **The axis every date normalises onto, and the one function that puts a
|
|
57
|
+
* reckoning on it.**
|
|
58
|
+
*
|
|
59
|
+
* A setting keeps several counts of years at once, each proclaimed by a body
|
|
60
|
+
* that has a note — a founding, a first occasion, a reign. A date either names
|
|
61
|
+
* one of them or names none, in which case it sits on the **canonical axis**.
|
|
62
|
+
*
|
|
63
|
+
* ## The canonical axis is an authoring device, not a calendar
|
|
64
|
+
*
|
|
65
|
+
* The axis is the line of integers dates written by peoples who never met are
|
|
66
|
+
* compared on. Nobody in a setting keeps it, no body stands behind it, and no
|
|
67
|
+
* shortcode addresses it: a date is on the axis exactly when it names no era.
|
|
68
|
+
* A reckoning whose year 1 happens to sit where the axis's year 1 sits
|
|
69
|
+
* coincides with it numerically and is still that people's own count.
|
|
70
|
+
*
|
|
71
|
+
* ## There is no year zero, and one function converts
|
|
72
|
+
*
|
|
73
|
+
* The authored numbering skips zero — `-1` sits immediately before `1` — while
|
|
74
|
+
* the normalised value does not, so that arithmetic across an epoch is one
|
|
75
|
+
* subtraction rather than a special case somebody forgets.
|
|
76
|
+
*
|
|
77
|
+
* ```text
|
|
78
|
+
* authored … -3 -2 -1 1 2 3 …
|
|
79
|
+
* canonicalYear … -2 -1 0 1 2 3 …
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* ```text
|
|
83
|
+
* canonicalYear(y, E) = E + (y > 0 ? y - 1 : y)
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* `E` is where a reckoning's year 1 sits on the axis — the `canonicalYear` of
|
|
87
|
+
* an era's own `start`. The axis is the same function with `E` of
|
|
88
|
+
* {@link CANONICAL_EPOCH}, which holds by definition: the axis's year 1 is the
|
|
89
|
+
* axis's year 1.
|
|
90
|
+
*
|
|
91
|
+
* **The conversion is piecewise on the sign**, and that is the property worth
|
|
92
|
+
* guarding. Collapsed to a single branch it is correct on one side of an epoch
|
|
93
|
+
* and off by one on the other, which is invisible in any list that does not
|
|
94
|
+
* span it.
|
|
95
|
+
*
|
|
96
|
+
* ## The month structure is the package's, and an absent bound checks nothing
|
|
97
|
+
*
|
|
98
|
+
* An era moves where year 1 sits; it does not divide the year differently. So
|
|
99
|
+
* the subdivision is declared once for the package, in `calendar.months` and
|
|
100
|
+
* `calendar.monthDays`, and a package that declares neither is bounded below
|
|
101
|
+
* and not above — a toolchain that invented twelve Gregorian months would
|
|
102
|
+
* refuse `667/2/30`, which is an ordinary date in a year of twelve thirty-day
|
|
103
|
+
* months.
|
|
104
|
+
*
|
|
105
|
+
* @module
|
|
106
|
+
*/
|
|
107
|
+
/**
|
|
108
|
+
* Where the canonical axis's own year 1 sits — on itself.
|
|
109
|
+
*
|
|
110
|
+
* It takes no configuration entry and no era row, because it is true by
|
|
111
|
+
* definition rather than by declaration.
|
|
112
|
+
*
|
|
113
|
+
* @type {number}
|
|
114
|
+
*/
|
|
115
|
+
export const CANONICAL_EPOCH: number;
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* `body.game .app blockquote` carries a left rule and an indent — so the
|
|
6
6
|
* treatment survives with no stylesheet of this package's own. The mark is a
|
|
7
7
|
* Font Awesome element, which Foundry bundles; `aria-hidden` keeps it out of a
|
|
8
|
-
* screen reader, where the label reads it.
|
|
8
|
+
* screen reader, where the label reads it. The sentence is an `em`, which every
|
|
9
|
+
* renderer of HTML sets in italic with no stylesheet to ask.
|
|
9
10
|
*
|
|
10
11
|
* @returns {string} The notice's HTML.
|
|
11
12
|
*/
|
|
@@ -41,9 +42,30 @@ export function withDraftNotice(fm: object | null | undefined, html: string): st
|
|
|
41
42
|
/**
|
|
42
43
|
* The `#let` the book's notice is drawn by.
|
|
43
44
|
*
|
|
44
|
-
* Emitted with the rest of the book's preamble. The mark is a
|
|
45
|
-
* exclamation
|
|
46
|
-
* page, so there is no font to resolve and nothing that can come out as a
|
|
45
|
+
* Emitted with the rest of the book's preamble. The mark is a disc with an
|
|
46
|
+
* exclamation knocked out of it: two primitives and the face already setting
|
|
47
|
+
* the page, so there is no font to resolve and nothing that can come out as a
|
|
48
|
+
* box. It is filled in the accent the book rules its headings with and the
|
|
49
|
+
* glyph is the colour of the paper, so the mark carries against the running
|
|
50
|
+
* face rather than reading as a character of it.
|
|
51
|
+
*
|
|
52
|
+
* **The mark is a column, not a word.** A two-column grid puts it beside the
|
|
53
|
+
* sentence rather than inside it, so a notice that takes two lines sets its
|
|
54
|
+
* second line against the first and never under the disc; the grid centres the
|
|
55
|
+
* mark's cell on the block, so it holds the middle of one line or two. The rule
|
|
56
|
+
* is the block's own left stroke and spans whatever height the sentence takes.
|
|
57
|
+
*
|
|
58
|
+
* **The sentence sets in the book's body face, size and ink**, because it is
|
|
59
|
+
* the one thing in the entry a reader has to read before trusting the rest. The
|
|
60
|
+
* rule and the mark set the block apart; nothing else has to, and type set
|
|
61
|
+
* smaller or lighter than the prose around it reads as apparatus a reader may
|
|
62
|
+
* skip. The mark scales with that text, so a consumer setting the book larger
|
|
63
|
+
* or smaller carries the disc with it.
|
|
64
|
+
*
|
|
65
|
+
* **One number sets the mark.** The glyph is a multiple of the disc's radius
|
|
66
|
+
* rather than a size of its own, so the exclamation keeps its share of the disc
|
|
67
|
+
* however large the disc is drawn and resizing the mark is one edit. Both are
|
|
68
|
+
* `em`, so the mark still follows the body text.
|
|
47
69
|
*
|
|
48
70
|
* @returns {string} The Typst definition.
|
|
49
71
|
*/
|
package/types/engine/index.d.mts
CHANGED
|
@@ -29,6 +29,8 @@ export * as mapGraphviz from "./map-graphviz.mjs";
|
|
|
29
29
|
export * as mapBuild from "./map-build.mjs";
|
|
30
30
|
export * as siteMaps from "./site-maps.mjs";
|
|
31
31
|
export * as systems from "./systems.mjs";
|
|
32
|
+
export * as calendars from "./calendars.mjs";
|
|
33
|
+
export * as noteDates from "./note-dates.mjs";
|
|
32
34
|
export * as contentSlug from "./content-slug.mjs";
|
|
33
35
|
export * as contentAddress from "./content-address.mjs";
|
|
34
36
|
export * as subtypeRegistry from "./subtype-registry.mjs";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse one authored date.
|
|
3
|
+
*
|
|
4
|
+
* Every field that holds a date — a birth, a death, an event's start and its
|
|
5
|
+
* end — parses through here, so there is one grammar to write, test, document
|
|
6
|
+
* and teach.
|
|
7
|
+
*
|
|
8
|
+
* Findings are returned rather than thrown, because a corpus pass wants every
|
|
9
|
+
* bad value in one run rather than the first. Each carries the `file` it was
|
|
10
|
+
* given and, where `raw` and `keyPath` locate the value, its line and column;
|
|
11
|
+
* a position that cannot be established honestly is dropped rather than
|
|
12
|
+
* defaulted, which is what keeps `file:line:column:` parseable.
|
|
13
|
+
*
|
|
14
|
+
* @param {unknown} value - The authored value. A number is what YAML hands
|
|
15
|
+
* back for a bare year, so it is accepted and stringified.
|
|
16
|
+
* @param {object} [options]
|
|
17
|
+
* @param {{months?: number|null, monthDays?: number|null}} [options.calendar]
|
|
18
|
+
* The resolved `calendar:` block, whose `months` and `monthDays` bound a
|
|
19
|
+
* written month and day. An absent bound is not applied.
|
|
20
|
+
* @param {string} [options.field] - The key that carried it, named in every
|
|
21
|
+
* message. Omitted, a message names the value alone.
|
|
22
|
+
* @param {boolean} [options.allowUnknown=true] - Whether `"unknown"` is a
|
|
23
|
+
* permitted value here. It is not, in a field whose absence already says the
|
|
24
|
+
* date is unrecorded.
|
|
25
|
+
* @param {string} [options.file] - The note, for the diagnostics.
|
|
26
|
+
* @param {string} [options.raw] - The note's full contents, to locate the value.
|
|
27
|
+
* @param {ReadonlyArray<string|number>} [options.keyPath] - Path to the key
|
|
28
|
+
* within the frontmatter, for the same.
|
|
29
|
+
* @returns {{date: object|null, findings: object[]}} The record, or `null` when
|
|
30
|
+
* the value is absent or refused, and every finding the value earned.
|
|
31
|
+
*/
|
|
32
|
+
export function parseNoteDate(value: unknown, options?: {
|
|
33
|
+
calendar?: {
|
|
34
|
+
months?: number | null;
|
|
35
|
+
monthDays?: number | null;
|
|
36
|
+
} | undefined;
|
|
37
|
+
field?: string | undefined;
|
|
38
|
+
allowUnknown?: boolean | undefined;
|
|
39
|
+
file?: string | undefined;
|
|
40
|
+
raw?: string | undefined;
|
|
41
|
+
keyPath?: readonly (string | number)[] | undefined;
|
|
42
|
+
}): {
|
|
43
|
+
date: object | null;
|
|
44
|
+
findings: object[];
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The literal that says a thing happened and the date is not recorded.
|
|
48
|
+
*
|
|
49
|
+
* @type {string}
|
|
50
|
+
*/
|
|
51
|
+
export const UNKNOWN_DATE: string;
|
|
52
|
+
/**
|
|
53
|
+
* What a date's trailing token may look like.
|
|
54
|
+
*
|
|
55
|
+
* An affiliation's address followed by `.<era shortcode>`. The `{0,3}`
|
|
56
|
+
* repetition is the four address forms exactly — bare `shortcode`,
|
|
57
|
+
* `type-shortcode`, `system-type-shortcode` and
|
|
58
|
+
* `package-system-type-shortcode` — so the hyphen separates an address's
|
|
59
|
+
* segments, the dot separates the address from the era, and the split needs no
|
|
60
|
+
* lookahead.
|
|
61
|
+
*
|
|
62
|
+
* @type {RegExp}
|
|
63
|
+
*/
|
|
64
|
+
export const ERA_QUALIFIER_PATTERN: RegExp;
|
|
65
|
+
/**
|
|
66
|
+
* The grammar, as one expression.
|
|
67
|
+
*
|
|
68
|
+
* @type {RegExp}
|
|
69
|
+
*/
|
|
70
|
+
export const NOTE_DATE_PATTERN: RegExp;
|
|
@@ -292,9 +292,16 @@ export function retiredTopLevelMessage(field: {
|
|
|
292
292
|
*
|
|
293
293
|
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
294
294
|
* @param {string} current - The field's current name.
|
|
295
|
+
* @param {object} [options] - Options.
|
|
296
|
+
* @param {boolean} [options.inData=false] - Search the `data:` container
|
|
297
|
+
* instead. A `data:` field has exactly one home, so its retired spelling has
|
|
298
|
+
* exactly one too, and searching the block or the top level for it would
|
|
299
|
+
* answer about a key the container check has already refused.
|
|
295
300
|
* @returns {boolean} Whether the retired spelling is declared.
|
|
296
301
|
*/
|
|
297
|
-
export function declaresRetiredAlias(fm: object | null | undefined, current: string
|
|
302
|
+
export function declaresRetiredAlias(fm: object | null | undefined, current: string, { inData }?: {
|
|
303
|
+
inData?: boolean | undefined;
|
|
304
|
+
}): boolean;
|
|
298
305
|
/**
|
|
299
306
|
* Read a field that has a retired spelling, the current name winning.
|
|
300
307
|
*
|
|
@@ -310,9 +317,16 @@ export function declaresRetiredAlias(fm: object | null | undefined, current: str
|
|
|
310
317
|
*
|
|
311
318
|
* @param {object|null|undefined} fm - Parsed frontmatter.
|
|
312
319
|
* @param {string} current - The field's current name.
|
|
320
|
+
* @param {object} [options] - Options.
|
|
321
|
+
* @param {boolean} [options.inData=false] - Read the `data:` container instead,
|
|
322
|
+
* which is where a `data:` field and its retired spelling are both written.
|
|
323
|
+
* Dotted names resolve inside it, so `appearance.eye_color` reads the way it
|
|
324
|
+
* is authored.
|
|
313
325
|
* @returns {any} The value, or `undefined` when neither spelling carries one.
|
|
314
326
|
*/
|
|
315
|
-
export function readAliasedField(fm: object | null | undefined, current: string
|
|
327
|
+
export function readAliasedField(fm: object | null | undefined, current: string, { inData }?: {
|
|
328
|
+
inData?: boolean | undefined;
|
|
329
|
+
}): any;
|
|
316
330
|
/**
|
|
317
331
|
* The current field name a retired spelling was renamed to.
|
|
318
332
|
*
|
|
@@ -336,6 +350,13 @@ export function readAliasedField(fm: object | null | undefined, current: string)
|
|
|
336
350
|
* read past. Only `affiliation` declares the field, so the alias is reported
|
|
337
351
|
* there and the old spelling stays an ordinary unknown key everywhere else.
|
|
338
352
|
*
|
|
353
|
+
* **`born`.** A birthday is a day that recurs; what a note records is the date
|
|
354
|
+
* somebody was born, once. The two read alike while every value belonged to a
|
|
355
|
+
* living person, and `born` is what the field has to be called beside `died`,
|
|
356
|
+
* which a being now also states. It is the first alias whose field lives in
|
|
357
|
+
* `data:` rather than in a system block, so the two readers below take
|
|
358
|
+
* `inData`.
|
|
359
|
+
*
|
|
339
360
|
* @type {Readonly<Record<string, string>>}
|
|
340
361
|
*/
|
|
341
362
|
export const RETIRED_FIELD_ALIASES: Readonly<Record<string, string>>;
|