@heroiclands/package-build 22.22.0 → 22.24.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 (140) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +1 -0
  3. package/bin/content-commands.mjs +51 -14
  4. package/ci/content-format-reference.mjs +1 -1
  5. package/content-config.mjs +80 -2
  6. package/docs/api.md +85 -72
  7. package/docs/authoring/assets.md +29 -0
  8. package/docs/authoring/dates-and-calendars.md +37 -1
  9. package/docs/authoring/links-and-markup.md +112 -31
  10. package/docs/commands.md +30 -29
  11. package/docs/configuration.md +92 -19
  12. package/docs/content-format.md +1 -1
  13. package/docs/guides/book.md +5 -4
  14. package/docs/guides/module-project.md +2 -2
  15. package/docs/guides/site.md +41 -0
  16. package/docs/project-setup.md +4 -4
  17. package/docs/reference/format-details.md +323 -60
  18. package/docs/reference/note-types.md +9 -9
  19. package/docs/reference/theme-pages.md +496 -0
  20. package/engine/anchors.mjs +16 -10
  21. package/engine/art-fields.mjs +118 -0
  22. package/engine/art-slots.mjs +9 -2
  23. package/engine/asset-index.mjs +160 -10
  24. package/engine/asset-replacement.mjs +93 -0
  25. package/engine/asset-types.mjs +240 -0
  26. package/engine/base-compiler.mjs +54 -2
  27. package/engine/bundles.mjs +13 -1
  28. package/engine/content-blocks.mjs +4 -4
  29. package/engine/content-embeds.mjs +19 -0
  30. package/engine/content-figures.mjs +468 -0
  31. package/engine/content-footnotes.mjs +10 -2
  32. package/engine/content-format.yaml +1 -1
  33. package/engine/content-images.mjs +126 -25
  34. package/engine/content-index.mjs +36 -10
  35. package/engine/field-reference.mjs +31 -10
  36. package/engine/field-spec.mjs +10 -8
  37. package/engine/foreign-catalog.mjs +145 -19
  38. package/engine/frontmatter-lint.mjs +73 -1
  39. package/engine/heading-attributes.mjs +31 -18
  40. package/engine/helpers.mjs +102 -33
  41. package/engine/infobox-registry.mjs +4 -1
  42. package/engine/infobox.mjs +47 -8
  43. package/engine/itinerary-scenes.mjs +4 -10
  44. package/engine/journals.mjs +264 -39
  45. package/engine/lore-events.mjs +197 -0
  46. package/engine/markdown-expressions.mjs +257 -7
  47. package/engine/note-dates.mjs +305 -12
  48. package/engine/note-vocabulary.mjs +11 -5
  49. package/engine/pathnames.mjs +36 -0
  50. package/engine/pdf-build.mjs +357 -34
  51. package/engine/pdf-images.mjs +64 -0
  52. package/engine/pdf-render.mjs +160 -36
  53. package/engine/scenes.mjs +16 -4
  54. package/engine/site-build.mjs +127 -13
  55. package/engine/site-config.mjs +19 -24
  56. package/engine/site-index.mjs +72 -1
  57. package/engine/site-preview.mjs +2 -0
  58. package/engine/system-block.mjs +44 -16
  59. package/hm3/actors.mjs +2 -2
  60. package/hugo-theme/data/banners.yaml +69 -0
  61. package/hugo-theme/layouts/404.html +85 -0
  62. package/hugo-theme/layouts/_default/baseof.html +64 -0
  63. package/hugo-theme/layouts/_default/list.html +139 -0
  64. package/hugo-theme/layouts/_default/single.html +4 -0
  65. package/hugo-theme/layouts/blog/list.html +54 -0
  66. package/hugo-theme/layouts/blog-post/single.html +36 -0
  67. package/hugo-theme/layouts/index.html +75 -0
  68. package/hugo-theme/layouts/partials/banner-url.html +117 -0
  69. package/hugo-theme/layouts/partials/cdn-url.html +36 -0
  70. package/hugo-theme/layouts/partials/draft-notice.html +41 -0
  71. package/hugo-theme/layouts/partials/draft-state.html +26 -0
  72. package/hugo-theme/layouts/partials/footer.html +29 -0
  73. package/hugo-theme/layouts/partials/header.html +35 -0
  74. package/hugo-theme/layouts/partials/hero-banner.html +86 -0
  75. package/hugo-theme/layouts/partials/holdings/entry.html +21 -0
  76. package/hugo-theme/layouts/partials/holdings/kind-groups.html +71 -0
  77. package/hugo-theme/layouts/partials/holdings.html +61 -0
  78. package/hugo-theme/layouts/partials/infobox/entry.html +15 -0
  79. package/hugo-theme/layouts/partials/infobox/items.html +26 -0
  80. package/hugo-theme/layouts/partials/infobox/section.html +56 -0
  81. package/hugo-theme/layouts/partials/infobox/value.html +43 -0
  82. package/hugo-theme/layouts/partials/infobox.html +57 -0
  83. package/hugo-theme/layouts/partials/page-body.html +154 -0
  84. package/hugo-theme/layouts/partials/place-map.html +9 -0
  85. package/hugo-theme/layouts/partials/related.html +121 -0
  86. package/hugo-theme/layouts/partials/search.html +84 -0
  87. package/hugo-theme/layouts/partials/site-url.html +32 -0
  88. package/hugo-theme/layouts/partials/tags.html +30 -0
  89. package/hugo-theme/layouts/partials/toc.html +28 -0
  90. package/hugo-theme/layouts/partials/warn-missing-banner.html +3 -0
  91. package/hugo-theme/layouts/shortcodes/cartouche.html +64 -0
  92. package/hugo-theme/layouts/shortcodes/exturl.html +12 -0
  93. package/hugo-theme/layouts/shortcodes/glyph.html +43 -0
  94. package/hugo-theme/layouts/shortcodes/pagelist.html +63 -0
  95. package/hugo-theme/static/css/sohl-icons.css +57 -0
  96. package/hugo-theme/static/css/style.css +2953 -0
  97. package/hugo-theme/static/fonts/game-icons-kb.woff2 +0 -0
  98. package/hugo-theme/static/img/banners/takheperu-hieroglyphs.svg +1 -0
  99. package/hugo-theme/static/img/cartouches/takheperu.svg +1 -0
  100. package/hugo-theme/theme.toml +11 -0
  101. package/manifest.mjs +30 -24
  102. package/package.json +2 -1
  103. package/packagebuild-metadata.jsonl +13 -13
  104. package/sohl/item-fields.mjs +4 -3
  105. package/types/content-config.d.mts +48 -1
  106. package/types/engine/anchors.d.mts +9 -4
  107. package/types/engine/art-fields.d.mts +64 -0
  108. package/types/engine/art-slots.d.mts +2 -1
  109. package/types/engine/asset-index.d.mts +43 -0
  110. package/types/engine/asset-replacement.d.mts +22 -0
  111. package/types/engine/asset-types.d.mts +18 -0
  112. package/types/engine/base-compiler.d.mts +0 -17
  113. package/types/engine/content-embeds.d.mts +16 -0
  114. package/types/engine/content-figures.d.mts +101 -0
  115. package/types/engine/content-footnotes.d.mts +7 -0
  116. package/types/engine/content-images.d.mts +78 -12
  117. package/types/engine/content-index.d.mts +8 -2
  118. package/types/engine/field-reference.d.mts +21 -0
  119. package/types/engine/field-spec.d.mts +20 -16
  120. package/types/engine/foreign-catalog.d.mts +39 -0
  121. package/types/engine/heading-attributes.d.mts +13 -8
  122. package/types/engine/helpers.d.mts +28 -10
  123. package/types/engine/infobox-registry.d.mts +4 -1
  124. package/types/engine/infobox.d.mts +6 -0
  125. package/types/engine/journals.d.mts +31 -9
  126. package/types/engine/lore-events.d.mts +14 -0
  127. package/types/engine/markdown-expressions.d.mts +23 -2
  128. package/types/engine/note-dates.d.mts +64 -1
  129. package/types/engine/pathnames.d.mts +26 -0
  130. package/types/engine/pdf-build.d.mts +28 -6
  131. package/types/engine/pdf-images.d.mts +52 -0
  132. package/types/engine/pdf-render.d.mts +12 -0
  133. package/types/engine/site-build.d.mts +14 -3
  134. package/types/engine/site-config.d.mts +9 -9
  135. package/types/engine/site-index.d.mts +54 -0
  136. package/types/engine/system-block.d.mts +26 -2
  137. package/types/hm3/actors.d.mts +18 -0
  138. package/types/manifest.d.mts +19 -23
  139. package/engine/content-captions.mjs +0 -230
  140. package/types/engine/content-captions.d.mts +0 -58
@@ -1,230 +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
- import { HEADING_LINE, parseHeadingLine, splitHeadingAttributes } from "./heading-attributes.mjs";
12
- import { imagesIn } from "./content-images.mjs";
13
-
14
- const parser = new MarkdownIt({ html: true }).use(footnotePlugin).use(deflistPlugin);
15
- const OPEN = /^:::caption\s+(\{[^}\n]*\})\s*$/;
16
- const CAPTION_LINE = /^:::caption\b/;
17
- const CLOSE = /^:::\s*$/;
18
-
19
- /** A caption's automatically determined kind. */
20
- export const CAPTION_NAMES = Object.freeze({
21
- code: "Code",
22
- table: "Table",
23
- figure: "Figure",
24
- prose: "Prose",
25
- });
26
-
27
- /** Number a sequence of captions with counters shared across documents. */
28
- export function numberCaptions(captions, counts) {
29
- return captions.map((caption) => {
30
- const number = (counts[caption.kind] ?? 0) + 1;
31
- counts[caption.kind] = number;
32
- return { ...caption, number, label: `${CAPTION_NAMES[caption.kind]} ${number}` };
33
- });
34
- }
35
-
36
- /**
37
- * Read caption directives and the Markdown block immediately after each one.
38
- * SQL fences are expanded before this pass, so their output is a table.
39
- *
40
- * @param {string} source - A note body with expanded tables.
41
- * @returns {{captions: Array<{id: string, text: string, kind: string, number: number,
42
- * label: string, line: number, blockStart: number, blockEnd: number,
43
- * directiveEnd: number}>, errors: Array<{line: number, column: number, message: string}>}}
44
- */
45
- export function scanCaptions(source) {
46
- const lines = String(source ?? "").split("\n");
47
- const captions = [];
48
- const errors = [];
49
- const counts = { code: 0, table: 0, figure: 0, prose: 0 };
50
- const ids = new Set();
51
- const headingIds = [];
52
- let codeFence = null;
53
-
54
- for (let i = 0; i < lines.length; i++) {
55
- const line = lines[i];
56
- const fence = /^ {0,3}(`{3,}|~{3,})/.exec(line);
57
- if (codeFence) {
58
- if (fence && fence[1][0] === codeFence[0] && fence[1].length >= codeFence.length)
59
- codeFence = null;
60
- continue;
61
- }
62
- if (fence) {
63
- codeFence = fence[1];
64
- continue;
65
- }
66
- const heading = HEADING_LINE.exec(line);
67
- const headingId = heading ? splitHeadingAttributes(heading[2]).id : "";
68
- if (headingId) headingIds.push({ id: slugify(headingId), line: i + 1 });
69
- if (!CAPTION_LINE.test(line)) continue;
70
- const open = OPEN.exec(line);
71
- if (!open) {
72
- errors.push({ line: i + 1, column: 1, message: "caption needs {#anchor} attributes" });
73
- continue;
74
- }
75
- const attributes = parseExtensionAttributes(open[1].slice(1, -1));
76
- if (
77
- attributes.problems.length ||
78
- !attributes.id ||
79
- attributes.classes.length ||
80
- Object.keys(attributes.values).length
81
- ) {
82
- errors.push({
83
- line: i + 1,
84
- column: 1,
85
- message: attributes.problems[0] ?? "caption accepts only {#anchor}",
86
- });
87
- continue;
88
- }
89
- const id = attributes.id;
90
- const start = i;
91
- let close = i + 1;
92
- while (close < lines.length && !CLOSE.test(lines[close])) close++;
93
- if (close === lines.length) {
94
- errors.push({ line: start + 1, column: 1, message: "caption needs a closing :::" });
95
- continue;
96
- }
97
- const caption = lines
98
- .slice(i + 1, close)
99
- .join("\n")
100
- .trim();
101
- if (!caption) errors.push({ line: start + 1, column: 1, message: "caption text is empty" });
102
- // An H1, or an anchored heading at any level, starts a Foundry
103
- // journal page — see `engine/content-blocks.mjs`'s identical refusal
104
- // for a named block.
105
- for (let at = i + 1; at < close; at++) {
106
- if (!parseHeadingLine(lines[at])?.startsPage) continue;
107
- errors.push({
108
- line: at + 1,
109
- column: 1,
110
- message:
111
- "a heading that starts a page cannot be written inside a caption — " +
112
- "keep an H1 or an anchored heading at the top level, or drop the " +
113
- "anchor and the level to stay inside it",
114
- });
115
- }
116
- if (ids.has(slugify(id)))
117
- errors.push({ line: start + 1, column: 1, message: `duplicate caption id "${id}"` });
118
- ids.add(slugify(id));
119
-
120
- let next = close + 1;
121
- while (next < lines.length && !lines[next].trim()) next++;
122
- if (next === lines.length || CAPTION_LINE.test(lines[next])) {
123
- errors.push({ line: start + 1, column: 1, message: "caption needs a following block" });
124
- i = close;
125
- continue;
126
- }
127
- const tokens = parser.parse(lines.slice(next).join("\n"), {});
128
- const first = tokens.find((token) => token.level === 0 && token.map);
129
- if (!first) {
130
- errors.push({ line: start + 1, column: 1, message: "caption needs a following block" });
131
- i = close;
132
- continue;
133
- }
134
- const blockEnd = next + first.map[1];
135
- const block = lines.slice(next, blockEnd).join("\n");
136
- const trimmedBlock = block.trim();
137
- // A markdown image is only a figure when it stands alone — nothing
138
- // else in the block, which is also what decides whether the image
139
- // plugin wraps it in a `<figure>` at all. A trailing footnote
140
- // reference, or any other trailing text, disqualifies it exactly as
141
- // it disqualifies `standsAlone`; such a block is prose with an inline
142
- // image in it, left as Markdown so a reference inside resolves
143
- // rather than classified as a figure whose own render strips nothing.
144
- const aloneImage =
145
- /^(?:!\[|!\[\[)/.test(trimmedBlock) &&
146
- imagesIn(trimmedBlock).some((image) => image.block);
147
- const kind =
148
- first.type === "table_open" ? "table"
149
- : first.type === "fence" && /^\s*sql\b/i.test(first.info ?? "") ? "table"
150
- : first.type === "fence" || first.type === "code_block" ? "code"
151
- : aloneImage || /^(?:<figure\b|<img\b)/.test(trimmedBlock) ? "figure"
152
- : "prose";
153
- const number = ++counts[kind];
154
- captions.push({
155
- id,
156
- text: caption,
157
- kind,
158
- number,
159
- label: `${CAPTION_NAMES[kind]} ${number}`,
160
- line: start + 1,
161
- blockStart: next,
162
- blockEnd,
163
- directiveEnd: close + 1,
164
- });
165
- i = close;
166
- }
167
- for (const heading of headingIds) {
168
- if (ids.has(heading.id))
169
- errors.push({
170
- line: heading.line,
171
- column: 1,
172
- message: `heading and caption declare the same anchor "${heading.id}"`,
173
- });
174
- }
175
- return { captions, errors };
176
- }
177
-
178
- /**
179
- * Render captioned blocks as HTML, leaving other Markdown untouched.
180
- *
181
- * A code, table or prose caption's block is left as Markdown inside the
182
- * wrapper, blank-line separated from its tags exactly as
183
- * {@link module:engine/content-blocks.renderBlocks} leaves a named block's
184
- * body — so the surrounding render sees it as part of its own document and a
185
- * footnote reference inside it resolves against the note's own definitions.
186
- *
187
- * A figure's block is still rendered here, because its `<figcaption>` has to
188
- * be stripped from HTML this pass controls directly; the surrounding render
189
- * never sees it as Markdown, so a footnote reference inside a figure caption
190
- * is not resolved, the same gap a block's own title carries.
191
- */
192
- export function renderCaptionBlocks(source, renderMarkdown = parser.render.bind(parser), numbers) {
193
- const { captions, errors } = scanCaptions(source);
194
- if (errors.length) return { markdown: source, errors };
195
- const byId = numbers ? new Map(numbers.map((entry) => [entry.id, entry])) : new Map();
196
- const lines = String(source ?? "").split("\n");
197
- const output = [];
198
- let cursor = 0;
199
- const escape = (value) =>
200
- String(value)
201
- .replace(/&/g, "&amp;")
202
- .replace(/</g, "&lt;")
203
- .replace(/>/g, "&gt;")
204
- .replace(/"/g, "&quot;");
205
- for (const caption of captions) {
206
- output.push(...lines.slice(cursor, caption.line - 1));
207
- const numbered = byId.get(caption.id) ?? caption;
208
- const block = lines.slice(caption.blockStart, caption.blockEnd).join("\n");
209
- const captionHtml = parser.renderInline(caption.text);
210
- output.push(
211
- `<div id="${escape(slugify(caption.id))}" class="content-caption content-caption-${caption.kind}">`,
212
- );
213
- if (caption.kind === "figure") {
214
- const html = renderMarkdown(block).replace(
215
- /<figcaption\b[^>]*>[\s\S]*?<\/figcaption>/i,
216
- "",
217
- );
218
- output.push(html.trim());
219
- } else {
220
- output.push("", block, "");
221
- }
222
- output.push(
223
- `<p class="content-caption-label">${escape(numbered.label)}: ${captionHtml}</p>`,
224
- "</div>",
225
- );
226
- cursor = caption.blockEnd;
227
- }
228
- output.push(...lines.slice(cursor));
229
- return { markdown: output.join("\n"), errors: [] };
230
- }
@@ -1,58 +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
- /**
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
- */
44
- export function renderCaptionBlocks(source: any, renderMarkdown: ((src: string, env?: import("markdown-it").Env) => string) | undefined, numbers: any): {
45
- markdown: any;
46
- errors: {
47
- line: number;
48
- column: number;
49
- message: string;
50
- }[];
51
- };
52
- /** A caption's automatically determined kind. */
53
- export const CAPTION_NAMES: Readonly<{
54
- code: "Code";
55
- table: "Table";
56
- figure: "Figure";
57
- prose: "Prose";
58
- }>;