@heroiclands/package-build 22.26.0 → 22.28.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 +17 -0
- package/README.md +3 -1
- package/assets/styles/content-base.css +46 -3
- package/bin/content-commands.mjs +53 -5
- package/ci/publish-npm.mjs +49 -0
- package/docs/api.md +44 -24
- package/docs/authoring/first-note.md +1 -1
- package/docs/authoring/frontmatter.md +2 -2
- package/docs/authoring/links-and-markup.md +96 -149
- package/docs/content-format.md +7 -3
- package/docs/guides/government-migration.md +52 -0
- package/docs/guides/site.md +5 -5
- package/docs/reference/format-details.md +131 -230
- package/docs/reference/note-types.md +27 -29
- package/docs/reference/theme-pages.md +31 -29
- package/engine/anchors.mjs +85 -13
- package/engine/art-fields.mjs +1 -1
- package/engine/base-compiler.mjs +9 -1
- package/engine/bundles.mjs +1 -1
- package/engine/code-fences.mjs +6 -2
- package/engine/content-alerts.mjs +195 -0
- package/engine/content-blocks.mjs +119 -241
- package/engine/content-embeds.mjs +1 -1
- package/engine/content-figures.mjs +166 -230
- package/engine/content-format-check.mjs +3 -2
- package/engine/content-format.yaml +0 -5
- package/engine/content-images.mjs +3 -3
- package/engine/content-poetry.mjs +141 -0
- package/engine/content-spans.mjs +177 -0
- package/engine/folder-notes.mjs +2 -3
- package/engine/foundry-entries.mjs +2 -0
- package/engine/frontmatter-lint.mjs +2 -1
- package/engine/heading-attributes.mjs +1 -1
- package/engine/helpers.mjs +8 -3
- package/engine/holdings.mjs +165 -273
- package/engine/index.mjs +2 -2
- package/engine/infobox.mjs +1 -1
- package/engine/journals.mjs +8 -6
- package/engine/markdown-expressions.mjs +9 -3
- package/engine/metadata-index.mjs +2 -3
- package/engine/note-vocabulary.mjs +19 -15
- package/engine/pdf-build.mjs +1 -1
- package/engine/pdf-render.mjs +90 -31
- package/engine/populations.mjs +17 -83
- package/engine/site-build.mjs +44 -16
- package/engine/site-preview.mjs +22 -6
- package/engine/stub-lint.mjs +20 -64
- package/engine/wikilinks.mjs +18 -4
- package/githooks/pre-push +1 -1
- package/hugo-theme/layouts/partials/holdings/entry.html +2 -2
- package/hugo-theme/layouts/partials/holdings.html +14 -14
- package/hugo-theme/static/css/style.css +40 -2
- package/package.json +3 -1
- package/sohl/item-fields.mjs +0 -16
- package/types/engine/anchors.d.mts +13 -4
- package/types/engine/art-fields.d.mts +1 -1
- package/types/engine/code-fences.d.mts +3 -1
- package/types/engine/content-alerts.d.mts +62 -0
- package/types/engine/content-blocks.d.mts +3 -10
- package/types/engine/content-embeds.d.mts +1 -1
- package/types/engine/content-figures.d.mts +13 -20
- package/types/engine/content-images.d.mts +1 -1
- package/types/engine/content-poetry.d.mts +32 -0
- package/types/engine/content-spans.d.mts +18 -0
- package/types/engine/holdings.d.mts +59 -105
- package/types/engine/markdown-expressions.d.mts +1 -1
- package/types/engine/note-vocabulary.d.mts +12 -4
- package/types/engine/populations.d.mts +4 -13
- package/types/engine/site-build.d.mts +3 -3
- package/types/engine/wikilinks.d.mts +5 -1
|
@@ -59,3 +59,144 @@ export function poetryMarkdown(body) {
|
|
|
59
59
|
)
|
|
60
60
|
.join("\n\n");
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
import { slugify } from "./content-slug.mjs";
|
|
64
|
+
import { parseExtensionAttributes, refusedAttributes } from "./extension-attributes.mjs";
|
|
65
|
+
|
|
66
|
+
/** Read poetry code fences; start/end are inclusive zero-based source lines.
|
|
67
|
+
* @param {string} source - Authored Markdown.
|
|
68
|
+
*/
|
|
69
|
+
export function scanPoetry(source) {
|
|
70
|
+
const lines = String(source ?? "").split("\n"),
|
|
71
|
+
blocks = [],
|
|
72
|
+
errors = [];
|
|
73
|
+
let opening = null;
|
|
74
|
+
for (let i = 0; i < lines.length; i++) {
|
|
75
|
+
const match = /^ *(`{3,}|~{3,})(.*)$/.exec(lines[i]);
|
|
76
|
+
if (opening) {
|
|
77
|
+
if (
|
|
78
|
+
match &&
|
|
79
|
+
match[1][0] === opening.fence[0] &&
|
|
80
|
+
match[1].length >= opening.fence.length &&
|
|
81
|
+
!match[2].trim()
|
|
82
|
+
) {
|
|
83
|
+
if (opening.name === "poetry") {
|
|
84
|
+
const body = lines.slice(opening.start + 1, i).join("\n");
|
|
85
|
+
if (!body.trim())
|
|
86
|
+
errors.push({
|
|
87
|
+
line: opening.start + 1,
|
|
88
|
+
column: 1,
|
|
89
|
+
message: "poetry block is empty",
|
|
90
|
+
});
|
|
91
|
+
for (let n = opening.start + 1; n < i; n++) {
|
|
92
|
+
const col = lines[n].indexOf("\t");
|
|
93
|
+
if (col >= 0)
|
|
94
|
+
errors.push({
|
|
95
|
+
line: n + 1,
|
|
96
|
+
column: col + 1,
|
|
97
|
+
message: "tabs are not allowed in poetry",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (
|
|
101
|
+
opening.attributes.syllables &&
|
|
102
|
+
opening.attributes.syllables.split(",").length !== verseLines(body).length
|
|
103
|
+
)
|
|
104
|
+
errors.push({
|
|
105
|
+
line: opening.start + 1,
|
|
106
|
+
column: 1,
|
|
107
|
+
message: `poetry syllables= gives ${opening.attributes.syllables.split(",").length} counts for ${verseLines(body).length} verse lines`,
|
|
108
|
+
});
|
|
109
|
+
blocks.push({ ...opening, end: i, body });
|
|
110
|
+
}
|
|
111
|
+
opening = null;
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (!match) continue;
|
|
116
|
+
const info = match[2].trim();
|
|
117
|
+
opening = {
|
|
118
|
+
start: i,
|
|
119
|
+
fence: match[1],
|
|
120
|
+
name: info.split(/\s/)[0],
|
|
121
|
+
attributes: {},
|
|
122
|
+
id: "",
|
|
123
|
+
classes: [],
|
|
124
|
+
indent: /^ */.exec(lines[i])[0],
|
|
125
|
+
};
|
|
126
|
+
if (opening.name !== "poetry") continue;
|
|
127
|
+
const raw = info.slice(6).trim();
|
|
128
|
+
const at = { line: i + 1, column: 1 };
|
|
129
|
+
if (raw && !/^\{[^}\n]*\}$/.test(raw)) {
|
|
130
|
+
errors.push({ ...at, message: "poetry attributes need {…} braces" });
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const parsed = parseExtensionAttributes(raw ? raw.slice(1, -1) : "");
|
|
134
|
+
opening.id = parsed.id;
|
|
135
|
+
opening.classes = parsed.classes;
|
|
136
|
+
opening.attributes = parsed.values;
|
|
137
|
+
for (const message of [...parsed.problems, ...refusedAttributes(parsed.values)])
|
|
138
|
+
errors.push({ ...at, message });
|
|
139
|
+
for (const [key, value] of Object.entries(parsed.values)) {
|
|
140
|
+
if (!["form", "meter", "rhyme", "syllables", "lang"].includes(key))
|
|
141
|
+
errors.push({ ...at, message: `${key}= is not a poetry attribute` });
|
|
142
|
+
else if (key === "syllables" && !/^[1-9]\d*(?:,[1-9]\d*)*$/.test(value))
|
|
143
|
+
errors.push({
|
|
144
|
+
...at,
|
|
145
|
+
message: "poetry syllables= needs positive comma-separated counts",
|
|
146
|
+
});
|
|
147
|
+
else if (key === "lang" && !/^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/.test(value))
|
|
148
|
+
errors.push({ ...at, message: "poetry lang= needs a language tag" });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (opening?.name === "poetry")
|
|
152
|
+
errors.push({
|
|
153
|
+
line: opening.start + 1,
|
|
154
|
+
column: 1,
|
|
155
|
+
message: "poetry block needs a closing code fence",
|
|
156
|
+
});
|
|
157
|
+
return { blocks, errors };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Replace verse fences with HTML wrappers whose body remains inline Markdown.
|
|
161
|
+
* @param {string} source - Authored Markdown.
|
|
162
|
+
*/
|
|
163
|
+
export function renderPoetry(source) {
|
|
164
|
+
const { blocks, errors } = scanPoetry(source),
|
|
165
|
+
lines = String(source ?? "").split("\n"),
|
|
166
|
+
output = [];
|
|
167
|
+
let cursor = 0;
|
|
168
|
+
const escape = (value) =>
|
|
169
|
+
String(value)
|
|
170
|
+
.replace(/&/g, "&")
|
|
171
|
+
.replace(/"/g, """)
|
|
172
|
+
.replace(/</g, "<")
|
|
173
|
+
.replace(/>/g, ">");
|
|
174
|
+
for (const block of blocks) {
|
|
175
|
+
output.push(...lines.slice(cursor, block.start));
|
|
176
|
+
const attrs = [`class="${escape(["poetry", ...block.classes].join(" "))}"`];
|
|
177
|
+
if (block.id) attrs.push(`id="${escape(slugify(block.id))}"`);
|
|
178
|
+
for (const [key, value] of Object.entries(block.attributes))
|
|
179
|
+
if (!refusedAttributes({ [key]: value }).length)
|
|
180
|
+
attrs.push(`${key === "lang" ? key : `data-${key}`}="${escape(value)}"`);
|
|
181
|
+
let indent = block.indent;
|
|
182
|
+
if (
|
|
183
|
+
indent.length >= 4 &&
|
|
184
|
+
!lines.slice(0, block.start).some((line) => /^ *(?:[-+*]|\d+[.)]) +/.test(line))
|
|
185
|
+
)
|
|
186
|
+
indent = "";
|
|
187
|
+
output.push(
|
|
188
|
+
"",
|
|
189
|
+
`${indent}<div ${attrs.join(" ")}>`,
|
|
190
|
+
"",
|
|
191
|
+
...poetryMarkdown(block.body)
|
|
192
|
+
.split("\n")
|
|
193
|
+
.map((line) => (line ? indent + line : line)),
|
|
194
|
+
"",
|
|
195
|
+
`${indent}</div>`,
|
|
196
|
+
"",
|
|
197
|
+
);
|
|
198
|
+
cursor = block.end + 1;
|
|
199
|
+
}
|
|
200
|
+
output.push(...lines.slice(cursor));
|
|
201
|
+
return { markdown: output.join("\n"), errors };
|
|
202
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
|
2
|
+
import MarkdownIt from "markdown-it";
|
|
3
|
+
import { alertMarkdownPlugin } from "./content-alerts.mjs";
|
|
4
|
+
import { slugify } from "./content-slug.mjs";
|
|
5
|
+
import { scanPoetry } from "./content-poetry.mjs";
|
|
6
|
+
import footnotePlugin from "markdown-it-footnote";
|
|
7
|
+
import { parseExtensionAttributes, refusedAttributes } from "./extension-attributes.mjs";
|
|
8
|
+
|
|
9
|
+
/** Add bracket spans after recognized Markdown links and footnotes.
|
|
10
|
+
* @param {import("markdown-it")} md - Markdown parser to extend.
|
|
11
|
+
*/
|
|
12
|
+
export function spanMarkdownPlugin(md) {
|
|
13
|
+
md.inline.ruler.after("link", "bracket_span", (state, silent) => {
|
|
14
|
+
const start = state.pos;
|
|
15
|
+
if (
|
|
16
|
+
state.src[start] !== "[" ||
|
|
17
|
+
/@[A-Za-z]+$/.test(state.src.slice(0, start)) ||
|
|
18
|
+
state.src[start + 1] === "[" ||
|
|
19
|
+
state.src[start + 1] === "^" ||
|
|
20
|
+
state.src[start + 1] === "!" ||
|
|
21
|
+
["!", "[", "]"].includes(state.src[start - 1])
|
|
22
|
+
)
|
|
23
|
+
return false;
|
|
24
|
+
const end = state.md.helpers.parseLinkLabel(state, start, false);
|
|
25
|
+
if (end < 0 || ["(", "["].includes(state.src[end + 1])) return false;
|
|
26
|
+
let finish = end + 1;
|
|
27
|
+
let parsed = { id: "", classes: [], values: {}, problems: [] };
|
|
28
|
+
if (state.src[finish] === "{") {
|
|
29
|
+
let quote = false,
|
|
30
|
+
escape = false,
|
|
31
|
+
close = finish + 1;
|
|
32
|
+
for (; close < state.posMax; close++) {
|
|
33
|
+
const char = state.src[close];
|
|
34
|
+
if (escape) {
|
|
35
|
+
escape = false;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (char === "\\") {
|
|
39
|
+
escape = true;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (char === '"') quote = !quote;
|
|
43
|
+
if (char === "}" && !quote) break;
|
|
44
|
+
}
|
|
45
|
+
if (close === state.posMax)
|
|
46
|
+
parsed.problems.push("span attributes need closing {…} braces");
|
|
47
|
+
else {
|
|
48
|
+
parsed = parseExtensionAttributes(state.src.slice(finish + 1, close));
|
|
49
|
+
finish = close + 1;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const problems = [...parsed.problems, ...refusedAttributes(parsed.values)];
|
|
53
|
+
if (silent) {
|
|
54
|
+
state.pos = finish;
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
const token = state.push("span_open", "span", 1);
|
|
58
|
+
token.meta = {
|
|
59
|
+
span: true,
|
|
60
|
+
start,
|
|
61
|
+
end: finish,
|
|
62
|
+
text: state.src.slice(start + 1, end),
|
|
63
|
+
problems,
|
|
64
|
+
};
|
|
65
|
+
if (parsed.id) token.attrSet("id", slugify(parsed.id));
|
|
66
|
+
if (parsed.classes.length) token.attrSet("class", parsed.classes.join(" "));
|
|
67
|
+
for (const [key, value] of Object.entries(parsed.values))
|
|
68
|
+
if (!refusedAttributes({ [key]: value }).length) token.attrSet(key, value);
|
|
69
|
+
const nested = [];
|
|
70
|
+
state.md.inline.parse(state.src.slice(start + 1, end), state.md, state.env, nested);
|
|
71
|
+
for (const child of nested)
|
|
72
|
+
if (child.meta?.span) {
|
|
73
|
+
child.meta.start += start + 1;
|
|
74
|
+
child.meta.end += start + 1;
|
|
75
|
+
}
|
|
76
|
+
state.tokens.push(...nested);
|
|
77
|
+
state.push("span_close", "span", -1);
|
|
78
|
+
state.pos = finish;
|
|
79
|
+
return true;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const parser = new MarkdownIt({ html: true })
|
|
84
|
+
.use(footnotePlugin)
|
|
85
|
+
.use(alertMarkdownPlugin)
|
|
86
|
+
.use(spanMarkdownPlugin);
|
|
87
|
+
/** Read span anchors and located attribute diagnostics outside literal code.
|
|
88
|
+
* @param {string} source - Authored Markdown.
|
|
89
|
+
*/
|
|
90
|
+
export function scanSpans(source) {
|
|
91
|
+
const text = String(source ?? ""),
|
|
92
|
+
env = {},
|
|
93
|
+
tokens = parser.parse(text, env),
|
|
94
|
+
spans = [],
|
|
95
|
+
errors = [];
|
|
96
|
+
const lines = text.split("\n"),
|
|
97
|
+
starts = [];
|
|
98
|
+
let offset = 0;
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
starts.push(offset);
|
|
101
|
+
offset += line.length + 1;
|
|
102
|
+
}
|
|
103
|
+
const collect = (children, position) => {
|
|
104
|
+
for (const child of children ?? []) {
|
|
105
|
+
if (!child.meta?.span) continue;
|
|
106
|
+
const start = position(child.meta.start),
|
|
107
|
+
end = position(child.meta.end);
|
|
108
|
+
const prefix = text.slice(0, start),
|
|
109
|
+
line = prefix.split("\n").length,
|
|
110
|
+
column = start - prefix.lastIndexOf("\n");
|
|
111
|
+
spans.push({
|
|
112
|
+
id: child.attrGet("id") ?? "",
|
|
113
|
+
line,
|
|
114
|
+
column,
|
|
115
|
+
start,
|
|
116
|
+
end,
|
|
117
|
+
text: child.meta.text,
|
|
118
|
+
attrs: child.attrs ?? [],
|
|
119
|
+
});
|
|
120
|
+
for (const message of child.meta.problems) errors.push({ line, column, message });
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
for (const token of tokens) {
|
|
124
|
+
if (token.type !== "inline" || !token.map) continue;
|
|
125
|
+
const contentLines = token.content.split("\n"),
|
|
126
|
+
positions = [];
|
|
127
|
+
let local = 0;
|
|
128
|
+
for (let n = 0; n < contentLines.length; n++) {
|
|
129
|
+
const sourceLine = token.map[0] + n;
|
|
130
|
+
const column = lines[sourceLine]?.indexOf(contentLines[n]) ?? -1;
|
|
131
|
+
positions.push({ local, absolute: starts[sourceLine] + Math.max(0, column) });
|
|
132
|
+
local += contentLines[n].length + 1;
|
|
133
|
+
}
|
|
134
|
+
collect(token.children, (pos) => {
|
|
135
|
+
const mapping = positions.findLast((mapping) => mapping.local <= pos) ?? positions[0];
|
|
136
|
+
return mapping.absolute + pos - mapping.local;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// Verse fences preserve inline Markdown even though the block parser
|
|
140
|
+
// normally treats every code fence as literal text.
|
|
141
|
+
for (const poem of scanPoetry(text).blocks) {
|
|
142
|
+
for (let line = poem.start + 1; line < poem.end; line++) {
|
|
143
|
+
const children = [];
|
|
144
|
+
parser.inline.parse(lines[line], parser, env, children);
|
|
145
|
+
collect(children, (pos) => starts[line] + pos);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
spans.sort((a, b) => a.start - b.start);
|
|
149
|
+
errors.sort((a, b) => a.line - b.line || a.column - b.column);
|
|
150
|
+
return { spans, errors };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Emit span tags while leaving their inline content as Markdown for Hugo.
|
|
154
|
+
* @param {string} source - Authored Markdown.
|
|
155
|
+
*/
|
|
156
|
+
export function renderSpans(source) {
|
|
157
|
+
const { spans, errors } = scanSpans(source);
|
|
158
|
+
let markdown = String(source ?? "");
|
|
159
|
+
const escape = (value) =>
|
|
160
|
+
String(value)
|
|
161
|
+
.replace(/&/g, "&")
|
|
162
|
+
.replace(/"/g, """)
|
|
163
|
+
.replace(/</g, "<")
|
|
164
|
+
.replace(/>/g, ">");
|
|
165
|
+
for (const span of spans
|
|
166
|
+
.filter(
|
|
167
|
+
(span) => !spans.some((parent) => parent.start < span.start && parent.end >= span.end),
|
|
168
|
+
)
|
|
169
|
+
.sort((a, b) => b.start - a.start)) {
|
|
170
|
+
const attrs = span.attrs.map(([key, value]) => ` ${key}="${escape(value)}"`).join("");
|
|
171
|
+
markdown =
|
|
172
|
+
markdown.slice(0, span.start) +
|
|
173
|
+
`<span${attrs}>${renderSpans(span.text).markdown}</span>` +
|
|
174
|
+
markdown.slice(span.end);
|
|
175
|
+
}
|
|
176
|
+
return { markdown, errors };
|
|
177
|
+
}
|
package/engine/folder-notes.mjs
CHANGED
|
@@ -42,9 +42,8 @@
|
|
|
42
42
|
* because a `Folder` is a core Foundry document like a `JournalEntry` or a
|
|
43
43
|
* `Scene`, not a system's.
|
|
44
44
|
*
|
|
45
|
-
* **A folder note
|
|
46
|
-
* no documentation journal and takes no part in `docEntryTypes
|
|
47
|
-
* open decision, settled the way it recommended.
|
|
45
|
+
* **A folder note requires a body** under author validation. That body creates
|
|
46
|
+
* no documentation journal and takes no part in `docEntryTypes`.
|
|
48
47
|
*
|
|
49
48
|
* @module
|
|
50
49
|
*/
|
|
@@ -85,6 +85,7 @@ import {
|
|
|
85
85
|
assertNoSectionField,
|
|
86
86
|
assertNoTraitsField,
|
|
87
87
|
} from "./retired-fields.mjs";
|
|
88
|
+
import { collectAnchors } from "./anchors.mjs";
|
|
88
89
|
import { journalPageId, splitPages } from "./journals.mjs";
|
|
89
90
|
import { routerFor } from "./pack-router.mjs";
|
|
90
91
|
import { loadPackConfig } from "./pack-config.mjs";
|
|
@@ -121,6 +122,7 @@ export function anchorsOf(entryUuid, entryId, body, name) {
|
|
|
121
122
|
const uuid = pageUuid(entryUuid, journalPageId(entryId, page));
|
|
122
123
|
if (index === 0) anchors[LEAD_ANCHOR] = uuid;
|
|
123
124
|
if (page.anchorSlug) anchors[page.anchorSlug] = uuid;
|
|
125
|
+
for (const anchor of collectAnchors(page.markdown)) anchors[anchor.slug] = uuid;
|
|
124
126
|
});
|
|
125
127
|
return anchors;
|
|
126
128
|
}
|
|
@@ -88,6 +88,7 @@ import { DEFAULT_PARENT } from "./folder-notes.mjs";
|
|
|
88
88
|
import {
|
|
89
89
|
BEING_ARCHETYPES,
|
|
90
90
|
dataFields,
|
|
91
|
+
isLegacyDataField,
|
|
91
92
|
declaredTags,
|
|
92
93
|
subTypeCharsetMessage,
|
|
93
94
|
typeCharsetMessage,
|
|
@@ -502,7 +503,7 @@ function checkDataContainer(note, { type, fields, packs, addressContext, index }
|
|
|
502
503
|
}
|
|
503
504
|
|
|
504
505
|
for (const key of Object.keys(entries)) {
|
|
505
|
-
if (declared.has(key)) continue;
|
|
506
|
+
if (declared.has(key) || isLegacyDataField(type, key)) continue;
|
|
506
507
|
const current = renamed.get(key);
|
|
507
508
|
if (current) {
|
|
508
509
|
findings.push({
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* an id a surface publishes as an anchor, classes a surface may honour, and
|
|
13
13
|
* attributes an HTML surface writes onto the element. This is where that
|
|
14
14
|
* reading lives, so there is one grammar — the same one a `:::` block and a
|
|
15
|
-
*
|
|
15
|
+
* captions and divs are written in.
|
|
16
16
|
*
|
|
17
17
|
* **A leaf, deliberately.** `anchors.mjs` is shared by the compilers, the index
|
|
18
18
|
* and the link checker and cannot import any of them; the figure pass is
|
package/engine/helpers.mjs
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* journals.mjs, actors.mjs).
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
import { renderAlerts } from "./content-alerts.mjs";
|
|
28
29
|
import { buildReferenceTargets } from "./reference-targets.mjs";
|
|
29
30
|
import { standingsDigest } from "./standings.mjs";
|
|
30
31
|
|
|
@@ -72,6 +73,7 @@ import { linkFindingMessage } from "./wikilink-syntax.mjs";
|
|
|
72
73
|
import { isDraftNote } from "./note-vocabulary.mjs";
|
|
73
74
|
import { DERIVED_PACKED_TYPES, NEVER_PACKED_TYPES } from "./note-claims.mjs";
|
|
74
75
|
import { expandContentTables } from "./content-tables.mjs";
|
|
76
|
+
import { spanMarkdownPlugin } from "./content-spans.mjs";
|
|
75
77
|
import { renderBlocks } from "./content-blocks.mjs";
|
|
76
78
|
import { headingAttributesPlugin } from "./heading-attributes.mjs";
|
|
77
79
|
import { renderFigureBlocks, scanFigures } from "./content-figures.mjs";
|
|
@@ -105,6 +107,7 @@ let localAssetImageInfoCache;
|
|
|
105
107
|
export const md = markdownit({ html: true })
|
|
106
108
|
.use(footnotePlugin)
|
|
107
109
|
.use(deflistPlugin)
|
|
110
|
+
.use(spanMarkdownPlugin)
|
|
108
111
|
// A heading's attribute block, written onto the heading element rather than
|
|
109
112
|
// typeset into it — the reading the website's renderer does for itself.
|
|
110
113
|
.use(headingAttributesPlugin)
|
|
@@ -220,11 +223,11 @@ md.renderer.rules.footnote_open = (tokens, idx, options, env, renderer) => {
|
|
|
220
223
|
* @returns {string} The rendered HTML.
|
|
221
224
|
*/
|
|
222
225
|
export function renderFoundryMarkdown(body, figures, footnoteNumbers, docId, resolveRole) {
|
|
223
|
-
const
|
|
224
|
-
const figured = renderFigureBlocks(blocks.markdown, (block) => md.render(block), figures, {
|
|
226
|
+
const figured = renderFigureBlocks(body, (block) => md.render(block), figures, {
|
|
225
227
|
resolveRole,
|
|
226
228
|
});
|
|
227
|
-
|
|
229
|
+
const blocks = renderBlocks(figured.markdown, "foundry");
|
|
230
|
+
return md.render(renderAlerts(blocks.markdown, "foundry").markdown, { footnoteNumbers, docId });
|
|
228
231
|
}
|
|
229
232
|
|
|
230
233
|
/**
|
|
@@ -918,6 +921,8 @@ export function buildContentLinkIndex(
|
|
|
918
921
|
// Read from the record rather than from a second reading of the
|
|
919
922
|
// note's headings — the one-anchor-reader rule.
|
|
920
923
|
anchors: new Set((record.anchors ?? []).map((anchor) => anchor.slug)),
|
|
924
|
+
anchorUuids: record.foundry?.note?.anchors,
|
|
925
|
+
docAnchorUuids: documentation?.foundry?.note?.anchors,
|
|
921
926
|
});
|
|
922
927
|
}
|
|
923
928
|
// Packages this build links *into* but does not publish. Each publishes
|