@openpresentation/opf 0.26.3 → 0.26.4
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/README.md +1 -1
- package/dist/browser.js +13 -11
- package/dist/{chunk-34LIUOT7.js → chunk-6IXSHIAL.js} +6 -6
- package/dist/{chunk-PXOXTJTN.js → chunk-6NBTZYYD.js} +1 -1
- package/dist/{chunk-FXNUM2PN.js → chunk-EDCRBTJF.js} +4 -4
- package/dist/chunk-GWJBZ4LC.js +639 -0
- package/dist/{chunk-YVNFACTC.js → chunk-H5QC3X3K.js} +1 -1
- package/dist/{chunk-WASENX2R.js → chunk-MN7DRK57.js} +40 -648
- package/dist/{chunk-GLLR3AQ5.js → chunk-NZLW6FBQ.js} +1 -1
- package/dist/{chunk-7RY7QWWJ.js → chunk-O6BY3AU2.js} +1 -1
- package/dist/{chunk-PB5X2VSV.js → chunk-PIO73BQ6.js} +4 -3
- package/dist/{chunk-DEI2M2H6.js → chunk-QAEBYDKY.js} +2 -4
- package/dist/{chunk-54OV2WQC.js → chunk-QH2W6HIM.js} +1 -1
- package/dist/chunk-RYZPBJLB.js +4 -0
- package/dist/{chunk-NJWI6MPT.js → chunk-XFOOKVRI.js} +16 -1
- package/dist/{chunk-2ZQBMZTZ.js → chunk-ZSZDVIDV.js} +2 -2
- package/dist/{conversion-BjsKgxBZ.d.ts → conversion-DSafOGzh.d.ts} +2 -2
- package/dist/conversion-QIAJM6DQ.js +20 -0
- package/dist/convert.js +5 -5
- package/dist/deck.js +9 -7
- package/dist/diff.js +2 -2
- package/dist/docs.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +17 -15
- package/dist/markdown.js +7 -5
- package/dist/node-engine.d.ts +2 -2
- package/dist/node-engine.js +17 -15
- package/dist/pagination.js +2 -2
- package/dist/patch.js +1 -1
- package/dist/schema.js +1 -1
- package/dist/source.d.ts +102 -4
- package/dist/source.js +388 -9
- package/dist/validator.js +1 -1
- package/dist/yaml.js +5 -4
- package/package.json +5 -5
- package/dist/conversion-F4UCOGR7.js +0 -18
|
@@ -1,218 +1,18 @@
|
|
|
1
|
-
import { parseQuoteLines, levelsFromIndents, parseCodeFence, splitWhen
|
|
2
|
-
import { Ctx, splitLines, lineRange, readYamlMapping,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { checkCatalogsOption, PROMOTED_REGION_KEY } from './chunk-XRMV2GMB.js';
|
|
1
|
+
import { parseQuoteLines, levelsFromIndents, parseCodeFence, splitWhen } from './chunk-O6BY3AU2.js';
|
|
2
|
+
import { Ctx, splitLines, lineRange, readYamlMapping, readCsv, DECIMAL } from './chunk-QAEBYDKY.js';
|
|
3
|
+
import { runText, isRecord, FORMATTING } from './chunk-6NBTZYYD.js';
|
|
4
|
+
import { PROMOTED_REGION_KEY } from './chunk-XRMV2GMB.js';
|
|
6
5
|
|
|
7
|
-
// src/markdown/
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
out[key] = value;
|
|
18
|
-
}
|
|
19
|
-
return Object.keys(out).length === 1 ? out.text : out;
|
|
20
|
-
}
|
|
21
|
-
var sameFormat = (a, b) => {
|
|
22
|
-
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
23
|
-
keys.delete("text");
|
|
24
|
-
for (const key of keys) if (a[key] !== b[key]) return false;
|
|
25
|
-
return true;
|
|
26
|
-
};
|
|
27
|
-
function canonText(value) {
|
|
28
|
-
if (typeof value === "string") return value;
|
|
29
|
-
if (!Array.isArray(value)) return void 0;
|
|
30
|
-
const merged = [];
|
|
31
|
-
for (const raw of value) {
|
|
32
|
-
const run = canonRun(raw);
|
|
33
|
-
if (run === void 0) return void 0;
|
|
34
|
-
if (runText(run) === "") continue;
|
|
35
|
-
const last = merged.at(-1);
|
|
36
|
-
if (last !== void 0) {
|
|
37
|
-
if (typeof last === "string" && typeof run === "string") {
|
|
38
|
-
merged[merged.length - 1] = last + run;
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (typeof last === "object" && typeof run === "object" && sameFormat(last, run)) {
|
|
42
|
-
merged[merged.length - 1] = { ...last, text: last.text + run.text };
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
merged.push(run);
|
|
47
|
-
}
|
|
48
|
-
if (merged.length === 0) return "";
|
|
49
|
-
if (merged.every(isPlainRun)) return merged.map(runText).join("");
|
|
50
|
-
return merged;
|
|
51
|
-
}
|
|
52
|
-
function canonItem(item) {
|
|
53
|
-
if (typeof item === "string" || Array.isArray(item)) return canonText(item);
|
|
54
|
-
if (!isRecord(item)) return void 0;
|
|
55
|
-
for (const key of Object.keys(item)) if (key !== "text" && key !== "level" && key !== "description") return void 0;
|
|
56
|
-
const text = canonText(item.text);
|
|
57
|
-
if (text === void 0) return void 0;
|
|
58
|
-
const level = item.level === void 0 ? 0 : item.level;
|
|
59
|
-
if (!Number.isInteger(level) || level < 0) return void 0;
|
|
60
|
-
const description = item.description === void 0 ? void 0 : canonText(item.description);
|
|
61
|
-
if (item.description !== void 0 && description === void 0) return void 0;
|
|
62
|
-
if (level === 0 && description === void 0) return text;
|
|
63
|
-
const out = { text };
|
|
64
|
-
if (description !== void 0) out.description = description;
|
|
65
|
-
if (level !== 0) out.level = level;
|
|
66
|
-
return out;
|
|
67
|
-
}
|
|
68
|
-
var levelOf = (item) => isRecord(item) && typeof item.level === "number" ? item.level : 0;
|
|
69
|
-
function canonItems(value) {
|
|
70
|
-
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
71
|
-
const items = [];
|
|
72
|
-
let previous = -1;
|
|
73
|
-
for (const raw of value) {
|
|
74
|
-
const item = canonItem(raw);
|
|
75
|
-
if (item === void 0) return void 0;
|
|
76
|
-
const level = levelOf(item);
|
|
77
|
-
if (level > previous + 1 || previous < 0 && level !== 0) return void 0;
|
|
78
|
-
previous = level;
|
|
79
|
-
items.push(item);
|
|
80
|
-
}
|
|
81
|
-
return items;
|
|
82
|
-
}
|
|
83
|
-
var only = (value, keys) => Object.keys(value).every((key) => keys.includes(key));
|
|
84
|
-
function canonQuote(value) {
|
|
85
|
-
if (typeof value === "string") return value;
|
|
86
|
-
if (!isRecord(value) || !only(value, ["text", "attribution", "source"])) return void 0;
|
|
87
|
-
const text = canonText(value.text);
|
|
88
|
-
if (text === void 0) return void 0;
|
|
89
|
-
for (const key of ["attribution", "source"]) if (value[key] !== void 0 && typeof value[key] !== "string") return void 0;
|
|
90
|
-
if (value.source !== void 0 && value.attribution === void 0) return void 0;
|
|
91
|
-
if (value.attribution === void 0) return typeof text === "string" ? text : { text };
|
|
92
|
-
const out = { text, attribution: value.attribution };
|
|
93
|
-
if (value.source !== void 0) out.source = value.source;
|
|
94
|
-
return out;
|
|
95
|
-
}
|
|
96
|
-
function canonCode(value) {
|
|
97
|
-
if (typeof value === "string") return value;
|
|
98
|
-
if (!isRecord(value) || !only(value, ["source", "language", "filename"]) || typeof value.source !== "string") return void 0;
|
|
99
|
-
for (const key of ["language", "filename"]) if (value[key] !== void 0 && (typeof value[key] !== "string" || value[key] === "")) return void 0;
|
|
100
|
-
if (value.language === void 0 && value.filename === void 0) return value.source;
|
|
101
|
-
const out = { source: value.source };
|
|
102
|
-
if (value.language !== void 0) out.language = value.language;
|
|
103
|
-
if (value.filename !== void 0) out.filename = value.filename;
|
|
104
|
-
return out;
|
|
105
|
-
}
|
|
106
|
-
function canonMetric(value) {
|
|
107
|
-
if (typeof value === "string" || typeof value === "number") return value;
|
|
108
|
-
if (!isRecord(value) || !only(value, ["value", "label", "description", "unit", "delta", "trend", "sentiment"])) return void 0;
|
|
109
|
-
if (typeof value.value !== "string" && typeof value.value !== "number") return void 0;
|
|
110
|
-
const out = { value: value.value };
|
|
111
|
-
for (const key of ["label", "description", "unit", "delta", "trend", "sentiment"]) {
|
|
112
|
-
const entry = value[key];
|
|
113
|
-
if (entry === void 0) continue;
|
|
114
|
-
if (typeof entry !== "string" && !(key === "delta" && typeof entry === "number")) return void 0;
|
|
115
|
-
out[key] = entry;
|
|
116
|
-
}
|
|
117
|
-
return Object.keys(out).length === 1 ? out.value : out;
|
|
118
|
-
}
|
|
119
|
-
function canonAsset(value) {
|
|
120
|
-
if (typeof value === "string") return value;
|
|
121
|
-
if (!isRecord(value) || !only(value, ["src", "alt", "title"]) || typeof value.src !== "string") return void 0;
|
|
122
|
-
for (const key of ["alt", "title"]) if (value[key] !== void 0 && typeof value[key] !== "string") return void 0;
|
|
123
|
-
const out = { src: value.src };
|
|
124
|
-
if (value.alt !== void 0) out.alt = value.alt;
|
|
125
|
-
if (value.title !== void 0) out.title = value.title;
|
|
126
|
-
return Object.keys(out).length === 1 ? out.src : out;
|
|
127
|
-
}
|
|
128
|
-
function canonCell(cell) {
|
|
129
|
-
if (cell === null) return cell;
|
|
130
|
-
if (typeof cell === "string" || Array.isArray(cell)) return canonText(cell);
|
|
131
|
-
return void 0;
|
|
132
|
-
}
|
|
133
|
-
function canonTable(value) {
|
|
134
|
-
if (!isRecord(value) || !only(value, ["columns", "rows"]) || !Array.isArray(value.rows)) return void 0;
|
|
135
|
-
const out = {};
|
|
136
|
-
if (value.columns !== void 0) {
|
|
137
|
-
if (!Array.isArray(value.columns) || value.columns.length === 0) return void 0;
|
|
138
|
-
const columns = value.columns.map((cell) => cell === null ? null : typeof cell === "string" || Array.isArray(cell) ? canonText(cell) : void 0);
|
|
139
|
-
if (columns.includes(void 0) || columns.every((cell) => cell === null || cell === "")) return void 0;
|
|
140
|
-
out.columns = columns;
|
|
141
|
-
}
|
|
142
|
-
const rows = [];
|
|
143
|
-
for (const row of value.rows) {
|
|
144
|
-
if (!Array.isArray(row)) return void 0;
|
|
145
|
-
const cells = row.map(canonCell);
|
|
146
|
-
if (cells.includes(void 0)) return void 0;
|
|
147
|
-
rows.push(cells);
|
|
148
|
-
}
|
|
149
|
-
out.rows = rows;
|
|
150
|
-
return out;
|
|
151
|
-
}
|
|
152
|
-
function canonChart(value) {
|
|
153
|
-
if (!isRecord(value) || !only(value, ["type", "alt", "data"]) || typeof value.type !== "string" || !isRecord(value.data)) return void 0;
|
|
154
|
-
if (value.alt !== void 0 && typeof value.alt !== "string") return void 0;
|
|
155
|
-
const alt = value.alt === void 0 ? {} : { alt: value.alt };
|
|
156
|
-
const data = value.data;
|
|
157
|
-
if (Array.isArray(data.columns) && Array.isArray(data.rows) && only(data, ["columns", "rows"])) {
|
|
158
|
-
if (!data.columns.every((column) => typeof column === "string") || data.columns.length === 0 || data.rows.length === 0) return void 0;
|
|
159
|
-
for (const row of data.rows) if (!Array.isArray(row) || !row.every((cell) => cell === null || ["string", "number", "boolean"].includes(typeof cell))) return void 0;
|
|
160
|
-
return { type: value.type, ...alt, data: { columns: data.columns, rows: data.rows } };
|
|
161
|
-
}
|
|
162
|
-
return void 0;
|
|
163
|
-
}
|
|
164
|
-
function canonEvent(event) {
|
|
165
|
-
if (!isRecord(event) || !only(event, ["when", "what", "description", "status"]) || typeof event.what !== "string") return void 0;
|
|
166
|
-
for (const key of ["when", "description"]) if (event[key] !== void 0 && typeof event[key] !== "string") return void 0;
|
|
167
|
-
if (event.status !== void 0 && !["done", "current", "planned"].includes(event.status)) return void 0;
|
|
168
|
-
const out = {};
|
|
169
|
-
if (event.when !== void 0) out.when = event.when;
|
|
170
|
-
out.what = event.what;
|
|
171
|
-
if (event.description !== void 0) out.description = event.description;
|
|
172
|
-
if (event.status !== void 0) out.status = event.status;
|
|
173
|
-
return out;
|
|
174
|
-
}
|
|
175
|
-
function canonTimeline(value) {
|
|
176
|
-
const events = Array.isArray(value) ? value : isRecord(value) && only(value, ["name", "description", "events"]) ? value.events : void 0;
|
|
177
|
-
if (!Array.isArray(events) || events.length === 0) return void 0;
|
|
178
|
-
const canon = events.map(canonEvent);
|
|
179
|
-
if (canon.includes(void 0)) return void 0;
|
|
180
|
-
if (Array.isArray(value)) return canon;
|
|
181
|
-
for (const key of ["name", "description"]) if (value[key] !== void 0 && typeof value[key] !== "string") return void 0;
|
|
182
|
-
const { name, description } = value;
|
|
183
|
-
if (name === void 0 && description === void 0) return canon;
|
|
184
|
-
const out = {};
|
|
185
|
-
if (name !== void 0) out.name = name;
|
|
186
|
-
if (description !== void 0) out.description = description;
|
|
187
|
-
out.events = canon;
|
|
188
|
-
return out;
|
|
189
|
-
}
|
|
190
|
-
function canonContent(key, value) {
|
|
191
|
-
switch (key) {
|
|
192
|
-
case "text":
|
|
193
|
-
return canonText(value);
|
|
194
|
-
case "items":
|
|
195
|
-
case "bullets":
|
|
196
|
-
return canonItems(value);
|
|
197
|
-
case "quote":
|
|
198
|
-
return canonQuote(value);
|
|
199
|
-
case "code":
|
|
200
|
-
return canonCode(value);
|
|
201
|
-
case "metric":
|
|
202
|
-
return canonMetric(value);
|
|
203
|
-
case "image":
|
|
204
|
-
case "video":
|
|
205
|
-
return canonAsset(value);
|
|
206
|
-
case "table":
|
|
207
|
-
return canonTable(value);
|
|
208
|
-
case "chart":
|
|
209
|
-
return canonChart(value);
|
|
210
|
-
case "timeline":
|
|
211
|
-
return canonTimeline(value);
|
|
212
|
-
default:
|
|
213
|
-
return void 0;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
6
|
+
// src/markdown/dialect.ts
|
|
7
|
+
var SLIDE_OPTION_KEYS = ["id", "type", "layout", "section", "tag", "hidden", "beat"];
|
|
8
|
+
var BLOCK_OPTION_KEYS = ["id", "type", "as", "region"];
|
|
9
|
+
var BLOCK_AS_VALUES = ["bullets", "video"];
|
|
10
|
+
var FENCE_KINDS = ["chart", "metric", "timeline", "opf-block", "opf-slide"];
|
|
11
|
+
var METRIC_KEYS = ["value", "label", "description", "unit", "delta", "trend", "sentiment"];
|
|
12
|
+
var FENCE_ATTRIBUTES = { chart: ["alt"], timeline: ["name", "description"], code: ["title"] };
|
|
13
|
+
var SPAN_FLAGS = ["bold", "italic", "underline", "strike", "sup", "sub"];
|
|
14
|
+
var SPAN_VALUES = ["color", "size", "font", "lang"];
|
|
15
|
+
var LINE_KEYWORDS = ["Note:", "Notes:"];
|
|
216
16
|
|
|
217
17
|
// src/markdown/inline.ts
|
|
218
18
|
var PUNCT = /[\p{P}\p{S}]/u;
|
|
@@ -222,7 +22,7 @@ var ASCII_PUNCT = /^[!-/:-@[-`{-~]$/;
|
|
|
222
22
|
var MAX_DEPTH = 16;
|
|
223
23
|
var MAX_TARGET = 2e3;
|
|
224
24
|
var MAX_SPAN = 300;
|
|
225
|
-
var
|
|
25
|
+
var SPAN_FLAGS2 = {
|
|
226
26
|
bold: "bold",
|
|
227
27
|
italic: "italic",
|
|
228
28
|
underline: "underline",
|
|
@@ -233,7 +33,7 @@ var SPAN_FLAGS = {
|
|
|
233
33
|
sub: "subscript",
|
|
234
34
|
subscript: "subscript"
|
|
235
35
|
};
|
|
236
|
-
var
|
|
36
|
+
var SPAN_VALUES2 = { color: "color", size: "fontSize", fontsize: "fontSize", font: "fontFamily", fontfamily: "fontFamily", lang: "lang" };
|
|
237
37
|
function parseAttributes(source) {
|
|
238
38
|
const pairs = [];
|
|
239
39
|
let i = 0;
|
|
@@ -273,8 +73,8 @@ function spanStyle(source, issue) {
|
|
|
273
73
|
if (error) issue("markdown/span-attributes", `${error} A span reads {bold italic color=#ff0000 size=24 font="Open Sans"}.`);
|
|
274
74
|
for (const [rawKey, value] of pairs) {
|
|
275
75
|
const key = rawKey.toLowerCase();
|
|
276
|
-
const flag =
|
|
277
|
-
const field =
|
|
76
|
+
const flag = SPAN_FLAGS2[key];
|
|
77
|
+
const field = SPAN_VALUES2[key];
|
|
278
78
|
if (flag) {
|
|
279
79
|
if (value === true || value === "true") style[flag] = true;
|
|
280
80
|
else if (value !== "false") issue("markdown/span-attributes", `Span flag ${rawKey} takes no value (or =true/false).`);
|
|
@@ -709,8 +509,6 @@ function serializeRuns(value, mode = "paragraph") {
|
|
|
709
509
|
}
|
|
710
510
|
|
|
711
511
|
// src/markdown/parse.ts
|
|
712
|
-
var SLIDE_OPTION_KEYS = ["id", "type", "layout", "section", "tag", "hidden", "beat"];
|
|
713
|
-
var BLOCK_OPTION_KEYS = ["id", "type", "as", "region"];
|
|
714
512
|
var BLANK = /^\s*$/;
|
|
715
513
|
var TIMELINE_STATUS_PREFIX = /^\[([x> ])\]\s+(?=\S)/;
|
|
716
514
|
var TIMELINE_STATUS_OF_MARK = { x: "done", ">": "current", " ": "planned" };
|
|
@@ -857,7 +655,7 @@ function parseOptions(text, keys, kind, ctx, range) {
|
|
|
857
655
|
else if (key in out) {
|
|
858
656
|
ctx.error("options-duplicate", `Option ${key} is given twice.`, help, range);
|
|
859
657
|
ok = false;
|
|
860
|
-
} else if (key === "as" && value
|
|
658
|
+
} else if (key === "as" && !BLOCK_AS_VALUES.includes(value)) {
|
|
861
659
|
ctx.error("options-value", `as takes bullets (a list stored as bullets) or video (an image line stored as video), not ${JSON.stringify(value)}.`, help, range);
|
|
862
660
|
ok = false;
|
|
863
661
|
} else out[key] = value;
|
|
@@ -922,8 +720,8 @@ var SlideParser = class {
|
|
|
922
720
|
}
|
|
923
721
|
this.blocks.push(block);
|
|
924
722
|
}
|
|
925
|
-
native(key, value, range) {
|
|
926
|
-
const block = { payload: { [key]: value }, range, key };
|
|
723
|
+
native(key, value, range, parts) {
|
|
724
|
+
const block = { payload: { [key]: value }, range, key, ...parts ? { parts } : {} };
|
|
927
725
|
this.add(block);
|
|
928
726
|
if (block.options) {
|
|
929
727
|
const as = block.options.as;
|
|
@@ -1070,7 +868,7 @@ var SlideParser = class {
|
|
|
1070
868
|
topIndent = Math.min(topIndent, item.indent);
|
|
1071
869
|
}
|
|
1072
870
|
if (item.ordered) numbered = true;
|
|
1073
|
-
entries.push({ indent: item.indent, rows: [item.content.trim()] });
|
|
871
|
+
entries.push({ indent: item.indent, rows: [item.content.trim()], first: i, last: i });
|
|
1074
872
|
i++;
|
|
1075
873
|
continue;
|
|
1076
874
|
}
|
|
@@ -1080,6 +878,7 @@ var SlideParser = class {
|
|
|
1080
878
|
if (description && indentWidth(description[1]) > current.indent && !current.description) current.description = [description[2].trim()];
|
|
1081
879
|
else if (current.description) current.description.push(text.trim());
|
|
1082
880
|
else current.rows.push(text);
|
|
881
|
+
current.last = i;
|
|
1083
882
|
i++;
|
|
1084
883
|
}
|
|
1085
884
|
this.i = i;
|
|
@@ -1099,7 +898,7 @@ var SlideParser = class {
|
|
|
1099
898
|
if (level) out.level = level;
|
|
1100
899
|
return out;
|
|
1101
900
|
});
|
|
1102
|
-
this.native("items", items, range);
|
|
901
|
+
this.native("items", items, range, entries.map((entry) => lineRange(lines[entry.first], lines[entry.last])));
|
|
1103
902
|
}
|
|
1104
903
|
// --- tables ----------------------------------------------------------------------------------
|
|
1105
904
|
table() {
|
|
@@ -1227,7 +1026,7 @@ var SlideParser = class {
|
|
|
1227
1026
|
}
|
|
1228
1027
|
metric(body, range) {
|
|
1229
1028
|
const { ctx } = this;
|
|
1230
|
-
const allowed =
|
|
1029
|
+
const allowed = METRIC_KEYS;
|
|
1231
1030
|
const help = `A metric block is key: value lines. Keys: ${allowed.join(", ")}. A value is plain text, or a JSON string in double quotes.`;
|
|
1232
1031
|
const out = {};
|
|
1233
1032
|
for (const line of body) {
|
|
@@ -1343,13 +1142,15 @@ var SlideParser = class {
|
|
|
1343
1142
|
const promoted = (block) => typeof block.options?.region === "string" && PROMOTED_REGION_KEY.test(block.options.region);
|
|
1344
1143
|
const content = this.blocks.filter((block) => !promoted(block));
|
|
1345
1144
|
const regions = this.blocks.filter(promoted);
|
|
1346
|
-
const [
|
|
1347
|
-
if (content.length === 1 &&
|
|
1348
|
-
slide[
|
|
1349
|
-
ctx.ranges.set(`${path}/${
|
|
1145
|
+
const [only] = content;
|
|
1146
|
+
if (content.length === 1 && only?.key !== void 0 && only.options?.id === void 0 && only.options?.type === void 0 && only.options?.region === void 0 && isContentKey(only.key) && Object.keys(only.payload).length === 1) {
|
|
1147
|
+
slide[only.key] = only.payload[only.key];
|
|
1148
|
+
ctx.ranges.set(`${path}/${only.key}`, only.range);
|
|
1149
|
+
for (const [k, part] of (only.parts ?? []).entries()) ctx.ranges.set(`${path}/${only.key}/${k}`, part);
|
|
1350
1150
|
} else if (content.length) {
|
|
1351
1151
|
slide.blocks = content.map((block, index) => {
|
|
1352
1152
|
ctx.ranges.set(`${path}/blocks/${index}`, block.range);
|
|
1153
|
+
if (block.key) for (const [k, part] of (block.parts ?? []).entries()) ctx.ranges.set(`${path}/blocks/${index}/${block.key}/${k}`, part);
|
|
1353
1154
|
const out = {};
|
|
1354
1155
|
if (block.options?.id !== void 0) out.id = block.options.id;
|
|
1355
1156
|
if (block.options?.type !== void 0) out.type = block.options.type;
|
|
@@ -1419,379 +1220,21 @@ function frontMatter(lines) {
|
|
|
1419
1220
|
return "unterminated";
|
|
1420
1221
|
}
|
|
1421
1222
|
|
|
1422
|
-
// src/markdown/
|
|
1423
|
-
|
|
1424
|
-
var isContent = (key) => CONTENT2.includes(key);
|
|
1425
|
-
var ROWS = "(?:top|middle|bottom|top\\+middle|middle\\+bottom|top\\+middle\\+bottom)";
|
|
1426
|
-
var COLUMNS = "(?:left|center|right|left\\+center|center\\+right|left\\+center\\+right)";
|
|
1427
|
-
var REGION = new RegExp(`^(?:${ROWS}(?::${COLUMNS})?|${COLUMNS})$`);
|
|
1428
|
-
var NATIVE_SLIDE = /* @__PURE__ */ new Set(["id", "type", "layout", "section", "tag", "hidden", "beat", "title", "subtitle", "notes", "blocks", ...CONTENT2]);
|
|
1429
|
-
function fenceFor(body, char = "`") {
|
|
1430
|
-
let longest = 2;
|
|
1431
|
-
for (const line of body) for (const run of line.matchAll(char === "`" ? /`+/g : /~+/g)) longest = Math.max(longest, run[0].length);
|
|
1432
|
-
return char.repeat(longest + 1);
|
|
1433
|
-
}
|
|
1434
|
-
var fenced = (info, body) => {
|
|
1435
|
-
const fence = fenceFor(body);
|
|
1436
|
-
return [`${fence}${info}`, ...body, fence];
|
|
1437
|
-
};
|
|
1438
|
-
var optionValue = (value) => {
|
|
1439
|
-
if (value.includes("--") || /[\r\n]/.test(value)) return void 0;
|
|
1440
|
-
return /^[A-Za-z0-9_][A-Za-z0-9_.:+/@#-]*$/.test(value) && value !== "true" && value !== "false" ? value : JSON.stringify(value);
|
|
1441
|
-
};
|
|
1442
|
-
var oneLine = (value) => typeof value === "string" && value !== "" && !/[\r\n]/.test(value) && value === value.trim();
|
|
1443
|
-
function listLines(items, marker) {
|
|
1444
|
-
const lines = [];
|
|
1445
|
-
for (const raw of items) {
|
|
1446
|
-
const item = raw;
|
|
1447
|
-
const text = isRecord(item) && !Array.isArray(item) ? item.text : item;
|
|
1448
|
-
const level = isRecord(item) && !Array.isArray(item) && typeof item.level === "number" ? item.level : 0;
|
|
1449
|
-
const rows = serializeRuns(text);
|
|
1450
|
-
if (!rows || rows.length !== 1) return void 0;
|
|
1451
|
-
const indent = " ".repeat(level);
|
|
1452
|
-
lines.push(rows[0] === "" ? `${indent}${marker}` : `${indent}${marker} ${rows[0]}`);
|
|
1453
|
-
if (isRecord(item) && !Array.isArray(item) && item.description !== void 0) {
|
|
1454
|
-
const description = serializeRuns(item.description);
|
|
1455
|
-
if (!description || description.length !== 1 || description[0] === "") return void 0;
|
|
1456
|
-
lines.push(`${indent} : ${description[0]}`);
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
return lines;
|
|
1460
|
-
}
|
|
1461
|
-
function destination(url) {
|
|
1462
|
-
if (!url || /[\r\n<>]/.test(url)) return void 0;
|
|
1463
|
-
return /^[^\s()]+$/.test(url) ? url : `<${url}>`;
|
|
1464
|
-
}
|
|
1465
|
-
function imageLines(value) {
|
|
1466
|
-
const asset = typeof value === "string" ? { src: value } : value;
|
|
1467
|
-
const dest = destination(String(asset.src));
|
|
1468
|
-
if (dest === void 0) return void 0;
|
|
1469
|
-
const alt = asset.alt === void 0 ? "" : String(asset.alt);
|
|
1470
|
-
if (/[\r\n]/.test(alt)) return void 0;
|
|
1471
|
-
let title = "";
|
|
1472
|
-
if (asset.title !== void 0) {
|
|
1473
|
-
if (/[\r\n]/.test(String(asset.title))) return void 0;
|
|
1474
|
-
title = ` "${String(asset.title).replace(/[\\"]/g, "\\$&")}"`;
|
|
1475
|
-
}
|
|
1476
|
-
return [``];
|
|
1477
|
-
}
|
|
1478
|
-
function tableLines(table) {
|
|
1479
|
-
const rows = table.rows;
|
|
1480
|
-
const columns = table.columns;
|
|
1481
|
-
const width = columns ? columns.length : Math.max(0, ...rows.map((row) => row.length));
|
|
1482
|
-
if (!width) return void 0;
|
|
1483
|
-
const cell = (value) => {
|
|
1484
|
-
if (value === null) return "";
|
|
1485
|
-
if (typeof value === "number") return String(value);
|
|
1486
|
-
const rows2 = serializeRuns(value, "cell");
|
|
1487
|
-
return rows2?.[0]?.replaceAll("|", "\\|");
|
|
1488
|
-
};
|
|
1489
|
-
const render = (cells) => {
|
|
1490
|
-
const out = cells.map(cell);
|
|
1491
|
-
return out.includes(void 0) ? void 0 : `| ${out.join(" | ")} |`;
|
|
1492
|
-
};
|
|
1493
|
-
const header = render(columns ?? Array.from({ length: width }, () => null));
|
|
1494
|
-
const body = rows.map(render);
|
|
1495
|
-
if (header === void 0 || body.includes(void 0)) return void 0;
|
|
1496
|
-
return [header, `| ${Array.from({ length: width }, () => "---").join(" | ")} |`, ...body];
|
|
1497
|
-
}
|
|
1498
|
-
function chartJson(data) {
|
|
1499
|
-
const entries = Object.entries(data).map(
|
|
1500
|
-
([key, value]) => key === "rows" && Array.isArray(value) ? [' "rows": [', ...value.map((row, index) => ` ${JSON.stringify(row)}${index < value.length - 1 ? "," : ""}`), " ]"] : [` ${JSON.stringify(key)}: ${JSON.stringify(value)}`]
|
|
1501
|
-
);
|
|
1502
|
-
return ["{", ...entries.flatMap((lines, index) => lines.map((line, at) => at === lines.length - 1 && index < entries.length - 1 ? `${line},` : line)), "}"];
|
|
1503
|
-
}
|
|
1504
|
-
function chartCandidates(chart) {
|
|
1505
|
-
if (!/^\S+$/.test(chart.type)) return [];
|
|
1506
|
-
const data = chart.data;
|
|
1507
|
-
if (typeof chart.alt === "string" && chart.alt.includes("`")) return [];
|
|
1508
|
-
const info = `chart ${chart.type}${typeof chart.alt === "string" ? ` alt=${JSON.stringify(chart.alt)}` : ""}`;
|
|
1509
|
-
const json = fenced(info, chartJson(data));
|
|
1510
|
-
if (Array.isArray(data.columns) && Array.isArray(data.rows)) {
|
|
1511
|
-
const body = [data.columns.map((column) => csvField(column, true)).join(","), ...data.rows.map((row) => row.map((value, index) => csvField(value, index === 0)).join(","))];
|
|
1512
|
-
return [fenced(info, body), json];
|
|
1513
|
-
}
|
|
1514
|
-
return [json];
|
|
1515
|
-
}
|
|
1516
|
-
var TIMELINE_STATUS_MARKS = { done: "[x] ", current: "[>] ", planned: "[ ] " };
|
|
1517
|
-
function timelineLines(timeline) {
|
|
1518
|
-
const events = Array.isArray(timeline) ? timeline : timeline.events;
|
|
1519
|
-
const meta = Array.isArray(timeline) ? {} : timeline;
|
|
1520
|
-
const info = ["timeline", ...["name", "description"].filter((key) => meta[key] !== void 0).map((key) => `${key}=${JSON.stringify(meta[key])}`)].join(" ");
|
|
1521
|
-
const body = [];
|
|
1522
|
-
for (const event of events) {
|
|
1523
|
-
for (const key of ["when", "what", "description"]) if (typeof event[key] === "string" && (/[\r\n]/.test(event[key]) || event[key] !== event[key].trim() || event[key] === "")) return void 0;
|
|
1524
|
-
const line = event.when === void 0 ? event.what : `${event.when} \u2014 ${event.what}`;
|
|
1525
|
-
if (event.status === void 0 && /^\[[x> ]\]\s+\S/.test(line)) return void 0;
|
|
1526
|
-
body.push(event.status === void 0 ? line : `${TIMELINE_STATUS_MARKS[event.status]}${line}`);
|
|
1527
|
-
if (event.description !== void 0) body.push(` ${event.description}`);
|
|
1528
|
-
}
|
|
1529
|
-
return fenced(info, body);
|
|
1530
|
-
}
|
|
1531
|
-
function metricLines(metric) {
|
|
1532
|
-
const value = typeof metric === "object" ? metric : { value: metric };
|
|
1533
|
-
const body = [];
|
|
1534
|
-
for (const key of ["value", "label", "description", "unit", "delta", "trend", "sentiment"]) if (value[key] !== void 0) body.push(`${key}: ${scalarText(value[key])}`);
|
|
1535
|
-
return fenced("metric", body);
|
|
1536
|
-
}
|
|
1537
|
-
function splitParagraphs(runs) {
|
|
1538
|
-
const out = [[]];
|
|
1539
|
-
for (const run of runs) {
|
|
1540
|
-
runText(run).split("\n\n").forEach((part, index) => {
|
|
1541
|
-
if (index > 0) out.push([]);
|
|
1542
|
-
if (part !== "") out.at(-1).push(typeof run === "string" ? part : { ...run, text: part });
|
|
1543
|
-
});
|
|
1544
|
-
}
|
|
1545
|
-
return out;
|
|
1546
|
-
}
|
|
1547
|
-
function quoteLines(quote) {
|
|
1548
|
-
const value = typeof quote === "string" ? { text: quote } : quote;
|
|
1549
|
-
const lines = [];
|
|
1550
|
-
const paragraphs = Array.isArray(value.text) ? splitParagraphs(value.text) : String(value.text).split("\n\n");
|
|
1551
|
-
for (const [index, paragraph] of paragraphs.entries()) {
|
|
1552
|
-
const rows = serializeRuns(paragraph);
|
|
1553
|
-
if (!rows) return void 0;
|
|
1554
|
-
if (index > 0) lines.push(">");
|
|
1555
|
-
for (const row of rows) lines.push(row === "" ? ">" : `> ${row}`);
|
|
1556
|
-
}
|
|
1557
|
-
for (const key of ["attribution", "source"]) {
|
|
1558
|
-
if (value[key] === void 0) continue;
|
|
1559
|
-
if (!oneLine(value[key])) return void 0;
|
|
1560
|
-
lines.push(`> \u2014 ${escapeInline(value[key])}`);
|
|
1561
|
-
}
|
|
1562
|
-
return lines;
|
|
1563
|
-
}
|
|
1564
|
-
function nativeCandidates(key, canon, marker) {
|
|
1565
|
-
if (key === "chart") return chartCandidates(canon);
|
|
1566
|
-
const lines = nativeLines(key, canon, marker);
|
|
1567
|
-
return lines?.length ? [lines] : [];
|
|
1568
|
-
}
|
|
1569
|
-
function nativeLines(key, canon, marker) {
|
|
1570
|
-
switch (key) {
|
|
1571
|
-
case "text": {
|
|
1572
|
-
if (canon === "") return void 0;
|
|
1573
|
-
return serializeRuns(canon);
|
|
1574
|
-
}
|
|
1575
|
-
case "items":
|
|
1576
|
-
case "bullets":
|
|
1577
|
-
return listLines(canon, marker);
|
|
1578
|
-
case "quote":
|
|
1579
|
-
return quoteLines(canon);
|
|
1580
|
-
case "code": {
|
|
1581
|
-
const code = typeof canon === "string" ? { source: canon } : canon;
|
|
1582
|
-
const language = code.language;
|
|
1583
|
-
if (language !== void 0 && (!/^[A-Za-z0-9_+#.-]+$/.test(language) || ["chart", "metric", "timeline", "opf-slide", "opf-block"].includes(language))) return void 0;
|
|
1584
|
-
return fenceCode(code.source, language, code.filename);
|
|
1585
|
-
}
|
|
1586
|
-
case "metric":
|
|
1587
|
-
return metricLines(canon);
|
|
1588
|
-
case "image":
|
|
1589
|
-
case "video":
|
|
1590
|
-
return imageLines(canon);
|
|
1591
|
-
case "table":
|
|
1592
|
-
return tableLines(canon);
|
|
1593
|
-
case "timeline":
|
|
1594
|
-
return timelineLines(canon);
|
|
1595
|
-
default:
|
|
1596
|
-
return void 0;
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1599
|
-
function blockOptionsLine(spec, as) {
|
|
1600
|
-
const parts = [];
|
|
1601
|
-
for (const [key, value] of [["id", spec.id], ["type", spec.type], ["as", as], ["region", spec.region ?? spec.pin]]) {
|
|
1602
|
-
if (value === void 0) continue;
|
|
1603
|
-
const text = typeof value === "string" ? optionValue(value) : void 0;
|
|
1604
|
-
if (text === void 0) return void 0;
|
|
1605
|
-
parts.push(`${key}=${text}`);
|
|
1606
|
-
}
|
|
1607
|
-
return parts.length ? `<!-- block: ${parts.join(" ")} -->` : "";
|
|
1608
|
-
}
|
|
1609
|
-
function nativeBlock(spec, marker) {
|
|
1610
|
-
const canon = canonContent(spec.key, spec.value);
|
|
1611
|
-
if (canon === void 0) return void 0;
|
|
1612
|
-
const as = spec.key === "bullets" ? "bullets" : spec.key === "video" ? "video" : void 0;
|
|
1613
|
-
const logical = spec.key === "bullets" ? "items" : spec.key === "video" ? "image" : spec.key;
|
|
1614
|
-
const options = blockOptionsLine(spec, as);
|
|
1615
|
-
if (options === void 0) return void 0;
|
|
1616
|
-
for (const lines of nativeCandidates(logical, canon, marker)) {
|
|
1617
|
-
const all = options ? [options, ...lines] : lines;
|
|
1618
|
-
const { slide, clean } = parseSlideText(all.join("\n"));
|
|
1619
|
-
if (!slide || !clean) continue;
|
|
1620
|
-
const target = spec.region !== void 0 ? slide[spec.region] : spec.id !== void 0 || spec.type !== void 0 || spec.pin !== void 0 ? slide.blocks?.[0] : slide;
|
|
1621
|
-
if (target && same(target[spec.key], canon)) return all;
|
|
1622
|
-
}
|
|
1623
|
-
return void 0;
|
|
1624
|
-
}
|
|
1625
|
-
function embed(value) {
|
|
1626
|
-
const text = writeYaml(value);
|
|
1627
|
-
return text === void 0 ? void 0 : fenced("opf-block", text.trimEnd().split("\n"));
|
|
1628
|
-
}
|
|
1629
|
-
function emitSlide(slide, index, mode) {
|
|
1630
|
-
const path = `/slides/${index}`;
|
|
1631
|
-
const embedded = [];
|
|
1632
|
-
const loss = [];
|
|
1633
|
-
const extras = {};
|
|
1634
|
-
const sections = [];
|
|
1635
|
-
const exp = {};
|
|
1636
|
-
const expBlocks = [];
|
|
1637
|
-
const handle = (where, reason, keep) => {
|
|
1638
|
-
if (mode === "embed") {
|
|
1639
|
-
embedded.push({ path: where, reason });
|
|
1640
|
-
keep();
|
|
1641
|
-
} else loss.push({ path: where, message: reason });
|
|
1642
|
-
};
|
|
1643
|
-
const options = [];
|
|
1644
|
-
const optionFields = [["id", slide.id], ["type", slide.type], ["beat", slide.beat], ["layout", slide.layout], ["section", slide.section], ["tag", slide.tag]];
|
|
1645
|
-
const failed = [];
|
|
1646
|
-
for (const [key, value] of optionFields) {
|
|
1647
|
-
if (value === void 0) continue;
|
|
1648
|
-
if (key === "beat" && Array.isArray(value) && value.length >= 2 && value.every((entry) => typeof entry === "string")) {
|
|
1649
|
-
const texts = value.map(optionValue);
|
|
1650
|
-
if (texts.every((entry) => entry !== void 0)) options.push(...texts.map((entry) => ["beat", entry]));
|
|
1651
|
-
else failed.push(key);
|
|
1652
|
-
continue;
|
|
1653
|
-
}
|
|
1654
|
-
const text = typeof value === "string" ? optionValue(value) : void 0;
|
|
1655
|
-
if (text === void 0) failed.push(key);
|
|
1656
|
-
else options.push([key, text]);
|
|
1657
|
-
}
|
|
1658
|
-
if (slide.hidden === true) options.push(["hidden", "true"]);
|
|
1659
|
-
else if (slide.hidden === false) options.push(["hidden", "false"]);
|
|
1660
|
-
else if (slide.hidden !== void 0) failed.push("hidden");
|
|
1661
|
-
const optionsText = options.map(([key, text]) => key === "hidden" && text === "true" ? "hidden" : `${key}=${text}`).join(" ");
|
|
1662
|
-
const optionsLine = options.length ? `<!-- slide: ${optionsText} -->` : "";
|
|
1663
|
-
if (optionsLine) {
|
|
1664
|
-
const { slide: back, clean } = parseSlideText(optionsLine);
|
|
1665
|
-
const expected = {};
|
|
1666
|
-
for (const [key] of options) expected[key] = slide[key];
|
|
1667
|
-
if (!back || !clean || !same(back, expected)) {
|
|
1668
|
-
for (const [key] of options) failed.push(key);
|
|
1669
|
-
options.length = 0;
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
for (const key of new Set(failed)) handle(`${path}/${key}`, `slide option ${key} has no <!-- slide: --> form`, () => extras[key] = slide[key]);
|
|
1673
|
-
const kept = options.length ? `<!-- slide: ${options.map(([key, text]) => key === "hidden" && text === "true" ? "hidden" : `${key}=${text}`).join(" ")} -->` : "";
|
|
1674
|
-
if (kept) {
|
|
1675
|
-
sections.push({ lines: [kept] });
|
|
1676
|
-
for (const [key] of options) exp[key] = slide[key];
|
|
1677
|
-
}
|
|
1678
|
-
for (const [key, mark] of [["title", "#"], ["subtitle", "##"]]) {
|
|
1679
|
-
const value = slide[key];
|
|
1680
|
-
if (value === void 0) continue;
|
|
1681
|
-
const canon = canonText(value);
|
|
1682
|
-
const row = canon === void 0 ? void 0 : typeof canon === "string" ? oneLine(canon) ? escapeInline(canon) : void 0 : serializeRuns(canon)?.length === 1 ? serializeRuns(canon)[0] : void 0;
|
|
1683
|
-
if (canon !== void 0 && row) {
|
|
1684
|
-
const line = `${mark} ${row.replace(/(^|\s)(#+)$/, "$1\\$2")}`;
|
|
1685
|
-
const { slide: back, clean } = parseSlideText(line);
|
|
1686
|
-
if (back && clean && same(back[key], canon)) {
|
|
1687
|
-
sections.push({ lines: [line] });
|
|
1688
|
-
exp[key] = canon;
|
|
1689
|
-
continue;
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
handle(`${path}/${key}`, `the ${key} has no single-line Markdown form`, () => extras[key] = value);
|
|
1693
|
-
}
|
|
1694
|
-
const specs = [];
|
|
1695
|
-
const handledRegions = /* @__PURE__ */ new Set();
|
|
1696
|
-
if (Array.isArray(slide.blocks)) {
|
|
1697
|
-
slide.blocks.forEach((block, i) => {
|
|
1698
|
-
const where = `${path}/blocks/${i}`;
|
|
1699
|
-
if (!isRecord(block)) return;
|
|
1700
|
-
const pin = typeof block.region === "string" ? block.region : void 0;
|
|
1701
|
-
const keys = Object.keys(block).filter((key) => key !== "id" && key !== "type" && !(key === "region" && pin !== void 0));
|
|
1702
|
-
const only3 = keys.length === 1 && isContent(keys[0]) ? keys[0] : void 0;
|
|
1703
|
-
specs.push({ spec: { key: only3 ?? "", value: only3 ? block[only3] : void 0, id: block.id, type: block.type, ...pin !== void 0 ? { pin } : {} }, where, payload: block });
|
|
1704
|
-
});
|
|
1705
|
-
for (const key of CONTENT2) if (slide[key] !== void 0) extras[key] = slide[key];
|
|
1706
|
-
} else {
|
|
1707
|
-
for (const key of CONTENT2) if (slide[key] !== void 0) specs.push({ spec: { key, value: slide[key] }, where: `${path}/${key}` });
|
|
1708
|
-
}
|
|
1709
|
-
for (const [key, value] of Object.entries(slide)) {
|
|
1710
|
-
if (!REGION.test(key) || !isRecord(value)) continue;
|
|
1711
|
-
const rest = Object.keys(value).filter((entry) => entry !== "id" && entry !== "type");
|
|
1712
|
-
const only3 = rest.length === 1 && isContent(rest[0]) ? rest[0] : void 0;
|
|
1713
|
-
if (only3 === void 0) continue;
|
|
1714
|
-
handledRegions.add(key);
|
|
1715
|
-
specs.push({ spec: { key: only3, value: value[only3], id: value.id, type: value.type, region: key }, where: `${path}/${key}`, payload: value });
|
|
1716
|
-
}
|
|
1717
|
-
let marker = "-";
|
|
1718
|
-
for (const { spec, where, payload } of specs) {
|
|
1719
|
-
const lines2 = spec.key ? nativeBlock(spec, marker) : void 0;
|
|
1720
|
-
if (lines2) {
|
|
1721
|
-
sections.push({ lines: lines2 });
|
|
1722
|
-
const written = { ...spec.id !== void 0 ? { id: spec.id } : {}, ...spec.type !== void 0 ? { type: spec.type } : {}, ...spec.pin !== void 0 ? { region: spec.pin } : {}, [spec.key]: canonContent(spec.key, spec.value) };
|
|
1723
|
-
if (spec.region !== void 0) exp[spec.region] = written;
|
|
1724
|
-
else expBlocks.push(written);
|
|
1725
|
-
const list = spec.key === "items" || spec.key === "bullets";
|
|
1726
|
-
marker = list ? marker === "-" ? "*" : "-" : "-";
|
|
1727
|
-
continue;
|
|
1728
|
-
}
|
|
1729
|
-
marker = "-";
|
|
1730
|
-
handle(where, spec.key ? `${spec.key} content has a part with no Markdown form` : "a block with several fields or a nested group has no Markdown form", () => {
|
|
1731
|
-
if (spec.region !== void 0) {
|
|
1732
|
-
extras[spec.region] = slide[spec.region];
|
|
1733
|
-
return;
|
|
1734
|
-
}
|
|
1735
|
-
const payloadValue = payload ?? { [spec.key]: spec.value };
|
|
1736
|
-
const lines3 = embed(payloadValue);
|
|
1737
|
-
if (!lines3) throw new OPFMarkdownError("not-representable", `The block at ${where} holds a value that YAML cannot carry exactly.`, { path: where });
|
|
1738
|
-
sections.push({ lines: lines3 });
|
|
1739
|
-
if (spec.region !== void 0) exp[spec.region] = structuredClone(payloadValue);
|
|
1740
|
-
else expBlocks.push(structuredClone(payloadValue));
|
|
1741
|
-
});
|
|
1742
|
-
}
|
|
1743
|
-
let notesLines;
|
|
1744
|
-
if (slide.notes !== void 0) {
|
|
1745
|
-
const notes = slide.notes;
|
|
1746
|
-
if (typeof notes === "string" && notes !== "" && !/\r/.test(notes)) {
|
|
1747
|
-
const rows = notes.split("\n");
|
|
1748
|
-
const candidate = [`Note: ${rows[0]}`, ...rows.slice(1)];
|
|
1749
|
-
const { slide: back, clean } = parseSlideText(candidate.join("\n"));
|
|
1750
|
-
if (back && clean && back.notes === notes) {
|
|
1751
|
-
notesLines = candidate;
|
|
1752
|
-
exp.notes = notes;
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
if (!notesLines) handle(`${path}/notes`, "the speaker notes have no Note: form (blank edges, a --- line or a carriage return)", () => extras.notes = notes);
|
|
1756
|
-
}
|
|
1757
|
-
for (const [key, value] of Object.entries(slide)) if (!NATIVE_SLIDE.has(key) && !handledRegions.has(key)) handle(`${path}/${key}`, `${JSON.stringify(key)} has no Markdown form`, () => extras[key] = value);
|
|
1758
|
-
if (Object.keys(extras).length) {
|
|
1759
|
-
const text = writeYaml(extras);
|
|
1760
|
-
if (text === void 0) throw new OPFMarkdownError("not-representable", `The slide at ${path} holds a value that YAML cannot carry exactly.`, { path });
|
|
1761
|
-
sections.push({ lines: fenced("opf-slide", text.trimEnd().split("\n")) });
|
|
1762
|
-
Object.assign(exp, structuredClone(extras));
|
|
1763
|
-
}
|
|
1764
|
-
const [only2] = expBlocks;
|
|
1765
|
-
const onlyKey = only2 && Object.keys(only2).length === 1 ? Object.keys(only2)[0] : void 0;
|
|
1766
|
-
if (expBlocks.length === 1 && onlyKey !== void 0 && isContent(onlyKey)) exp[onlyKey] = only2[onlyKey];
|
|
1767
|
-
else if (expBlocks.length) exp.blocks = expBlocks;
|
|
1768
|
-
if (notesLines) sections.push({ lines: notesLines });
|
|
1769
|
-
if (!sections.length) sections.push({ lines: ["<!-- slide -->"] });
|
|
1770
|
-
const lines = sections.flatMap((section, i) => i === 0 || i === 1 && kept ? section.lines : ["", ...section.lines]);
|
|
1771
|
-
if (mode === "embed") {
|
|
1772
|
-
const { slide: back, clean } = parseSlideText(lines.join("\n"));
|
|
1773
|
-
if (!back || !clean || !same(back, exp)) throw new OPFMarkdownError("not-representable", `The slide at ${path} does not read back from its Markdown.`, { path, expected: exp, actual: back });
|
|
1774
|
-
}
|
|
1775
|
-
return { lines, embedded, loss };
|
|
1776
|
-
}
|
|
1777
|
-
|
|
1778
|
-
// src/markdown.ts
|
|
1779
|
-
function fromMarkdown(markdown, options = {}) {
|
|
1780
|
-
if (typeof markdown !== "string") throw new TypeError("fromMarkdown expects a string.");
|
|
1781
|
-
checkCatalogsOption(options.catalogs, "fromMarkdown");
|
|
1782
|
-
const source = markdown;
|
|
1223
|
+
// src/markdown/read.ts
|
|
1224
|
+
function readMarkdown(source, options = {}) {
|
|
1783
1225
|
const ctx = new Ctx(source);
|
|
1784
1226
|
let lines = splitLines(source);
|
|
1785
1227
|
if (lines[0]?.text.startsWith("\uFEFF")) lines = [{ ...lines[0], text: lines[0].text.slice(1), start: lines[0].start + 1 }, ...lines.slice(1)];
|
|
1786
1228
|
const front = {};
|
|
1787
|
-
let report = { schemaValid: null, checks: NOT_CHECKED };
|
|
1788
1229
|
let body = lines;
|
|
1230
|
+
let frontRange;
|
|
1789
1231
|
const matter = frontMatter(lines);
|
|
1790
1232
|
if (matter === "unterminated") {
|
|
1791
|
-
ctx.error("front-matter-unterminated", "The front matter starting on line 1 is not closed.", "Close it with a line containing only ---, or remove the opening --- (a deck without front matter starts with its first slide).", lineRange(lines[0]));
|
|
1233
|
+
ctx.error("front-matter-unterminated", "The front matter starting on line 1 is not closed.", "Close it with a line containing only ---, or remove the opening --- (a deck without front matter starts with its first slide).", lineRange(lines[0]), "");
|
|
1792
1234
|
body = lines.slice(1);
|
|
1793
1235
|
} else if (matter) {
|
|
1794
1236
|
const range = { start: matter.yaml.start, end: matter.yaml.end };
|
|
1237
|
+
frontRange = range;
|
|
1795
1238
|
const parsed = readYamlMapping(source.slice(range.start, range.end), range.start, ctx, "front-matter", range);
|
|
1796
1239
|
ctx.ranges.set("", range);
|
|
1797
1240
|
if (parsed.ok && !Object.keys(parsed.value).length && source.slice(range.start, range.end).trim()) ctx.warn("front-matter-comments", "The text between the first two --- lines is only YAML comments, so it was not read as slide content.", "A deck that starts with a slide must not start with a --- line: only front matter may open the file. Remove the first --- or add the deck properties.", range);
|
|
@@ -1806,6 +1249,7 @@ function fromMarkdown(markdown, options = {}) {
|
|
|
1806
1249
|
const split = options.split ?? "auto";
|
|
1807
1250
|
const segments = splitSegments(body, split === "headings" || split === "auto" && isOutline(body) ? "headings" : "rules");
|
|
1808
1251
|
const slides = [];
|
|
1252
|
+
const kept = [];
|
|
1809
1253
|
segments.forEach((segment, index) => {
|
|
1810
1254
|
if (emptySegment(segment)) {
|
|
1811
1255
|
const edge = index === 0 || index === segments.length - 1;
|
|
@@ -1815,63 +1259,11 @@ function fromMarkdown(markdown, options = {}) {
|
|
|
1815
1259
|
}
|
|
1816
1260
|
return;
|
|
1817
1261
|
}
|
|
1262
|
+
kept.push(segment);
|
|
1818
1263
|
slides.push(parseSlide(ctx, segment, slides.length));
|
|
1819
1264
|
});
|
|
1820
1265
|
document.slides = slides;
|
|
1821
|
-
|
|
1822
|
-
else if (options.validate !== false) {
|
|
1823
|
-
const checked = validate(document, options.validate === true || options.validate === void 0 ? { only: ["format", "references"], ...options.catalogs ? { catalogs: options.catalogs } : {} } : options.validate);
|
|
1824
|
-
report = {
|
|
1825
|
-
schemaValid: checked.schemaValid,
|
|
1826
|
-
checks: { ...checked.checks, syntax: "checked" },
|
|
1827
|
-
...checked.template === void 0 ? {} : { template: checked.template },
|
|
1828
|
-
...checked.unfilledVariables === void 0 ? {} : { unfilledVariables: checked.unfilledVariables }
|
|
1829
|
-
};
|
|
1830
|
-
for (const found of checked.findings) {
|
|
1831
|
-
const range = ctx.rangeOf(found.path) ?? ctx.rangeOf("") ?? { start: 0, end: 0 };
|
|
1832
|
-
ctx.findings.push({ ...found, location: ctx.location(range) });
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
const findings = [...ctx.findings].sort((a, b) => a.location.offset - b.location.offset);
|
|
1836
|
-
const counts = { error: 0, warning: 0, info: 0 };
|
|
1837
|
-
for (const entry of findings) counts[entry.severity]++;
|
|
1838
|
-
return { presentation: document, valid: counts.error === 0, findings, counts, ...report };
|
|
1839
|
-
}
|
|
1840
|
-
function toMarkdown(document, options = {}) {
|
|
1841
|
-
const checked = validate(document, { only: ["format"] });
|
|
1842
|
-
if (!checked.valid) {
|
|
1843
|
-
const errors = checked.findings.filter((entry) => entry.severity === "error");
|
|
1844
|
-
const first = errors[0];
|
|
1845
|
-
throw new OPFMarkdownError("invalid-document", `The document is not valid OPF: ${first?.message ?? "unknown error"}${first?.path ? ` (${first.path})` : ""}.`, { issues: errors });
|
|
1846
|
-
}
|
|
1847
|
-
const mode = options.unsupported === "drop" ? "drop" : "embed";
|
|
1848
|
-
const { slides, ...rest } = document;
|
|
1849
|
-
const parts = [];
|
|
1850
|
-
if (Object.keys(rest).length) {
|
|
1851
|
-
const yaml = writeYaml(rest);
|
|
1852
|
-
if (yaml === void 0) throw new OPFMarkdownError("not-representable", "The deck properties hold a value that YAML cannot carry exactly.");
|
|
1853
|
-
if (!same(readBack(yaml), rest)) throw new OPFMarkdownError("not-representable", "The deck properties do not read back from their front matter.");
|
|
1854
|
-
parts.push(`---
|
|
1855
|
-
${yaml}---
|
|
1856
|
-
`);
|
|
1857
|
-
}
|
|
1858
|
-
const embedded = [];
|
|
1859
|
-
const loss = [];
|
|
1860
|
-
const bodies = [];
|
|
1861
|
-
slides.forEach((slide, index) => {
|
|
1862
|
-
const out = emitSlide(slide, index, mode);
|
|
1863
|
-
embedded.push(...out.embedded);
|
|
1864
|
-
loss.push(...out.loss.map((part) => `${part.path}: ${part.message}`));
|
|
1865
|
-
bodies.push(out.lines.join("\n"));
|
|
1866
|
-
});
|
|
1867
|
-
const markdown = `${parts.length ? `${parts[0]}
|
|
1868
|
-
` : ""}${bodies.join("\n\n---\n\n")}
|
|
1869
|
-
`;
|
|
1870
|
-
return { markdown, report: { lossless: loss.length === 0, native: loss.length === 0 && embedded.length === 0, embedded, loss } };
|
|
1871
|
-
}
|
|
1872
|
-
function readBack(yaml) {
|
|
1873
|
-
const ctx = new Ctx(yaml);
|
|
1874
|
-
return readYamlMapping(yaml, 0, ctx, "front-matter", { start: 0, end: yaml.length }).value;
|
|
1266
|
+
return { ctx, document, ...frontRange ? { front: frontRange } : {}, segments: kept };
|
|
1875
1267
|
}
|
|
1876
1268
|
|
|
1877
|
-
export {
|
|
1269
|
+
export { BLOCK_AS_VALUES, BLOCK_OPTION_KEYS, COMMENT, FENCE_ATTRIBUTES, FENCE_KINDS, LINE_KEYWORDS, METRIC_KEYS, NOTES, SLIDE_OPTION_KEYS, SPAN_FLAGS, SPAN_VALUES, escapeInline, fenceClose, fenceStart, frontMatter, isOutline, parseSlideText, readMarkdown, serializeRuns, splitSegments };
|