@heroiclands/package-build 22.20.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 (118) hide show
  1. package/CHANGELOG-content-build.md +0 -8
  2. package/CHANGELOG.md +204 -0
  3. package/assets/styles/content-base.css +162 -0
  4. package/bin/content-commands.mjs +26 -3
  5. package/bin/package-build.mjs +15 -2
  6. package/ci/content-format-reference.mjs +68 -1
  7. package/config.mjs +26 -0
  8. package/content-config.mjs +73 -4
  9. package/docs/api.md +46 -28
  10. package/docs/authoring/dates-and-calendars.md +4 -2
  11. package/docs/authoring/frontmatter.md +2 -2
  12. package/docs/authoring/links-and-markup.md +237 -28
  13. package/docs/configuration.md +27 -1
  14. package/docs/content-format.md +3 -1
  15. package/docs/guides/reference-pages.md +15 -0
  16. package/docs/reference/format-details.md +265 -42
  17. package/docs/reference/note-types.md +45 -28
  18. package/engine/actor-compiler.mjs +23 -0
  19. package/engine/anchored-sections.mjs +35 -4
  20. package/engine/anchors.mjs +10 -20
  21. package/engine/base-compiler.mjs +86 -21
  22. package/engine/base-styles.mjs +149 -0
  23. package/engine/bundles.mjs +0 -1
  24. package/engine/compendiums.mjs +8 -1
  25. package/engine/content-blocks.mjs +410 -0
  26. package/engine/content-captions.mjs +57 -8
  27. package/engine/content-footnotes.mjs +189 -0
  28. package/engine/content-format-check.mjs +15 -12
  29. package/engine/content-index.mjs +2 -2
  30. package/engine/content-links.mjs +14 -6
  31. package/engine/content-tables.mjs +45 -4
  32. package/engine/content-tasklists.mjs +189 -0
  33. package/engine/docs-checks.mjs +4 -4
  34. package/engine/extension-attributes.mjs +48 -2
  35. package/engine/frontmatter-lint.mjs +62 -66
  36. package/engine/frontmatter.mjs +3 -3
  37. package/engine/heading-attributes.mjs +308 -0
  38. package/engine/helpers.mjs +19 -13
  39. package/engine/homepage.mjs +8 -8
  40. package/engine/index.mjs +3 -0
  41. package/engine/infobox-render.mjs +21 -4
  42. package/engine/infobox.mjs +167 -7
  43. package/engine/journals.mjs +36 -17
  44. package/engine/macros.mjs +1 -1
  45. package/engine/markdown-expressions.mjs +178 -53
  46. package/engine/note-addresses.mjs +17 -8
  47. package/engine/note-dates.mjs +49 -0
  48. package/engine/note-frontmatter.mjs +7 -16
  49. package/engine/note-vocabulary.mjs +216 -42
  50. package/engine/page-lists.mjs +358 -0
  51. package/engine/pdf-build.mjs +76 -42
  52. package/engine/pdf-render.mjs +422 -49
  53. package/engine/rank-ladder.mjs +198 -0
  54. package/engine/reckoning-markers.mjs +1 -1
  55. package/engine/reference-targets.mjs +13 -1
  56. package/engine/retired-fields.mjs +8 -13
  57. package/engine/scenes.mjs +10 -3
  58. package/engine/site-build.mjs +41 -12
  59. package/engine/site-config.mjs +10 -1
  60. package/engine/site-index.mjs +39 -4
  61. package/engine/site-preview.mjs +18 -7
  62. package/engine/social-ties.mjs +52 -14
  63. package/engine/sql-tables.mjs +66 -9
  64. package/engine/standing-terms.mjs +19 -0
  65. package/engine/standings.mjs +538 -0
  66. package/engine/svg-theme.mjs +15 -4
  67. package/engine/system-block.mjs +12 -14
  68. package/engine/web-wikilinks.mjs +48 -1
  69. package/engine/wikilinks.mjs +3 -1
  70. package/hm3/actors.mjs +6 -3
  71. package/manifest.mjs +13 -0
  72. package/package.json +1 -1
  73. package/sohl/actors.mjs +82 -103
  74. package/sohl/item-fields.mjs +4 -5
  75. package/sohl/items.mjs +1 -1
  76. package/sohl/note-schemas.mjs +11 -21
  77. package/types/config.d.mts +9 -0
  78. package/types/engine/actor-compiler.d.mts +11 -0
  79. package/types/engine/anchored-sections.d.mts +24 -0
  80. package/types/engine/base-compiler.d.mts +19 -0
  81. package/types/engine/base-styles.d.mts +93 -0
  82. package/types/engine/content-blocks.d.mts +87 -0
  83. package/types/engine/content-captions.d.mts +14 -1
  84. package/types/engine/content-footnotes.d.mts +73 -0
  85. package/types/engine/content-format-check.d.mts +8 -6
  86. package/types/engine/content-tables.d.mts +12 -4
  87. package/types/engine/content-tasklists.d.mts +72 -0
  88. package/types/engine/extension-attributes.d.mts +22 -1
  89. package/types/engine/frontmatter.d.mts +1 -1
  90. package/types/engine/heading-attributes.d.mts +174 -0
  91. package/types/engine/helpers.d.mts +7 -4
  92. package/types/engine/homepage.d.mts +7 -7
  93. package/types/engine/index.d.mts +1 -0
  94. package/types/engine/infobox-render.d.mts +6 -1
  95. package/types/engine/journals.d.mts +14 -7
  96. package/types/engine/markdown-expressions.d.mts +23 -0
  97. package/types/engine/note-frontmatter.d.mts +1 -4
  98. package/types/engine/note-vocabulary.d.mts +98 -8
  99. package/types/engine/page-lists.d.mts +126 -0
  100. package/types/engine/pdf-render.d.mts +72 -2
  101. package/types/engine/rank-ladder.d.mts +32 -0
  102. package/types/engine/retired-fields.d.mts +4 -6
  103. package/types/engine/site-build.d.mts +13 -1
  104. package/types/engine/site-config.d.mts +9 -0
  105. package/types/engine/site-index.d.mts +13 -1
  106. package/types/engine/site-preview.d.mts +1 -1
  107. package/types/engine/sql-tables.d.mts +31 -7
  108. package/types/engine/standing-terms.d.mts +16 -0
  109. package/types/engine/standings.d.mts +147 -0
  110. package/types/engine/system-block.d.mts +11 -12
  111. package/types/engine/web-wikilinks.d.mts +3 -1
  112. package/types/engine/wikilinks.d.mts +2 -1
  113. package/types/hm3/actors.d.mts +3 -3
  114. package/types/sohl/actors.d.mts +25 -7
  115. package/engine/content-admonitions.mjs +0 -114
  116. package/engine/content-secrets.mjs +0 -112
  117. package/types/engine/content-admonitions.d.mts +0 -24
  118. package/types/engine/content-secrets.d.mts +0 -17
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The cascade layer the sheet loads into, per package kind.
3
+ *
4
+ * Foundry declares its layer order in `css/foundry2.css`, ending
5
+ * `… layouts, system, modules, exceptions` — the eighth and ninth being, by its
6
+ * own comments, default game system styles and default module styles. That is
7
+ * what this sheet is, so it takes the layer its kind is given and is therefore
8
+ * the weakest thing a package can load:
9
+ *
10
+ * - **An unlayered package sheet beats it**, because an unlayered rule beats
11
+ * every layered one whatever its specificity.
12
+ * - **A package sheet naming any other layer beats it too**, because a layer
13
+ * Foundry's order does not mention is appended after the ones it does.
14
+ *
15
+ * So a package overrides the base sheet from its own, whichever way it declares
16
+ * one, and the override needs no `!important` and no specificity contest.
17
+ *
18
+ * @param {"system"|"module"} artifact - Which artifact is shipped.
19
+ * @returns {string} The layer name.
20
+ */
21
+ export function baseStyleLayer(artifact: "system" | "module"): string;
22
+ /**
23
+ * The manifest `styles` entry for the base sheet.
24
+ *
25
+ * Foundry's `styles` is an array of `{layer?, src}`, and a bare string is
26
+ * migrated into that shape on read, so the object form is written directly.
27
+ *
28
+ * @param {"system"|"module"} artifact - Which artifact is shipped.
29
+ * @returns {{src: string, layer: string}} The entry.
30
+ */
31
+ export function baseStyleEntry(artifact: "system" | "module"): {
32
+ src: string;
33
+ layer: string;
34
+ };
35
+ /**
36
+ * The absolute path of the sheet as installed, so the stage can copy it.
37
+ *
38
+ * Resolved against this module rather than against the consumer's working
39
+ * directory, because the file belongs to this package and arrives through
40
+ * `node_modules`.
41
+ *
42
+ * @returns {string} Absolute path of the shipped stylesheet.
43
+ */
44
+ export function baseStylesheetSource(): string;
45
+ /**
46
+ * The `[source, destination]` pair that stages the sheet.
47
+ *
48
+ * The destination is relative, in the same spelling the repository's own asset
49
+ * table uses, so the caller resolves both against the repository root exactly
50
+ * as it resolves every other entry.
51
+ *
52
+ * @param {string} stageDir - The configured stage directory, relative to the
53
+ * repository root.
54
+ * @returns {[string, string]} The pair, for `stageAssets`.
55
+ */
56
+ export function baseStyleStageEntry(stageDir: string): [string, string];
57
+ /**
58
+ * The manifest's `styles`, with the base sheet named first.
59
+ *
60
+ * **First rather than last**, because `styles` is load order: a package's own
61
+ * sheet has to be read after the base one for an override written at equal
62
+ * specificity in an equal layer to take. The cascade layer makes the override
63
+ * work anyway, and the order makes it work for the right reason.
64
+ *
65
+ * A declared entry addressing the same file is not duplicated — a repository
66
+ * that named the staged path itself gets one entry, keeping whatever layer it
67
+ * chose, so declaring it is a way of placing it rather than an error.
68
+ *
69
+ * @param {object} options
70
+ * @param {unknown} [options.declared] - The declared `styles`, whatever shape
71
+ * it was written in; anything that is not a list is returned untouched.
72
+ * @param {"system"|"module"} options.artifact - Which artifact is shipped.
73
+ * @param {boolean} [options.enabled] - Whether the package takes the base
74
+ * sheet. `false` returns the declared list unchanged.
75
+ * @returns {unknown} The `styles` to emit, or `undefined` when there is
76
+ * nothing to emit at all.
77
+ */
78
+ export function withBaseStyle({ declared, artifact, enabled }: {
79
+ declared?: unknown;
80
+ artifact: "system" | "module";
81
+ enabled?: boolean | undefined;
82
+ }): unknown;
83
+ /**
84
+ * Where the shipped sheet lands inside the staged package.
85
+ *
86
+ * Under `styles/` beside a package's own sheet, and named for the toolchain so
87
+ * nothing a repository writes can collide with it. The path is relative to the
88
+ * staged package root, which is what both the stage entry and the manifest's
89
+ * `styles` are addressed in.
90
+ *
91
+ * @type {string}
92
+ */
93
+ export const BASE_STYLESHEET_DEST: string;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Find named blocks, leaving code examples alone.
3
+ *
4
+ * A block reaches `blocks` only when it is well formed and closed; everything
5
+ * else is a finding, and the surrounding blocks are unaffected.
6
+ *
7
+ * @param {string} source - Markdown that may contain named blocks.
8
+ * @returns {{blocks: Array<{name: string, title: string, id: string, classes: string[], attributes: Record<string, string>, start: number, end: number, body: string}>, errors: Array<{line: number, column: number, message: string}>}}
9
+ */
10
+ export function scanBlocks(source: string): {
11
+ blocks: Array<{
12
+ name: string;
13
+ title: string;
14
+ id: string;
15
+ classes: string[];
16
+ attributes: Record<string, string>;
17
+ start: number;
18
+ end: number;
19
+ body: string;
20
+ }>;
21
+ errors: Array<{
22
+ line: number;
23
+ column: number;
24
+ message: string;
25
+ }>;
26
+ };
27
+ /**
28
+ * Render named blocks for one publishing surface.
29
+ *
30
+ * `foundry` emits a `<section>` whose heading is its first line; `web` emits a
31
+ * `<details>` the reader opens. Neither carries an inline `style`.
32
+ *
33
+ * The body is left as Markdown between the wrapper's tags, each separated from
34
+ * its tag by a blank line — see the module docs for why that is what lets the
35
+ * surrounding render treat the body as real content rather than an opaque
36
+ * fragment.
37
+ *
38
+ * A finding does not stop the well-formed blocks around it from rendering.
39
+ *
40
+ * @param {string} source - Markdown containing named blocks.
41
+ * @param {"foundry"|"web"} target - Publishing surface.
42
+ * @returns {{markdown: string, errors: Array<{line: number, column: number, message: string}>}}
43
+ */
44
+ export function renderBlocks(source: string, target: "foundry" | "web"): {
45
+ markdown: string;
46
+ errors: Array<{
47
+ line: number;
48
+ column: number;
49
+ message: string;
50
+ }>;
51
+ };
52
+ /**
53
+ * Wrap each withheld section in the disclosure a `secret` block renders as.
54
+ *
55
+ * The section's own Markdown is left as Markdown, with a blank line either side
56
+ * of it, so the page's renderer still reads it: the heading keeps its id, the
57
+ * prose keeps its links, and only the disclosure around them is written as HTML.
58
+ *
59
+ * **A spoiler, not access control.** The section is in the published page, so a
60
+ * reader who opens the element or reads the source reads it. Foundry is the one
61
+ * surface that withholds anything.
62
+ *
63
+ * @param {string} source - A page's Markdown, headings intact.
64
+ * @returns {string} The same Markdown, each withheld section inside a
65
+ * `<details>`.
66
+ */
67
+ export function renderWithheldSections(source: string): string;
68
+ /** The blocks an author may open, with the heading each takes by default. */
69
+ export const BLOCK_NAMES: Readonly<{
70
+ info: "Info";
71
+ secret: "Secret";
72
+ warn: "Warn";
73
+ }>;
74
+ /**
75
+ * Blocks that may hold another named block.
76
+ *
77
+ * A box inside a GM-only section is a real thing to write — the section is a
78
+ * container for whatever the GM reads, boxes included — so a `secret` holds one
79
+ * and the box stays inside it. A box inside a box is noise, so `info` and `warn`
80
+ * hold no named block and a second opener inside one is a finding.
81
+ *
82
+ * Exported because a renderer has to know it: the body of a container is read
83
+ * for blocks of its own, and the body of a box is not.
84
+ *
85
+ * @type {readonly string[]}
86
+ */
87
+ export const BLOCK_CONTAINERS: readonly string[];
@@ -27,7 +27,20 @@ export function scanCaptions(source: string): {
27
27
  message: string;
28
28
  }>;
29
29
  };
30
- /** Render captioned blocks as HTML, leaving other Markdown untouched. */
30
+ /**
31
+ * Render captioned blocks as HTML, leaving other Markdown untouched.
32
+ *
33
+ * A code, table or prose caption's block is left as Markdown inside the
34
+ * wrapper, blank-line separated from its tags exactly as
35
+ * {@link module:engine/content-blocks.renderBlocks} leaves a named block's
36
+ * body — so the surrounding render sees it as part of its own document and a
37
+ * footnote reference inside it resolves against the note's own definitions.
38
+ *
39
+ * A figure's block is still rendered here, because its `<figcaption>` has to
40
+ * be stripped from HTML this pass controls directly; the surrounding render
41
+ * never sees it as Markdown, so a footnote reference inside a figure caption
42
+ * is not resolved, the same gap a block's own title carries.
43
+ */
31
44
  export function renderCaptionBlocks(source: any, renderMarkdown: ((src: string, env?: import("markdown-it").Env) => string) | undefined, numbers: any): {
32
45
  markdown: any;
33
46
  errors: {
@@ -8,3 +8,76 @@ export function separateFootnotes(source: string): {
8
8
  markdown: string;
9
9
  definitions: string;
10
10
  };
11
+ /**
12
+ * A footnote definition written inside a list item or a block quote, where
13
+ * the web and Foundry can resolve it but the book drops it silently.
14
+ *
15
+ * A definition inside a table cell is not reported here: a table cell is
16
+ * inline content, so `[^id]:` there is never recognised as a definition on
17
+ * any surface and simply prints as written — the reference it was meant to
18
+ * answer is what {@link unresolvedFootnoteReferences} reports.
19
+ *
20
+ * @param {string} source - A note's markdown body.
21
+ * @returns {Array<{line: number, column: number, label: string, message: string}>}
22
+ */
23
+ export function misplacedFootnoteDefinitions(source: string): Array<{
24
+ line: number;
25
+ column: number;
26
+ label: string;
27
+ message: string;
28
+ }>;
29
+ /**
30
+ * A footnote reference with no top-level definition to resolve against.
31
+ *
32
+ * Covers both commoner slips alike: a label never defined at all, and one
33
+ * "defined" only inside a table cell, which is inline content and never
34
+ * registers as a definition on any surface. A label misplaced in a list or a
35
+ * block quote is excluded — {@link misplacedFootnoteDefinitions} already
36
+ * reports that mistake at the definition's own line, and reporting it again
37
+ * at the reference would say the same thing twice.
38
+ *
39
+ * @param {string} source - A note's markdown body.
40
+ * @param {string} [definitions] - Its top-level definitions, when a caller
41
+ * already extracted them with {@link separateFootnotes}. Derived from
42
+ * `source` when omitted; a caller already holding both passes its own
43
+ * extraction instead, so the two cannot disagree about what is defined.
44
+ * @returns {Array<{line: number, column: number, message: string}>}
45
+ */
46
+ export function unresolvedFootnoteReferences(source: string, definitions?: string): Array<{
47
+ line: number;
48
+ column: number;
49
+ message: string;
50
+ }>;
51
+ /**
52
+ * A top-level footnote definition no reference anywhere in the note uses.
53
+ *
54
+ * `separateFootnotes` lifts it out correctly, nothing resolves against it,
55
+ * and the paragraph it held vanishes from every surface with the build
56
+ * exiting 0 — a dropped paragraph is not something a reader can notice is
57
+ * missing, so it is an error here rather than a warning, the same severity
58
+ * every other silent loss this pass catches already carries. A note mid-edit,
59
+ * with the reference already deleted and the definition not yet followed, is
60
+ * exactly the case this refuses — which is the point: the alternative is
61
+ * shipping it.
62
+ *
63
+ * @param {string} source - A note's markdown body.
64
+ * @returns {Array<{line: number, column: number, message: string}>}
65
+ */
66
+ export function unusedFootnoteDefinitions(source: string): Array<{
67
+ line: number;
68
+ column: number;
69
+ message: string;
70
+ }>;
71
+ /**
72
+ * Every footnote placement finding for one note body — the union
73
+ * {@link misplacedFootnoteDefinitions}, {@link unresolvedFootnoteReferences}
74
+ * and {@link unusedFootnoteDefinitions} report, in document order.
75
+ *
76
+ * @param {string} source - A note's markdown body.
77
+ * @returns {Array<{line: number, column: number, message: string}>}
78
+ */
79
+ export function footnoteFindings(source: string): Array<{
80
+ line: number;
81
+ column: number;
82
+ message: string;
83
+ }>;
@@ -180,12 +180,14 @@ export function checkDeclaredFields({ format, itemFields, system, severity }: {
180
180
  /**
181
181
  * Top-level keys the format names as the note's own, whatever its type.
182
182
  *
183
- * Top level is otherwise **open** — an unrecognised key there is a Hugo or
184
- * theme parameter this build has no standing to refuse — so this set exists
185
- * only to keep a note's own identity fields from being mistaken for a
186
- * misplaced `data` property where the two share a name. `type` and `subType`
187
- * are the pair that matters: several mapping tables name `subType` as a shared
188
- * source, and it is authored at top level by design.
183
+ * The top-level vocabulary itself, read rather than restated: this set exists
184
+ * to keep a note's own identity fields from being mistaken for a misplaced
185
+ * `data` property where the two share a name, and the keys that are a note's
186
+ * own are exactly the ones the region declares. `type` and `subType` are the
187
+ * pair that matters — several mapping tables name `subType` as a shared source,
188
+ * and it is authored at top level by design. A key the region does *not*
189
+ * declare, such as `id` beside `data.id`, is reported as the misplaced `data`
190
+ * property it is.
189
191
  *
190
192
  * @type {ReadonlySet<string>}
191
193
  */
@@ -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<{