@heroiclands/package-build 22.21.0 → 22.23.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 (152) hide show
  1. package/CHANGELOG.md +223 -0
  2. package/assets/styles/content-base.css +162 -0
  3. package/bin/content-commands.mjs +77 -17
  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 +153 -6
  8. package/docs/api.md +120 -89
  9. package/docs/authoring/assets.md +14 -0
  10. package/docs/authoring/dates-and-calendars.md +41 -3
  11. package/docs/authoring/frontmatter.md +2 -2
  12. package/docs/authoring/links-and-markup.md +325 -35
  13. package/docs/commands.md +12 -10
  14. package/docs/configuration.md +117 -14
  15. package/docs/content-format.md +4 -2
  16. package/docs/guides/book.md +5 -4
  17. package/docs/guides/reference-pages.md +15 -0
  18. package/docs/reference/format-details.md +545 -104
  19. package/docs/reference/note-types.md +30 -13
  20. package/engine/actor-compiler.mjs +23 -0
  21. package/engine/anchored-sections.mjs +35 -4
  22. package/engine/anchors.mjs +26 -30
  23. package/engine/art-fields.mjs +118 -0
  24. package/engine/art-slots.mjs +7 -1
  25. package/engine/asset-index.mjs +160 -10
  26. package/engine/asset-replacement.mjs +93 -0
  27. package/engine/asset-types.mjs +240 -0
  28. package/engine/base-compiler.mjs +139 -22
  29. package/engine/base-styles.mjs +149 -0
  30. package/engine/bundles.mjs +13 -2
  31. package/engine/compendiums.mjs +8 -1
  32. package/engine/content-blocks.mjs +410 -0
  33. package/engine/content-embeds.mjs +19 -0
  34. package/engine/content-figures.mjs +468 -0
  35. package/engine/content-footnotes.mjs +197 -0
  36. package/engine/content-format-check.mjs +15 -12
  37. package/engine/content-format.yaml +1 -1
  38. package/engine/content-images.mjs +126 -25
  39. package/engine/content-index.mjs +38 -12
  40. package/engine/content-links.mjs +14 -6
  41. package/engine/content-tables.mjs +45 -4
  42. package/engine/content-tasklists.mjs +189 -0
  43. package/engine/docs-checks.mjs +4 -4
  44. package/engine/extension-attributes.mjs +48 -2
  45. package/engine/field-reference.mjs +31 -10
  46. package/engine/field-spec.mjs +10 -8
  47. package/engine/foreign-catalog.mjs +143 -18
  48. package/engine/frontmatter-lint.mjs +120 -65
  49. package/engine/frontmatter.mjs +3 -3
  50. package/engine/heading-attributes.mjs +321 -0
  51. package/engine/helpers.mjs +109 -41
  52. package/engine/homepage.mjs +8 -8
  53. package/engine/index.mjs +3 -0
  54. package/engine/infobox-registry.mjs +4 -1
  55. package/engine/infobox-render.mjs +15 -3
  56. package/engine/infobox.mjs +129 -15
  57. package/engine/itinerary-scenes.mjs +4 -10
  58. package/engine/journals.mjs +294 -50
  59. package/engine/lore-events.mjs +197 -0
  60. package/engine/macros.mjs +1 -1
  61. package/engine/markdown-expressions.mjs +434 -59
  62. package/engine/note-dates.mjs +354 -12
  63. package/engine/note-frontmatter.mjs +7 -16
  64. package/engine/note-vocabulary.mjs +183 -42
  65. package/engine/page-lists.mjs +358 -0
  66. package/engine/pathnames.mjs +36 -0
  67. package/engine/pdf-build.mjs +431 -74
  68. package/engine/pdf-images.mjs +64 -0
  69. package/engine/pdf-render.mjs +563 -66
  70. package/engine/rank-ladder.mjs +198 -0
  71. package/engine/reckoning-markers.mjs +1 -1
  72. package/engine/retired-fields.mjs +8 -13
  73. package/engine/scenes.mjs +26 -7
  74. package/engine/site-build.mjs +166 -23
  75. package/engine/site-config.mjs +10 -1
  76. package/engine/site-index.mjs +90 -3
  77. package/engine/site-preview.mjs +20 -7
  78. package/engine/social-ties.mjs +52 -14
  79. package/engine/sql-tables.mjs +66 -9
  80. package/engine/standings.mjs +89 -6
  81. package/engine/system-block.mjs +56 -30
  82. package/engine/web-wikilinks.mjs +48 -1
  83. package/hm3/actors.mjs +8 -5
  84. package/manifest.mjs +43 -24
  85. package/package.json +1 -1
  86. package/packagebuild-metadata.jsonl +13 -13
  87. package/sohl/actors.mjs +22 -99
  88. package/sohl/item-fields.mjs +5 -5
  89. package/sohl/items.mjs +1 -1
  90. package/sohl/note-schemas.mjs +11 -21
  91. package/types/config.d.mts +9 -0
  92. package/types/content-config.d.mts +48 -1
  93. package/types/engine/actor-compiler.d.mts +11 -0
  94. package/types/engine/anchored-sections.d.mts +24 -0
  95. package/types/engine/anchors.d.mts +9 -4
  96. package/types/engine/art-fields.d.mts +64 -0
  97. package/types/engine/asset-index.d.mts +43 -0
  98. package/types/engine/asset-replacement.d.mts +22 -0
  99. package/types/engine/asset-types.d.mts +18 -0
  100. package/types/engine/base-compiler.d.mts +19 -17
  101. package/types/engine/base-styles.d.mts +93 -0
  102. package/types/engine/content-blocks.d.mts +87 -0
  103. package/types/engine/content-embeds.d.mts +16 -0
  104. package/types/engine/content-figures.d.mts +101 -0
  105. package/types/engine/content-footnotes.d.mts +80 -0
  106. package/types/engine/content-format-check.d.mts +8 -6
  107. package/types/engine/content-images.d.mts +78 -12
  108. package/types/engine/content-index.d.mts +8 -2
  109. package/types/engine/content-tables.d.mts +12 -4
  110. package/types/engine/content-tasklists.d.mts +72 -0
  111. package/types/engine/extension-attributes.d.mts +22 -1
  112. package/types/engine/field-reference.d.mts +21 -0
  113. package/types/engine/field-spec.d.mts +20 -16
  114. package/types/engine/foreign-catalog.d.mts +39 -0
  115. package/types/engine/frontmatter.d.mts +1 -1
  116. package/types/engine/heading-attributes.d.mts +179 -0
  117. package/types/engine/helpers.d.mts +29 -10
  118. package/types/engine/homepage.d.mts +7 -7
  119. package/types/engine/index.d.mts +1 -0
  120. package/types/engine/infobox-registry.d.mts +4 -1
  121. package/types/engine/infobox-render.d.mts +6 -1
  122. package/types/engine/infobox.d.mts +6 -0
  123. package/types/engine/journals.d.mts +41 -12
  124. package/types/engine/lore-events.d.mts +14 -0
  125. package/types/engine/markdown-expressions.d.mts +46 -2
  126. package/types/engine/note-dates.d.mts +64 -1
  127. package/types/engine/note-frontmatter.d.mts +1 -4
  128. package/types/engine/note-vocabulary.d.mts +82 -6
  129. package/types/engine/page-lists.d.mts +126 -0
  130. package/types/engine/pathnames.d.mts +26 -0
  131. package/types/engine/pdf-build.d.mts +28 -6
  132. package/types/engine/pdf-images.d.mts +52 -0
  133. package/types/engine/pdf-render.d.mts +84 -2
  134. package/types/engine/rank-ladder.d.mts +32 -0
  135. package/types/engine/retired-fields.d.mts +4 -6
  136. package/types/engine/site-build.d.mts +27 -4
  137. package/types/engine/site-config.d.mts +9 -0
  138. package/types/engine/site-index.d.mts +67 -1
  139. package/types/engine/site-preview.d.mts +1 -1
  140. package/types/engine/sql-tables.d.mts +31 -7
  141. package/types/engine/standings.d.mts +21 -3
  142. package/types/engine/system-block.d.mts +37 -14
  143. package/types/engine/web-wikilinks.d.mts +3 -1
  144. package/types/hm3/actors.d.mts +21 -3
  145. package/types/manifest.d.mts +19 -23
  146. package/types/sohl/actors.d.mts +6 -7
  147. package/engine/content-admonitions.mjs +0 -114
  148. package/engine/content-captions.mjs +0 -181
  149. package/engine/content-secrets.mjs +0 -112
  150. package/types/engine/content-admonitions.d.mts +0 -24
  151. package/types/engine/content-captions.d.mts +0 -45
  152. package/types/engine/content-secrets.d.mts +0 -17
@@ -1,181 +0,0 @@
1
- /*
2
- * SPDX-License-Identifier: GPL-3.0-or-later
3
- */
4
-
5
- import MarkdownIt from "markdown-it";
6
- import footnotePlugin from "markdown-it-footnote";
7
- import deflistPlugin from "markdown-it-deflist";
8
-
9
- import { slugify } from "./content-slug.mjs";
10
- import { parseExtensionAttributes } from "./extension-attributes.mjs";
11
-
12
- const parser = new MarkdownIt({ html: true }).use(footnotePlugin).use(deflistPlugin);
13
- const OPEN = /^:::caption\s+(\{[^}\n]*\})\s*$/;
14
- const CAPTION_LINE = /^:::caption\b/;
15
- const CLOSE = /^:::\s*$/;
16
-
17
- /** A caption's automatically determined kind. */
18
- export const CAPTION_NAMES = Object.freeze({
19
- code: "Code",
20
- table: "Table",
21
- figure: "Figure",
22
- prose: "Prose",
23
- });
24
-
25
- /** Number a sequence of captions with counters shared across documents. */
26
- export function numberCaptions(captions, counts) {
27
- return captions.map((caption) => {
28
- const number = (counts[caption.kind] ?? 0) + 1;
29
- counts[caption.kind] = number;
30
- return { ...caption, number, label: `${CAPTION_NAMES[caption.kind]} ${number}` };
31
- });
32
- }
33
-
34
- /**
35
- * Read caption directives and the Markdown block immediately after each one.
36
- * SQL fences are expanded before this pass, so their output is a table.
37
- *
38
- * @param {string} source - A note body with expanded tables.
39
- * @returns {{captions: Array<{id: string, text: string, kind: string, number: number,
40
- * label: string, line: number, blockStart: number, blockEnd: number,
41
- * directiveEnd: number}>, errors: Array<{line: number, column: number, message: string}>}}
42
- */
43
- export function scanCaptions(source) {
44
- const lines = String(source ?? "").split("\n");
45
- const captions = [];
46
- const errors = [];
47
- const counts = { code: 0, table: 0, figure: 0, prose: 0 };
48
- const ids = new Set();
49
- const headingIds = [];
50
- let codeFence = null;
51
-
52
- for (let i = 0; i < lines.length; i++) {
53
- const line = lines[i];
54
- const fence = /^ {0,3}(`{3,}|~{3,})/.exec(line);
55
- if (codeFence) {
56
- if (fence && fence[1][0] === codeFence[0] && fence[1].length >= codeFence.length)
57
- codeFence = null;
58
- continue;
59
- }
60
- if (fence) {
61
- codeFence = fence[1];
62
- continue;
63
- }
64
- const heading = /^\s*#{1,6}\s+.+?\{#([^}]+)\}\s*$/.exec(line);
65
- if (heading) headingIds.push({ id: slugify(heading[1].trim()), line: i + 1 });
66
- if (!CAPTION_LINE.test(line)) continue;
67
- const open = OPEN.exec(line);
68
- if (!open) {
69
- errors.push({ line: i + 1, column: 1, message: "caption needs {#anchor} attributes" });
70
- continue;
71
- }
72
- const attributes = parseExtensionAttributes(open[1].slice(1, -1));
73
- if (
74
- attributes.problems.length ||
75
- !attributes.id ||
76
- attributes.classes.length ||
77
- Object.keys(attributes.values).length
78
- ) {
79
- errors.push({
80
- line: i + 1,
81
- column: 1,
82
- message: attributes.problems[0] ?? "caption accepts only {#anchor}",
83
- });
84
- continue;
85
- }
86
- const id = attributes.id;
87
- const start = i;
88
- let close = i + 1;
89
- while (close < lines.length && !CLOSE.test(lines[close])) close++;
90
- if (close === lines.length) {
91
- errors.push({ line: start + 1, column: 1, message: "caption needs a closing :::" });
92
- continue;
93
- }
94
- const caption = lines
95
- .slice(i + 1, close)
96
- .join("\n")
97
- .trim();
98
- if (!caption) errors.push({ line: start + 1, column: 1, message: "caption text is empty" });
99
- if (ids.has(slugify(id)))
100
- errors.push({ line: start + 1, column: 1, message: `duplicate caption id "${id}"` });
101
- ids.add(slugify(id));
102
-
103
- let next = close + 1;
104
- while (next < lines.length && !lines[next].trim()) next++;
105
- if (next === lines.length || CAPTION_LINE.test(lines[next])) {
106
- errors.push({ line: start + 1, column: 1, message: "caption needs a following block" });
107
- i = close;
108
- continue;
109
- }
110
- const tokens = parser.parse(lines.slice(next).join("\n"), {});
111
- const first = tokens.find((token) => token.level === 0 && token.map);
112
- if (!first) {
113
- errors.push({ line: start + 1, column: 1, message: "caption needs a following block" });
114
- i = close;
115
- continue;
116
- }
117
- const blockEnd = next + first.map[1];
118
- const block = lines.slice(next, blockEnd).join("\n");
119
- const kind =
120
- first.type === "table_open" ? "table"
121
- : first.type === "fence" && /^\s*sql\b/i.test(first.info ?? "") ? "table"
122
- : first.type === "fence" || first.type === "code_block" ? "code"
123
- : /^(?:!\[|!\[\[|<figure\b|<img\b)/.test(block.trim()) ? "figure"
124
- : "prose";
125
- const number = ++counts[kind];
126
- captions.push({
127
- id,
128
- text: caption,
129
- kind,
130
- number,
131
- label: `${CAPTION_NAMES[kind]} ${number}`,
132
- line: start + 1,
133
- blockStart: next,
134
- blockEnd,
135
- directiveEnd: close + 1,
136
- });
137
- i = close;
138
- }
139
- for (const heading of headingIds) {
140
- if (ids.has(heading.id))
141
- errors.push({
142
- line: heading.line,
143
- column: 1,
144
- message: `heading and caption declare the same anchor "${heading.id}"`,
145
- });
146
- }
147
- return { captions, errors };
148
- }
149
-
150
- /** Render captioned blocks as HTML, leaving other Markdown untouched. */
151
- export function renderCaptionBlocks(source, renderMarkdown = parser.render.bind(parser), numbers) {
152
- const { captions, errors } = scanCaptions(source);
153
- if (errors.length) return { markdown: source, errors };
154
- const byId = numbers ? new Map(numbers.map((entry) => [entry.id, entry])) : new Map();
155
- const lines = String(source ?? "").split("\n");
156
- const output = [];
157
- let cursor = 0;
158
- const escape = (value) =>
159
- String(value)
160
- .replace(/&/g, "&amp;")
161
- .replace(/</g, "&lt;")
162
- .replace(/>/g, "&gt;")
163
- .replace(/"/g, "&quot;");
164
- for (const caption of captions) {
165
- output.push(...lines.slice(cursor, caption.line - 1));
166
- const numbered = byId.get(caption.id) ?? caption;
167
- let html = renderMarkdown(lines.slice(caption.blockStart, caption.blockEnd).join("\n"));
168
- if (caption.kind === "figure")
169
- html = html.replace(/<figcaption\b[^>]*>[\s\S]*?<\/figcaption>/i, "");
170
- const captionHtml = parser.renderInline(caption.text);
171
- output.push(
172
- `<div id="${escape(slugify(caption.id))}" class="content-caption content-caption-${caption.kind}">`,
173
- html.trim(),
174
- `<p class="content-caption-label">${escape(numbered.label)}: ${captionHtml}</p>`,
175
- "</div>",
176
- );
177
- cursor = caption.blockEnd;
178
- }
179
- output.push(...lines.slice(cursor));
180
- return { markdown: output.join("\n"), errors: [] };
181
- }
@@ -1,112 +0,0 @@
1
- /*
2
- * SPDX-License-Identifier: GPL-3.0-or-later
3
- */
4
-
5
- import crypto from "node:crypto";
6
- import markdownit from "markdown-it";
7
- import footnotePlugin from "markdown-it-footnote";
8
- import deflistPlugin from "markdown-it-deflist";
9
-
10
- const webMarkdown = markdownit({ html: true }).use(footnotePlugin).use(deflistPlugin);
11
-
12
- /**
13
- * Render whole-line `:::secret` blocks for one publishing surface.
14
- * The source line of each syntax error is relative to the supplied body.
15
- *
16
- * @param {string} source - Markdown containing secret blocks.
17
- * @param {"foundry"|"web"|"book"} target - Publishing surface.
18
- * @param {(markdown: string) => string} [renderMarkdown] - Foundry's configured renderer.
19
- * @returns {{markdown: string, errors: Array<{line: number, column: number, message: string}>}}
20
- */
21
- export function renderSecretBlocks(
22
- source,
23
- target,
24
- renderMarkdown = webMarkdown.render.bind(webMarkdown),
25
- ) {
26
- const lines = String(source ?? "").split("\n");
27
- const result = [];
28
- const errors = [];
29
- let opening = -1;
30
- let body = [];
31
- let codeFence = null;
32
-
33
- for (let index = 0; index < lines.length; index++) {
34
- const line = lines[index];
35
- const fence = line.match(/^ {0,3}(`{3,}|~{3,})/);
36
- if (codeFence) {
37
- if (fence && fence[1][0] === codeFence[0] && fence[1].length >= codeFence.length) {
38
- codeFence = null;
39
- }
40
- if (opening >= 0) body.push(line);
41
- else result.push(line);
42
- continue;
43
- }
44
- if (fence) {
45
- codeFence = fence[1];
46
- if (opening >= 0) body.push(line);
47
- else result.push(line);
48
- continue;
49
- }
50
- if (/^:::secret[ \t]*$/.test(line)) {
51
- if (opening >= 0) {
52
- errors.push({
53
- line: index + 1,
54
- column: 1,
55
- message: "nested secret blocks are not supported",
56
- });
57
- body.push(line);
58
- } else {
59
- opening = index;
60
- body = [];
61
- }
62
- continue;
63
- }
64
- if (/^:::[ \t]*$/.test(line)) {
65
- if (opening < 0) {
66
- errors.push({
67
- line: index + 1,
68
- column: 1,
69
- message: "secret block has no opening :::secret line",
70
- });
71
- result.push(line);
72
- continue;
73
- }
74
- const inner = body.join("\n").trim();
75
- if (target === "book") {
76
- result.push("", "**GM note**", "", inner, "");
77
- } else {
78
- const html = renderMarkdown(inner).trim();
79
- if (target === "foundry") {
80
- const id = crypto
81
- .createHash("sha256")
82
- .update(`${opening}:${inner}`)
83
- .digest("hex")
84
- .slice(0, 12);
85
- result.push(
86
- "",
87
- `<section class="secret" id="secret-${id}">`,
88
- html,
89
- "</section>",
90
- "",
91
- );
92
- } else {
93
- result.push("", "<details><summary>Spoiler</summary>", html, "</details>", "");
94
- }
95
- }
96
- opening = -1;
97
- body = [];
98
- continue;
99
- }
100
- if (opening >= 0) body.push(line);
101
- else result.push(line);
102
- }
103
- if (opening >= 0) {
104
- errors.push({
105
- line: opening + 1,
106
- column: 1,
107
- message: "secret block needs a closing ::: line",
108
- });
109
- result.push(":::secret", ...body);
110
- }
111
- return { markdown: result.join("\n"), errors };
112
- }
@@ -1,24 +0,0 @@
1
- /** Find inline info and warning blocks without interpreting code examples. */
2
- export function scanAdmonitions(source: any): {
3
- blocks: {
4
- end: number;
5
- body: string;
6
- start: number;
7
- kind: string;
8
- id: string;
9
- }[];
10
- errors: {
11
- line: number;
12
- column: number;
13
- message: string;
14
- }[];
15
- };
16
- /** Render info and warning blocks as styled, labelled HTML. */
17
- export function renderAdmonitions(source: any, renderMarkdown?: (src: string, env?: import("markdown-it").Env) => string): {
18
- markdown: any;
19
- errors: {
20
- line: number;
21
- column: number;
22
- message: string;
23
- }[];
24
- };
@@ -1,45 +0,0 @@
1
- /** Number a sequence of captions with counters shared across documents. */
2
- export function numberCaptions(captions: any, counts: any): any;
3
- /**
4
- * Read caption directives and the Markdown block immediately after each one.
5
- * SQL fences are expanded before this pass, so their output is a table.
6
- *
7
- * @param {string} source - A note body with expanded tables.
8
- * @returns {{captions: Array<{id: string, text: string, kind: string, number: number,
9
- * label: string, line: number, blockStart: number, blockEnd: number,
10
- * directiveEnd: number}>, errors: Array<{line: number, column: number, message: string}>}}
11
- */
12
- export function scanCaptions(source: string): {
13
- captions: Array<{
14
- id: string;
15
- text: string;
16
- kind: string;
17
- number: number;
18
- label: string;
19
- line: number;
20
- blockStart: number;
21
- blockEnd: number;
22
- directiveEnd: number;
23
- }>;
24
- errors: Array<{
25
- line: number;
26
- column: number;
27
- message: string;
28
- }>;
29
- };
30
- /** Render captioned blocks as HTML, leaving other Markdown untouched. */
31
- export function renderCaptionBlocks(source: any, renderMarkdown: ((src: string, env?: import("markdown-it").Env) => string) | undefined, numbers: any): {
32
- markdown: any;
33
- errors: {
34
- line: number;
35
- column: number;
36
- message: string;
37
- }[];
38
- };
39
- /** A caption's automatically determined kind. */
40
- export const CAPTION_NAMES: Readonly<{
41
- code: "Code";
42
- table: "Table";
43
- figure: "Figure";
44
- prose: "Prose";
45
- }>;
@@ -1,17 +0,0 @@
1
- /**
2
- * Render whole-line `:::secret` blocks for one publishing surface.
3
- * The source line of each syntax error is relative to the supplied body.
4
- *
5
- * @param {string} source - Markdown containing secret blocks.
6
- * @param {"foundry"|"web"|"book"} target - Publishing surface.
7
- * @param {(markdown: string) => string} [renderMarkdown] - Foundry's configured renderer.
8
- * @returns {{markdown: string, errors: Array<{line: number, column: number, message: string}>}}
9
- */
10
- export function renderSecretBlocks(source: string, target: "foundry" | "web" | "book", renderMarkdown?: (markdown: string) => string): {
11
- markdown: string;
12
- errors: Array<{
13
- line: number;
14
- column: number;
15
- message: string;
16
- }>;
17
- };