@heroiclands/package-build 23.12.7 → 23.13.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 +54 -0
- package/README.md +1 -1
- package/assets/preview/gitea.ini +2 -2
- package/assets/styles/content-base.css +143 -23
- package/bin/content-commands.mjs +15 -4
- package/bin/package-build-preview.mjs +2 -2
- package/docs/api.md +18 -18
- package/docs/authoring/assets.md +1 -1
- package/docs/authoring/links-and-markup.md +156 -22
- package/docs/commands.md +32 -10
- package/docs/configuration.md +8 -1
- package/docs/content-format.md +3 -3
- package/docs/diagnostics.md +42 -0
- package/docs/reference/format-details.md +105 -22
- package/docs/reference/note-types.md +8 -1
- package/docs/reference/theme-pages.md +51 -0
- package/docs/source-preview.md +1 -1
- package/e2e.mjs +1 -1
- package/engine/anchors.mjs +2 -2
- package/engine/asset-shipping.mjs +22 -8
- package/engine/code-fences.mjs +3 -3
- package/engine/content-blocks.mjs +3 -3
- package/engine/content-figures.mjs +5 -5
- package/engine/content-index.mjs +1 -1
- package/engine/content-scripts.mjs +55 -21
- package/engine/content-spans.mjs +2 -2
- package/engine/content-verse.mjs +750 -0
- package/engine/dictionary-rows.mjs +15 -4
- package/engine/field-reference.mjs +4 -4
- package/engine/first-use.mjs +17 -27
- package/engine/key-case-collisions.mjs +136 -0
- package/engine/language-data.mjs +203 -6
- package/engine/language-terms.mjs +74 -5
- package/engine/map-lexicon.mjs +42 -23
- package/engine/names.mjs +36 -9
- package/engine/note-matter.mjs +66 -0
- package/engine/pdf-build.mjs +1 -0
- package/engine/pdf-render.mjs +107 -18
- package/engine/read-stdin.mjs +29 -0
- package/engine/run-rows.mjs +2 -2
- package/engine/run-words.mjs +4 -111
- package/engine/site-build.mjs +4 -4
- package/engine/site-preview.mjs +14 -14
- package/engine/sql-tables.mjs +20 -3
- package/engine/web-wikilinks.mjs +1 -1
- package/engine/word-forms.mjs +349 -0
- package/engine/word-terms.mjs +9 -0
- package/hugo-theme/static/css/style.css +92 -14
- package/package.json +3 -3
- package/types/e2e.d.mts +2 -2
- package/types/engine/address-fields.d.mts +1 -1
- package/types/engine/address-values.d.mts +1 -1
- package/types/engine/asset-shipping.d.mts +5 -0
- package/types/engine/code-fences.d.mts +3 -3
- package/types/engine/content-format-check.d.mts +1 -1
- package/types/engine/content-scripts.d.mts +8 -4
- package/types/engine/content-verse.d.mts +240 -0
- package/types/engine/dictionary-rows.d.mts +10 -0
- package/types/engine/field-reference.d.mts +1 -1
- package/types/engine/key-case-collisions.d.mts +28 -0
- package/types/engine/language-data.d.mts +0 -2
- package/types/engine/map-lexicon.d.mts +19 -6
- package/types/engine/names.d.mts +34 -3
- package/types/engine/note-matter.d.mts +48 -0
- package/types/engine/note-vocabulary.d.mts +1 -1
- package/types/engine/pdf-coverage.d.mts +1 -1
- 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/sql-tables.d.mts +4 -1
- package/types/engine/text-maps.d.mts +1 -1
- package/types/engine/web-wikilinks.d.mts +1 -1
- package/types/engine/word-forms.d.mts +82 -0
- package/types/release.d.mts +1 -1
- package/engine/content-poetry.mjs +0 -240
- package/types/engine/content-poetry.d.mts +0 -58
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* One run as an index record carries it.
|
|
3
3
|
*
|
|
4
4
|
* @typedef {object} RecordRun
|
|
5
|
-
* @property {"span"|"block"|"
|
|
5
|
+
* @property {"span"|"block"|"verse"} kind - The construct.
|
|
6
6
|
* @property {string|null} lang - The language it is set in, as written: its
|
|
7
7
|
* own `lang=`, else the one the div or fence around it states.
|
|
8
8
|
* @property {string|null} tag - The BCP 47 tag it resolves to, or null.
|
|
@@ -57,7 +57,7 @@ export type RecordRun = {
|
|
|
57
57
|
/**
|
|
58
58
|
* - The construct.
|
|
59
59
|
*/
|
|
60
|
-
kind: "span" | "block" | "
|
|
60
|
+
kind: "span" | "block" | "verse";
|
|
61
61
|
/**
|
|
62
62
|
* - The language it is set in, as written: its
|
|
63
63
|
* own `lang=`, else the one the div or fence around it states.
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A word as it is compared: NFC, lowercase, `’` read as `'`.
|
|
3
|
-
*
|
|
4
|
-
* @param {string} word - The word.
|
|
5
|
-
* @returns {string} Its folded form.
|
|
6
|
-
*/
|
|
7
|
-
export function foldWord(word: string): string;
|
|
8
|
-
/**
|
|
9
|
-
* The words a language note accepts of its own: its forms, and what its
|
|
10
|
-
* plural rows, verb pattern and replacing suffixes derive from them.
|
|
11
|
-
*
|
|
12
|
-
* @param {Record<string, any>} data - The language note's `data`.
|
|
13
|
-
* @returns {Set<string>} The words, folded.
|
|
14
|
-
*/
|
|
15
|
-
export function lexiconWords(data: Record<string, any>): Set<string>;
|
|
16
1
|
/**
|
|
17
2
|
* The words of every name a language note registers under a tongue other than
|
|
18
3
|
* `translation`.
|
|
@@ -65,10 +65,12 @@ export function findSqlBlocks(markdown: string): Array<{
|
|
|
65
65
|
* @param {object} [opts.addressContext] - Address resolution context.
|
|
66
66
|
* @param {"all"|"public"} [opts.audience] - Whether to exclude GM notes from
|
|
67
67
|
* local and dependency relations.
|
|
68
|
+
* @param {string} [opts.contentBase] - The directory the records' paths are
|
|
69
|
+
* relative to, so a refused key is reported at its file and line.
|
|
68
70
|
* @returns {Promise<{query: (sql: string) => Promise<object[]>,
|
|
69
71
|
* close: () => Promise<void>}>} The open database.
|
|
70
72
|
*/
|
|
71
|
-
export function openNotesDatabase(records: object[], { dir, dependencies, addressContext, audience }?: {
|
|
73
|
+
export function openNotesDatabase(records: object[], { dir, dependencies, addressContext, audience, contentBase }?: {
|
|
72
74
|
dir?: string | undefined;
|
|
73
75
|
dependencies?: {
|
|
74
76
|
id: string;
|
|
@@ -76,6 +78,7 @@ export function openNotesDatabase(records: object[], { dir, dependencies, addres
|
|
|
76
78
|
}[] | undefined;
|
|
77
79
|
addressContext?: object | undefined;
|
|
78
80
|
audience?: "all" | "public" | undefined;
|
|
81
|
+
contentBase?: string | undefined;
|
|
79
82
|
}): Promise<{
|
|
80
83
|
query: (sql: string) => Promise<object[]>;
|
|
81
84
|
close: () => Promise<void>;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Values are read from the *parsed* frontmatter, so a `[[` inside a YAML comment
|
|
20
20
|
* is not a hit, and every hit can be named by the path a reader would look at.
|
|
21
21
|
*
|
|
22
|
-
* @param {unknown} fm - Parsed frontmatter, as `
|
|
22
|
+
* @param {unknown} fm - Parsed frontmatter, as `readMatter` returns it.
|
|
23
23
|
* @returns {Array<{path: string, link: string}>} In reading order; `path` is the
|
|
24
24
|
* dotted key path of the offending value (`government.summary`, `aliases.1`).
|
|
25
25
|
*/
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A word as it is compared: NFC, lowercase, `’` read as `'`.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} word - The word.
|
|
5
|
+
* @returns {string} Its folded form.
|
|
6
|
+
*/
|
|
7
|
+
export function foldWord(word: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Add a text's words to a set, each whole and each part of a compound.
|
|
10
|
+
*
|
|
11
|
+
* @param {Set<string>} set - The set.
|
|
12
|
+
* @param {string} text - A form or a name.
|
|
13
|
+
*/
|
|
14
|
+
export function addWords(set: Set<string>, text: string): void;
|
|
15
|
+
/**
|
|
16
|
+
* One form a rule derives from a lexicon word.
|
|
17
|
+
*
|
|
18
|
+
* @typedef {object} DerivedForm
|
|
19
|
+
* @property {string} form - The form, folded.
|
|
20
|
+
* @property {Record<string, any>} word - The `data.words` entry it comes from.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Every form the language's rules derive from its lexicon words, in
|
|
24
|
+
* `data.words` order, each once, leaving out a form that is a lexicon word.
|
|
25
|
+
*
|
|
26
|
+
* @param {Record<string, any>|undefined} data - The language note's `data`.
|
|
27
|
+
* @returns {DerivedForm[]} The forms.
|
|
28
|
+
*/
|
|
29
|
+
export function derivedForms(data: Record<string, any> | undefined): DerivedForm[];
|
|
30
|
+
/**
|
|
31
|
+
* The words a language note accepts of its own: its forms, and what its rules
|
|
32
|
+
* derive from them (see {@link derivedForms}), each whole and each part of a
|
|
33
|
+
* compound.
|
|
34
|
+
*
|
|
35
|
+
* @param {Record<string, any>} data - The language note's `data`.
|
|
36
|
+
* @returns {Set<string>} The words, folded.
|
|
37
|
+
*/
|
|
38
|
+
export function lexiconWords(data: Record<string, any>): Set<string>;
|
|
39
|
+
/**
|
|
40
|
+
* A language's agreement rule, as derivation reads it.
|
|
41
|
+
*/
|
|
42
|
+
export type Agreement = {
|
|
43
|
+
/**
|
|
44
|
+
* - The class bases that agree.
|
|
45
|
+
*/
|
|
46
|
+
classes: ReadonlySet<string>;
|
|
47
|
+
/**
|
|
48
|
+
* - The endings, folded, longest first.
|
|
49
|
+
*/
|
|
50
|
+
endings: readonly string[];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* A language's participle rule, as derivation reads it.
|
|
54
|
+
*/
|
|
55
|
+
export type Participles = {
|
|
56
|
+
/**
|
|
57
|
+
* - The root endings, folded, longest
|
|
58
|
+
* first.
|
|
59
|
+
*/
|
|
60
|
+
root: readonly string[];
|
|
61
|
+
/**
|
|
62
|
+
* -
|
|
63
|
+
* The participle suffixes, folded.
|
|
64
|
+
*/
|
|
65
|
+
suffixes: Array<{
|
|
66
|
+
suffix: string;
|
|
67
|
+
replaces: string | undefined;
|
|
68
|
+
}>;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* One form a rule derives from a lexicon word.
|
|
72
|
+
*/
|
|
73
|
+
export type DerivedForm = {
|
|
74
|
+
/**
|
|
75
|
+
* - The form, folded.
|
|
76
|
+
*/
|
|
77
|
+
form: string;
|
|
78
|
+
/**
|
|
79
|
+
* - The `data.words` entry it comes from.
|
|
80
|
+
*/
|
|
81
|
+
word: Record<string, any>;
|
|
82
|
+
};
|
package/types/release.d.mts
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
export function packRelease({ stageDir, outDir, artifact, metadataDir, pdf, }?: {
|
|
39
39
|
stageDir?: string | undefined;
|
|
40
40
|
outDir?: string | undefined;
|
|
41
|
-
artifact?: "
|
|
41
|
+
artifact?: "system" | "module" | undefined;
|
|
42
42
|
metadataDir?: string | undefined;
|
|
43
43
|
pdf?: boolean | undefined;
|
|
44
44
|
}): Promise<{
|
|
@@ -1,240 +0,0 @@
|
|
|
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
|
-
/** The authored verse lines, excluding stanza separators. */
|
|
15
|
-
export function verseLines(body) {
|
|
16
|
-
return String(body ?? "")
|
|
17
|
-
.split("\n")
|
|
18
|
-
.filter((line) => line.trim());
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* The least-indented verse is level zero. Every two additional leading spaces
|
|
23
|
-
* add one level; a leftover space is discarded and the level stops at eight.
|
|
24
|
-
* Blank lines divide stanzas rather than contributing to the baseline.
|
|
25
|
-
*/
|
|
26
|
-
export function poetryStanzas(body) {
|
|
27
|
-
const lines = String(body ?? "").split("\n");
|
|
28
|
-
const verses = verseLines(body);
|
|
29
|
-
if (!verses.length) return [];
|
|
30
|
-
const baseline = Math.min(...verses.map((line) => /^ */.exec(line)[0].length));
|
|
31
|
-
const stanzas = [];
|
|
32
|
-
let stanza = [];
|
|
33
|
-
for (const line of lines) {
|
|
34
|
-
if (!line.trim()) {
|
|
35
|
-
if (stanza.length) stanzas.push(stanza);
|
|
36
|
-
stanza = [];
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
const spaces = /^ */.exec(line)[0].length;
|
|
40
|
-
stanza.push({
|
|
41
|
-
text: line.slice(spaces).trimEnd(),
|
|
42
|
-
level: Math.min(8, Math.floor((spaces - baseline) / 2)),
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
if (stanza.length) stanzas.push(stanza);
|
|
46
|
-
return stanzas;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** A verse is inline Markdown even when it starts with block syntax. */
|
|
50
|
-
export function inlineVerse(text) {
|
|
51
|
-
return String(text)
|
|
52
|
-
.replace(/^(#{1,6}\s|>\s|[-+*]\s|:{1,2}\s|`{3,}|~{3,}|-{3,}\s*$)/, "\\$1")
|
|
53
|
-
.replace(/^(\d+)([.)])\s/, "$1\\$2 ");
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Keep stanzas as Markdown paragraphs and make every verse line a hard break.
|
|
58
|
-
* The surrounding renderer still sees the entire note, so links and footnote
|
|
59
|
-
* definitions remain in scope. The span carries the indent for each line.
|
|
60
|
-
*/
|
|
61
|
-
export function poetryMarkdown(body) {
|
|
62
|
-
return poetryStanzas(body)
|
|
63
|
-
.map((stanza) =>
|
|
64
|
-
stanza
|
|
65
|
-
.map(
|
|
66
|
-
({ text, level }) =>
|
|
67
|
-
`<span class="poetry-line${level ? ` i${level}` : ""}">${inlineVerse(text)}</span>`,
|
|
68
|
-
)
|
|
69
|
-
.join(" \n"),
|
|
70
|
-
)
|
|
71
|
-
.join("\n\n");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
import MarkdownIt from "markdown-it";
|
|
75
|
-
import { slugify } from "./content-slug.mjs";
|
|
76
|
-
import { parseExtensionAttributes, refusedAttributes } from "./extension-attributes.mjs";
|
|
77
|
-
|
|
78
|
-
const structureParser = new MarkdownIt({ html: true });
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* The keys a poetry fence's attribute block takes. `lang`, `font` and `map`
|
|
82
|
-
* are read by `scanRuns` in content-scripts.mjs; `dir`, and on the site
|
|
83
|
-
* `data-pagefind-ignore`, are written by its rewrite of a fence set in one of
|
|
84
|
-
* the faces, and reach the element as they are written.
|
|
85
|
-
*/
|
|
86
|
-
export const POETRY_KEYS = Object.freeze([
|
|
87
|
-
"form",
|
|
88
|
-
"meter",
|
|
89
|
-
"rhyme",
|
|
90
|
-
"syllables",
|
|
91
|
-
"lang",
|
|
92
|
-
"font",
|
|
93
|
-
"map",
|
|
94
|
-
"dir",
|
|
95
|
-
"data-pagefind-ignore",
|
|
96
|
-
]);
|
|
97
|
-
|
|
98
|
-
/** Read poetry code fences; start/end are inclusive zero-based source lines.
|
|
99
|
-
* @param {string} source - Authored Markdown.
|
|
100
|
-
*/
|
|
101
|
-
export function scanPoetry(source) {
|
|
102
|
-
const lines = String(source ?? "").split("\n"),
|
|
103
|
-
blocks = [],
|
|
104
|
-
errors = [];
|
|
105
|
-
const literalLines = new Set();
|
|
106
|
-
for (const token of structureParser.parse(String(source ?? ""), {})) {
|
|
107
|
-
if (!token.map || !["blockquote_open", "html_block"].includes(token.type)) continue;
|
|
108
|
-
for (let line = token.map[0]; line < token.map[1]; line++) literalLines.add(line);
|
|
109
|
-
}
|
|
110
|
-
let opening = null;
|
|
111
|
-
for (let i = 0; i < lines.length; i++) {
|
|
112
|
-
if (literalLines.has(i)) continue;
|
|
113
|
-
const match = /^ *(`{3,}|~{3,})(.*)$/.exec(lines[i]);
|
|
114
|
-
if (opening) {
|
|
115
|
-
if (
|
|
116
|
-
match &&
|
|
117
|
-
match[1][0] === opening.fence[0] &&
|
|
118
|
-
match[1].length >= opening.fence.length &&
|
|
119
|
-
!match[2].trim()
|
|
120
|
-
) {
|
|
121
|
-
if (opening.name === "poetry") {
|
|
122
|
-
const body = lines.slice(opening.start + 1, i).join("\n");
|
|
123
|
-
if (!body.trim())
|
|
124
|
-
errors.push({
|
|
125
|
-
line: opening.start + 1,
|
|
126
|
-
column: 1,
|
|
127
|
-
message: "poetry block is empty",
|
|
128
|
-
});
|
|
129
|
-
for (let n = opening.start + 1; n < i; n++) {
|
|
130
|
-
const col = lines[n].indexOf("\t");
|
|
131
|
-
if (col >= 0)
|
|
132
|
-
errors.push({
|
|
133
|
-
line: n + 1,
|
|
134
|
-
column: col + 1,
|
|
135
|
-
message: "tabs are not allowed in poetry",
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
if (
|
|
139
|
-
opening.attributes.syllables &&
|
|
140
|
-
opening.attributes.syllables.split(",").length !== verseLines(body).length
|
|
141
|
-
)
|
|
142
|
-
errors.push({
|
|
143
|
-
line: opening.start + 1,
|
|
144
|
-
column: 1,
|
|
145
|
-
message: `poetry syllables= gives ${opening.attributes.syllables.split(",").length} counts for ${verseLines(body).length} verse lines`,
|
|
146
|
-
});
|
|
147
|
-
blocks.push({ ...opening, end: i, body });
|
|
148
|
-
}
|
|
149
|
-
opening = null;
|
|
150
|
-
}
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
if (!match) continue;
|
|
154
|
-
const info = match[2].trim();
|
|
155
|
-
opening = {
|
|
156
|
-
start: i,
|
|
157
|
-
fence: match[1],
|
|
158
|
-
name: info.split(/\s/)[0],
|
|
159
|
-
attributes: {},
|
|
160
|
-
id: "",
|
|
161
|
-
classes: [],
|
|
162
|
-
indent: /^ */.exec(lines[i])[0],
|
|
163
|
-
};
|
|
164
|
-
if (opening.name !== "poetry") continue;
|
|
165
|
-
const raw = info.slice(6).trim();
|
|
166
|
-
const at = { line: i + 1, column: 1 };
|
|
167
|
-
if (raw && !/^\{[^}\n]*\}$/.test(raw)) {
|
|
168
|
-
errors.push({ ...at, message: "poetry attributes need {…} braces" });
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
const parsed = parseExtensionAttributes(raw ? raw.slice(1, -1) : "");
|
|
172
|
-
opening.id = parsed.id;
|
|
173
|
-
opening.classes = parsed.classes;
|
|
174
|
-
opening.attributes = parsed.values;
|
|
175
|
-
for (const message of [...parsed.problems, ...refusedAttributes(parsed.values)])
|
|
176
|
-
errors.push({ ...at, message });
|
|
177
|
-
for (const [key, value] of Object.entries(parsed.values)) {
|
|
178
|
-
if (!POETRY_KEYS.includes(key))
|
|
179
|
-
errors.push({ ...at, message: `${key}= is not a poetry attribute` });
|
|
180
|
-
else if (key === "syllables" && !/^[1-9]\d*(?:,[1-9]\d*)*$/.test(value))
|
|
181
|
-
errors.push({
|
|
182
|
-
...at,
|
|
183
|
-
message: "poetry syllables= needs positive comma-separated counts",
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (opening?.name === "poetry")
|
|
188
|
-
errors.push({
|
|
189
|
-
line: opening.start + 1,
|
|
190
|
-
column: 1,
|
|
191
|
-
message: "poetry block needs a closing code fence",
|
|
192
|
-
});
|
|
193
|
-
return { blocks, errors };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/** Replace verse fences with HTML wrappers whose body remains inline Markdown.
|
|
197
|
-
* @param {string} source - Authored Markdown.
|
|
198
|
-
*/
|
|
199
|
-
export function renderPoetry(source) {
|
|
200
|
-
const { blocks, errors } = scanPoetry(source),
|
|
201
|
-
lines = String(source ?? "").split("\n"),
|
|
202
|
-
output = [];
|
|
203
|
-
let cursor = 0;
|
|
204
|
-
const escape = (value) =>
|
|
205
|
-
String(value)
|
|
206
|
-
.replace(/&/g, "&")
|
|
207
|
-
.replace(/"/g, """)
|
|
208
|
-
.replace(/</g, "<")
|
|
209
|
-
.replace(/>/g, ">");
|
|
210
|
-
for (const block of blocks) {
|
|
211
|
-
output.push(...lines.slice(cursor, block.start));
|
|
212
|
-
const attrs = [`class="${escape(["poetry", ...block.classes].join(" "))}"`];
|
|
213
|
-
if (block.id) attrs.push(`id="${escape(slugify(block.id))}"`);
|
|
214
|
-
for (const [key, value] of Object.entries(block.attributes))
|
|
215
|
-
if (!refusedAttributes({ [key]: value }).length)
|
|
216
|
-
attrs.push(
|
|
217
|
-
`${["lang", "dir", "data-pagefind-ignore"].includes(key) ? key : `data-${key}`}="${escape(value)}"`,
|
|
218
|
-
);
|
|
219
|
-
let indent = block.indent;
|
|
220
|
-
if (
|
|
221
|
-
indent.length >= 4 &&
|
|
222
|
-
!lines.slice(0, block.start).some((line) => /^ *(?:[-+*]|\d+[.)]) +/.test(line))
|
|
223
|
-
)
|
|
224
|
-
indent = "";
|
|
225
|
-
output.push(
|
|
226
|
-
"",
|
|
227
|
-
`${indent}<div ${attrs.join(" ")}>`,
|
|
228
|
-
"",
|
|
229
|
-
...poetryMarkdown(block.body)
|
|
230
|
-
.split("\n")
|
|
231
|
-
.map((line) => (line ? indent + line : line)),
|
|
232
|
-
"",
|
|
233
|
-
`${indent}</div>`,
|
|
234
|
-
"",
|
|
235
|
-
);
|
|
236
|
-
cursor = block.end + 1;
|
|
237
|
-
}
|
|
238
|
-
output.push(...lines.slice(cursor));
|
|
239
|
-
return { markdown: output.join("\n"), errors };
|
|
240
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/** The authored verse lines, excluding stanza separators. */
|
|
2
|
-
export function verseLines(body: any): string[];
|
|
3
|
-
/**
|
|
4
|
-
* The least-indented verse is level zero. Every two additional leading spaces
|
|
5
|
-
* add one level; a leftover space is discarded and the level stops at eight.
|
|
6
|
-
* Blank lines divide stanzas rather than contributing to the baseline.
|
|
7
|
-
*/
|
|
8
|
-
export function poetryStanzas(body: any): {
|
|
9
|
-
text: string;
|
|
10
|
-
level: number;
|
|
11
|
-
}[][];
|
|
12
|
-
/** A verse is inline Markdown even when it starts with block syntax. */
|
|
13
|
-
export function inlineVerse(text: any): string;
|
|
14
|
-
/**
|
|
15
|
-
* Keep stanzas as Markdown paragraphs and make every verse line a hard break.
|
|
16
|
-
* The surrounding renderer still sees the entire note, so links and footnote
|
|
17
|
-
* definitions remain in scope. The span carries the indent for each line.
|
|
18
|
-
*/
|
|
19
|
-
export function poetryMarkdown(body: any): string;
|
|
20
|
-
/** Read poetry code fences; start/end are inclusive zero-based source lines.
|
|
21
|
-
* @param {string} source - Authored Markdown.
|
|
22
|
-
*/
|
|
23
|
-
export function scanPoetry(source: string): {
|
|
24
|
-
blocks: {
|
|
25
|
-
end: number;
|
|
26
|
-
body: string;
|
|
27
|
-
start: number;
|
|
28
|
-
fence: string;
|
|
29
|
-
name: string;
|
|
30
|
-
attributes: {};
|
|
31
|
-
id: string;
|
|
32
|
-
classes: never[];
|
|
33
|
-
indent: string;
|
|
34
|
-
}[];
|
|
35
|
-
errors: {
|
|
36
|
-
line: number;
|
|
37
|
-
column: number;
|
|
38
|
-
message: string;
|
|
39
|
-
}[];
|
|
40
|
-
};
|
|
41
|
-
/** Replace verse fences with HTML wrappers whose body remains inline Markdown.
|
|
42
|
-
* @param {string} source - Authored Markdown.
|
|
43
|
-
*/
|
|
44
|
-
export function renderPoetry(source: string): {
|
|
45
|
-
markdown: string;
|
|
46
|
-
errors: {
|
|
47
|
-
line: number;
|
|
48
|
-
column: number;
|
|
49
|
-
message: string;
|
|
50
|
-
}[];
|
|
51
|
-
};
|
|
52
|
-
/**
|
|
53
|
-
* The keys a poetry fence's attribute block takes. `lang`, `font` and `map`
|
|
54
|
-
* are read by `scanRuns` in content-scripts.mjs; `dir`, and on the site
|
|
55
|
-
* `data-pagefind-ignore`, are written by its rewrite of a fence set in one of
|
|
56
|
-
* the faces, and reach the element as they are written.
|
|
57
|
-
*/
|
|
58
|
-
export const POETRY_KEYS: readonly string[];
|