@heroiclands/package-build 23.12.8 → 24.0.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.
- package/CHANGELOG.md +84 -0
- package/README.md +3 -2
- package/assets/preview/gitea.ini +12 -7
- package/assets/styles/content-base.css +293 -24
- package/bin/content-commands.mjs +370 -213
- package/bin/package-build-preview.mjs +2 -2
- package/bin/package-build.mjs +3 -2
- package/ci/check-banners.mjs +7 -7
- package/config.mjs +21 -6
- package/content-config.mjs +199 -80
- package/docs/api.md +110 -89
- package/docs/authoring/links-and-markup.md +469 -97
- package/docs/commands.md +259 -78
- package/docs/configuration.md +211 -103
- package/docs/content-format.md +4 -4
- package/docs/diagnostics.md +44 -2
- package/docs/getting-started.md +1 -1
- package/docs/guides/icons.md +1 -1
- package/docs/guides/module-project.md +5 -8
- package/docs/guides/release-and-deploy.md +2 -2
- package/docs/guides/site.md +112 -23
- package/docs/guides/system-project.md +1 -1
- package/docs/guides/writing-in-invented-scripts.md +1247 -0
- package/docs/index.md +1 -0
- package/docs/project-setup.md +28 -30
- package/docs/reference/format-details.md +677 -319
- package/docs/reference/note-types.md +47 -10
- package/docs/reference/theme-pages.md +178 -108
- package/docs/source-preview.md +16 -2
- package/engine/anchors.mjs +2 -2
- package/engine/asset-shipping.mjs +22 -10
- package/engine/base-compiler.mjs +62 -104
- package/engine/changelog-lint.mjs +1 -1
- package/engine/code-fences.mjs +3 -3
- package/engine/content-address.mjs +1 -1
- package/engine/content-alerts.mjs +8 -4
- package/engine/content-blocks.mjs +29 -14
- package/engine/content-embeds.mjs +1 -1
- package/engine/content-expand.mjs +376 -0
- package/engine/content-figures.mjs +18 -11
- package/engine/content-format.yaml +8 -0
- package/engine/content-glyphs.mjs +213 -0
- package/engine/content-images.mjs +6 -7
- package/engine/content-index.mjs +1 -1
- package/engine/content-links.mjs +3 -3
- package/engine/content-lint.mjs +1 -1
- package/engine/content-render.mjs +453 -0
- package/engine/content-scripts.mjs +376 -43
- package/engine/content-spans.mjs +6 -3
- package/engine/content-styles.mjs +91 -0
- package/engine/content-symbols.mjs +186 -0
- package/engine/content-verse.mjs +738 -0
- package/engine/core-report.mjs +169 -0
- package/engine/core-vocabulary.mjs +208 -0
- package/engine/dictionary-html.mjs +119 -0
- package/engine/dictionary-rows.mjs +15 -4
- package/engine/field-reference.mjs +5 -5
- package/engine/first-use.mjs +17 -27
- package/engine/frontmatter-lint.mjs +1 -1
- package/engine/heading-attributes.mjs +89 -0
- package/engine/helpers.mjs +61 -101
- package/engine/homepage.mjs +17 -29
- package/engine/html-minify.mjs +70 -0
- package/engine/index.mjs +8 -5
- package/engine/key-case-collisions.mjs +136 -0
- package/engine/language-data.mjs +203 -6
- package/engine/language-rows.mjs +8 -2
- package/engine/language-terms.mjs +186 -16
- package/engine/language-words.mjs +12 -0
- package/engine/logographic.mjs +255 -0
- package/engine/map-lexicon.mjs +262 -47
- package/engine/map-places.mjs +5 -4
- package/engine/markdown-expressions.mjs +0 -2
- package/engine/markdownlint-run.mjs +9 -3
- package/engine/markup-validation.mjs +21 -0
- package/engine/metre-declaration.mjs +134 -0
- package/engine/names.mjs +36 -9
- package/engine/note-matter.mjs +66 -0
- package/engine/note-vocabulary.mjs +187 -3
- package/engine/pdf-build.mjs +69 -133
- package/engine/pdf-render.mjs +205 -58
- package/engine/post-fields.mjs +157 -0
- package/engine/preview-index.mjs +130 -0
- package/engine/preview-source.mjs +450 -0
- package/engine/project-init.mjs +5 -12
- package/engine/prose-config.mjs +3 -3
- package/engine/read-stdin.mjs +29 -0
- package/engine/run-rows.mjs +2 -2
- package/engine/run-words.mjs +6 -112
- package/engine/script-emphasis-terms.mjs +38 -0
- package/engine/script-emphasis.mjs +295 -0
- package/engine/script-font-delivery.mjs +4 -4
- package/engine/site-build.mjs +260 -315
- package/engine/site-config.mjs +26 -238
- package/engine/site-files.mjs +175 -0
- package/engine/site-index.mjs +9 -4
- package/engine/site-preview.mjs +52 -525
- package/engine/site-render.mjs +593 -0
- package/engine/site-root.mjs +22 -13
- package/engine/site-search.mjs +161 -0
- package/engine/site-serve.mjs +273 -0
- package/engine/site-theme-banner.mjs +154 -0
- package/engine/site-theme-urls.mjs +125 -0
- package/engine/site-theme.mjs +926 -0
- package/engine/sql-tables.mjs +20 -3
- package/engine/text-maps.mjs +57 -10
- package/engine/typographer.mjs +248 -0
- package/engine/verse-metre.mjs +357 -0
- package/engine/wikilink-syntax.mjs +29 -47
- package/engine/wikilinks.mjs +758 -279
- package/engine/word-forms.mjs +364 -0
- package/engine/word-rows.mjs +12 -2
- package/engine/word-terms.mjs +38 -4
- package/manifest.mjs +3 -4
- package/package.json +11 -8
- package/{hugo-theme → site-theme}/data/banners.yaml +6 -10
- package/site-theme/layouts/base.hbs +31 -0
- package/site-theme/layouts/blog-list.hbs +30 -0
- package/site-theme/layouts/blog-post.hbs +40 -0
- package/site-theme/layouts/home.hbs +35 -0
- package/site-theme/layouts/list.hbs +20 -0
- package/site-theme/layouts/not-found.hbs +31 -0
- package/site-theme/layouts/page.hbs +7 -0
- package/site-theme/partials/copy-buttons.hbs +37 -0
- package/site-theme/partials/draft-notice.hbs +5 -0
- package/site-theme/partials/exturl.hbs +2 -0
- package/{hugo-theme/layouts/partials/footer.html → site-theme/partials/footer.hbs} +10 -7
- package/site-theme/partials/header.hbs +44 -0
- package/site-theme/partials/hero-banner.hbs +11 -0
- package/site-theme/partials/infobox-entry.hbs +2 -0
- package/site-theme/partials/infobox-section.hbs +31 -0
- package/site-theme/partials/infobox-value.hbs +2 -0
- package/site-theme/partials/infobox.hbs +18 -0
- package/site-theme/partials/list-members.hbs +9 -0
- package/site-theme/partials/nav-links.hbs +5 -0
- package/site-theme/partials/page-body.hbs +48 -0
- package/site-theme/partials/page-list.hbs +14 -0
- package/site-theme/partials/related.hbs +24 -0
- package/site-theme/partials/search-meta.hbs +6 -0
- package/site-theme/partials/search-names.hbs +2 -0
- package/site-theme/partials/search.hbs +52 -0
- package/site-theme/partials/tags.hbs +4 -0
- package/site-theme/partials/toc.hbs +7 -0
- package/{hugo-theme → site-theme}/static/css/style.css +106 -14
- package/stage.mjs +1 -2
- package/types/config.d.mts +8 -4
- package/types/content-config.d.mts +15 -15
- package/types/engine/asset-shipping.d.mts +5 -2
- package/types/engine/base-compiler.d.mts +20 -2
- package/types/engine/code-fences.d.mts +3 -3
- package/types/engine/content-address.d.mts +1 -1
- package/types/engine/content-alerts.d.mts +8 -2
- package/types/engine/content-blocks.d.mts +11 -4
- package/types/engine/content-expand.d.mts +288 -0
- package/types/engine/content-figures.d.mts +11 -3
- package/types/engine/content-glyphs.d.mts +85 -0
- package/types/engine/content-images.d.mts +6 -7
- package/types/engine/content-links.d.mts +1 -1
- package/types/engine/content-render.d.mts +199 -0
- package/types/engine/content-scripts.d.mts +70 -4
- package/types/engine/content-spans.d.mts +1 -1
- package/types/engine/content-styles.d.mts +33 -0
- package/types/engine/content-symbols.d.mts +35 -0
- package/types/engine/content-verse.d.mts +240 -0
- package/types/engine/core-report.d.mts +45 -0
- package/types/engine/core-vocabulary.d.mts +53 -0
- package/types/engine/dictionary-html.d.mts +8 -0
- package/types/engine/dictionary-rows.d.mts +10 -0
- package/types/engine/field-reference.d.mts +2 -2
- package/types/engine/heading-attributes.d.mts +20 -0
- package/types/engine/helpers.d.mts +3 -10
- package/types/engine/homepage.d.mts +11 -24
- package/types/engine/html-minify.d.mts +9 -0
- package/types/engine/index.d.mts +2 -1
- package/types/engine/key-case-collisions.d.mts +28 -0
- package/types/engine/language-data.d.mts +0 -2
- package/types/engine/language-terms.d.mts +24 -2
- package/types/engine/logographic.d.mts +45 -0
- package/types/engine/map-lexicon.d.mts +66 -13
- package/types/engine/map-places.d.mts +8 -0
- package/types/engine/markdown-expressions.d.mts +0 -2
- package/types/engine/metre-declaration.d.mts +39 -0
- package/types/engine/names.d.mts +34 -3
- package/types/engine/note-matter.d.mts +48 -0
- package/types/engine/pdf-render.d.mts +6 -2
- package/types/engine/post-fields.d.mts +44 -0
- package/types/engine/preview-index.d.mts +72 -0
- package/types/engine/preview-source.d.mts +51 -0
- package/types/engine/read-stdin.d.mts +11 -0
- package/types/engine/run-rows.d.mts +2 -2
- package/types/engine/run-words.d.mts +0 -15
- package/types/engine/script-emphasis-terms.d.mts +21 -0
- package/types/engine/script-emphasis.d.mts +84 -0
- package/types/engine/script-font-delivery.d.mts +4 -4
- package/types/engine/site-build.d.mts +71 -113
- package/types/engine/site-config.d.mts +14 -134
- package/types/engine/site-files.d.mts +73 -0
- package/types/engine/site-index.d.mts +11 -3
- package/types/engine/site-preview.d.mts +15 -19
- package/types/engine/site-render.d.mts +171 -0
- package/types/engine/site-root.d.mts +12 -4
- package/types/engine/site-search.d.mts +54 -0
- package/types/engine/site-serve.d.mts +44 -0
- package/types/engine/site-theme-banner.d.mts +51 -0
- package/types/engine/site-theme-urls.d.mts +60 -0
- package/types/engine/site-theme.d.mts +127 -0
- package/types/engine/sql-tables.d.mts +4 -1
- package/types/engine/text-maps.d.mts +17 -3
- package/types/engine/typographer.d.mts +11 -0
- package/types/engine/verse-metre.d.mts +131 -0
- package/types/engine/wikilink-syntax.d.mts +30 -48
- package/types/engine/wikilinks.d.mts +330 -14
- package/types/engine/word-forms.d.mts +83 -0
- package/types/manifest.d.mts +3 -4
- package/types/stage.d.mts +1 -2
- package/types/vendor/markdownlint.d.mts +8 -0
- package/vendor/markdownlint.mjs +20388 -0
- package/engine/content-poetry.mjs +0 -240
- package/engine/web-wikilinks.mjs +0 -594
- package/hugo-theme/layouts/404.html +0 -85
- package/hugo-theme/layouts/_default/baseof.html +0 -67
- package/hugo-theme/layouts/_default/list.html +0 -139
- package/hugo-theme/layouts/_default/single.html +0 -4
- package/hugo-theme/layouts/blog/list.html +0 -54
- package/hugo-theme/layouts/blog-post/single.html +0 -36
- package/hugo-theme/layouts/index.html +0 -75
- package/hugo-theme/layouts/partials/banner-url.html +0 -141
- package/hugo-theme/layouts/partials/cdn-url.html +0 -36
- package/hugo-theme/layouts/partials/dictionary-lists.html +0 -58
- package/hugo-theme/layouts/partials/draft-notice.html +0 -41
- package/hugo-theme/layouts/partials/draft-state.html +0 -26
- package/hugo-theme/layouts/partials/header.html +0 -35
- package/hugo-theme/layouts/partials/hero-banner.html +0 -85
- package/hugo-theme/layouts/partials/infobox/entry.html +0 -18
- package/hugo-theme/layouts/partials/infobox/items.html +0 -26
- package/hugo-theme/layouts/partials/infobox/section.html +0 -56
- package/hugo-theme/layouts/partials/infobox/value.html +0 -43
- package/hugo-theme/layouts/partials/infobox.html +0 -57
- package/hugo-theme/layouts/partials/page-body.html +0 -152
- package/hugo-theme/layouts/partials/related.html +0 -138
- package/hugo-theme/layouts/partials/search.html +0 -84
- package/hugo-theme/layouts/partials/site-url.html +0 -32
- package/hugo-theme/layouts/partials/tags.html +0 -30
- package/hugo-theme/layouts/partials/toc.html +0 -28
- package/hugo-theme/layouts/partials/warn-missing-banner.html +0 -3
- package/hugo-theme/layouts/shortcodes/cartouche.html +0 -64
- package/hugo-theme/layouts/shortcodes/exturl.html +0 -12
- package/hugo-theme/layouts/shortcodes/glyph.html +0 -43
- package/hugo-theme/layouts/shortcodes/pagelist.html +0 -63
- package/hugo-theme/theme.toml +0 -11
- package/types/engine/content-poetry.d.mts +0 -58
- package/types/engine/web-wikilinks.d.mts +0 -107
- /package/{hugo-theme → site-theme}/data/banner-kinds.yaml +0 -0
- /package/{hugo-theme → site-theme}/static/css/sohl-icons.css +0 -0
- /package/{hugo-theme → site-theme}/static/fonts/game-icons-kb.woff2 +0 -0
- /package/{hugo-theme → site-theme}/static/img/banners/takheperu-hieroglyphs.svg +0 -0
- /package/{hugo-theme → site-theme}/static/img/cartouches/takheperu.svg +0 -0
- /package/{hugo-theme → site-theme}/static/js/inline-spoilers.js +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
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 expansion step.** {@link expandContent} turns a note's authored body
|
|
16
|
+
* into markdown with every construct that reads the content index resolved,
|
|
17
|
+
* before a target renders it. In order:
|
|
18
|
+
*
|
|
19
|
+
* 1. **Tables.** Each prepared `sql` fence and page list is replaced by the
|
|
20
|
+
* markdown its query returned, and the note's generated sections are
|
|
21
|
+
* appended — see {@link module:engine/content-tables.expandContentTables}.
|
|
22
|
+
* This runs outside code protection, because a table is authored as a
|
|
23
|
+
* fence.
|
|
24
|
+
* 2. **Figure numbers.** The expanded body's captions are numbered by
|
|
25
|
+
* {@link module:engine/content-figures.scanFigures}; a `ref` and a
|
|
26
|
+
* `[[#id]]` link read the label.
|
|
27
|
+
* 3. **Expressions.** `{{…}}` is evaluated — `sql`, `ref`, dates and the
|
|
28
|
+
* helpers — see {@link module:engine/markdown-expressions.renderMarkdownExpressions}.
|
|
29
|
+
* 4. **Wikilinks.** Each `[[…]]` outside code is resolved against the index by
|
|
30
|
+
* {@link module:engine/wikilinks.resolveWikilinks}, and written in the
|
|
31
|
+
* target's form.
|
|
32
|
+
*
|
|
33
|
+
* Steps 1 and 2 are {@link expandTables}, which a caller numbering figures
|
|
34
|
+
* across more than one note — the book — runs on its own first.
|
|
35
|
+
*
|
|
36
|
+
* **What the target decides.** Every step reads the same on each target except
|
|
37
|
+
* where the target's own medium differs:
|
|
38
|
+
*
|
|
39
|
+
* | | `foundry` | `web` | `book` |
|
|
40
|
+
* | --- | --- | --- | --- |
|
|
41
|
+
* | a resolved link | an `@UUID[…]{label}` content link | a markdown link to the page's URL | a markdown link to the page's URL, which the book sets as a cross-reference to the entry it prints |
|
|
42
|
+
* | an unresolved link | the error that fails the note, as `linkError` | a `sohl-unresolved-link` span, and a finding | the label, then `(unresolved link)`, and a finding |
|
|
43
|
+
* | a `ref` | a `[[#anchor\|label]]` link, which the link pass addresses by its journal page | a markdown link to the anchor, or to another note's page | as on the web |
|
|
44
|
+
* | the generated sections | part of the one body | resolved apart, and returned as `generated` | resolved with the body, and returned as `generated` |
|
|
45
|
+
* | the date context | the compile's link index | the site index's `dateContext`, which carries the present | as on the web |
|
|
46
|
+
* | a figure's number | counted on the note | counted on the note | counted across the book, by the caller |
|
|
47
|
+
*
|
|
48
|
+
* Expansion reports and never throws on what it finds: each step's findings
|
|
49
|
+
* are returned for the caller to report in its own form.
|
|
50
|
+
*
|
|
51
|
+
* @module
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
import { collectAnchors, eventAnchors } from "./anchors.mjs";
|
|
55
|
+
import { protectCode } from "./code-fences.mjs";
|
|
56
|
+
import { scanFigures } from "./content-figures.mjs";
|
|
57
|
+
import { expandContentTables } from "./content-tables.mjs";
|
|
58
|
+
import { markGenerated, splitGenerated } from "./generated-sections.mjs";
|
|
59
|
+
import { convertNoteWikilinks } from "./helpers.mjs";
|
|
60
|
+
import { renderMarkdownExpressions } from "./markdown-expressions.mjs";
|
|
61
|
+
import { reckoningContext } from "./reckoning-markers.mjs";
|
|
62
|
+
import { resolveCrossNoteFigures, wikiContext } from "./site-index.mjs";
|
|
63
|
+
import { resolveWebWikilinks } from "./wikilinks.mjs";
|
|
64
|
+
|
|
65
|
+
/** The targets a body is expanded for. */
|
|
66
|
+
export const EXPANSION_TARGETS = Object.freeze(["foundry", "web", "book"]);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A `ref` call's Foundry rendering: a same-page wikilink rather than a
|
|
70
|
+
* Markdown link, so the wikilink pass that runs after expressions render
|
|
71
|
+
* addresses it exactly as an authored `[[#anchor|Text]]` is — by the
|
|
72
|
+
* JournalEntryPage the figure's own fence became, whether that page holds
|
|
73
|
+
* only the figure or carries it alongside other prose. A Markdown link's
|
|
74
|
+
* `#anchor` fragment resolves nowhere in Foundry, which addresses a page by
|
|
75
|
+
* UUID and an anchor within a page by that page's own anchor.
|
|
76
|
+
*
|
|
77
|
+
* @param {{anchor: string}} target - The figure's own anchor.
|
|
78
|
+
* @param {string} label - The link's text, which may itself carry Markdown.
|
|
79
|
+
* @returns {string} `[[#anchor|label]]`, with a literal `|` in the label
|
|
80
|
+
* escaped so it cannot be read as the wikilink's own separator.
|
|
81
|
+
*/
|
|
82
|
+
function foundryRefLink({ anchor }, label) {
|
|
83
|
+
return `[[#${anchor}|${label.replace(/\|/g, "\\|")}]]`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The note a body belongs to.
|
|
88
|
+
*
|
|
89
|
+
* @typedef {object} ExpandedNote
|
|
90
|
+
* @property {Record<string, any>} fm - Its frontmatter.
|
|
91
|
+
* @property {string} [file] - Its file, which findings name.
|
|
92
|
+
* @property {string} source - What a table's or a link's finding names the
|
|
93
|
+
* note by: its path on the web, its name in Foundry.
|
|
94
|
+
* @property {number} [bodyLine] - The file line its body starts on.
|
|
95
|
+
* @property {number} [bodyColumn] - The column its body starts at on that line.
|
|
96
|
+
*/
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* How a target resolves links, beyond the index.
|
|
100
|
+
*
|
|
101
|
+
* @typedef {object} ExpandLinks
|
|
102
|
+
* @property {string|null} [pack] - `foundry`: the pack the note's own document
|
|
103
|
+
* lands in, which a `[[#slug]]` self-link addresses.
|
|
104
|
+
* @property {string|null} [docPack] - `foundry`: the pack its journal lands in.
|
|
105
|
+
* @property {Map<string, object>} [foreign] - `web` and `book`: the merged
|
|
106
|
+
* foreign index.
|
|
107
|
+
* @property {object} [assets] - `web` and `book`: the address space an embed
|
|
108
|
+
* resolves against, from {@link module:engine/art-fields.assetAddressIndex}.
|
|
109
|
+
* @property {(text: string) => string} [beforeLinks] - `web` and `book`: a pass
|
|
110
|
+
* run on the text outside code before its links resolve.
|
|
111
|
+
* @property {(text: string) => string} [afterLinks] - `web` and `book`: a pass
|
|
112
|
+
* run on the text outside code once its links resolve.
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The first two steps of an expansion: the tables, and the figures of the
|
|
117
|
+
* table-expanded body.
|
|
118
|
+
*
|
|
119
|
+
* @typedef {object} ExpandedTables
|
|
120
|
+
* @property {{markdown: string, errors: object[], lineMap: object[],
|
|
121
|
+
* generatedFrom: number|undefined}} tables - From
|
|
122
|
+
* {@link module:engine/content-tables.expandContentTables}.
|
|
123
|
+
* @property {{figures: object[], errors: object[]}} figures - The figure scan
|
|
124
|
+
* of `tables.markdown`. A caller numbering figures across notes replaces
|
|
125
|
+
* `figures.figures` with its numbering before expanding the rest.
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Expand a note's tables and scan the result for figures — steps 1 and 2 of
|
|
130
|
+
* {@link expandContent}.
|
|
131
|
+
*
|
|
132
|
+
* @param {string} markdown - The body as authored.
|
|
133
|
+
* @param {object} options
|
|
134
|
+
* @param {string} options.source - What a table's finding names the note by.
|
|
135
|
+
* @param {{sqlTables?: object[], pageLists?: object[]}} [options.prepared] -
|
|
136
|
+
* This note's prepared SQL tables and page lists.
|
|
137
|
+
* @param {(address: string) => string|undefined} [options.resolveRole] - The
|
|
138
|
+
* role an embedded picture takes, which numbers a map apart from a figure.
|
|
139
|
+
* @returns {ExpandedTables} The two steps' results.
|
|
140
|
+
*/
|
|
141
|
+
export function expandTables(markdown, { source, prepared = {}, resolveRole }) {
|
|
142
|
+
const tables = expandContentTables(markdown ?? "", {
|
|
143
|
+
source,
|
|
144
|
+
sqlTables: prepared.sqlTables,
|
|
145
|
+
pageLists: prepared.pageLists,
|
|
146
|
+
});
|
|
147
|
+
return { tables, figures: scanFigures(tables.markdown, { resolveRole }) };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The expanded body, and what each step found.
|
|
152
|
+
*
|
|
153
|
+
* @typedef {object} Expansion
|
|
154
|
+
* @property {string} markdown - The whole body, expanded.
|
|
155
|
+
* @property {string} authored - The author's text, expanded. In Foundry it is
|
|
156
|
+
* the whole body.
|
|
157
|
+
* @property {string} generated - The generated sections, expanded apart from
|
|
158
|
+
* the author's text on the web; `""` in Foundry, and wherever there are none.
|
|
159
|
+
* @property {Array<{line: number, generated: boolean}>} lineMap - The authored
|
|
160
|
+
* line, 0-based, each line of the table-expanded body came from.
|
|
161
|
+
* @property {number|undefined} generatedFrom - The 0-based line of the
|
|
162
|
+
* table-expanded body the generated sections start on.
|
|
163
|
+
* @property {{figures: object[], errors: object[]}} figures - The figure scan
|
|
164
|
+
* of the table-expanded body.
|
|
165
|
+
* @property {{tables: object[], captions: object[], expressions: object[], links: object[]}} findings -
|
|
166
|
+
* Each step's findings, as the step reports them: a table's and a caption's
|
|
167
|
+
* by expanded body line, an expression's in file coordinates, a link's by
|
|
168
|
+
* occurrence of its literal.
|
|
169
|
+
* @property {Error|undefined} linkError - `foundry`: the error
|
|
170
|
+
* {@link module:engine/helpers.convertNoteWikilinks} fails a note with,
|
|
171
|
+
* carrying `file` and `position`.
|
|
172
|
+
* @property {object[]} unresolved - `foundry`: the links that did not resolve.
|
|
173
|
+
* @property {object[]} resolved - `web` and `book`: every link that resolved,
|
|
174
|
+
* in order.
|
|
175
|
+
* @property {number} authoredLinks - `web`: how many of `resolved` the
|
|
176
|
+
* author's text made, before the generated sections'.
|
|
177
|
+
*/
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Expand a note's body for one target.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} markdown - The body as authored.
|
|
183
|
+
* @param {object} options
|
|
184
|
+
* @param {"foundry"|"web"|"book"} options.target - What the body is expanded
|
|
185
|
+
* for.
|
|
186
|
+
* @param {object} options.index - What links resolve against: the compile's
|
|
187
|
+
* link index for `foundry`, the {@link module:engine/site-index.buildSiteIndex}
|
|
188
|
+
* index for `web` and `book`.
|
|
189
|
+
* @param {ExpandedNote} options.note - The note.
|
|
190
|
+
* @param {{sqlTables?: object[], pageLists?: object[], inline?: object[]}} [options.prepared] -
|
|
191
|
+
* This note's prepared SQL tables, page lists and inline query results.
|
|
192
|
+
* @param {object} [options.events] - Every note's events, from
|
|
193
|
+
* {@link module:engine/event-fields.eventNoteIndex}.
|
|
194
|
+
* @param {(address: string) => string|undefined} [options.resolveRole] - The
|
|
195
|
+
* role an embedded picture takes, which numbers a map apart from a figure.
|
|
196
|
+
* @param {Map<string, Map<string, object>>} [options.figuresByAddress] - `web`
|
|
197
|
+
* and `book`: every page's figures, by the address a cross-note `ref` writes.
|
|
198
|
+
* @param {ExpandedTables} [options.tables] - The note's {@link expandTables},
|
|
199
|
+
* already run — with the figures numbered, for the book. Run here when
|
|
200
|
+
* omitted.
|
|
201
|
+
* @param {ExpandLinks} [options.links] - How the target resolves links.
|
|
202
|
+
* @returns {Expansion} The expanded body and the findings.
|
|
203
|
+
* @throws {Error} When `target` is not one of {@link EXPANSION_TARGETS}.
|
|
204
|
+
*/
|
|
205
|
+
export function expandContent(
|
|
206
|
+
markdown,
|
|
207
|
+
{
|
|
208
|
+
target,
|
|
209
|
+
index,
|
|
210
|
+
note,
|
|
211
|
+
prepared = {},
|
|
212
|
+
events,
|
|
213
|
+
resolveRole,
|
|
214
|
+
figuresByAddress = new Map(),
|
|
215
|
+
tables: staged,
|
|
216
|
+
links = {},
|
|
217
|
+
},
|
|
218
|
+
) {
|
|
219
|
+
if (!EXPANSION_TARGETS.includes(target))
|
|
220
|
+
throw new Error(
|
|
221
|
+
`\`${target}\` is not an expansion target; expected ` +
|
|
222
|
+
EXPANSION_TARGETS.slice(0, -1)
|
|
223
|
+
.map((name) => `\`${name}\``)
|
|
224
|
+
.join(", ") +
|
|
225
|
+
` or \`${EXPANSION_TARGETS.at(-1)}\``,
|
|
226
|
+
);
|
|
227
|
+
const { fm, file, source, bodyLine, bodyColumn } = note;
|
|
228
|
+
// The website and the book read the site index, and resolve the same way.
|
|
229
|
+
const web = target !== "foundry";
|
|
230
|
+
|
|
231
|
+
const { tables, figures } = staged ?? expandTables(markdown, { source, prepared, resolveRole });
|
|
232
|
+
const { lineMap, generatedFrom } = tables;
|
|
233
|
+
const figuresById = new Map(
|
|
234
|
+
figures.figures
|
|
235
|
+
.filter((figure) => figure.id)
|
|
236
|
+
.map((figure) => [
|
|
237
|
+
figure.id,
|
|
238
|
+
{ label: figure.label, caption: figure.caption, hasCaption: figure.hasCaption },
|
|
239
|
+
]),
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const expressions = renderMarkdownExpressions(
|
|
243
|
+
web ? markGenerated(tables.markdown, generatedFrom) : tables.markdown,
|
|
244
|
+
{
|
|
245
|
+
fm,
|
|
246
|
+
dates: web ? index.dateContext : reckoningContext(index),
|
|
247
|
+
file,
|
|
248
|
+
bodyLine,
|
|
249
|
+
sqlResults: prepared.inline,
|
|
250
|
+
events,
|
|
251
|
+
figures:
|
|
252
|
+
web ?
|
|
253
|
+
{
|
|
254
|
+
get: (id) => figuresById.get(id),
|
|
255
|
+
// A cross-note `ref` resolves the written address
|
|
256
|
+
// exactly as a wikilink does, against every page's
|
|
257
|
+
// figures scanned before any page's expressions render.
|
|
258
|
+
note: (address) =>
|
|
259
|
+
resolveCrossNoteFigures(address, {
|
|
260
|
+
contentTypes: index.contentTypes,
|
|
261
|
+
packages: index.packages,
|
|
262
|
+
noIndexPackages: index.noIndexPackages,
|
|
263
|
+
contentPackage: index.contentPackage,
|
|
264
|
+
siteIndexMap: index.index,
|
|
265
|
+
figuresByAddress,
|
|
266
|
+
}),
|
|
267
|
+
}
|
|
268
|
+
: { get: (id) => figuresById.get(id), link: foundryRefLink },
|
|
269
|
+
},
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const findings = {
|
|
273
|
+
tables: tables.errors,
|
|
274
|
+
captions: figures.errors,
|
|
275
|
+
expressions: expressions.findings,
|
|
276
|
+
links: [],
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
if (!web) {
|
|
280
|
+
let converted;
|
|
281
|
+
let linkError;
|
|
282
|
+
try {
|
|
283
|
+
converted = convertNoteWikilinks(expressions.markdown, {
|
|
284
|
+
type: fm.type,
|
|
285
|
+
id: fm.id,
|
|
286
|
+
file,
|
|
287
|
+
bodyLine,
|
|
288
|
+
bodyColumn,
|
|
289
|
+
lineMap,
|
|
290
|
+
pack: links.pack,
|
|
291
|
+
docPack: links.docPack,
|
|
292
|
+
index,
|
|
293
|
+
name: source,
|
|
294
|
+
});
|
|
295
|
+
} catch (error) {
|
|
296
|
+
linkError = error;
|
|
297
|
+
}
|
|
298
|
+
const body = converted?.markdown ?? expressions.markdown;
|
|
299
|
+
return {
|
|
300
|
+
markdown: body,
|
|
301
|
+
authored: body,
|
|
302
|
+
generated: "",
|
|
303
|
+
lineMap,
|
|
304
|
+
generatedFrom,
|
|
305
|
+
figures,
|
|
306
|
+
findings,
|
|
307
|
+
linkError,
|
|
308
|
+
unresolved: converted?.unresolved ?? [],
|
|
309
|
+
resolved: [],
|
|
310
|
+
authoredLinks: 0,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const resolved = [];
|
|
315
|
+
const book = target === "book";
|
|
316
|
+
const ctx = wikiContext(index, {
|
|
317
|
+
src: source,
|
|
318
|
+
file,
|
|
319
|
+
type: fm.type ?? null,
|
|
320
|
+
errors: findings.links,
|
|
321
|
+
foreignIndex: links.foreign,
|
|
322
|
+
assets: links.assets,
|
|
323
|
+
resolved,
|
|
324
|
+
});
|
|
325
|
+
ctx.captionLabels = new Map(
|
|
326
|
+
figures.figures.filter((figure) => figure.id).map((figure) => [figure.id, figure.label]),
|
|
327
|
+
);
|
|
328
|
+
// This page's own anchors, so a `[[#slug]]` self-link is checked against
|
|
329
|
+
// what the page actually declares rather than trusted unconditionally.
|
|
330
|
+
ctx.anchors = new Set(collectAnchors(tables.markdown).map((anchor) => anchor.slug));
|
|
331
|
+
ctx.eventAnchors = new Set(eventAnchors(fm).map((anchor) => anchor.slug));
|
|
332
|
+
if (book) ctx.output = "book";
|
|
333
|
+
const resolve = (text) => {
|
|
334
|
+
const before = links.beforeLinks ? links.beforeLinks(text) : text;
|
|
335
|
+
const linked = resolveWebWikilinks(before, ctx);
|
|
336
|
+
return links.afterLinks ? links.afterLinks(linked) : linked;
|
|
337
|
+
};
|
|
338
|
+
// The book resolves the body whole, so a link's occurrence counts across
|
|
339
|
+
// the author's text and the generated sections it prints after it.
|
|
340
|
+
if (book) {
|
|
341
|
+
const split = splitGenerated(protectCode(expressions.markdown, resolve));
|
|
342
|
+
return {
|
|
343
|
+
markdown: split.generated ? `${split.authored}\n${split.generated}` : split.authored,
|
|
344
|
+
authored: split.authored,
|
|
345
|
+
generated: split.generated ?? "",
|
|
346
|
+
lineMap,
|
|
347
|
+
generatedFrom,
|
|
348
|
+
figures,
|
|
349
|
+
findings,
|
|
350
|
+
linkError: undefined,
|
|
351
|
+
unresolved: [],
|
|
352
|
+
resolved,
|
|
353
|
+
authoredLinks: 0,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
// The author's text and the generated sections are resolved apart, so a
|
|
357
|
+
// caller can tell the links the author wrote from the ones a section
|
|
358
|
+
// derives.
|
|
359
|
+
const split = splitGenerated(expressions.markdown);
|
|
360
|
+
const authored = protectCode(split.authored, resolve);
|
|
361
|
+
const authoredLinks = resolved.length;
|
|
362
|
+
const generated = split.generated ? protectCode(split.generated, resolve) : "";
|
|
363
|
+
return {
|
|
364
|
+
markdown: split.generated ? `${authored}\n${generated}` : authored,
|
|
365
|
+
authored,
|
|
366
|
+
generated,
|
|
367
|
+
lineMap,
|
|
368
|
+
generatedFrom,
|
|
369
|
+
figures,
|
|
370
|
+
findings,
|
|
371
|
+
linkError: undefined,
|
|
372
|
+
unresolved: [],
|
|
373
|
+
resolved,
|
|
374
|
+
authoredLinks,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
@@ -154,7 +154,7 @@ function picturesAreMap(contents, resolveRole) {
|
|
|
154
154
|
* @returns {"code"|"table"|"figure"|"map"|"poem"|"prose"} The kind.
|
|
155
155
|
*/
|
|
156
156
|
function figureKind(first, contents, resolveRole) {
|
|
157
|
-
if (/^(?:<div\b[^>]*\bclass="
|
|
157
|
+
if (/^(?:<div\b[^>]*\bclass="verse(?:\s|"))/.test(contents)) return "poem";
|
|
158
158
|
if (first?.type === "table_open") return "table";
|
|
159
159
|
if (first?.type === "fence" && /^\s*sql\b/i.test(first.info ?? "")) return "table";
|
|
160
160
|
if (first?.type === "fence" || first?.type === "code_block") return "code";
|
|
@@ -220,7 +220,7 @@ export function scanFigures(source, { resolveRole = () => undefined } = {}) {
|
|
|
220
220
|
const explicit = attributes.values.type;
|
|
221
221
|
if (
|
|
222
222
|
explicit &&
|
|
223
|
-
!["figure", "table", "
|
|
223
|
+
!["figure", "table", "verse", "code", "prose", "example"].includes(explicit)
|
|
224
224
|
)
|
|
225
225
|
fault(i, `unsupported caption type "${explicit}"`);
|
|
226
226
|
if (!caption) fault(i, "a caption needs text");
|
|
@@ -281,7 +281,7 @@ export function scanFigures(source, { resolveRole = () => undefined } = {}) {
|
|
|
281
281
|
) {
|
|
282
282
|
fault(
|
|
283
283
|
bodyStart,
|
|
284
|
-
"a caption must be followed by an image, table,
|
|
284
|
+
"a caption must be followed by an image, table, verse or code fence, paragraph, quote, or fenced div",
|
|
285
285
|
);
|
|
286
286
|
continue;
|
|
287
287
|
}
|
|
@@ -306,11 +306,11 @@ export function scanFigures(source, { resolveRole = () => undefined } = {}) {
|
|
|
306
306
|
continue;
|
|
307
307
|
}
|
|
308
308
|
kind =
|
|
309
|
-
first.type === "fence" && /^
|
|
309
|
+
first.type === "fence" && /^verse(?:\s|$)/.test(first.info) ?
|
|
310
310
|
"poem"
|
|
311
311
|
: figureKind(first, contents.trim(), resolveRole);
|
|
312
312
|
}
|
|
313
|
-
if (explicit) kind = explicit === "
|
|
313
|
+
if (explicit) kind = explicit === "verse" ? "poem" : explicit;
|
|
314
314
|
const id = attributes.id;
|
|
315
315
|
if (id && ids.has(slugify(id))) fault(i, `duplicate figure id "${id}"`);
|
|
316
316
|
if (id) ids.add(slugify(id));
|
|
@@ -374,18 +374,24 @@ export function scanFigures(source, { resolveRole = () => undefined } = {}) {
|
|
|
374
374
|
* @param {string} source - A note body with expanded tables.
|
|
375
375
|
* @param {(markdown: string) => string} [renderMarkdown] - How to render a
|
|
376
376
|
* figure's own contents.
|
|
377
|
-
* @param {Array<{id: string, label: string}>} [numbers] -
|
|
378
|
-
*
|
|
377
|
+
* @param {Array<{id: string, kind: string, label: string}>} [numbers] - The
|
|
378
|
+
* figures as {@link scanFigures} numbered them, with roles, where the
|
|
379
|
+
* surface counted them: across a note's pages, or in expansion, where a
|
|
380
|
+
* `ref` reads the same label. They are matched in order when they are as many
|
|
381
|
+
* as the text holds, by id otherwise, and the caption takes its number, kind
|
|
382
|
+
* and label from them. Omitted, the text is numbered as it stands.
|
|
379
383
|
* @param {object} [options] - Options.
|
|
380
384
|
* @param {(address: string) => string|undefined} [options.resolveRole] - See
|
|
381
385
|
* {@link scanFigures}.
|
|
386
|
+
* @param {(markdown: string) => string} [options.renderInline] - How a caption
|
|
387
|
+
* is rendered; omitted, a caption is plain inline Markdown.
|
|
382
388
|
* @returns {{markdown: string, errors: Array<{line: number, column: number, message: string}>}}
|
|
383
389
|
*/
|
|
384
390
|
export function renderFigureBlocks(
|
|
385
391
|
source,
|
|
386
392
|
renderMarkdown = parser.render.bind(parser),
|
|
387
393
|
numbers,
|
|
388
|
-
{ resolveRole } = {},
|
|
394
|
+
{ resolveRole, renderInline = parser.renderInline.bind(parser) } = {},
|
|
389
395
|
) {
|
|
390
396
|
const { figures, errors } = scanFigures(source, { resolveRole });
|
|
391
397
|
if (errors.length) return { markdown: source, errors };
|
|
@@ -393,12 +399,13 @@ export function renderFigureBlocks(
|
|
|
393
399
|
numbers ?
|
|
394
400
|
new Map(numbers.filter((entry) => entry.id).map((entry) => [entry.id, entry]))
|
|
395
401
|
: new Map();
|
|
402
|
+
const inOrder = numbers?.length === figures.length ? numbers : undefined;
|
|
396
403
|
const lines = String(source ?? "").split("\n");
|
|
397
404
|
const output = [];
|
|
398
405
|
let cursor = 0;
|
|
399
|
-
for (const figure of figures) {
|
|
406
|
+
for (const [at, figure] of figures.entries()) {
|
|
400
407
|
output.push(...lines.slice(cursor, figure.line - 1));
|
|
401
|
-
const numbered = (figure.id ? byId.get(figure.id) : null) ?? figure;
|
|
408
|
+
const numbered = inOrder?.[at] ?? (figure.id ? byId.get(figure.id) : null) ?? figure;
|
|
402
409
|
const contents = lines.slice(figure.bodyStart, figure.bodyEnd).join("\n");
|
|
403
410
|
const classes = ["content-figure", `content-figure-${numbered.kind}`, ...figure.classes];
|
|
404
411
|
const id = figure.slug ? ` id="${escapeHtml(figure.slug)}"` : "";
|
|
@@ -416,7 +423,7 @@ export function renderFigureBlocks(
|
|
|
416
423
|
output.push(
|
|
417
424
|
`<p class="content-figure-label">${
|
|
418
425
|
figure.hasCaption ?
|
|
419
|
-
`${label ? `${label}: ` : ""}${
|
|
426
|
+
`${label ? `${label}: ` : ""}${renderInline(figure.caption)}`
|
|
420
427
|
: label
|
|
421
428
|
}</p>`,
|
|
422
429
|
"</div>",
|
|
@@ -207,6 +207,7 @@ types:
|
|
|
207
207
|
- names
|
|
208
208
|
- family
|
|
209
209
|
- abbreviation
|
|
210
|
+
- core
|
|
210
211
|
- classModifiers
|
|
211
212
|
- fields
|
|
212
213
|
- words
|
|
@@ -224,6 +225,8 @@ types:
|
|
|
224
225
|
- older
|
|
225
226
|
- negation
|
|
226
227
|
- naming
|
|
228
|
+
- emphasis
|
|
229
|
+
- script
|
|
227
230
|
subTypes:
|
|
228
231
|
- social
|
|
229
232
|
- nature
|
|
@@ -363,7 +366,11 @@ types:
|
|
|
363
366
|
doc:
|
|
364
367
|
data:
|
|
365
368
|
- culture
|
|
369
|
+
- concepts
|
|
370
|
+
- date
|
|
371
|
+
- author
|
|
366
372
|
- map
|
|
373
|
+
- metres
|
|
367
374
|
subTypes:
|
|
368
375
|
- rules
|
|
369
376
|
- userguide
|
|
@@ -371,6 +378,7 @@ types:
|
|
|
371
378
|
- howto
|
|
372
379
|
- concept
|
|
373
380
|
- settingguide
|
|
381
|
+
- post
|
|
374
382
|
macro:
|
|
375
383
|
data: []
|
|
376
384
|
subTypes: []
|