@openpresentation/opf 0.26.2 → 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.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/dist/browser.js +15 -13
  3. package/dist/{chunk-5VJCLUUA.js → chunk-6IXSHIAL.js} +6 -6
  4. package/dist/{chunk-O2ST5ZKL.js → chunk-6NBTZYYD.js} +1 -1
  5. package/dist/{chunk-NWO6HT4F.js → chunk-EDCRBTJF.js} +4 -4
  6. package/dist/{chunk-JI3PHXPJ.js → chunk-EQ6S5SEZ.js} +1 -1
  7. package/dist/{chunk-N6CNJCIF.js → chunk-F4HFDP6I.js} +0 -18
  8. package/dist/chunk-GWJBZ4LC.js +639 -0
  9. package/dist/{chunk-LN2ASLMF.js → chunk-H5QC3X3K.js} +1 -1
  10. package/dist/{chunk-Q6KMKTEH.js → chunk-MN7DRK57.js} +40 -648
  11. package/dist/{chunk-OFWBICDO.js → chunk-NZLW6FBQ.js} +1 -1
  12. package/dist/{chunk-CRCOQ4K2.js → chunk-O6BY3AU2.js} +1 -1
  13. package/dist/{chunk-7YZ72FRP.js → chunk-PIO73BQ6.js} +6 -5
  14. package/dist/{chunk-6JAFSZVL.js → chunk-QAEBYDKY.js} +2 -4
  15. package/dist/{chunk-L65YNP4J.js → chunk-QH2W6HIM.js} +1 -1
  16. package/dist/chunk-RYZPBJLB.js +4 -0
  17. package/dist/{chunk-LUQYSUXS.js → chunk-XFOOKVRI.js} +57 -4
  18. package/dist/{chunk-XFK4KPKK.js → chunk-ZSZDVIDV.js} +2 -2
  19. package/dist/{conversion-CX_I6Vna.d.ts → conversion-DSafOGzh.d.ts} +2 -2
  20. package/dist/conversion-QIAJM6DQ.js +20 -0
  21. package/dist/convert.js +6 -6
  22. package/dist/deck.js +11 -9
  23. package/dist/diff.js +3 -3
  24. package/dist/docs.js +3 -3
  25. package/dist/format.js +2 -2
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.js +19 -17
  28. package/dist/markdown.js +8 -6
  29. package/dist/node-engine.d.ts +2 -2
  30. package/dist/node-engine.js +19 -17
  31. package/dist/pagination.js +3 -3
  32. package/dist/patch.js +2 -2
  33. package/dist/schema.d.ts +18 -4
  34. package/dist/schema.js +2 -2
  35. package/dist/schemas.js +1 -1
  36. package/dist/source.d.ts +102 -4
  37. package/dist/source.js +389 -10
  38. package/dist/spec/schemas/opf.schema.json +0 -16
  39. package/dist/spec/schemas/theme.schema.json +0 -2
  40. package/dist/validator.js +2 -2
  41. package/dist/yaml.js +7 -6
  42. package/package.json +5 -5
  43. package/dist/conversion-BKCKBECG.js +0 -18
package/dist/source.js CHANGED
@@ -1,10 +1,376 @@
1
- import { createLocator, parsePointer } from './chunk-LUQYSUXS.js';
2
- export { analyzeJsonSyntax, applyTextEdits } from './chunk-LUQYSUXS.js';
1
+ import { readMarkdown, frontMatter, splitSegments, isOutline, NOTES, fenceStart, fenceClose, COMMENT, SLIDE_OPTION_KEYS, FENCE_KINDS } from './chunk-MN7DRK57.js';
2
+ export { BLOCK_AS_VALUES, BLOCK_OPTION_KEYS, FENCE_ATTRIBUTES, FENCE_KINDS, LINE_KEYWORDS, METRIC_KEYS, SLIDE_OPTION_KEYS, SPAN_FLAGS, SPAN_VALUES } from './chunk-MN7DRK57.js';
3
+ import './chunk-O6BY3AU2.js';
4
+ import { splitLines } from './chunk-QAEBYDKY.js';
5
+ import './chunk-6NBTZYYD.js';
6
+ import { rememberByText, createLocator, parsePointer } from './chunk-XFOOKVRI.js';
7
+ export { analyzeJsonSyntax, applyTextEdits } from './chunk-XFOOKVRI.js';
3
8
  import './chunk-XRMV2GMB.js';
4
- import './chunk-N6CNJCIF.js';
9
+ import './chunk-F4HFDP6I.js';
5
10
  import { findNodeAtOffset, getNodePath, parseTree } from 'jsonc-parser';
11
+ import { parseDocument, isMap, isSeq, isScalar, isAlias } from 'yaml';
6
12
 
13
+ var read = rememberByText(readText);
14
+ function readText(text) {
15
+ const bom = text.startsWith("\uFEFF") ? 1 : 0;
16
+ const body = bom ? text.slice(1) : text;
17
+ const document = parseDocument(body, { prettyErrors: false, uniqueKeys: false, merge: false, strict: false });
18
+ return { text: body, root: document.contents ?? null, shift: bom };
19
+ }
20
+ var isBlank = (char) => char === " " || char === " " || char === "\n" || char === "\r";
21
+ function endOf(text, node) {
22
+ let end = node.range[1];
23
+ const start = node.range[0];
24
+ while (end > start && isBlank(text[end - 1])) end--;
25
+ return end;
26
+ }
27
+ var startOf = (node) => node.range[0];
28
+ var keyName = (key) => isScalar(key) ? String(key.value) : isAlias(key) ? void 0 : key !== null && typeof key === "object" && "toString" in key ? String(key.toString()) : void 0;
29
+ var hasText = (text, node) => !!node && node.range[0] < endOf(text, node);
30
+ var pairKey = (pair) => pair.key ?? null;
31
+ var pairValue = (pair) => pair.value ?? null;
32
+ function colonEnd(text, key) {
33
+ let at = key.range[1];
34
+ while (text[at] === " " || text[at] === " ") at++;
35
+ return text[at] === ":" ? at + 1 : key.range[1];
36
+ }
37
+ function member(node, segment) {
38
+ if (isMap(node)) {
39
+ const pair = node.items.find((item) => keyName(item.key) === segment);
40
+ return pair ? { pair, value: pairValue(pair) } : void 0;
41
+ }
42
+ if (isSeq(node)) {
43
+ if (!/^(?:0|[1-9][0-9]*)$/.test(segment)) return void 0;
44
+ const item = node.items[Number(segment)];
45
+ return item === void 0 ? void 0 : { value: item };
46
+ }
47
+ return void 0;
48
+ }
49
+ function openOf(text, value) {
50
+ const start = startOf(value);
51
+ if (isMap(value) && !value.flow) {
52
+ const first = value.items[0];
53
+ const key = first ? pairKey(first) : null;
54
+ if (key) return { offset: startOf(key), length: key.range[1] - startOf(key) };
55
+ }
56
+ return { offset: start, length: Math.min(1, endOf(text, value) - start) };
57
+ }
58
+ function locateYaml(text, segments, part) {
59
+ const { text: body, root, shift } = read(text);
60
+ if (!root || !hasText(body, root)) return void 0;
61
+ let value = root;
62
+ let pair;
63
+ let depth = 0;
64
+ for (const segment of segments) {
65
+ const next = value ? member(value, segment) : void 0;
66
+ if (!value || !next) break;
67
+ pair = next.pair;
68
+ value = next.value;
69
+ depth++;
70
+ }
71
+ const exact = depth === segments.length;
72
+ const key = pair ? pairKey(pair) : null;
73
+ const keySpan = key ? { offset: startOf(key), length: key.range[1] - startOf(key) } : void 0;
74
+ const keyAndColon = key ? { offset: startOf(key), length: colonEnd(body, key) - startOf(key) } : void 0;
75
+ const valueSpan = hasText(body, value) ? { offset: startOf(value), length: endOf(body, value) - startOf(value) } : void 0;
76
+ const choose = (wanted) => {
77
+ switch (wanted) {
78
+ case "value":
79
+ return valueSpan ?? keyAndColon;
80
+ case "open":
81
+ return hasText(body, value) ? openOf(body, value) : keyAndColon;
82
+ case "key":
83
+ return keySpan;
84
+ case "colon":
85
+ return keyAndColon;
86
+ case "entry":
87
+ return keySpan ? { offset: keySpan.offset, length: (valueSpan ? valueSpan.offset + valueSpan.length : (keyAndColon?.offset ?? 0) + (keyAndColon?.length ?? 0)) - keySpan.offset } : void 0;
88
+ }
89
+ };
90
+ const found = choose(part) ?? choose("value") ?? (depth === 0 ? { offset: startOf(root), length: endOf(body, root) - startOf(root) } : void 0);
91
+ return found ? { offset: found.offset + shift, length: found.length, exact } : void 0;
92
+ }
93
+ function pathAtYaml(text, offset) {
94
+ const { text: body, root, shift } = read(text);
95
+ const at = offset - shift;
96
+ const none = { path: [], part: "none" };
97
+ if (!root || !hasText(body, root) || at < startOf(root) || at > endOf(body, root)) return none;
98
+ const span = (node) => ({ offset: startOf(node) + shift, length: endOf(body, node) - startOf(node) });
99
+ const inside = (node) => hasText(body, node) && at >= startOf(node) && at <= endOf(body, node);
100
+ const walk = (node, path) => {
101
+ if (isMap(node)) {
102
+ for (const pair of node.items) {
103
+ const key = pairKey(pair);
104
+ const value = pairValue(pair);
105
+ const name = keyName(pair.key);
106
+ if (name === void 0) continue;
107
+ if (key && at >= startOf(key) && at <= key.range[1]) return { path: [...path, name], part: "key", range: span(key) };
108
+ if (inside(value)) return walk(value, [...path, name]);
109
+ if (key && at > key.range[1] && at <= (hasText(body, value) ? startOf(value) : colonEnd(body, key))) {
110
+ const end = hasText(body, value) ? startOf(value) : colonEnd(body, key);
111
+ return { path: [...path, name], part: "between", range: { offset: startOf(key) + shift, length: end - startOf(key) } };
112
+ }
113
+ }
114
+ return { path, part: "between", range: span(node) };
115
+ }
116
+ if (isSeq(node)) {
117
+ for (const [index, item] of node.items.entries()) if (inside(item)) return walk(item, [...path, String(index)]);
118
+ return { path, part: "between", range: span(node) };
119
+ }
120
+ return { path, part: "value", range: span(node) };
121
+ };
122
+ return walk(root, []);
123
+ }
124
+
125
+ // src/source-markdown.ts
126
+ var BLANK = /^\s*$/;
127
+ var readMarkdown2 = rememberByText((text) => readMarkdown(text));
128
+ var scanMemo = rememberByText((text) => scanUncached(text));
129
+ var isFenceKind = (word) => FENCE_KINDS.includes(word);
130
+ function scanMarkdown(source2, options = {}) {
131
+ return options.split === void 0 ? scanMemo(source2) : scanUncached(source2, options);
132
+ }
133
+ function scanUncached(source2, options = {}) {
134
+ let lines = splitLines(source2);
135
+ if (lines[0]?.text.startsWith("\uFEFF")) lines = [{ ...lines[0], text: lines[0].text.slice(1), start: lines[0].start + 1 }, ...lines.slice(1)];
136
+ const scan = { slides: [], separators: [], comments: [], fences: [], notes: [] };
137
+ const matter = frontMatter(lines);
138
+ let body = lines;
139
+ if (matter === "unterminated") {
140
+ const first = lines[0];
141
+ scan.frontMatter = { from: first.start, to: source2.length, line: 1, yamlFrom: first.end, yamlTo: source2.length, closed: false };
142
+ body = lines.slice(1);
143
+ } else if (matter) {
144
+ const closing = lines[matter.end - 1];
145
+ scan.frontMatter = { from: lines[0].start, to: closing.end, line: 1, yamlFrom: matter.yaml.start, yamlTo: matter.yaml.end, closed: true };
146
+ body = lines.slice(matter.end);
147
+ }
148
+ const split = options.split ?? "auto";
149
+ const segments = splitSegments(body, split === "headings" || split === "auto" && isOutline(body) ? "headings" : "rules");
150
+ const inSegment = new Set(segments.flat());
151
+ for (const line of body) if (!inSegment.has(line) && !BLANK.test(line.text)) scan.separators.push(line.start);
152
+ let index = 0;
153
+ for (const segment of segments) {
154
+ const first = segment.find((line) => !BLANK.test(line.text));
155
+ if (!first) continue;
156
+ const last = [...segment].reverse().find((line) => !BLANK.test(line.text));
157
+ scan.slides.push({ index, from: first.start, to: last.end, line: first.no });
158
+ scanSegment(segment, index, scan);
159
+ index++;
160
+ }
161
+ return scan;
162
+ }
163
+ function scanSegment(lines, slide, scan) {
164
+ let i = 0;
165
+ while (i < lines.length) {
166
+ const line = lines[i];
167
+ const text = line.text;
168
+ if (BLANK.test(text)) {
169
+ i++;
170
+ continue;
171
+ }
172
+ if (NOTES.test(text)) {
173
+ scan.notes.push({ from: line.start, to: lines.at(-1).end, line: line.no, slide });
174
+ return;
175
+ }
176
+ const open = fenceStart(text);
177
+ if (open) {
178
+ const close = fenceClose(open.fence);
179
+ let end = i + 1;
180
+ while (end < lines.length && !close.test(lines[end].text)) end++;
181
+ const closed = end < lines.length;
182
+ const last = closed ? lines[end] : lines.at(-1);
183
+ const indent = /^ */.exec(text)[0].length;
184
+ const marker = open.fence;
185
+ const infoFrom = line.start + indent + marker.length + /^[ \t]*/.exec(text.slice(indent + marker.length))[0].length;
186
+ const word = /^[^\s{]*/.exec(open.info)[0];
187
+ const bodyLines = lines.slice(i + 1, closed ? end : lines.length);
188
+ scan.fences.push({
189
+ from: line.start,
190
+ to: last.end,
191
+ line: line.no,
192
+ marker,
193
+ info: open.info,
194
+ infoFrom,
195
+ infoTo: infoFrom + open.info.length,
196
+ word,
197
+ kind: isFenceKind(word) ? word : "code",
198
+ bodyFrom: bodyLines[0]?.start ?? line.end,
199
+ bodyTo: bodyLines.at(-1)?.end ?? line.end,
200
+ closed,
201
+ slide
202
+ });
203
+ i = closed ? end + 1 : lines.length;
204
+ continue;
205
+ }
206
+ if (COMMENT.test(text)) {
207
+ let end = i;
208
+ while (end < lines.length && !lines[end].text.includes("-->", end === i ? lines[end].text.indexOf("<!--") + 4 : 0)) end++;
209
+ const closed = end < lines.length;
210
+ const last = closed ? lines[end] : lines.at(-1);
211
+ const open2 = text.indexOf("<!--");
212
+ const innerFrom = line.start + open2 + 4;
213
+ const closeAt = closed ? last.start + last.text.indexOf("-->", end === i ? open2 + 4 : 0) : last.end;
214
+ const inner = source(lines, i, closed ? end : lines.length - 1).slice(open2 + 4);
215
+ const kind = /^\s*(slide|block)(?::|(?=\s*(?:-->|$)))/.exec(inner);
216
+ scan.comments.push({ from: line.start, to: closed ? closeAt + 3 : last.end, line: line.no, kind: kind?.[1] ?? "comment", innerFrom, innerTo: closeAt, closed, slide });
217
+ i = closed ? end + 1 : lines.length;
218
+ continue;
219
+ }
220
+ i++;
221
+ }
222
+ }
223
+ function source(lines, first, last) {
224
+ return lines.slice(first, last + 1).map((line) => line.text).join(" ");
225
+ }
226
+ var segmentsOfPointer = (path) => path.map((segment) => `/${segment.replaceAll("~", "~0").replaceAll("/", "~1")}`).join("");
227
+ var SLIDE_OPTIONS = new Set(SLIDE_OPTION_KEYS);
228
+ var OPTIONS_COMMENT = /^\s*<!--/;
229
+ function firstLine(text, range) {
230
+ const end = text.slice(range.start, range.end).search(/[\r\n]/);
231
+ return { offset: range.start, length: end < 0 ? range.end - range.start : end };
232
+ }
233
+ function optionToken(text, comment, key, nth = 0) {
234
+ const inner = text.slice(comment.start, comment.end);
235
+ const open = inner.indexOf("<!--");
236
+ const colon = inner.indexOf(":", open);
237
+ const from = colon < 0 ? open + 4 : colon + 1;
238
+ const pattern = /(?:^|[\s,])([A-Za-z][\w-]*)(=("(?:[^"\\]|\\.)*"|[^\s,"]+))?/g;
239
+ const tail = inner.slice(from);
240
+ let seen = 0;
241
+ for (const match of tail.matchAll(pattern)) {
242
+ if (match[1] !== key) continue;
243
+ if (seen++ !== nth) continue;
244
+ const keyStart = comment.start + from + match.index + match[0].indexOf(match[1]);
245
+ const keySpan = { offset: keyStart, length: key.length };
246
+ const value = match[3] === void 0 ? void 0 : { offset: keyStart + key.length + 1, length: match[3].length };
247
+ return { key: keySpan, ...value ? { value } : {}, entry: { offset: keyStart, length: (value ? value.offset + value.length : keyStart + key.length) - keyStart } };
248
+ }
249
+ return void 0;
250
+ }
251
+ var MARKER = /^( {0,3}(?:#{1,6}|[-+*]|\d{1,9}[.)])[ \t]+)/;
252
+ function afterMarker(text, range) {
253
+ const line = firstLine(text, range);
254
+ const lineText = text.slice(line.offset, line.offset + line.length);
255
+ const match = MARKER.exec(lineText);
256
+ if (!match) return void 0;
257
+ const lead = match[1];
258
+ const markerLength = lead.trimEnd().length;
259
+ const content = lineText.slice(lead.length);
260
+ const trimmed = content.replace(/(?:^|[ \t]+)#+[ \t]*$/, "").trimEnd();
261
+ return { marker: { offset: line.offset + lead.length - lead.trimStart().length, length: markerLength - (lead.length - lead.trimStart().length) }, content: { offset: line.offset + lead.length, length: trimmed.length } };
262
+ }
263
+ function embeddedFenceAt(scan, offset) {
264
+ return scan.fences.find((fence) => (fence.kind === "opf-block" || fence.kind === "opf-slide") && offset >= fence.from && offset <= fence.to);
265
+ }
266
+ function locateMarkdown(text, segments, part) {
267
+ const { ctx, front } = readMarkdown2(text);
268
+ if (segments.length === 0) {
269
+ const trimmed = text.replace(/\s+$/, "").length;
270
+ return trimmed > 0 ? { offset: 0, length: trimmed, exact: true } : void 0;
271
+ }
272
+ if (segments[0] !== "slides") {
273
+ if (!front) return void 0;
274
+ const found = locateYaml(text.slice(front.start, front.end), segments, part);
275
+ if (found) return { offset: found.offset + front.start, length: found.length, exact: found.exact };
276
+ return { offset: front.start, length: Math.max(0, front.end - front.start), exact: false };
277
+ }
278
+ const slidesSpan = () => {
279
+ const first = ctx.ranges.get("/slides/0");
280
+ let last;
281
+ for (let index = 0; ctx.ranges.get(`/slides/${index}`); index++) last = ctx.ranges.get(`/slides/${index}`);
282
+ return first && last ? { start: first.start, end: last.end } : void 0;
283
+ };
284
+ let consumed = segments.length;
285
+ let range;
286
+ for (; consumed > 1; consumed--) {
287
+ range = ctx.ranges.get(segmentsOfPointer(segments.slice(0, consumed)));
288
+ if (range) break;
289
+ }
290
+ if (!range) {
291
+ const whole = slidesSpan();
292
+ return whole ? { offset: whole.start, length: whole.end - whole.start, exact: segments.length === 1 } : void 0;
293
+ }
294
+ const exact = consumed === segments.length;
295
+ const field = segments[2];
296
+ if (consumed >= 3 && field !== void 0 && SLIDE_OPTIONS.has(field) && OPTIONS_COMMENT.test(text.slice(range.start, range.end))) {
297
+ const beatIndex = field === "beat" && consumed === 3 && segments[3] !== void 0 && /^\d+$/.test(segments[3]) ? Number(segments[3]) : 0;
298
+ const token = optionToken(text, range, field, beatIndex);
299
+ if (token) {
300
+ const span = part === "key" ? token.key : part === "entry" || part === "colon" ? token.entry : token.value ?? token.key;
301
+ return { ...span, exact: exact || field === "beat" && consumed === 3 && segments.length === 4 };
302
+ }
303
+ }
304
+ if (!exact) {
305
+ const fence = embeddedFenceAt(scanMarkdown(text), range.start);
306
+ if (fence) {
307
+ const rest = consumed >= 4 && segments[2] === "blocks" ? segments.slice(4) : consumed === 3 ? segments.slice(2) : void 0;
308
+ const inside = rest?.length ? locateYaml(text.slice(fence.bodyFrom, fence.bodyTo), rest, part) : void 0;
309
+ if (inside) return { offset: inside.offset + fence.bodyFrom, length: inside.length, exact: inside.exact };
310
+ }
311
+ }
312
+ const title = consumed === 3 && (field === "title" || field === "subtitle");
313
+ const item = exact && segments.length >= 4 && /^\d+$/.test(segments[segments.length - 1]) && segments[segments.length - 2] !== "blocks";
314
+ if (exact && (title || item)) {
315
+ const marked = afterMarker(text, range);
316
+ if (marked) {
317
+ const span = part === "key" ? marked.marker : part === "entry" ? firstLine(text, range) : marked.content;
318
+ if (span.length > 0) return { ...span, exact: true };
319
+ }
320
+ }
321
+ if (part === "open") return { ...firstLine(text, range), exact };
322
+ return { offset: range.start, length: Math.max(0, range.end - range.start), exact };
323
+ }
324
+ var unescapeSegment = (segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~");
325
+ var spanOf = (range) => ({ offset: range.start, length: range.end - range.start });
326
+ function pathAtMarkdown(text, offset) {
327
+ const { ctx, front } = readMarkdown2(text);
328
+ if (front && offset >= front.start && offset <= front.end) {
329
+ const inside = pathAtYaml(text.slice(front.start, front.end), offset - front.start);
330
+ return { path: inside.path, part: inside.part === "none" ? "between" : inside.part, ...inside.range ? { range: { offset: inside.range.offset + front.start, length: inside.range.length } } : {} };
331
+ }
332
+ const scan = scanMarkdown(text);
333
+ const fence = embeddedFenceAt(scan, offset);
334
+ if (fence && offset >= fence.bodyFrom && offset <= fence.bodyTo) {
335
+ const inside = pathAtYaml(text.slice(fence.bodyFrom, fence.bodyTo), offset - fence.bodyFrom);
336
+ if (inside.part !== "none" && inside.path.length) {
337
+ const block = fence.kind === "opf-block" ? blockIndex(ctx.ranges, fence.slide, fence.from) : -1;
338
+ const prefix = block >= 0 ? ["slides", String(fence.slide), "blocks", String(block)] : ["slides", String(fence.slide)];
339
+ return { path: [...prefix, ...inside.path], part: inside.part, ...inside.range ? { range: { offset: inside.range.offset + fence.bodyFrom, length: inside.range.length } } : {} };
340
+ }
341
+ }
342
+ let best;
343
+ for (const [pointer, range] of ctx.ranges) {
344
+ if (!pointer.startsWith("/slides/") || offset < range.start || offset > range.end) continue;
345
+ const depth = pointer.split("/").length;
346
+ if (!best || depth > best.depth || depth === best.depth && range.end - range.start < best.range.end - best.range.start) best = { pointer, range, depth };
347
+ }
348
+ if (!best) return { path: [], part: "none" };
349
+ const path = best.pointer.split("/").slice(1).map(unescapeSegment);
350
+ const field = path[2];
351
+ if (path.length === 3 && field && SLIDE_OPTIONS.has(field) && OPTIONS_COMMENT.test(text.slice(best.range.start, best.range.end))) {
352
+ for (const key of SLIDE_OPTIONS) {
353
+ for (let nth = 0; ; nth++) {
354
+ const token = optionToken(text, best.range, key, nth);
355
+ if (!token) break;
356
+ const at = key === "beat" ? [path[0], path[1], key, String(nth)] : [path[0], path[1], key];
357
+ if (offset >= token.key.offset && offset <= token.key.offset + token.key.length) return { path: at, part: "key", range: token.key };
358
+ if (token.value && offset >= token.value.offset && offset <= token.value.offset + token.value.length) return { path: at, part: "value", range: token.value };
359
+ }
360
+ }
361
+ return { path: path.slice(0, 2), part: "between", range: spanOf(best.range) };
362
+ }
363
+ return { path, part: "value", range: spanOf(best.range) };
364
+ }
365
+ function blockIndex(ranges, slide, start) {
366
+ for (let index = 0; ranges.has(`/slides/${slide}/blocks/${index}`); index++) if (ranges.get(`/slides/${slide}/blocks/${index}`).start === start) return index;
367
+ return -1;
368
+ }
369
+
370
+ // src/source-locate.ts
371
+ var indexOrKey = (segment) => /^(?:0|[1-9][0-9]*)$/.test(segment) ? Number(segment) : segment;
7
372
  var isJson = (format) => format === "json";
373
+ var isMarkdown = (format) => format === "md" || format === "markdown";
8
374
  var parseText = (text) => parseTree(text.startsWith("\uFEFF") ? ` ${text.slice(1)}` : text, [], { allowTrailingComma: true, allowEmptyContent: true, disallowComments: false });
9
375
  function segmentsOf(pointer) {
10
376
  if (Array.isArray(pointer)) return pointer.map(String);
@@ -15,7 +381,7 @@ function segmentsOf(pointer) {
15
381
  return void 0;
16
382
  }
17
383
  }
18
- function member(node, segment) {
384
+ function member2(node, segment) {
19
385
  if (node.type === "array") return /^(?:0|[1-9][0-9]*)$/.test(segment) ? node.children?.[Number(segment)] : void 0;
20
386
  if (node.type === "object") return node.children?.find((property) => property.children?.[0]?.value === segment);
21
387
  return void 0;
@@ -37,17 +403,26 @@ function partOf(value, property, part) {
37
403
  }
38
404
  }
39
405
  function locate(text, format, pointer, options = {}) {
40
- if (!isJson(format)) return void 0;
41
406
  const segments = segmentsOf(pointer);
42
- const root = segments ? parseText(text) : void 0;
43
- if (!segments || !root) return void 0;
407
+ if (!segments) return void 0;
44
408
  const part = options.part ?? "value";
45
409
  const place = createLocator(text);
410
+ if (format === "yaml") {
411
+ const found2 = locateYaml(text, segments, part);
412
+ return found2 ? { ...place(found2.offset, found2.length), ...found2.exact ? {} : { exact: false } } : void 0;
413
+ }
414
+ if (isMarkdown(format)) {
415
+ const found2 = locateMarkdown(text, segments, part);
416
+ return found2 ? { ...place(found2.offset, found2.length), ...found2.exact ? {} : { exact: false } } : void 0;
417
+ }
418
+ if (!isJson(format)) return void 0;
419
+ const root = parseText(text);
420
+ if (!root) return void 0;
46
421
  let value = root;
47
422
  let property;
48
423
  let depth = 0;
49
424
  for (const segment of segments) {
50
- const next = value ? member(value, segment) : void 0;
425
+ const next = value ? member2(value, segment) : void 0;
51
426
  if (!value || !next) break;
52
427
  if (value.type === "object") {
53
428
  property = next;
@@ -64,9 +439,13 @@ function locate(text, format, pointer, options = {}) {
64
439
  return { ...place(found.offset, found.length), ...exact ? {} : { exact: false } };
65
440
  }
66
441
  function pathAt(text, format, offset) {
442
+ const place = createLocator(text);
443
+ if (format === "yaml" || isMarkdown(format)) {
444
+ const found = format === "yaml" ? pathAtYaml(text, offset) : pathAtMarkdown(text, offset);
445
+ return { path: found.path.map(indexOrKey), part: found.part, ...found.range ? { range: place(found.range.offset, found.range.length) } : {} };
446
+ }
67
447
  if (!isJson(format)) return void 0;
68
448
  const root = parseText(text);
69
- const place = createLocator(text);
70
449
  const none = { path: [], part: "none" };
71
450
  if (!root || offset < root.offset || offset > root.offset + root.length) return none;
72
451
  const node = findNodeAtOffset(root, offset, true);
@@ -87,4 +466,4 @@ function pathAt(text, format, offset) {
87
466
  return { path: getNodePath(node), part: "value", range: range(node) };
88
467
  }
89
468
 
90
- export { locate, pathAt };
469
+ export { locate, pathAt, scanMarkdown };
@@ -62,14 +62,12 @@
62
62
  "examples": ["Bob Lee", ["Bob Lee", "Carla Diaz"]]
63
63
  },
64
64
  "audience": {
65
- "x-opf-catalog": "audiences",
66
65
  "oneOf": [
67
66
  { "type": "string" },
68
67
  { "$ref": "#/$defs/Audience" },
69
68
  {
70
69
  "type": "array",
71
70
  "items": {
72
- "x-opf-catalog": "audiences",
73
71
  "oneOf": [
74
72
  { "type": "string" },
75
73
  { "$ref": "#/$defs/Audience" }
@@ -98,7 +96,6 @@
98
96
  ]
99
97
  },
100
98
  "purpose": {
101
- "x-opf-catalog": "purposes",
102
99
  "oneOf": [
103
100
  { "type": "string" },
104
101
  { "$ref": "#/$defs/Purpose" }
@@ -127,7 +124,6 @@
127
124
  ]
128
125
  },
129
126
  "tone": {
130
- "x-opf-catalog": "tones",
131
127
  "oneOf": [{ "$ref": "#/$defs/CatalogReference" }, { "$ref": "#/$defs/Tone" }],
132
128
  "description": "Desired tone for the presentation. Accepts either:\n- A tones reference: a bare id ('formal') or 'name:id' for a record of a named catalogs group.\n- An inline Tone object for custom tone metadata or overrides on a referenced record. Object form has the fields of https://openpresentation.org/schema/opf-tone/v1.\n\nA reference resolves like every content reference (see catalogs); one that resolves nowhere is the warning opf/unresolved-reference. Nothing is drawn from the tone.",
133
129
  "examples": [
@@ -185,7 +181,6 @@
185
181
  "default": false
186
182
  },
187
183
  "narrative": {
188
- "x-opf-catalog": "narratives",
189
184
  "$ref": "#/$defs/CatalogReference",
190
185
  "description": "The deck's narrative plan, by reference: a bare narratives id ('classic-story') or 'name:id' for a record of a named catalogs group ('acme:founder-pitch'). The deck holds only this pointer; the plan (the arc, its beats and what each beat must do) lives in the record, which has the fields of https://openpresentation.org/schema/opf-narrative/v1. A narrative the document defines itself is a record in catalogs.custom.narratives, referenced here by its key.\n\nSlides link themselves to beats with slides[].beat. Core validate warns about a slides[].beat id the resolved narrative does not define and about a root 'duration' outside its 'duration' range, and reports a beat that no slide references. The preview and the PPTX export draw nothing from the narrative; opf-pptx stores the reference for round-trip.\n\nThe reference resolves like every content reference (see catalogs); one that resolves nowhere is the warning opf/unresolved-reference, never an error.\n\nDeck-level concerns that are not part of the storyline (audience, tone, takeaway, duration) live as siblings on the presentation root.",
191
186
  "examples": ["classic-story", "problem-solution", "scqa", "pitch-deck", "acme:founder-pitch"]
@@ -282,7 +277,6 @@
282
277
  ],
283
278
  "properties": {
284
279
  "id": {
285
- "x-opf-catalog": "audiences",
286
280
  "$ref": "#/$defs/CatalogReference",
287
281
  "description": "Optional audiences reference (a bare id or 'name:id') resolved before applying the inline overrides.",
288
282
  "examples": ["executive", "board", "engineering-team"]
@@ -345,7 +339,6 @@
345
339
  ],
346
340
  "properties": {
347
341
  "id": {
348
- "x-opf-catalog": "purposes",
349
342
  "$ref": "#/$defs/CatalogReference",
350
343
  "description": "Optional purposes reference (a bare id or 'name:id') resolved before applying the inline overrides.",
351
344
  "examples": ["inform", "decide", "align", "persuade", "educate"]
@@ -425,12 +418,10 @@
425
418
  "examples": ["Latn", "Cyrl", "Grek", "Arab", "Hebr", "Jpan", "Kore", "Hans", "Hant", "Deva", "Thai"]
426
419
  },
427
420
  "fontScheme": {
428
- "x-opf-catalog": "fontSchemes",
429
421
  "$ref": "#/$defs/CatalogReference",
430
422
  "description": "Font-scheme reference (a bare id or 'name:id') for this language's script slot when targeting PowerPoint output. Its major/minor families fill the language's script slot (eastAsian or complexScript, chosen by 'script') unless the effective design font scheme sets that slot itself; the latin slot always follows the design font scheme. Without it, engines use the default script fonts of their language vocabulary."
431
423
  },
432
424
  "googleFontScheme": {
433
- "x-opf-catalog": "fontSchemes",
434
425
  "$ref": "#/$defs/CatalogReference",
435
426
  "description": "Font-scheme reference (a bare id or 'name:id') used in place of 'fontScheme' when resolving script fonts for Google Slides output."
436
427
  },
@@ -458,7 +449,6 @@
458
449
  ],
459
450
  "properties": {
460
451
  "id": {
461
- "x-opf-catalog": "tones",
462
452
  "$ref": "#/$defs/CatalogReference",
463
453
  "description": "Optional tones reference (a bare id or 'name:id') resolved before applying the inline overrides.",
464
454
  "examples": ["formal", "casual", "technical", "persuasive"]
@@ -658,13 +648,11 @@
658
648
  "description": "Visual design system applied to the presentation; individual slides may override fields via Slide.design.",
659
649
  "properties": {
660
650
  "theme": {
661
- "x-opf-catalog": "themes",
662
651
  "$ref": "#/$defs/CatalogReference",
663
652
  "description": "Theme for the deck: a themes reference, a bare id ('minimal') or 'name:id' ('acme:brand'). The theme supplies the default colour scheme, font scheme, background and dimensions; design.colorScheme, design.fontScheme, design.background and design.dimensions override it field by field. A reference that resolves nowhere is the warning opf/unresolved-reference and engines use the engine default theme.",
664
653
  "examples": ["minimal", "classic", "bold", "acme:brand"]
665
654
  },
666
655
  "colorScheme": {
667
- "x-opf-catalog": "colorSchemes",
668
656
  "oneOf": [
669
657
  { "type": "string" },
670
658
  { "$ref": "#/$defs/ColorScheme" }
@@ -678,7 +666,6 @@
678
666
  ]
679
667
  },
680
668
  "fontScheme": {
681
- "x-opf-catalog": "fontSchemes",
682
669
  "oneOf": [
683
670
  { "type": "string" },
684
671
  { "$ref": "#/$defs/FontScheme" }
@@ -798,7 +785,6 @@
798
785
  "description": "Color palette used by the design system. Every slot and role is a hex color ('#RGB', '#RRGGBB' or '#RRGGBBAA'); a color-scheme name or a 'var:<id>' reference is not valid here, because those resolve through the scheme. The slot fields (accent1-accent6, dark1, dark2, light1, light2, hyperlink, followedHyperlink) mirror color-scheme.schema.json (https://openpresentation.org/schema/opf-color-scheme/v1) so library records and inline OPF overrides are interchangeable on those fields.\n\nTwo parallel models are supported and may be mixed:\n- OOXML slots - the 12-slot PowerPoint theme model that round-trips directly to OOXML. Use these for full control over the palette as PowerPoint sees it.\n- Abstract roles (primary, secondary, accent, background, surface, text, textSecondary) - OPF-specific semantic aliases the engine maps onto the OOXML slots. Convenient for inline overrides without thinking in slot numbers; not part of the catalog record schema.\n\nIn design.colorScheme, 'id' resolves a colorSchemes record as the base; slot and role overrides on the same object take precedence over the resolved scheme. The string form of design.colorScheme is equivalent to setting only 'id'.",
799
786
  "properties": {
800
787
  "id": {
801
- "x-opf-catalog": "colorSchemes",
802
788
  "$ref": "#/$defs/CatalogReference",
803
789
  "description": "Colour-scheme reference (a bare id or 'name:id') resolved as the base; slot and role overrides on the surrounding ColorScheme object take precedence over the resolved scheme.",
804
790
  "examples": ["cool-horizon", "boost", "burnt-orange", "ocean-depth", "corporate-blue"]
@@ -905,7 +891,6 @@
905
891
  "description": "Typography selections used by the design system. The pair fields (major, minor) and refinement fields (type, app, languageFamily) mirror font-scheme.schema.json (https://openpresentation.org/schema/opf-font-scheme/v1) so library records and inline OPF overrides are interchangeable on those fields.\n\nTwo parallel models are supported and may be mixed:\n- OOXML pairs (major, minor) - heading and body family names that round-trip directly to PowerPoint majorFont/minorFont entries.\n- Abstract roles (heading, body, accent, code) - OPF-specific font family names the engine maps onto the OOXML pair when serializing. Convenient for inline overrides and for adding accent/code roles that don't have a direct OOXML slot. Catalog records may also carry 'code' (for example the consolas and courier-new records); heading, body and accent are not part of the catalog record schema. A role is a family name only: weight, style and tracking come from the text styles, and PowerPoint has no numeric weight.\n\nIn design.fontScheme, 'id' resolves a fontSchemes record as the base; pair and role overrides on the same object take precedence over the resolved scheme. The string form of design.fontScheme is equivalent to setting only 'id'.",
906
892
  "properties": {
907
893
  "id": {
908
- "x-opf-catalog": "fontSchemes",
909
894
  "$ref": "#/$defs/CatalogReference",
910
895
  "description": "Font-scheme reference (a bare id or 'name:id') resolved as the base; field overrides on the surrounding FontScheme object take precedence over the resolved scheme.",
911
896
  "examples": ["aptos", "tenorite", "seaford", "noto-sans", "modern-sans", "classic-serif"]
@@ -1952,7 +1937,6 @@
1952
1937
  "description": "Optional reference to one or more beats of the deck's narrative (each value is the id of a beat in the narrative record that the root 'narrative' resolves to). A single string declares the slide's primary beat; an array declares that one slide covers multiple beats. Declares the slide's role in the story arc; does not constrain ordering or structure. Core validate warns about a beat id the resolved narrative does not define, and reports a beat that no slide references. Slides without a beat are valid; multiple slides may share a beat."
1953
1938
  },
1954
1939
  "layout": {
1955
- "x-opf-catalog": "layouts",
1956
1940
  "$ref": "#/$defs/CatalogReference",
1957
1941
  "description": "Optional layout: a layouts reference, a bare id ('pillars') or 'name:id' ('acme:hero'), or 'auto'. When omitted or 'auto', the slide is composed with no layout record: engines arrange title, subtitle, tag and content automatically, a slide with no body payload is a cover (vertically centered), and design hints apply; there is no finding. 'auto' is reserved, so no catalog may define a layout of that id, and opf format drops it. A layout is a grid template (layout.schema.json): the slide's content stays flat and binds to the layout's regions by reading order, role and kind, a block may pin itself to a region with 'region', and an empty region collapses. A reference that resolves nowhere is the warning opf/unresolved-reference (and the 'unresolved-reference' diagnostic of resolveSlideContext), the slide composes automatically, and a strict export fails. A layout id of the 0.18 default catalog that 0.19 no longer has is such a reference: 0.19 has no migration path for layouts. A layout never changes what a slide contains, and engines never substitute a different layout.",
1958
1942
  "examples": ["cover", "pillars", "chart-beside", "auto", "acme:hero"]
@@ -50,14 +50,12 @@
50
50
  ]
51
51
  },
52
52
  "colorScheme": {
53
- "x-opf-catalog": "colorSchemes",
54
53
  "type": "string",
55
54
  "pattern": "^(?:[a-z][a-z0-9-]*:)?[a-z][a-z0-9-]*$",
56
55
  "description": "The theme's default colour scheme: a reference (a bare id or 'name:id') resolved like design.colorScheme, but in the theme's own catalogs group first.",
57
56
  "examples": ["cool-horizon", "boost", "burnt-orange"]
58
57
  },
59
58
  "fontScheme": {
60
- "x-opf-catalog": "fontSchemes",
61
59
  "type": "string",
62
60
  "pattern": "^(?:[a-z][a-z0-9-]*:)?[a-z][a-z0-9-]*$",
63
61
  "description": "The theme's default font scheme: a reference (a bare id or 'name:id') resolved like design.fontScheme, but in the theme's own catalogs group first.",
package/dist/validator.js CHANGED
@@ -1,3 +1,3 @@
1
- export { DEFAULT_VALIDATION_THRESHOLDS, OPFValidationError, assertValid, assertValidCatalogRecord, findValidationRule, validate, validateCatalogRecord, validationCategories, validationRules } from './chunk-LUQYSUXS.js';
1
+ export { DEFAULT_VALIDATION_THRESHOLDS, OPFValidationError, assertValid, assertValidCatalogRecord, findValidationRule, validate, validateCatalogRecord, validationCategories, validationRules } from './chunk-XFOOKVRI.js';
2
2
  export { promotedRegionKeys } from './chunk-XRMV2GMB.js';
3
- import './chunk-N6CNJCIF.js';
3
+ import './chunk-F4HFDP6I.js';
package/dist/yaml.js CHANGED
@@ -1,7 +1,8 @@
1
- export { OPFYamlError, YAML_SCHEMA_URL, fromYaml, parseYamlData, scanYamlComments, toYaml } from './chunk-7YZ72FRP.js';
2
- import './chunk-6JAFSZVL.js';
3
- import './chunk-O2ST5ZKL.js';
4
- import './chunk-LUQYSUXS.js';
1
+ export { OPFYamlError, YAML_SCHEMA_URL, fromYaml, parseYamlData, scanYamlComments, toYaml } from './chunk-PIO73BQ6.js';
2
+ import './chunk-RYZPBJLB.js';
3
+ import './chunk-QAEBYDKY.js';
4
+ import './chunk-6NBTZYYD.js';
5
+ import './chunk-XFOOKVRI.js';
5
6
  import './chunk-XRMV2GMB.js';
6
- import './chunk-JI3PHXPJ.js';
7
- import './chunk-N6CNJCIF.js';
7
+ import './chunk-EQ6S5SEZ.js';
8
+ import './chunk-F4HFDP6I.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpresentation/opf",
3
- "version": "0.26.2",
3
+ "version": "0.26.4",
4
4
  "description": "Canonical Open Presentation Format schemas, TypeScript types, local validation and composition; in Node, the file API and the CLI defaults register the pptx.gallery catalog of @openpresentation/gallery.",
5
5
  "license": "MIT",
6
6
  "author": "Open Presentation",
@@ -145,7 +145,7 @@
145
145
  "ajv-formats": "^3.0.1",
146
146
  "jsonc-parser": "3.3.1",
147
147
  "yaml": "^2.9.1",
148
- "@openpresentation/gallery": "^0.26.2"
148
+ "@openpresentation/gallery": "^0.26.4"
149
149
  },
150
150
  "devDependencies": {
151
151
  "@expo-google-fonts/arimo": "0.4.3",
@@ -165,8 +165,8 @@
165
165
  "typescript": "~5.9.3"
166
166
  },
167
167
  "peerDependencies": {
168
- "@openpresentation/opf-pptx": "^0.26.2",
169
- "@openpresentation/opf-render": "^0.26.2"
168
+ "@openpresentation/opf-pptx": "^0.26.4",
169
+ "@openpresentation/opf-render": "^0.26.4"
170
170
  },
171
171
  "peerDependenciesMeta": {
172
172
  "@openpresentation/opf-pptx": {
@@ -186,5 +186,5 @@
186
186
  "test:packed": "node test/packed-install-smoke.mjs",
187
187
  "pack:dry-run": "pnpm run build:deps && pnpm run build && npm pack --dry-run --cache /tmp/opf-npm-cache"
188
188
  },
189
- "gitHead": "0440759ebddc9ea01fdf52c88a0406e21c955375"
189
+ "gitHead": "8809122a74a43aa99cdd383a856839ad6f018af6"
190
190
  }
@@ -1,18 +0,0 @@
1
- export { convertFiles, deckFileFormat, exportFormatOf, openDeck, planConversion, readInput, saveDeck, writePlanned } from './chunk-5VJCLUUA.js';
2
- import './chunk-XFK4KPKK.js';
3
- import './chunk-OFWBICDO.js';
4
- import './chunk-UJSAR6GF.js';
5
- import './chunk-PJSPHHRK.js';
6
- import './chunk-TKQ2VFV5.js';
7
- import './chunk-L65YNP4J.js';
8
- import './chunk-NWO6HT4F.js';
9
- import './chunk-Q6KMKTEH.js';
10
- import './chunk-CRCOQ4K2.js';
11
- import './chunk-7YZ72FRP.js';
12
- import './chunk-6JAFSZVL.js';
13
- import './chunk-O2ST5ZKL.js';
14
- import './chunk-LN2ASLMF.js';
15
- import './chunk-LUQYSUXS.js';
16
- import './chunk-XRMV2GMB.js';
17
- import './chunk-JI3PHXPJ.js';
18
- import './chunk-N6CNJCIF.js';