@heroiclands/package-build 23.13.0 → 24.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +2 -1
  3. package/assets/preview/gitea.ini +12 -7
  4. package/assets/styles/content-base.css +150 -1
  5. package/bin/content-commands.mjs +356 -210
  6. package/bin/package-build.mjs +3 -2
  7. package/ci/check-banners.mjs +7 -7
  8. package/config.mjs +21 -6
  9. package/content-config.mjs +199 -80
  10. package/docs/api.md +93 -72
  11. package/docs/authoring/links-and-markup.md +316 -78
  12. package/docs/commands.md +248 -80
  13. package/docs/configuration.md +210 -102
  14. package/docs/content-format.md +2 -2
  15. package/docs/diagnostics.md +2 -2
  16. package/docs/getting-started.md +1 -1
  17. package/docs/guides/icons.md +1 -1
  18. package/docs/guides/module-project.md +5 -8
  19. package/docs/guides/release-and-deploy.md +2 -2
  20. package/docs/guides/site.md +155 -23
  21. package/docs/guides/system-project.md +1 -1
  22. package/docs/guides/writing-in-invented-scripts.md +1247 -0
  23. package/docs/index.md +1 -0
  24. package/docs/project-setup.md +28 -30
  25. package/docs/reference/format-details.md +589 -308
  26. package/docs/reference/note-types.md +40 -10
  27. package/docs/reference/theme-pages.md +127 -108
  28. package/docs/source-preview.md +15 -1
  29. package/engine/asset-shipping.mjs +22 -10
  30. package/engine/base-compiler.mjs +62 -104
  31. package/engine/changelog-lint.mjs +1 -1
  32. package/engine/content-address.mjs +1 -1
  33. package/engine/content-alerts.mjs +8 -4
  34. package/engine/content-blocks.mjs +26 -11
  35. package/engine/content-embeds.mjs +1 -1
  36. package/engine/content-expand.mjs +376 -0
  37. package/engine/content-figures.mjs +13 -6
  38. package/engine/content-format.yaml +8 -0
  39. package/engine/content-glyphs.mjs +213 -0
  40. package/engine/content-images.mjs +6 -7
  41. package/engine/content-links.mjs +3 -3
  42. package/engine/content-lint.mjs +1 -1
  43. package/engine/content-render.mjs +453 -0
  44. package/engine/content-scripts.mjs +326 -27
  45. package/engine/content-spans.mjs +4 -1
  46. package/engine/content-styles.mjs +91 -0
  47. package/engine/content-symbols.mjs +186 -0
  48. package/engine/content-verse.mjs +6 -18
  49. package/engine/core-report.mjs +169 -0
  50. package/engine/core-vocabulary.mjs +208 -0
  51. package/engine/dictionary-html.mjs +119 -0
  52. package/engine/field-reference.mjs +1 -1
  53. package/engine/frontmatter-lint.mjs +1 -1
  54. package/engine/heading-attributes.mjs +89 -0
  55. package/engine/helpers.mjs +61 -101
  56. package/engine/homepage.mjs +17 -29
  57. package/engine/html-minify.mjs +70 -0
  58. package/engine/index.mjs +8 -5
  59. package/engine/language-rows.mjs +8 -2
  60. package/engine/language-terms.mjs +115 -14
  61. package/engine/language-words.mjs +12 -0
  62. package/engine/logographic.mjs +255 -0
  63. package/engine/map-lexicon.mjs +221 -25
  64. package/engine/map-places.mjs +5 -4
  65. package/engine/markdown-expressions.mjs +0 -2
  66. package/engine/markdownlint-run.mjs +9 -3
  67. package/engine/markup-validation.mjs +21 -0
  68. package/engine/metre-declaration.mjs +134 -0
  69. package/engine/note-matter.mjs +2 -2
  70. package/engine/note-vocabulary.mjs +187 -3
  71. package/engine/pdf-build.mjs +68 -133
  72. package/engine/pdf-render.mjs +102 -44
  73. package/engine/post-fields.mjs +157 -0
  74. package/engine/preview-index.mjs +130 -0
  75. package/engine/preview-source.mjs +450 -0
  76. package/engine/project-init.mjs +5 -12
  77. package/engine/prose-config.mjs +3 -3
  78. package/engine/run-words.mjs +3 -2
  79. package/engine/script-emphasis-terms.mjs +38 -0
  80. package/engine/script-emphasis.mjs +295 -0
  81. package/engine/script-font-delivery.mjs +4 -4
  82. package/engine/site-build.mjs +265 -313
  83. package/engine/site-config.mjs +26 -238
  84. package/engine/site-files.mjs +175 -0
  85. package/engine/site-index.mjs +9 -4
  86. package/engine/site-preview.mjs +50 -523
  87. package/engine/site-reach.mjs +213 -0
  88. package/engine/site-render.mjs +668 -0
  89. package/engine/site-root.mjs +90 -39
  90. package/engine/site-search.mjs +161 -0
  91. package/engine/site-serve.mjs +274 -0
  92. package/engine/site-theme-banner.mjs +154 -0
  93. package/engine/site-theme-urls.mjs +125 -0
  94. package/engine/site-theme.mjs +939 -0
  95. package/engine/sql-tables.mjs +9 -6
  96. package/engine/text-maps.mjs +57 -10
  97. package/engine/typographer.mjs +248 -0
  98. package/engine/verse-metre.mjs +357 -0
  99. package/engine/wikilink-syntax.mjs +29 -47
  100. package/engine/wikilinks.mjs +758 -279
  101. package/engine/word-forms.mjs +39 -24
  102. package/engine/word-rows.mjs +12 -2
  103. package/engine/word-terms.mjs +29 -4
  104. package/manifest.mjs +3 -4
  105. package/package.json +10 -7
  106. package/{hugo-theme → site-theme}/data/banners.yaml +6 -10
  107. package/site-theme/layouts/base.hbs +31 -0
  108. package/site-theme/layouts/blog-list.hbs +30 -0
  109. package/site-theme/layouts/blog-post.hbs +40 -0
  110. package/site-theme/layouts/home.hbs +35 -0
  111. package/site-theme/layouts/list.hbs +20 -0
  112. package/site-theme/layouts/not-found.hbs +31 -0
  113. package/site-theme/layouts/page.hbs +7 -0
  114. package/site-theme/partials/copy-buttons.hbs +37 -0
  115. package/site-theme/partials/draft-notice.hbs +5 -0
  116. package/site-theme/partials/exturl.hbs +2 -0
  117. package/{hugo-theme/layouts/partials/footer.html → site-theme/partials/footer.hbs} +10 -7
  118. package/site-theme/partials/header.hbs +44 -0
  119. package/site-theme/partials/hero-banner.hbs +11 -0
  120. package/site-theme/partials/infobox-entry.hbs +2 -0
  121. package/site-theme/partials/infobox-section.hbs +31 -0
  122. package/site-theme/partials/infobox-value.hbs +2 -0
  123. package/site-theme/partials/infobox.hbs +18 -0
  124. package/site-theme/partials/list-members.hbs +9 -0
  125. package/site-theme/partials/nav-links.hbs +5 -0
  126. package/site-theme/partials/page-body.hbs +48 -0
  127. package/site-theme/partials/page-list.hbs +14 -0
  128. package/site-theme/partials/related.hbs +24 -0
  129. package/site-theme/partials/search-meta.hbs +6 -0
  130. package/site-theme/partials/search-names.hbs +2 -0
  131. package/site-theme/partials/search.hbs +52 -0
  132. package/site-theme/partials/tags.hbs +4 -0
  133. package/site-theme/partials/toc.hbs +7 -0
  134. package/{hugo-theme → site-theme}/static/css/style.css +14 -0
  135. package/stage.mjs +1 -2
  136. package/types/config.d.mts +8 -4
  137. package/types/content-config.d.mts +15 -15
  138. package/types/engine/asset-shipping.d.mts +5 -2
  139. package/types/engine/base-compiler.d.mts +20 -2
  140. package/types/engine/content-address.d.mts +1 -1
  141. package/types/engine/content-alerts.d.mts +8 -2
  142. package/types/engine/content-blocks.d.mts +11 -4
  143. package/types/engine/content-expand.d.mts +288 -0
  144. package/types/engine/content-figures.d.mts +11 -3
  145. package/types/engine/content-glyphs.d.mts +85 -0
  146. package/types/engine/content-images.d.mts +6 -7
  147. package/types/engine/content-links.d.mts +1 -1
  148. package/types/engine/content-render.d.mts +199 -0
  149. package/types/engine/content-scripts.d.mts +62 -0
  150. package/types/engine/content-spans.d.mts +1 -1
  151. package/types/engine/content-styles.d.mts +33 -0
  152. package/types/engine/content-symbols.d.mts +35 -0
  153. package/types/engine/core-report.d.mts +45 -0
  154. package/types/engine/core-vocabulary.d.mts +53 -0
  155. package/types/engine/dictionary-html.d.mts +8 -0
  156. package/types/engine/field-reference.d.mts +1 -1
  157. package/types/engine/heading-attributes.d.mts +20 -0
  158. package/types/engine/helpers.d.mts +3 -10
  159. package/types/engine/homepage.d.mts +11 -24
  160. package/types/engine/html-minify.d.mts +9 -0
  161. package/types/engine/index.d.mts +2 -1
  162. package/types/engine/language-terms.d.mts +24 -2
  163. package/types/engine/logographic.d.mts +45 -0
  164. package/types/engine/map-lexicon.d.mts +48 -8
  165. package/types/engine/map-places.d.mts +8 -0
  166. package/types/engine/markdown-expressions.d.mts +0 -2
  167. package/types/engine/metre-declaration.d.mts +39 -0
  168. package/types/engine/pdf-render.d.mts +6 -2
  169. package/types/engine/post-fields.d.mts +44 -0
  170. package/types/engine/preview-index.d.mts +72 -0
  171. package/types/engine/preview-source.d.mts +51 -0
  172. package/types/engine/script-emphasis-terms.d.mts +21 -0
  173. package/types/engine/script-emphasis.d.mts +84 -0
  174. package/types/engine/script-font-delivery.d.mts +4 -4
  175. package/types/engine/site-build.d.mts +71 -113
  176. package/types/engine/site-config.d.mts +14 -134
  177. package/types/engine/site-files.d.mts +73 -0
  178. package/types/engine/site-index.d.mts +11 -3
  179. package/types/engine/site-preview.d.mts +15 -19
  180. package/types/engine/site-reach.d.mts +76 -0
  181. package/types/engine/site-render.d.mts +173 -0
  182. package/types/engine/site-root.d.mts +14 -4
  183. package/types/engine/site-search.d.mts +54 -0
  184. package/types/engine/site-serve.d.mts +44 -0
  185. package/types/engine/site-theme-banner.d.mts +51 -0
  186. package/types/engine/site-theme-urls.d.mts +60 -0
  187. package/types/engine/site-theme.d.mts +127 -0
  188. package/types/engine/text-maps.d.mts +17 -3
  189. package/types/engine/typographer.d.mts +11 -0
  190. package/types/engine/verse-metre.d.mts +131 -0
  191. package/types/engine/wikilink-syntax.d.mts +30 -48
  192. package/types/engine/wikilinks.d.mts +330 -14
  193. package/types/engine/word-forms.d.mts +3 -2
  194. package/types/manifest.d.mts +3 -4
  195. package/types/stage.d.mts +1 -2
  196. package/types/vendor/markdownlint.d.mts +8 -0
  197. package/vendor/markdownlint.mjs +20388 -0
  198. package/engine/web-wikilinks.mjs +0 -594
  199. package/hugo-theme/layouts/404.html +0 -85
  200. package/hugo-theme/layouts/_default/baseof.html +0 -67
  201. package/hugo-theme/layouts/_default/list.html +0 -139
  202. package/hugo-theme/layouts/_default/single.html +0 -4
  203. package/hugo-theme/layouts/blog/list.html +0 -54
  204. package/hugo-theme/layouts/blog-post/single.html +0 -36
  205. package/hugo-theme/layouts/index.html +0 -75
  206. package/hugo-theme/layouts/partials/banner-url.html +0 -141
  207. package/hugo-theme/layouts/partials/cdn-url.html +0 -36
  208. package/hugo-theme/layouts/partials/dictionary-lists.html +0 -58
  209. package/hugo-theme/layouts/partials/draft-notice.html +0 -41
  210. package/hugo-theme/layouts/partials/draft-state.html +0 -26
  211. package/hugo-theme/layouts/partials/header.html +0 -35
  212. package/hugo-theme/layouts/partials/hero-banner.html +0 -85
  213. package/hugo-theme/layouts/partials/infobox/entry.html +0 -18
  214. package/hugo-theme/layouts/partials/infobox/items.html +0 -26
  215. package/hugo-theme/layouts/partials/infobox/section.html +0 -56
  216. package/hugo-theme/layouts/partials/infobox/value.html +0 -43
  217. package/hugo-theme/layouts/partials/infobox.html +0 -57
  218. package/hugo-theme/layouts/partials/page-body.html +0 -152
  219. package/hugo-theme/layouts/partials/related.html +0 -138
  220. package/hugo-theme/layouts/partials/search.html +0 -84
  221. package/hugo-theme/layouts/partials/site-url.html +0 -32
  222. package/hugo-theme/layouts/partials/tags.html +0 -30
  223. package/hugo-theme/layouts/partials/toc.html +0 -28
  224. package/hugo-theme/layouts/partials/warn-missing-banner.html +0 -3
  225. package/hugo-theme/layouts/shortcodes/cartouche.html +0 -64
  226. package/hugo-theme/layouts/shortcodes/exturl.html +0 -12
  227. package/hugo-theme/layouts/shortcodes/glyph.html +0 -43
  228. package/hugo-theme/layouts/shortcodes/pagelist.html +0 -63
  229. package/hugo-theme/theme.toml +0 -11
  230. package/types/engine/web-wikilinks.d.mts +0 -107
  231. /package/{hugo-theme → site-theme}/data/banner-kinds.yaml +0 -0
  232. /package/{hugo-theme → site-theme}/static/css/sohl-icons.css +0 -0
  233. /package/{hugo-theme → site-theme}/static/fonts/game-icons-kb.woff2 +0 -0
  234. /package/{hugo-theme → site-theme}/static/img/banners/takheperu-hieroglyphs.svg +0 -0
  235. /package/{hugo-theme → site-theme}/static/img/cartouches/takheperu.svg +0 -0
  236. /package/{hugo-theme → site-theme}/static/js/inline-spoilers.js +0 -0
@@ -0,0 +1,213 @@
1
+ /*
2
+ * This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
3
+ * Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
4
+ *
5
+ * This work is licensed under the GNU General Public License v3.0 (GPLv3).
6
+ * You may copy, modify, and distribute it under the terms of that license.
7
+ *
8
+ * For full terms, see the LICENSE.md file in the project root or visit:
9
+ * https://www.gnu.org/licenses/gpl-3.0.html
10
+ *
11
+ * SPDX-License-Identifier: GPL-3.0-or-later
12
+ */
13
+
14
+ /**
15
+ * **The pages a reader can reach.** Every note is written as a page, but what
16
+ * a reader finds is what the homepage leads to: the sitemap and the search
17
+ * index cover the pages reachable from the homepage by the links the rendered
18
+ * HTML carries, and no others. An unlinked page is still served at its
19
+ * address, so an author reads a page in place before linking it to publish it.
20
+ *
21
+ * Reachability is read from the rendered pages, never from the source notes:
22
+ * the `<a>` and `<link>` targets that lie inside the site's base path, the
23
+ * menu and the chrome included. A link that lands on a feed makes the pages
24
+ * the feed lists reachable too, because a feed is a list of links.
25
+ *
26
+ * @module
27
+ */
28
+
29
+ /** A URL with a scheme, or a protocol-relative one: not a path inside the site. */
30
+ const EXTERNAL = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
31
+
32
+ /** An `<a>` or `<link>` and its `href`, quoted either way or bare. */
33
+ const LINK = /<(?:a|link)\b[^>]*?\shref=(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
34
+
35
+ /**
36
+ * The internal targets a page links.
37
+ *
38
+ * @param {string} html - A rendered page.
39
+ * @param {string} from - The page's own address, beginning with `/`.
40
+ * @param {string} base - The site's base path, beginning and ending with `/`.
41
+ * @returns {string[]} Absolute paths inside the base, without fragment or query.
42
+ */
43
+ export function internalLinks(html, from, base) {
44
+ const found = [];
45
+ for (const match of html.matchAll(LINK)) {
46
+ const href = (match[1] ?? match[2] ?? match[3] ?? "").trim().replace(/&amp;/g, "&");
47
+ if (!href || href.startsWith("#") || EXTERNAL.test(href)) continue;
48
+ let pathname;
49
+ try {
50
+ pathname = new URL(href, `http://site.invalid${from}`).pathname;
51
+ } catch {
52
+ continue;
53
+ }
54
+ if (pathname.startsWith(base)) found.push(decodeURI(pathname));
55
+ }
56
+ return found;
57
+ }
58
+
59
+ /**
60
+ * The address a link to a file names a page by: `…/index.html` is its
61
+ * directory, and a bare directory ends in a slash.
62
+ *
63
+ * @param {string} pathname - An absolute path.
64
+ * @returns {string} The page address it would be keyed by.
65
+ */
66
+ export function pageAddress(pathname) {
67
+ if (pathname.endsWith("/index.html")) return pathname.slice(0, -"index.html".length);
68
+ return pathname.endsWith("/") || /\.[a-z0-9]+$/i.test(pathname) ? pathname : `${pathname}/`;
69
+ }
70
+
71
+ /**
72
+ * The pages reachable from the homepage.
73
+ *
74
+ * @param {object} options - The inputs.
75
+ * @param {string} options.base - The site's base path, beginning and ending
76
+ * with `/`; the homepage is at the base.
77
+ * @param {(url: string) => string|undefined} options.html - A page's rendered
78
+ * HTML by its address, or `undefined` where the site has no such page.
79
+ * @param {(pathname: string) => readonly string[]} [options.expand] - The page
80
+ * addresses a linked non-page file, such as a feed, lists.
81
+ * @returns {Set<string>} The address of every reachable page, the homepage
82
+ * included.
83
+ */
84
+ export function reachablePages({ base, html, expand = () => [] }) {
85
+ /** @type {Set<string>} */
86
+ const reached = new Set();
87
+ const queue = [base];
88
+ const seenFiles = new Set();
89
+ while (queue.length) {
90
+ const url = queue.pop();
91
+ if (reached.has(url)) continue;
92
+ const body = html(url);
93
+ if (body === undefined) continue;
94
+ reached.add(url);
95
+ for (const target of internalLinks(body, url, base)) {
96
+ const address = pageAddress(target);
97
+ if (html(address) !== undefined) {
98
+ queue.push(address);
99
+ } else if (!seenFiles.has(target)) {
100
+ seenFiles.add(target);
101
+ for (const listed of expand(target)) queue.push(pageAddress(listed));
102
+ }
103
+ }
104
+ }
105
+ return reached;
106
+ }
107
+
108
+ /**
109
+ * The page addresses an Atom feed lists, as paths.
110
+ *
111
+ * @param {string} xml - The feed.
112
+ * @returns {string[]} The `href` of every `<link>` that is an absolute URL,
113
+ * as its path.
114
+ */
115
+ export function feedPaths(xml) {
116
+ const paths = [];
117
+ for (const match of xml.matchAll(/<link\b[^>]*?\shref="([^"]*)"/g)) {
118
+ try {
119
+ paths.push(new URL(match[1].replace(/&amp;/g, "&")).pathname);
120
+ } catch {
121
+ // A relative or malformed link names no page.
122
+ }
123
+ }
124
+ return paths;
125
+ }
126
+
127
+ /**
128
+ * The comment that separates a page's authored text from the sections the
129
+ * build generated after it, in the HTML the renderer writes.
130
+ *
131
+ * @type {string}
132
+ */
133
+ export const GENERATED_MARK = "<!--generated-sections-->";
134
+
135
+ /**
136
+ * A page body split at {@link GENERATED_MARK}.
137
+ *
138
+ * @param {string} html - A page body.
139
+ * @returns {{authored: string, generated: string}} The author's text, and the
140
+ * generated sections after it, `""` where there are none.
141
+ */
142
+ export function splitContent(html) {
143
+ const text = String(html ?? "");
144
+ const at = text.indexOf(GENERATED_MARK);
145
+ if (at < 0) return { authored: text, generated: "" };
146
+ return { authored: text.slice(0, at), generated: text.slice(at + GENERATED_MARK.length) };
147
+ }
148
+
149
+ /** An `<a>` element with an `href`, whole. */
150
+ const ANCHOR = /<a\b[^>]*?\shref=(?:"([^"]*)"|'([^']*)'|([^\s>]+))[^>]*>[\s\S]*?<\/a>/gi;
151
+
152
+ /**
153
+ * Generated sections with the entries that lead outside a set of pages taken
154
+ * out.
155
+ *
156
+ * An entry is a list item, a table row, or a link in a paragraph's comma
157
+ * separated run; a paragraph, list or table left with no entry goes, and so
158
+ * does a section left with nothing under its heading. A link to anything that
159
+ * is not a page of the site is kept, and so is text that was never a link.
160
+ *
161
+ * @param {string} html - The generated sections, rendered.
162
+ * @param {(href: string) => boolean} outside - Whether an `href` names a page
163
+ * of the site that is not reachable.
164
+ * @returns {string} The sections, or `""` when nothing is left.
165
+ */
166
+ export function pruneGenerated(html, outside) {
167
+ const dead = (anchor) => {
168
+ const href = new RegExp(ANCHOR.source, "i").exec(anchor);
169
+ const value = href ? (href[1] ?? href[2] ?? href[3] ?? "").replace(/&amp;/g, "&") : "";
170
+ return value !== "" && outside(value);
171
+ };
172
+ const holdsDead = (fragment) => [...fragment.matchAll(ANCHOR)].some((match) => dead(match[0]));
173
+
174
+ // Each section runs from its heading to the next one.
175
+ const parts = String(html ?? "").split(/(?=<h1\b)/);
176
+ const kept = parts.map((part) => {
177
+ if (!holdsDead(part)) return part;
178
+ let out = part
179
+ .replace(/<li\b[^>]*>(?:(?!<\/li>)[\s\S])*<\/li>\s*/g, (item) =>
180
+ holdsDead(item) ? "" : item,
181
+ )
182
+ .replace(/<tr\b[^>]*>(?:(?!<\/tr>)[\s\S])*<\/tr>\s*/g, (row) =>
183
+ holdsDead(row) ? "" : row,
184
+ )
185
+ .replace(/<p\b[^>]*>(?:(?!<\/p>)[\s\S])*<\/p>\s*/g, (paragraph) => {
186
+ if (!holdsDead(paragraph)) return paragraph;
187
+ const trimmed = paragraph.replace(ANCHOR, (anchor) =>
188
+ dead(anchor) ? "\u0000" : anchor,
189
+ );
190
+ const entries = trimmed
191
+ .replace(/<\/?p\b[^>]*>/g, "")
192
+ .replace(/^\s*<strong>[^<]*<\/strong>/, "")
193
+ .replace(/\u0000/g, "")
194
+ .replace(/[\s,]+/g, " ")
195
+ .replace(/<[^>]*>/g, "")
196
+ .trim();
197
+ if (!entries) return "";
198
+ return trimmed
199
+ .replace(/\u0000,\s*/g, "")
200
+ .replace(/,\s*\u0000/g, "")
201
+ .replace(/\u0000/g, "");
202
+ });
203
+ out = out
204
+ .replace(/<(ul|ol)\b[^>]*>\s*<\/\1>\s*/g, "")
205
+ .replace(/<tbody\b[^>]*>\s*<\/tbody>\s*/g, "")
206
+ .replace(/<table\b[^>]*>(?:(?!<tr\b)[\s\S])*<\/table>\s*/g, (table) =>
207
+ /<tr\b/.test(table) ? table : "",
208
+ );
209
+ const rest = out.replace(/^<h1\b[^>]*>[\s\S]*?<\/h1>/, "").replace(/<!--[\s\S]*?-->/g, "");
210
+ return rest.trim() === "" ? "" : out;
211
+ });
212
+ return kept.join("");
213
+ }