@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.
- package/README.md +71 -60
- package/dist/annotations.d.ts +16 -16
- package/dist/annotations.js +71 -79
- package/dist/assets.d.ts +7 -7
- package/dist/assets.js +15 -15
- package/dist/background-options.d.ts +2 -2
- package/dist/background-options.js +6 -6
- package/dist/block-controls.js +7 -7
- package/dist/block-convert.d.ts +7 -7
- package/dist/block-convert.js +22 -21
- package/dist/blocks.d.ts +8 -8
- package/dist/blocks.js +41 -40
- package/dist/canvas-fields.js +35 -12
- package/dist/canvas.d.ts +22 -30
- package/dist/canvas.js +57 -64
- package/dist/chart-data.d.ts +10 -10
- package/dist/chart-data.js +21 -21
- package/dist/chart-options-panel.d.ts +2 -2
- package/dist/chart-options-panel.js +82 -8
- package/dist/chart-options.d.ts +35 -9
- package/dist/chart-options.js +133 -40
- package/dist/checks.js +21 -0
- package/dist/color-authoring.js +1 -1
- package/dist/content-actions.d.ts +15 -15
- package/dist/content-actions.js +71 -70
- package/dist/content-controls.js +12 -12
- package/dist/data-grid.js +7 -8
- package/dist/data.d.ts +4 -4
- package/dist/data.js +5 -5
- package/dist/dataset-refs.d.ts +2 -2
- package/dist/dataset-refs.js +5 -5
- package/dist/design-controls.js +61 -44
- package/dist/design-options.d.ts +16 -14
- package/dist/design-options.js +86 -74
- package/dist/edit-helpers.js +8 -7
- package/dist/exact-source.js +5 -5
- package/dist/export.d.ts +5 -7
- package/dist/export.js +40 -14
- package/dist/find-panel.js +5 -5
- package/dist/find-replace.d.ts +3 -3
- package/dist/find-replace.js +23 -20
- package/dist/font-gate.js +49 -58
- package/dist/galleries.js +36 -52
- package/dist/grid-model.d.ts +25 -27
- package/dist/grid-model.js +92 -102
- package/dist/image-crop.d.ts +4 -4
- package/dist/image-crop.js +20 -20
- package/dist/image-cropper.js +3 -3
- package/dist/index.d.ts +28 -28
- package/dist/index.js +74 -90
- package/dist/json-options.js +17 -16
- package/dist/layout-handles.js +5 -5
- package/dist/layout-placeholders.js +4 -4
- package/dist/layout.d.ts +2 -2
- package/dist/layout.js +8 -7
- package/dist/numbering-panel.js +3 -5
- package/dist/numbering.d.ts +3 -5
- package/dist/numbering.js +14 -26
- package/dist/outline-view.js +24 -24
- package/dist/outline.d.ts +12 -12
- package/dist/outline.js +52 -50
- package/dist/persistence.d.ts +1 -1
- package/dist/persistence.js +19 -18
- package/dist/presentation-ids.js +2 -2
- package/dist/react.js +3 -3
- package/dist/review-panel.d.ts +30 -13
- package/dist/review-panel.js +154 -53
- package/dist/review.d.ts +36 -43
- package/dist/review.js +92 -53
- package/dist/rich-text-input.js +4 -3
- package/dist/rich-text-toolbar.js +5 -3
- package/dist/rich-text.js +5 -2
- package/dist/schema-inspector.d.ts +2 -2
- package/dist/schema-inspector.js +16 -11
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +25 -5
- package/dist/slide-manager.d.ts +2 -2
- package/dist/slide-manager.js +18 -18
- package/dist/slides.d.ts +20 -20
- package/dist/slides.js +99 -97
- package/dist/svelte.js +1 -1
- package/dist/switches.d.ts +10 -10
- package/dist/switches.js +124 -114
- package/dist/table-options.d.ts +8 -8
- package/dist/table-options.js +28 -27
- package/dist/table-structure.d.ts +10 -10
- package/dist/table-structure.js +21 -21
- package/dist/template-panel.d.ts +3 -3
- package/dist/template-panel.js +37 -13
- package/dist/templates.d.ts +22 -11
- package/dist/templates.js +48 -55
- package/dist/transfer.d.ts +4 -4
- package/dist/transfer.js +49 -59
- package/package.json +8 -6
- package/dist/font-defaults.js +0 -25
package/dist/slide-manager.js
CHANGED
|
@@ -52,9 +52,9 @@ const isMac = () => /Mac|iPhone|iPad/.test(globalThis.navigator?.platform ?? "")
|
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* Mount the slide list into `container` (a `nav` or `div`; one `.slide-card` button per slide, with section headers between when the deck has
|
|
55
|
-
* sections). Options: `editor`, `getSlideIndex()`, `setSlideIndex(index)`, `renderThumbnail(
|
|
55
|
+
* sections). Options: `editor`, `getSlideIndex()`, `setSlideIndex(index)`, `renderThumbnail(presentation, index)` (an HTML string), `variant`
|
|
56
56
|
* (`"navigator"` or `"sorter"`), `toolbar` (an element to fill with the action buttons), `contentActions` (the RR-26 split and merge
|
|
57
|
-
* functions: `{ splitSlideByBlocks, mergeSlides }`), `layoutOptions(
|
|
57
|
+
* functions: `{ splitSlideByBlocks, mergeSlides }`), `layoutOptions(presentation)` (layout choices for "Add slide with layout"), `onStatus(message)`
|
|
58
58
|
* `autoRender` (false to skip the first draw) and `onError(error)`. Returns `{ render, getSelection, setSelection, openLayoutPicker, destroy }`.
|
|
59
59
|
*/
|
|
60
60
|
export function createSlideManager(container, options) {
|
|
@@ -79,7 +79,7 @@ export function createSlideManager(container, options) {
|
|
|
79
79
|
: "Up and Down arrows move between slides. Shift plus arrow keys extend the selection. Alt plus Up or Down moves the selected slides. Delete removes them. Press the context menu key for more actions." });
|
|
80
80
|
container.after(hint);
|
|
81
81
|
|
|
82
|
-
const deck = () => editor.
|
|
82
|
+
const deck = () => editor.presentation;
|
|
83
83
|
const count = () => deck().slides.length;
|
|
84
84
|
const current = () => Math.max(0, Math.min(count() - 1, options.getSlideIndex()));
|
|
85
85
|
const announce = (message) => {
|
|
@@ -107,9 +107,9 @@ export function createSlideManager(container, options) {
|
|
|
107
107
|
return `${occurrence}:${section.name ?? ""}`;
|
|
108
108
|
}
|
|
109
109
|
function render() {
|
|
110
|
-
const
|
|
111
|
-
const sections = hasSections(
|
|
112
|
-
const slideCount =
|
|
110
|
+
const presentation = deck();
|
|
111
|
+
const sections = hasSections(presentation) ? listSections(presentation) : [];
|
|
112
|
+
const slideCount = presentation.slides.length;
|
|
113
113
|
selection = new Set([...selection].filter((index) => index < slideCount));
|
|
114
114
|
selection.add(current());
|
|
115
115
|
if (selection.size === 1) anchor = current();
|
|
@@ -122,19 +122,19 @@ export function createSlideManager(container, options) {
|
|
|
122
122
|
const nodes = [];
|
|
123
123
|
const sectionAtStart = new Map(sections.map((section) => [section.start, section]));
|
|
124
124
|
const sectionOfIndex = (index) => sections.find((section) => index >= section.start && index < section.start + section.count);
|
|
125
|
-
|
|
125
|
+
presentation.slides.forEach((slide, index) => {
|
|
126
126
|
const section = sectionAtStart.get(index);
|
|
127
127
|
if (section) nodes.push(sectionHeader(sections, section));
|
|
128
128
|
const owner = sectionOfIndex(index);
|
|
129
129
|
if (owner && collapsed.has(sectionKey(sections, owner))) return;
|
|
130
|
-
nodes.push(card(
|
|
130
|
+
nodes.push(card(presentation, slide, index, slideCount));
|
|
131
131
|
});
|
|
132
132
|
container.replaceChildren(...nodes);
|
|
133
133
|
container.setAttribute("aria-label", variant === "sorter" ? "Slide sorter" : "Slides");
|
|
134
134
|
if (hadFocus) container.querySelector(`.slide-card[data-index="${focusIndex}"]`)?.focus({ preventScroll: false });
|
|
135
135
|
renderToolbar();
|
|
136
136
|
}
|
|
137
|
-
function card(
|
|
137
|
+
function card(presentation, slide, index, slideCount) {
|
|
138
138
|
const selected = selection.has(index);
|
|
139
139
|
const title = slideTitle(slide) || "Untitled slide";
|
|
140
140
|
const hidden = slide.hidden === true;
|
|
@@ -150,7 +150,7 @@ export function createSlideManager(container, options) {
|
|
|
150
150
|
const number = el("span", { class: "thumbnail-number", draggable: "true", text: String(index + 1).padStart(2, "0") });
|
|
151
151
|
const thumbnail = el("span", { class: "thumbnail", "aria-hidden": "true" });
|
|
152
152
|
let html = "";
|
|
153
|
-
try { html = options.renderThumbnail?.(
|
|
153
|
+
try { html = options.renderThumbnail?.(presentation, index) ?? ""; } catch { html = "Preview unavailable"; }
|
|
154
154
|
thumbnail.innerHTML = html;
|
|
155
155
|
thumbnail.querySelectorAll("[tabindex]").forEach((node) => node.removeAttribute("tabindex"));
|
|
156
156
|
const label = el("span", { class: "thumbnail-title", text: slideTitle(slide) || "Untitled slide" });
|
|
@@ -281,9 +281,9 @@ export function createSlideManager(container, options) {
|
|
|
281
281
|
} catch (error) { report(error); }
|
|
282
282
|
}
|
|
283
283
|
function sectionNote(index) {
|
|
284
|
-
const
|
|
285
|
-
if (!hasSections(
|
|
286
|
-
const name =
|
|
284
|
+
const presentation = deck();
|
|
285
|
+
if (!hasSections(presentation)) return "";
|
|
286
|
+
const name = presentation.slides[index].section;
|
|
287
287
|
return name ? `, in section ${name}` : ", with no section";
|
|
288
288
|
}
|
|
289
289
|
function moveTo(indices, edge) {
|
|
@@ -339,8 +339,8 @@ export function createSlideManager(container, options) {
|
|
|
339
339
|
} catch (error) { report(error); }
|
|
340
340
|
}
|
|
341
341
|
async function sectionPrompt(index) {
|
|
342
|
-
const
|
|
343
|
-
const hasAny = hasSections(
|
|
342
|
+
const presentation = deck();
|
|
343
|
+
const hasAny = hasSections(presentation);
|
|
344
344
|
const name = await ask("Add section", "Section name", hasAny ? "" : "Section 1", `The section starts at slide ${index + 1}.`);
|
|
345
345
|
if (name === null) { focusCard(index); return; }
|
|
346
346
|
try {
|
|
@@ -380,9 +380,9 @@ export function createSlideManager(container, options) {
|
|
|
380
380
|
|
|
381
381
|
function slideMenu() {
|
|
382
382
|
const list = selectedList();
|
|
383
|
-
const
|
|
384
|
-
const sections = hasSections(
|
|
385
|
-
const everyHidden = list.every((index) =>
|
|
383
|
+
const presentation = deck();
|
|
384
|
+
const sections = hasSections(presentation) ? listSections(presentation) : [];
|
|
385
|
+
const everyHidden = list.every((index) => presentation.slides[index].hidden === true);
|
|
386
386
|
const first = list[0];
|
|
387
387
|
const items = [
|
|
388
388
|
{ label: list.length > 1 ? `Duplicate ${list.length} slides` : "Duplicate slide", shortcut: `${isMac() ? "⌘" : "Ctrl"}+D`, run: () => act("duplicate") },
|
package/dist/slides.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { EditorChange, EditorSession, JsonPatchOperation } from "./index.js
|
|
|
2
2
|
|
|
3
3
|
/** One section of the deck: a maximal run of consecutive slides sharing a `section` label (or none). */
|
|
4
4
|
export interface SlideSection {
|
|
5
|
-
/** Position in `listSections(
|
|
5
|
+
/** Position in `listSections(presentation)`, the id every section operation takes. */
|
|
6
6
|
index: number;
|
|
7
7
|
/** The label; undefined for the unnamed run of slides with no label. */
|
|
8
8
|
name: string | undefined;
|
|
@@ -15,7 +15,7 @@ export interface SlideSummary { index: number; id?: string; title: string; hidde
|
|
|
15
15
|
|
|
16
16
|
/** What every `prepare*` function returns without touching a session. Empty `patches` and `changed: false` mean nothing to do. */
|
|
17
17
|
export interface PreparedSlideChange {
|
|
18
|
-
|
|
18
|
+
presentation: unknown;
|
|
19
19
|
patches: JsonPatchOperation[];
|
|
20
20
|
changed: boolean;
|
|
21
21
|
/** The slide indices to select after the change (the new, moved or remaining slides). */
|
|
@@ -38,8 +38,8 @@ export interface PreparedSlideChange {
|
|
|
38
38
|
section?: string;
|
|
39
39
|
}
|
|
40
40
|
/** The session change of an applied operation, with the same report fields. One undo step; an unchanged operation commits nothing. */
|
|
41
|
-
export interface SlideChange extends Omit<EditorChange, "
|
|
42
|
-
|
|
41
|
+
export interface SlideChange extends Omit<EditorChange, "presentation" | "patches">, Omit<PreparedSlideChange, "presentation" | "patches"> {
|
|
42
|
+
presentation: unknown;
|
|
43
43
|
patches: JsonPatchOperation[];
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -61,36 +61,36 @@ export interface AddSlideOptions {
|
|
|
61
61
|
record?: Record<string, unknown>;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export declare function listSections(
|
|
65
|
-
export declare function hasSections(
|
|
66
|
-
export declare function sectionForSlide(
|
|
64
|
+
export declare function listSections(presentation: unknown): SlideSection[];
|
|
65
|
+
export declare function hasSections(presentation: unknown): boolean;
|
|
66
|
+
export declare function sectionForSlide(presentation: unknown, slideIndex: number): SlideSection | undefined;
|
|
67
67
|
export declare function slideTitle(slide: unknown): string;
|
|
68
|
-
export declare function slideSummaries(
|
|
68
|
+
export declare function slideSummaries(presentation: unknown): SlideSummary[];
|
|
69
69
|
|
|
70
|
-
export declare function prepareAddSlide(
|
|
70
|
+
export declare function prepareAddSlide(presentation: unknown, options?: AddSlideOptions): PreparedSlideChange;
|
|
71
71
|
export declare function addSlide(editor: EditorSession, options?: AddSlideOptions, meta?: Record<string, unknown>): SlideChange;
|
|
72
|
-
export declare function prepareDuplicateSlides(
|
|
72
|
+
export declare function prepareDuplicateSlides(presentation: unknown, indices: number | number[], options?: { section?: "keep" }): PreparedSlideChange;
|
|
73
73
|
export declare function duplicateSlides(editor: EditorSession, indices: number | number[], options?: { section?: "keep" }, meta?: Record<string, unknown>): SlideChange;
|
|
74
74
|
/** Throws `cannot-remove-all-slides` when every slide is chosen. */
|
|
75
|
-
export declare function prepareRemoveSlides(
|
|
75
|
+
export declare function prepareRemoveSlides(presentation: unknown, indices: number | number[]): PreparedSlideChange;
|
|
76
76
|
export declare function removeSlides(editor: EditorSession, indices: number | number[], meta?: Record<string, unknown>): SlideChange;
|
|
77
77
|
/** `to` is the drop gap in the original order (0 to the slide count). */
|
|
78
|
-
export declare function prepareMoveSlides(
|
|
78
|
+
export declare function prepareMoveSlides(presentation: unknown, indices: number | number[], to: number, options?: { section?: MoveSectionMode }): PreparedSlideChange;
|
|
79
79
|
export declare function moveSlides(editor: EditorSession, indices: number | number[], to: number, options?: { section?: MoveSectionMode }, meta?: Record<string, unknown>): SlideChange;
|
|
80
|
-
export declare function prepareMoveSlidesBy(
|
|
80
|
+
export declare function prepareMoveSlidesBy(presentation: unknown, indices: number | number[], delta: number, options?: { section?: MoveSectionMode }): PreparedSlideChange;
|
|
81
81
|
export declare function moveSlidesBy(editor: EditorSession, indices: number | number[], delta: number, options?: { section?: MoveSectionMode }, meta?: Record<string, unknown>): SlideChange;
|
|
82
82
|
/** `hidden` omitted toggles: all hidden become shown, otherwise all become hidden. */
|
|
83
|
-
export declare function prepareSetHidden(
|
|
83
|
+
export declare function prepareSetHidden(presentation: unknown, indices: number | number[], hidden?: boolean): PreparedSlideChange;
|
|
84
84
|
export declare function setHidden(editor: EditorSession, indices: number | number[], hidden?: boolean, meta?: Record<string, unknown>): SlideChange;
|
|
85
|
-
export declare function prepareSetSection(
|
|
85
|
+
export declare function prepareSetSection(presentation: unknown, indices: number | number[], name: string | null): PreparedSlideChange;
|
|
86
86
|
export declare function setSection(editor: EditorSession, indices: number | number[], name: string | null, meta?: Record<string, unknown>): SlideChange;
|
|
87
|
-
export declare function prepareAddSection(
|
|
87
|
+
export declare function prepareAddSection(presentation: unknown, slideIndex: number, name: string): PreparedSlideChange;
|
|
88
88
|
export declare function addSection(editor: EditorSession, slideIndex: number, name: string, meta?: Record<string, unknown>): SlideChange;
|
|
89
|
-
export declare function prepareRenameSection(
|
|
89
|
+
export declare function prepareRenameSection(presentation: unknown, sectionIndex: number, name: string): PreparedSlideChange;
|
|
90
90
|
export declare function renameSection(editor: EditorSession, sectionIndex: number, name: string, meta?: Record<string, unknown>): SlideChange;
|
|
91
|
-
export declare function prepareRemoveSection(
|
|
91
|
+
export declare function prepareRemoveSection(presentation: unknown, sectionIndex: number, options?: { deleteSlides?: boolean }): PreparedSlideChange;
|
|
92
92
|
export declare function removeSection(editor: EditorSession, sectionIndex: number, options?: { deleteSlides?: boolean }, meta?: Record<string, unknown>): SlideChange;
|
|
93
93
|
/** `to` is a gap among the sections (0 to the section count). */
|
|
94
|
-
export declare function prepareMoveSection(
|
|
94
|
+
export declare function prepareMoveSection(presentation: unknown, sectionIndex: number, to: number): PreparedSlideChange;
|
|
95
95
|
export declare function moveSection(editor: EditorSession, sectionIndex: number, to: number, meta?: Record<string, unknown>): SlideChange;
|
|
96
|
-
export declare const SLIDE_OPERATIONS: Readonly<Record<string, (
|
|
96
|
+
export declare const SLIDE_OPERATIONS: Readonly<Record<string, (presentation: unknown, ...args: any[]) => PreparedSlideChange>>;
|
package/dist/slides.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Slide management (RR-21): add, duplicate, remove, move, hide and section slides as one validated, undoable
|
|
2
2
|
// change each. Every operation has a `prepare*` form that computes the JSON Patch from a document without
|
|
3
|
-
// touching a session (a dry run: `changed`, `patches` and the resulting `
|
|
3
|
+
// touching a session (a dry run: `changed`, `patches` and the resulting `presentation`), and an apply form that
|
|
4
4
|
// commits that patch through the session as ONE transaction, so a single Undo restores the deck exactly and
|
|
5
5
|
// the preview, thumbnails and PPTX export follow from the document.
|
|
6
6
|
//
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
// the same label form a section (see `listSections`). Slides without a label form an unnamed run. `hidden`
|
|
9
9
|
// is a boolean on the slide. Slide order is the `slides` array, so a move is a remove plus an add of the same
|
|
10
10
|
// slide (removing and re-adding keeps every other slide's patch path stable).
|
|
11
|
-
import { validateOpfDocument } from "./index.js";
|
|
12
11
|
import { checkedDocument, fail, same } from "./edit-helpers.js";
|
|
13
12
|
import { collectReservedPresentationIds, remapSlideTreeIds } from "./presentation-ids.js";
|
|
14
13
|
import { prepareDimensionSwitch } from "./switches.js";
|
|
14
|
+
import { checkFormat } from "./checks.js";
|
|
15
15
|
|
|
16
16
|
const isIndex = (value) => Number.isInteger(value) && value >= 0;
|
|
17
17
|
const clone = (value) => structuredClone(value);
|
|
@@ -19,13 +19,13 @@ const clone = (value) => structuredClone(value);
|
|
|
19
19
|
function requireEditor(editor) {
|
|
20
20
|
if (!editor || typeof editor.applyPatch !== "function") throw fail("invalid-editor", "Expected an editor session created by createEditorSession.");
|
|
21
21
|
}
|
|
22
|
-
function requireSlides(
|
|
23
|
-
if (!
|
|
24
|
-
return
|
|
22
|
+
function requireSlides(presentation) {
|
|
23
|
+
if (!presentation || typeof presentation !== "object" || !Array.isArray(presentation.slides)) throw fail("invalid-input", "Slide management needs an OPF document with a slides array.");
|
|
24
|
+
return presentation.slides;
|
|
25
25
|
}
|
|
26
26
|
/** Validate and sort a list of slide indices; a single index is accepted too. */
|
|
27
|
-
function selectionOf(
|
|
28
|
-
const slides = requireSlides(
|
|
27
|
+
function selectionOf(presentation, indices, what = "Choose at least one slide.") {
|
|
28
|
+
const slides = requireSlides(presentation);
|
|
29
29
|
const list = [...new Set(Array.isArray(indices) ? indices : [indices])];
|
|
30
30
|
if (!list.length) throw fail("no-slides-chosen", what);
|
|
31
31
|
for (const index of list) if (!isIndex(index) || index >= slides.length) throw fail("slide-index-out-of-range", `Slide ${index} does not exist.`, { slideIndex: index });
|
|
@@ -33,21 +33,21 @@ function selectionOf(document, indices, what = "Choose at least one slide.") {
|
|
|
33
33
|
}
|
|
34
34
|
const sectionOf = (slide) => (typeof slide?.section === "string" && slide.section.trim() !== "" ? slide.section : undefined);
|
|
35
35
|
|
|
36
|
-
function unchanged(
|
|
37
|
-
return {
|
|
36
|
+
function unchanged(presentation, extra = {}) {
|
|
37
|
+
return { presentation: clone(presentation), patches: [], changed: false, selection: [], ...extra };
|
|
38
38
|
}
|
|
39
39
|
/** Validate the candidate the way every switch does (a valid deck must stay valid) and package the result. */
|
|
40
|
-
function finish(
|
|
41
|
-
if (!patches.length) return unchanged(
|
|
42
|
-
const before =
|
|
43
|
-
const next = checkedDocument(
|
|
44
|
-
return {
|
|
40
|
+
function finish(presentation, patches, extra) {
|
|
41
|
+
if (!patches.length) return unchanged(presentation, extra);
|
|
42
|
+
const before = checkFormat(presentation);
|
|
43
|
+
const next = checkedDocument(presentation, patches, before);
|
|
44
|
+
return { presentation: next, patches, changed: true, ...extra };
|
|
45
45
|
}
|
|
46
46
|
function apply(editor, prepared, meta, action) {
|
|
47
47
|
requireEditor(editor);
|
|
48
|
-
const {
|
|
49
|
-
void
|
|
50
|
-
if (!prepared.changed) return { ...summary,
|
|
48
|
+
const { presentation, patches, ...summary } = prepared;
|
|
49
|
+
void presentation;
|
|
50
|
+
if (!prepared.changed) return { ...summary, presentation: editor.presentation, patches: [], inversePatches: [], validation: editor.validation };
|
|
51
51
|
const change = editor.applyPatch(patches, { ...meta, source: meta?.source ?? "slides", action });
|
|
52
52
|
return { ...change, ...summary };
|
|
53
53
|
}
|
|
@@ -59,8 +59,8 @@ function apply(editor, prepared, meta, action) {
|
|
|
59
59
|
* of slides with no label is reported with `name: undefined` (`unnamed: true`). `index` is the position in
|
|
60
60
|
* the returned list, the id every section operation takes.
|
|
61
61
|
*/
|
|
62
|
-
export function listSections(
|
|
63
|
-
const slides = requireSlides(
|
|
62
|
+
export function listSections(presentation) {
|
|
63
|
+
const slides = requireSlides(presentation);
|
|
64
64
|
const sections = [];
|
|
65
65
|
slides.forEach((slide, slideIndex) => {
|
|
66
66
|
const name = sectionOf(slide);
|
|
@@ -71,20 +71,22 @@ export function listSections(document) {
|
|
|
71
71
|
return sections;
|
|
72
72
|
}
|
|
73
73
|
/** Whether any slide carries a section label (a deck with none shows no section headers). */
|
|
74
|
-
export function hasSections(
|
|
75
|
-
return requireSlides(
|
|
74
|
+
export function hasSections(presentation) {
|
|
75
|
+
return requireSlides(presentation).some((slide) => sectionOf(slide) !== undefined);
|
|
76
76
|
}
|
|
77
77
|
/** The section (from `listSections`) that holds a slide. */
|
|
78
|
-
export function sectionForSlide(
|
|
79
|
-
return listSections(
|
|
78
|
+
export function sectionForSlide(presentation, slideIndex) {
|
|
79
|
+
return listSections(presentation).find((section) => slideIndex >= section.start && slideIndex < section.start + section.count);
|
|
80
80
|
}
|
|
81
81
|
/** The title shown for a slide in navigators and outlines. */
|
|
82
82
|
export function slideTitle(slide) {
|
|
83
|
-
|
|
83
|
+
// A title is a string or TextRun[] (FA-10): either shows as its plain text.
|
|
84
|
+
const title = typeof slide?.title === "string" ? slide.title : Array.isArray(slide?.title) ? slide.title.map((run) => (typeof run === "string" ? run : (run?.text ?? ""))).join("") : "";
|
|
85
|
+
return title.trim() ? title : "";
|
|
84
86
|
}
|
|
85
87
|
/** One row per slide for pickers and navigators: `{ index, id, title, hidden, section, layout }`. */
|
|
86
|
-
export function slideSummaries(
|
|
87
|
-
return requireSlides(
|
|
88
|
+
export function slideSummaries(presentation) {
|
|
89
|
+
return requireSlides(presentation).map((slide, index) => ({ index, id: slide.id, title: slideTitle(slide), hidden: slide.hidden === true, section: sectionOf(slide), layout: slide.layout }));
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
// --- patches ---------------------------------------------------------------------------------------
|
|
@@ -120,9 +122,9 @@ function permutationPatches(moved, order, values) {
|
|
|
120
122
|
|
|
121
123
|
// --- add -------------------------------------------------------------------------------------------
|
|
122
124
|
|
|
123
|
-
function newSlideId(
|
|
124
|
-
const used = new Set(collectReservedPresentationIds(
|
|
125
|
-
let number =
|
|
125
|
+
function newSlideId(presentation) {
|
|
126
|
+
const used = new Set(collectReservedPresentationIds(presentation));
|
|
127
|
+
let number = presentation.slides.length + 1;
|
|
126
128
|
while (used.has(`slide-${number}`)) number += 1;
|
|
127
129
|
return `slide-${number}`;
|
|
128
130
|
}
|
|
@@ -133,11 +135,11 @@ function newSlideId(document) {
|
|
|
133
135
|
* `title`, `text`, `id`, `section` (default: the section of the slide before it), `slide` (a ready slide to
|
|
134
136
|
* insert, for hosts that build their own), `catalogs`/`record` for catalog lookup (as `prepareDimensionSwitch`).
|
|
135
137
|
*/
|
|
136
|
-
export function prepareAddSlide(
|
|
137
|
-
const slides = requireSlides(
|
|
138
|
+
export function prepareAddSlide(presentation, options = {}) {
|
|
139
|
+
const slides = requireSlides(presentation);
|
|
138
140
|
const at = options.at === undefined ? slides.length : options.at;
|
|
139
141
|
if (!isIndex(at) || at > slides.length) throw fail("slide-index-out-of-range", "Add the slide at an index from 0 to the slide count.", { at });
|
|
140
|
-
const id = options.id ?? options.slide?.id ?? newSlideId(
|
|
142
|
+
const id = options.id ?? options.slide?.id ?? newSlideId(presentation);
|
|
141
143
|
let slide = options.slide ? clone(options.slide) : { id };
|
|
142
144
|
if (!options.slide) {
|
|
143
145
|
if (options.title !== undefined) slide.title = String(options.title);
|
|
@@ -149,20 +151,20 @@ export function prepareAddSlide(document, options = {}) {
|
|
|
149
151
|
slide = withSection(slide, inherited);
|
|
150
152
|
if (options.layout) {
|
|
151
153
|
// Switch the layout on a scratch copy to reuse the placeholder rules and the catalog lookup.
|
|
152
|
-
const scratch = { ...clone(
|
|
154
|
+
const scratch = { ...clone(presentation), slides: [...clone(slides)] };
|
|
153
155
|
scratch.slides.splice(at, 0, slide);
|
|
154
156
|
const switched = prepareDimensionSwitch(scratch, "layouts", options.layout, { slideIndex: at, catalogs: options.catalogs, record: options.record });
|
|
155
157
|
const catalogOps = switched.patches.filter((patch) => !patch.path.startsWith("/slides/"));
|
|
156
|
-
slide = switched.
|
|
158
|
+
slide = switched.presentation.slides[at];
|
|
157
159
|
const patches = [...catalogOps, { op: "add", path: slidePointer(at), value: slide }];
|
|
158
|
-
return finish(
|
|
160
|
+
return finish(presentation, patches, { action: "add", index: at, id: slide.id, selection: [at], layout: options.layout });
|
|
159
161
|
}
|
|
160
|
-
return finish(
|
|
162
|
+
return finish(presentation, [{ op: "add", path: slidePointer(at), value: slide }], { action: "add", index: at, id: slide.id, selection: [at] });
|
|
161
163
|
}
|
|
162
164
|
/** Add a slide (see `prepareAddSlide`) as one undoable step. The change reports `index`, `id` and `selection`. */
|
|
163
165
|
export function addSlide(editor, options = {}, meta) {
|
|
164
166
|
requireEditor(editor);
|
|
165
|
-
return apply(editor, prepareAddSlide(editor.
|
|
167
|
+
return apply(editor, prepareAddSlide(editor.presentation, options), meta, "add");
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
// --- duplicate -------------------------------------------------------------------------------------
|
|
@@ -172,11 +174,11 @@ export function addSlide(editor, options = {}, meta) {
|
|
|
172
174
|
* the slide and any content it identifies (`slide-2` becomes `slide-2-2`, as when pagination copies slides).
|
|
173
175
|
* Copies join the section of the slide they follow unless `options.section` is `"keep"`.
|
|
174
176
|
*/
|
|
175
|
-
export function prepareDuplicateSlides(
|
|
176
|
-
const list = selectionOf(
|
|
177
|
-
const slides =
|
|
177
|
+
export function prepareDuplicateSlides(presentation, indices, options = {}) {
|
|
178
|
+
const list = selectionOf(presentation, indices, "Choose at least one slide to duplicate.");
|
|
179
|
+
const slides = presentation.slides;
|
|
178
180
|
const at = list.at(-1) + 1;
|
|
179
|
-
const ids = new Set(collectReservedPresentationIds(
|
|
181
|
+
const ids = new Set(collectReservedPresentationIds(presentation));
|
|
180
182
|
const section = options.section === "keep" ? undefined : sectionOf(slides[at - 1]);
|
|
181
183
|
const copies = list.map((index) => {
|
|
182
184
|
let copy = clone(slides[index]);
|
|
@@ -185,11 +187,11 @@ export function prepareDuplicateSlides(document, indices, options = {}) {
|
|
|
185
187
|
return copy;
|
|
186
188
|
});
|
|
187
189
|
const patches = copies.map((copy, offset) => ({ op: "add", path: slidePointer(at + offset), value: copy }));
|
|
188
|
-
return finish(
|
|
190
|
+
return finish(presentation, patches, { action: "duplicate", selection: copies.map((_, offset) => at + offset), range: { start: at, count: copies.length }, ids: copies.map((copy) => copy.id) });
|
|
189
191
|
}
|
|
190
192
|
export function duplicateSlides(editor, indices, options = {}, meta) {
|
|
191
193
|
requireEditor(editor);
|
|
192
|
-
return apply(editor, prepareDuplicateSlides(editor.
|
|
194
|
+
return apply(editor, prepareDuplicateSlides(editor.presentation, indices, options), meta, "duplicate");
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
// --- remove ----------------------------------------------------------------------------------------
|
|
@@ -199,17 +201,17 @@ export function duplicateSlides(editor, indices, options = {}, meta) {
|
|
|
199
201
|
* `cannot-remove-all-slides`. `selection` is the slide to show next (the one that moves into the first
|
|
200
202
|
* removed position, or the last slide).
|
|
201
203
|
*/
|
|
202
|
-
export function prepareRemoveSlides(
|
|
203
|
-
const list = selectionOf(
|
|
204
|
-
const slides =
|
|
204
|
+
export function prepareRemoveSlides(presentation, indices) {
|
|
205
|
+
const list = selectionOf(presentation, indices, "Choose at least one slide to delete.");
|
|
206
|
+
const slides = presentation.slides;
|
|
205
207
|
if (list.length >= slides.length) throw fail("cannot-remove-all-slides", "A presentation needs at least one slide.", { count: slides.length });
|
|
206
208
|
const patches = [...list].reverse().map((index) => ({ op: "remove", path: slidePointer(index) }));
|
|
207
209
|
const remaining = slides.length - list.length;
|
|
208
|
-
return finish(
|
|
210
|
+
return finish(presentation, patches, { action: "remove", removed: list, selection: [Math.min(list[0], remaining - 1)] });
|
|
209
211
|
}
|
|
210
212
|
export function removeSlides(editor, indices, meta) {
|
|
211
213
|
requireEditor(editor);
|
|
212
|
-
return apply(editor, prepareRemoveSlides(editor.
|
|
214
|
+
return apply(editor, prepareRemoveSlides(editor.presentation, indices), meta, "remove");
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
// --- move ------------------------------------------------------------------------------------------
|
|
@@ -221,9 +223,9 @@ export function removeSlides(editor, indices, meta) {
|
|
|
221
223
|
* first slide's when moving to the start), `"keep"` leaves labels alone, a string sets that section and
|
|
222
224
|
* `null` clears it.
|
|
223
225
|
*/
|
|
224
|
-
export function prepareMoveSlides(
|
|
225
|
-
const list = selectionOf(
|
|
226
|
-
const slides =
|
|
226
|
+
export function prepareMoveSlides(presentation, indices, to, options = {}) {
|
|
227
|
+
const list = selectionOf(presentation, indices, "Choose at least one slide to move.");
|
|
228
|
+
const slides = presentation.slides;
|
|
227
229
|
if (!isIndex(to) || to > slides.length) throw fail("slide-index-out-of-range", "Move to a gap from 0 to the slide count.", { to });
|
|
228
230
|
const chosen = new Set(list);
|
|
229
231
|
const rest = slides.map((_, index) => index).filter((index) => !chosen.has(index));
|
|
@@ -237,18 +239,18 @@ export function prepareMoveSlides(document, indices, to, options = {}) {
|
|
|
237
239
|
const values = new Map(list.map((index) => [index, target ? withSection(slides[index], target.name) : slides[index]]));
|
|
238
240
|
const sameOrder = order.every((old, at) => old === at);
|
|
239
241
|
const sameValues = list.every((index) => same(values.get(index), slides[index]));
|
|
240
|
-
if (sameOrder && sameValues) return unchanged(
|
|
242
|
+
if (sameOrder && sameValues) return unchanged(presentation, { action: "move", selection: list });
|
|
241
243
|
// A pure relabel keeps the order: patch the labels in place instead of remove and add.
|
|
242
244
|
const moved = new Set(list);
|
|
243
245
|
const patches = sameOrder
|
|
244
246
|
? list.flatMap((index) => fieldPatch(slides[index], index, "section", sectionOf(values.get(index))))
|
|
245
247
|
: permutationPatches(moved, order, values);
|
|
246
248
|
const selection = list.map((old) => order.indexOf(old));
|
|
247
|
-
return finish(
|
|
249
|
+
return finish(presentation, patches, { action: "move", selection, order });
|
|
248
250
|
}
|
|
249
251
|
export function moveSlides(editor, indices, to, options = {}, meta) {
|
|
250
252
|
requireEditor(editor);
|
|
251
|
-
return apply(editor, prepareMoveSlides(editor.
|
|
253
|
+
return apply(editor, prepareMoveSlides(editor.presentation, indices, to, options), meta, "move");
|
|
252
254
|
}
|
|
253
255
|
|
|
254
256
|
/**
|
|
@@ -256,12 +258,12 @@ export function moveSlides(editor, indices, to, options = {}, meta) {
|
|
|
256
258
|
* scattered one moves each slide past its neighbour, like Alt+Arrow in a list. Moving past the first or last
|
|
257
259
|
* slide changes nothing (`changed` is false).
|
|
258
260
|
*/
|
|
259
|
-
export function prepareMoveSlidesBy(
|
|
260
|
-
const list = selectionOf(
|
|
261
|
-
const count =
|
|
262
|
-
if (!Number.isInteger(delta) || delta === 0) return unchanged(
|
|
261
|
+
export function prepareMoveSlidesBy(presentation, indices, delta, options = {}) {
|
|
262
|
+
const list = selectionOf(presentation, indices, "Choose at least one slide to move.");
|
|
263
|
+
const count = presentation.slides.length;
|
|
264
|
+
if (!Number.isInteger(delta) || delta === 0) return unchanged(presentation, { action: "move", selection: list });
|
|
263
265
|
const step = Math.sign(delta);
|
|
264
|
-
const order =
|
|
266
|
+
const order = presentation.slides.map((_, index) => index);
|
|
265
267
|
const selected = new Set(list);
|
|
266
268
|
for (let turn = 0; turn < Math.abs(delta); turn += 1) {
|
|
267
269
|
const sequence = step < 0 ? [...order.keys()] : [...order.keys()].reverse();
|
|
@@ -274,8 +276,8 @@ export function prepareMoveSlidesBy(document, indices, delta, options = {}) {
|
|
|
274
276
|
}
|
|
275
277
|
if (!moved) break;
|
|
276
278
|
}
|
|
277
|
-
if (order.every((old, at) => old === at)) return unchanged(
|
|
278
|
-
const slides =
|
|
279
|
+
if (order.every((old, at) => old === at)) return unchanged(presentation, { action: "move", selection: list });
|
|
280
|
+
const slides = presentation.slides;
|
|
279
281
|
const mode = options.section === undefined ? "adopt" : options.section;
|
|
280
282
|
// A moved slide adopts the section of the nearest slide that did not move: the one before it in the new order,
|
|
281
283
|
// else the one after it.
|
|
@@ -288,11 +290,11 @@ export function prepareMoveSlidesBy(document, indices, delta, options = {}) {
|
|
|
288
290
|
const name = mode === "keep" ? sectionOf(slides[index]) : mode === "adopt" ? adopted(order.indexOf(index)) : mode === null ? undefined : String(mode);
|
|
289
291
|
return [index, withSection(slides[index], name)];
|
|
290
292
|
}));
|
|
291
|
-
return finish(
|
|
293
|
+
return finish(presentation, permutationPatches(selected, order, values), { action: "move", selection: list.map((old) => order.indexOf(old)), order });
|
|
292
294
|
}
|
|
293
295
|
export function moveSlidesBy(editor, indices, delta, options = {}, meta) {
|
|
294
296
|
requireEditor(editor);
|
|
295
|
-
return apply(editor, prepareMoveSlidesBy(editor.
|
|
297
|
+
return apply(editor, prepareMoveSlidesBy(editor.presentation, indices, delta, options), meta, "move");
|
|
296
298
|
}
|
|
297
299
|
|
|
298
300
|
// --- hide ------------------------------------------------------------------------------------------
|
|
@@ -302,24 +304,24 @@ export function moveSlidesBy(editor, indices, delta, options = {}, meta) {
|
|
|
302
304
|
* navigator but is skipped when presenting; showing removes the `hidden` field instead of writing `false`.
|
|
303
305
|
* Without `hidden`, the slides toggle together: all hidden become shown, otherwise all become hidden.
|
|
304
306
|
*/
|
|
305
|
-
export function prepareSetHidden(
|
|
306
|
-
const list = selectionOf(
|
|
307
|
-
const slides =
|
|
307
|
+
export function prepareSetHidden(presentation, indices, hidden) {
|
|
308
|
+
const list = selectionOf(presentation, indices, "Choose at least one slide.");
|
|
309
|
+
const slides = presentation.slides;
|
|
308
310
|
const value = hidden === undefined ? !list.every((index) => slides[index].hidden === true) : Boolean(hidden);
|
|
309
311
|
const patches = list.flatMap((index) => fieldPatch(slides[index], index, "hidden", value ? true : undefined));
|
|
310
312
|
// A stored `hidden: false` counts as shown; showing it removes the field, hiding replaces it.
|
|
311
|
-
return finish(
|
|
313
|
+
return finish(presentation, patches, { action: value ? "hide" : "show", hidden: value, selection: list });
|
|
312
314
|
}
|
|
313
315
|
export function setHidden(editor, indices, hidden, meta) {
|
|
314
316
|
requireEditor(editor);
|
|
315
|
-
const prepared = prepareSetHidden(editor.
|
|
317
|
+
const prepared = prepareSetHidden(editor.presentation, indices, hidden);
|
|
316
318
|
return apply(editor, prepared, meta, prepared.action);
|
|
317
319
|
}
|
|
318
320
|
|
|
319
321
|
// --- sections --------------------------------------------------------------------------------------
|
|
320
322
|
|
|
321
|
-
function sectionAt(
|
|
322
|
-
const section = listSections(
|
|
323
|
+
function sectionAt(presentation, sectionIndex) {
|
|
324
|
+
const section = listSections(presentation)[sectionIndex];
|
|
323
325
|
if (!section) throw fail("section-index-out-of-range", `Section ${sectionIndex} does not exist.`, { sectionIndex });
|
|
324
326
|
return section;
|
|
325
327
|
}
|
|
@@ -329,24 +331,24 @@ const cleanName = (name) => (typeof name === "string" ? name.trim() : name);
|
|
|
329
331
|
* Compute giving slides a section label (`name`), or clearing it (`null`). The slides keep their order, so
|
|
330
332
|
* labelling a middle slide splits its section in two runs; use `prepareAddSection` to start a section.
|
|
331
333
|
*/
|
|
332
|
-
export function prepareSetSection(
|
|
333
|
-
const list = selectionOf(
|
|
334
|
+
export function prepareSetSection(presentation, indices, name) {
|
|
335
|
+
const list = selectionOf(presentation, indices, "Choose at least one slide.");
|
|
334
336
|
const label = name === null || name === undefined ? undefined : cleanName(name);
|
|
335
337
|
if (label === "") throw fail("invalid-section-name", "A section needs a name. Use null to remove the section from a slide.");
|
|
336
|
-
const patches = list.flatMap((index) => fieldPatch(
|
|
337
|
-
return finish(
|
|
338
|
+
const patches = list.flatMap((index) => fieldPatch(presentation.slides[index], index, "section", label));
|
|
339
|
+
return finish(presentation, patches, { action: "section", section: label, selection: list });
|
|
338
340
|
}
|
|
339
341
|
export function setSection(editor, indices, name, meta) {
|
|
340
342
|
requireEditor(editor);
|
|
341
|
-
return apply(editor, prepareSetSection(editor.
|
|
343
|
+
return apply(editor, prepareSetSection(editor.presentation, indices, name), meta, "section");
|
|
342
344
|
}
|
|
343
345
|
|
|
344
346
|
/**
|
|
345
347
|
* Compute starting a section at a slide: that slide and the slides after it up to the end of its current
|
|
346
348
|
* section take `name` (PowerPoint's "Add section", whose header sits above the chosen slide).
|
|
347
349
|
*/
|
|
348
|
-
export function prepareAddSection(
|
|
349
|
-
const slides = requireSlides(
|
|
350
|
+
export function prepareAddSection(presentation, slideIndex, name) {
|
|
351
|
+
const slides = requireSlides(presentation);
|
|
350
352
|
if (!isIndex(slideIndex) || slideIndex >= slides.length) throw fail("slide-index-out-of-range", `Slide ${slideIndex} does not exist.`, { slideIndex });
|
|
351
353
|
const label = cleanName(name);
|
|
352
354
|
if (typeof label !== "string" || label === "") throw fail("invalid-section-name", "Give the section a name.");
|
|
@@ -358,25 +360,25 @@ export function prepareAddSection(document, slideIndex, name) {
|
|
|
358
360
|
})();
|
|
359
361
|
const indices = Array.from({ length: end - slideIndex + 1 }, (_, offset) => slideIndex + offset);
|
|
360
362
|
const patches = indices.flatMap((index) => fieldPatch(slides[index], index, "section", label));
|
|
361
|
-
return finish(
|
|
363
|
+
return finish(presentation, patches, { action: "add-section", section: label, selection: indices });
|
|
362
364
|
}
|
|
363
365
|
export function addSection(editor, slideIndex, name, meta) {
|
|
364
366
|
requireEditor(editor);
|
|
365
|
-
return apply(editor, prepareAddSection(editor.
|
|
367
|
+
return apply(editor, prepareAddSection(editor.presentation, slideIndex, name), meta, "add-section");
|
|
366
368
|
}
|
|
367
369
|
|
|
368
370
|
/** Compute renaming a section (an index from `listSections`) for all of its slides. */
|
|
369
|
-
export function prepareRenameSection(
|
|
370
|
-
const section = sectionAt(
|
|
371
|
+
export function prepareRenameSection(presentation, sectionIndex, name) {
|
|
372
|
+
const section = sectionAt(presentation, sectionIndex);
|
|
371
373
|
const label = cleanName(name);
|
|
372
374
|
if (typeof label !== "string" || label === "") throw fail("invalid-section-name", "Give the section a name.");
|
|
373
375
|
const indices = Array.from({ length: section.count }, (_, offset) => section.start + offset);
|
|
374
|
-
const patches = indices.flatMap((index) => fieldPatch(
|
|
375
|
-
return finish(
|
|
376
|
+
const patches = indices.flatMap((index) => fieldPatch(presentation.slides[index], index, "section", label));
|
|
377
|
+
return finish(presentation, patches, { action: "rename-section", section: label, selection: indices });
|
|
376
378
|
}
|
|
377
379
|
export function renameSection(editor, sectionIndex, name, meta) {
|
|
378
380
|
requireEditor(editor);
|
|
379
|
-
return apply(editor, prepareRenameSection(editor.
|
|
381
|
+
return apply(editor, prepareRenameSection(editor.presentation, sectionIndex, name), meta, "rename-section");
|
|
380
382
|
}
|
|
381
383
|
|
|
382
384
|
/**
|
|
@@ -384,37 +386,37 @@ export function renameSection(editor, sectionIndex, name, meta) {
|
|
|
384
386
|
* lose their label). With `deleteSlides: true` the slides are deleted too (not allowed when they are every
|
|
385
387
|
* slide of the deck).
|
|
386
388
|
*/
|
|
387
|
-
export function prepareRemoveSection(
|
|
388
|
-
const sections = listSections(
|
|
389
|
-
const section = sectionAt(
|
|
389
|
+
export function prepareRemoveSection(presentation, sectionIndex, options = {}) {
|
|
390
|
+
const sections = listSections(presentation);
|
|
391
|
+
const section = sectionAt(presentation, sectionIndex);
|
|
390
392
|
const indices = Array.from({ length: section.count }, (_, offset) => section.start + offset);
|
|
391
|
-
if (options.deleteSlides) return { ...prepareRemoveSlides(
|
|
393
|
+
if (options.deleteSlides) return { ...prepareRemoveSlides(presentation, indices), action: "remove-section" };
|
|
392
394
|
const previous = sections[sectionIndex - 1];
|
|
393
395
|
const label = previous?.name;
|
|
394
|
-
const patches = indices.flatMap((index) => fieldPatch(
|
|
395
|
-
return finish(
|
|
396
|
+
const patches = indices.flatMap((index) => fieldPatch(presentation.slides[index], index, "section", label));
|
|
397
|
+
return finish(presentation, patches, { action: "remove-section", section: label, selection: indices });
|
|
396
398
|
}
|
|
397
399
|
export function removeSection(editor, sectionIndex, options = {}, meta) {
|
|
398
400
|
requireEditor(editor);
|
|
399
|
-
return apply(editor, prepareRemoveSection(editor.
|
|
401
|
+
return apply(editor, prepareRemoveSection(editor.presentation, sectionIndex, options), meta, "remove-section");
|
|
400
402
|
}
|
|
401
403
|
|
|
402
404
|
/**
|
|
403
405
|
* Compute moving a whole section to the gap `to` among the sections (0 to the section count, in the
|
|
404
406
|
* original order of `listSections`). The slides keep their labels.
|
|
405
407
|
*/
|
|
406
|
-
export function prepareMoveSection(
|
|
407
|
-
const sections = listSections(
|
|
408
|
-
const section = sectionAt(
|
|
408
|
+
export function prepareMoveSection(presentation, sectionIndex, to) {
|
|
409
|
+
const sections = listSections(presentation);
|
|
410
|
+
const section = sectionAt(presentation, sectionIndex);
|
|
409
411
|
if (!isIndex(to) || to > sections.length) throw fail("section-index-out-of-range", "Move to a gap from 0 to the section count.", { to });
|
|
410
|
-
const gap = to === sections.length ?
|
|
412
|
+
const gap = to === sections.length ? presentation.slides.length : sections[to].start;
|
|
411
413
|
const indices = Array.from({ length: section.count }, (_, offset) => section.start + offset);
|
|
412
|
-
const prepared = prepareMoveSlides(
|
|
414
|
+
const prepared = prepareMoveSlides(presentation, indices, gap, { section: "keep" });
|
|
413
415
|
return { ...prepared, action: "move-section", section: section.name };
|
|
414
416
|
}
|
|
415
417
|
export function moveSection(editor, sectionIndex, to, meta) {
|
|
416
418
|
requireEditor(editor);
|
|
417
|
-
return apply(editor, prepareMoveSection(editor.
|
|
419
|
+
return apply(editor, prepareMoveSection(editor.presentation, sectionIndex, to), meta, "move-section");
|
|
418
420
|
}
|
|
419
421
|
|
|
420
422
|
/** Every slide operation as a name to its `prepare` function, for hosts that map a command id to a change. */
|