@openpresentation/opf-editor 0.12.1 → 0.14.2

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 (95) hide show
  1. package/README.md +71 -60
  2. package/dist/annotations.d.ts +16 -16
  3. package/dist/annotations.js +71 -79
  4. package/dist/assets.d.ts +7 -7
  5. package/dist/assets.js +15 -15
  6. package/dist/background-options.d.ts +2 -2
  7. package/dist/background-options.js +6 -6
  8. package/dist/block-controls.js +7 -7
  9. package/dist/block-convert.d.ts +7 -7
  10. package/dist/block-convert.js +22 -21
  11. package/dist/blocks.d.ts +8 -8
  12. package/dist/blocks.js +41 -40
  13. package/dist/canvas-fields.js +35 -12
  14. package/dist/canvas.d.ts +22 -30
  15. package/dist/canvas.js +57 -64
  16. package/dist/chart-data.d.ts +10 -10
  17. package/dist/chart-data.js +21 -21
  18. package/dist/chart-options-panel.d.ts +2 -2
  19. package/dist/chart-options-panel.js +82 -8
  20. package/dist/chart-options.d.ts +35 -9
  21. package/dist/chart-options.js +133 -40
  22. package/dist/checks.js +21 -0
  23. package/dist/color-authoring.js +1 -1
  24. package/dist/content-actions.d.ts +15 -15
  25. package/dist/content-actions.js +71 -70
  26. package/dist/content-controls.js +12 -12
  27. package/dist/data-grid.js +7 -8
  28. package/dist/data.d.ts +4 -4
  29. package/dist/data.js +5 -5
  30. package/dist/dataset-refs.d.ts +2 -2
  31. package/dist/dataset-refs.js +5 -5
  32. package/dist/design-controls.js +61 -44
  33. package/dist/design-options.d.ts +16 -14
  34. package/dist/design-options.js +86 -74
  35. package/dist/edit-helpers.js +8 -7
  36. package/dist/exact-source.js +5 -5
  37. package/dist/export.d.ts +5 -7
  38. package/dist/export.js +40 -14
  39. package/dist/find-panel.js +5 -5
  40. package/dist/find-replace.d.ts +3 -3
  41. package/dist/find-replace.js +23 -20
  42. package/dist/font-gate.js +49 -58
  43. package/dist/galleries.js +36 -52
  44. package/dist/grid-model.d.ts +25 -27
  45. package/dist/grid-model.js +92 -102
  46. package/dist/image-crop.d.ts +4 -4
  47. package/dist/image-crop.js +20 -20
  48. package/dist/image-cropper.js +3 -3
  49. package/dist/index.d.ts +28 -28
  50. package/dist/index.js +74 -90
  51. package/dist/json-options.js +17 -16
  52. package/dist/layout-handles.js +5 -5
  53. package/dist/layout-placeholders.js +4 -4
  54. package/dist/layout.d.ts +2 -2
  55. package/dist/layout.js +8 -7
  56. package/dist/numbering-panel.js +3 -5
  57. package/dist/numbering.d.ts +3 -5
  58. package/dist/numbering.js +14 -26
  59. package/dist/outline-view.js +24 -24
  60. package/dist/outline.d.ts +12 -12
  61. package/dist/outline.js +52 -50
  62. package/dist/persistence.d.ts +1 -1
  63. package/dist/persistence.js +19 -18
  64. package/dist/presentation-ids.js +2 -2
  65. package/dist/react.js +3 -3
  66. package/dist/review-panel.d.ts +30 -13
  67. package/dist/review-panel.js +154 -53
  68. package/dist/review.d.ts +36 -43
  69. package/dist/review.js +92 -53
  70. package/dist/rich-text-input.js +4 -3
  71. package/dist/rich-text-toolbar.js +5 -3
  72. package/dist/rich-text.js +5 -2
  73. package/dist/schema-inspector.d.ts +2 -2
  74. package/dist/schema-inspector.js +16 -11
  75. package/dist/schema.d.ts +1 -1
  76. package/dist/schema.js +25 -5
  77. package/dist/slide-manager.d.ts +2 -2
  78. package/dist/slide-manager.js +18 -18
  79. package/dist/slides.d.ts +20 -20
  80. package/dist/slides.js +99 -97
  81. package/dist/svelte.js +1 -1
  82. package/dist/switches.d.ts +10 -10
  83. package/dist/switches.js +124 -114
  84. package/dist/table-options.d.ts +8 -8
  85. package/dist/table-options.js +28 -27
  86. package/dist/table-structure.d.ts +10 -10
  87. package/dist/table-structure.js +21 -21
  88. package/dist/template-panel.d.ts +3 -3
  89. package/dist/template-panel.js +37 -13
  90. package/dist/templates.d.ts +22 -11
  91. package/dist/templates.js +48 -55
  92. package/dist/transfer.d.ts +4 -4
  93. package/dist/transfer.js +49 -59
  94. package/package.json +8 -6
  95. package/dist/font-defaults.js +0 -25
package/dist/layout.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type {ComposedFlow, Composition} from '@openpresentation/opf/composition';
2
2
  import type {JsonPatchOperation} from './index.js';
3
3
  /** Resize adjacent tracks. Fractions clamp to 5–95%; automatic flows become explicit grids. */
4
- export declare function prepareTrackResize(document:unknown,flow:ComposedFlow,boundary:number,fraction:number):{
5
- document:unknown;patches:JsonPatchOperation[];composition:Composition;fraction:number;
4
+ export declare function prepareTrackResize(presentation:unknown,flow:ComposedFlow,boundary:number,fraction:number):{
5
+ presentation:unknown;patches:JsonPatchOperation[];composition:Composition;fraction:number;
6
6
  };
7
7
 
8
8
  export {prepareBlockMove,listBlockContainers,prepareBlockInsert,prepareBlockDuplicate,prepareBlockRemove,prepareBlockReplace,createContentBlock} from './blocks.js';
package/dist/layout.js CHANGED
@@ -1,7 +1,8 @@
1
- import {getValueAtPath, createValuePatch, applyJsonPatch, validateOpfDocument, opfPathToJsonPointer} from './index.js';
1
+ import { getValueAtPath, createValuePatch, applyJsonPatch, opfPathToJsonPointer } from './index.js';
2
+ import { firstErrorMessage, checkFormat } from "./checks.js";
2
3
 
3
4
  /** Prepare a validated track resize; the test operation guards its container revision. */
4
- export function prepareTrackResize(document, flow, boundary, fraction) {
5
+ export function prepareTrackResize(presentation, flow, boundary, fraction) {
5
6
  const vertical = flow?.composition?.mode === 'column';
6
7
  const tracks = vertical ? flow?.rows : flow?.columns;
7
8
  if (!Array.isArray(tracks) || tracks.length < 2 || tracks.length > 12)
@@ -12,7 +13,7 @@ export function prepareTrackResize(document, flow, boundary, fraction) {
12
13
  throw new RangeError('Track boundary is out of range.');
13
14
  if (!Number.isFinite(fraction) || fraction < 0 || fraction > 1)
14
15
  throw new RangeError('Track fraction must be between zero and one.');
15
- const container = getValueAtPath(document,flow.path);
16
+ const container = getValueAtPath(presentation,flow.path);
16
17
  if (!container || typeof container !== 'object') throw new Error('The layout container no longer exists.');
17
18
  const effective = flow.composition.weights ?? [];
18
19
  const weights = Array.from({length:Math.max(tracks.length,effective.length)},(_,index)=>effective[index]??1);
@@ -25,10 +26,10 @@ export function prepareTrackResize(document, flow, boundary, fraction) {
25
26
  const composition={...container.composition,mode:flow.composition.mode??'auto',weights};
26
27
  // A drag expresses an explicit arrangement. Preserve the automatic engine's chosen columns.
27
28
  if(composition.mode==='auto'){composition.mode='grid';composition.columns=flow.columns.length;}
28
- const patches=[{op:'test',path:opfPathToJsonPointer(flow.path),value:structuredClone(container)},...createValuePatch(document,[...flow.path.split('.'),'composition'],composition)];
29
- const next=applyJsonPatch(document,patches),validation=validateOpfDocument(next);
30
- if(!validation.valid)throw new Error(validation.errors[0]?.message??'Track resize is not valid OPF.');
31
- return {document:next,patches,composition,fraction:share};
29
+ const patches=[{op:'test',path:opfPathToJsonPointer(flow.path),value:structuredClone(container)},...createValuePatch(presentation,[...flow.path.split('.'),'composition'],composition)];
30
+ const next=applyJsonPatch(presentation,patches),validation=checkFormat(next);
31
+ if(!validation.valid)throw new Error(firstErrorMessage(validation, 'Track resize is not valid OPF.'));
32
+ return {presentation:next,patches,composition,fraction:share};
32
33
  }
33
34
 
34
35
  export {prepareBlockMove,listBlockContainers,prepareBlockInsert,prepareBlockDuplicate,prepareBlockRemove,prepareBlockReplace,createContentBlock} from './blocks.js';
@@ -8,7 +8,6 @@ import {
8
8
  NUMBERING_STYLE_OPTIONS,
9
9
  NUMBERING_SUFFIX_OPTIONS,
10
10
  findNumberableLists,
11
- numberingAvailable,
12
11
  numberingState,
13
12
  numberingValue,
14
13
  setEntryStart,
@@ -36,7 +35,6 @@ function h(doc, tag, attributes = {}, ...children) {
36
35
  * `onStatus(message, {error})`.
37
36
  */
38
37
  export function createNumberingPanel(container, options) {
39
- if (!numberingAvailable()) throw new Error("The numbering control needs a core release that ships numbering.");
40
38
  const { editor, getTarget, getSlideIndex, onStatus } = options;
41
39
  const doc = container.ownerDocument;
42
40
  const id = `opf-numbering-${++panelCounter}`;
@@ -58,9 +56,9 @@ export function createNumberingPanel(container, options) {
58
56
 
59
57
  const currentPath = () => {
60
58
  const slide = getSlideIndex?.() ?? 0;
61
- const lists = findNumberableLists(editor.document, slide);
59
+ const lists = findNumberableLists(editor.presentation, slide);
62
60
  const wanted = explicit ?? getTarget?.();
63
- if (wanted && numberingState(editor.document, wanted)) return { path: wanted, lists };
61
+ if (wanted && numberingState(editor.presentation, wanted)) return { path: wanted, lists };
64
62
  return { path: lists[0]?.path, lists };
65
63
  };
66
64
 
@@ -100,7 +98,7 @@ export function createNumberingPanel(container, options) {
100
98
  function draw(force = false) {
101
99
  if (destroyed) return;
102
100
  const { path, lists } = currentPath();
103
- const state = path ? numberingState(editor.document, path) : undefined;
101
+ const state = path ? numberingState(editor.presentation, path) : undefined;
104
102
  const signature = JSON.stringify([path, state, lists.map((entry) => [entry.path, entry.numbered])]);
105
103
  if (!force && signature === drawn) return;
106
104
  drawn = signature;
@@ -12,8 +12,6 @@ export declare const NUMBERING_SUFFIX_OPTIONS: readonly { value: NumberingSuffix
12
12
  export declare const MAX_NUMBERING_LEVELS: 9;
13
13
  export declare const MAX_NUMBERING_START: 32767;
14
14
 
15
- /** True when the core this editor runs on composes numbered lists. */
16
- export declare function numberingAvailable(): boolean;
17
15
 
18
16
  export interface ListPayloadLocation {
19
17
  /** The payload that owns the list ("" for a document-level path is not possible; a slide, region or block). */
@@ -26,11 +24,11 @@ export interface ListPayloadLocation {
26
24
  entry?: number;
27
25
  }
28
26
  /** Where a list lives for a path that points at it or into it (`slides.0.items`, `slides.0.items.2`, `slides.0.left.items.1.text`). */
29
- export declare function listPayloadAt(document: unknown, path: string): ListPayloadLocation | undefined;
27
+ export declare function listPayloadAt(presentation: unknown, path: string): ListPayloadLocation | undefined;
30
28
 
31
29
  export interface NumberableList { path: string; payloadPath: string; field: "items" | "bullets"; label: string; count: number; numbered: boolean }
32
30
  /** Every list of a slide: path, label, entry count and whether it is numbered. */
33
- export declare function findNumberableLists(document: unknown, slideIndex: number): NumberableList[];
31
+ export declare function findNumberableLists(presentation: unknown, slideIndex: number): NumberableList[];
34
32
 
35
33
  /** The shortest `numbering` value for per-level settings (a style name, an object of the non-default fields, or an array). */
36
34
  export declare function numberingValue(levels: readonly Partial<NumberingLevel>[]): NumberingValue;
@@ -54,7 +52,7 @@ export interface NumberingState {
54
52
  /** The entry the path selects, with its own `start` and the marker it draws. */
55
53
  entry?: { index: number; start?: number; marker?: string };
56
54
  }
57
- export declare function numberingState(document: unknown, path: string): NumberingState | undefined;
55
+ export declare function numberingState(presentation: unknown, path: string): NumberingState | undefined;
58
56
 
59
57
  /** Number a list, change its numbering, or (with `undefined` or `null`) turn it off, as one undoable validated edit. Turning it off also removes the entry `start` values. */
60
58
  export declare function setNumbering(editor: EditorSession, path: string, value: NumberingValue | undefined | null, meta?: Record<string, unknown>): unknown;
package/dist/numbering.js CHANGED
@@ -1,9 +1,8 @@
1
1
  // List numbering (RR-33): the headless model behind the numbering control. A list payload (`items` or `bullets`) can
2
2
  // carry `numbering`: a style name, a { style, start, suffix } object, or an array with one entry per list level. Every
3
3
  // write goes through the session as a validated JSON Patch edit, so each change is one undoable edit. Importing this
4
- // module needs no DOM. Core is read from the namespace so an older core still loads this module; the writes then
5
- // throw "numbering-unavailable".
6
- import * as core from "@openpresentation/opf";
4
+ // module needs no DOM.
5
+ import { listNumbers } from "@openpresentation/opf/composition";
7
6
  import { OPFEditorError, getValueAtPath, opfPathToJsonPointer, splitOpfPath } from "./index.js";
8
7
 
9
8
  /** The number styles and how each draws, in the order a picker lists them. */
@@ -41,21 +40,12 @@ function checkEditor(editor) {
41
40
  }
42
41
  }
43
42
 
44
- /** True when the core this editor runs on composes numbered lists (the core that ships `numbering`). */
45
- export function numberingAvailable() {
46
- return typeof core.listNumbers === "function" && typeof core.formatListNumber === "function";
47
- }
48
-
49
- function requireCore() {
50
- if (!numberingAvailable()) throw fail("numbering-unavailable", "List numbering needs a core release that ships numbering (@openpresentation/opf after 0.11.4).");
51
- }
52
-
53
43
  /**
54
44
  * Where a list lives for a path that points at it or into it: `slides.0.items`, `slides.0.items.2` or
55
45
  * `slides.0.left.items.1.text` all resolve to the payload that owns the list. Returns undefined when the path is not
56
46
  * inside a list.
57
47
  */
58
- export function listPayloadAt(document, path) {
48
+ export function listPayloadAt(presentation, path) {
59
49
  let segments;
60
50
  try {
61
51
  segments = splitOpfPath(path);
@@ -67,7 +57,7 @@ export function listPayloadAt(document, path) {
67
57
  const next = segments[index + 1];
68
58
  if (next !== undefined && !/^\d+$/.test(next)) continue;
69
59
  const payloadPath = segments.slice(0, index);
70
- const payload = payloadPath.length ? getValueAtPath(document, payloadPath) : document;
60
+ const payload = payloadPath.length ? getValueAtPath(presentation, payloadPath) : presentation;
71
61
  if (!isObject(payload) || !Array.isArray(payload[segments[index]])) continue;
72
62
  const entry = next === undefined ? undefined : Number(next);
73
63
  return { payloadPath: payloadPath.join("."), field: segments[index], path: [...payloadPath, segments[index]].join("."), payload, entry };
@@ -78,8 +68,8 @@ export function listPayloadAt(document, path) {
78
68
  const isRegionKey = (key) => /^(?:(?:top|middle|bottom)(?:\+(?:top|middle|bottom))*(?::(?:left|center|right)(?:\+(?:left|center|right))*)?|(?:left|center|right)(?:\+(?:left|center|right))*)$/.test(key);
79
69
 
80
70
  /** Every list (`items` or `bullets`) of a slide: its path, label, entry count and whether it is numbered. */
81
- export function findNumberableLists(document, slideIndex) {
82
- const slide = document?.slides?.[slideIndex];
71
+ export function findNumberableLists(presentation, slideIndex) {
72
+ const slide = presentation?.slides?.[slideIndex];
83
73
  if (!isObject(slide)) return [];
84
74
  const found = [];
85
75
  const visit = (payload, base, label, depth = 0) => {
@@ -137,8 +127,8 @@ const maxLevelOf = (items) => items.reduce((max, item) => Math.max(max, isObject
137
127
  * numbering applies to every level), the number of levels the list uses, the markers it draws and which entry (if any)
138
128
  * the path selects with that entry's own `start`.
139
129
  */
140
- export function numberingState(document, path) {
141
- const target = listPayloadAt(document, path);
130
+ export function numberingState(presentation, path) {
131
+ const target = listPayloadAt(presentation, path);
142
132
  if (!target) return undefined;
143
133
  const { payload, field, entry } = target;
144
134
  const items = payload[field];
@@ -147,9 +137,9 @@ export function numberingState(document, path) {
147
137
  const levels = numbered ? resolveLevels(numbering) : [{ ...DEFAULT_LEVEL }];
148
138
  const depth = Math.min(MAX_NUMBERING_LEVELS, maxLevelOf(items) + 1);
149
139
  let markers = [];
150
- if (numbered && numberingAvailable()) {
140
+ if (numbered) {
151
141
  try {
152
- markers = core.listNumbers(items, numbering).map((number) => ({ index: number.index, level: number.level, text: number.text, value: number.value, adapted: number.adapted }));
142
+ markers = listNumbers(items, numbering).map((number) => ({ index: number.index, level: number.level, text: number.text, value: number.value, adapted: number.adapted }));
153
143
  } catch {
154
144
  markers = [];
155
145
  }
@@ -180,14 +170,13 @@ function numberingPointer(target) {
180
170
  * the entry `start` values, which mean nothing without it. `path` addresses the list or anything inside it.
181
171
  */
182
172
  export function setNumbering(editor, path, value, meta = {}) {
183
- requireCore();
184
173
  checkEditor(editor);
185
- const target = listPayloadAt(editor.document, path);
174
+ const target = listPayloadAt(editor.presentation, path);
186
175
  if (!target) throw fail("not-a-list", "Select a list (items or bullets) to number.", { path });
187
176
  const patches = [];
188
177
  const present = Object.hasOwn(target.payload, "numbering");
189
178
  if (value === undefined || value === null) {
190
- if (!present) return { document: editor.document, patches: [], inversePatches: [], validation: editor.validation };
179
+ if (!present) return { presentation: editor.presentation, patches: [], inversePatches: [], validation: editor.validation };
191
180
  patches.push({ op: "remove", path: numberingPointer(target) });
192
181
  target.payload[target.field].forEach((item, index) => {
193
182
  if (isObject(item) && item.start !== undefined) patches.push({ op: "remove", path: opfPathToJsonPointer(`${target.path}.${index}.start`) });
@@ -203,9 +192,8 @@ export function setNumbering(editor, path, value, meta = {}) {
203
192
  * removes the restart. A plain entry (a string or runs) becomes the object form to carry it. One undoable validated edit.
204
193
  */
205
194
  export function setEntryStart(editor, itemPath, start, meta = {}) {
206
- requireCore();
207
195
  checkEditor(editor);
208
- const target = listPayloadAt(editor.document, itemPath);
196
+ const target = listPayloadAt(editor.presentation, itemPath);
209
197
  if (!target || target.entry === undefined) throw fail("not-an-entry", "Select a list entry to restart its numbering.", { path: itemPath });
210
198
  if (target.payload.numbering === undefined) throw fail("not-numbered", "Number the list first; an entry start has no effect on a bulleted list.", { path: itemPath });
211
199
  const item = target.payload[target.field][target.entry];
@@ -218,6 +206,6 @@ export function setEntryStart(editor, itemPath, start, meta = {}) {
218
206
  if (isObject(item)) patches.push({ op: item.start === undefined ? "add" : "replace", path: `${pointer}/start`, value: start });
219
207
  else patches.push({ op: "replace", path: pointer, value: { text: structuredClone(item), start } });
220
208
  }
221
- if (!patches.length) return { document: editor.document, patches: [], inversePatches: [], validation: editor.validation };
209
+ if (!patches.length) return { presentation: editor.presentation, patches: [], inversePatches: [], validation: editor.validation };
222
210
  return editor.applyPatch(patches, { source: "list-numbering-start", rejectInvalid: true, ...meta });
223
211
  }
@@ -92,9 +92,9 @@ export function createOutlineView(container, options) {
92
92
  const firstDraw = !lastSnapshot;
93
93
  if (snapshot === lastSnapshot && !pendingFocus) return;
94
94
  lastSnapshot = snapshot;
95
- const document = editor.document;
96
- rows = readOutline(document).rows;
97
- const sections = hasSections(document);
95
+ const presentation = editor.presentation;
96
+ rows = readOutline(presentation).rows;
97
+ const sections = hasSections(presentation);
98
98
  const entries = [];
99
99
  let lastSection;
100
100
  for (const row of rows) {
@@ -143,18 +143,18 @@ export function createOutlineView(container, options) {
143
143
  function activeRow() { return activeKey ? rowByKey(activeKey) : undefined; }
144
144
  function updateTools() {
145
145
  const row = activeRow();
146
- const document = editor.document;
146
+ const presentation = editor.presentation;
147
147
  const can = { promote: false, demote: false, up: false, down: false, add: Boolean(row) };
148
148
  if (row) {
149
149
  try {
150
150
  if (row.kind === "item") {
151
- can.demote = prepareShiftOutlineItem(document, row, 1).changed;
152
- can.promote = row.level > 1 ? prepareShiftOutlineItem(document, row, -1).changed : row.editable;
153
- can.up = prepareMoveOutlineItem(document, row, -1).changed;
154
- can.down = prepareMoveOutlineItem(document, row, 1).changed;
151
+ can.demote = prepareShiftOutlineItem(presentation, row, 1).changed;
152
+ can.promote = row.level > 1 ? prepareShiftOutlineItem(presentation, row, -1).changed : row.editable;
153
+ can.up = prepareMoveOutlineItem(presentation, row, -1).changed;
154
+ can.down = prepareMoveOutlineItem(presentation, row, 1).changed;
155
155
  } else if (row.kind === "slide") {
156
156
  can.up = row.slideIndex > 0;
157
- can.down = row.slideIndex < document.slides.length - 1;
157
+ can.down = row.slideIndex < presentation.slides.length - 1;
158
158
  can.demote = row.slideIndex > 0;
159
159
  }
160
160
  } catch { /* a row that vanished mid-edit leaves the tools off */ }
@@ -167,7 +167,7 @@ export function createOutlineView(container, options) {
167
167
  if (!row?.editable) return false;
168
168
  if (input.value === row.text) return false;
169
169
  try {
170
- const prepared = prepareSetOutlineText(editor.document, row, input.value);
170
+ const prepared = prepareSetOutlineText(editor.presentation, row, input.value);
171
171
  if (!prepared.changed) return false;
172
172
  applyOutlineChange(editor, prepared);
173
173
  render();
@@ -203,25 +203,25 @@ export function createOutlineView(container, options) {
203
203
  const key = activeKey;
204
204
  const row = settle(input ?? inputFor(key), key);
205
205
  if (!row) return;
206
- const document = editor.document;
206
+ const presentation = editor.presentation;
207
207
  if (action === "add") {
208
- if (row.kind === "item") apply(() => prepareInsertOutlineItem(document, row), "Line added");
209
- else if (row.kind === "slide") apply(() => prepareAddOutlineBullet(document, row.slideIndex), "Bullet added under the slide title");
208
+ if (row.kind === "item") apply(() => prepareInsertOutlineItem(presentation, row), "Line added");
209
+ else if (row.kind === "slide") apply(() => prepareAddOutlineBullet(presentation, row.slideIndex), "Bullet added under the slide title");
210
210
  else announce("Add a line from a title or a bullet");
211
211
  } else if (action === "demote") {
212
- if (row.kind === "item") apply(() => prepareShiftOutlineItem(document, row, 1), () => "Demoted");
213
- else if (row.kind === "slide") apply(() => prepareOutlineDemoteSlide(document, row.slideIndex), "Slide joined the slide before it as a bullet");
212
+ if (row.kind === "item") apply(() => prepareShiftOutlineItem(presentation, row, 1), () => "Demoted");
213
+ else if (row.kind === "slide") apply(() => prepareOutlineDemoteSlide(presentation, row.slideIndex), "Slide joined the slide before it as a bullet");
214
214
  else announce("Only titles and bullets change level");
215
215
  } else if (action === "promote") {
216
216
  if (row.kind === "item") {
217
- if (row.level > 1) apply(() => prepareShiftOutlineItem(document, row, -1), "Promoted");
218
- else apply(() => prepareOutlinePromote(document, row), (prepared) => `Promoted to slide ${prepared.newSlideIndex + 1}`);
217
+ if (row.level > 1) apply(() => prepareShiftOutlineItem(presentation, row, -1), "Promoted");
218
+ else apply(() => prepareOutlinePromote(presentation, row), (prepared) => `Promoted to slide ${prepared.newSlideIndex + 1}`);
219
219
  } else if (row.kind === "slide") announce("Slide titles are already the top level");
220
220
  else announce("Only titles and bullets change level");
221
221
  } else if (action === "up" || action === "down") {
222
222
  const delta = action === "up" ? -1 : 1;
223
- if (row.kind === "item") apply(() => prepareMoveOutlineItem(document, row, delta), `Moved ${action}`);
224
- else if (row.kind === "slide") apply(() => prepareMoveOutlineSlide(document, row.slideIndex, delta), (prepared) => `Slide moved to position ${prepared.selection[0] + 1} of ${document.slides.length}`);
223
+ if (row.kind === "item") apply(() => prepareMoveOutlineItem(presentation, row, delta), `Moved ${action}`);
224
+ else if (row.kind === "slide") apply(() => prepareMoveOutlineSlide(presentation, row.slideIndex, delta), (prepared) => `Slide moved to position ${prepared.selection[0] + 1} of ${presentation.slides.length}`);
225
225
  else announce("Move bullets or slides");
226
226
  }
227
227
  }
@@ -244,18 +244,18 @@ export function createOutlineView(container, options) {
244
244
  event.preventDefault();
245
245
  const settled = settle(input, key);
246
246
  if (!settled) return;
247
- const document = editor.document;
248
- if (settled.kind === "item") apply(() => prepareInsertOutlineItem(document, settled), "Line added");
249
- else if (settled.kind === "slide") apply(() => prepareInsertOutlineSlide(document, settled.slideIndex), (prepared) => `Slide ${prepared.index + 1} added`);
247
+ const presentation = editor.presentation;
248
+ if (settled.kind === "item") apply(() => prepareInsertOutlineItem(presentation, settled), "Line added");
249
+ else if (settled.kind === "slide") apply(() => prepareInsertOutlineSlide(presentation, settled.slideIndex), (prepared) => `Slide ${prepared.index + 1} added`);
250
250
  else focusAt(index + 1);
251
251
  return;
252
252
  }
253
253
  if (event.key === "Backspace" && input.value === "" && input.selectionStart === 0 && !input.readOnly) {
254
- if (row.kind === "item") { event.preventDefault(); apply(() => prepareRemoveOutlineItem(editor.document, row), "Line removed"); }
254
+ if (row.kind === "item") { event.preventDefault(); apply(() => prepareRemoveOutlineItem(editor.presentation, row), "Line removed"); }
255
255
  else if (row.kind === "slide" && !rows.some((other) => other.slideIndex === row.slideIndex && other.kind !== "slide")) {
256
256
  event.preventDefault();
257
257
  apply(() => {
258
- const prepared = prepareRemoveSlides(editor.document, [row.slideIndex]);
258
+ const prepared = prepareRemoveSlides(editor.presentation, [row.slideIndex]);
259
259
  return { ...prepared, focus: `slide:slides.${Math.max(0, row.slideIndex - 1)}` };
260
260
  }, `Slide ${row.slideIndex + 1} removed`);
261
261
  }
package/dist/outline.d.ts CHANGED
@@ -35,22 +35,22 @@ export interface PreparedOutlineChange extends Omit<PreparedSlideChange, "select
35
35
  reason?: string;
36
36
  newSlideIndex?: number;
37
37
  }
38
- export type OutlineChange = Omit<EditorChange, "document" | "patches"> & Omit<PreparedOutlineChange, "document" | "patches"> & { document: unknown; patches: JsonPatchOperation[] };
38
+ export type OutlineChange = Omit<EditorChange, "presentation" | "patches"> & Omit<PreparedOutlineChange, "presentation" | "patches"> & { presentation: unknown; patches: JsonPatchOperation[] };
39
39
 
40
- export declare function readOutline(document: unknown): Outline;
41
- export declare function prepareSetOutlineText(document: unknown, row: OutlineRow, text: string): PreparedOutlineChange;
40
+ export declare function readOutline(presentation: unknown): Outline;
41
+ export declare function prepareSetOutlineText(presentation: unknown, row: OutlineRow, text: string): PreparedOutlineChange;
42
42
  export declare function setOutlineText(editor: EditorSession, row: OutlineRow, text: string, meta?: Record<string, unknown>): OutlineChange;
43
43
  /** Nest (`delta` 1) or un-nest (-1) a list item with the items under it. A top-level item reports `changed: false` and a `reason`. */
44
- export declare function prepareShiftOutlineItem(document: unknown, row: OutlineRow, delta: 1 | -1): PreparedOutlineChange;
45
- export declare function prepareMoveOutlineItem(document: unknown, row: OutlineRow, delta: 1 | -1): PreparedOutlineChange;
46
- export declare function prepareInsertOutlineItem(document: unknown, row: OutlineRow, text?: string): PreparedOutlineChange;
47
- export declare function prepareAddOutlineBullet(document: unknown, slideIndex: number, text?: string): PreparedOutlineChange;
48
- export declare function prepareRemoveOutlineItem(document: unknown, row: OutlineRow): PreparedOutlineChange;
44
+ export declare function prepareShiftOutlineItem(presentation: unknown, row: OutlineRow, delta: 1 | -1): PreparedOutlineChange;
45
+ export declare function prepareMoveOutlineItem(presentation: unknown, row: OutlineRow, delta: 1 | -1): PreparedOutlineChange;
46
+ export declare function prepareInsertOutlineItem(presentation: unknown, row: OutlineRow, text?: string): PreparedOutlineChange;
47
+ export declare function prepareAddOutlineBullet(presentation: unknown, slideIndex: number, text?: string): PreparedOutlineChange;
48
+ export declare function prepareRemoveOutlineItem(presentation: unknown, row: OutlineRow): PreparedOutlineChange;
49
49
  /** A top-level list item becomes the title of a new slide that takes the items after it. */
50
- export declare function prepareOutlinePromote(document: unknown, row: OutlineRow): PreparedOutlineChange;
50
+ export declare function prepareOutlinePromote(presentation: unknown, row: OutlineRow): PreparedOutlineChange;
51
51
  /** A plain slide becomes a bullet (with its text nested) of the slide before it. Throws `outline-row-not-supported` rather than dropping content. */
52
- export declare function prepareOutlineDemoteSlide(document: unknown, slideIndex: number): PreparedOutlineChange;
53
- export declare function prepareMoveOutlineSlide(document: unknown, slideIndex: number, delta: number): PreparedOutlineChange;
54
- export declare function prepareInsertOutlineSlide(document: unknown, slideIndex: number): PreparedOutlineChange;
52
+ export declare function prepareOutlineDemoteSlide(presentation: unknown, slideIndex: number): PreparedOutlineChange;
53
+ export declare function prepareMoveOutlineSlide(presentation: unknown, slideIndex: number, delta: number): PreparedOutlineChange;
54
+ export declare function prepareInsertOutlineSlide(presentation: unknown, slideIndex: number): PreparedOutlineChange;
55
55
  /** Apply any prepared outline change as one undoable step. */
56
56
  export declare function applyOutlineChange(editor: EditorSession, prepared: PreparedOutlineChange | PreparedSlideChange, meta?: Record<string, unknown>): OutlineChange;
package/dist/outline.js CHANGED
@@ -9,10 +9,11 @@
9
9
  // item one list item or bullet; `listPath` and `itemIndex` locate it and `level` is 1 plus its nesting level
10
10
  // other content that is not text, shown by its kind (`label`), never edited here
11
11
  // Text that carries formatting (a run array) is shown flattened and is not editable in the outline, so nothing is flattened away.
12
- import { getValueAtPath, opfPathToJsonPointer, splitOpfPath, validateOpfDocument } from "./index.js";
12
+ import { getValueAtPath, opfPathToJsonPointer, splitOpfPath } from "./index.js";
13
13
  import { checkedDocument, fail } from "./edit-helpers.js";
14
14
  import { visitContentPayloads } from "./presentation-ids.js";
15
15
  import { prepareAddSlide, prepareMoveSlidesBy } from "./slides.js";
16
+ import { checkFormat } from "./checks.js";
16
17
 
17
18
  const clone = (value) => structuredClone(value);
18
19
  const isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -46,14 +47,15 @@ function walkPayload(node, path, slideIndex, rows) {
46
47
  * Read the deck as outline rows (see the file header). `rows` follow document order: for each slide its title, subtitle, the
47
48
  * slide's own text and list, then every block and region in order.
48
49
  */
49
- export function readOutline(document) {
50
- if (!document || !Array.isArray(document.slides)) throw fail("invalid-input", "The outline needs an OPF document with a slides array.");
50
+ export function readOutline(presentation) {
51
+ if (!presentation || !Array.isArray(presentation.slides)) throw fail("invalid-input", "The outline needs an OPF document with a slides array.");
51
52
  const rows = [];
52
- document.slides.forEach((slide, slideIndex) => {
53
+ presentation.slides.forEach((slide, slideIndex) => {
53
54
  const base = `slides.${slideIndex}`;
54
- const title = typeof slide?.title === "string" ? slide.title : "";
55
+ // FA-10: a title or subtitle may be TextRun[]: the row shows its plain text and is edited as runs elsewhere (a text row writes a string).
56
+ const title = flatten(slide?.title);
55
57
  rows.push({ key: `slide:${base}`, kind: "slide", slideIndex, level: 0, text: title, path: `${base}.title`, editable: slide?.title === undefined || typeof slide.title === "string", hidden: slide?.hidden === true, section: typeof slide?.section === "string" ? slide.section : undefined, id: slide?.id });
56
- if (typeof slide?.subtitle === "string") rows.push({ key: `subtitle:${base}.subtitle`, kind: "subtitle", slideIndex, level: 1, text: slide.subtitle, path: `${base}.subtitle`, editable: true });
58
+ if (typeof slide?.subtitle === "string" || Array.isArray(slide?.subtitle)) rows.push({ key: `subtitle:${base}.subtitle`, kind: "subtitle", slideIndex, level: 1, text: flatten(slide.subtitle), path: `${base}.subtitle`, editable: typeof slide.subtitle === "string" });
57
59
  if (!isObject(slide)) return;
58
60
  walkPayload(slide, base, slideIndex, rows);
59
61
  visitContentPayloads(slide, base, (payload, path) => walkPayload(payload, path, slideIndex, rows));
@@ -64,23 +66,23 @@ export function readOutline(document) {
64
66
  // --- text ------------------------------------------------------------------------------------------
65
67
 
66
68
  /** Compute editing the text of a row (a title, subtitle, text paragraph or list item); a row that is not plain text throws. */
67
- export function prepareSetOutlineText(document, row, text) {
69
+ export function prepareSetOutlineText(presentation, row, text) {
68
70
  if (!row?.editable) throw fail("outline-row-not-editable", "This text carries formatting or is not text. Edit it on the slide.", { path: row?.path });
69
71
  const value = String(text);
70
- const current = getValueAtPath(document, splitOpfPath(row.path));
71
- if (current === value || (current === undefined && value === "")) return { document: clone(document), patches: [], changed: false, selection: [row.slideIndex] };
72
+ const current = getValueAtPath(presentation, splitOpfPath(row.path));
73
+ if (current === value || (current === undefined && value === "")) return { presentation: clone(presentation), patches: [], changed: false, selection: [row.slideIndex] };
72
74
  const pointer = opfPathToJsonPointer(row.path);
73
75
  const patches = [{ op: current === undefined ? "add" : "replace", path: pointer, value }];
74
- return finish(document, patches, { action: "outline-text", selection: [row.slideIndex] });
76
+ return finish(presentation, patches, { action: "outline-text", selection: [row.slideIndex] });
75
77
  }
76
78
  export function setOutlineText(editor, row, text, meta) {
77
- return apply(editor, prepareSetOutlineText(editor.document, row, text), meta, "outline-text");
79
+ return apply(editor, prepareSetOutlineText(editor.presentation, row, text), meta, "outline-text");
78
80
  }
79
81
 
80
82
  // --- list levels -----------------------------------------------------------------------------------
81
83
 
82
- function listAt(document, row) {
83
- const list = getValueAtPath(document, splitOpfPath(row.listPath));
84
+ function listAt(presentation, row) {
85
+ const list = getValueAtPath(presentation, splitOpfPath(row.listPath));
84
86
  if (!Array.isArray(list) || list[row.itemIndex] === undefined) throw fail("outline-row-not-found", "That list item is no longer there.", { path: row.listPath });
85
87
  return list;
86
88
  }
@@ -104,17 +106,17 @@ function blockEnd(levels, index) {
104
106
  * than the item above, and the first item cannot be nested. Un-nesting an item that is already at the top level is
105
107
  * `prepareOutlinePromote`'s job (it becomes a slide); here it reports `changed: false` with a `reason`.
106
108
  */
107
- export function prepareShiftOutlineItem(document, row, delta) {
108
- const list = listAt(document, row);
109
+ export function prepareShiftOutlineItem(presentation, row, delta) {
110
+ const list = listAt(presentation, row);
109
111
  const levels = list.map((item) => itemParts(item).level);
110
112
  const index = row.itemIndex;
111
- const none = (reason) => ({ document: clone(document), patches: [], changed: false, reason, selection: [row.slideIndex] });
113
+ const none = (reason) => ({ presentation: clone(presentation), patches: [], changed: false, reason, selection: [row.slideIndex] });
112
114
  if (delta > 0 && (index === 0 || levels[index] > levels[index - 1])) return none(index === 0 ? "The first item cannot be nested." : "This item is already one level in from the item above.");
113
115
  if (delta < 0 && levels[index] === 0) return none("This item is already at the top level.");
114
116
  const end = blockEnd(levels, index);
115
117
  const next = list.map((item, at) => (at >= index && at < end ? withLevel(item, Math.max(0, levels[at] + delta)) : item));
116
118
  const patches = [{ op: "replace", path: opfPathToJsonPointer(row.listPath), value: next }];
117
- return finish(document, patches, { action: delta > 0 ? "outline-demote" : "outline-promote", selection: [row.slideIndex], focus: `item:${row.listPath}.${index}` });
119
+ return finish(presentation, patches, { action: delta > 0 ? "outline-demote" : "outline-promote", selection: [row.slideIndex], focus: `item:${row.listPath}.${index}` });
118
120
  }
119
121
 
120
122
  // --- item order ------------------------------------------------------------------------------------
@@ -123,11 +125,11 @@ export function prepareShiftOutlineItem(document, row, delta) {
123
125
  * Compute moving a list item (with the items nested under it) one place up (`delta` -1) or down (1) among the items at its level
124
126
  * and under the same parent. At the first or last place nothing changes (`changed` is false).
125
127
  */
126
- export function prepareMoveOutlineItem(document, row, delta) {
127
- const list = listAt(document, row);
128
+ export function prepareMoveOutlineItem(presentation, row, delta) {
129
+ const list = listAt(presentation, row);
128
130
  const levels = list.map((item) => itemParts(item).level);
129
131
  const index = row.itemIndex, end = blockEnd(levels, index);
130
- const none = { document: clone(document), patches: [], changed: false, selection: [row.slideIndex] };
132
+ const none = { presentation: clone(presentation), patches: [], changed: false, selection: [row.slideIndex] };
131
133
  let order, focusIndex;
132
134
  if (delta < 0) {
133
135
  // The previous sibling starts at the nearest earlier item on the same level, provided no shallower item lies between.
@@ -143,32 +145,32 @@ export function prepareMoveOutlineItem(document, row, delta) {
143
145
  focusIndex = index + (siblingEnd - end);
144
146
  }
145
147
  const patches = [{ op: "replace", path: opfPathToJsonPointer(row.listPath), value: order }];
146
- return finish(document, patches, { action: "outline-move", selection: [row.slideIndex], focus: `item:${row.listPath}.${focusIndex}` });
148
+ return finish(presentation, patches, { action: "outline-move", selection: [row.slideIndex], focus: `item:${row.listPath}.${focusIndex}` });
147
149
  }
148
150
 
149
151
  // --- insert and delete -----------------------------------------------------------------------------
150
152
 
151
153
  /** Compute adding an empty list item right after the item at `row` and everything nested under it, at the same level. */
152
- export function prepareInsertOutlineItem(document, row, text = "") {
153
- const list = listAt(document, row);
154
+ export function prepareInsertOutlineItem(presentation, row, text = "") {
155
+ const list = listAt(presentation, row);
154
156
  const levels = list.map((item) => itemParts(item).level);
155
157
  const end = blockEnd(levels, row.itemIndex);
156
158
  const item = levels[row.itemIndex] > 0 ? { text, level: levels[row.itemIndex] } : text;
157
- return finish(document, [{ op: "add", path: `${opfPathToJsonPointer(row.listPath)}/${end}`, value: item }], { action: "outline-insert", selection: [row.slideIndex], focus: `item:${row.listPath}.${end}` });
159
+ return finish(presentation, [{ op: "add", path: `${opfPathToJsonPointer(row.listPath)}/${end}`, value: item }], { action: "outline-insert", selection: [row.slideIndex], focus: `item:${row.listPath}.${end}` });
158
160
  }
159
161
  /** Compute adding the first bullet under a slide: it goes in the slide's own list, or creates one. */
160
- export function prepareAddOutlineBullet(document, slideIndex, text = "") {
161
- const slide = document.slides?.[slideIndex];
162
+ export function prepareAddOutlineBullet(presentation, slideIndex, text = "") {
163
+ const slide = presentation.slides?.[slideIndex];
162
164
  if (!isObject(slide)) throw fail("slide-index-out-of-range", `Slide ${slideIndex} does not exist.`, { slideIndex });
163
165
  const key = LIST_KEYS.find((candidate) => Array.isArray(slide[candidate]));
164
166
  const base = `/slides/${slideIndex}`;
165
- if (key) return finish(document, [{ op: "add", path: `${base}/${key}/${slide[key].length}`, value: text }], { action: "outline-insert", selection: [slideIndex], focus: `item:slides.${slideIndex}.${key}.${slide[key].length}` });
167
+ if (key) return finish(presentation, [{ op: "add", path: `${base}/${key}/${slide[key].length}`, value: text }], { action: "outline-insert", selection: [slideIndex], focus: `item:slides.${slideIndex}.${key}.${slide[key].length}` });
166
168
  if (Array.isArray(slide.blocks) || slide.type !== undefined) throw fail("outline-row-not-supported", "Add a list to this slide from the Content panel; its content is arranged in blocks.", { slideIndex });
167
- return finish(document, [{ op: "add", path: `${base}/items`, value: [text] }], { action: "outline-insert", selection: [slideIndex], focus: `item:slides.${slideIndex}.items.0` });
169
+ return finish(presentation, [{ op: "add", path: `${base}/items`, value: [text] }], { action: "outline-insert", selection: [slideIndex], focus: `item:slides.${slideIndex}.items.0` });
168
170
  }
169
171
  /** Compute deleting a list item together with the items nested under it. */
170
- export function prepareRemoveOutlineItem(document, row) {
171
- const list = listAt(document, row);
172
+ export function prepareRemoveOutlineItem(presentation, row) {
173
+ const list = listAt(presentation, row);
172
174
  const levels = list.map((item) => itemParts(item).level);
173
175
  const end = blockEnd(levels, row.itemIndex);
174
176
  const patches = [];
@@ -178,7 +180,7 @@ export function prepareRemoveOutlineItem(document, row) {
178
180
  const removeList = survivors === 0 && row.listPath.split(".").length === 3;
179
181
  const finalPatches = removeList ? [{ op: "remove", path: opfPathToJsonPointer(row.listPath) }] : patches;
180
182
  const focus = survivors ? `item:${row.listPath}.${Math.max(0, row.itemIndex - 1)}` : `slide:slides.${row.slideIndex}`;
181
- return finish(document, finalPatches, { action: "outline-remove", selection: [row.slideIndex], focus });
183
+ return finish(presentation, finalPatches, { action: "outline-remove", selection: [row.slideIndex], focus });
182
184
  }
183
185
 
184
186
  // --- promote and demote across slides --------------------------------------------------------------
@@ -187,14 +189,14 @@ export function prepareRemoveOutlineItem(document, row) {
187
189
  * Compute promoting a top-level list item to a slide: the item becomes the title of a new slide right after its own, and the items that
188
190
  * follow it in the same list move to that slide (PowerPoint's outline does the same). Items above it stay.
189
191
  */
190
- export function prepareOutlinePromote(document, row) {
191
- const list = listAt(document, row);
192
+ export function prepareOutlinePromote(presentation, row) {
193
+ const list = listAt(presentation, row);
192
194
  const title = itemParts(list[row.itemIndex]);
193
195
  if (title.level !== 0) throw fail("outline-row-not-supported", "Only a top-level item becomes a slide.", { path: row.path });
194
196
  if (!title.plain) throw fail("outline-row-not-editable", "This item carries formatting. Edit it on the slide.", { path: row.path });
195
197
  const following = list.slice(row.itemIndex + 1);
196
- const added = prepareAddSlide(document, { at: row.slideIndex + 1, title: title.text });
197
- const slide = clone(added.document.slides[row.slideIndex + 1]);
198
+ const added = prepareAddSlide(presentation, { at: row.slideIndex + 1, title: title.text });
199
+ const slide = clone(added.presentation.slides[row.slideIndex + 1]);
198
200
  const key = row.listPath.endsWith(".bullets") ? "bullets" : "items";
199
201
  if (following.length) slide[key] = clone(following);
200
202
  const pointer = opfPathToJsonPointer(row.listPath);
@@ -204,7 +206,7 @@ export function prepareOutlinePromote(document, row) {
204
206
  ? [{ op: "remove", path: pointer }]
205
207
  : Array.from({ length: list.length - row.itemIndex }, (_, offset) => ({ op: "remove", path: `${pointer}/${list.length - 1 - offset}` }));
206
208
  const patches = [...removal, { op: "add", path: `/slides/${row.slideIndex + 1}`, value: slide }];
207
- return finish(document, patches, { action: "outline-promote-slide", selection: [row.slideIndex + 1], focus: `slide:slides.${row.slideIndex + 1}`, newSlideIndex: row.slideIndex + 1 });
209
+ return finish(presentation, patches, { action: "outline-promote-slide", selection: [row.slideIndex + 1], focus: `slide:slides.${row.slideIndex + 1}`, newSlideIndex: row.slideIndex + 1 });
208
210
  }
209
211
 
210
212
  const MERGEABLE = new Set(["id", "title", "text", "items", "bullets", "section", "hidden"]);
@@ -213,8 +215,8 @@ const MERGEABLE = new Set(["id", "title", "text", "items", "bullets", "section",
213
215
  * beneath it. Only a plain slide qualifies: one with a title and at most text or a list (no blocks, regions, notes, design or layout),
214
216
  * after a slide that has a list of its own or no body. Anything else throws `outline-row-not-supported` so nothing is dropped.
215
217
  */
216
- export function prepareOutlineDemoteSlide(document, slideIndex) {
217
- const slides = document.slides;
218
+ export function prepareOutlineDemoteSlide(presentation, slideIndex) {
219
+ const slides = presentation.slides;
218
220
  const slide = slides?.[slideIndex];
219
221
  if (!isObject(slide)) throw fail("slide-index-out-of-range", `Slide ${slideIndex} does not exist.`, { slideIndex });
220
222
  if (slideIndex === 0) throw fail("outline-row-not-supported", "The first slide has no slide before it to join.", { slideIndex });
@@ -231,36 +233,36 @@ export function prepareOutlineDemoteSlide(document, slideIndex) {
231
233
  const added = [slide.title, ...(typeof slide.text === "string" && slide.text ? [{ text: slide.text, level: 1 }] : []), ...nested];
232
234
  const base = `/slides/${slideIndex - 1}`;
233
235
  const patches = [{ op: "remove", path: `/slides/${slideIndex}` }, listKey ? { op: "replace", path: `${base}/${key}`, value: [...previous[key], ...added] } : { op: "add", path: `${base}/${key}`, value: added }];
234
- return finish(document, patches, { action: "outline-demote-slide", selection: [slideIndex - 1], focus: `item:slides.${slideIndex - 1}.${key}.${(previous[key]?.length ?? 0)}` });
236
+ return finish(presentation, patches, { action: "outline-demote-slide", selection: [slideIndex - 1], focus: `item:slides.${slideIndex - 1}.${key}.${(previous[key]?.length ?? 0)}` });
235
237
  }
236
238
 
237
239
  // --- slides ----------------------------------------------------------------------------------------
238
240
 
239
241
  /** Compute moving a slide (the whole slide: its text moves with its title) by `delta` places; see `prepareMoveSlidesBy`. */
240
- export function prepareMoveOutlineSlide(document, slideIndex, delta) {
241
- const prepared = prepareMoveSlidesBy(document, [slideIndex], delta);
242
+ export function prepareMoveOutlineSlide(presentation, slideIndex, delta) {
243
+ const prepared = prepareMoveSlidesBy(presentation, [slideIndex], delta);
242
244
  return prepared.changed ? { ...prepared, focus: `slide:slides.${prepared.selection[0]}` } : prepared;
243
245
  }
244
246
  /** Compute adding a slide right after `slideIndex`, with an empty title for the outline to type into. */
245
- export function prepareInsertOutlineSlide(document, slideIndex) {
246
- return { ...prepareAddSlide(document, { at: slideIndex + 1, title: "" }), action: "outline-insert-slide", focus: `slide:slides.${slideIndex + 1}` };
247
+ export function prepareInsertOutlineSlide(presentation, slideIndex) {
248
+ return { ...prepareAddSlide(presentation, { at: slideIndex + 1, title: "" }), action: "outline-insert-slide", focus: `slide:slides.${slideIndex + 1}` };
247
249
  }
248
250
 
249
251
  // --- apply -----------------------------------------------------------------------------------------
250
252
 
251
- function finish(document, patches, extra) {
252
- if (!patches.length) return { document: clone(document), patches: [], changed: false, ...extra };
253
- const before = validateOpfDocument(document);
254
- return { document: checkedDocument(document, patches, before), patches, changed: true, ...extra };
253
+ function finish(presentation, patches, extra) {
254
+ if (!patches.length) return { presentation: clone(presentation), patches: [], changed: false, ...extra };
255
+ const before = checkFormat(presentation);
256
+ return { presentation: checkedDocument(presentation, patches, before), patches, changed: true, ...extra };
255
257
  }
256
258
  function requireEditor(editor) {
257
259
  if (!editor || typeof editor.applyPatch !== "function") throw fail("invalid-editor", "Expected an editor session created by createEditorSession.");
258
260
  }
259
261
  function apply(editor, prepared, meta, action) {
260
262
  requireEditor(editor);
261
- const { document, patches, ...summary } = prepared;
262
- void document;
263
- if (!prepared.changed) return { ...summary, document: editor.document, patches: [], inversePatches: [], validation: editor.validation };
263
+ const { presentation, patches, ...summary } = prepared;
264
+ void presentation;
265
+ if (!prepared.changed) return { ...summary, presentation: editor.presentation, patches: [], inversePatches: [], validation: editor.validation };
264
266
  const change = editor.applyPatch(patches, { ...meta, source: meta?.source ?? "outline", action });
265
267
  return { ...change, ...summary };
266
268
  }