@heroiclands/package-build 25.1.3 → 25.2.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.
Files changed (87) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/assets/styles/content-base.css +0 -9
  3. package/bin/content-commands.mjs +22 -16
  4. package/bin/package-build.mjs +24 -25
  5. package/content-config.mjs +7 -34
  6. package/docs/api.md +38 -21
  7. package/docs/authoring/first-note.md +3 -3
  8. package/docs/authoring/frontmatter.md +30 -2
  9. package/docs/authoring/links-and-markup.md +92 -31
  10. package/docs/commands.md +103 -59
  11. package/docs/configuration.md +10 -45
  12. package/docs/content-format.md +8 -6
  13. package/docs/diagnostics.md +75 -0
  14. package/docs/guides/book.md +3 -3
  15. package/docs/guides/module-project.md +1 -1
  16. package/docs/guides/reference-pages.md +7 -3
  17. package/docs/project-setup.md +3 -0
  18. package/docs/reference/format-details.md +299 -185
  19. package/docs/reference/note-types.md +16 -17
  20. package/docs/source-preview.md +2 -1
  21. package/engine/changelog-group.mjs +84 -173
  22. package/engine/changelog-labels.mjs +73 -0
  23. package/engine/changelog-lint.mjs +168 -44
  24. package/engine/content-expand.mjs +58 -38
  25. package/engine/content-format.yaml +0 -2
  26. package/engine/content-index.mjs +49 -23
  27. package/engine/content-links.mjs +4 -0
  28. package/engine/content-lint.mjs +2 -3
  29. package/engine/content-render.mjs +6 -2
  30. package/engine/culture-parents.mjs +1 -1
  31. package/engine/culture-reach.mjs +10 -1
  32. package/engine/diagnostics.mjs +3 -2
  33. package/engine/dictionary-rows.mjs +3 -1
  34. package/engine/first-use.mjs +301 -87
  35. package/engine/frontmatter-lint.mjs +40 -14
  36. package/engine/glossary-entries.mjs +5 -5
  37. package/engine/glossary-language.mjs +21 -44
  38. package/engine/homepage.mjs +0 -4
  39. package/engine/index-records.mjs +3 -1
  40. package/engine/infobox.mjs +6 -3
  41. package/engine/journals.mjs +14 -14
  42. package/engine/language-names.mjs +3 -2
  43. package/engine/language-rows.mjs +6 -7
  44. package/engine/lexical-sections.mjs +3 -1
  45. package/engine/literature-notes.mjs +4 -76
  46. package/engine/metadata-index.mjs +15 -8
  47. package/engine/name-forms.mjs +99 -0
  48. package/engine/name-refs.mjs +143 -0
  49. package/engine/name-rows.mjs +11 -1
  50. package/engine/names.mjs +27 -22
  51. package/engine/note-addresses.mjs +0 -3
  52. package/engine/note-frontmatter.mjs +3 -5
  53. package/engine/note-language.mjs +348 -0
  54. package/engine/note-vocabulary.mjs +13 -11
  55. package/engine/pdf-index.mjs +3 -2
  56. package/engine/pdf-render.mjs +2 -4
  57. package/engine/preview-source.mjs +15 -8
  58. package/engine/settlement-tables.mjs +4 -3
  59. package/engine/site-build.mjs +2 -7
  60. package/engine/site-search.mjs +3 -5
  61. package/engine/site-theme.mjs +1 -1
  62. package/engine/sql-tables.mjs +29 -14
  63. package/engine/wikilink-syntax.mjs +2 -0
  64. package/engine/word-citations.mjs +3 -3
  65. package/engine/word-rows.mjs +3 -1
  66. package/engine/word-terms.mjs +2 -2
  67. package/package.json +1 -1
  68. package/site-theme/static/css/style.css +0 -4
  69. package/types/engine/changelog-group.d.mts +6 -10
  70. package/types/engine/changelog-labels.d.mts +10 -0
  71. package/types/engine/changelog-lint.d.mts +11 -6
  72. package/types/engine/content-index.d.mts +16 -16
  73. package/types/engine/diagnostics.d.mts +1 -1
  74. package/types/engine/first-use.d.mts +97 -1
  75. package/types/engine/frontmatter-lint.d.mts +9 -2
  76. package/types/engine/glossary-language.d.mts +13 -6
  77. package/types/engine/homepage.d.mts +0 -3
  78. package/types/engine/journals.d.mts +3 -3
  79. package/types/engine/literature-notes.d.mts +2 -27
  80. package/types/engine/name-forms.d.mts +61 -0
  81. package/types/engine/name-refs.d.mts +51 -0
  82. package/types/engine/name-rows.d.mts +4 -0
  83. package/types/engine/names.d.mts +12 -92
  84. package/types/engine/note-frontmatter.d.mts +1 -4
  85. package/types/engine/note-language.d.mts +47 -0
  86. package/types/engine/settlement-tables.d.mts +1 -1
  87. package/types/engine/site-build.d.mts +2 -6
@@ -55,6 +55,7 @@ import { LANGUAGE_COLUMN_TYPES, LANGUAGE_RELATIONS } from "./language-rows.mjs";
55
55
  import { VERB_PERSON_FIELDS } from "./language-terms.mjs";
56
56
  import { renderDictionary, renderGlossary } from "./dictionary-layout.mjs";
57
57
  import { glossaryLanguage } from "./glossary-language.mjs";
58
+ import { languageNotesByCode } from "./note-language.mjs";
58
59
  import { lexicalSectionsFor } from "./lexical-sections.mjs";
59
60
  import { eventViewSections } from "./event-views.mjs";
60
61
  import { appendGeneratedSections, joinSections } from "./generated-sections.mjs";
@@ -466,6 +467,22 @@ export async function openNotesDatabase(
466
467
  system: "note",
467
468
  },
468
469
  stubsExcluded,
470
+ // Each language code's note, for a page whose language is read from
471
+ // its frontmatter.
472
+ languageNotes: languageNotesByCode(encoded, (record) =>
473
+ (
474
+ typeof record.package === "string" &&
475
+ typeof record.type === "string" &&
476
+ typeof record.shortcode === "string"
477
+ ) ?
478
+ renderAddress({
479
+ package: record.package,
480
+ system: "note",
481
+ type: record.type,
482
+ shortcode: record.shortcode,
483
+ })
484
+ : undefined,
485
+ ),
469
486
  async query(sql) {
470
487
  const reader = await connection.runAndReadAll(sql);
471
488
  return {
@@ -1019,7 +1036,7 @@ function toPlain(value) {
1019
1036
  * DuckDB returns an integer as a `BigInt`, which `String()` renders without its
1020
1037
  * `n` — but a struct or a list would stringify as `[object Object]`, so those
1021
1038
  * are refused rather than printed. A list of scalars is joined, because that is
1022
- * what a `tags` or `aliases` column means.
1039
+ * what a `tags` column means.
1023
1040
  *
1024
1041
  * @param {unknown} value - The cell value.
1025
1042
  * @param {string} column - Its column, named in the message.
@@ -1296,20 +1313,17 @@ async function noteLabels(db) {
1296
1313
 
1297
1314
  /**
1298
1315
  * The language a note's frontmatter says it is in: a language note's own
1299
- * Address, else its `data.language`.
1316
+ * Address, else the note of its language — the code the content index derived
1317
+ * for it where the frontmatter is its record, else its own `data.lang`.
1300
1318
  *
1301
- * @param {object|undefined} frontmatter - The note's authored frontmatter.
1302
- * @param {object} db - From {@link openNotesDatabase}, for its Address context.
1319
+ * @param {object|undefined} frontmatter - The note's frontmatter or record.
1320
+ * @param {object} db - From {@link openNotesDatabase}, for its Address context
1321
+ * and its language notes.
1303
1322
  * @returns {string|undefined} The language's Address.
1304
1323
  */
1305
1324
  function frontmatterLanguage(frontmatter, db) {
1306
1325
  if (!frontmatter || typeof frontmatter !== "object") return undefined;
1307
1326
  const context = db.addressContext ?? {};
1308
- const read = (value, type) => {
1309
- if (typeof value !== "string" || !value) return undefined;
1310
- const tuple = parseAddress(value, { ...context, system: "note", type }, { declared: true });
1311
- return tuple.reason ? undefined : renderAddress({ ...tuple, system: "note" });
1312
- };
1313
1327
  if (
1314
1328
  String(frontmatter.type ?? "").toLowerCase() === "skill" &&
1315
1329
  frontmatter.subType === "language" &&
@@ -1322,7 +1336,8 @@ function frontmatterLanguage(frontmatter, db) {
1322
1336
  type: "skill",
1323
1337
  shortcode: frontmatter.shortcode,
1324
1338
  });
1325
- return read(frontmatter.data?.language, "skill");
1339
+ const code = frontmatter.lang ?? frontmatter.data?.lang;
1340
+ return typeof code === "string" ? db.languageNotes?.get(code) : undefined;
1326
1341
  }
1327
1342
 
1328
1343
  /** Evaluate scalar SQL calls before synchronous Markdown renderers run. */
@@ -1535,9 +1550,8 @@ async function attachPreparedAnchors(prepared, records, contentBase, parsed, con
1535
1550
 
1536
1551
  /**
1537
1552
  * The language each note of a tree is in, by its file: a language note itself,
1538
- * a culture its `data.language`, its own or one it inherits, and an
1539
- * affiliation the language its glossary resolves to — see
1540
- * {@link module:engine/glossary-language}.
1553
+ * and every other note the note of the language the content index derived for
1554
+ * it — see {@link module:engine/glossary-language}.
1541
1555
  *
1542
1556
  * @param {object[]} records - Content-index records.
1543
1557
  * @param {string} contentBase - Root of the content tree.
@@ -1566,12 +1580,13 @@ function pageLanguages(records, contentBase) {
1566
1580
  .filter((record) => isNoteRecord(record))
1567
1581
  .map((record, i) => [noteFile(contentBase, record), notes[i]]),
1568
1582
  );
1583
+ const languageNotes = languageNotesByCode(byKey.values(), keyOf);
1569
1584
  return (source) => {
1570
1585
  const record = byFile.get(source);
1571
1586
  if (!record) return undefined;
1572
1587
  return glossaryLanguage(undefined, record, {
1573
1588
  keyOf: (value) => (typeof value === "string" && value ? value : undefined),
1574
- lookup: (key) => byKey.get(key),
1589
+ noteOf: (code) => languageNotes.get(code),
1575
1590
  self: keyOf(record),
1576
1591
  });
1577
1592
  };
@@ -104,6 +104,8 @@ export function parseWikilink(rawInner, literal = "") {
104
104
  const attributes =
105
105
  suffix.startsWith("{") ? parseExtensionAttributes(suffix.slice(1, -1)) : null;
106
106
  if (attributes) attributes.problems.push(...refusedAttributes(attributes.values));
107
+ if (attributes && Object.hasOwn(attributes.values, "ref"))
108
+ attributes.problems.push("a wikilink never takes ref; its address is its ref");
107
109
  return {
108
110
  inner: inner.trim(),
109
111
  target,
@@ -40,6 +40,7 @@
40
40
  import MarkdownIt from "markdown-it";
41
41
 
42
42
  import { isLanguageRecord, noteAddressOf } from "./word-rows.mjs";
43
+ import { nameFormsOf } from "./name-forms.mjs";
43
44
 
44
45
  /** The parser marked uses are read with: CommonMark's emphasis and nothing added. */
45
46
  const PARSER = new MarkdownIt("commonmark");
@@ -101,7 +102,7 @@ export function markedUses(body) {
101
102
 
102
103
  /**
103
104
  * The names a note carries whole, which cite a word they spell exactly: its
104
- * full name and aliases, and an affiliation's rank and office titles.
105
+ * full name and listed forms, and an affiliation's rank and office titles.
105
106
  *
106
107
  * @param {Record<string, any>} record - An index record.
107
108
  * @returns {string[]}
@@ -110,8 +111,7 @@ function namesCarried(record) {
110
111
  const out = [];
111
112
  const name = isMap(record.name) ? record.name : {};
112
113
  if (typeof name.full === "string") out.push(name.full);
113
- for (const alias of Array.isArray(name.aliases) ? name.aliases : [])
114
- if (typeof alias === "string") out.push(alias);
114
+ out.push(...nameFormsOf(name));
115
115
  const governance = isMap(record.data?.governance) ? record.data.governance : {};
116
116
  for (const rank of Array.isArray(governance.ranks) ? governance.ranks : [])
117
117
  if (isMap(rank) && typeof rank.title === "string") out.push(rank.title);
@@ -36,6 +36,7 @@
36
36
 
37
37
  import { renderAddress } from "./address.mjs";
38
38
  import { glossaryLanguage } from "./glossary-language.mjs";
39
+ import { languageNotesByCode } from "./note-language.mjs";
39
40
  import {
40
41
  AFFIX_KEYS,
41
42
  ZERO_AFFIX,
@@ -368,9 +369,10 @@ export function glossaryRows(records, all = records) {
368
369
  const key = noteAddressOf(record);
369
370
  if (key && !byAddress.has(key)) byAddress.set(key, record);
370
371
  }
372
+ const languageNotes = languageNotesByCode(byAddress.values(), noteAddressOf);
371
373
  const reader = {
372
374
  keyOf: (value) => (typeof value === "string" && value ? value : undefined),
373
- lookup: (key) => byAddress.get(key),
375
+ noteOf: (code) => languageNotes.get(code),
374
376
  };
375
377
  const rows = [];
376
378
  for (const record of records) {
@@ -489,8 +489,8 @@ export const GLOSSARY_ENTRY_FIELDS = Object.freeze([
489
489
  kind: "address",
490
490
  shape: "a language skill Address",
491
491
  describe:
492
- "The language the term is in. Unstated, it is the affiliation's language, its " +
493
- "parents', or its culture's, or the culture's own.",
492
+ "The language the term is in. Unstated, it is the note of the language the " +
493
+ "note is in, its `data.lang` as the content index derives it.",
494
494
  }),
495
495
  ]);
496
496
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "25.1.3",
3
+ "version": "25.2.1",
4
4
  "description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package \u2014 content compilation, manifest, localization, staging, bundle, release and deployment.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "type": "module",
@@ -93,10 +93,6 @@ body {
93
93
  -webkit-font-smoothing: antialiased;
94
94
  }
95
95
 
96
- .name {
97
- font-weight: 700;
98
- }
99
-
100
96
  a {
101
97
  color: var(--accent);
102
98
  text-decoration: none;
@@ -1,18 +1,14 @@
1
1
  /**
2
- * `changelog group`: fold the newest release's changeset blocks together by
3
- * their bold label, order them, and file an undeclared one last.
2
+ * `changelog group`: give the newest release its final shape.
4
3
  *
5
- * Every earlier release section is untouched, byte for byte — only the
6
- * first `## <version>` section's `### <Bump> Changes` bodies are rewritten,
7
- * each independently (a label groups within its own bump level, never
8
- * across one). Running this on its own output is a no-op: a release already
9
- * in label order, with each label merged to one block, groups to itself.
4
+ * Every earlier release section is untouched, byte for byte. Running this on
5
+ * its own output is a no-op.
10
6
  *
11
7
  * @param {string} text - The changelog's full contents.
12
8
  * @param {object} [opts]
13
- * @param {readonly string[]|null} [opts.labels] - `changelog.labels`, in
14
- * display order. `null`/absent orders every group by first appearance
15
- * instead, lead paragraph first, and files nothing as unknown.
9
+ * @param {readonly string[]|null} [opts.labels] - the changelog `labels`, in
10
+ * display order. `null`/absent orders by first appearance and reports
11
+ * nothing as undeclared.
16
12
  * @returns {{text: string, findings: Array<{line: number,
17
13
  * severity: "warning", message: string}>}}
18
14
  */
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Read `labels` from the changelog options in `.changeset/config.json`.
3
+ *
4
+ * @param {string} [cwd] - The repository root.
5
+ * @returns {readonly string[]|null} The labels in display order, or `null`
6
+ * when the file, the options or `labels` is absent.
7
+ * @throws {Error} When `config.json` is not JSON, or `labels` is not an array
8
+ * of distinct non-empty strings or names `Miscellaneous`.
9
+ */
10
+ export function readChangelogLabels(cwd?: string): readonly string[] | null;
@@ -77,9 +77,9 @@ export function topLevelBlocks(text: string, codeLines: Set<number>, scaffoldLin
77
77
  *
78
78
  * @param {string} text - The file's full contents, frontmatter included.
79
79
  * @param {object} [opts]
80
- * @param {readonly string[]|null} [opts.labels] - `changelog.labels`, in
81
- * display order, or `null`/absent when the repository declares none —
82
- * {@link checkUnknownLabels} checks nothing in that case.
80
+ * @param {readonly string[]|null} [opts.labels] - the changelog `labels`, in
81
+ * display order, or `null`/absent when the repository declares none, in
82
+ * which case only {@link MISCELLANEOUS} is accepted.
83
83
  * @returns {{findings: Array<{line: number, column?: number,
84
84
  * severity: "error"|"warning", message: string}>}}
85
85
  */
@@ -98,9 +98,9 @@ export function lintChangesetText(text: string, { labels }?: {
98
98
  *
99
99
  * @param {string} text - The changelog's full contents.
100
100
  * @param {object} [opts]
101
- * @param {readonly string[]|null} [opts.labels] - `changelog.labels`, in
102
- * display order, or `null`/absent when the repository declares none —
103
- * {@link checkUnknownLabels} checks nothing in that case.
101
+ * @param {readonly string[]|null} [opts.labels] - the changelog `labels`, in
102
+ * display order, or `null`/absent when the repository declares none, in
103
+ * which case only {@link MISCELLANEOUS} is accepted.
104
104
  * @returns {{findings: Array<{line?: number, column?: number,
105
105
  * severity: "error"|"warning", message: string}>}}
106
106
  */
@@ -135,6 +135,11 @@ export function releaseSectionRange(text: string): {
135
135
  start: number;
136
136
  end: number;
137
137
  } | null;
138
+ /**
139
+ * The label every repository may use without declaring it, for the changes
140
+ * that fit no declared label. `changelog group` writes it last.
141
+ */
142
+ export const MISCELLANEOUS: "Miscellaneous";
138
143
  /**
139
144
  * The finding one rule reports, before its line is mapped into the caller's
140
145
  * file.
@@ -32,30 +32,30 @@ export function noteAddress(frontmatter: Record<string, any>, contentPackage: st
32
32
  canonical: string;
33
33
  } | null;
34
34
  /**
35
- * A note's `name.aliases` reduced to printable 7-bit ASCII, in order.
35
+ * A note's listed name forms reduced to printable 7-bit ASCII, by language.
36
36
  *
37
- * An alias is the name a reader is at least as likely to reach for as the
38
- * canonical one — `Killer Whale` for an orca, `Ice Bear` for a polar bear,
39
- * `Ix'balam` for a jaguar — so anything searching or completing over the index
40
- * has to match them too, and needs the same keyboard-typeable form
41
- * {@link asciiName} gives the primary name.
37
+ * A listed form is a name a reader is at least as likely to reach for as the
38
+ * full one — `Killer Whale` for an orca, a people's own name for the people —
39
+ * so anything searching or completing over the index has to match them too,
40
+ * and needs the same keyboard-typeable form {@link asciiName} gives the full
41
+ * name.
42
42
  *
43
- * Order is the authored order, so a caller can pair an entry with the alias it
44
- * came from. An alias that is not a non-empty string, or that leaves nothing
45
- * printable behind, is dropped rather than left as a hole — the array is a set
46
- * of names to match, and a null in it is not one.
43
+ * Each language keeps its authored order, so a caller can pair an entry with
44
+ * the form it came from. A form that leaves nothing printable behind is
45
+ * dropped rather than left as a hole, and a language left with no form is
46
+ * left out.
47
47
  *
48
- * @param {unknown} aliases - The note's `name.aliases`; may be absent or null.
49
- * @returns {Array<string>} Possibly empty, never null: a note with no aliases
50
- * has an empty set of them, which is a fact rather than a missing value, and
51
- * a consumer iterating it should not have to check first.
48
+ * @param {unknown} name - The note's `name`; may be absent or null.
49
+ * @returns {Record<string, string[]>} Possibly empty, never null: a note
50
+ * listing no form has an empty map, which a consumer iterating it need not
51
+ * check first.
52
52
  */
53
- export function asciiAliases(aliases: unknown): Array<string>;
53
+ export function asciiForms(name: unknown): Record<string, string[]>;
54
54
  /**
55
55
  * Build one index record from a note's frontmatter and its place in the tree.
56
56
  *
57
57
  * The frontmatter as authored, plus what the index derives from it: the package
58
- * it compiles as, its address, ASCII folds of its name and aliases, the anchors
58
+ * it compiles as, its address, ASCII folds of its name and listed forms, the anchors
59
59
  * of its body, its Foundry block, the address of its documentation journal, and
60
60
  * where the file sits within the tree.
61
61
  *
@@ -130,7 +130,7 @@ export function positionInBody(body: string, offset: number, { bodyLine, bodyCol
130
130
  * @param {string} raw - The file's full contents, frontmatter included.
131
131
  * @param {string} key - The top-level frontmatter key.
132
132
  * @param {string} [value] - When given, prefer the occurrence whose line also
133
- * carries this text. A list-valued key (`aliases`) is reported at the entry
133
+ * carries this text. A list-valued key (`tags`) is reported at the entry
134
134
  * that is wrong, not at the key that introduces it.
135
135
  * @param {object} [options] - Options.
136
136
  * @param {boolean} [options.topLevel=false] - Require the key at column 1, so
@@ -1,5 +1,51 @@
1
1
  /**
2
- * Prepare indexed names once, then check only explicit occurrences without reading files.
2
+ * A use of a name in a page's prose.
3
+ *
4
+ * @typedef {object} NameUse
5
+ * @property {number} start - Inclusive UTF-16 offset of the use.
6
+ * @property {number} end - Exclusive UTF-16 offset after it, attributes included.
7
+ * @property {string} key - What the use is counted under.
8
+ * @property {boolean} emphasised - It sits inside authored strong text.
9
+ */
10
+ /**
11
+ * Every use of a name in a body's prose, in source order.
12
+ *
13
+ * @param {string} markdown - A body, as authored or expanded.
14
+ * @param {NameVocabulary} [vocabulary] - What its addresses are read against.
15
+ * @returns {NameUse[]} The uses that count toward a first use.
16
+ */
17
+ export function nameUses(markdown: string, vocabulary?: NameVocabulary): NameUse[];
18
+ /**
19
+ * Mark the first use of each name in a body's prose as bold.
20
+ *
21
+ * Each first use is wrapped in {@link FIRST_USE_OPEN} and
22
+ * {@link FIRST_USE_CLOSE}, which {@link firstUsePlugin} renders as strong
23
+ * text. A first use already inside authored strong text is left as it is.
24
+ *
25
+ * @param {string} markdown - A body's prose, with its generated sections split
26
+ * off and its wikilinks not yet resolved.
27
+ * @param {NameVocabulary} [vocabulary] - What its addresses are read against.
28
+ * @returns {string} The same markdown with each first use marked.
29
+ */
30
+ export function boldFirstUses(markdown: string, vocabulary?: NameVocabulary): string;
31
+ /**
32
+ * Render the first-use marks {@link boldFirstUses} wrote as strong text.
33
+ *
34
+ * A mark in inline text opens or closes a `strong`; a mark anywhere else, as in
35
+ * raw HTML or code, is dropped, so no mark reaches a reader.
36
+ *
37
+ * @param {import("markdown-it")} md - Markdown parser to extend.
38
+ */
39
+ export function firstUsePlugin(md: typeof import("markdown-it")): void;
40
+ /**
41
+ * Prepare indexed names once, then check each note's marked names without
42
+ * reading files.
43
+ *
44
+ * One rule, a warning at the occurrence:
45
+ *
46
+ * - `unlinked-name`: a `.name` span with no `ref` names an indexed note, which
47
+ * a link or a `ref` ties to its note.
48
+ *
3
49
  * @param {{records: readonly object[], index: object,
4
50
  * catalog?: import("./names.mjs").NameCatalog,
5
51
  * parsed?: Map<string, {raw: string, body: string, bodyLine?: number, bodyColumn?: number}>,
@@ -29,3 +75,53 @@ export function createFirstUseChecker({ records, index, parsed, syntax, catalog,
29
75
  rules: string[];
30
76
  message: string;
31
77
  }>;
78
+ /**
79
+ * Opens a first use in expanded markdown: a Unicode noncharacter, which no
80
+ * authored text, font or encoding uses.
81
+ */
82
+ export const FIRST_USE_OPEN: "\uFDD0";
83
+ /** Closes a first use in expanded markdown. */
84
+ export const FIRST_USE_CLOSE: "\uFDD1";
85
+ /**
86
+ * The addresses a page's names are read against: the tree's types and
87
+ * packages, and the package an address that names none belongs to.
88
+ */
89
+ export type NameVocabulary = {
90
+ /**
91
+ * - Every type an address may name.
92
+ */
93
+ types?: Set<string> | undefined;
94
+ /**
95
+ * - Every package an address may name.
96
+ */
97
+ packages?: Set<string> | undefined;
98
+ /**
99
+ * - Packages with no content index.
100
+ */
101
+ noIndexPackages?: Set<string> | undefined;
102
+ /**
103
+ * - The package the page belongs to.
104
+ */
105
+ contentPackage?: string | undefined;
106
+ };
107
+ /**
108
+ * A use of a name in a page's prose.
109
+ */
110
+ export type NameUse = {
111
+ /**
112
+ * - Inclusive UTF-16 offset of the use.
113
+ */
114
+ start: number;
115
+ /**
116
+ * - Exclusive UTF-16 offset after it, attributes included.
117
+ */
118
+ end: number;
119
+ /**
120
+ * - What the use is counted under.
121
+ */
122
+ key: string;
123
+ /**
124
+ * - It sits inside authored strong text.
125
+ */
126
+ emphasised: boolean;
127
+ };
@@ -54,13 +54,16 @@ export function systemAddressFindings(note: object, { index, schemas, systems }?
54
54
  * configuration through {@link systemBlocksFor}, and this module states no
55
55
  * system name of its own. See {@link DEFAULT_SYSTEM_BLOCKS} for the fallback.
56
56
  * @param {object} [opts.addressContext] - Builder package and Address type vocabulary.
57
+ * @param {Iterable<string>} [opts.languages] - The language codes the
58
+ * configuration's `languages:` declares. A `name` key that is one of them, or
59
+ * `en`, is a list of forms; absent, only `en` is.
57
60
  * @param {readonly string[]} [opts.packs] - The pack names this package
58
61
  * declares, for a `data:` field whose map is keyed by pack. Supplied by the
59
62
  * caller like `schemas` and `vocabulary`, and absent it no claim is made
60
63
  * about those keys.
61
64
  * @returns {object[]} Findings, each with a locator where one is obtainable.
62
65
  */
63
- export function lintNote(note: object, { schemas, index, currentDate, vocabulary, packs, emittedArt, addressContext, systems, }: {
66
+ export function lintNote(note: object, { schemas, index, currentDate, vocabulary, packs, emittedArt, addressContext, languages, systems, }: {
64
67
  schemas: Record<string, readonly object[]>;
65
68
  currentDate?: string | undefined;
66
69
  index?: object | undefined;
@@ -71,6 +74,7 @@ export function lintNote(note: object, { schemas, index, currentDate, vocabulary
71
74
  } | null) | undefined;
72
75
  systems?: Readonly<Record<string, SystemBlockSpec>> | undefined;
73
76
  addressContext?: object | undefined;
77
+ languages?: Iterable<string> | undefined;
74
78
  packs?: readonly string[] | undefined;
75
79
  }): object[];
76
80
  /**
@@ -86,6 +90,8 @@ export function lintNote(note: object, { schemas, index, currentDate, vocabulary
86
90
  * @param {Readonly<Record<string, SystemBlockSpec>>} [opts.systems]
87
91
  * The system blocks to check; see {@link lintNote} and
88
92
  * {@link systemBlocksFor}.
93
+ * @param {Iterable<string>} [opts.languages] - The configured language codes,
94
+ * which a note's `name` lists its forms under; see {@link lintNote}.
89
95
  * @param {readonly string[]} [opts.packs] - The declared pack names; see
90
96
  * {@link lintNote}.
91
97
  * @param {(type: string) => {document: string|null, art: readonly string[]}|null} [opts.emittedArt]
@@ -93,12 +99,13 @@ export function lintNote(note: object, { schemas, index, currentDate, vocabulary
93
99
  * @returns {{findings: object[], notes: number}} The findings, and how many
94
100
  * notes were inspected.
95
101
  */
96
- export function lintFrontmatter(index: object, { schemas, vocabulary, packs, emittedArt, references, systems, currentDate }: {
102
+ export function lintFrontmatter(index: object, { schemas, vocabulary, packs, emittedArt, references, systems, currentDate, languages }: {
97
103
  schemas: Record<string, readonly object[]>;
98
104
  currentDate?: string | undefined;
99
105
  vocabulary?: Record<string, object> | undefined;
100
106
  references?: boolean | undefined;
101
107
  systems?: Readonly<Record<string, SystemBlockSpec>> | undefined;
108
+ languages?: Iterable<string> | undefined;
102
109
  packs?: readonly string[] | undefined;
103
110
  emittedArt?: ((type: string) => {
104
111
  document: string | null;
@@ -5,10 +5,17 @@
5
5
  * @property {(value: unknown, type: string) => string|undefined} keyOf - A
6
6
  * value's canonical Address in the `note` system, read with `type` as the
7
7
  * default type, or `undefined` where it is no Address.
8
- * @property {(key: string) => object|undefined} lookup - The note an Address
9
- * names, or `undefined` where none is known.
8
+ * @property {(code: string) => string|undefined} noteOf - The Address of a
9
+ * language code's note, or `undefined` where no note is configured for it.
10
10
  * @property {string} [self] - The owning note's own Address, for a language.
11
11
  */
12
+ /**
13
+ * The language a note is in, as the content index derived it.
14
+ *
15
+ * @param {object} note - An index record, or a link-index note carrying it.
16
+ * @returns {string|undefined} The code.
17
+ */
18
+ export function noteLang(note: object): string | undefined;
12
19
  /**
13
20
  * The language a glossary entry resolves to.
14
21
  *
@@ -19,7 +26,7 @@
19
26
  * @returns {string|undefined} The language's Address, or `undefined` where the
20
27
  * chain finds none.
21
28
  */
22
- export function glossaryLanguage(entry: object | undefined, owner: object, { keyOf, lookup, self }: GlossaryLanguageReader): string | undefined;
29
+ export function glossaryLanguage(entry: object | undefined, owner: object, { keyOf, noteOf, self }: GlossaryLanguageReader): string | undefined;
23
30
  /**
24
31
  * How a caller reads Addresses and finds notes.
25
32
  */
@@ -31,10 +38,10 @@ export type GlossaryLanguageReader = {
31
38
  */
32
39
  keyOf: (value: unknown, type: string) => string | undefined;
33
40
  /**
34
- * - The note an Address
35
- * names, or `undefined` where none is known.
41
+ * - The Address of a
42
+ * language code's note, or `undefined` where no note is configured for it.
36
43
  */
37
- lookup: (key: string) => object | undefined;
44
+ noteOf: (code: string) => string | undefined;
38
45
  /**
39
46
  * - The owning note's own Address, for a language.
40
47
  */
@@ -140,9 +140,6 @@ export function homepageTitle(fm: object | null | undefined, config: object): st
140
140
  * **No `url` and no `slug`.** The homepage publishes at the package root, so
141
141
  * the page has no address to state.
142
142
  *
143
- * An authored `aliases` is dropped, as it is on every other page: this build
144
- * emits no redirects.
145
- *
146
143
  * `related` is derived like every other page's — what links to the homepage
147
144
  * is a fact about the rest of the tree — so an authored value is dropped and
148
145
  * the caller's block, where it has one, is set in its place.
@@ -3,9 +3,9 @@
3
3
  * code blocks are respected so `# foo` inside ``` or ~~~ blocks doesn't
4
4
  * trigger a split. Content before the first H1 (if non-empty) becomes a
5
5
  * leading page. Each H1 yields a page whose name is the heading
6
- * text (with any `{#anchor-id}` suffix stripped out and surfaced as
7
- * `anchorSlug`). The classes the heading declares come along as `classes`, so
8
- * a surface that honours one has it without reading the suffix again.
6
+ * text without inline markup (with any `{#anchor-id}` suffix stripped out and
7
+ * surfaced as `anchorSlug`). The classes the heading declares come along as
8
+ * `classes`, so a surface that honours one has it without reading the suffix again.
9
9
  *
10
10
  * `leadName` names that leading page. A journal note's is "Introduction",
11
11
  * because it introduces the pages that follow. An item doc's is the item — a
@@ -1,20 +1,5 @@
1
1
  /**
2
- * Require `data.language` to address a `skill` note whose subType is
3
- * `language`.
4
- *
5
- * An Address that does not resolve is the reference check's to report, so
6
- * this speaks only about a target that exists and is the wrong kind.
7
- *
8
- * @param {object} note
9
- * @param {{index?: object}} [options]
10
- * @returns {object[]} The findings.
11
- */
12
- export function checkLanguageChoice(note: object, { index }?: {
13
- index?: object;
14
- }): object[];
15
- /**
16
- * Refuse a literature field on a lore note of any other subType, and
17
- * `data.language` on a lore note that is neither a work nor a culture.
2
+ * Refuse a literature field on a lore note of any other subType.
18
3
  *
19
4
  * @param {object} note
20
5
  * @returns {object[]} One error per key written off the subTypes that take it.
@@ -22,18 +7,8 @@ export function checkLanguageChoice(note: object, { index }?: {
22
7
  export function checkLiteratureNote(note: object): object[];
23
8
  /** The `lore` subType whose notes are works of literature. */
24
9
  export const LITERATURE_SUBTYPE: "literature";
25
- /** The `lore` subTypes that may name a language. */
26
- export const LANGUAGE_SUBTYPES: readonly string[];
27
- /**
28
- * `data.language`: the tongue a work is composed in, the tongue a culture's
29
- * people speak, or the tongue an affiliation keeps. Optional on all three.
30
- *
31
- * @type {object}
32
- */
33
- export const LANGUAGE_FIELD: object;
34
10
  /**
35
- * The fields only a `literature` note may state, beside the shared
36
- * {@link LANGUAGE_FIELD}. Every one is optional.
11
+ * The fields only a `literature` note may state. Every one is optional.
37
12
  *
38
13
  * @type {ReadonlyArray<object>}
39
14
  */