@heroiclands/package-build 22.21.0 → 22.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CHANGELOG.md +191 -0
  2. package/assets/styles/content-base.css +162 -0
  3. package/bin/content-commands.mjs +26 -3
  4. package/bin/package-build.mjs +15 -2
  5. package/ci/content-format-reference.mjs +68 -1
  6. package/config.mjs +26 -0
  7. package/content-config.mjs +73 -4
  8. package/docs/api.md +36 -18
  9. package/docs/authoring/dates-and-calendars.md +4 -2
  10. package/docs/authoring/frontmatter.md +2 -2
  11. package/docs/authoring/links-and-markup.md +237 -28
  12. package/docs/configuration.md +27 -1
  13. package/docs/content-format.md +3 -1
  14. package/docs/guides/reference-pages.md +15 -0
  15. package/docs/reference/format-details.md +224 -46
  16. package/docs/reference/note-types.md +21 -4
  17. package/engine/actor-compiler.mjs +23 -0
  18. package/engine/anchored-sections.mjs +35 -4
  19. package/engine/anchors.mjs +10 -20
  20. package/engine/base-compiler.mjs +86 -21
  21. package/engine/base-styles.mjs +149 -0
  22. package/engine/bundles.mjs +0 -1
  23. package/engine/compendiums.mjs +8 -1
  24. package/engine/content-blocks.mjs +410 -0
  25. package/engine/content-captions.mjs +57 -8
  26. package/engine/content-footnotes.mjs +189 -0
  27. package/engine/content-format-check.mjs +15 -12
  28. package/engine/content-index.mjs +2 -2
  29. package/engine/content-links.mjs +14 -6
  30. package/engine/content-tables.mjs +45 -4
  31. package/engine/content-tasklists.mjs +189 -0
  32. package/engine/docs-checks.mjs +4 -4
  33. package/engine/extension-attributes.mjs +48 -2
  34. package/engine/frontmatter-lint.mjs +47 -64
  35. package/engine/frontmatter.mjs +3 -3
  36. package/engine/heading-attributes.mjs +308 -0
  37. package/engine/helpers.mjs +14 -13
  38. package/engine/homepage.mjs +8 -8
  39. package/engine/index.mjs +3 -0
  40. package/engine/infobox-render.mjs +15 -3
  41. package/engine/infobox.mjs +83 -8
  42. package/engine/journals.mjs +36 -17
  43. package/engine/macros.mjs +1 -1
  44. package/engine/markdown-expressions.mjs +178 -53
  45. package/engine/note-dates.mjs +49 -0
  46. package/engine/note-frontmatter.mjs +7 -16
  47. package/engine/note-vocabulary.mjs +172 -37
  48. package/engine/page-lists.mjs +358 -0
  49. package/engine/pdf-build.mjs +76 -42
  50. package/engine/pdf-render.mjs +422 -49
  51. package/engine/rank-ladder.mjs +198 -0
  52. package/engine/reckoning-markers.mjs +1 -1
  53. package/engine/retired-fields.mjs +8 -13
  54. package/engine/scenes.mjs +10 -3
  55. package/engine/site-build.mjs +41 -12
  56. package/engine/site-config.mjs +10 -1
  57. package/engine/site-index.mjs +18 -2
  58. package/engine/site-preview.mjs +18 -7
  59. package/engine/social-ties.mjs +52 -14
  60. package/engine/sql-tables.mjs +66 -9
  61. package/engine/standings.mjs +89 -6
  62. package/engine/system-block.mjs +12 -14
  63. package/engine/web-wikilinks.mjs +48 -1
  64. package/hm3/actors.mjs +6 -3
  65. package/manifest.mjs +13 -0
  66. package/package.json +1 -1
  67. package/sohl/actors.mjs +22 -99
  68. package/sohl/item-fields.mjs +4 -5
  69. package/sohl/items.mjs +1 -1
  70. package/sohl/note-schemas.mjs +11 -21
  71. package/types/config.d.mts +9 -0
  72. package/types/engine/actor-compiler.d.mts +11 -0
  73. package/types/engine/anchored-sections.d.mts +24 -0
  74. package/types/engine/base-compiler.d.mts +19 -0
  75. package/types/engine/base-styles.d.mts +93 -0
  76. package/types/engine/content-blocks.d.mts +87 -0
  77. package/types/engine/content-captions.d.mts +14 -1
  78. package/types/engine/content-footnotes.d.mts +73 -0
  79. package/types/engine/content-format-check.d.mts +8 -6
  80. package/types/engine/content-tables.d.mts +12 -4
  81. package/types/engine/content-tasklists.d.mts +72 -0
  82. package/types/engine/extension-attributes.d.mts +22 -1
  83. package/types/engine/frontmatter.d.mts +1 -1
  84. package/types/engine/heading-attributes.d.mts +174 -0
  85. package/types/engine/helpers.d.mts +7 -4
  86. package/types/engine/homepage.d.mts +7 -7
  87. package/types/engine/index.d.mts +1 -0
  88. package/types/engine/infobox-render.d.mts +6 -1
  89. package/types/engine/journals.d.mts +14 -7
  90. package/types/engine/markdown-expressions.d.mts +23 -0
  91. package/types/engine/note-frontmatter.d.mts +1 -4
  92. package/types/engine/note-vocabulary.d.mts +82 -6
  93. package/types/engine/page-lists.d.mts +126 -0
  94. package/types/engine/pdf-render.d.mts +72 -2
  95. package/types/engine/rank-ladder.d.mts +32 -0
  96. package/types/engine/retired-fields.d.mts +4 -6
  97. package/types/engine/site-build.d.mts +13 -1
  98. package/types/engine/site-config.d.mts +9 -0
  99. package/types/engine/site-index.d.mts +13 -1
  100. package/types/engine/site-preview.d.mts +1 -1
  101. package/types/engine/sql-tables.d.mts +31 -7
  102. package/types/engine/standings.d.mts +21 -3
  103. package/types/engine/system-block.d.mts +11 -12
  104. package/types/engine/web-wikilinks.d.mts +3 -1
  105. package/types/hm3/actors.d.mts +3 -3
  106. package/types/sohl/actors.d.mts +6 -7
  107. package/engine/content-admonitions.mjs +0 -114
  108. package/engine/content-secrets.mjs +0 -112
  109. package/types/engine/content-admonitions.d.mts +0 -24
  110. package/types/engine/content-secrets.d.mts +0 -17
@@ -1,13 +1,21 @@
1
1
  /**
2
- * Replace prepared SQL fences with Markdown tables while retaining source lines.
3
- * Query execution happens in the asynchronous SQL preparation pass.
2
+ * Replace prepared SQL fences and page lists with Markdown while retaining
3
+ * source lines.
4
+ *
5
+ * Both directives read the content index, so both are answered before this
6
+ * pass runs — query execution in the asynchronous SQL preparation pass, page
7
+ * lists in {@link module:engine/page-lists.preparePageLists} — and each is
8
+ * looked up by the ordinal of its own kind of fence within the body.
9
+ *
4
10
  * @param {string} markdown - The authored body.
5
- * @param {{source?: string, sqlTables?: object[]}} [context] - Prepared results.
11
+ * @param {{source?: string, sqlTables?: object[], pageLists?: object[]}} [context] -
12
+ * Prepared results.
6
13
  * @returns {{markdown: string, errors: object[], warnings: object[], lineMap: object[]}}
7
14
  */
8
- export function expandContentTables(markdown: string, { source, sqlTables }?: {
15
+ export function expandContentTables(markdown: string, { source, sqlTables, pageLists }?: {
9
16
  source?: string;
10
17
  sqlTables?: object[];
18
+ pageLists?: object[];
11
19
  }): {
12
20
  markdown: string;
13
21
  errors: object[];
@@ -0,0 +1,72 @@
1
+ /**
2
+ * What a note opening a list item with `[ ]` or `[x]` is told.
3
+ *
4
+ * @param {string} marker - The matched marker, as written.
5
+ * @returns {string} The message, unpunctuated at the end as a finding is.
6
+ */
7
+ export function taskListMessage(marker: string): string;
8
+ /**
9
+ * Every list item opened with `[ ]` or `[x]` in one note's body.
10
+ *
11
+ * @param {string} body - The note's markdown, without its frontmatter.
12
+ * @param {string} file - The note's path, for the finding.
13
+ * @param {object} [opts]
14
+ * @param {number} [opts.bodyLine=1] - The 1-based file line the body starts on.
15
+ * @param {number} [opts.bodyColumn=1] - The 1-based file column it starts at.
16
+ * @returns {Array<{file: string, line: number, column: number,
17
+ * severity: "warning", message: string}>} One finding per item, in source
18
+ * order.
19
+ */
20
+ export function checkTaskLists(body: string, file: string, { bodyLine, bodyColumn }?: {
21
+ bodyLine?: number | undefined;
22
+ bodyColumn?: number | undefined;
23
+ }): Array<{
24
+ file: string;
25
+ line: number;
26
+ column: number;
27
+ severity: "warning";
28
+ message: string;
29
+ }>;
30
+ /**
31
+ * Walk a content tree and report every list item opened with `[ ]` or `[x]`.
32
+ *
33
+ * The frontmatter fence is taken off first, so what is scanned is the body and
34
+ * the positions are still the file's. A file with no frontmatter is scanned
35
+ * whole: it is not a note, but a stray `.md` in the tree carrying the marker is
36
+ * the same problem for the same reason.
37
+ *
38
+ * A finding names its file **relative to the working directory**, which is
39
+ * where a reader is standing and what `formatDiagnostic` emits. The content
40
+ * root is where the walk starts, not what a path is measured from.
41
+ *
42
+ * @param {string} contentBase - Root of the content tree.
43
+ * @param {object} [opts]
44
+ * @param {readonly string[]} [opts.skipDirectories] - Directory names to ignore
45
+ * in addition to the dot-directories always skipped.
46
+ * @returns {{findings: Array<{file: string, line: number, column: number,
47
+ * severity: "warning", message: string}>, files: number}} The findings, and
48
+ * how many files were read.
49
+ */
50
+ export function lintContentTaskLists(contentBase: string, { skipDirectories }?: {
51
+ skipDirectories?: readonly string[] | undefined;
52
+ }): {
53
+ findings: Array<{
54
+ file: string;
55
+ line: number;
56
+ column: number;
57
+ severity: "warning";
58
+ message: string;
59
+ }>;
60
+ files: number;
61
+ };
62
+ /**
63
+ * A list item's marker, immediately followed by `[ ]` or `[x]` and a space —
64
+ * bulleted (`-`, `*`, `+`) or ordered (`1.` or `1)`), at the start of a line.
65
+ *
66
+ * The leading indentation is a lookbehind rather than part of the match, so a
67
+ * nested item's finding points at the marker itself, not at the line's first
68
+ * column.
69
+ *
70
+ * @type {RegExp}
71
+ */
72
+ export const TASK_LIST_MARKER: RegExp;
@@ -2,9 +2,15 @@
2
2
  * Read the shared braced attribute grammar used by body extensions.
3
3
  * Callers validate which names and classes their extension understands.
4
4
  * @param {string} source - Text inside the braces.
5
+ * @param {object} [options={}]
6
+ * @param {RegExp} [options.idPattern=NAME] - What an `#id` may be named. A
7
+ * construct whose id is published as an HTML `id` admits a digit first and
8
+ * passes a looser pattern; the default is the pattern every other name takes.
5
9
  * @returns {{id: string, classes: string[], values: Record<string, string>, problems: string[]}}
6
10
  */
7
- export function parseExtensionAttributes(source: string): {
11
+ export function parseExtensionAttributes(source: string, { idPattern }?: {
12
+ idPattern?: RegExp | undefined;
13
+ }): {
8
14
  id: string;
9
15
  classes: string[];
10
16
  values: Record<string, string>;
@@ -12,3 +18,18 @@ export function parseExtensionAttributes(source: string): {
12
18
  };
13
19
  /** A Boolean attribute is spelled exactly `true` or `false`. */
14
20
  export function booleanAttribute(value: any, key: any): boolean;
21
+ /**
22
+ * The attribute keys no construct writes onto an element.
23
+ *
24
+ * `id=` and `class=` are refused in favour of `#id` and `.class`, which the
25
+ * grammar already states; a key beginning `on` is an event handler, and nothing
26
+ * authored in a note becomes code on a published page.
27
+ *
28
+ * Shared by every construct that writes an author's attributes onto an element,
29
+ * because a refusal that held on one and not the next would be the hole the
30
+ * other construct is checked for.
31
+ *
32
+ * @param {Record<string, string>} values - The `key=value` attributes parsed.
33
+ * @returns {string[]} One message per refused key, in the order written.
34
+ */
35
+ export function refusedAttributes(values: Record<string, string>): string[];
@@ -137,7 +137,7 @@ export function resolveRelation(fm: object, ctx?: string): Record<string, string
137
137
  *
138
138
  * @param {object} fm - The item frontmatter.
139
139
  * @param {string} [ctx] - Optional label for the error (defaults to the item's
140
- * title/name, else "item").
140
+ * display name, else "item").
141
141
  * @returns {string} The declared subType.
142
142
  * @throws {Error} When `subType` is missing or blank.
143
143
  */
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Read a heading's text and the attribute block it may end with.
3
+ *
4
+ * A suffix that is not an attribute block — a set written in braces, a die
5
+ * expression — is left in the text and reported, so no surface silently prints
6
+ * a heading with words missing and no author is told to fix nothing.
7
+ *
8
+ * **One mistake draws one finding.** Braces holding nothing the grammar
9
+ * recognises are reported once, naming the braces: an author who wrote
10
+ * `{a, b}` meant no attribute block at all, and a complaint per fragment inside
11
+ * it says the same thing twice in words that name neither the heading nor the
12
+ * fix. Braces that *are* an attribute block, carrying one bad attribute beside
13
+ * good ones, relay the grammar's own complaint about that attribute.
14
+ *
15
+ * @param {string} heading - A heading's text, hashes already removed.
16
+ * @returns {{text: string, id: string, classes: string[],
17
+ * values: Record<string, string>, problems: string[], braces: boolean}}
18
+ * `text` is what a surface sets, `braces` whether a suffix was there at all.
19
+ */
20
+ export function splitHeadingAttributes(heading: string): {
21
+ text: string;
22
+ id: string;
23
+ classes: string[];
24
+ values: Record<string, string>;
25
+ problems: string[];
26
+ braces: boolean;
27
+ };
28
+ /**
29
+ * Whether a heading opens a journal page.
30
+ *
31
+ * A Foundry UUID addresses a page and nothing smaller, so a section a link can
32
+ * reach has to be one: a top-level heading starts a page, and so does any
33
+ * heading that names an anchor. {@link module:engine/journals.splitPages}
34
+ * decides *where* a page begins — a captioned heading and a heading inside a
35
+ * fence or a named block are its business — and asks this which headings are
36
+ * eligible, so no caller re-decides it.
37
+ *
38
+ * @param {{level: number, id: string}} heading - From
39
+ * {@link scanHeadingAttributes}, or any `{level, id}` pair.
40
+ * @returns {boolean} Whether the heading opens a page.
41
+ */
42
+ export function opensPage({ level, id }: {
43
+ level: number;
44
+ id: string;
45
+ }): boolean;
46
+ /**
47
+ * Every heading in a body, with what its attribute block declares.
48
+ *
49
+ * Fenced code is skipped: `#` opens a comment in most of what gets fenced, and
50
+ * an example heading in a document is not a heading of it.
51
+ *
52
+ * @param {string} source - A note's markdown body.
53
+ * @param {number} [bodyLine=1] - The 1-based file line the body starts on, so a
54
+ * finding lands where an editor can open it.
55
+ * @returns {{headings: Array<{level: number, line: number, text: string,
56
+ * id: string, classes: string[], values: Record<string, string>}>,
57
+ * errors: Array<{line: number, column: number, message: string}>}}
58
+ */
59
+ export function scanHeadingAttributes(source: string, bodyLine?: number): {
60
+ headings: Array<{
61
+ level: number;
62
+ line: number;
63
+ text: string;
64
+ id: string;
65
+ classes: string[];
66
+ values: Record<string, string>;
67
+ }>;
68
+ errors: Array<{
69
+ line: number;
70
+ column: number;
71
+ message: string;
72
+ }>;
73
+ };
74
+ /**
75
+ * A markdown-it plugin writing a heading's attribute block onto its element.
76
+ *
77
+ * The website's renderer reads the block itself, so the surfaces that use
78
+ * markdown-it read it here and publish the same `id`, the same classes and the
79
+ * same attributes — rather than typesetting the braces an author wrote.
80
+ *
81
+ * @param {object} md - A markdown-it instance.
82
+ * @returns {void}
83
+ */
84
+ export function headingAttributesPlugin(md: object): void;
85
+ /**
86
+ * Parse one line as a heading, and say whether it starts a journal page.
87
+ *
88
+ * The single reading of that rule. {@link module:engine/journals.splitPages}
89
+ * builds its pages from it; `scanBlocks` and `scanCaptions` refuse a heading
90
+ * that starts a page written where it cannot become one. All three ask this, so
91
+ * none of them can disagree about what a heading line means or about what starts
92
+ * a page — and the attribute block comes off here, so a class beside an anchor
93
+ * is not read as part of the anchor by any of them.
94
+ *
95
+ * @param {string} line - One line of a note's body.
96
+ * @returns {{level: number, text: string, id: string, classes: string[],
97
+ * values: Record<string, string>, problems: string[], startsPage: boolean}|null}
98
+ * `null` when the line is not a heading.
99
+ */
100
+ export function parseHeadingLine(line: string): {
101
+ level: number;
102
+ text: string;
103
+ id: string;
104
+ classes: string[];
105
+ values: Record<string, string>;
106
+ problems: string[];
107
+ startsPage: boolean;
108
+ } | null;
109
+ /**
110
+ * Every heading that opens a journal page, by the line it sits on.
111
+ *
112
+ * The walk that decides it: fenced code holds no headings, a named block holds
113
+ * no page — the block is one passage of the page it is written in — and what is
114
+ * left is read by {@link opensPage}. {@link module:engine/journals.splitPages}
115
+ * builds the pages from this, and the passes that honour a class on a
116
+ * page-opening heading ask the same question of the same answer.
117
+ *
118
+ * A caption also opens a page, which is `splitPages`' business: a caption is
119
+ * found by the caption pass, and a heading it captions is listed here and
120
+ * dropped there.
121
+ *
122
+ * @param {string} source - A note's markdown body.
123
+ * @returns {Map<number, {level: number, line: number, text: string, id: string,
124
+ * classes: string[], values: Record<string, string>}>} Keyed by 0-based line.
125
+ */
126
+ export function pageOpenings(source: string): Map<number, {
127
+ level: number;
128
+ line: number;
129
+ text: string;
130
+ id: string;
131
+ classes: string[];
132
+ values: Record<string, string>;
133
+ }>;
134
+ /**
135
+ * The spans a `.secret` heading withholds, and the classes written where no
136
+ * surface can honour them.
137
+ *
138
+ * A span runs from its heading to the line before the next heading that opens a
139
+ * page, which is the stretch Foundry makes one page of. A `.secret` on a heading
140
+ * that opens no page covers no page, so no surface can withhold it: that is a
141
+ * finding at the heading's own line rather than a class quietly ignored.
142
+ *
143
+ * @param {string} source - A note's markdown body.
144
+ * @param {number} [bodyLine=1] - The 1-based file line the body starts on.
145
+ * @returns {{sections: Array<{start: number, end: number, heading: object}>,
146
+ * errors: Array<{line: number, column: number, message: string}>}} `start` and
147
+ * `end` are 0-based line indexes, `end` exclusive.
148
+ */
149
+ export function withheldSections(source: string, bodyLine?: number): {
150
+ sections: Array<{
151
+ start: number;
152
+ end: number;
153
+ heading: object;
154
+ }>;
155
+ errors: Array<{
156
+ line: number;
157
+ column: number;
158
+ message: string;
159
+ }>;
160
+ };
161
+ /**
162
+ * A heading line: its hashes and the text after them.
163
+ *
164
+ * Shared with every pass that has to find a heading before reading what it
165
+ * declares, so the passes cannot disagree about which lines are headings.
166
+ */
167
+ export const HEADING_LINE: RegExp;
168
+ /**
169
+ * The class that withholds a section from players.
170
+ *
171
+ * Written on the heading that opens the section, so the section stays linkable:
172
+ * secrecy is a property of the section, not of the name a link resolves to.
173
+ */
174
+ export const WITHHELD_CLASS: "secret";
@@ -1,4 +1,4 @@
1
- /** Render a note body with Foundry's secret section markup. */
1
+ /** Render a note body with Foundry's named-block markup. */
2
2
  export function renderFoundryMarkdown(body: any, captions: any, footnoteNumbers: any, docId: any): string;
3
3
  /**
4
4
  * Parses a markdown file with YAML frontmatter.
@@ -426,18 +426,21 @@ export function convertNoteWikilinks(body: string, { type, id, pack, docPack, in
426
426
  unresolved: Array<object>;
427
427
  };
428
428
  /**
429
- * Expand prepared SQL tables before resolving wikilinks in generated cells.
429
+ * Expand prepared SQL tables and page lists before resolving wikilinks in the
430
+ * links they generate.
430
431
  * @param {string} body - The note body.
431
- * @param {object} ctx - Source and prepared query results.
432
+ * @param {object} ctx - Source and prepared results.
432
433
  * @param {string} ctx.name - Name used in diagnostics.
433
434
  * @param {number} [ctx.bodyLine] - The first body line in the source file.
434
435
  * @param {object[]} [ctx.sqlTables] - Prepared SQL results in document order.
436
+ * @param {object[]} [ctx.pageLists] - Prepared page lists in document order.
435
437
  * @returns {{markdown: string, lineMap: Array<{line: number, generated: boolean}>}}
436
438
  */
437
- export function expandNoteTables(body: string, { name, bodyLine, sqlTables }: {
439
+ export function expandNoteTables(body: string, { name, bodyLine, sqlTables, pageLists }: {
438
440
  name: string;
439
441
  bodyLine?: number | undefined;
440
442
  sqlTables?: object[] | undefined;
443
+ pageLists?: object[] | undefined;
441
444
  }): {
442
445
  markdown: string;
443
446
  lineMap: Array<{
@@ -119,7 +119,7 @@ export function checkHomepageCount(found: ReadonlyArray<{
119
119
  * `packageBuild.manifest.title` — the name Foundry already shows for the
120
120
  * package — so a homepage that adds nothing to it need not restate it. An
121
121
  * authored `name.full` wins, because a front page may greet a reader
122
- * differently from a package browser. A top-level `title` is also readable.
122
+ * differently from a package browser.
123
123
  *
124
124
  * Falls back to `contentPackage` last, so a package that has no manifest of its
125
125
  * own still yields a titled page rather than a blank heading.
@@ -244,12 +244,12 @@ export const HOMEPAGE_DESTINATION: string;
244
244
  * `name` and `shortcode` are permitted: the shortcode is what a link is
245
245
  * written with, and `name` titles the page like every other note's.
246
246
  *
247
- * **A named class, not an allow-list, and that boundary is the decision.** A
248
- * homepage's frontmatter is *emitted into the published page*
249
- * ({@link homepageFrontmatter}), so an unrecognised key is a Hugo or theme
250
- * parameter this build has never heard of and has no standing to refuse.
251
- * Rejecting unknown keys would make every new theme parameter wait on a
252
- * package-build release.
247
+ * **A named class, not an allow-list, and that boundary is the decision.**
248
+ * What this class answers is a key the top-level vocabulary *accepts* and a
249
+ * homepage still cannot use, which is a different question from an unknown
250
+ * key: the frontmatter lint refuses those for every note alike, naming the
251
+ * closed vocabulary, so repeating that judgement per type would be a second
252
+ * answer to one question.
253
253
  *
254
254
  * `aliases` is deliberately not in the class: it is a **retired** field, refused
255
255
  * on every note whatever its type, so it is answered there rather than
@@ -61,6 +61,7 @@ export * as contentCharset from "./content-charset.mjs";
61
61
  export * as contentIcons from "./content-icons.mjs";
62
62
  export * as contentHtml from "./content-html.mjs";
63
63
  export * as contentImages from "./content-images.mjs";
64
+ export * as contentTaskLists from "./content-tasklists.mjs";
64
65
  export * as contentEmbeds from "./content-embeds.mjs";
65
66
  export * as pathnames from "./pathnames.mjs";
66
67
  export * as contentLinks from "./content-links.mjs";
@@ -54,12 +54,17 @@ export function sectionHasContent(section: object): boolean;
54
54
  * reference, so the link works on paper as a cross-reference and in a PDF
55
55
  * viewer as a jump. Everything else is set as its own words.
56
56
  *
57
+ * A panel's rows address pages the same way prose does, so a page the book does
58
+ * not print is set as the absolute URL a reader can follow — see
59
+ * {@link module:engine/pdf-render.absolutePageUrl}.
60
+ *
57
61
  * @param {object} value - A `link` value.
58
62
  * @param {Map<string, string>} links - Address slug → the book's anchor.
59
63
  * @param {(anchor: string) => string} labelFor - The anchor's Typst label.
64
+ * @param {string} [site] - The absolute address this book's pages are served at.
60
65
  * @returns {string} Typst markup.
61
66
  */
62
- export function linkToTypst(value: object, links: Map<string, string>, labelFor: (anchor: string) => string): string;
67
+ export function linkToTypst(value: object, links: Map<string, string>, labelFor: (anchor: string) => string, site?: string): string;
63
68
  /**
64
69
  * Every box as one Typst panel each, breaking between sections.
65
70
  *
@@ -4,7 +4,8 @@
4
4
  * a split. Content before the first H1 (if non-empty) becomes a leading
5
5
  * page. Each H1 yields a page whose name is the heading
6
6
  * text (with any `{#anchor-id}` suffix stripped out and surfaced as
7
- * `anchorSlug`).
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.
8
9
  *
9
10
  * `leadName` names that leading page. A journal note's is "Introduction",
10
11
  * because it introduces the pages that follow. An item doc's is the item — a
@@ -15,12 +16,13 @@
15
16
  * @param {string} body - Markdown body to split.
16
17
  * @param {string} [leadName] - Name of the page before the first heading.
17
18
  * @returns {Array<{name: string, anchorSlug: string|null, level: number,
18
- * markdown: string}>} Pages in document order.
19
+ * classes: string[], markdown: string}>} Pages in document order.
19
20
  */
20
21
  export function splitPages(body: string, leadName?: string): Array<{
21
22
  name: string;
22
23
  anchorSlug: string | null;
23
24
  level: number;
25
+ classes: string[];
24
26
  markdown: string;
25
27
  }>;
26
28
  /**
@@ -97,10 +99,14 @@ export function journalPageId(entryId: string, page: {
97
99
  * @param {Array<object>} rawPages - From {@link splitPages}.
98
100
  * @param {string} entryId - The owning JournalEntry's `_id`.
99
101
  * @param {string} noteName - The note, for error messages.
102
+ * A page whose heading carries `.secret` is given to the GM alone, by the one
103
+ * ownership value Foundry reads as "nobody but a GM". Every other page states
104
+ * no ownership and inherits the journal's.
105
+ *
100
106
  * @returns {Array<{_id: string, name: string, type: string,
101
107
  * title: {show: boolean, level: number},
102
- * text: {format: number, content: string}, _key: string}>} The page
103
- * documents, in order.
108
+ * text: {format: number, content: string}, _key: string,
109
+ * ownership?: {default: number}}>} The page documents, in order.
104
110
  * @throws {Error} When the note has no content at all, or repeats an anchor.
105
111
  */
106
112
  export function buildPages(rawPages: Array<object>, entryId: string, noteName: string, captions: any): Array<{
@@ -116,6 +122,9 @@ export function buildPages(rawPages: Array<object>, entryId: string, noteName: s
116
122
  content: string;
117
123
  };
118
124
  _key: string;
125
+ ownership?: {
126
+ default: number;
127
+ };
119
128
  }>;
120
129
  /**
121
130
  * Assemble one JournalEntry document from a note's converted markdown.
@@ -133,7 +142,6 @@ export function buildPages(rawPages: Array<object>, entryId: string, noteName: s
133
142
  * @param {string} [params.leadName] - Name for the page before the first
134
143
  * heading; see {@link splitPages}.
135
144
  * @param {string|null} [params.folder] - The folder id, or `null`.
136
- * @param {object} [params.flags] - Document flags.
137
145
  * @param {object} [params.stats] - The `_stats` block to stamp. Passed by the
138
146
  * caller because it is a property of the *pack* being written, not of the
139
147
  * entry: a module may ship the same content for two systems, and each pack's
@@ -150,13 +158,12 @@ export function buildPages(rawPages: Array<object>, entryId: string, noteName: s
150
158
  * before they read it rather than after.
151
159
  * @returns {object} The JournalEntry document, keyed for the pack.
152
160
  */
153
- export function buildJournalEntry({ id, name, markdown, leadName, folder, flags, stats, infoboxes, notice, }: {
161
+ export function buildJournalEntry({ id, name, markdown, leadName, folder, stats, infoboxes, notice, }: {
154
162
  id: string;
155
163
  name: string;
156
164
  markdown: string;
157
165
  leadName?: string | undefined;
158
166
  folder?: string | null | undefined;
159
- flags?: object | undefined;
160
167
  stats?: object | undefined;
161
168
  infoboxes?: {
162
169
  id: string;
@@ -15,3 +15,26 @@ export function renderMarkdownExpressions(body: string, { fm, dates, sqlResults,
15
15
  markdown: string;
16
16
  findings: any[];
17
17
  };
18
+ /**
19
+ * An inline expression, as an author writes one.
20
+ *
21
+ * Hugo shortcodes begin `{{<` or `{{%` and stay in the source for Hugo.
22
+ *
23
+ * Exported because a renderer that meets one has met a body this pass never ran
24
+ * over, and has to be able to say so.
25
+ *
26
+ * @type {RegExp}
27
+ */
28
+ export const EXPRESSION: RegExp;
29
+ /**
30
+ * Each helper's parameters and what it does, keyed by name.
31
+ *
32
+ * The authoring document describes this surface, and a test holds the two
33
+ * together. A consumer reads it to list what an expression may call.
34
+ *
35
+ * @type {Readonly<Record<string, Readonly<{params: string, summary: string}>>>}
36
+ */
37
+ export const EXPRESSION_HELPERS: Readonly<Record<string, Readonly<{
38
+ params: string;
39
+ summary: string;
40
+ }>>>;
@@ -1,10 +1,7 @@
1
1
  /** Return authored keys from an opening YAML frontmatter map. */
2
2
  export function authoredNoteKeys(markdown: any): string[];
3
- /** The complete ordered top-level vocabulary for addressed content notes. */
4
- export const NOTE_TOP_LEVEL_KEYS: readonly string[];
5
- /** Membership test for the top-level vocabulary. */
6
- export const NOTE_TOP_LEVEL_KEY_SET: Set<string>;
7
3
  /** The name properties every note accepts, in display order. */
8
4
  export const NOTE_NAME_KEYS: readonly string[];
9
5
  /** Additional name components accepted by an individual character. */
10
6
  export const CHARACTER_NAME_KEYS: readonly string[];
7
+ export { NOTE_TOP_LEVEL_FIELDS, NOTE_TOP_LEVEL_KEYS, NOTE_TOP_LEVEL_KEY_SET } from "./note-vocabulary.mjs";
@@ -42,8 +42,6 @@ export function exclusiveTagGroups(type: string, groups?: object): {
42
42
  * The spelling of the tag *itself* still is — a near miss is a near miss, and
43
43
  * the frontmatter lint is what reports it; nothing here guesses.
44
44
  *
45
- * Reads `tags` and the singular `tag` spelling.
46
- *
47
45
  * @param {object|null|undefined} fm - Parsed frontmatter.
48
46
  * @param {string} tag - The tag to look for, in its declared spelling.
49
47
  * @returns {boolean} Whether the note carries it.
@@ -135,6 +133,59 @@ export function dataFields(type: string, vocabulary?: Readonly<Record<string, Ty
135
133
  * it has no `subType` at all — see {@link TypeVocabulary}.
136
134
  */
137
135
  export function subTypes(type: string, vocabulary?: Readonly<Record<string, TypeVocabulary>>): readonly string[] | null | undefined;
136
+ /**
137
+ * One top-level frontmatter key.
138
+ *
139
+ * Narrower than a {@link DataFieldSpec}: the top level is a fixed list rather
140
+ * than a per-type vocabulary, nothing there is keyed by pack or resolved as an
141
+ * Address, and the value's shape is checked by whatever owns the region the key
142
+ * opens — `name`, `tags` and each system block each have their own check.
143
+ *
144
+ * @typedef {object} TopLevelFieldSpec
145
+ * @property {string} name - The key, as a note writes it.
146
+ * @property {boolean} [system] - The key opens a game system's block, so it is
147
+ * one of {@link module:engine/systems.SYSTEM_IDS} rather than a fixed key.
148
+ * @property {string} describe - One line, for the author-facing reference.
149
+ */
150
+ /**
151
+ * **The top-level vocabulary**, in the order a formatted note writes it.
152
+ *
153
+ * The region describes the note as a published artefact: what it is called,
154
+ * what kind of thing it is about, how it is classified, and which of the other
155
+ * two regions it opens. Everything else a note states is a fact about the
156
+ * subject, which belongs under `data:`, or a fact about one system's document,
157
+ * which belongs inside that system's block.
158
+ *
159
+ * **Closed, like the other two regions.** A key this list does not name is a
160
+ * finding at its own line, because a region that passed one through would turn
161
+ * a misspelling into a value nothing reads — `title` beside `name`, or `pack`
162
+ * where `data.pack` was meant.
163
+ *
164
+ * **The system blocks are derived from the system registry**, so recognising a
165
+ * further system is a change to {@link module:engine/systems.SYSTEM_IDS} and to
166
+ * nothing else. They sort by name after the fixed keys: a block is the largest
167
+ * thing a note writes, every one of them belongs at the end, and a rule a
168
+ * reader can restate in one sentence is worth more than a hand-kept order.
169
+ *
170
+ * Five readers derive from this one list — the frontmatter lint's accepted set,
171
+ * the message it prints, the formatter's key order, the content-format check,
172
+ * and the author-facing reference — so none of them can fall behind the others.
173
+ *
174
+ * @type {readonly TopLevelFieldSpec[]}
175
+ */
176
+ export const NOTE_TOP_LEVEL_FIELDS: readonly TopLevelFieldSpec[];
177
+ /**
178
+ * The top-level keys, in formatted order.
179
+ *
180
+ * @type {readonly string[]}
181
+ */
182
+ export const NOTE_TOP_LEVEL_KEYS: readonly string[];
183
+ /**
184
+ * Membership test for the top-level vocabulary.
185
+ *
186
+ * @type {ReadonlySet<string>}
187
+ */
188
+ export const NOTE_TOP_LEVEL_KEY_SET: ReadonlySet<string>;
138
189
  /**
139
190
  * The `data:` keys **every** note type accepts, whatever it is.
140
191
  *
@@ -174,15 +225,17 @@ export const GM_TAG: "gm";
174
225
  /**
175
226
  * The tags that **classify** a note, grouped by what they classify.
176
227
  *
177
- * `tags:` lives at the open top level and most tags belong there: a theme, a
178
- * region, a working state is the author's own and this build has no opinion
179
- * about it. A classifying tag is different, because something queries it — a
228
+ * `tags:` holds an open vocabulary of *values*, and most tags belong there: a
229
+ * theme, a region, a working state is the author's own and this build has no
230
+ * opinion about it. A classifying tag is different, because something queries
231
+ * it — a
180
232
  * settlement tagged `village` appears in the list of villages and an untagged
181
233
  * one does not, so `vilage` does not merely look wrong, it removes the note from
182
234
  * an index while the index still renders a table that looks complete.
183
235
  *
184
236
  * **This list is not a closed set.** An unrecognised tag is legal, because the
185
- * region is open; what is reported is a **near miss** — a tag close enough to a
237
+ * values are the author's; what is reported is a **near miss** — a tag close
238
+ * enough to a
186
239
  * declared one to be a typo of it.
187
240
  *
188
241
  * **Each group names the types it applies to**, and that scope is what makes the
@@ -418,3 +471,26 @@ export type TypeVocabulary = {
418
471
  index?: object;
419
472
  }) => object[]) | undefined;
420
473
  };
474
+ /**
475
+ * One top-level frontmatter key.
476
+ *
477
+ * Narrower than a {@link DataFieldSpec}: the top level is a fixed list rather
478
+ * than a per-type vocabulary, nothing there is keyed by pack or resolved as an
479
+ * Address, and the value's shape is checked by whatever owns the region the key
480
+ * opens — `name`, `tags` and each system block each have their own check.
481
+ */
482
+ export type TopLevelFieldSpec = {
483
+ /**
484
+ * - The key, as a note writes it.
485
+ */
486
+ name: string;
487
+ /**
488
+ * - The key opens a game system's block, so it is
489
+ * one of {@link module :engine/systems.SYSTEM_IDS} rather than a fixed key.
490
+ */
491
+ system?: boolean | undefined;
492
+ /**
493
+ * - One line, for the author-facing reference.
494
+ */
495
+ describe: string;
496
+ };