@heroiclands/package-build 22.7.0 → 22.9.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 +47 -0
- package/docs/api.md +45 -0
- package/docs/configuration.md +2 -2
- package/docs/content-format.md +152 -13
- package/engine/calendar-notes.mjs +805 -0
- package/engine/calendars.mjs +233 -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 +9 -0
- package/engine/infobox.mjs +7 -2
- package/engine/note-dates.mjs +350 -0
- package/engine/note-vocabulary.mjs +69 -8
- package/engine/pdf-render.mjs +10 -0
- package/engine/retired-fields.mjs +58 -8
- package/package.json +1 -1
- package/types/engine/calendar-notes.d.mts +173 -0
- package/types/engine/calendars.d.mts +206 -0
- package/types/engine/draft-notice.d.mts +26 -4
- package/types/engine/index.d.mts +3 -0
- package/types/engine/note-dates.d.mts +70 -0
- package/types/engine/retired-fields.d.mts +23 -2
|
@@ -0,0 +1,233 @@
|
|
|
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 axis every date normalises onto, and the one function that puts a
|
|
16
|
+
* reckoning on it.**
|
|
17
|
+
*
|
|
18
|
+
* A setting keeps several counts of years at once, each proclaimed by a body
|
|
19
|
+
* that has a note — a founding, a first occasion, a reign. A date either names
|
|
20
|
+
* one of them or names none, in which case it sits on the **canonical axis**.
|
|
21
|
+
*
|
|
22
|
+
* ## The canonical axis is an authoring device, not a calendar
|
|
23
|
+
*
|
|
24
|
+
* The axis is the line of integers dates written by peoples who never met are
|
|
25
|
+
* compared on. Nobody in a setting keeps it, no body stands behind it, and no
|
|
26
|
+
* shortcode addresses it: a date is on the axis exactly when it names no era.
|
|
27
|
+
* A reckoning whose year 1 happens to sit where the axis's year 1 sits
|
|
28
|
+
* coincides with it numerically and is still that people's own count.
|
|
29
|
+
*
|
|
30
|
+
* ## There is no year zero, and one function converts
|
|
31
|
+
*
|
|
32
|
+
* The authored numbering skips zero — `-1` sits immediately before `1` — while
|
|
33
|
+
* the normalised value does not, so that arithmetic across an epoch is one
|
|
34
|
+
* subtraction rather than a special case somebody forgets.
|
|
35
|
+
*
|
|
36
|
+
* ```text
|
|
37
|
+
* authored … -3 -2 -1 1 2 3 …
|
|
38
|
+
* canonicalYear … -2 -1 0 1 2 3 …
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* ```text
|
|
42
|
+
* canonicalYear(y, E) = E + (y > 0 ? y - 1 : y)
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* `E` is where a reckoning's year 1 sits on the axis — the `canonicalYear` of
|
|
46
|
+
* an era's own `start`. The axis is the same function with `E` of
|
|
47
|
+
* {@link CANONICAL_EPOCH}, which holds by definition: the axis's year 1 is the
|
|
48
|
+
* axis's year 1.
|
|
49
|
+
*
|
|
50
|
+
* **The conversion is piecewise on the sign**, and that is the property worth
|
|
51
|
+
* guarding. Collapsed to a single branch it is correct on one side of an epoch
|
|
52
|
+
* and off by one on the other, which is invisible in any list that does not
|
|
53
|
+
* span it.
|
|
54
|
+
*
|
|
55
|
+
* ## The month structure is the calendar's, and an absent list bounds nothing
|
|
56
|
+
*
|
|
57
|
+
* An era moves where year 1 sits; it does not divide the year differently. The
|
|
58
|
+
* division belongs to the calendar, which is a note declaring an **ordered
|
|
59
|
+
* list** of months, each with a name and a day count. Position in the list is
|
|
60
|
+
* position in the year, so a five-day month sits wherever its people put it and
|
|
61
|
+
* needs no rule of its own.
|
|
62
|
+
*
|
|
63
|
+
* A date is bounded by the calendar it is written in, and a calendar whose list
|
|
64
|
+
* nobody has written down bounds nothing — a toolchain that invented twelve
|
|
65
|
+
* Gregorian months would refuse `667/2/30`, an ordinary date in a year of
|
|
66
|
+
* twelve thirty-day months.
|
|
67
|
+
*
|
|
68
|
+
* ## The lunar phase takes a floored modulo, not JavaScript's
|
|
69
|
+
*
|
|
70
|
+
* `%` takes the dividend's sign, so a day before the epoch yields `-7` where
|
|
71
|
+
* the phase is 23. {@link lunarPhase} is the one place that is written.
|
|
72
|
+
*
|
|
73
|
+
* @module
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Where the canonical axis's own year 1 sits — on itself.
|
|
78
|
+
*
|
|
79
|
+
* It takes no configuration entry and no era row, because it is true by
|
|
80
|
+
* definition rather than by declaration.
|
|
81
|
+
*
|
|
82
|
+
* @type {number}
|
|
83
|
+
*/
|
|
84
|
+
export const CANONICAL_EPOCH = 1;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The year on the canonical axis that a written year lands on.
|
|
88
|
+
*
|
|
89
|
+
* The whole of the conversion. A reckoning differs from its neighbours in
|
|
90
|
+
* where its year 1 sits and in nothing else, so an era states one number and
|
|
91
|
+
* this states what to do with it.
|
|
92
|
+
*
|
|
93
|
+
* The two branches are the year-zero hole: the authored numbering has no year
|
|
94
|
+
* 0, so a positive year is one place closer to the epoch than its magnitude
|
|
95
|
+
* suggests and a negative year is exactly its magnitude below it.
|
|
96
|
+
*
|
|
97
|
+
* @param {number} year - The year as authored, signed, within its own
|
|
98
|
+
* reckoning. Never 0 — the authored numbering has no such year.
|
|
99
|
+
* @param {number} [epoch] - Where that reckoning's year 1 sits on the axis.
|
|
100
|
+
* Omitted, the axis itself.
|
|
101
|
+
* @returns {number} The signed year on the axis — authored `-1` is `0`.
|
|
102
|
+
*/
|
|
103
|
+
export function canonicalYear(year, epoch = CANONICAL_EPOCH) {
|
|
104
|
+
return epoch + (year > 0 ? year - 1 : year);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The total-order key a date sorts on.
|
|
109
|
+
*
|
|
110
|
+
* Month and day fold into the fraction, so a mixed-precision list sorts stably
|
|
111
|
+
* against a single number instead of a three-key comparator. The arithmetic is
|
|
112
|
+
* done in ten-thousandths and divided once, because `year + 6 / 100 + 19 /
|
|
113
|
+
* 10000` accumulates a rounding error and `689.0619` is a value a reader
|
|
114
|
+
* compares against by eye.
|
|
115
|
+
*
|
|
116
|
+
* A coarser value sorts **before** the finer values inside it: `689` precedes
|
|
117
|
+
* `689/1`, which is the ordering a reader expects of a year whose month nobody
|
|
118
|
+
* wrote.
|
|
119
|
+
*
|
|
120
|
+
* @param {number} year - The year on the canonical axis.
|
|
121
|
+
* @param {number|null} month - The month, or `null`.
|
|
122
|
+
* @param {number|null} day - The day, or `null`.
|
|
123
|
+
* @returns {number} The sort key.
|
|
124
|
+
*/
|
|
125
|
+
export function dateSortKey(year, month, day) {
|
|
126
|
+
return (year * 10000 + (month ?? 0) * 100 + (day ?? 0)) / 10000;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* One month of a calendar, as the note declares it.
|
|
131
|
+
*
|
|
132
|
+
* @typedef {object} CalendarMonth
|
|
133
|
+
* @property {string} name - What the month is called.
|
|
134
|
+
* @property {string} [abbreviation] - A short form, where the note gives one.
|
|
135
|
+
* @property {number} days - How many days it holds.
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The months a calendar keeps, or `null` where it declares none.
|
|
140
|
+
*
|
|
141
|
+
* Separate from the check that reads it so a caller can ask what a calendar
|
|
142
|
+
* divides its year into without phrasing a finding about it. An empty list
|
|
143
|
+
* reads as no list: a calendar that declares nothing and one that declares
|
|
144
|
+
* nothing in a pair of brackets divide the year identically.
|
|
145
|
+
*
|
|
146
|
+
* @param {{months?: readonly CalendarMonth[]|null}} [calendar] - The calendar
|
|
147
|
+
* the date is written in, as its note declares it.
|
|
148
|
+
* @returns {{months: readonly CalendarMonth[]|null}} What it divides the year
|
|
149
|
+
* into.
|
|
150
|
+
*/
|
|
151
|
+
export function calendarStructure(calendar) {
|
|
152
|
+
const written = calendar?.months;
|
|
153
|
+
if (!Array.isArray(written) || written.length === 0) return { months: null };
|
|
154
|
+
return { months: written };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* How long one month of a calendar is, or `null` where the calendar is silent.
|
|
159
|
+
*
|
|
160
|
+
* @param {{months?: readonly CalendarMonth[]|null}} [calendar] - The calendar.
|
|
161
|
+
* @param {number} month - The month, numbered from 1.
|
|
162
|
+
* @returns {number|null} Its length in days, or `null`.
|
|
163
|
+
*/
|
|
164
|
+
export function daysInMonth(calendar, month) {
|
|
165
|
+
const { months } = calendarStructure(calendar);
|
|
166
|
+
if (!months || month < 1 || month > months.length) return null;
|
|
167
|
+
const days = months[month - 1]?.days;
|
|
168
|
+
return typeof days === "number" ? days : null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* How many days the months add up to.
|
|
173
|
+
*
|
|
174
|
+
* The number a calendar's list claims the year is. Whether that is the year the
|
|
175
|
+
* world keeps is a question about the world, and is asked elsewhere.
|
|
176
|
+
*
|
|
177
|
+
* @param {readonly CalendarMonth[]} months - The ordered month list.
|
|
178
|
+
* @returns {number} The sum of their day counts.
|
|
179
|
+
*/
|
|
180
|
+
export function daysInYear(months) {
|
|
181
|
+
return months.reduce((sum, month) => sum + (Number(month?.days) || 0), 0);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The day of the year each month opens on, in order, numbered from 1.
|
|
186
|
+
*
|
|
187
|
+
* The sum read as a sequence rather than as a total. Two lists holding the same
|
|
188
|
+
* numbers in a different order sum alike and open their months on different
|
|
189
|
+
* days, which is the error a total cannot see.
|
|
190
|
+
*
|
|
191
|
+
* @param {readonly CalendarMonth[]} months - The ordered month list.
|
|
192
|
+
* @returns {number[]} One entry per month: the day of the year it begins on.
|
|
193
|
+
*/
|
|
194
|
+
export function monthStarts(months) {
|
|
195
|
+
const starts = [];
|
|
196
|
+
let day = 1;
|
|
197
|
+
for (const month of months) {
|
|
198
|
+
starts.push(day);
|
|
199
|
+
day += Number(month?.days) || 0;
|
|
200
|
+
}
|
|
201
|
+
return starts;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Which day of the year a written month and day land on, numbered from 1.
|
|
206
|
+
*
|
|
207
|
+
* @param {readonly CalendarMonth[]} months - The ordered month list.
|
|
208
|
+
* @param {number} month - The month, numbered from 1.
|
|
209
|
+
* @param {number} day - The day within it, numbered from 1.
|
|
210
|
+
* @returns {number} The day of the year.
|
|
211
|
+
*/
|
|
212
|
+
export function dayOfYear(months, month, day) {
|
|
213
|
+
return monthStarts(months)[month - 1] + day - 1;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* How far into its cycle a moon is on a given day.
|
|
218
|
+
*
|
|
219
|
+
* **Floored, not truncated.** JavaScript's `%` takes the dividend's sign, so a
|
|
220
|
+
* day seven before the epoch yields `-7` where the phase is 23 — a value that
|
|
221
|
+
* is right on one side of the epoch and wrong on the other, which is invisible
|
|
222
|
+
* in any list that does not span it.
|
|
223
|
+
*
|
|
224
|
+
* @param {number} dayNumber - The day, on whatever continuous day count the
|
|
225
|
+
* caller keeps.
|
|
226
|
+
* @param {number} epochDay - The day of that same count on which the moon was
|
|
227
|
+
* new.
|
|
228
|
+
* @param {number} cycle - The cycle's length in days.
|
|
229
|
+
* @returns {number} Days into the cycle, `0` to `cycle - 1`.
|
|
230
|
+
*/
|
|
231
|
+
export function lunarPhase(dayNumber, epochDay, cycle) {
|
|
232
|
+
return (((dayNumber - epochDay) % cycle) + cycle) % cycle;
|
|
233
|
+
}
|
|
@@ -102,6 +102,7 @@ import { currentType } from "./ids.mjs";
|
|
|
102
102
|
import { SCHEMA_ARTIFACT_VERSION } from "./schema-check.mjs";
|
|
103
103
|
import { authoredFields } from "./field-spec.mjs";
|
|
104
104
|
import { positionInFrontmatter } from "./diagnostics.mjs";
|
|
105
|
+
import { RETIRED_FIELD_ALIASES } from "./retired-fields.mjs";
|
|
105
106
|
|
|
106
107
|
/**
|
|
107
108
|
* Every field path any subtype of a published schema declares.
|
|
@@ -357,8 +358,16 @@ export function measureNote(note, format, { severity = "warning" } = {}) {
|
|
|
357
358
|
|
|
358
359
|
// `data:` is closed: every key it carries must be one the type declares.
|
|
359
360
|
const data = isBlock(fm.data) ? fm.data : undefined;
|
|
361
|
+
// The retired spelling of a key the format states is measured as the key it
|
|
362
|
+
// will be written as, exactly as a note still on a renamed *type* is
|
|
363
|
+
// measured against the section it will move to. The rename itself is the
|
|
364
|
+
// frontmatter lint's finding, and reporting it here as well would count one
|
|
365
|
+
// note twice in a migration whose counts are the point.
|
|
366
|
+
const renamedDataKeys = new Set(
|
|
367
|
+
[...spec.dataKeys].map((key) => RETIRED_FIELD_ALIASES[key]).filter(Boolean),
|
|
368
|
+
);
|
|
360
369
|
for (const key of Object.keys(data ?? {})) {
|
|
361
|
-
if (spec.dataKeys.has(key)) continue;
|
|
370
|
+
if (spec.dataKeys.has(key) || renamedDataKeys.has(key)) continue;
|
|
362
371
|
const guess = nearest(key, spec.dataKeys);
|
|
363
372
|
add(
|
|
364
373
|
"unknown-data-key",
|
package/engine/draft-notice.mjs
CHANGED
|
@@ -33,6 +33,14 @@
|
|
|
33
33
|
* is the floor each surface is built to, because each of them can lose
|
|
34
34
|
* everything around it.
|
|
35
35
|
*
|
|
36
|
+
* **The label is bold roman and the sentence is italic**, on every surface. The
|
|
37
|
+
* words around it are the world describing itself; this one sentence is the
|
|
38
|
+
* text speaking about its own state, and italic is how a reader is told the
|
|
39
|
+
* voice has changed. It is the one typographic mark the sentence carries:
|
|
40
|
+
* smaller or lighter type would say the reader may skip it, which is the
|
|
41
|
+
* opposite of what it is for, and a bold italic beside the mark would be one
|
|
42
|
+
* emphasis too many.
|
|
43
|
+
*
|
|
36
44
|
* ## What each surface can rely on
|
|
37
45
|
*
|
|
38
46
|
* - **Foundry** renders a JournalEntry's own HTML inside a sheet, so the floor
|
|
@@ -87,7 +95,8 @@ export const BOOK_DRAFT_NOTICE = "book-draft-notice";
|
|
|
87
95
|
* `body.game .app blockquote` carries a left rule and an indent — so the
|
|
88
96
|
* treatment survives with no stylesheet of this package's own. The mark is a
|
|
89
97
|
* Font Awesome element, which Foundry bundles; `aria-hidden` keeps it out of a
|
|
90
|
-
* screen reader, where the label reads it.
|
|
98
|
+
* screen reader, where the label reads it. The sentence is an `em`, which every
|
|
99
|
+
* renderer of HTML sets in italic with no stylesheet to ask.
|
|
91
100
|
*
|
|
92
101
|
* @returns {string} The notice's HTML.
|
|
93
102
|
*/
|
|
@@ -95,7 +104,7 @@ export function draftNoticeHtml() {
|
|
|
95
104
|
return (
|
|
96
105
|
`<blockquote class="${DRAFT_NOTICE_CLASS}">` +
|
|
97
106
|
`<p><i class="fa-solid ${DRAFT_NOTICE_ICON}" aria-hidden="true"></i> ` +
|
|
98
|
-
`<strong>${DRAFT_NOTICE_LABEL}</strong>
|
|
107
|
+
`<strong>${DRAFT_NOTICE_LABEL}</strong> <em>${DRAFT_NOTICE}</em></p>` +
|
|
99
108
|
`</blockquote>`
|
|
100
109
|
);
|
|
101
110
|
}
|
|
@@ -139,9 +148,30 @@ export function withDraftNotice(fm, html) {
|
|
|
139
148
|
/**
|
|
140
149
|
* The `#let` the book's notice is drawn by.
|
|
141
150
|
*
|
|
142
|
-
* Emitted with the rest of the book's preamble. The mark is a
|
|
143
|
-
* exclamation
|
|
144
|
-
* page, so there is no font to resolve and nothing that can come out as a
|
|
151
|
+
* Emitted with the rest of the book's preamble. The mark is a disc with an
|
|
152
|
+
* exclamation knocked out of it: two primitives and the face already setting
|
|
153
|
+
* the page, so there is no font to resolve and nothing that can come out as a
|
|
154
|
+
* box. It is filled in the accent the book rules its headings with and the
|
|
155
|
+
* glyph is the colour of the paper, so the mark carries against the running
|
|
156
|
+
* face rather than reading as a character of it.
|
|
157
|
+
*
|
|
158
|
+
* **The mark is a column, not a word.** A two-column grid puts it beside the
|
|
159
|
+
* sentence rather than inside it, so a notice that takes two lines sets its
|
|
160
|
+
* second line against the first and never under the disc; the grid centres the
|
|
161
|
+
* mark's cell on the block, so it holds the middle of one line or two. The rule
|
|
162
|
+
* is the block's own left stroke and spans whatever height the sentence takes.
|
|
163
|
+
*
|
|
164
|
+
* **The sentence sets in the book's body face, size and ink**, because it is
|
|
165
|
+
* the one thing in the entry a reader has to read before trusting the rest. The
|
|
166
|
+
* rule and the mark set the block apart; nothing else has to, and type set
|
|
167
|
+
* smaller or lighter than the prose around it reads as apparatus a reader may
|
|
168
|
+
* skip. The mark scales with that text, so a consumer setting the book larger
|
|
169
|
+
* or smaller carries the disc with it.
|
|
170
|
+
*
|
|
171
|
+
* **One number sets the mark.** The glyph is a multiple of the disc's radius
|
|
172
|
+
* rather than a size of its own, so the exclamation keeps its share of the disc
|
|
173
|
+
* however large the disc is drawn and resizing the mark is one edit. Both are
|
|
174
|
+
* `em`, so the mark still follows the body text.
|
|
145
175
|
*
|
|
146
176
|
* @returns {string} The Typst definition.
|
|
147
177
|
*/
|
|
@@ -152,14 +182,16 @@ export function bookDraftNoticePreamble() {
|
|
|
152
182
|
" inset: (left: 0.7em, top: 0.35em, bottom: 0.35em),\n" +
|
|
153
183
|
" stroke: (left: 1.6pt + book-faint))[\n" +
|
|
154
184
|
" #set par(justify: false, first-line-indent: 0em)\n" +
|
|
155
|
-
" #
|
|
156
|
-
" #
|
|
157
|
-
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
'
|
|
161
|
-
"
|
|
162
|
-
"
|
|
185
|
+
" #let radius = 0.69em\n" +
|
|
186
|
+
" #grid(columns: (auto, 1fr), column-gutter: 0.5em,\n" +
|
|
187
|
+
" align: (center + horizon, horizon),\n" +
|
|
188
|
+
" circle(radius: radius, fill: book-accent)[\n" +
|
|
189
|
+
" #align(center + horizon)[\n" +
|
|
190
|
+
' #text(size: 1.6 * radius, weight: "bold", fill: book-paper)[!]\n' +
|
|
191
|
+
" ]\n" +
|
|
192
|
+
" ],\n" +
|
|
193
|
+
' [#text(weight: "bold")[#label]#h(0.25em)#emph[#body]],\n' +
|
|
194
|
+
" )\n" +
|
|
163
195
|
"]"
|
|
164
196
|
);
|
|
165
197
|
}
|
|
@@ -455,8 +455,34 @@ function checkDataContainer(note, { type, fields, packs }) {
|
|
|
455
455
|
/** First segment of each declared name — `charges.value` is authored as `charges`. */
|
|
456
456
|
const declared = new Set(fields.map((f) => f.name.split(".")[0]));
|
|
457
457
|
|
|
458
|
+
// The retired spelling of a key this type declares → what to write now.
|
|
459
|
+
// Built from the type's own declaration, so a renamed `data:` key is
|
|
460
|
+
// retired exactly where its replacement exists and stays an unrecognised
|
|
461
|
+
// key on every other type.
|
|
462
|
+
const renamed = new Map();
|
|
463
|
+
for (const name of declared) {
|
|
464
|
+
const retired = RETIRED_FIELD_ALIASES[name];
|
|
465
|
+
if (retired) renamed.set(retired, name);
|
|
466
|
+
}
|
|
467
|
+
|
|
458
468
|
for (const key of Object.keys(entries)) {
|
|
459
469
|
if (declared.has(key)) continue;
|
|
470
|
+
const current = renamed.get(key);
|
|
471
|
+
if (current) {
|
|
472
|
+
findings.push({
|
|
473
|
+
file: note.file,
|
|
474
|
+
...positionOfFrontmatterPath(raw, ["data", key], { key: true }),
|
|
475
|
+
// A warning, as every retired spelling is: the note compiles to
|
|
476
|
+
// the correct document, so failing a build over it would red a
|
|
477
|
+
// tree that has done nothing wrong yet. The refusal comes once
|
|
478
|
+
// no tree writes the key, and needs no code — with the alias
|
|
479
|
+
// gone the spelling is an unrecognised key again, which the
|
|
480
|
+
// loop below already refuses.
|
|
481
|
+
severity: "warning",
|
|
482
|
+
message: retiredAliasMessage(key, current),
|
|
483
|
+
});
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
460
486
|
const guess = nearest(key, declared);
|
|
461
487
|
findings.push({
|
|
462
488
|
file: note.file,
|
package/engine/index.mjs
CHANGED
|
@@ -124,6 +124,15 @@ export * as siteMaps from "./site-maps.mjs";
|
|
|
124
124
|
/** The closed registry of system ids, and the `none` that stands for no system. */
|
|
125
125
|
export * as systems from "./systems.mjs";
|
|
126
126
|
|
|
127
|
+
/** The reckonings a package dates by, and the arithmetic that puts them on one line. */
|
|
128
|
+
export * as calendars from "./calendars.mjs";
|
|
129
|
+
|
|
130
|
+
/** A note's dates: one authored string, one grammar, one record shape. */
|
|
131
|
+
export * as noteDates from "./note-dates.mjs";
|
|
132
|
+
|
|
133
|
+
/** Calendars as notes: what one may declare, and the definition emitted from it. */
|
|
134
|
+
export * as calendarNotes from "./calendar-notes.mjs";
|
|
135
|
+
|
|
127
136
|
/** The one normalisation this build makes: prose to a URL-safe token. */
|
|
128
137
|
export * as contentSlug from "./content-slug.mjs";
|
|
129
138
|
|
package/engine/infobox.mjs
CHANGED
|
@@ -115,6 +115,7 @@
|
|
|
115
115
|
import { getFrontmatter } from "./frontmatter.mjs";
|
|
116
116
|
import { currentType } from "./ids.mjs";
|
|
117
117
|
import { NOTE_VOCABULARY, dataFields } from "./note-vocabulary.mjs";
|
|
118
|
+
import { readAliasedField } from "./retired-fields.mjs";
|
|
118
119
|
import { subtypeRow } from "./document-subtypes.mjs";
|
|
119
120
|
|
|
120
121
|
/**
|
|
@@ -272,7 +273,6 @@ export const NOTE_FIELD_PRESENTATION = Object.freeze({
|
|
|
272
273
|
banner: Object.freeze({ withheld: "an image, which the box never carries" }),
|
|
273
274
|
overlay: Object.freeze({ withheld: "an image, which the box never carries" }),
|
|
274
275
|
|
|
275
|
-
birthday: Object.freeze({ label: "Born" }),
|
|
276
276
|
assocSkill: Object.freeze({ label: "Skill" }),
|
|
277
277
|
assocAffiliation: Object.freeze({ label: "Affiliation" }),
|
|
278
278
|
parentSkill: Object.freeze({ label: "Specialises" }),
|
|
@@ -637,7 +637,12 @@ function noteBox(
|
|
|
637
637
|
for (const field of dataFields(fm?.type, vocabulary) ?? []) {
|
|
638
638
|
const overlay = overlayFor(presentation, fm?.type, field.name);
|
|
639
639
|
if (overlay.withheld) continue;
|
|
640
|
-
|
|
640
|
+
// The retired spelling underneath, so a note part-way through a rename
|
|
641
|
+
// still shows its value rather than losing the row with nothing said.
|
|
642
|
+
// The current name wins, which is the whole of the retirement window's
|
|
643
|
+
// behaviour and is `readAliasedField`'s answer, not a second one.
|
|
644
|
+
const own = getFrontmatter(data, field.name, undefined);
|
|
645
|
+
const raw = hasValue(own) ? own : readAliasedField(fm, field.name, { inData: true });
|
|
641
646
|
if (!hasValue(raw)) continue;
|
|
642
647
|
|
|
643
648
|
if (overlay.group) {
|