@heroiclands/package-build 23.0.0 → 23.1.1
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 +66 -0
- package/README.md +1 -1
- package/bin/content-commands.mjs +1 -1
- package/ci/content-format-reference.mjs +46 -0
- package/docs/api.md +124 -141
- package/docs/authoring/assets.md +1 -1
- package/docs/authoring/dates-and-calendars.md +12 -6
- package/docs/authoring/frontmatter.md +2 -2
- package/docs/authoring/links-and-markup.md +145 -29
- package/docs/commands.md +8 -10
- package/docs/configuration.md +13 -11
- package/docs/content-format.md +2 -2
- package/docs/guides/book.md +59 -4
- package/docs/guides/icons.md +4 -5
- package/docs/guides/packs.md +6 -0
- package/docs/guides/reference-pages.md +21 -1
- package/docs/guides/release-and-deploy.md +39 -7
- package/docs/guides/site.md +17 -18
- package/docs/reference/format-details.md +877 -276
- package/docs/reference/note-types.md +278 -91
- package/docs/reference/theme-pages.md +58 -97
- package/engine/address-values.mjs +124 -4
- package/engine/address.mjs +44 -3
- package/engine/anchors.mjs +103 -3
- package/engine/art-slots.mjs +2 -9
- package/engine/base-compiler.mjs +25 -6
- package/engine/calendar-artifacts.mjs +4 -1
- package/engine/calendar-notes.mjs +166 -102
- package/engine/content-format-check.mjs +2 -3
- package/engine/content-format.mjs +44 -1
- package/engine/content-format.yaml +79 -16
- package/engine/content-images.mjs +10 -1
- package/engine/content-index.mjs +37 -9
- package/engine/content-links.mjs +24 -5
- package/engine/content-tables.mjs +24 -3
- package/engine/data-keys.mjs +347 -0
- package/engine/derived-sections.mjs +322 -0
- package/engine/diagnostics.mjs +28 -0
- package/engine/event-fields.mjs +187 -0
- package/engine/event-rows.mjs +126 -0
- package/engine/event-views.mjs +305 -0
- package/engine/exported-scene.mjs +29 -53
- package/engine/frontmatter-lint.mjs +103 -77
- package/engine/frontmatter.mjs +7 -2
- package/engine/generate.mjs +53 -0
- package/engine/generated-sections.mjs +223 -0
- package/engine/heading-attributes.mjs +4 -2
- package/engine/helpers.mjs +12 -1
- package/engine/holdings.mjs +12 -33
- package/engine/ids.mjs +17 -11
- package/engine/index.mjs +4 -4
- package/engine/infobox-render.mjs +21 -6
- package/engine/infobox.mjs +217 -99
- package/engine/journals.mjs +104 -47
- package/engine/literature-notes.mjs +4 -1
- package/engine/literature-works.mjs +21 -13
- package/engine/map-graphviz.mjs +4 -18
- package/engine/markdown-expressions.mjs +116 -11
- package/engine/metadata-index.mjs +26 -0
- package/engine/near-miss.mjs +90 -0
- package/engine/note-addresses.mjs +75 -19
- package/engine/note-dates.mjs +5 -3
- package/engine/note-event-terms.mjs +364 -0
- package/engine/note-events.mjs +823 -0
- package/engine/note-vocabulary.mjs +184 -88
- package/engine/office-holders.mjs +20 -54
- package/engine/packagebuild-index.mjs +16 -1
- package/engine/pdf-build.mjs +97 -60
- package/engine/pdf-index.mjs +263 -0
- package/engine/pdf-render.mjs +47 -5
- package/engine/place-relation-terms.mjs +174 -0
- package/engine/place-relations.mjs +72 -160
- package/engine/rank-ladder.mjs +14 -41
- package/engine/reckoning-markers.mjs +2 -0
- package/engine/region-events.mjs +6 -12
- package/engine/scenes.mjs +73 -468
- package/engine/site-build.mjs +75 -101
- package/engine/site-index.mjs +3 -1
- package/engine/site-maps.mjs +43 -29
- package/engine/site-preview.mjs +40 -31
- package/engine/social-ties.mjs +2 -1
- package/engine/sql-tables.mjs +212 -9
- package/engine/standing-terms.mjs +145 -6
- package/engine/standings.mjs +37 -49
- package/engine/web-wikilinks.mjs +21 -1
- package/engine/wikilink-syntax.mjs +34 -4
- package/engine/wikilinks.mjs +27 -2
- package/engine/yaml-lint.mjs +7 -4
- package/hugo-theme/layouts/partials/infobox/entry.html +4 -1
- package/hugo-theme/layouts/partials/page-body.html +0 -3
- package/hugo-theme/static/css/style.css +25 -55
- package/package.json +1 -2
- package/packagebuild-metadata.jsonl +13 -13
- package/release.mjs +62 -4
- package/sohl/note-schemas.mjs +8 -73
- package/types/engine/address-values.d.mts +50 -3
- package/types/engine/address.d.mts +21 -0
- package/types/engine/anchors.d.mts +49 -2
- package/types/engine/art-slots.d.mts +1 -1
- package/types/engine/base-compiler.d.mts +13 -4
- package/types/engine/calendar-notes.d.mts +7 -0
- package/types/engine/content-format.d.mts +52 -0
- package/types/engine/content-images.d.mts +5 -1
- package/types/engine/content-tables.d.mts +9 -1
- package/types/engine/data-keys.d.mts +145 -0
- package/types/engine/derived-sections.d.mts +73 -0
- package/types/engine/diagnostics.d.mts +15 -0
- package/types/engine/event-fields.d.mts +99 -0
- package/types/engine/event-rows.d.mts +12 -0
- package/types/engine/event-views.d.mts +67 -0
- package/types/engine/exported-scene.d.mts +5 -2
- package/types/engine/frontmatter-lint.d.mts +3 -3
- package/types/engine/generated-sections.d.mts +143 -0
- package/types/engine/holdings.d.mts +6 -20
- package/types/engine/ids.d.mts +15 -12
- package/types/engine/index.d.mts +1 -1
- package/types/engine/infobox.d.mts +13 -3
- package/types/engine/journals.d.mts +24 -11
- package/types/engine/literature-works.d.mts +1 -2
- package/types/engine/map-graphviz.d.mts +2 -10
- package/types/engine/markdown-expressions.d.mts +12 -3
- package/types/engine/near-miss.d.mts +42 -0
- package/types/engine/note-addresses.d.mts +7 -1
- package/types/engine/note-event-terms.d.mts +68 -0
- package/types/engine/note-events.d.mts +71 -0
- package/types/engine/note-vocabulary.d.mts +37 -8
- package/types/engine/office-holders.d.mts +3 -0
- package/types/engine/pdf-index.d.mts +35 -0
- package/types/engine/pdf-render.d.mts +8 -2
- package/types/engine/place-relation-terms.d.mts +83 -0
- package/types/engine/place-relations.d.mts +11 -66
- package/types/engine/rank-ladder.d.mts +2 -0
- package/types/engine/region-events.d.mts +6 -12
- package/types/engine/scenes.d.mts +6 -32
- package/types/engine/site-build.d.mts +8 -22
- package/types/engine/site-maps.d.mts +15 -7
- package/types/engine/sql-tables.d.mts +77 -2
- package/types/engine/standing-terms.d.mts +37 -4
- package/types/engine/standings.d.mts +14 -9
- package/types/engine/wikilink-syntax.d.mts +20 -0
- package/types/engine/wikilinks.d.mts +3 -2
- package/types/engine/yaml-lint.d.mts +5 -4
- package/types/release.d.mts +4 -0
- package/docs/guides/government-migration.md +0 -52
- package/engine/itinerary-scenes.mjs +0 -166
- package/engine/lore-events.mjs +0 -197
- package/engine/map-notes.mjs +0 -1309
- package/engine/map-raster.mjs +0 -51
- package/hugo-theme/layouts/partials/holdings/entry.html +0 -21
- package/hugo-theme/layouts/partials/holdings/kind-groups.html +0 -71
- package/hugo-theme/layouts/partials/holdings.html +0 -61
- package/hugo-theme/layouts/partials/literature-works.html +0 -29
- package/hugo-theme/layouts/partials/place-map.html +0 -9
- package/types/engine/itinerary-scenes.d.mts +0 -29
- package/types/engine/lore-events.d.mts +0 -14
- package/types/engine/map-notes.d.mts +0 -297
- package/types/engine/map-raster.d.mts +0 -8
package/engine/infobox.mjs
CHANGED
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
* a side rail on wide screens and inline boxes on narrow screens.
|
|
57
57
|
* 2. **It contains no image.** A picture is authored in the text with its own
|
|
58
58
|
* directive, and its position governs. {@link NOTE_FIELD_PRESENTATION}
|
|
59
|
-
* withholds the art slots
|
|
59
|
+
* withholds the art slots for that reason and no other.
|
|
60
60
|
* 3. **A section is the unit that flows.** Sections are whole and unbreakable;
|
|
61
61
|
* the panel breaks between them. This is what lets a long box cross a
|
|
62
62
|
* column or page boundary without splitting a stat grid.
|
|
@@ -119,20 +119,16 @@ import { NOTE_VOCABULARY, dataFields } from "./note-vocabulary.mjs";
|
|
|
119
119
|
import { readAliasedField } from "./retired-fields.mjs";
|
|
120
120
|
import { subtypeRow } from "./document-subtypes.mjs";
|
|
121
121
|
import { authoredKey } from "./system-block.mjs";
|
|
122
|
-
import {
|
|
123
|
-
formatDateInCalendar,
|
|
124
|
-
formatNoteDate,
|
|
125
|
-
parseNoteDate,
|
|
126
|
-
resolvedDateFields,
|
|
127
|
-
} from "./note-dates.mjs";
|
|
122
|
+
import { formatDateInCalendar, formatNoteDate, parseNoteDate } from "./note-dates.mjs";
|
|
128
123
|
import { displayBeingHeight, displayBeingWeight } from "./being-measurements.mjs";
|
|
129
124
|
import {
|
|
130
125
|
officeAnchor,
|
|
131
126
|
officeRoster,
|
|
132
127
|
rankAnchor,
|
|
133
128
|
readStandings,
|
|
134
|
-
|
|
129
|
+
standingLead,
|
|
135
130
|
} from "./standings.mjs";
|
|
131
|
+
import { AddressEntries } from "./address-values.mjs";
|
|
136
132
|
|
|
137
133
|
/**
|
|
138
134
|
* How a section arranges what it holds.
|
|
@@ -260,10 +256,11 @@ export const GEAR_UNITS = Object.freeze({
|
|
|
260
256
|
* Three properties, all optional:
|
|
261
257
|
*
|
|
262
258
|
* - `label` — what the row is called. Absent means the key, humanised.
|
|
263
|
-
* - `withheld` — why the field carries no row.
|
|
259
|
+
* - `withheld` — why the field carries no row. Three reasons only: it is
|
|
264
260
|
* machinery — something that steers a build or an interface rather than
|
|
265
|
-
* describing the subject
|
|
266
|
-
* box
|
|
261
|
+
* describing the subject, which includes a calendar's definition and a map's
|
|
262
|
+
* canvas — it is an image, which rule 2 keeps out of the box, or it is a
|
|
263
|
+
* source system's own detail, which no surface's box shows.
|
|
267
264
|
* - `unit` — what the quantity is measured in, appended to the value verbatim.
|
|
268
265
|
* See {@link applyUnit}.
|
|
269
266
|
* - `group` / `phrase` — the field composes into one row with its group mates
|
|
@@ -287,15 +284,39 @@ export const NOTE_FIELD_PRESENTATION = Object.freeze({
|
|
|
287
284
|
templatePriority: Object.freeze({
|
|
288
285
|
withheld: "template machinery, not a fact about the subject",
|
|
289
286
|
}),
|
|
287
|
+
id: Object.freeze({ withheld: "document identity machinery, not a fact about the subject" }),
|
|
288
|
+
pack: Object.freeze({ withheld: "compendium routing machinery, not a fact about the subject" }),
|
|
289
|
+
packFolder: Object.freeze({
|
|
290
|
+
withheld: "compendium routing machinery, not a fact about the subject",
|
|
291
|
+
}),
|
|
292
|
+
harnworld: Object.freeze({
|
|
293
|
+
withheld: "HârnWorld source detail, which no surface's box shows",
|
|
294
|
+
}),
|
|
290
295
|
color: Object.freeze({
|
|
291
296
|
withheld: "sidebar machinery, not a fact about the subject",
|
|
292
297
|
}),
|
|
293
298
|
icon: Object.freeze({ withheld: "an image, which the box never carries" }),
|
|
294
299
|
tokenIcon: Object.freeze({ withheld: "an image, which the box never carries" }),
|
|
295
|
-
bgImage: Object.freeze({ withheld: "an image, which the box never carries" }),
|
|
296
300
|
banner: Object.freeze({ withheld: "an image, which the box never carries" }),
|
|
297
|
-
|
|
298
|
-
|
|
301
|
+
events: Object.freeze({ withheld: "chronology machinery, which no infobox row draws on" }),
|
|
302
|
+
made: Object.freeze({
|
|
303
|
+
withheld: "chronology machinery — an event, which no infobox row draws on",
|
|
304
|
+
}),
|
|
305
|
+
lost: Object.freeze({
|
|
306
|
+
withheld: "chronology machinery — an event, which no infobox row draws on",
|
|
307
|
+
}),
|
|
308
|
+
// A calendar's definition is what dates are read through, not a fact a
|
|
309
|
+
// summary row can carry: a list of months or eras is the calendar itself.
|
|
310
|
+
"lore.months": Object.freeze({ withheld: "calendar machinery, not a summary row" }),
|
|
311
|
+
"lore.weekdays": Object.freeze({ withheld: "calendar machinery, not a summary row" }),
|
|
312
|
+
"lore.seasons": Object.freeze({ withheld: "calendar machinery, not a summary row" }),
|
|
313
|
+
"lore.namedDays": Object.freeze({ withheld: "calendar machinery, not a summary row" }),
|
|
314
|
+
"lore.eras": Object.freeze({ withheld: "calendar machinery, not a summary row" }),
|
|
315
|
+
"lore.formats": Object.freeze({ withheld: "calendar machinery, not a summary row" }),
|
|
316
|
+
// A map's canvas — the exported Scene and its asset fixups — is the
|
|
317
|
+
// Scene itself, not a fact a summary row can carry.
|
|
318
|
+
"map.scene": Object.freeze({ withheld: "canvas machinery, not a summary row" }),
|
|
319
|
+
"map.fixup": Object.freeze({ withheld: "canvas machinery, not a summary row" }),
|
|
299
320
|
|
|
300
321
|
assocSkill: Object.freeze({ label: "Skill" }),
|
|
301
322
|
assocAffiliation: Object.freeze({ label: "Affiliation" }),
|
|
@@ -569,66 +590,63 @@ export function valueKindOf(field, value) {
|
|
|
569
590
|
* @param {(ref: unknown, hint?: object) => object|undefined} resolve - The
|
|
570
591
|
* medium's resolver.
|
|
571
592
|
* @param {object} [hint] - What the reference is expected to name.
|
|
593
|
+
* @param {boolean} [verbatim] - The value is a symbol, shown exactly as written.
|
|
572
594
|
* @returns {unknown} The row value, shaped for the kind.
|
|
573
595
|
*/
|
|
574
|
-
function rowValue(kind, raw, resolve, hint) {
|
|
596
|
+
function rowValue(kind, raw, resolve, hint, verbatim = false) {
|
|
575
597
|
if (kind === "link") return linkValue(raw, resolve, hint);
|
|
576
598
|
if (kind === "links") {
|
|
577
599
|
return (Array.isArray(raw) ? raw : [raw])
|
|
578
600
|
.filter(hasValue)
|
|
579
601
|
.map((r) => linkValue(r, resolve, hint));
|
|
580
602
|
}
|
|
603
|
+
// A symbol such as a unit is shown as written, never recased.
|
|
604
|
+
const shown = (value) => (verbatim ? String(value) : presentValue(value));
|
|
581
605
|
if (kind === "list") {
|
|
582
|
-
return (Array.isArray(raw) ? raw : [raw]).filter(hasValue).map((v) =>
|
|
606
|
+
return (Array.isArray(raw) ? raw : [raw]).filter(hasValue).map((v) => shown(v));
|
|
583
607
|
}
|
|
584
608
|
if (kind === "number") return raw;
|
|
585
|
-
return
|
|
609
|
+
return shown(raw);
|
|
586
610
|
}
|
|
587
611
|
|
|
588
612
|
/**
|
|
589
|
-
*
|
|
613
|
+
* The section id and title a being's memberships are drawn under.
|
|
614
|
+
*
|
|
615
|
+
* @type {Readonly<{id: string, label: string}>}
|
|
616
|
+
*/
|
|
617
|
+
export const MEMBERSHIPS_SECTION = Object.freeze({ id: "affiliations", label: "Affiliations" });
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* A being's memberships, one entry each, in the order the note writes them.
|
|
590
621
|
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
622
|
+
* Each entry is the line a reader meets — `War Chief, Hárár (5), of Vrystwald
|
|
623
|
+
* Tribes` — with the body's name as the link and the standing before it as the
|
|
624
|
+
* entry's `lead`, drawn as plain text. The rung's title is read from the body's
|
|
625
|
+
* own ladder, carried on the reference as its `standings` digest, so a renamed
|
|
626
|
+
* rung renames every line pointing at it. A body that publishes no page keeps
|
|
627
|
+
* its name as plain text, and a membership in the list form is the name alone.
|
|
594
628
|
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
* being wrote reaches the description without anyone authoring a note about the
|
|
599
|
-
* post. In a medium with no page to reach — a compendium journal links by
|
|
600
|
-
* document UUID — the entry keeps the body's own document, so the reader still
|
|
601
|
-
* lands somewhere.
|
|
629
|
+
* The entries are a `list` section of their own rather than one `links` row,
|
|
630
|
+
* because a line already holds commas and a `links` row joins its values with
|
|
631
|
+
* them: one line per membership is what keeps two memberships apart.
|
|
602
632
|
*
|
|
603
633
|
* @param {object} field - The declaration.
|
|
604
634
|
* @param {unknown} raw - The authored value, in either form.
|
|
605
635
|
* @param {(ref: unknown, hint?: object) => object|undefined} resolve - The
|
|
606
636
|
* medium's resolver.
|
|
607
|
-
* @
|
|
608
|
-
* @returns {object[]} The row, or none where nothing is held.
|
|
637
|
+
* @returns {object[]} One entry per membership.
|
|
609
638
|
*/
|
|
610
|
-
function
|
|
639
|
+
function membershipEntries(field, raw, resolve) {
|
|
611
640
|
const { entries } = readStandings(raw);
|
|
612
|
-
const
|
|
641
|
+
const out = [];
|
|
613
642
|
for (const { body, standing } of entries) {
|
|
614
643
|
if (!hasValue(body)) continue;
|
|
615
644
|
const link = linkValue(body, resolve, { type: field.ref });
|
|
616
645
|
const digest = resolve?.(body, { type: field.ref })?.standings;
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
(
|
|
620
|
-
typeof standing?.office === "string" &&
|
|
621
|
-
digest?.offices?.[standing.office] !== undefined
|
|
622
|
-
) ?
|
|
623
|
-
officeAnchor(standing.office)
|
|
624
|
-
: "";
|
|
625
|
-
value.push({
|
|
626
|
-
...link,
|
|
627
|
-
text,
|
|
628
|
-
...(anchor && link.url ? { url: `${link.url}#${anchor}` } : {}),
|
|
629
|
-
});
|
|
646
|
+
const lead = standingLead(isMapping(standing) ? standing : {}, digest);
|
|
647
|
+
out.push(lead ? { lead, ...link } : link);
|
|
630
648
|
}
|
|
631
|
-
return
|
|
649
|
+
return out;
|
|
632
650
|
}
|
|
633
651
|
|
|
634
652
|
/**
|
|
@@ -710,7 +728,8 @@ function rankRows(raw, resolve) {
|
|
|
710
728
|
* short Address with none handed to it skips rather than guesses.
|
|
711
729
|
*/
|
|
712
730
|
function structuredRows(field, raw, resolve, label, contentPackage) {
|
|
713
|
-
|
|
731
|
+
// Drawn as a section of their own; see `membershipEntries`.
|
|
732
|
+
if (field.standings) return [];
|
|
714
733
|
if (field.roster) {
|
|
715
734
|
const roster = rosterRows(raw);
|
|
716
735
|
if (roster) return roster;
|
|
@@ -741,43 +760,145 @@ function structuredRows(field, raw, resolve, label, contentPackage) {
|
|
|
741
760
|
});
|
|
742
761
|
return links.length ? [{ label, kind: "links", value: links }] : [];
|
|
743
762
|
}
|
|
744
|
-
if (isMapping(raw)
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
763
|
+
if (!isMapping(raw) || isAddressTuple(raw)) return null;
|
|
764
|
+
// A map keyed by Address relates this note to others, and its value is
|
|
765
|
+
// the term the relation goes by: the term labels the row and the targets
|
|
766
|
+
// are its links.
|
|
767
|
+
if (field.keyKind === "address") return relationRows(field, raw, resolve, contentPackage);
|
|
768
|
+
// A map keyed by Shortcode weighs each named thing: one row, each target
|
|
769
|
+
// linked and its weight beside it.
|
|
770
|
+
if (field.keyKind === "shortcode") return weightedRows(field, raw, resolve, label);
|
|
771
|
+
// A map with declared keys: each key is a row's label and its value the
|
|
772
|
+
// row's value.
|
|
773
|
+
if (field.fields) return declaredKeyRows(field.fields, raw);
|
|
774
|
+
// Any other map has no summary shape, and a map is never drawn as text.
|
|
775
|
+
return [];
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* The authored entries of a keyed map, with each key's Address where the note
|
|
780
|
+
* boundary has typed it.
|
|
781
|
+
*
|
|
782
|
+
* @param {unknown} raw - The authored map.
|
|
783
|
+
* @returns {Array<[unknown, unknown]>} `[key, value]` pairs, in authored order.
|
|
784
|
+
*/
|
|
785
|
+
function keyedEntries(raw) {
|
|
786
|
+
if (raw instanceof AddressEntries)
|
|
787
|
+
return raw.entries.map((entry) => [entry.target, entry.value]);
|
|
788
|
+
return Object.entries(raw);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* A map keyed by Address, one row per relation term, each target linked.
|
|
793
|
+
*
|
|
794
|
+
* @param {object} field - The declaration.
|
|
795
|
+
* @param {unknown} raw - The authored map.
|
|
796
|
+
* @param {(ref: unknown, hint?: object) => object|undefined} resolve - The
|
|
797
|
+
* medium's resolver.
|
|
798
|
+
* @param {string} [contentPackage] - This build's content package.
|
|
799
|
+
* @returns {object[]} The rows, in the declared term order where one is
|
|
800
|
+
* declared and in authored order otherwise.
|
|
801
|
+
*/
|
|
802
|
+
function relationRows(field, raw, resolve, contentPackage) {
|
|
803
|
+
const groups = new Map();
|
|
804
|
+
const seen = new Set();
|
|
805
|
+
for (const [target, relation] of keyedEntries(raw)) {
|
|
806
|
+
if (!hasValue(target) || !hasValue(relation) || isMapping(relation)) continue;
|
|
807
|
+
const term = String(relation);
|
|
808
|
+
if (field.terms && !field.terms.some((entry) => entry.term === term)) continue;
|
|
809
|
+
if (field.terms) {
|
|
810
|
+
const address =
|
|
811
|
+
isAddressTuple(target) ? target : (
|
|
812
|
+
parseAddress(
|
|
813
|
+
target,
|
|
814
|
+
{ package: contentPackage, system: "note", types: new Set(field.accepts) },
|
|
815
|
+
{ declared: true },
|
|
816
|
+
)
|
|
760
817
|
);
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
818
|
+
if (address.reason || (field.accepts && !field.accepts.includes(address.type)))
|
|
819
|
+
continue;
|
|
820
|
+
const canonical = renderAddress(address);
|
|
821
|
+
if (seen.has(canonical)) continue;
|
|
822
|
+
seen.add(canonical);
|
|
823
|
+
}
|
|
824
|
+
const links = groups.get(term) ?? [];
|
|
825
|
+
links.push(linkValue(target, resolve, { type: field.ref }));
|
|
826
|
+
groups.set(term, links);
|
|
827
|
+
}
|
|
828
|
+
const terms = field.terms ? field.terms.map(({ term }) => term) : [...groups.keys()];
|
|
829
|
+
return terms
|
|
830
|
+
.filter((term) => groups.has(term))
|
|
831
|
+
.map((term) => ({ label: presentValue(term), kind: "links", value: groups.get(term) }));
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* A map keyed by Shortcode, one row: each key linked, its value beside it.
|
|
836
|
+
*
|
|
837
|
+
* A `subType:<subtype>` key names every skill of that kind rather than one
|
|
838
|
+
* note, so it reads as the kind and links nowhere.
|
|
839
|
+
*
|
|
840
|
+
* @param {object} field - The declaration.
|
|
841
|
+
* @param {unknown} raw - The authored map.
|
|
842
|
+
* @param {(ref: unknown, hint?: object) => object|undefined} resolve - The
|
|
843
|
+
* medium's resolver.
|
|
844
|
+
* @param {string} label - The row's label.
|
|
845
|
+
* @returns {object[]} The row, or none where nothing is stated.
|
|
846
|
+
*/
|
|
847
|
+
function weightedRows(field, raw, resolve, label) {
|
|
848
|
+
const value = [];
|
|
849
|
+
for (const [key, weight] of keyedEntries(raw)) {
|
|
850
|
+
if (!hasValue(key) || !hasValue(weight) || isMapping(weight)) continue;
|
|
851
|
+
const selector =
|
|
852
|
+
field.keySelector === "subType" &&
|
|
853
|
+
typeof key === "string" &&
|
|
854
|
+
key.startsWith("subType:");
|
|
855
|
+
const link =
|
|
856
|
+
selector ?
|
|
857
|
+
{ text: `${humanizeFieldName(key.slice("subType:".length))} skills` }
|
|
858
|
+
: linkValue(key, resolve, { type: field.ref });
|
|
859
|
+
const amount = Number(weight);
|
|
860
|
+
const shown = Number.isFinite(amount) && amount > 0 ? `+${amount}` : presentValue(weight);
|
|
861
|
+
value.push({ ...link, text: `${link.text} (${shown})` });
|
|
862
|
+
}
|
|
863
|
+
return value.length ? [{ label, kind: "links", value }] : [];
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* A map whose keys are declared: one row per key that holds a value, in
|
|
868
|
+
* declared order, labelled by the key and carrying its value.
|
|
869
|
+
*
|
|
870
|
+
* A list of words is one row of them; a key holding a map has no summary
|
|
871
|
+
* shape and takes no row; a key holding nothing takes none either.
|
|
872
|
+
*
|
|
873
|
+
* @param {readonly object[]} fields - The declared keys.
|
|
874
|
+
* @param {Record<string, unknown>} raw - The authored map.
|
|
875
|
+
* @returns {object[]} The rows.
|
|
876
|
+
*/
|
|
877
|
+
function declaredKeyRows(fields, raw) {
|
|
878
|
+
const rows = [];
|
|
879
|
+
for (const inner of fields) {
|
|
880
|
+
const value = raw[inner.name];
|
|
881
|
+
if (isMapping(value) && !isAddressTuple(value)) continue;
|
|
882
|
+
if (!hasValue(value)) continue;
|
|
883
|
+
const label = inner.label ?? humanizeFieldName(inner.name);
|
|
884
|
+
// A symbol such as a unit is shown as written: `km` recased is a
|
|
885
|
+
// different unit, or none.
|
|
886
|
+
const shown = (entry) => (inner.verbatim ? String(entry) : presentValue(entry));
|
|
887
|
+
if (Array.isArray(value)) {
|
|
888
|
+
const words = value
|
|
889
|
+
.filter((entry) => hasValue(entry) && !isMapping(entry))
|
|
890
|
+
.map((entry) => shown(entry));
|
|
891
|
+
if (words.length) rows.push({ label, kind: "list", value: words });
|
|
892
|
+
} else if (
|
|
893
|
+
(inner.kind === "number" || inner.kind === "integer") &&
|
|
894
|
+
Number.isFinite(Number(value))
|
|
895
|
+
) {
|
|
896
|
+
rows.push({ label, kind: "number", value: Number(value) });
|
|
897
|
+
} else {
|
|
898
|
+
rows.push({ label, kind: "text", value: shown(value) });
|
|
770
899
|
}
|
|
771
|
-
const terms = field.terms ? field.terms.map(({ term }) => term) : [...groups.keys()];
|
|
772
|
-
return terms
|
|
773
|
-
.filter((term) => groups.has(term))
|
|
774
|
-
.map((term) => ({
|
|
775
|
-
label: presentValue(term),
|
|
776
|
-
kind: "links",
|
|
777
|
-
value: groups.get(term),
|
|
778
|
-
}));
|
|
779
900
|
}
|
|
780
|
-
return
|
|
901
|
+
return rows;
|
|
781
902
|
}
|
|
782
903
|
|
|
783
904
|
/**
|
|
@@ -895,6 +1016,8 @@ function noteBox(
|
|
|
895
1016
|
const data = isMapping(fm?.data) ? fm.data : {};
|
|
896
1017
|
/** @type {Map<string, {label: string, entries: string[]}>} */
|
|
897
1018
|
const groups = new Map();
|
|
1019
|
+
/** One entry per membership, drawn as a section after the profile. */
|
|
1020
|
+
const memberships = [];
|
|
898
1021
|
|
|
899
1022
|
for (const field of dataFields(fm?.type, vocabulary) ?? []) {
|
|
900
1023
|
const overlay = overlayFor(presentation, fm?.type, field.name);
|
|
@@ -932,6 +1055,12 @@ function noteBox(
|
|
|
932
1055
|
printable ??= formatNoteDate(parsed, era, dates.daysPerYear);
|
|
933
1056
|
if (printable) raw = printable.prose ?? printable.text;
|
|
934
1057
|
}
|
|
1058
|
+
if (field.standings) {
|
|
1059
|
+
const entries = membershipEntries(field, raw, resolve);
|
|
1060
|
+
memberships.push(...entries);
|
|
1061
|
+
if (entries.length) shown.add(field.name);
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
935
1064
|
const structured = structuredRows(
|
|
936
1065
|
field,
|
|
937
1066
|
raw,
|
|
@@ -977,35 +1106,24 @@ function noteBox(
|
|
|
977
1106
|
}
|
|
978
1107
|
|
|
979
1108
|
const declaredKind = valueKindOf(field, raw);
|
|
980
|
-
const built = rowValue(declaredKind, raw, resolve);
|
|
1109
|
+
const built = rowValue(declaredKind, raw, resolve, undefined, field.verbatim);
|
|
981
1110
|
if (!hasRenderableValue(declaredKind, built)) continue;
|
|
982
1111
|
const { kind, value } = applyUnit(declaredKind, built, overlay.unit);
|
|
983
1112
|
rows.push({ label: overlay.label ?? humanizeFieldName(field.name), kind, value });
|
|
984
1113
|
shown.add(field.name);
|
|
985
1114
|
}
|
|
986
1115
|
|
|
987
|
-
// `events` itself stays withheld — the family's shape is chronology
|
|
988
|
-
// machinery, not a summary row — but a reader meets each occurrence's
|
|
989
|
-
// own next date, the way a being's computed `age` reaches the box.
|
|
990
|
-
if (fm?.type === "lore" && Array.isArray(data.events) && data.events.length && dates) {
|
|
991
|
-
const resolved = resolvedDateFields(fm, dates);
|
|
992
|
-
for (const eventEntry of resolved.events ?? []) {
|
|
993
|
-
if (!eventEntry.next) continue;
|
|
994
|
-
rows.push({
|
|
995
|
-
label: "Next occurrence",
|
|
996
|
-
kind: "text",
|
|
997
|
-
value: eventEntry.next.prose ?? eventEntry.next.text,
|
|
998
|
-
});
|
|
999
|
-
shown.add("events");
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
1116
|
return {
|
|
1004
1117
|
box: {
|
|
1005
1118
|
id: NOTE_BOX_ID,
|
|
1006
1119
|
kind: "note",
|
|
1007
1120
|
title: NOTE_BOX_TITLE,
|
|
1008
|
-
sections: [
|
|
1121
|
+
sections: [
|
|
1122
|
+
{ id: NOTE_SECTION_ID, layout: "rows", rows },
|
|
1123
|
+
...(memberships.length ?
|
|
1124
|
+
[{ ...MEMBERSHIPS_SECTION, layout: "list", entries: memberships }]
|
|
1125
|
+
: []),
|
|
1126
|
+
],
|
|
1009
1127
|
},
|
|
1010
1128
|
shown,
|
|
1011
1129
|
};
|