@heroiclands/package-build 25.5.3 → 25.5.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +7 -0
  3. package/bin/content-commands.mjs +32 -8
  4. package/docs/api.md +57 -16
  5. package/docs/authoring/links-and-markup.md +51 -3
  6. package/docs/commands.md +60 -17
  7. package/docs/configuration.md +3 -2
  8. package/docs/content-format.md +10 -0
  9. package/docs/diagnostics.md +17 -0
  10. package/docs/guides/module-project.md +11 -3
  11. package/docs/guides/site.md +12 -2
  12. package/docs/guides/writing-in-invented-scripts.md +5 -2
  13. package/docs/reference/format-details.md +61 -11
  14. package/docs/reference/note-types.md +32 -32
  15. package/e2e.mjs +1 -1
  16. package/engine/base-compiler.mjs +2 -0
  17. package/engine/compile-corpus.mjs +6 -2
  18. package/engine/content-charset.mjs +1 -1
  19. package/engine/content-render.mjs +14 -2
  20. package/engine/content-scripts.mjs +44 -6
  21. package/engine/content-session.mjs +57 -20
  22. package/engine/content-spans.mjs +3 -4
  23. package/engine/content-verse.mjs +52 -10
  24. package/engine/core-report.mjs +27 -18
  25. package/engine/language-names.mjs +53 -23
  26. package/engine/markdownlint-run.mjs +1 -1
  27. package/engine/markup-validation.mjs +2 -0
  28. package/engine/note-vocabulary.mjs +10 -5
  29. package/engine/npc-generate.mjs +52 -7
  30. package/engine/page-breaks.mjs +115 -0
  31. package/engine/pdf-build.mjs +12 -4
  32. package/engine/pdf-render.mjs +10 -1
  33. package/engine/preview-index.mjs +4 -2
  34. package/engine/site-build.mjs +12 -3
  35. package/engine/site-preview.mjs +3 -0
  36. package/engine/site-render.mjs +18 -4
  37. package/engine/site-root.mjs +10 -1
  38. package/engine/site-serve-http.mjs +151 -0
  39. package/engine/site-serve.mjs +196 -126
  40. package/engine/sql-tables.mjs +379 -214
  41. package/engine/verse-metre.mjs +50 -5
  42. package/engine/verse-regions.mjs +16 -0
  43. package/package.json +3 -3
  44. package/types/e2e.d.mts +1 -1
  45. package/types/engine/content-scripts.d.mts +9 -1
  46. package/types/engine/content-session.d.mts +1 -1
  47. package/types/engine/content-verse.d.mts +11 -2
  48. package/types/engine/core-report.d.mts +6 -2
  49. package/types/engine/language-names.d.mts +18 -6
  50. package/types/engine/npc-generate.d.mts +3 -0
  51. package/types/engine/page-breaks.d.mts +30 -0
  52. package/types/engine/pdf-render.d.mts +2 -0
  53. package/types/engine/site-build.d.mts +3 -1
  54. package/types/engine/site-render.d.mts +6 -2
  55. package/types/engine/site-root.d.mts +3 -1
  56. package/types/engine/site-serve-http.d.mts +1 -0
  57. package/types/engine/site-serve.d.mts +12 -6
  58. package/types/engine/sql-tables.d.mts +37 -8
  59. package/types/engine/verse-metre.d.mts +2 -1
  60. package/types/engine/verse-regions.d.mts +8 -0
@@ -618,11 +618,14 @@ that the language lists in `data.sounds.syllables.vowelPairs` is one syllable.
618
618
 
619
619
  **The beat convention, in plain words:**
620
620
 
621
- - Every word of two or more syllables has one beat, on its stressed syllable.
621
+ - A word of two or more syllables has one beat, on its stressed syllable,
622
+ but may go unstressed when every lexicon class is a function class: any base
623
+ other than `n`, `v`, `adj`, `num` or `name`. A word with any content class,
624
+ or no lexicon entry, keeps its beat.
622
625
  - A one-syllable noun, verb, adjective, numeral or name has a beat. A
623
626
  one-syllable particle, preposition, conjunction, adverb or pronoun has none.
624
627
  The lexicon entry's class decides, and a form that the language's rules derive
625
- takes its word's class. A word whose entries differ in class is a beat that
628
+ takes its word's class. A one-syllable word with both content and function classes has a beat that
626
629
  may be left out.
627
630
  - A one-syllable word the lexicon does not hold is a content word and has a
628
631
  beat; the word check reports the word separately.
@@ -2104,6 +2104,11 @@ keys makes a separate presentation choice. `enc=default` inside an encoded
2104
2104
  parent keeps the inherited language but does not inherit its inferred script
2105
2105
  face. An explicitly authored font remains a font choice.
2106
2106
 
2107
+ The content charset admits characters in every registered setting-script block,
2108
+ including the format controls those scripts use. The selected native face must
2109
+ still draw each character. In a language run, write native characters through
2110
+ literal codage; pasting a native character directly is a finding.
2111
+
2107
2112
  A name's braces use the shared name controls. Resolved references encode only
2108
2113
  their visible labels, retaining URLs and presentation cues. These keys apply
2109
2114
  to spans, divs and scroll fences. A scroll's selected font must
@@ -2282,11 +2287,14 @@ counted.
2282
2287
  The fence states `lang=` with a language whose `note` names a language note
2283
2288
  stating `data.sounds.stress` (see [a language's register, sounds and
2284
2289
  history](#a-languages-register-sounds-and-history)). A word has one beat on its
2285
- stressed syllable when it has two or more syllables; a one-syllable word has a
2290
+ stressed syllable when it has two or more syllables, but may go unstressed
2291
+ when all its lexicon classes have bases outside `n`, `v`, `adj`, `num` and
2292
+ `name`; a multi-syllable word with any of those bases, or no lexicon entry,
2293
+ keeps its beat. A one-syllable word has a
2286
2294
  beat when the `class` base of its lexicon entry is `n`, `v`, `adj`, `num` or
2287
2295
  `name`, and none when it is any other base; a one-syllable word the lexicon
2288
- does not hold is counted as a content word; a word whose entries differ in
2289
- class is a beat that may be left out; and a word of four or more syllables
2296
+ does not hold is counted as a content word; a one-syllable word with both
2297
+ content and function classes has a beat that may be left out; and a word of four or more syllables
2290
2298
  whose stress is not on its first may take a second beat there. A line scans when
2291
2299
  its count can be the metre's.
2292
2300
 
@@ -4009,6 +4017,30 @@ the tag and, where the directive restricts one, the type. An empty list under
4009
4017
  an authored heading is how a misspelled tag survives a build, so the
4010
4018
  directive's silence is what is refused rather than its output.
4011
4019
 
4020
+ ##### Book page breaks
4021
+
4022
+ Write `--- {.page-break}` on its own line between blocks, with blank lines around it:
4023
+
4024
+ ```markdown
4025
+ The first passage ends here.
4026
+
4027
+ --- {.page-break}
4028
+
4029
+ The next passage starts on a fresh book page.
4030
+ ```
4031
+
4032
+ The book starts a fresh page without drawing a rule. If the marker already
4033
+ falls at the top of a page, it adds no blank page. The website, Foundry and
4034
+ both editor preview views omit the marker. Plain `---`, `***` and `___` keep
4035
+ their ordinary horizontal rules. Code blocks and verse divs keep the marker
4036
+ as literal text.
4037
+
4038
+ Only `---` accepts `.page-break`; `*** {.page-break}` and `___ {.page-break}`
4039
+ are findings. Other classes, ids and attributes on an attributed `---` line
4040
+ are findings too. A page break stands between blocks at the top level of a
4041
+ note: putting one inside a list, block quote, alert, secret block or fenced
4042
+ div is a finding at the marker's line and column.
4043
+
4012
4044
  ##### Captions and numbered references
4013
4045
 
4014
4046
  Write a caption on its own line **before** the item it describes, with a blank line before and after the caption. `:` produces an unnumbered caption; `:@` produces a numbered caption. One or more spaces separate the marker from its text. An optional attribute block at the end accepts an identifier, classes and named attributes:
@@ -4922,10 +4954,11 @@ language:
4922
4954
  - **a map of `gender` words** to a pattern or a list of them, for a tongue
4923
4955
  whose forms differ by gender. A gender with no pattern of its own takes
4924
4956
  every pattern stated.
4925
- - `given` maps a `gender` word to a list of given names. `female` and `male`
4926
- are required. `{given}` draws from the list for the person's gender, or from
4927
- every given list together where that gender has none. `{given.<gender>}`
4928
- draws from that gender's list whatever the person's gender, as a patronymic
4957
+ - `given` is one list of names for everyone, or a map of `gender` words to
4958
+ lists. A shared list supplies every gender. A map requires `female` and
4959
+ `male`; `{given}` draws from the person's gender's list, or every given list
4960
+ together where that gender has none. `{given.<gender>}` draws from the shared
4961
+ list or that gender's list whatever the person's gender, as a patronymic
4929
4962
  draws a father's name.
4930
4963
  - `en` holds English names, in groups of lists. A group is named by letters,
4931
4964
  digits and underscores, beginning with a letter, and a pattern reaches it by
@@ -4941,6 +4974,18 @@ Each placeholder draws one name from its list, each name as likely as any
4941
4974
  other, and a drawn name takes one of its gender's patterns, each as likely as
4942
4975
  any other.
4943
4976
 
4977
+ A tongue whose given names suit anyone states one list:
4978
+
4979
+ ```yaml
4980
+ data:
4981
+ names:
4982
+ pattern: "{given} {clan}"
4983
+ given: [Nara, Taro]
4984
+ clan: [Mori, Sela]
4985
+ ```
4986
+
4987
+ `Nara Mori` and `Taro Sela` fit every gender; each draw uses the same list.
4988
+
4944
4989
  A tongue whose people are named by standing states each form in a list:
4945
4990
 
4946
4991
  ```yaml
@@ -6074,7 +6119,7 @@ SoHL sets `system.quantity` to `1` for each compendium item.
6074
6119
  | `parentSkill` | `Address` | Parent skill this skill specializes |
6075
6120
  | `names` | `{ pattern, given, en?, <name>... }` | `language` only: how its speakers are named, for `package-build npc new` — see [name lists](#name-lists) |
6076
6121
  | `names.pattern` | `string`, `string[]` or map of `gender` words to either, required | Placeholders such as `{given}`, `{given.male}` and `{clan}`, each naming a list, with literal text around them; a list states several forms, a map each gender's forms |
6077
- | `names.given` | map of `gender` words to lists of names, required | Given names by gender; `female` and `male` are required |
6122
+ | `names.given` | list of names or map of `gender` words to lists, required | One shared list for every gender, or a map requiring `female` and `male` |
6078
6123
  | `names.given.<name>` | `string[]` | The given names of one gender |
6079
6124
  | `names.en` | map of list names to lists of English names | English names, never encoded in the tongue's script; a pattern draws from one list as `{en.<list>}` |
6080
6125
  | `names.en.<name>` | `string[]` | A list of English names, such as `epithet` |
@@ -6082,7 +6127,7 @@ SoHL sets `system.quantity` to `1` for each compendium item.
6082
6127
  | `family` | `Address` | `language` only: the parent language, naming a skill note with `subType: language`; this language reads the parent's words as its own — see [a language's words](#a-languages-words) |
6083
6128
  | `abbreviation` | `string` | `language` only: the tag a dictionary prints for it, as `Prov.`; its full name where unstated — see [dictionaries and glossaries](#dictionaries-and-glossaries) |
6084
6129
  | `core` | `boolean` | `language` only: `true` where the language must cover the [core vocabulary](#the-core-vocabulary) |
6085
- | `roman` | literal NFC string, may be empty | Exact permitted Roman source characters, including both cases, accents, digits and punctuation; required on each configured language note. No family inheritance or range syntax. |
6130
+ | `roman` | literal NFC string, may be empty | Additions: letters in accepted cases, accents, catch signs and digits. Listing shared punctuation or whitespace is an error. Required per configured language note; no inheritance or ranges. |
6086
6131
  | `grammar` | `{ plural?, verbs?, agreement?, participles? }` | Optional noun plurals, one verb pattern, agreement and participles; supplied shapes only are checked |
6087
6132
  | `grammar.plural` | `{ ending, becomes }[]` | Literal suffix replacements in authored order; the first match wins |
6088
6133
  | `grammar.plural[].ending` | `string`, required, may be empty | Bare suffix to replace; empty matches every noun |
@@ -6652,8 +6697,13 @@ meaning, `built` may name its existing pieces, and `concept` may name every
6652
6697
  concept those senses support. The report counts declared ids; it does not
6653
6698
  prove an expression's meaning or infer coverage from grammar or untagged prose.
6654
6699
 
6655
- `package-build core` is the report. A concept no word serves is never a lint
6656
- finding; the report lists it so the language can be filled in. See
6700
+ `package-build core` measures only language notes that state `data.core: true`.
6701
+ It ends with `Not covered (no data.core): …`, naming the other language notes
6702
+ in name order, when any exist. The JSON report includes the same names in its
6703
+ `notCovered` array. `--language <name>` measures that language whether or not
6704
+ it states `data.core`, with an empty `notCovered` array and no skip line.
6705
+ A concept no word serves is never a lint finding; the report lists it so the
6706
+ language can be filled in. See
6657
6707
  [`package-build core`](../commands.md#package-build-core).
6658
6708
 
6659
6709
  #### A language's register, sounds and history
@@ -454,44 +454,44 @@ These fields are accepted by every note type. A field's value can still be irrel
454
454
 
455
455
  **Subtypes:** `social`, `nature`, `craft`, `lore`, `language`, `script`, `mystical`, `physical`, `combat`, `combattechnique`.
456
456
 
457
- | Field | Shape | Meaning |
458
- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
459
- | `data.icon` | an Address | The document's profile art — an `icon` address, resolved into `img`. |
460
- | `data.templatePriority` | number | Template priority; unset means the note is not a template. |
461
- | `data.masteryLevel` | number | Mastery before any modifier. |
462
- | `data.parentSkill` | an Address | The skill this one specialises. |
463
- | `data.names` | a language's name lists | How this language's speakers are named, for `package-build npc new`: `pattern` builds a name from placeholders such as "{given} {clan}" and literal text, `given` maps a gender to a list of given names, `female` and `male` required, and `en` holds groups of English names, and every other key is a list of names a placeholder of the same name draws from. A language skill note only. See [name lists](format-details.md#name-lists). |
464
- | `data.family` | an Address | The parent language, as a language skill note: this language reads the parent's words, and its parents' in turn, as its own. A language skill note only. See [a language's words](format-details.md#a-languages-words). |
465
- | `data.abbreviation` | a string, such as `Prov.` | The tag a dictionary prints for the language, beside an entry's class and in an etymology, as `Prov.`; the language's full name where unstated. A language skill note only. See [a language's words](format-details.md#a-languages-words). |
466
- | `data.core` | `true` or `false` | Whether the language must cover the core vocabulary: `true` lists it in the `package-build core` report. A language skill note only. See [the core vocabulary](format-details.md#the-core-vocabulary). |
467
- | `data.classModifiers` | list of word class modifiers | The class modifiers the language uses, from the master word classes. A word's class may carry only these. Empty when unstated. A language skill note only. |
468
- | `data.fields` | list of `{ name, holds }` | The fields the language's words are grouped under. A language skill note only. |
469
- | `data.words` | list of `{ form, class, field, gloss, … }` | The words this language states, one map each, in authored order; its automatic dictionary sorts them alphabetically. A language skill note only. See [a language's words](format-details.md#a-languages-words). |
470
- | `data.affixes` | list of `{ form, gloss, attaches?, examples? }` | The affixes a word's `built` pieces may name, each with its gloss. A language skill note only. |
471
- | `data.roman` | a literal NFC string of additional source characters, including exact letter case and digits | The additional Roman source characters the language permits, each once. Standard punctuation and structural whitespace remain available. No ranges, case expansion or inherited inventory. An empty string declares no additions. Required on each configured language note. |
472
- | `data.grammar` | a map — `{ plural?, verbs?, agreement?, participles?, native? }` | Optional noun plurals, one verb pattern, agreement, participles and the endings' native writings, for dictionary presentation, language word checks and encodings. Only the shape of supplied keys is checked; no word must follow or state a rule. A language skill note only. |
473
- | `data.tongues` | list of `{ tag, meaning, language? }` | The tags a register row's `tongue` may name, each with what it means. A language skill note only. |
474
- | `data.register` | list of `{ name, tongue?, kind?, built?, gloss?, why? }` | Every name the setting gives in the language's reach, with its tongue and pieces; the notes carrying each name are derived. A language skill note only. |
475
- | `data.retired` | list of `{ form, instead?, why }` | The forms the setting does not write, and why. A language skill note only. |
476
- | `data.scope` | a map — `{ folders?, cultures?, lore?, parents?, addresses?, paths?, types?, skip? }` | The notes the register covers. A language skill note only. |
477
- | `data.sounds` | a map — `{ letters?, edges?, clusters?, stress?, finals?, syllables?, joining?, joins?, marks?, skeleton?, frames?, readPast?, mutations? }` | The letters the language writes and the rules its words keep. A language skill note only. |
478
- | `data.soundChanges` | list of `{ name, from?, to?, takes?, steps }` | Named sets of ordered changes, each making one tongue's words from another's. A language skill note only. |
479
- | `data.elements` | list of `{ form, variants?, position?, kind?, sense, examples?, word?, joining? }` | The elements the language's names are built from. A language skill note only. |
480
- | `data.roots` | list of `{ root, sense, field? }` | The consonantal roots the language's words are set from. A language skill note only. |
481
- | `data.cognates` | list of `{ forms, proto?, gloss }` | Sets of forms one older word takes in several languages, stated on the eldest. A language skill note only. |
482
- | `data.callingNames` | a map — `{ closings, minSyllables }` | The short name a long given name is called by. A language skill note only. |
483
- | `data.older` | list of `{ form, gloss?, layer? }` | The words and names older than the language's rules, which no sound or name rule holds. A language skill note only. |
484
- | `data.negation` | a map — `{ particle, mutation? }` | How a verb is negated. A language skill note only. |
485
- | `data.naming` | a map — `{ shapes?, edges?, draws?, heldFrom?, nearName? }` | The shapes, edges and sources the language's names keep beyond its words' rules. A language skill note only. |
486
- | `data.emphasis` | `words`, `rubric` or `overline` | How the language marks a stressed word, over its script's `emphasis`: `rubric` or `overline` sets `*…*` and `**…**` so in a run of the language, and `words` marks it with a word, so either is a finding. Only a skill of `subType: language` states it. |
487
- | `data.script` | a map — `{ fonts, emphasis, direction? }` | A script note's faces, how it marks emphasis and the direction it is written in. Only a skill of `subType: script` states it. |
457
+ | Field | Shape | Meaning |
458
+ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
459
+ | `data.icon` | an Address | The document's profile art — an `icon` address, resolved into `img`. |
460
+ | `data.templatePriority` | number | Template priority; unset means the note is not a template. |
461
+ | `data.masteryLevel` | number | Mastery before any modifier. |
462
+ | `data.parentSkill` | an Address | The skill this one specialises. |
463
+ | `data.names` | a language's name lists | How this language's speakers are named, for `package-build npc new`: `pattern` builds a name from placeholders such as "{given} {clan}" and literal text, `given` is one list of given names for everyone or a map by gender with `female` and `male` required, and `en` holds groups of English names, and every other key is a list of names a placeholder of the same name draws from. A language skill note only. See [name lists](format-details.md#name-lists). |
464
+ | `data.family` | an Address | The parent language, as a language skill note: this language reads the parent's words, and its parents' in turn, as its own. A language skill note only. See [a language's words](format-details.md#a-languages-words). |
465
+ | `data.abbreviation` | a string, such as `Prov.` | The tag a dictionary prints for the language, beside an entry's class and in an etymology, as `Prov.`; the language's full name where unstated. A language skill note only. See [a language's words](format-details.md#a-languages-words). |
466
+ | `data.core` | `true` or `false` | Whether the language must cover the core vocabulary: `true` lists it in the `package-build core` report. A language skill note only. See [the core vocabulary](format-details.md#the-core-vocabulary). |
467
+ | `data.classModifiers` | list of word class modifiers | The class modifiers the language uses, from the master word classes. A word's class may carry only these. Empty when unstated. A language skill note only. |
468
+ | `data.fields` | list of `{ name, holds }` | The fields the language's words are grouped under. A language skill note only. |
469
+ | `data.words` | list of `{ form, class, field, gloss, … }` | The words this language states, one map each, in authored order; its automatic dictionary sorts them alphabetically. A language skill note only. See [a language's words](format-details.md#a-languages-words). |
470
+ | `data.affixes` | list of `{ form, gloss, attaches?, examples? }` | The affixes a word's `built` pieces may name, each with its gloss. A language skill note only. |
471
+ | `data.roman` | a literal NFC string of additional source characters, including exact letter case and digits | The additional Roman source characters the language permits, each once. Standard punctuation and structural whitespace remain available. No ranges, case expansion or inherited inventory. An empty string declares no additions. Required on each configured language note. |
472
+ | `data.grammar` | a map — `{ plural?, verbs?, agreement?, participles?, native? }` | Optional noun plurals, one verb pattern, agreement, participles and the endings' native writings, for dictionary presentation, language word checks and encodings. Only the shape of supplied keys is checked; no word must follow or state a rule. A language skill note only. |
473
+ | `data.tongues` | list of `{ tag, meaning, language? }` | The tags a register row's `tongue` may name, each with what it means. A language skill note only. |
474
+ | `data.register` | list of `{ name, tongue?, kind?, built?, gloss?, why? }` | Every name the setting gives in the language's reach, with its tongue and pieces; the notes carrying each name are derived. A language skill note only. |
475
+ | `data.retired` | list of `{ form, instead?, why }` | The forms the setting does not write, and why. A language skill note only. |
476
+ | `data.scope` | a map — `{ folders?, cultures?, lore?, parents?, addresses?, paths?, types?, skip? }` | The notes the register covers. A language skill note only. |
477
+ | `data.sounds` | a map — `{ letters?, edges?, clusters?, stress?, finals?, syllables?, joining?, joins?, marks?, skeleton?, frames?, readPast?, mutations? }` | The letters the language writes and the rules its words keep. A language skill note only. |
478
+ | `data.soundChanges` | list of `{ name, from?, to?, takes?, steps }` | Named sets of ordered changes, each making one tongue's words from another's. A language skill note only. |
479
+ | `data.elements` | list of `{ form, variants?, position?, kind?, sense, examples?, word?, joining? }` | The elements the language's names are built from. A language skill note only. |
480
+ | `data.roots` | list of `{ root, sense, field? }` | The consonantal roots the language's words are set from. A language skill note only. |
481
+ | `data.cognates` | list of `{ forms, proto?, gloss }` | Sets of forms one older word takes in several languages, stated on the eldest. A language skill note only. |
482
+ | `data.callingNames` | a map — `{ closings, minSyllables }` | The short name a long given name is called by. A language skill note only. |
483
+ | `data.older` | list of `{ form, gloss?, layer? }` | The words and names older than the language's rules, which no sound or name rule holds. A language skill note only. |
484
+ | `data.negation` | a map — `{ particle, mutation? }` | How a verb is negated. A language skill note only. |
485
+ | `data.naming` | a map — `{ shapes?, edges?, draws?, heldFrom?, nearName? }` | The shapes, edges and sources the language's names keep beyond its words' rules. A language skill note only. |
486
+ | `data.emphasis` | `words`, `rubric` or `overline` | How the language marks a stressed word, over its script's `emphasis`: `rubric` or `overline` sets `*…*` and `**…**` so in a run of the language, and `words` marks it with a word, so either is a finding. Only a skill of `subType: language` states it. |
487
+ | `data.script` | a map — `{ fonts, emphasis, direction? }` | A script note's faces, how it marks emphasis and the direction it is written in. Only a skill of `subType: script` states it. |
488
488
 
489
489
  **Inner keys** — a key a field's value does not declare is an error at its own line and column.
490
490
 
491
491
  | Key | Shape | Required | Meaning |
492
492
  | ----------------------------------------- | --------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
493
493
  | `data.names.pattern` | a pattern such as "{given} {clan}", a list of them, or a map of genders to either | yes | How a name is built: each `{list}` placeholder is filled from the list of that name, `{given.<gender>}` from that gender's given names, `{en.<list>}` from a list of English names, and the text around them is written as it stands. A list states every form a name may take; a map keyed by gender states each gender's forms, and a gender with none takes every pattern stated. |
494
- | `data.names.given` | a map of genders to lists of names | yes | Given names by gender word. A gender with no list draws from every given list together. |
494
+ | `data.names.given` | a list of names or a map of genders to lists of names | yes | One shared list supplies every gender. A gender map requires female and male; a gender with no list draws from every given list together. |
495
495
  | `data.names.en` | a map of list names to lists of English names | no | English names, never encoded in the tongue's script. A pattern draws from one list as `{en.<list>}`, as `{en.epithet}` does. |
496
496
  | `data.fields[].name` | a string | yes | The field's name, which a word's `field` names. |
497
497
  | `data.fields[].holds` | a string | yes | What the field holds. |
package/e2e.mjs CHANGED
@@ -799,7 +799,7 @@ export function suiteExecutables(command) {
799
799
  }
800
800
 
801
801
  /** The Chrome for Testing build shared by local and CI Cypress runs. */
802
- export const CHROME_BUILD_ID = "158.0.8122.0";
802
+ export const CHROME_BUILD_ID = "159.0.8136.0";
803
803
 
804
804
  /**
805
805
  * The browser cache survives reseeding and container teardown.
@@ -61,6 +61,7 @@
61
61
  * @module
62
62
  */
63
63
 
64
+ import { scanPageBreaks } from "./page-breaks.mjs";
64
65
  import { scanAlerts } from "./content-alerts.mjs";
65
66
  import { authoredFrontmatter } from "./index-records.mjs";
66
67
  import { markupAnchorFindings } from "./anchors.mjs";
@@ -697,6 +698,7 @@ export class BasePackCompiler {
697
698
  for (const secretError of [
698
699
  ...(scanned?.blocks ?? scanBlocks(body)).errors,
699
700
  ...(scanned?.alerts ?? scanAlerts(body)).errors,
701
+ ...scanPageBreaks(body).errors,
700
702
  ...(scanned?.spans ?? scanSpans(body)).errors,
701
703
  ...scanRuns(body, this.corpus?.runs, syntax).errors,
702
704
  ...markupAnchorFindings(body, syntax),
@@ -42,6 +42,7 @@ import { indexRecordsFor } from "./content-index.mjs";
42
42
  import { buildContentLinkIndex } from "./helpers.mjs";
43
43
  import { loadPackConfig } from "./pack-config.mjs";
44
44
  import { runContext } from "./content-scripts.mjs";
45
+ import { resolveReference } from "./wikilinks.mjs";
45
46
  import { prepareTreeSqlTables } from "./sql-tables.mjs";
46
47
 
47
48
  /**
@@ -99,13 +100,16 @@ export async function buildCompileCorpus({
99
100
  const corpusOptions = { config: resolved, records };
100
101
  const sqlOptions = { ...corpusOptions, skipDirectories, parsed };
101
102
  const sqlTables = await prepareTreeSqlTables(contentBase, sqlOptions);
103
+ const linkIndex = buildContentLinkIndex(contentBase, router, { ...corpusOptions, sqlTables });
102
104
  return {
103
105
  currentDate: resolved.currentDate,
104
- runs: runContext(resolved, records),
106
+ runs: runContext(resolved, records, {
107
+ referenceName: (target) => resolveReference(linkIndex, target)?.name,
108
+ }),
105
109
  records,
106
110
  parsed,
107
111
  problems: collected,
108
- linkIndex: buildContentLinkIndex(contentBase, router, { ...corpusOptions, sqlTables }),
112
+ linkIndex,
109
113
  sqlTables,
110
114
  };
111
115
  }
@@ -177,7 +177,7 @@ export const NOTATION = Object.freeze(
177
177
  */
178
178
  const SCRIPT_BLOCKS = Object.freeze([
179
179
  [0xf0000, 0xf2fff], // plane 15: Pirzath, Thuravark, Semaron, the reserved alphabets, Teng Chou
180
- [0xf3000, 0xf3fff], // Ch'alish P'ilam Common
180
+ [0xf3000, 0xf4fff], // Ch'alish P'ilam Common and Ceremonial
181
181
  [0x1e960, 0x1ec6f], // Zethu overflow, Qalezu, Zethu
182
182
  [0x1ecc0, 0x1ecff], // Qalezu catch-before forms
183
183
  [0x1ed50, 0x1ed7f], // Veldikko
@@ -28,6 +28,7 @@
28
28
  * | quotes, dashes and ellipses | as written | typographic | as written | as written, set by Typst |
29
29
  * | raw HTML in a note | passed through | passed through | shown as text | shown as text |
30
30
  * | a name's first use | bold | bold | bold, with an index | bold |
31
+ * | `--- {.page-break}` | omitted | omitted | omitted | a weak page break |
31
32
  * | the output | HTML | HTML | HTML | the token stream, for the Typst stage |
32
33
  *
33
34
  * A target differs from the others only in that table. The shared plugins are
@@ -42,6 +43,8 @@
42
43
  * {@link Renderer#prepare} then writes the constructs markdown-it has no
43
44
  * syntax for as HTML between blank lines, in this order:
44
45
  *
46
+ * Valid authored page breaks are omitted before these stages.
47
+ *
45
48
  * 1. **Marked text** — a run's language, face and mapping, from the
46
49
  * configuration's script notes.
47
50
  * 2. **Captioned items** — each figure's wrapper and label. Its number, kind
@@ -64,6 +67,7 @@
64
67
  * @module
65
68
  */
66
69
 
70
+ import { omitPageBreaks, pageBreakPlugin } from "./page-breaks.mjs";
67
71
  import MarkdownIt from "markdown-it";
68
72
  import footnotePlugin from "markdown-it-footnote";
69
73
  import deflistPlugin from "markdown-it-deflist";
@@ -122,7 +126,14 @@ function parseVerseContent(token, md, env, region) {
122
126
  const inline = token.type === "verse_inline";
123
127
  const paired = inline ? undefined : env.verses?.get(body);
124
128
  const footnotes = new Set(Object.keys(env.footnotes?.refs ?? {}).map((key) => key.slice(1)));
125
- const model = paired?.model ?? region.model ?? parseVerse(body, { inline, footnotes });
129
+ const model =
130
+ paired?.model ??
131
+ region.model ??
132
+ parseVerse(region.logicalBody ?? body, {
133
+ inline,
134
+ footnotes,
135
+ sourceOffsets: region.bodyOffsets,
136
+ });
126
137
  token.meta ??= {};
127
138
  token.meta.verse = region;
128
139
  token.meta.versePaired = paired;
@@ -395,6 +406,7 @@ export function createRenderer(target, options = {}) {
395
406
 
396
407
  use("footnotes", footnotePlugin);
397
408
  if (book) {
409
+ use("page-breaks", pageBreakPlugin);
398
410
  use("alerts", alertMarkdownPlugin);
399
411
  use("spans", spanMarkdownPlugin, { verseInline: quotedVerse });
400
412
  use("verse-div", verseDivPlugin);
@@ -442,7 +454,7 @@ export function createRenderer(target, options = {}) {
442
454
 
443
455
  /** Write the constructs markdown-it has no syntax for as HTML. */
444
456
  const prepare = (markdown, env = {}) => {
445
- let text = String(markdown ?? "");
457
+ let text = omitPageBreaks(String(markdown ?? ""));
446
458
  if (preview) text = sourceMalformedAlerts(text);
447
459
  let verses = env.verses;
448
460
  if (env.runs) {
@@ -74,6 +74,9 @@ import { plainRomanVerse } from "./roman-verse.mjs";
74
74
  import { encodeText } from "./encoding.mjs";
75
75
  import { WIKILINK } from "./wikilink-syntax.mjs";
76
76
  import { matchAllOutsideCode } from "./code-fences.mjs";
77
+ import { noteAddressOf } from "./word-rows.mjs";
78
+ import { isNoteRecord } from "./index-records.mjs";
79
+ import { parseAddress, isAddressTuple, renderAddress } from "./address.mjs";
77
80
  import { parseExtensionAttributes } from "./extension-attributes.mjs";
78
81
 
79
82
  /** The keys this module reads, which no construct passes through as written. */
@@ -118,6 +121,7 @@ const CONFIG = "package-build.config.yaml";
118
121
  * @property {ReadonlyMap<string, import("./verse-metre.mjs").MetreBook>} [metreBooks] -
119
122
  * What each configured language's verse is counted by, from
120
123
  * {@link module:engine/verse-metre.metreBooksFromRecords}.
124
+ * @property {(target: string) => string|undefined} [referenceName] - An indexed wikilink target's visible name.
121
125
  * @property {ReadonlyMap<string, import("./roman-characters.mjs").RomanInventory>} [romanInventories] -
122
126
  * Exact Roman source inventories by configured alias and BCP 47 tag.
123
127
  * @property {ReadonlyMap<string, import("./script-emphasis.mjs").Emphasis>} [emphases] -
@@ -164,12 +168,16 @@ const withEncodings = new WeakMap();
164
168
  * resolved configuration.
165
169
  * @param {Iterable<Record<string, any>>} [records] - The content index's
166
170
  * records, whose language notes supply lexical writings and script choices.
171
+ * @param {{referenceName?: (target: string) => string|undefined}} [options] -
172
+ * The rendering or validation index's own target-name lookup.
167
173
  * @returns {RunContext} Its languages, the faces installed where it sits, and
168
174
  * the encodings. A fonts package that cannot be read gives no faces; a run naming
169
175
  * one is reported.
170
176
  */
171
- export function runContext(config, records) {
172
- if (!config) return {};
177
+ export function runContext(config, records, { referenceName: indexedName } = {}) {
178
+ const withReferences = (context) =>
179
+ indexedName ? Object.freeze({ ...context, referenceName: indexedName }) : context;
180
+ if (!config) return withReferences({});
173
181
  let base = contexts.get(config);
174
182
  if (!base) {
175
183
  let faces;
@@ -192,14 +200,41 @@ export function runContext(config, records) {
192
200
  });
193
201
  contexts.set(config, base);
194
202
  }
195
- if (!records || typeof records !== "object") return base;
203
+ if (!records || typeof records !== "object") return withReferences(base);
196
204
  let byRecords = withEncodings.get(config);
197
205
  if (!byRecords) withEncodings.set(config, (byRecords = new WeakMap()));
198
206
  const known = byRecords.get(records);
199
- if (known) return known;
207
+ if (known) return withReferences(known);
200
208
  const loaded = encodingsFromRecords(records, config, base.fonts);
201
209
  const encodings = loaded.encodings;
202
210
  const languageRecords = languageRecordsByName(records, config);
211
+ const referenceNames = new Map();
212
+ for (const record of records) {
213
+ if (!isNoteRecord(record)) continue;
214
+ const name = record.name?.full ?? record.name;
215
+ if (typeof name !== "string") continue;
216
+ const note = noteAddressOf(record);
217
+ if (note) referenceNames.set(note, name);
218
+ const canonical = record.address?.canonical;
219
+ if (canonical)
220
+ referenceNames.set(
221
+ isAddressTuple(canonical) ? renderAddress(canonical) : canonical,
222
+ name,
223
+ );
224
+ }
225
+ const referenceTypes = new Set([...records].map((record) => record.type));
226
+ const referenceName = (target) => {
227
+ const address = parseAddress(
228
+ target,
229
+ {
230
+ package: config.contentPackage,
231
+ system: "note",
232
+ types: referenceTypes,
233
+ },
234
+ { declared: true },
235
+ );
236
+ return isAddressTuple(address) ? referenceNames.get(renderAddress(address)) : undefined;
237
+ };
203
238
  const context = Object.freeze({
204
239
  ...base,
205
240
  encodings,
@@ -211,12 +246,13 @@ export function runContext(config, records) {
211
246
  wordBooks: wordBooksFromRecords(records, config),
212
247
  metres: metresFromRecords(records),
213
248
  metreBooks: metreBooksFromRecords(records, config),
249
+ referenceName,
214
250
  emphases: emphasesFromRecords(records),
215
251
  languageEmphases: languageEmphasesOf(languageRecords),
216
252
  directions: directionsFromRecords(records),
217
253
  });
218
254
  byRecords.set(records, context);
219
- return context;
255
+ return withReferences(context);
220
256
  }
221
257
 
222
258
  /**
@@ -2056,6 +2092,7 @@ export function scanRuns(source, ctx = {}, syntax) {
2056
2092
  languages: ctx.languages,
2057
2093
  metreBooks: ctx.metreBooks,
2058
2094
  lexicons: ctx.lexicons,
2095
+ referenceName: ctx.referenceName,
2059
2096
  attributeColumns,
2060
2097
  });
2061
2098
  const merged = (found, extra) =>
@@ -2715,7 +2752,8 @@ export function renderRuns(source, ctx = {}, syntax, options = {}) {
2715
2752
  ...(detail.face?.direction === "rtl" ? { dir: "rtl" } : {}),
2716
2753
  ...(search ? { "data-pagefind-ignore": "true" } : {}),
2717
2754
  };
2718
- const marker = `<!--verse-pair:${verses.size + 1}-->${poem.inline ? "" : "\n"}`;
2755
+ const prefix = poem.inline ? "" : block.body.slice(0, block.bodyOffsets[0]);
2756
+ const marker = `${prefix}<!--verse-pair:${verses.size + 1}-->${poem.inline ? "" : "\n"}`;
2719
2757
  const body = marker + block.body;
2720
2758
  verses.set(body, { model, classes, attributes, inline: poem.inline });
2721
2759
  pairedEdits.push({ from: block.bodyStart, to: block.bodyStart, text: marker });