@heroiclands/package-build 23.12.3 → 23.12.5
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/ci/ci-steps.mjs +5 -5
- package/content-config.mjs +19 -0
- package/docs/api.md +1 -0
- package/docs/authoring/frontmatter.md +1 -1
- package/docs/authoring/links-and-markup.md +4 -4
- package/docs/commands.md +5 -4
- package/docs/configuration.md +29 -12
- package/docs/guides/book.md +15 -13
- package/docs/guides/release-and-deploy.md +6 -9
- package/docs/project-setup.md +11 -11
- package/docs/reference/format-details.md +28 -16
- package/docs/reference/note-types.md +1 -1
- package/engine/content-index.mjs +2 -39
- package/engine/event-fields.mjs +24 -1
- package/engine/event-rows.mjs +37 -2
- package/engine/event-views.mjs +58 -14
- package/engine/frontmatter-lint.mjs +11 -0
- package/engine/generated-sections.mjs +8 -1
- package/engine/holdings.mjs +8 -2
- package/engine/infobox-render.mjs +16 -5
- package/engine/infobox.mjs +23 -2
- package/engine/literature-works.mjs +8 -2
- package/engine/metadata-index.mjs +3 -0
- package/engine/name-sort.mjs +91 -0
- package/engine/note-frontmatter.mjs +1 -1
- package/engine/note-vocabulary.mjs +1 -1
- package/engine/page-lists.mjs +3 -2
- package/engine/pdf-build.mjs +1 -0
- package/engine/pdf-index.mjs +33 -12
- package/engine/pdf-pages.mjs +13 -3
- package/engine/pdf-render.mjs +60 -33
- package/engine/pdf-toc.mjs +4 -3
- package/engine/project-init.mjs +2 -2
- package/engine/related-pages.mjs +12 -2
- package/engine/site-build.mjs +3 -0
- package/engine/sql-tables.mjs +20 -5
- package/githooks/pre-push +5 -5
- package/package.json +1 -1
- package/types/content-config.d.mts +5 -0
- package/types/engine/content-index.d.mts +2 -31
- package/types/engine/event-fields.d.mts +11 -0
- package/types/engine/event-rows.d.mts +4 -1
- package/types/engine/event-views.d.mts +7 -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/pdf-index.d.mts +7 -3
- package/types/engine/pdf-pages.d.mts +2 -1
- package/types/engine/pdf-render.d.mts +16 -5
- package/types/engine/pdf-toc.d.mts +1 -1
- package/types/engine/related-pages.d.mts +5 -0
- package/ci/confirm-npm-version.mjs +0 -135
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 23.12.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
In PDF books, the page number and section name at the foot of each page are set at the size of the body text.
|
|
8
|
+
|
|
9
|
+
In PDF books, list items sit apart from one another and carry a bullet large enough to see at book size.
|
|
10
|
+
|
|
11
|
+
In PDF books, a reference to another book that already sits in brackets joins them, reading (The Crossing, Thal. p524) rather than nesting a second pair.
|
|
12
|
+
|
|
13
|
+
Chronology sections, and the other event sections, list each event's date first, written in the setting's calendar. A chronology shows its Felt here column only when some event is felt in the place, and prints across the full page in PDF books. A captioned table can ask to span the page in a PDF book with `.full-width`. A wide table floats to where it fits, and the text runs on past its heading rather than leaving an empty page.
|
|
14
|
+
|
|
15
|
+
In PDF books, a profile box too long for the rest of a page continues on the next one between its rows, instead of running off the page over the footer.
|
|
16
|
+
|
|
17
|
+
A note's name may give a `sort` form, the way an index would write it: `sort: Black Flame, The` for _The Black Flame_, or `sort: Hilvan, Doran`. It files the note in the book index, page lists and other name-ordered lists, and it is the note's entry text in the book index; every other list still shows the full name. Authored `sql` fences can order by the new `nameSort` column of `entries`, which is the `sort` form or, without one, the full name.
|
|
18
|
+
|
|
19
|
+
Profile figures read as numbers do: 12,500 under a million, and 11 million or 1.2 million above it, on the website, in Foundry and in PDF books.
|
|
20
|
+
|
|
21
|
+
## 23.12.4
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
A PDF book may declare a short `abbreviation`, so references into it read `(Aelw. p307)` instead of naming the full title, and the other books' indexes list its notes the same way.
|
|
26
|
+
|
|
27
|
+
In PDF books, a definition list sets its term in bold at the start of the line with the description running on after it, indented on later lines, and no space appears between a linked name and the punctuation that follows it.
|
|
28
|
+
|
|
29
|
+
`project init` scaffolds the build workflow at `.gitea/workflows/build.yml`, and `project check` and `package-build ci` read it from there.
|
|
30
|
+
|
|
3
31
|
## 23.12.3
|
|
4
32
|
|
|
5
33
|
### Patch Changes
|
package/ci/ci-steps.mjs
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* of the command list is a second statement of one thing, and the copy is the
|
|
19
19
|
* one that goes stale — silently, because a pre-push check that runs four of
|
|
20
20
|
* five steps still exits 0. So the steps are parsed out of
|
|
21
|
-
* `.
|
|
21
|
+
* `.gitea/workflows/build.yml` at run time and there is nothing to keep in
|
|
22
22
|
* step.
|
|
23
23
|
*
|
|
24
24
|
* **It refuses rather than assumes.** If the workflow's shape changes so that
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
*
|
|
29
29
|
* **What it cannot run**, and says so: a `uses:` step is a published action, not
|
|
30
30
|
* a command — the forbidden-marker check, the coverage upload. Those stay
|
|
31
|
-
*
|
|
31
|
+
* CI's to run, and the summary names them so the gap is visible rather than
|
|
32
32
|
* assumed away.
|
|
33
33
|
*
|
|
34
34
|
* **It parses the workflow itself rather than importing a YAML library**, for a
|
|
@@ -64,7 +64,7 @@ const ROOT = process.cwd();
|
|
|
64
64
|
* failed*. Mirrors the same constant in `ci-docker.mjs`.
|
|
65
65
|
*/
|
|
66
66
|
export const UNAVAILABLE = 2;
|
|
67
|
-
const WORKFLOW_DIR = path.join(ROOT, ".
|
|
67
|
+
const WORKFLOW_DIR = path.join(ROOT, ".gitea/workflows");
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* The workflow's steps, split into what can be run here and what cannot.
|
|
@@ -77,7 +77,7 @@ export function ciSteps() {
|
|
|
77
77
|
const files = prTriggeredWorkflows();
|
|
78
78
|
if (!files.length) {
|
|
79
79
|
throw new NoWorkflow(
|
|
80
|
-
`no workflow under .
|
|
80
|
+
`no workflow under .gitea/workflows is triggered by \`pull_request\`, ` +
|
|
81
81
|
`so there is nothing here that a pull request would run.`,
|
|
82
82
|
);
|
|
83
83
|
}
|
|
@@ -236,7 +236,7 @@ function main() {
|
|
|
236
236
|
});
|
|
237
237
|
if (result.status !== 0) {
|
|
238
238
|
console.error(
|
|
239
|
-
`\nci-steps: FAILED at "${step.name}" — this is what
|
|
239
|
+
`\nci-steps: FAILED at "${step.name}" — this is what CI would report.\n` +
|
|
240
240
|
` Fix it, or push with --no-verify if you mean to.`,
|
|
241
241
|
);
|
|
242
242
|
return result.status ?? 1;
|
package/content-config.mjs
CHANGED
|
@@ -787,6 +787,7 @@ const PDF_KEYS = [
|
|
|
787
787
|
const PDF_BOOK_KEYS = [
|
|
788
788
|
"id",
|
|
789
789
|
"title",
|
|
790
|
+
"abbreviation",
|
|
790
791
|
"subtitle",
|
|
791
792
|
"coverImage",
|
|
792
793
|
"front",
|
|
@@ -1813,6 +1814,8 @@ function normalizeSite(value) {
|
|
|
1813
1814
|
* @typedef {object} ResolvedPdfBook
|
|
1814
1815
|
* @property {string} id Its name on the command line, `--book <id>`.
|
|
1815
1816
|
* @property {string} title
|
|
1817
|
+
* @property {string} abbreviation Names the book in another book's
|
|
1818
|
+
* references; `""` when none is declared.
|
|
1816
1819
|
* @property {string} subtitle `""` when none is declared.
|
|
1817
1820
|
* @property {string} coverImage Absolute; `""` when none is declared.
|
|
1818
1821
|
* Whether the file exists is checked when the
|
|
@@ -1984,6 +1987,8 @@ function normalizePdfBooks(value, rootDir, contentPackage) {
|
|
|
1984
1987
|
const ids = new Set();
|
|
1985
1988
|
/** @type {Map<string, number>} Stem → the book that took it. */
|
|
1986
1989
|
const stems = new Map();
|
|
1990
|
+
/** @type {Map<string, string>} Abbreviation → the id of the book that took it. */
|
|
1991
|
+
const abbreviations = new Map();
|
|
1987
1992
|
return Object.freeze(
|
|
1988
1993
|
value.map((entry, i) => {
|
|
1989
1994
|
const where = `pdf.books[${i}]`;
|
|
@@ -2003,6 +2008,19 @@ function normalizePdfBooks(value, rootDir, contentPackage) {
|
|
|
2003
2008
|
if (ids.has(id)) fail(`${where}.id`, `is \`${id}\`, which names a book more than once`);
|
|
2004
2009
|
ids.add(id);
|
|
2005
2010
|
const title = requireNonEmptyString(book.title, `${where}.title`);
|
|
2011
|
+
let abbreviation = "";
|
|
2012
|
+
if (book.abbreviation !== undefined) {
|
|
2013
|
+
abbreviation = requireNonEmptyString(book.abbreviation, `${where}.abbreviation`);
|
|
2014
|
+
const holder = abbreviations.get(abbreviation);
|
|
2015
|
+
if (holder !== undefined) {
|
|
2016
|
+
fail(
|
|
2017
|
+
`${where}.abbreviation`,
|
|
2018
|
+
`is \`${abbreviation}\`, which books \`${holder}\` and \`${id}\` both ` +
|
|
2019
|
+
"declare — a reader could not tell which book to open",
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
abbreviations.set(abbreviation, id);
|
|
2023
|
+
}
|
|
2006
2024
|
|
|
2007
2025
|
// One tree, from exactly one place: two would leave the reader to
|
|
2008
2026
|
// guess which is printed, and none prints nothing.
|
|
@@ -2059,6 +2077,7 @@ function normalizePdfBooks(value, rootDir, contentPackage) {
|
|
|
2059
2077
|
return Object.freeze({
|
|
2060
2078
|
id,
|
|
2061
2079
|
title,
|
|
2080
|
+
abbreviation,
|
|
2062
2081
|
subtitle:
|
|
2063
2082
|
book.subtitle === undefined ?
|
|
2064
2083
|
""
|
package/docs/api.md
CHANGED
|
@@ -384,6 +384,7 @@ The declared infobox: what a note's summary panel holds, decided once and render
|
|
|
384
384
|
| `assertInfoboxSet` | `assertInfoboxSet(boxes, fm, options)` | `readonly object[]`, throws | refusing a page that carries anything but the boxes its type maps to |
|
|
385
385
|
| `buildInfoboxes` | `buildInfoboxes(fm, options)` | `object[]` | building every box a note carries, against a given set of maps and declarations |
|
|
386
386
|
| `defineInfobox` | `defineInfobox(declaration)` | `object` | declaring one system's half of the infobox |
|
|
387
|
+
| `formatQuantity` | `formatQuantity(value)` | `string` | writing a number as a profile shows it: `12,500` under a million, `11 million` above |
|
|
387
388
|
| `hasRenderableValue` | `hasRenderableValue(kind, value)` | `boolean` | deciding whether a built value is worth a row |
|
|
388
389
|
| `hasValue` | `hasValue(value)` | `boolean` | deciding whether an authored value is worth a row |
|
|
389
390
|
| `humanizeFieldName` | `humanizeFieldName(name)` | `string` | turning a declared key into the label a reader sees |
|
|
@@ -21,7 +21,7 @@ data: {}
|
|
|
21
21
|
---
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
`name` is a map with a required, nonempty `full` display name. Every note may also use `aliases` for an ordered list of other names
|
|
24
|
+
`name` is a map with a required, nonempty `full` display name. Every note may also use `aliases` for an ordered list of other names, and `sort` for the form the name takes in an index, such as `Black Flame, The` or `Hilvan, Doran`. The note infobox lists aliases directly beneath the full name. `sort` files the note wherever notes are ordered by name — the book index, page lists, generated lists of related notes, and the `nameSort` column of the `entries` relation — and is the note's entry text in the book index. Every other list still displays the `full` name. A note with no `sort` files and indexes by its `full` name. For `name: {full: The Black Flame, sort: "Black Flame, The"}`, the book index prints _Black Flame, The_ under **B**, and a page list prints _The Black Flame_ at the same place.
|
|
25
25
|
|
|
26
26
|
`description` is the short page summary. `tags` hold draft state and descriptive labels. The note type's classification belongs in `subType` where it is declared. `data` holds facts about the subject shared by systems. A YAML comment is the place for an author-only note: commented values remain valid YAML but do not enter the content index or generated documents.
|
|
27
27
|
|
|
@@ -197,7 +197,7 @@ The caption attaches to exactly the next supported block:
|
|
|
197
197
|
|
|
198
198
|
The optional `type` attribute overrides inference and accepts `figure`, `table`, `poetry`, `code`, `prose`, and `example`. `type=example` uses **Example 1**, **Example 2**, and so on. It does not allow an unsupported target. A heading, list, thematic break, another caption or end of document after a caption is an error. Caption markers without separating spaces or caption text are errors too.
|
|
199
199
|
|
|
200
|
-
Identifiers are optional and must be unique within the note. Classes are unrestricted; `.border` uses the shared border style, while other classes can be styled by the medium. Named attributes use `key=value`, with quoted values for text containing spaces. Use `#id` and `.class` rather than `id=` and `class=`; event-handler attributes are refused.
|
|
200
|
+
Identifiers are optional and must be unique within the note. Classes are unrestricted; `.border` uses the shared border style, while other classes can be styled by the medium. On a captioned table or SQL fence, `.full-width` sets the table across the page in a PDF book instead of in one column (`: Regional trade routes {.full-width}`); the other surfaces ignore it. Named attributes use `key=value`, with quoted values for text containing spaces. Use `#id` and `.class` rather than `id=` and `class=`; event-handler attributes are refused.
|
|
201
201
|
|
|
202
202
|
Each numbered type has its own counter. Websites and Foundry number within a note; books number across their reading order. Unnumbered captions neither display nor consume a number. A map image can retain its asset-derived **Map** classification when no type is specified.
|
|
203
203
|
|
|
@@ -543,7 +543,7 @@ A column's heading is its SQL alias, so `AS "Name"` is what a reader sees. A cel
|
|
|
543
543
|
| `market` | The market scale as three columns — `value`, `name`, `trade` — so a table can print `village` beside the number a note wrote. |
|
|
544
544
|
| `<package>.notes` | The same relations — `notes`, `entries`, `events`, `names`, `words`, `affixes`, `fields`, `glossary`, `language_lineage`, `dictionary`, `terms`, `register`, `tongues`, `retired`, `scope`, `letters`, `edges`, `clusters`, `stress`, `finals`, `syllables`, `joining`, `joins`, `marks`, `skeleton`, `frames`, `read_past`, `mutations`, `cluster_groups`, `sound_changes`, `elements`, `roots`, `cognates`, `calling_names`, `older`, `negation`, `name_shapes`, `name_edges`, `name_draws`, `held_from` and `near_names` — for each package this one declares a dependency on, in a schema named for it. |
|
|
545
545
|
|
|
546
|
-
Nested frontmatter is addressed exactly as a note writes it: `name.full`, `sohl.weight`, `data.born`. Every Address is a string — the full canonical Address, as in `list_contains(data.parents, 'thalorna-note-place-xerathia')` — whichever package published it. Every note carries a computed `state` column of `full`, `draft` or `stub`. Notes tagged `gm` are absent from the relations on public surfaces.
|
|
546
|
+
Nested frontmatter is addressed exactly as a note writes it: `name.full`, `sohl.weight`, `data.born`. Every Address is a string — the full canonical Address, as in `list_contains(data.parents, 'thalorna-note-place-xerathia')` — whichever package published it. Every note carries a computed `state` column of `full`, `draft` or `stub`, and a `nameSort` column holding its `name.sort` or, without one, its `name.full`, to `ORDER BY`. Notes tagged `gm` are absent from the relations on public surfaces.
|
|
547
547
|
|
|
548
548
|
### Sections the build adds
|
|
549
549
|
|
|
@@ -567,7 +567,7 @@ The vale holds three villages, of which only [[place-ford|Ford]] is walled.
|
|
|
567
567
|
|
|
568
568
|
### Sections the build adds about events
|
|
569
569
|
|
|
570
|
-
A note's events are listed for you on the notes they concern: a place gets a **Chronology** of what happened there and below it, with every `depth: world` event as context, a being, affiliation or people the **Events** it took part in, an affiliation, place or people the **Accounts** it gives, and a note holding events **What followed** them. Each is a section appended after everything you wrote, with a fixed anchor — `chronology`, `events`, `accounts`, `followed` — and it appears only where it has something to list.
|
|
570
|
+
A note's events are listed for you on the notes they concern: a place gets a **Chronology** of what happened there and below it, with every `depth: world` event as context, a being, affiliation or people the **Events** it took part in, an affiliation, place or people the **Accounts** it gives, and a note holding events **What followed** them. Each is a section appended after everything you wrote, with a fixed anchor — `chronology`, `events`, `accounts`, `followed` — and it appears only where it has something to list. Each lists the date first, written in the setting's calendar, followed by what happened; a Chronology adds a **Felt here** column only where some event is felt in the place, and a PDF book prints it across the full page width.
|
|
571
571
|
|
|
572
572
|
To replace one, write the section yourself with the same anchor. The build then adds nothing for it and leaves yours exactly as written:
|
|
573
573
|
|
|
@@ -577,7 +577,7 @@ To replace one, write the section yourself with the same anchor. The build then
|
|
|
577
577
|
The fells keep no reckoning before the raising.
|
|
578
578
|
|
|
579
579
|
```sql
|
|
580
|
-
SELECT summary AS "What happened",
|
|
580
|
+
SELECT summary AS "What happened", whenText AS "When"
|
|
581
581
|
FROM events
|
|
582
582
|
WHERE list_contains("where".locus, 'thalorna-note-place-ironfells')
|
|
583
583
|
ORDER BY whenSort
|
package/docs/commands.md
CHANGED
|
@@ -128,16 +128,16 @@ package-build ci [--native]
|
|
|
128
128
|
|
|
129
129
|
The default command runs in Docker on Linux against a clean export of the
|
|
130
130
|
current commit. It reads the repository's pull-request workflows under
|
|
131
|
-
`.
|
|
131
|
+
`.gitea/workflows/` and runs their `run:` steps in workflow order. A failed
|
|
132
132
|
step stops the replay. Uncommitted changes are absent from the clean export.
|
|
133
133
|
|
|
134
134
|
`--native` runs the same steps on the host in the current working tree. It
|
|
135
135
|
needs no Docker and includes uncommitted changes, but its operating system and
|
|
136
|
-
existing files can differ from a clean
|
|
136
|
+
existing files can differ from a clean CI runner. The steps themselves
|
|
137
137
|
still come from the workflow files.
|
|
138
138
|
|
|
139
139
|
The command lists `uses:` steps that it cannot run locally. A successful replay
|
|
140
|
-
covers the listed `run:` steps;
|
|
140
|
+
covers the listed `run:` steps; the forge runs the published actions separately.
|
|
141
141
|
Workflow steps may install dependencies, generate files, or perform other
|
|
142
142
|
declared build actions.
|
|
143
143
|
|
|
@@ -2632,7 +2632,8 @@ Typst compiler; `--no-compile` emits the Typst source and stops there,
|
|
|
2632
2632
|
which is what lets the source be inspected or compiled by hand.
|
|
2633
2633
|
|
|
2634
2634
|
A reference to a note that another book is home to prints that book and page,
|
|
2635
|
-
`Name (_Book title_, p. 12)
|
|
2635
|
+
`Name (_Book title_, p. 12)`, or `Name (Aelw. p12)` when that book declares an
|
|
2636
|
+
`abbreviation`. A compile renders and lays out every book, writes
|
|
2636
2637
|
`pages.json` (each note's home book and the page its entry starts on) into the
|
|
2637
2638
|
output directory, and renders again with those pages; `--no-compile` runs
|
|
2638
2639
|
neither pass and prints `p. ?`. See the
|
package/docs/configuration.md
CHANGED
|
@@ -1156,6 +1156,7 @@ pdf:
|
|
|
1156
1156
|
books:
|
|
1157
1157
|
- id: core
|
|
1158
1158
|
title: The World of Thalorna
|
|
1159
|
+
abbreviation: Thal.
|
|
1159
1160
|
subtitle: A Song of Heroic Lands Sourcebook
|
|
1160
1161
|
coverImage: assets/images/covers/core.webp
|
|
1161
1162
|
document: book.yaml
|
|
@@ -1209,7 +1210,8 @@ The keys of `pdf` itself are shared by every book:
|
|
|
1209
1210
|
|
|
1210
1211
|
- `pdf.indexSeeAlso` adds a line to a book's index for each note that another
|
|
1211
1212
|
book prints and this book mentions in authored prose, written
|
|
1212
|
-
`Name: _Book title_, p. N`
|
|
1213
|
+
`Name: _Book title_, p. N` (or `Name: Aelw. p307` when that book declares an
|
|
1214
|
+
`abbreviation`) with the page the note's entry starts on in its
|
|
1213
1215
|
home book. It is `false` by default, and the index then lists only the notes
|
|
1214
1216
|
the book is home to. See [The index](guides/book.md#the-index).
|
|
1215
1217
|
- `pdf.indexHeads` lists leading words that open many names, such as
|
|
@@ -1236,20 +1238,31 @@ Targud` is also listed as `Targud, Kingdom of`, with the same pages as the
|
|
|
1236
1238
|
|
|
1237
1239
|
Each entry of `pdf.books` is one PDF:
|
|
1238
1240
|
|
|
1239
|
-
| Key
|
|
1240
|
-
|
|
|
1241
|
-
| `pdf.books[].id`
|
|
1242
|
-
| `pdf.books[].title`
|
|
1243
|
-
| `pdf.books[].
|
|
1244
|
-
| `pdf.books[].
|
|
1245
|
-
| `pdf.books[].
|
|
1246
|
-
| `pdf.books[].
|
|
1247
|
-
| `pdf.books[].
|
|
1248
|
-
| `pdf.books[].
|
|
1241
|
+
| Key | Type | Required | Default |
|
|
1242
|
+
| -------------------------- | ------------------------- | -------------- | ------------------------- |
|
|
1243
|
+
| `pdf.books[].id` | string | yes | — |
|
|
1244
|
+
| `pdf.books[].title` | string | yes | — |
|
|
1245
|
+
| `pdf.books[].abbreviation` | string | no | `""` — the title names it |
|
|
1246
|
+
| `pdf.books[].subtitle` | string | no | `""` |
|
|
1247
|
+
| `pdf.books[].coverImage` | string (path) | no | `""` — no cover |
|
|
1248
|
+
| `pdf.books[].front` | string[] (markdown files) | no | `[]` |
|
|
1249
|
+
| `pdf.books[].out` | string (file stem) | no | `<contentPackage>-<id>` |
|
|
1250
|
+
| `pdf.books[].contents` | list (the tree, inline) | one of the two | — |
|
|
1251
|
+
| `pdf.books[].document` | string (path to the tree) | one of the two | — |
|
|
1249
1252
|
|
|
1250
1253
|
- `id` is lowercase letters, digits and hyphens, starting with a letter or
|
|
1251
1254
|
digit. It is what `--book <id>` names and the tail of the default file stem.
|
|
1252
1255
|
Each `id` is used once.
|
|
1256
|
+
- `abbreviation` is a short name for the book, such as `Aelw.`. It is used only
|
|
1257
|
+
where another book refers to this one: a reference to a note this book
|
|
1258
|
+
prints reads `Name (Aelw. p307)`, with a non-breaking space between the
|
|
1259
|
+
abbreviation and the page, and the other book's index, when
|
|
1260
|
+
`pdf.indexSeeAlso` is on, lists the note as `Name: Aelw. p307`. A book
|
|
1261
|
+
with no abbreviation is named by its full title in italics, `Name (_Book
|
|
1262
|
+
title_, p. 307)`. A link the text closes a bracket on takes the reference
|
|
1263
|
+
inside that bracket after a comma, so `(see The Crossing)` prints
|
|
1264
|
+
`(see The Crossing, Thal. p524)` rather than nesting brackets. Each abbreviation is used once, since a reader could not
|
|
1265
|
+
tell which book to open from one shared by two.
|
|
1253
1266
|
- `contents` is the tree written inline — the same `sectionName` /
|
|
1254
1267
|
`contents` / `filter` / `file` shape a tree file holds under its own
|
|
1255
1268
|
`contents:` key — and `document` names the file holding it. A book declares
|
|
@@ -1296,6 +1309,10 @@ file, line and column the key is written at:
|
|
|
1296
1309
|
|
|
1297
1310
|
> ``package-build config: `pdf.books[0].title` must be a non-empty string.``
|
|
1298
1311
|
|
|
1312
|
+
> ``package-build config: `pdf.books[0].abbreviation` must be a non-empty string.``
|
|
1313
|
+
|
|
1314
|
+
> ``package-build config: `pdf.books[1].abbreviation` is `Thal.`, which books `core` and `vedyara` both declare — a reader could not tell which book to open.``
|
|
1315
|
+
|
|
1299
1316
|
> ``package-build config: `pdf.books[0]` must declare exactly one of `contents` and `document` — the tree written inline, or the file holding it.``
|
|
1300
1317
|
|
|
1301
1318
|
> ``package-build config: `pdf.books[0].out` is `dist/core`, which is not a file stem — letters, digits, `.`, `_` and `-`, with no directory; the directory is `pdf.out`.``
|
|
@@ -1304,7 +1321,7 @@ file, line and column the key is written at:
|
|
|
1304
1321
|
|
|
1305
1322
|
> ``package-build config: `pdf.books[0].front` must be a list of markdown files.``
|
|
1306
1323
|
|
|
1307
|
-
> ``package-build config: `pdf.books[0].<key>` is not a recognized option (expected one of: id, title, subtitle, coverImage, front, out, contents, document).``
|
|
1324
|
+
> ``package-build config: `pdf.books[0].<key>` is not a recognized option (expected one of: id, title, abbreviation, subtitle, coverImage, front, out, contents, document).``
|
|
1308
1325
|
|
|
1309
1326
|
> ``package-build config: `pdf.coverage` must be one of: warning, error, off.``
|
|
1310
1327
|
|
package/docs/guides/book.md
CHANGED
|
@@ -174,11 +174,11 @@ the Lyrebird Inn (The Tenebrae Volume, p. 214)
|
|
|
174
174
|
|
|
175
175
|
Which form a wikilink takes depends on where its target is printed:
|
|
176
176
|
|
|
177
|
-
| The target is | The link prints
|
|
178
|
-
| ------------------------------------------------------------- |
|
|
179
|
-
| An entry of the same book, including a note the book reprints | An internal destination
|
|
180
|
-
| An entry of another book, and not of this one | `Name (_Book title_, p. N)`, with no link |
|
|
181
|
-
| A note in no book | The web address of the note's page
|
|
177
|
+
| The target is | The link prints |
|
|
178
|
+
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
179
|
+
| An entry of the same book, including a note the book reprints | An internal destination |
|
|
180
|
+
| An entry of another book, and not of this one | `Name (_Book title_, p. N)`, or `Name (Aelw. pN)` with an abbreviation, with no link; inside a bracket the text closes, `(Name, Aelw. pN)` |
|
|
181
|
+
| A note in no book | The web address of the note's page |
|
|
182
182
|
|
|
183
183
|
The page is the page the PDF itself numbers, which is the number printed in the
|
|
184
184
|
footer and used by the contents and the index, so the printed `p. N` is the
|
|
@@ -406,12 +406,12 @@ A book that prints at least one note ends with an **Index** chapter, grouped
|
|
|
406
406
|
under letter headings. Nothing configures it: the build derives it from the
|
|
407
407
|
notes the book prints.
|
|
408
408
|
|
|
409
|
-
| Term | Source
|
|
410
|
-
| ------------------------ |
|
|
411
|
-
| A note's name | `name.full`
|
|
412
|
-
| A name without its head | `name.full`
|
|
413
|
-
| An alias | `name.aliases`
|
|
414
|
-
| An event's in-world name | `data.events[].names`
|
|
409
|
+
| Term | Source | Shown as |
|
|
410
|
+
| ------------------------ | ----------------------------- | ---------------------------------------------------------------------------------- |
|
|
411
|
+
| A note's name | `name.sort`, else `name.full` | the page of the note's entry in **bold**, then the pages of every authored mention |
|
|
412
|
+
| A name without its head | `name.full` | the same pages as the note's entry, filed as `Targud, Kingdom of` |
|
|
413
|
+
| An alias | `name.aliases` | _see_ the note's name |
|
|
414
|
+
| An event's in-world name | `data.events[].names` | _see_ the name of the note that holds the event |
|
|
415
415
|
|
|
416
416
|
A name that opens with a generic word a reader would not look under, such as
|
|
417
417
|
`Kingdom of Targud` or `Ritual: Blessing`, is also filed under the rest of the
|
|
@@ -438,7 +438,7 @@ first book in `pdf.books` that selects it. In a package of several books:
|
|
|
438
438
|
as the cross-book references (`pages.json`), so the page a reference prints
|
|
439
439
|
for a note is the page its home book's index lists for the note's own entry.
|
|
440
440
|
- A mention of a note another book prints is not a term of the mentioning
|
|
441
|
-
book. The mention reads `Name (_Book title_, p. N)` in the text, and the note's
|
|
441
|
+
book. The mention reads `Name (_Book title_, p. N)` in the text (`Name (Aelw. pN)` when the home book declares an `abbreviation`), and the note's
|
|
442
442
|
entry and its page are in the other book's index.
|
|
443
443
|
|
|
444
444
|
Set [`pdf.indexSeeAlso`](../configuration.md) to `true` to list those mentions
|
|
@@ -447,6 +447,8 @@ then has a line, filed under its name:
|
|
|
447
447
|
|
|
448
448
|
> Sword: _Book A_, p. 12
|
|
449
449
|
|
|
450
|
+
A home book with an `abbreviation` files the line as `Sword: BkA. p12`.
|
|
451
|
+
|
|
450
452
|
Two kinds of link add nothing:
|
|
451
453
|
|
|
452
454
|
- **Links in generated sections** such as "Within" or "Governed by". They name
|
|
@@ -458,7 +460,7 @@ Two kinds of link add nothing:
|
|
|
458
460
|
|
|
459
461
|
Terms sort on the ASCII fold the content index derives for names (`nameAscii`),
|
|
460
462
|
ignoring case, so an accented name files under its base letter and the order
|
|
461
|
-
matches the table of contents. A term that does not begin with a letter files
|
|
463
|
+
matches the table of contents. A note's own entry reads as its `name.sort` when it declares one, in index form such as `Black Flame, The` or `Hilvan, Doran`, and as its `name.full` otherwise; it files, and takes its letter heading, from that form. A note that declares a `sort` is not also filed under `pdf.indexHeads`. A term that does not begin with a letter files
|
|
462
464
|
first, under `#`. An alias or event name that folds to the same text as the
|
|
463
465
|
note's own name is not listed again.
|
|
464
466
|
|
|
@@ -182,17 +182,14 @@ release runs in two halves, one on each:
|
|
|
182
182
|
pushes the tag `v<version>` at the merged version commit. Releasing from
|
|
183
183
|
Gitea is switched on by the repository variable `HL_RELEASE_ENABLED`.
|
|
184
184
|
2. **Publish, on GitHub.** The push mirror carries the tag to GitHub, where it
|
|
185
|
-
starts the **Publish to npm** workflow, `.github/workflows/release.yml
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
changelog section, unless one exists.
|
|
185
|
+
starts the **Publish to npm** workflow, `.github/workflows/release.yml`, the
|
|
186
|
+
only workflow GitHub runs. Every check has already run on Gitea before the
|
|
187
|
+
tag exists, so the workflow installs, publishes through npm Trusted
|
|
188
|
+
Publishing with provenance, and creates the GitHub Release from the
|
|
189
|
+
version's changelog section.
|
|
191
190
|
|
|
192
191
|
Nothing on Gitea publishes to npm, and nothing on GitHub runs for a branch
|
|
193
|
-
push.
|
|
194
|
-
dispatching it with the tag as its ref, finishes a release whose publish or
|
|
195
|
-
Release step failed and changes nothing for one that completed. The
|
|
192
|
+
push. The
|
|
196
193
|
[npm package page](https://www.npmjs.com/package/@heroiclands/package-build)
|
|
197
194
|
shows the published version and README.
|
|
198
195
|
|
package/docs/project-setup.md
CHANGED
|
@@ -513,7 +513,7 @@ tool:
|
|
|
513
513
|
organisation-level and defined in `HeroicLands/.github`, so the repository
|
|
514
514
|
carries no label registry of its own.
|
|
515
515
|
|
|
516
|
-
`.
|
|
516
|
+
`.gitea/workflows/` carries the repository's own CI. The org-wide
|
|
517
517
|
`HeroicLands/.github` repository supplies the shared composite actions those
|
|
518
518
|
workflows `uses:` — `labels`, `no-attribution` and `todos` — plus the reusable
|
|
519
519
|
site-deploy workflow, so a fix to any of them reaches every repository at once.
|
|
@@ -522,16 +522,16 @@ site-deploy workflow, so a fix to any of them reaches every repository at once.
|
|
|
522
522
|
|
|
523
523
|
A summary, because "where does this come from" is the question that recurs.
|
|
524
524
|
|
|
525
|
-
| Read
|
|
526
|
-
|
|
|
527
|
-
| `package.json`
|
|
528
|
-
| `package-build.config.yaml`
|
|
529
|
-
| `assets/content/**/*.md`
|
|
530
|
-
| `.gitignore`
|
|
531
|
-
| `.prettierignore`
|
|
532
|
-
| `lang/*.json`
|
|
533
|
-
| `.env.local`
|
|
534
|
-
| `.
|
|
525
|
+
| Read | By |
|
|
526
|
+
| ---------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
527
|
+
| `package.json` | The Foundry package id, the version, the release addresses; the site's `baseURL`, description and author. |
|
|
528
|
+
| `package-build.config.yaml` | Everything else about the build. |
|
|
529
|
+
| `assets/content/**/*.md` | Every content command. |
|
|
530
|
+
| `.gitignore` | `package-build format`, `package-build markdown`. |
|
|
531
|
+
| `.prettierignore` | `package-build format`. |
|
|
532
|
+
| `lang/*.json` | `package-build lang`. |
|
|
533
|
+
| `.env.local` | `package-build deploy`. |
|
|
534
|
+
| `.gitea/workflows/build.yml` | The `pre-push` hook, for its step list. |
|
|
535
535
|
|
|
536
536
|
Everything written goes under `build/`. Nothing the toolchain generates is
|
|
537
537
|
committed.
|
|
@@ -379,7 +379,13 @@ content, comment out its YAML lines with `#`. Comments remain valid YAML text
|
|
|
379
379
|
and do not become data in the JSONL index, generated pages, or Foundry documents.
|
|
380
380
|
|
|
381
381
|
`name` is a map with a required nonempty `full` string when present. It may
|
|
382
|
-
also contain `aliases`, an ordered list of nonempty strings
|
|
382
|
+
also contain `aliases`, an ordered list of nonempty strings, and `sort`, a nonempty
|
|
383
|
+
string giving the form the name takes in an index, such as `Black Flame, The` or
|
|
384
|
+
`Hilvan, Doran`. `sort` files the note wherever notes are ordered by name (page
|
|
385
|
+
lists, generated lists of related notes, the book index, the `nameSort` column)
|
|
386
|
+
and is the note's entry text in the book index; every other list still displays
|
|
387
|
+
`full`. Without `sort`, the note files and indexes by `full`. Ordering compares
|
|
388
|
+
the ASCII fold of the key (`Árdavon` files beside `Ardavon`). A being with
|
|
383
389
|
`subType: character` may additionally contain nonempty `given` and `clan`
|
|
384
390
|
strings. Other keys under `name` are errors. For a character or NPC, the first
|
|
385
391
|
alias is its nickname. A being's homes belong in `data.homes`.
|
|
@@ -2916,7 +2922,9 @@ reads `NULL` rather than failing. `data.culture` is the one field that reads as
|
|
|
2916
2922
|
a derived value: the note's own culture, or the one it inherits. See
|
|
2917
2923
|
[Culture in the index](#culture-in-the-index).
|
|
2918
2924
|
|
|
2919
|
-
**`notes` leaves out stubs; `entries` does not.** Both carry a derived `
|
|
2925
|
+
**`notes` leaves out stubs; `entries` does not.** Both carry a derived `nameSort`
|
|
2926
|
+
column — the note's `name.sort` when it declares one, otherwise its `name.full`,
|
|
2927
|
+
unfolded — for `ORDER BY`, and a derived `state`
|
|
2920
2928
|
column — `stub`, `draft` or `full` — and `notes` is exactly
|
|
2921
2929
|
`SELECT * FROM entries WHERE state <> 'stub'`. So every table already written
|
|
2922
2930
|
selects what it always selected, and gains `state` for free; a table that should
|
|
@@ -2957,20 +2965,21 @@ query rather than a list kept by hand:
|
|
|
2957
2965
|
|
|
2958
2966
|
````markdown
|
|
2959
2967
|
```sql
|
|
2960
|
-
SELECT note AS _ref,
|
|
2968
|
+
SELECT note AS _ref, whenText AS "When", summary AS "What happened"
|
|
2961
2969
|
FROM events
|
|
2962
2970
|
WHERE kind IN ('war', 'siege', 'battle')
|
|
2963
2971
|
ORDER BY whenSort
|
|
2964
2972
|
```
|
|
2965
2973
|
````
|
|
2966
2974
|
|
|
2967
|
-
| Column | What it holds
|
|
2968
|
-
| ------------------------ |
|
|
2969
|
-
| `note` | The canonical Address of the note the event is written on.
|
|
2970
|
-
| `address` | The event's own Address: `note#id` where the event has an `id`, the note's Address alone where it has none.
|
|
2971
|
-
| `id`, `kind`, `depth`, … | Every key of an event, each its own column under its own name — `id`, `kind`, `depth`, `when`, `until`, `recurs`, `summary`, `standing`, `names`, `where`, `who`, `follows`, `accounts`, `unresolved`, `sources`, `stated`.
|
|
2972
|
-
| `whenSort`, `untilSort` | The date's position on the canonical axis: the canonical year, with a day as its fraction of the year where the calendar states the year's length. `ORDER BY whenSort` is date order.
|
|
2973
|
-
| `
|
|
2975
|
+
| Column | What it holds |
|
|
2976
|
+
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2977
|
+
| `note` | The canonical Address of the note the event is written on. |
|
|
2978
|
+
| `address` | The event's own Address: `note#id` where the event has an `id`, the note's Address alone where it has none. |
|
|
2979
|
+
| `id`, `kind`, `depth`, … | Every key of an event, each its own column under its own name — `id`, `kind`, `depth`, `when`, `until`, `recurs`, `summary`, `standing`, `names`, `where`, `who`, `follows`, `accounts`, `unresolved`, `sources`, `stated`. |
|
|
2980
|
+
| `whenSort`, `untilSort` | The date's position on the canonical axis: the canonical year, with a day as its fraction of the year where the calendar states the year's length. `ORDER BY whenSort` is date order. |
|
|
2981
|
+
| `whenText`, `untilText` | The date as a reader sees it, printed as `dateformat` prints it in the calendar the owning note names in `data.calendar`, else the one the world note names, else in the era the date itself names; `~` marks an approximate date. A date that does not resolve keeps the text its note wrote. |
|
|
2982
|
+
| `whenYear`, `untilYear` | The canonical year alone, for a filter such as `whenYear BETWEEN 200 AND 299`. |
|
|
2974
2983
|
|
|
2975
2984
|
`when` and `until` read as the text the note wrote, `~` included. The sort
|
|
2976
2985
|
keys and years are positions on the canonical axis, which counts a year `0`
|
|
@@ -3498,7 +3507,7 @@ The caption attaches to exactly the next supported block:
|
|
|
3498
3507
|
|
|
3499
3508
|
The optional `type` attribute overrides inference and accepts `figure`, `table`, `poetry`, `code`, `prose`, and `example`. `type=example` uses **Example 1**, **Example 2**, and so on. It does not allow an unsupported target. A heading, list, thematic break, another caption or end of document after a caption is an error. Caption markers without separating spaces or caption text are errors too.
|
|
3500
3509
|
|
|
3501
|
-
Identifiers are optional and must be unique within the note. Classes are unrestricted; `.border` uses the shared border style, while other classes can be styled by the medium. Named attributes use `key=value`, with quoted values for text containing spaces. Use `#id` and `.class` rather than `id=` and `class=`; event-handler attributes are refused.
|
|
3510
|
+
Identifiers are optional and must be unique within the note. Classes are unrestricted; `.border` uses the shared border style, while other classes can be styled by the medium. On a captioned table or SQL fence, `.full-width` sets the table across the page in a PDF book instead of in one column (`: Regional trade routes {.full-width}`); the other surfaces ignore it. Named attributes use `key=value`, with quoted values for text containing spaces. Use `#id` and `.class` rather than `id=` and `class=`; event-handler attributes are refused.
|
|
3502
3511
|
|
|
3503
3512
|
Each numbered type has its own counter. Websites and Foundry number within a note; books number across their reading order. Unnumbered captions neither display nor consume a number. A map image can retain its asset-derived **Map** classification when no type is specified.
|
|
3504
3513
|
|
|
@@ -3763,11 +3772,14 @@ The build appends sections to the end of a note's body that list the events conc
|
|
|
3763
3772
|
|
|
3764
3773
|
| View | Anchor | On | Rows | Columns |
|
|
3765
3774
|
| ------------- | ------------ | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
|
|
3766
|
-
| Chronology | `chronology` | a `place` | events whose `where.locus` names the place or a place below it in `parents`; events whose `where.reach` names one, with that entry's `how`; every `depth: world` event as context | What happened,
|
|
3767
|
-
| Events | `events` | a `being`, an `affiliation`, a `lore` note of `subType: culture` | events whose `who[].ref` names the note, with the role it played | What happened,
|
|
3768
|
-
| Accounts | `accounts` | an `affiliation`, a `place`, a `lore` note of `subType: culture` | each `accounts[]` entry the note gives, with the event it concerns |
|
|
3769
|
-
| What followed | `followed` | a note holding events | events whose `follows[].event` names one of this note's events, with how | What followed,
|
|
3770
|
-
|
|
3775
|
+
| Chronology | `chronology` | a `place` | events whose `where.locus` names the place or a place below it in `parents`; events whose `where.reach` names one, with that entry's `how`; every `depth: world` event as context | When, What happened, Felt here |
|
|
3776
|
+
| Events | `events` | a `being`, an `affiliation`, a `lore` note of `subType: culture` | events whose `who[].ref` names the note, with the role it played | When, What happened, Role |
|
|
3777
|
+
| Accounts | `accounts` | an `affiliation`, a `place`, a `lore` note of `subType: culture` | each `accounts[]` entry the note gives, with the event it concerns | When, Event, What they say, Agrees |
|
|
3778
|
+
| What followed | `followed` | a note holding events | events whose `follows[].event` names one of this note's events, with how | When, What followed, How |
|
|
3779
|
+
|
|
3780
|
+
- **Every view's first column is When**, the event's date written in the setting's calendar — the `events` relation's `whenText` column. An event whose date does not resolve shows the text its note wrote.
|
|
3781
|
+
- **A chronology's Felt here column appears only where some row is felt in the place.** A place whose rows are all events that happened there, or world events, has the When and What happened columns alone.
|
|
3782
|
+
- **A chronology is always set across the full page width in a PDF book**, whatever its columns.
|
|
3771
3783
|
- **The views follow everything the author wrote**, in the order of the [generated sections](#generated-sections).
|
|
3772
3784
|
- **A view with no rows is not generated.** A chronology is generated only where the place has rows of its own; the world events beside them are context and make no chronology by themselves.
|
|
3773
3785
|
- **An author's own section replaces a view.** A note whose body declares a prose anchor with the view's slug — `# Chronology {#chronology}`, or a block or span with that `id` — gets no generated section for it, and its own section is left exactly as written. This is the one way to turn a view off, and the way to choose a different table: write the section and its own `sql` fence.
|
|
@@ -18,7 +18,7 @@ These are the only keys a note writes at its top level, in the order the formatt
|
|
|
18
18
|
| Key | Meaning |
|
|
19
19
|
| ------------- | ----------------------------------------------------------------------------------------- |
|
|
20
20
|
| `shortcode` | The note's own address segment, unique within its type. |
|
|
21
|
-
| `name` | The display names — a required `full`, and any `aliases`.
|
|
21
|
+
| `name` | The display names — a required `full`, and any `aliases` and `sort`. |
|
|
22
22
|
| `type` | What the note is about, which decides its vocabulary and its document. |
|
|
23
23
|
| `subType` | The type's own genre, where it declares one. |
|
|
24
24
|
| `description` | The short page summary. |
|
package/engine/content-index.mjs
CHANGED
|
@@ -108,8 +108,9 @@ import { analyzeNameSpans } from "./names.mjs";
|
|
|
108
108
|
import fs from "node:fs";
|
|
109
109
|
import path from "node:path";
|
|
110
110
|
|
|
111
|
-
import
|
|
111
|
+
import { asciiName } from "./name-sort.mjs";
|
|
112
112
|
|
|
113
|
+
export { asciiName };
|
|
113
114
|
import { loadForeignIndexes, metadataFileName } from "./metadata-index.mjs";
|
|
114
115
|
import { collectAssetRecords } from "./asset-index.mjs";
|
|
115
116
|
import { checkForeignAssetBindings } from "./asset-bindings.mjs";
|
|
@@ -245,44 +246,6 @@ export function noteAddress(frontmatter, contentPackage) {
|
|
|
245
246
|
};
|
|
246
247
|
}
|
|
247
248
|
|
|
248
|
-
/**
|
|
249
|
-
* A note's display name reduced to printable 7-bit ASCII.
|
|
250
|
-
*
|
|
251
|
-
* Content names carry the setting's orthography — `Kûrbúl Helm`, `Hârn`,
|
|
252
|
-
* `Kèthîra` — and nobody types them. A reader searching the index, or an editor
|
|
253
|
-
* completing a wikilink, needs a form that matches what a keyboard produces, so
|
|
254
|
-
* the record states one rather than leaving every consumer to invent it (and to
|
|
255
|
-
* invent a *different* one, which is how two searches over the same data come
|
|
256
|
-
* to disagree).
|
|
257
|
-
*
|
|
258
|
-
* **Transliterated, not stripped.** `unidecode` — the same table
|
|
259
|
-
* {@link slugify} already runs, so an ASCII name and a slug can never disagree
|
|
260
|
-
* about a character — carries a letter across rather than deleting it:
|
|
261
|
-
* diacritics fold (`â`→`a`, `è`→`e`), ligatures expand (`æ`→`ae`, `Œ`→`OE`,
|
|
262
|
-
* `ß`→`ss`), the runic letters spell out (`þ`→`th`, `Þ`→`Th`, `ð`→`d`), and
|
|
263
|
-
* even a vulgar fraction becomes readable (`¾`→`3/4`). Deleting them instead
|
|
264
|
-
* would collapse `Kûrbúl` to `Krbl`, which is worse than the original.
|
|
265
|
-
*
|
|
266
|
-
* Anything still outside printable ASCII after that becomes a space, and runs
|
|
267
|
-
* of whitespace collapse — a space rather than nothing, so a character that
|
|
268
|
-
* transliterates away cannot silently weld two words together.
|
|
269
|
-
*
|
|
270
|
-
* The value is emitted even when it equals the name, so a consumer matching on
|
|
271
|
-
* it never has to branch on whether the name happened to be ASCII already.
|
|
272
|
-
*
|
|
273
|
-
* @param {unknown} name - The note's `name.full`.
|
|
274
|
-
* @returns {string|null} The ASCII form, or `null` when there is no name, or
|
|
275
|
-
* nothing printable survives.
|
|
276
|
-
*/
|
|
277
|
-
export function asciiName(name) {
|
|
278
|
-
if (typeof name !== "string") return null;
|
|
279
|
-
const folded = unidecode(name)
|
|
280
|
-
.replace(/[^\x20-\x7E]/g, " ")
|
|
281
|
-
.replace(/\s+/g, " ")
|
|
282
|
-
.trim();
|
|
283
|
-
return folded === "" ? null : folded;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
249
|
/**
|
|
287
250
|
* A note's `name.aliases` reduced to printable 7-bit ASCII, in order.
|
|
288
251
|
*
|