@openpresentation/opf 0.26.3 → 0.26.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +3 -1
  2. package/dist/browser.js +14 -11
  3. package/dist/{chunk-NJWI6MPT.js → chunk-CIWC7DRZ.js} +1985 -366
  4. package/dist/{chunk-7RY7QWWJ.js → chunk-DXTPLVAK.js} +1 -1
  5. package/dist/{chunk-54OV2WQC.js → chunk-EFMR32A5.js} +1 -1
  6. package/dist/{chunk-PXOXTJTN.js → chunk-HPEZCAYC.js} +1 -1
  7. package/dist/{chunk-2ZQBMZTZ.js → chunk-K5Q3LLYK.js} +2 -2
  8. package/dist/chunk-KPALXWX3.js +712 -0
  9. package/dist/{chunk-34LIUOT7.js → chunk-L7YEVQHP.js} +6 -6
  10. package/dist/chunk-MD3CJ5YJ.js +452 -0
  11. package/dist/{chunk-WASENX2R.js → chunk-MLH5LIGF.js} +29 -647
  12. package/dist/chunk-RYZPBJLB.js +4 -0
  13. package/dist/{chunk-FXNUM2PN.js → chunk-STIYINEL.js} +4 -4
  14. package/dist/{chunk-YVNFACTC.js → chunk-TAV5S2JG.js} +1 -1
  15. package/dist/{chunk-GLLR3AQ5.js → chunk-UFEWQDQI.js} +1 -1
  16. package/dist/chunk-UY6SQVR2.js +639 -0
  17. package/dist/{chunk-PB5X2VSV.js → chunk-WAZDP2WS.js} +12 -20
  18. package/dist/{conversion-BjsKgxBZ.d.ts → conversion-C7o2EpDb.d.ts} +2 -2
  19. package/dist/conversion-PMJWLHDV.js +21 -0
  20. package/dist/convert.js +5 -5
  21. package/dist/deck.js +9 -7
  22. package/dist/diff.js +2 -2
  23. package/dist/docs.js +3 -3
  24. package/dist/explain-JSuESV-Y.d.ts +71 -0
  25. package/dist/index.d.ts +3 -2
  26. package/dist/index.js +18 -15
  27. package/dist/markdown.js +7 -5
  28. package/dist/node-engine.d.ts +2 -2
  29. package/dist/node-engine.js +18 -15
  30. package/dist/pagination.js +2 -2
  31. package/dist/patch.js +1 -1
  32. package/dist/schema.js +1 -1
  33. package/dist/source.d.ts +184 -5
  34. package/dist/source.js +391 -11
  35. package/dist/validator.js +1 -1
  36. package/dist/yaml.js +5 -4
  37. package/package.json +5 -5
  38. package/dist/chunk-DEI2M2H6.js +0 -194
  39. package/dist/conversion-F4UCOGR7.js +0 -18
@@ -0,0 +1,712 @@
1
+ import { isRecord, same } from './chunk-HPEZCAYC.js';
2
+ import { createLocator } from './chunk-CIWC7DRZ.js';
3
+ import { parseDocument, isMap, isSeq, isScalar, YAMLMap, Scalar, stringify, visit } from 'yaml';
4
+
5
+ var sentence = (text) => text.charAt(0).toUpperCase() + text.slice(1);
6
+ var code = (text) => `\`${text}\``;
7
+ function lineAt(text, offset) {
8
+ const at = Math.max(0, Math.min(offset, text.length));
9
+ let start = at;
10
+ while (start > 0 && text[start - 1] !== "\n" && text[start - 1] !== "\r") start--;
11
+ let end = at;
12
+ while (end < text.length && text[end] !== "\n" && text[end] !== "\r") end++;
13
+ return { start, end };
14
+ }
15
+ var lineNumber = (text, offset) => createLocator(text)(offset, 0).line;
16
+ function contentEnd(text, from, end) {
17
+ let quote = "";
18
+ let stop = end;
19
+ for (let index = from; index < end; index++) {
20
+ const char = text[index];
21
+ if (quote) {
22
+ if (char === "\\" && quote === '"') index++;
23
+ else if (char === quote) quote = "";
24
+ continue;
25
+ }
26
+ if ((char === '"' || char === "'") && (index === from || /[\s:,[{-]/.test(text[index - 1]))) quote = char;
27
+ else if (char === "#" && (index === from || /\s/.test(text[index - 1]))) {
28
+ stop = index;
29
+ break;
30
+ }
31
+ }
32
+ while (stop > from && /\s/.test(text[stop - 1])) stop--;
33
+ return stop;
34
+ }
35
+ function scan(text) {
36
+ const openers = [];
37
+ let quote;
38
+ let block;
39
+ let atLineStart = true;
40
+ let index = 0;
41
+ const startsValue = (at) => {
42
+ let back = at - 1;
43
+ while (back >= 0 && (text[back] === " " || text[back] === " ")) back--;
44
+ if (back < 0) return true;
45
+ const previous = text[back];
46
+ if (previous === "\n" || previous === "\r" || previous === "," || previous === "[" || previous === "{") return true;
47
+ if (previous === "-") return back === 0 || /\s/.test(text[back - 1]) || text[back - 1] === "\n";
48
+ if (previous === ":") return at > back + 1 || at === back + 1;
49
+ return false;
50
+ };
51
+ while (index < text.length) {
52
+ const char = text[index];
53
+ if (quote) {
54
+ if (quote.char === '"' && char === "\\") index += 2;
55
+ else if (quote.char === "'" && char === "'" && text[index + 1] === "'") index += 2;
56
+ else {
57
+ if (char === quote.char) quote = void 0;
58
+ index++;
59
+ }
60
+ continue;
61
+ }
62
+ if (char === "\n" || char === "\r") {
63
+ index++;
64
+ atLineStart = true;
65
+ continue;
66
+ }
67
+ if (atLineStart) {
68
+ atLineStart = false;
69
+ const line = lineAt(text, index);
70
+ const indent = /^[ ]*/.exec(text.slice(line.start, line.end))?.[0].length ?? 0;
71
+ const blank = text.slice(line.start, line.end).trim() === "";
72
+ if (block !== void 0) {
73
+ if (blank || indent > block) {
74
+ index = line.end;
75
+ continue;
76
+ }
77
+ block = void 0;
78
+ }
79
+ const body = contentEnd(text, line.start, line.end);
80
+ if (/(^|[:-]\s+|^\s*)[|>][+-]?\d?[+-]?$/.test(text.slice(line.start, body))) block = indent;
81
+ }
82
+ if (char === "#" && (index === 0 || /\s/.test(text[index - 1]))) {
83
+ index = lineAt(text, index).end;
84
+ continue;
85
+ }
86
+ if ((char === '"' || char === "'") && startsValue(index)) {
87
+ quote = { offset: index, char };
88
+ index++;
89
+ continue;
90
+ }
91
+ if ((char === "[" || char === "{") && startsValue(index)) openers.push({ offset: index, char });
92
+ else if ((char === "]" || char === "}") && openers.length) {
93
+ const last = openers[openers.length - 1];
94
+ if (last.char === "[" && char === "]" || last.char === "{" && char === "}") openers.pop();
95
+ }
96
+ index++;
97
+ }
98
+ return { ...quote ? { openQuote: quote } : {}, openers };
99
+ }
100
+ var indentOf = (text, offset) => {
101
+ const line = lineAt(text, offset);
102
+ return /^[ \t]*/.exec(text.slice(line.start, line.end))?.[0].length ?? 0;
103
+ };
104
+ function previousContentLine(text, offset) {
105
+ let line = lineAt(text, offset);
106
+ while (line.start > 0) {
107
+ line = lineAt(text, line.start - 1);
108
+ const body = text.slice(line.start, line.end).trim();
109
+ if (body && !body.startsWith("#")) return line;
110
+ }
111
+ return void 0;
112
+ }
113
+ function keyColumn(text, line) {
114
+ const raw = text.slice(line.start, line.end);
115
+ const match = /^([ \t]*(?:-[ \t]+)*)/.exec(raw);
116
+ return match?.[0].length ?? 0;
117
+ }
118
+ var fix = (id, title, edits, extra = {}) => ({ id, title, safe: false, edits, ...extra });
119
+ function readTab(text, error) {
120
+ const line = lineAt(text, error.pos[0]);
121
+ let end = line.start;
122
+ while (end < line.end && (text[end] === " " || text[end] === " ")) end++;
123
+ const number = lineNumber(text, line.start);
124
+ const tabLines = [];
125
+ let cursor = 0;
126
+ while (cursor <= text.length) {
127
+ const current = lineAt(text, cursor);
128
+ let stop = current.start;
129
+ while (stop < current.end && (text[stop] === " " || text[stop] === " ")) stop++;
130
+ if (text.slice(current.start, stop).includes(" ") && text.slice(stop, current.end).trim() !== "") tabLines.push({ start: current.start, end: stop });
131
+ if (current.end >= text.length) break;
132
+ cursor = current.end + 1;
133
+ if (text[current.end] === "\r" && text[current.end + 1] === "\n") cursor++;
134
+ }
135
+ const edit = (range) => ({ offset: range.start, length: range.end - range.start, insert: text.slice(range.start, range.end).replaceAll(" ", " ") });
136
+ const fixes = [];
137
+ const here = { start: line.start, end };
138
+ if (tabLines.length > 1) {
139
+ fixes.push(fix("tabs-to-spaces-line", "Convert tabs to spaces on this line", [edit(here)], { safe: true }));
140
+ fixes.push(fix("tabs-to-spaces", "Convert tabs to spaces in the file", tabLines.map(edit), { safe: true }));
141
+ } else fixes.push(fix("tabs-to-spaces", "Convert tabs to spaces", [edit(here)], { safe: true }));
142
+ return { code: "tab-indent", start: line.start, end, message: `Line ${number} is indented with a tab. YAML needs spaces.`, next: "replace the tab with spaces (two for each level of indentation).", fixes };
143
+ }
144
+ function readQuote(text, error, scanned) {
145
+ const char = /'/.test(error.message.slice(0, 20)) ? "'" : '"';
146
+ const open = scanned.openQuote && scanned.openQuote.char === char ? scanned.openQuote : scanned.openQuote;
147
+ if (!open) return void 0;
148
+ const line = lineAt(text, open.offset);
149
+ const number = lineNumber(text, open.offset);
150
+ const insertAt = line.end;
151
+ let at = insertAt;
152
+ while (at > open.offset && /\s/.test(text[at - 1])) at--;
153
+ return {
154
+ code: "unclosed-quote",
155
+ start: open.offset,
156
+ end: Math.max(open.offset + 1, line.end),
157
+ message: `The ${open.char === '"' ? "double " : "single "}quote opened on line ${number} is never closed.`,
158
+ next: `end the text with a matching ${open.char} (a quoted value can continue on the next lines, but it must close).`,
159
+ fixes: [fix("close-quote", "Insert closing quote", [{ offset: at, length: 0, insert: open.char }], { caret: at + 1 })]
160
+ };
161
+ }
162
+ function readFlow(text, error, scanned) {
163
+ const wantsList = /Flow sequence/.test(error.message);
164
+ const opener = [...scanned.openers].reverse().find((entry) => entry.char === (wantsList ? "[" : "{")) ?? scanned.openers[scanned.openers.length - 1];
165
+ if (!opener) return void 0;
166
+ const closer = opener.char === "[" ? "]" : "}";
167
+ const line = lineAt(text, opener.offset);
168
+ const number = lineNumber(text, opener.offset);
169
+ let last = previousContentLine(text, error.pos[0]) ?? line;
170
+ if (last.start < line.start) last = line;
171
+ const at = contentEnd(text, last.start, last.end);
172
+ return {
173
+ code: opener.char === "[" ? "unclosed-list" : "unclosed-map",
174
+ start: opener.offset,
175
+ end: Math.max(opener.offset + 1, contentEnd(text, line.start, line.end)),
176
+ message: `The ${opener.char === "[" ? "list" : "map"} that starts with ${code(opener.char)} on line ${number} is never closed.`,
177
+ next: `add ${code(closer)} after the last ${opener.char === "[" ? "item" : "entry"}.`,
178
+ fixes: [fix("insert-closer", `Insert ${closer}`, [{ offset: at, length: 0, insert: closer }], { caret: at + 1 })]
179
+ };
180
+ }
181
+ function readImplicit(text, error) {
182
+ const line = lineAt(text, error.pos[0]);
183
+ const body = text.slice(error.pos[0], line.end);
184
+ const end = error.pos[0] + contentEnd(body, 0, body.length);
185
+ const key = text.slice(error.pos[0], end).trim();
186
+ if (key.startsWith("- ") || key === "-") {
187
+ return {
188
+ code: "list-item-under-value",
189
+ start: error.pos[0],
190
+ end: Math.max(end, error.pos[0] + 1),
191
+ message: "This list item sits under a value that isn't a list.",
192
+ next: "put the item under a key such as `items:`, or indent it under the key above."
193
+ };
194
+ }
195
+ return {
196
+ code: "incomplete-key",
197
+ start: error.pos[0],
198
+ end: Math.max(end, error.pos[0] + 1),
199
+ message: `${code(key.length > 40 ? `${key.slice(0, 39)}\u2026` : key)} isn't a complete ${code("key: value")} line.`,
200
+ next: "add a colon and a value, or remove the line.",
201
+ fixes: [
202
+ fix("add-colon", "Add a colon", [{ offset: end, length: 0, insert: ": " }], { caret: end + 2 }),
203
+ fix("remove-line", "Remove the line", [{ offset: line.start, length: Math.min(text.length, nextLineStart(text, line.end)) - line.start, insert: "" }])
204
+ ]
205
+ };
206
+ }
207
+ function nextLineStart(text, end) {
208
+ if (text[end] === "\r" && text[end + 1] === "\n") return end + 2;
209
+ return end < text.length ? end + 1 : end;
210
+ }
211
+ function readColonInValue(text, error) {
212
+ const line = lineAt(text, error.pos[0]);
213
+ const end = contentEnd(text, error.pos[0], line.end);
214
+ const value = text.slice(error.pos[0], end);
215
+ return {
216
+ code: "colon-in-value",
217
+ start: error.pos[0],
218
+ end: Math.max(end, error.pos[0] + 1),
219
+ message: `A value can't contain ${code(": ")} unless it is quoted.`,
220
+ next: "wrap the value in quotes.",
221
+ fixes: [fix("quote-value", "Quote the value", [{ offset: error.pos[0], length: end - error.pos[0], insert: JSON.stringify(value) }])]
222
+ };
223
+ }
224
+ function readAlignment(text, error, missingDash) {
225
+ const line = lineAt(text, error.pos[0]);
226
+ const number = lineNumber(text, line.start);
227
+ const indent = indentOf(text, line.start);
228
+ const previous = previousContentLine(text, line.start);
229
+ const fixes = [];
230
+ const reindent = (id, title, column) => {
231
+ if (column === indent || column < 0) return;
232
+ fixes.push(fix(id, title, [{ offset: line.start, length: indent, insert: " ".repeat(column) }]));
233
+ };
234
+ if (previous) {
235
+ reindent("align-with-line-above", "Align with the line above", keyColumn(text, previous));
236
+ let parent = previousContentLine(text, previous.start);
237
+ while (parent && keyColumn(text, parent) >= keyColumn(text, previous)) parent = previousContentLine(text, parent.start);
238
+ if (parent && !missingDash) reindent("align-with-parent", "Align with the parent key", keyColumn(text, parent));
239
+ }
240
+ const end = contentEnd(text, line.start, line.end);
241
+ return {
242
+ code: missingDash ? "missing-dash" : "misaligned",
243
+ start: line.start,
244
+ end: Math.max(end, line.start + 1),
245
+ message: missingDash ? `Line ${number} does not line up with the list item above.` : `Line ${number} is indented differently from the keys above it.`,
246
+ next: missingDash ? "align it with the item's first key, or start a new item with `- `." : "align it with the other keys of this mapping.",
247
+ fixes: fixes.slice(0, 2)
248
+ };
249
+ }
250
+ function readTrailing(text, error) {
251
+ const line = lineAt(text, error.pos[0]);
252
+ const end = Math.max(error.pos[0] + 1, contentEnd(text, error.pos[0], line.end));
253
+ const snippet = text.slice(error.pos[0], end);
254
+ let from = error.pos[0];
255
+ while (from > line.start && /[ \t]/.test(text[from - 1])) from--;
256
+ return {
257
+ code: "trailing-text",
258
+ start: error.pos[0],
259
+ end,
260
+ message: `Unexpected text ${code(snippet.length > 30 ? `${snippet.slice(0, 29)}\u2026` : snippet)} after the value ends.`,
261
+ next: "remove it, or put the whole value in quotes if it belongs to the value.",
262
+ fixes: [fix("remove-text", "Remove the text", [{ offset: from, length: end - from, insert: "" }])]
263
+ };
264
+ }
265
+ function readMissingColon(text, error) {
266
+ const line = lineAt(text, error.pos[0]);
267
+ const end = contentEnd(text, line.start, line.end);
268
+ const key = text.slice(line.start, end).trim();
269
+ return {
270
+ code: "missing-colon",
271
+ start: Math.min(line.start + (text.slice(line.start, line.end).length - text.slice(line.start, line.end).trimStart().length), end),
272
+ end: Math.max(end, line.start + 1),
273
+ message: `${code(key.length > 40 ? `${key.slice(0, 39)}\u2026` : key)} needs a colon and a value.`,
274
+ next: `write ${code(`${key}: `)} and a value.`,
275
+ fixes: [fix("add-colon", "Add a colon", [{ offset: end, length: 0, insert: ": " }], { caret: end + 2 })]
276
+ };
277
+ }
278
+ function readEscape(text, error) {
279
+ const written = text.slice(error.pos[0], error.pos[0] + 2);
280
+ return {
281
+ code: "bad-escape",
282
+ start: error.pos[0],
283
+ end: Math.min(text.length, error.pos[0] + 2),
284
+ message: `${code(written)} is not an escape YAML knows.`,
285
+ next: 'write a backslash as `\\\\` (the escapes are `\\n`, `\\t`, `\\"`, `\\\\`, `\\/`, `\\xNN` and `\\uNNNN`), or use single quotes, where a backslash is just a backslash.',
286
+ fixes: [fix("double-backslash", "Double the backslash", [{ offset: error.pos[0], length: 0, insert: "\\" }])]
287
+ };
288
+ }
289
+ function readReservedStart(text, error) {
290
+ const line = lineAt(text, error.pos[0]);
291
+ const end = contentEnd(text, error.pos[0], line.end);
292
+ const value = text.slice(error.pos[0], end);
293
+ return {
294
+ code: "reserved-start",
295
+ start: error.pos[0],
296
+ end: Math.max(end, error.pos[0] + 1),
297
+ message: `A plain value can't start with ${code(text[error.pos[0]] ?? "")}.`,
298
+ next: "put the value in quotes.",
299
+ fixes: [fix("quote-value", "Quote the value", [{ offset: error.pos[0], length: end - error.pos[0], insert: JSON.stringify(value) }])]
300
+ };
301
+ }
302
+ function firstEqualKey(doc, start) {
303
+ let found;
304
+ visit(doc, {
305
+ Map(_key, map) {
306
+ const items = map.items;
307
+ const at = items.findIndex((item) => isScalar(item.key) && item.key.range?.[0] === start);
308
+ if (at < 0) return void 0;
309
+ const repeat = items[at]?.key;
310
+ const earlier = items.slice(0, at).find((item) => isScalar(item.key) && String(item.key.value) === String(repeat.value));
311
+ if (earlier) found = { key: earlier.key, pair: earlier };
312
+ return visit.BREAK;
313
+ }
314
+ });
315
+ return found;
316
+ }
317
+ function removeEntryEdit(text, key, value) {
318
+ const start = lineAt(text, key.range[0]).start;
319
+ if (text.slice(start, key.range[0]).trim() !== "") return void 0;
320
+ const finish = Math.max(key.range[1], value?.range ? Math.max(value.range[0], value.range[1] - 1) : key.range[1]);
321
+ const lineEnd = lineAt(text, finish).end;
322
+ return { offset: start, length: nextLineStart(text, lineEnd) - start, insert: "" };
323
+ }
324
+ function readDuplicate(text, error, doc) {
325
+ const start = error.pos[0];
326
+ const lineOfRepeat = lineNumber(text, start);
327
+ const earlier = firstEqualKey(doc, start);
328
+ let repeatPair;
329
+ visit(doc, {
330
+ Pair(_key, pair) {
331
+ const key = pair.key;
332
+ if (key?.range && key.range[0] === start) {
333
+ repeatPair = { key, value: pair.value };
334
+ return visit.BREAK;
335
+ }
336
+ return void 0;
337
+ }
338
+ });
339
+ const repeatKey = repeatPair?.key;
340
+ const name = repeatKey ? text.slice(repeatKey.range[0], repeatKey.range[1]).trim() : String(earlier?.key?.value ?? "key");
341
+ const firstLine = earlier ? lineNumber(text, earlier.key.range[0]) : void 0;
342
+ const fixes = [];
343
+ if (earlier) {
344
+ const first = removeEntryEdit(text, earlier.key, earlier.pair.value);
345
+ const second = repeatPair ? removeEntryEdit(text, repeatPair.key, repeatPair.value) : void 0;
346
+ if (first) fixes.push(fix("remove-first", "Remove the first", [first]));
347
+ if (second) fixes.push(fix("remove-second", "Remove the second", [second]));
348
+ }
349
+ return {
350
+ rule: "duplicate-key",
351
+ code: "duplicate-key",
352
+ start,
353
+ end: Math.max(start + 1, repeatKey ? repeatKey.range[1] : error.pos[1]),
354
+ message: `${code(name)} appears twice at this level${firstLine ? ` (lines ${firstLine} and ${lineOfRepeat})` : ""}. The first value is ignored.`,
355
+ next: "keep one of them, or rename the one you meant as another key.",
356
+ fixes,
357
+ ...earlier ? { related: [{ start: earlier.key.range[0], end: Math.max(earlier.key.range[0] + 1, earlier.key.range[1]) }] } : {}
358
+ };
359
+ }
360
+ function readMultipleDocuments(text, error) {
361
+ const start = error.pos[0];
362
+ const line = lineAt(text, start);
363
+ const marker = /^---\s*$/.test(text.slice(line.start, line.end).trimEnd()) ? line : void 0;
364
+ return {
365
+ rule: "multiple-documents",
366
+ code: "multiple-documents",
367
+ start,
368
+ end: Math.max(error.pos[1], start + 1),
369
+ message: "The input holds more than one YAML document.",
370
+ next: "remove the extra `---` separators, or put each deck in its own file.",
371
+ help: "An OPF file is exactly one document: remove the extra --- separators, or put each deck in its own file.",
372
+ fixes: marker ? [fix("remove-separator", "Remove the extra `---`", [{ offset: marker.start, length: nextLineStart(text, marker.end) - marker.start, insert: "" }])] : []
373
+ };
374
+ }
375
+ function readOther(error) {
376
+ const text = error.message.split("\n")[0] ?? error.message;
377
+ return { code: "other", start: error.pos[0], end: Math.max(error.pos[0], error.pos[1]), message: `YAML: ${text}`, next: "fix the YAML at this position. An OPF file holds JSON-compatible YAML 1.2: mappings, lists and plain values." };
378
+ }
379
+ function readError(text, error, doc, scanned) {
380
+ const message = error.message;
381
+ switch (error.code) {
382
+ case "TAB_AS_INDENT":
383
+ return readTab(text, error);
384
+ case "DUPLICATE_KEY":
385
+ return readDuplicate(text, error, doc);
386
+ case "MULTIPLE_DOCS":
387
+ return readMultipleDocuments(text, error);
388
+ case "BAD_DQ_ESCAPE":
389
+ return readEscape(text, error);
390
+ case "BAD_SCALAR_START":
391
+ return readReservedStart(text, error);
392
+ case "KEY_OVER_1024_CHARS":
393
+ return { code: "long-key", start: error.pos[0], end: Math.max(error.pos[0] + 1, Math.min(text.length, error.pos[0] + 60)), message: "This key is longer than 1024 characters.", next: "shorten the key, or check for a missing colon before the end of the line." };
394
+ case "MULTILINE_IMPLICIT_KEY":
395
+ return readImplicit(text, error);
396
+ case "BLOCK_AS_IMPLICIT_KEY":
397
+ return readColonInValue(text, error);
398
+ case "UNEXPECTED_TOKEN":
399
+ return readTrailing(text, error);
400
+ case "MISSING_CHAR":
401
+ if (/Missing closing/.test(message)) return readQuote(text, error, scanned) ?? readOther(error);
402
+ if (/Sequence item without - indicator/.test(message)) return readAlignment(text, error, true);
403
+ if (/Implicit map keys need to be followed/.test(message)) return readMissingColon(text, error);
404
+ return readOther(error);
405
+ case "BAD_INDENT":
406
+ if (/Flow (sequence|map)/.test(message)) return readFlow(text, error, scanned) ?? readOther(error);
407
+ if (/same column/.test(message)) return readAlignment(text, error, false);
408
+ return readOther(error);
409
+ default:
410
+ return readOther(error);
411
+ }
412
+ }
413
+ var CASCADE = /* @__PURE__ */ new Set(["UNEXPECTED_TOKEN", "MISSING_CHAR", "MULTILINE_IMPLICIT_KEY", "BAD_INDENT", "BLOCK_AS_IMPLICIT_KEY"]);
414
+ function describeYamlErrors(text, doc, errors, limit = 5) {
415
+ if (!errors.length) return [];
416
+ const scanned = scan(text);
417
+ const out = [];
418
+ let lastLine = -10;
419
+ let lastCode = "";
420
+ for (const error of errors) {
421
+ const line = lineNumber(text, error.pos[0]);
422
+ if (out.length && CASCADE.has(error.code) && CASCADE.has(lastCode) && line - lastLine <= 2 && error.code !== "DUPLICATE_KEY") continue;
423
+ const reading = readError(text, error, doc, scanned);
424
+ const next = reading.next;
425
+ out.push({
426
+ rule: reading.rule ?? "syntax",
427
+ code: reading.code,
428
+ start: reading.start,
429
+ end: reading.end,
430
+ message: reading.message,
431
+ next,
432
+ help: reading.help ?? sentence(next),
433
+ fixes: reading.fixes ?? [],
434
+ ...reading.related ? { related: reading.related } : {}
435
+ });
436
+ lastLine = line;
437
+ lastCode = error.code;
438
+ if (out.length >= limit) break;
439
+ }
440
+ return out;
441
+ }
442
+ var SLIDE_KEYS = /* @__PURE__ */ new Set(["title", "subtitle", "items", "text", "layout", "blocks", "image", "chart", "table", "quote", "code", "metric", "timeline", "notes"]);
443
+ function analyzeYamlSyntax(text, options = {}) {
444
+ const bom = text.startsWith("\uFEFF") ? 1 : 0;
445
+ const body = text.slice(bom);
446
+ const locate = createLocator(text);
447
+ const range = (start, end) => locate(start + bom, Math.max(0, end - start));
448
+ const shift = (edit) => ({ ...edit, offset: edit.offset + bom });
449
+ const doc = parseDocument(body, { schema: "core", uniqueKeys: true, merge: false, prettyErrors: false });
450
+ const wrap = (problem) => ({
451
+ ruleId: `yaml/${problem.rule}`,
452
+ code: problem.code,
453
+ severity: "error",
454
+ range: range(problem.start, problem.end),
455
+ message: problem.message,
456
+ next: problem.next,
457
+ fixes: problem.fixes.map((entry) => ({ ...entry, edits: entry.edits.map(shift), ...entry.caret !== void 0 ? { caret: entry.caret + bom } : {} })),
458
+ ...problem.related ? { related: problem.related.map((entry) => range(entry.start, entry.end)) } : {}
459
+ });
460
+ if (doc.errors.length) return describeYamlErrors(body, doc, doc.errors, options.limit ?? 5).map(wrap);
461
+ const root = doc.contents;
462
+ if (root === null || !root.range) {
463
+ const hasText = body.length > 0;
464
+ const prefix = hasText && !/\n$/.test(body) ? "\n" : "";
465
+ return [
466
+ wrap({
467
+ rule: "empty",
468
+ code: "empty-document",
469
+ start: 0,
470
+ end: Math.min(1, body.length),
471
+ message: "The document is empty (or everything in it is a comment).",
472
+ next: "write `slides:` and at least one `- title:` line.",
473
+ fixes: [fix("insert-minimal-deck", "Insert minimal deck", [{ offset: body.length, length: 0, insert: `${prefix}slides:
474
+ - title: Title
475
+ ` }], { safe: true })]
476
+ })
477
+ ];
478
+ }
479
+ if ((options.rootKind ?? "mapping") === "mapping" && !isMap(root)) {
480
+ const list = isSeq(root);
481
+ const slideLike = list && root.items.length > 0 && root.items.every((item) => isMap(item) && item.items.some((pair) => isScalar(pair.key) && SLIDE_KEYS.has(String(pair.key.value))));
482
+ const fixes = [];
483
+ if (slideLike) {
484
+ const first = lineAt(body, root.range[0]).start;
485
+ const edits = [{ offset: first, length: 0, insert: "slides:\n" }];
486
+ for (let cursor = first; cursor < body.length; ) {
487
+ const line = lineAt(body, cursor);
488
+ if (body.slice(line.start, line.end).trim() !== "") edits.push({ offset: line.start, length: 0, insert: " " });
489
+ const following = nextLineStart(body, line.end);
490
+ if (following <= cursor) break;
491
+ cursor = following;
492
+ }
493
+ fixes.push(fix("wrap-in-slides", "Wrap as `slides:`", edits));
494
+ }
495
+ return [
496
+ wrap({
497
+ rule: "not-mapping",
498
+ code: "not-a-mapping",
499
+ start: root.range[0],
500
+ end: Math.max(root.range[0] + 1, root.range[1]),
501
+ message: `The top level must be ${code("key: value")} lines, but this is ${list ? "a list" : "plain text"}.`,
502
+ next: slideLike ? "put the list under `slides:`." : "start with `name:` and `slides:` lines.",
503
+ fixes
504
+ })
505
+ ];
506
+ }
507
+ return [];
508
+ }
509
+ function splitLines(source) {
510
+ const lines = [];
511
+ let start = 0;
512
+ let no = 1;
513
+ let i = 0;
514
+ while (i < source.length) {
515
+ const c = source[i];
516
+ if (c === "\n" || c === "\r") {
517
+ lines.push({ text: source.slice(start, i), start, end: i, no: no++ });
518
+ if (c === "\r" && source[i + 1] === "\n") i++;
519
+ i++;
520
+ start = i;
521
+ } else i++;
522
+ }
523
+ if (start < source.length) lines.push({ text: source.slice(start), start, end: source.length, no });
524
+ return lines;
525
+ }
526
+ var Ctx = class {
527
+ constructor(source) {
528
+ this.source = source;
529
+ for (let i = 0; i < source.length; i++) {
530
+ if (source[i] === "\r") {
531
+ if (source[i + 1] === "\n") i++;
532
+ this.lineStarts.push(i + 1);
533
+ } else if (source[i] === "\n") this.lineStarts.push(i + 1);
534
+ }
535
+ }
536
+ source;
537
+ findings = [];
538
+ /** JSON Pointer in the converted document to the source range that produced it. */
539
+ ranges = /* @__PURE__ */ new Map();
540
+ lineStarts = [0];
541
+ location(range) {
542
+ let lo = 0;
543
+ let hi = this.lineStarts.length;
544
+ while (lo + 1 < hi) {
545
+ const mid = lo + hi >> 1;
546
+ if ((this.lineStarts[mid] ?? 0) <= range.start) lo = mid;
547
+ else hi = mid;
548
+ }
549
+ return { offset: range.start, length: Math.max(0, range.end - range.start), line: lo + 1, column: range.start - (this.lineStarts[lo] ?? 0) + 1 };
550
+ }
551
+ report(ruleId, severity, message, help, range, path = "", suggestions) {
552
+ this.findings.push({
553
+ ruleId,
554
+ severity,
555
+ category: "format",
556
+ scope: "document",
557
+ path,
558
+ message,
559
+ help,
560
+ location: this.location(range),
561
+ ...suggestions?.length ? { suggestions: suggestions.map((value) => ({ value, label: value, origin: "schema" })) } : {}
562
+ });
563
+ }
564
+ /** `suggestions`: values that would fix the finding (a near-miss option name, the chart types), offered by editors as "Change to ...". */
565
+ error(rule, message, help, range, path = "", suggestions) {
566
+ this.report(`markdown/${rule}`, "error", message, help, range, path, suggestions);
567
+ }
568
+ warn(rule, message, help, range, path = "", suggestions) {
569
+ this.report(`markdown/${rule}`, "warning", message, help, range, path, suggestions);
570
+ }
571
+ get errors() {
572
+ return this.findings.filter((d) => d.severity === "error").length;
573
+ }
574
+ /** The source range of a JSON Pointer, falling back to the nearest ancestor that has one. */
575
+ rangeOf(pointer) {
576
+ for (let path = pointer; ; path = path.slice(0, path.lastIndexOf("/"))) {
577
+ const found = this.ranges.get(path);
578
+ if (found) return found;
579
+ if (!path) return void 0;
580
+ }
581
+ }
582
+ };
583
+ var lineRange = (first, last = first) => ({ start: first.start, end: last.end });
584
+ function readYamlMapping(text, base, ctx, rule, context) {
585
+ const keys = /* @__PURE__ */ new Map();
586
+ const doc = parseDocument(text, { schema: "core", uniqueKeys: true, merge: false, prettyErrors: false });
587
+ let ok = true;
588
+ for (const problem of describeYamlErrors(text, doc, doc.errors)) {
589
+ ok = false;
590
+ ctx.error(rule, problem.message, problem.help, { start: base + problem.start, end: base + Math.max(problem.end, problem.start) });
591
+ }
592
+ if (!ok) return { value: {}, keys, ok };
593
+ if (doc.contents === null) return { value: {}, keys, ok };
594
+ if (!(doc.contents instanceof YAMLMap)) {
595
+ const root = doc.contents;
596
+ const where = root.range ? { start: base + root.range[0], end: base + Math.max(root.range[0] + 1, root.range[1]) } : context;
597
+ if (root instanceof Scalar && typeof root.value === "string" && root.range) {
598
+ const key = text.slice(root.range[0], root.range[1]).trim();
599
+ ctx.error(`${rule}-not-mapping`, `\`${key.length > 40 ? `${key.slice(0, 39)}\u2026` : key}\` needs a colon and a value.`, `Write \`${key}: \` and a value, one \`key: value\` line each.`, where);
600
+ } else ctx.error(`${rule}-not-mapping`, "Expected a YAML mapping of keys and values.", "Write `key: value` lines; a list or a bare value is not accepted here.", where);
601
+ return { value: {}, keys, ok: false };
602
+ }
603
+ for (const pair of doc.contents.items) {
604
+ const key = pair.key;
605
+ const name = key instanceof Scalar ? String(key.value) : void 0;
606
+ if (name !== void 0 && key?.range) keys.set(name, { start: base + key.range[0], end: base + (pair.value?.range?.[1] ?? key.range[1]) });
607
+ }
608
+ let value;
609
+ try {
610
+ value = doc.toJS({ maxAliasCount: 0 });
611
+ } catch (error) {
612
+ ctx.error(rule, `YAML: ${error.message}`, "Anchors and aliases are not accepted; write each value out.", context);
613
+ return { value: {}, keys, ok: false };
614
+ }
615
+ const bad = nonJson(value);
616
+ if (bad) {
617
+ ctx.error(rule, `YAML value is not JSON-compatible (${bad}).`, "Quote special numbers such as .inf or .nan to keep them as strings.", context);
618
+ return { value: {}, keys, ok: false };
619
+ }
620
+ return { value, keys, ok };
621
+ }
622
+ function nonJson(value) {
623
+ if (typeof value === "number") return Number.isFinite(value) ? void 0 : "a number that is not finite";
624
+ if (value === null || typeof value === "string" || typeof value === "boolean") return void 0;
625
+ if (Array.isArray(value)) return value.map(nonJson).find(Boolean);
626
+ if (isRecord(value)) return Object.values(value).map(nonJson).find(Boolean);
627
+ return `a ${typeof value}`;
628
+ }
629
+ function writeYaml(value) {
630
+ for (const blockQuote of [true, false]) {
631
+ const text = stringify(value, { indent: 2, lineWidth: 0, minContentWidth: 0, blockQuote, defaultStringType: "PLAIN", defaultKeyType: "PLAIN" });
632
+ const doc = parseDocument(text, { schema: "core", uniqueKeys: true, merge: false });
633
+ if (!doc.errors.length && same(doc.toJS({ maxAliasCount: 0 }), value)) return text.endsWith("\n") ? text : `${text}
634
+ `;
635
+ }
636
+ return void 0;
637
+ }
638
+ var DECIMAL = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
639
+ function scalarText(value) {
640
+ if (typeof value === "number") return DECIMAL.test(String(value)) ? String(value) : JSON.stringify(String(value));
641
+ const plain = value !== "" && value === value.trim() && !value.startsWith('"') && !/[\r\n]/.test(value) && !DECIMAL.test(value);
642
+ return plain ? value : JSON.stringify(value);
643
+ }
644
+ function readCsv(text) {
645
+ const rows = [];
646
+ let row = [];
647
+ let i = 0;
648
+ const n = text.length;
649
+ const endRecord = () => {
650
+ rows.push(row);
651
+ row = [];
652
+ };
653
+ while (i < n) {
654
+ while (text[i] === " " || text[i] === " ") i++;
655
+ if (text[i] === '"') {
656
+ let value = "";
657
+ i++;
658
+ for (; ; ) {
659
+ if (i >= n) return { rows, error: "A quoted field is not closed." };
660
+ if (text[i] === '"') {
661
+ if (text[i + 1] === '"') {
662
+ value += '"';
663
+ i += 2;
664
+ continue;
665
+ }
666
+ i++;
667
+ break;
668
+ }
669
+ value += text[i++];
670
+ }
671
+ while (text[i] === " " || text[i] === " ") i++;
672
+ if (i < n && text[i] !== "," && text[i] !== "\n" && text[i] !== "\r") return { rows, error: "Text follows a closing quote." };
673
+ row.push({ value, quoted: true });
674
+ } else {
675
+ let j = i;
676
+ while (j < n && text[j] !== "," && text[j] !== "\n" && text[j] !== "\r") j++;
677
+ row.push({ value: text.slice(i, j).trim(), quoted: false });
678
+ i = j;
679
+ }
680
+ if (text[i] === ",") {
681
+ i++;
682
+ if (i >= n || text[i] === "\n" || text[i] === "\r") row.push({ value: "", quoted: false });
683
+ continue;
684
+ }
685
+ if (text[i] === "\r" && text[i + 1] === "\n") i++;
686
+ i++;
687
+ endRecord();
688
+ }
689
+ if (row.length) endRecord();
690
+ return { rows };
691
+ }
692
+ var needsQuotes = (value) => /[",\r\n]/.test(value) || value !== value.trim() || value === "";
693
+ function csvField(cell, header = false) {
694
+ if (cell === null) return "";
695
+ if (typeof cell === "number") return String(cell);
696
+ if (typeof cell === "boolean") return String(cell);
697
+ const text = String(cell);
698
+ const ambiguous = !header && (DECIMAL.test(text) || /^(?:true|false)$/.test(text));
699
+ return needsQuotes(text) || ambiguous ? `"${text.replaceAll('"', '""')}"` : text;
700
+ }
701
+ var OPFMarkdownError = class extends Error {
702
+ code;
703
+ details;
704
+ constructor(code2, message, details = {}) {
705
+ super(message);
706
+ this.name = "OPFMarkdownError";
707
+ this.code = code2;
708
+ this.details = details;
709
+ }
710
+ };
711
+
712
+ export { Ctx, DECIMAL, OPFMarkdownError, analyzeYamlSyntax, csvField, describeYamlErrors, lineRange, readCsv, readYamlMapping, scalarText, splitLines, writeYaml };