@openpresentation/opf-editor 0.12.1 → 0.15.0
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 +122 -80
- package/dist/annotations.d.ts +16 -16
- package/dist/annotations.js +71 -79
- package/dist/assets.d.ts +7 -7
- package/dist/assets.js +16 -16
- package/dist/background-options.d.ts +12 -5
- package/dist/background-options.js +44 -19
- 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 +30 -31
- package/dist/canvas.js +64 -68
- package/dist/catalogs.d.ts +48 -0
- package/dist/catalogs.js +217 -0
- 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 +23 -0
- package/dist/color-authoring.js +1 -1
- package/dist/content-actions.d.ts +17 -17
- package/dist/content-actions.js +74 -73
- package/dist/content-controls.js +21 -21
- 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.d.ts +12 -5
- package/dist/design-controls.js +312 -111
- package/dist/design-options.d.ts +20 -18
- package/dist/design-options.js +91 -99
- package/dist/edit-helpers.js +8 -7
- package/dist/exact-source.js +5 -5
- package/dist/export.d.ts +7 -7
- package/dist/export.js +48 -17
- 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 +50 -101
- 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 +28 -29
- package/dist/image-cropper.js +3 -3
- package/dist/image-options.d.ts +45 -0
- package/dist/image-options.js +195 -0
- package/dist/index.d.ts +48 -31
- package/dist/index.js +154 -126
- package/dist/json-editor.js +2 -2
- package/dist/json-menu.js +2 -2
- package/dist/json-options.d.ts +3 -3
- package/dist/json-options.js +44 -54
- 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 +160 -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 +26 -6
- package/dist/slide-manager.d.ts +2 -2
- package/dist/slide-manager.js +19 -19
- package/dist/slides.d.ts +21 -21
- package/dist/slides.js +104 -101
- package/dist/svelte.js +2 -3
- package/dist/switches.d.ts +37 -22
- package/dist/switches.js +282 -171
- 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 +15 -4
- package/dist/transfer.js +107 -161
- package/package.json +12 -6
- package/dist/font-defaults.js +0 -25
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
|
|
|
@@ -48,7 +48,7 @@ export type MoveSectionMode = "adopt" | "keep" | string | null;
|
|
|
48
48
|
export interface AddSlideOptions {
|
|
49
49
|
/** The index the slide takes (default: the end). */
|
|
50
50
|
at?: number;
|
|
51
|
-
/** A
|
|
51
|
+
/** A layout reference (`id` or `name:id`); the layout's placeholders are added as empty slots. */
|
|
52
52
|
layout?: string;
|
|
53
53
|
title?: string;
|
|
54
54
|
text?: string;
|
|
@@ -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,11 @@
|
|
|
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 { mergeCatalogs } from "./catalogs.js";
|
|
15
|
+
import { checkFormat } from "./checks.js";
|
|
15
16
|
|
|
16
17
|
const isIndex = (value) => Number.isInteger(value) && value >= 0;
|
|
17
18
|
const clone = (value) => structuredClone(value);
|
|
@@ -19,13 +20,13 @@ const clone = (value) => structuredClone(value);
|
|
|
19
20
|
function requireEditor(editor) {
|
|
20
21
|
if (!editor || typeof editor.applyPatch !== "function") throw fail("invalid-editor", "Expected an editor session created by createEditorSession.");
|
|
21
22
|
}
|
|
22
|
-
function requireSlides(
|
|
23
|
-
if (!
|
|
24
|
-
return
|
|
23
|
+
function requireSlides(presentation) {
|
|
24
|
+
if (!presentation || typeof presentation !== "object" || !Array.isArray(presentation.slides)) throw fail("invalid-input", "Slide management needs an OPF document with a slides array.");
|
|
25
|
+
return presentation.slides;
|
|
25
26
|
}
|
|
26
27
|
/** Validate and sort a list of slide indices; a single index is accepted too. */
|
|
27
|
-
function selectionOf(
|
|
28
|
-
const slides = requireSlides(
|
|
28
|
+
function selectionOf(presentation, indices, what = "Choose at least one slide.") {
|
|
29
|
+
const slides = requireSlides(presentation);
|
|
29
30
|
const list = [...new Set(Array.isArray(indices) ? indices : [indices])];
|
|
30
31
|
if (!list.length) throw fail("no-slides-chosen", what);
|
|
31
32
|
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 +34,21 @@ function selectionOf(document, indices, what = "Choose at least one slide.") {
|
|
|
33
34
|
}
|
|
34
35
|
const sectionOf = (slide) => (typeof slide?.section === "string" && slide.section.trim() !== "" ? slide.section : undefined);
|
|
35
36
|
|
|
36
|
-
function unchanged(
|
|
37
|
-
return {
|
|
37
|
+
function unchanged(presentation, extra = {}) {
|
|
38
|
+
return { presentation: clone(presentation), patches: [], changed: false, selection: [], ...extra };
|
|
38
39
|
}
|
|
39
40
|
/** 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 {
|
|
41
|
+
function finish(presentation, patches, extra) {
|
|
42
|
+
if (!patches.length) return unchanged(presentation, extra);
|
|
43
|
+
const before = checkFormat(presentation);
|
|
44
|
+
const next = checkedDocument(presentation, patches, before);
|
|
45
|
+
return { presentation: next, patches, changed: true, ...extra };
|
|
45
46
|
}
|
|
46
47
|
function apply(editor, prepared, meta, action) {
|
|
47
48
|
requireEditor(editor);
|
|
48
|
-
const {
|
|
49
|
-
void
|
|
50
|
-
if (!prepared.changed) return { ...summary,
|
|
49
|
+
const { presentation, patches, ...summary } = prepared;
|
|
50
|
+
void presentation;
|
|
51
|
+
if (!prepared.changed) return { ...summary, presentation: editor.presentation, patches: [], inversePatches: [], validation: editor.validation };
|
|
51
52
|
const change = editor.applyPatch(patches, { ...meta, source: meta?.source ?? "slides", action });
|
|
52
53
|
return { ...change, ...summary };
|
|
53
54
|
}
|
|
@@ -59,8 +60,8 @@ function apply(editor, prepared, meta, action) {
|
|
|
59
60
|
* of slides with no label is reported with `name: undefined` (`unnamed: true`). `index` is the position in
|
|
60
61
|
* the returned list, the id every section operation takes.
|
|
61
62
|
*/
|
|
62
|
-
export function listSections(
|
|
63
|
-
const slides = requireSlides(
|
|
63
|
+
export function listSections(presentation) {
|
|
64
|
+
const slides = requireSlides(presentation);
|
|
64
65
|
const sections = [];
|
|
65
66
|
slides.forEach((slide, slideIndex) => {
|
|
66
67
|
const name = sectionOf(slide);
|
|
@@ -71,20 +72,22 @@ export function listSections(document) {
|
|
|
71
72
|
return sections;
|
|
72
73
|
}
|
|
73
74
|
/** Whether any slide carries a section label (a deck with none shows no section headers). */
|
|
74
|
-
export function hasSections(
|
|
75
|
-
return requireSlides(
|
|
75
|
+
export function hasSections(presentation) {
|
|
76
|
+
return requireSlides(presentation).some((slide) => sectionOf(slide) !== undefined);
|
|
76
77
|
}
|
|
77
78
|
/** The section (from `listSections`) that holds a slide. */
|
|
78
|
-
export function sectionForSlide(
|
|
79
|
-
return listSections(
|
|
79
|
+
export function sectionForSlide(presentation, slideIndex) {
|
|
80
|
+
return listSections(presentation).find((section) => slideIndex >= section.start && slideIndex < section.start + section.count);
|
|
80
81
|
}
|
|
81
82
|
/** The title shown for a slide in navigators and outlines. */
|
|
82
83
|
export function slideTitle(slide) {
|
|
83
|
-
|
|
84
|
+
// A title is a string or TextRun[] (FA-10): either shows as its plain text.
|
|
85
|
+
const title = typeof slide?.title === "string" ? slide.title : Array.isArray(slide?.title) ? slide.title.map((run) => (typeof run === "string" ? run : (run?.text ?? ""))).join("") : "";
|
|
86
|
+
return title.trim() ? title : "";
|
|
84
87
|
}
|
|
85
88
|
/** One row per slide for pickers and navigators: `{ index, id, title, hidden, section, layout }`. */
|
|
86
|
-
export function slideSummaries(
|
|
87
|
-
return requireSlides(
|
|
89
|
+
export function slideSummaries(presentation) {
|
|
90
|
+
return requireSlides(presentation).map((slide, index) => ({ index, id: slide.id, title: slideTitle(slide), hidden: slide.hidden === true, section: sectionOf(slide), layout: slide.layout }));
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
// --- patches ---------------------------------------------------------------------------------------
|
|
@@ -120,24 +123,24 @@ function permutationPatches(moved, order, values) {
|
|
|
120
123
|
|
|
121
124
|
// --- add -------------------------------------------------------------------------------------------
|
|
122
125
|
|
|
123
|
-
function newSlideId(
|
|
124
|
-
const used = new Set(collectReservedPresentationIds(
|
|
125
|
-
let number =
|
|
126
|
+
function newSlideId(presentation) {
|
|
127
|
+
const used = new Set(collectReservedPresentationIds(presentation));
|
|
128
|
+
let number = presentation.slides.length + 1;
|
|
126
129
|
while (used.has(`slide-${number}`)) number += 1;
|
|
127
130
|
return `slide-${number}`;
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
/**
|
|
131
|
-
* Compute adding a slide. Options: `at` (the index the slide takes, default the end), `layout` (a
|
|
132
|
-
*
|
|
134
|
+
* Compute adding a slide. Options: `at` (the index the slide takes, default the end), `layout` (a layout
|
|
135
|
+
* reference, `id` or `name:id`; the layout's placeholders are added as empty slots, exactly like switching a slide's layout),
|
|
133
136
|
* `title`, `text`, `id`, `section` (default: the section of the slide before it), `slide` (a ready slide to
|
|
134
|
-
* insert, for hosts that build their own), `catalogs`/`record` for catalog lookup (as `prepareDimensionSwitch`).
|
|
137
|
+
* insert, for hosts that build their own), `catalogs`/`record`/`recordSource` for catalog lookup (as `prepareDimensionSwitch`).
|
|
135
138
|
*/
|
|
136
|
-
export function prepareAddSlide(
|
|
137
|
-
const slides = requireSlides(
|
|
139
|
+
export function prepareAddSlide(presentation, options = {}) {
|
|
140
|
+
const slides = requireSlides(presentation);
|
|
138
141
|
const at = options.at === undefined ? slides.length : options.at;
|
|
139
142
|
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(
|
|
143
|
+
const id = options.id ?? options.slide?.id ?? newSlideId(presentation);
|
|
141
144
|
let slide = options.slide ? clone(options.slide) : { id };
|
|
142
145
|
if (!options.slide) {
|
|
143
146
|
if (options.title !== undefined) slide.title = String(options.title);
|
|
@@ -149,20 +152,20 @@ export function prepareAddSlide(document, options = {}) {
|
|
|
149
152
|
slide = withSection(slide, inherited);
|
|
150
153
|
if (options.layout) {
|
|
151
154
|
// Switch the layout on a scratch copy to reuse the placeholder rules and the catalog lookup.
|
|
152
|
-
const scratch = { ...clone(
|
|
155
|
+
const scratch = { ...clone(presentation), slides: [...clone(slides)] };
|
|
153
156
|
scratch.slides.splice(at, 0, slide);
|
|
154
|
-
const switched = prepareDimensionSwitch(scratch, "layouts", options.layout, { slideIndex: at, catalogs: options.catalogs, record: options.record });
|
|
157
|
+
const switched = prepareDimensionSwitch(scratch, "layouts", options.layout, { slideIndex: at, catalogs: options.catalogs, record: options.record, recordSource: options.recordSource });
|
|
155
158
|
const catalogOps = switched.patches.filter((patch) => !patch.path.startsWith("/slides/"));
|
|
156
|
-
slide = switched.
|
|
159
|
+
slide = switched.presentation.slides[at];
|
|
157
160
|
const patches = [...catalogOps, { op: "add", path: slidePointer(at), value: slide }];
|
|
158
|
-
return finish(
|
|
161
|
+
return finish(presentation, patches, { action: "add", index: at, id: slide.id, selection: [at], layout: options.layout });
|
|
159
162
|
}
|
|
160
|
-
return finish(
|
|
163
|
+
return finish(presentation, [{ op: "add", path: slidePointer(at), value: slide }], { action: "add", index: at, id: slide.id, selection: [at] });
|
|
161
164
|
}
|
|
162
165
|
/** Add a slide (see `prepareAddSlide`) as one undoable step. The change reports `index`, `id` and `selection`. */
|
|
163
166
|
export function addSlide(editor, options = {}, meta) {
|
|
164
167
|
requireEditor(editor);
|
|
165
|
-
return apply(editor, prepareAddSlide(editor.
|
|
168
|
+
return apply(editor, prepareAddSlide(editor.presentation, { ...options, catalogs: mergeCatalogs(editor.catalogs, options.catalogs) }), meta, "add");
|
|
166
169
|
}
|
|
167
170
|
|
|
168
171
|
// --- duplicate -------------------------------------------------------------------------------------
|
|
@@ -172,11 +175,11 @@ export function addSlide(editor, options = {}, meta) {
|
|
|
172
175
|
* the slide and any content it identifies (`slide-2` becomes `slide-2-2`, as when pagination copies slides).
|
|
173
176
|
* Copies join the section of the slide they follow unless `options.section` is `"keep"`.
|
|
174
177
|
*/
|
|
175
|
-
export function prepareDuplicateSlides(
|
|
176
|
-
const list = selectionOf(
|
|
177
|
-
const slides =
|
|
178
|
+
export function prepareDuplicateSlides(presentation, indices, options = {}) {
|
|
179
|
+
const list = selectionOf(presentation, indices, "Choose at least one slide to duplicate.");
|
|
180
|
+
const slides = presentation.slides;
|
|
178
181
|
const at = list.at(-1) + 1;
|
|
179
|
-
const ids = new Set(collectReservedPresentationIds(
|
|
182
|
+
const ids = new Set(collectReservedPresentationIds(presentation));
|
|
180
183
|
const section = options.section === "keep" ? undefined : sectionOf(slides[at - 1]);
|
|
181
184
|
const copies = list.map((index) => {
|
|
182
185
|
let copy = clone(slides[index]);
|
|
@@ -185,11 +188,11 @@ export function prepareDuplicateSlides(document, indices, options = {}) {
|
|
|
185
188
|
return copy;
|
|
186
189
|
});
|
|
187
190
|
const patches = copies.map((copy, offset) => ({ op: "add", path: slidePointer(at + offset), value: copy }));
|
|
188
|
-
return finish(
|
|
191
|
+
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
192
|
}
|
|
190
193
|
export function duplicateSlides(editor, indices, options = {}, meta) {
|
|
191
194
|
requireEditor(editor);
|
|
192
|
-
return apply(editor, prepareDuplicateSlides(editor.
|
|
195
|
+
return apply(editor, prepareDuplicateSlides(editor.presentation, indices, options), meta, "duplicate");
|
|
193
196
|
}
|
|
194
197
|
|
|
195
198
|
// --- remove ----------------------------------------------------------------------------------------
|
|
@@ -199,17 +202,17 @@ export function duplicateSlides(editor, indices, options = {}, meta) {
|
|
|
199
202
|
* `cannot-remove-all-slides`. `selection` is the slide to show next (the one that moves into the first
|
|
200
203
|
* removed position, or the last slide).
|
|
201
204
|
*/
|
|
202
|
-
export function prepareRemoveSlides(
|
|
203
|
-
const list = selectionOf(
|
|
204
|
-
const slides =
|
|
205
|
+
export function prepareRemoveSlides(presentation, indices) {
|
|
206
|
+
const list = selectionOf(presentation, indices, "Choose at least one slide to delete.");
|
|
207
|
+
const slides = presentation.slides;
|
|
205
208
|
if (list.length >= slides.length) throw fail("cannot-remove-all-slides", "A presentation needs at least one slide.", { count: slides.length });
|
|
206
209
|
const patches = [...list].reverse().map((index) => ({ op: "remove", path: slidePointer(index) }));
|
|
207
210
|
const remaining = slides.length - list.length;
|
|
208
|
-
return finish(
|
|
211
|
+
return finish(presentation, patches, { action: "remove", removed: list, selection: [Math.min(list[0], remaining - 1)] });
|
|
209
212
|
}
|
|
210
213
|
export function removeSlides(editor, indices, meta) {
|
|
211
214
|
requireEditor(editor);
|
|
212
|
-
return apply(editor, prepareRemoveSlides(editor.
|
|
215
|
+
return apply(editor, prepareRemoveSlides(editor.presentation, indices), meta, "remove");
|
|
213
216
|
}
|
|
214
217
|
|
|
215
218
|
// --- move ------------------------------------------------------------------------------------------
|
|
@@ -221,9 +224,9 @@ export function removeSlides(editor, indices, meta) {
|
|
|
221
224
|
* first slide's when moving to the start), `"keep"` leaves labels alone, a string sets that section and
|
|
222
225
|
* `null` clears it.
|
|
223
226
|
*/
|
|
224
|
-
export function prepareMoveSlides(
|
|
225
|
-
const list = selectionOf(
|
|
226
|
-
const slides =
|
|
227
|
+
export function prepareMoveSlides(presentation, indices, to, options = {}) {
|
|
228
|
+
const list = selectionOf(presentation, indices, "Choose at least one slide to move.");
|
|
229
|
+
const slides = presentation.slides;
|
|
227
230
|
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
231
|
const chosen = new Set(list);
|
|
229
232
|
const rest = slides.map((_, index) => index).filter((index) => !chosen.has(index));
|
|
@@ -237,18 +240,18 @@ export function prepareMoveSlides(document, indices, to, options = {}) {
|
|
|
237
240
|
const values = new Map(list.map((index) => [index, target ? withSection(slides[index], target.name) : slides[index]]));
|
|
238
241
|
const sameOrder = order.every((old, at) => old === at);
|
|
239
242
|
const sameValues = list.every((index) => same(values.get(index), slides[index]));
|
|
240
|
-
if (sameOrder && sameValues) return unchanged(
|
|
243
|
+
if (sameOrder && sameValues) return unchanged(presentation, { action: "move", selection: list });
|
|
241
244
|
// A pure relabel keeps the order: patch the labels in place instead of remove and add.
|
|
242
245
|
const moved = new Set(list);
|
|
243
246
|
const patches = sameOrder
|
|
244
247
|
? list.flatMap((index) => fieldPatch(slides[index], index, "section", sectionOf(values.get(index))))
|
|
245
248
|
: permutationPatches(moved, order, values);
|
|
246
249
|
const selection = list.map((old) => order.indexOf(old));
|
|
247
|
-
return finish(
|
|
250
|
+
return finish(presentation, patches, { action: "move", selection, order });
|
|
248
251
|
}
|
|
249
252
|
export function moveSlides(editor, indices, to, options = {}, meta) {
|
|
250
253
|
requireEditor(editor);
|
|
251
|
-
return apply(editor, prepareMoveSlides(editor.
|
|
254
|
+
return apply(editor, prepareMoveSlides(editor.presentation, indices, to, options), meta, "move");
|
|
252
255
|
}
|
|
253
256
|
|
|
254
257
|
/**
|
|
@@ -256,12 +259,12 @@ export function moveSlides(editor, indices, to, options = {}, meta) {
|
|
|
256
259
|
* scattered one moves each slide past its neighbour, like Alt+Arrow in a list. Moving past the first or last
|
|
257
260
|
* slide changes nothing (`changed` is false).
|
|
258
261
|
*/
|
|
259
|
-
export function prepareMoveSlidesBy(
|
|
260
|
-
const list = selectionOf(
|
|
261
|
-
const count =
|
|
262
|
-
if (!Number.isInteger(delta) || delta === 0) return unchanged(
|
|
262
|
+
export function prepareMoveSlidesBy(presentation, indices, delta, options = {}) {
|
|
263
|
+
const list = selectionOf(presentation, indices, "Choose at least one slide to move.");
|
|
264
|
+
const count = presentation.slides.length;
|
|
265
|
+
if (!Number.isInteger(delta) || delta === 0) return unchanged(presentation, { action: "move", selection: list });
|
|
263
266
|
const step = Math.sign(delta);
|
|
264
|
-
const order =
|
|
267
|
+
const order = presentation.slides.map((_, index) => index);
|
|
265
268
|
const selected = new Set(list);
|
|
266
269
|
for (let turn = 0; turn < Math.abs(delta); turn += 1) {
|
|
267
270
|
const sequence = step < 0 ? [...order.keys()] : [...order.keys()].reverse();
|
|
@@ -274,8 +277,8 @@ export function prepareMoveSlidesBy(document, indices, delta, options = {}) {
|
|
|
274
277
|
}
|
|
275
278
|
if (!moved) break;
|
|
276
279
|
}
|
|
277
|
-
if (order.every((old, at) => old === at)) return unchanged(
|
|
278
|
-
const slides =
|
|
280
|
+
if (order.every((old, at) => old === at)) return unchanged(presentation, { action: "move", selection: list });
|
|
281
|
+
const slides = presentation.slides;
|
|
279
282
|
const mode = options.section === undefined ? "adopt" : options.section;
|
|
280
283
|
// A moved slide adopts the section of the nearest slide that did not move: the one before it in the new order,
|
|
281
284
|
// else the one after it.
|
|
@@ -288,11 +291,11 @@ export function prepareMoveSlidesBy(document, indices, delta, options = {}) {
|
|
|
288
291
|
const name = mode === "keep" ? sectionOf(slides[index]) : mode === "adopt" ? adopted(order.indexOf(index)) : mode === null ? undefined : String(mode);
|
|
289
292
|
return [index, withSection(slides[index], name)];
|
|
290
293
|
}));
|
|
291
|
-
return finish(
|
|
294
|
+
return finish(presentation, permutationPatches(selected, order, values), { action: "move", selection: list.map((old) => order.indexOf(old)), order });
|
|
292
295
|
}
|
|
293
296
|
export function moveSlidesBy(editor, indices, delta, options = {}, meta) {
|
|
294
297
|
requireEditor(editor);
|
|
295
|
-
return apply(editor, prepareMoveSlidesBy(editor.
|
|
298
|
+
return apply(editor, prepareMoveSlidesBy(editor.presentation, indices, delta, options), meta, "move");
|
|
296
299
|
}
|
|
297
300
|
|
|
298
301
|
// --- hide ------------------------------------------------------------------------------------------
|
|
@@ -302,24 +305,24 @@ export function moveSlidesBy(editor, indices, delta, options = {}, meta) {
|
|
|
302
305
|
* navigator but is skipped when presenting; showing removes the `hidden` field instead of writing `false`.
|
|
303
306
|
* Without `hidden`, the slides toggle together: all hidden become shown, otherwise all become hidden.
|
|
304
307
|
*/
|
|
305
|
-
export function prepareSetHidden(
|
|
306
|
-
const list = selectionOf(
|
|
307
|
-
const slides =
|
|
308
|
+
export function prepareSetHidden(presentation, indices, hidden) {
|
|
309
|
+
const list = selectionOf(presentation, indices, "Choose at least one slide.");
|
|
310
|
+
const slides = presentation.slides;
|
|
308
311
|
const value = hidden === undefined ? !list.every((index) => slides[index].hidden === true) : Boolean(hidden);
|
|
309
312
|
const patches = list.flatMap((index) => fieldPatch(slides[index], index, "hidden", value ? true : undefined));
|
|
310
313
|
// A stored `hidden: false` counts as shown; showing it removes the field, hiding replaces it.
|
|
311
|
-
return finish(
|
|
314
|
+
return finish(presentation, patches, { action: value ? "hide" : "show", hidden: value, selection: list });
|
|
312
315
|
}
|
|
313
316
|
export function setHidden(editor, indices, hidden, meta) {
|
|
314
317
|
requireEditor(editor);
|
|
315
|
-
const prepared = prepareSetHidden(editor.
|
|
318
|
+
const prepared = prepareSetHidden(editor.presentation, indices, hidden);
|
|
316
319
|
return apply(editor, prepared, meta, prepared.action);
|
|
317
320
|
}
|
|
318
321
|
|
|
319
322
|
// --- sections --------------------------------------------------------------------------------------
|
|
320
323
|
|
|
321
|
-
function sectionAt(
|
|
322
|
-
const section = listSections(
|
|
324
|
+
function sectionAt(presentation, sectionIndex) {
|
|
325
|
+
const section = listSections(presentation)[sectionIndex];
|
|
323
326
|
if (!section) throw fail("section-index-out-of-range", `Section ${sectionIndex} does not exist.`, { sectionIndex });
|
|
324
327
|
return section;
|
|
325
328
|
}
|
|
@@ -329,24 +332,24 @@ const cleanName = (name) => (typeof name === "string" ? name.trim() : name);
|
|
|
329
332
|
* Compute giving slides a section label (`name`), or clearing it (`null`). The slides keep their order, so
|
|
330
333
|
* labelling a middle slide splits its section in two runs; use `prepareAddSection` to start a section.
|
|
331
334
|
*/
|
|
332
|
-
export function prepareSetSection(
|
|
333
|
-
const list = selectionOf(
|
|
335
|
+
export function prepareSetSection(presentation, indices, name) {
|
|
336
|
+
const list = selectionOf(presentation, indices, "Choose at least one slide.");
|
|
334
337
|
const label = name === null || name === undefined ? undefined : cleanName(name);
|
|
335
338
|
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(
|
|
339
|
+
const patches = list.flatMap((index) => fieldPatch(presentation.slides[index], index, "section", label));
|
|
340
|
+
return finish(presentation, patches, { action: "section", section: label, selection: list });
|
|
338
341
|
}
|
|
339
342
|
export function setSection(editor, indices, name, meta) {
|
|
340
343
|
requireEditor(editor);
|
|
341
|
-
return apply(editor, prepareSetSection(editor.
|
|
344
|
+
return apply(editor, prepareSetSection(editor.presentation, indices, name), meta, "section");
|
|
342
345
|
}
|
|
343
346
|
|
|
344
347
|
/**
|
|
345
348
|
* Compute starting a section at a slide: that slide and the slides after it up to the end of its current
|
|
346
349
|
* section take `name` (PowerPoint's "Add section", whose header sits above the chosen slide).
|
|
347
350
|
*/
|
|
348
|
-
export function prepareAddSection(
|
|
349
|
-
const slides = requireSlides(
|
|
351
|
+
export function prepareAddSection(presentation, slideIndex, name) {
|
|
352
|
+
const slides = requireSlides(presentation);
|
|
350
353
|
if (!isIndex(slideIndex) || slideIndex >= slides.length) throw fail("slide-index-out-of-range", `Slide ${slideIndex} does not exist.`, { slideIndex });
|
|
351
354
|
const label = cleanName(name);
|
|
352
355
|
if (typeof label !== "string" || label === "") throw fail("invalid-section-name", "Give the section a name.");
|
|
@@ -358,25 +361,25 @@ export function prepareAddSection(document, slideIndex, name) {
|
|
|
358
361
|
})();
|
|
359
362
|
const indices = Array.from({ length: end - slideIndex + 1 }, (_, offset) => slideIndex + offset);
|
|
360
363
|
const patches = indices.flatMap((index) => fieldPatch(slides[index], index, "section", label));
|
|
361
|
-
return finish(
|
|
364
|
+
return finish(presentation, patches, { action: "add-section", section: label, selection: indices });
|
|
362
365
|
}
|
|
363
366
|
export function addSection(editor, slideIndex, name, meta) {
|
|
364
367
|
requireEditor(editor);
|
|
365
|
-
return apply(editor, prepareAddSection(editor.
|
|
368
|
+
return apply(editor, prepareAddSection(editor.presentation, slideIndex, name), meta, "add-section");
|
|
366
369
|
}
|
|
367
370
|
|
|
368
371
|
/** Compute renaming a section (an index from `listSections`) for all of its slides. */
|
|
369
|
-
export function prepareRenameSection(
|
|
370
|
-
const section = sectionAt(
|
|
372
|
+
export function prepareRenameSection(presentation, sectionIndex, name) {
|
|
373
|
+
const section = sectionAt(presentation, sectionIndex);
|
|
371
374
|
const label = cleanName(name);
|
|
372
375
|
if (typeof label !== "string" || label === "") throw fail("invalid-section-name", "Give the section a name.");
|
|
373
376
|
const indices = Array.from({ length: section.count }, (_, offset) => section.start + offset);
|
|
374
|
-
const patches = indices.flatMap((index) => fieldPatch(
|
|
375
|
-
return finish(
|
|
377
|
+
const patches = indices.flatMap((index) => fieldPatch(presentation.slides[index], index, "section", label));
|
|
378
|
+
return finish(presentation, patches, { action: "rename-section", section: label, selection: indices });
|
|
376
379
|
}
|
|
377
380
|
export function renameSection(editor, sectionIndex, name, meta) {
|
|
378
381
|
requireEditor(editor);
|
|
379
|
-
return apply(editor, prepareRenameSection(editor.
|
|
382
|
+
return apply(editor, prepareRenameSection(editor.presentation, sectionIndex, name), meta, "rename-section");
|
|
380
383
|
}
|
|
381
384
|
|
|
382
385
|
/**
|
|
@@ -384,37 +387,37 @@ export function renameSection(editor, sectionIndex, name, meta) {
|
|
|
384
387
|
* lose their label). With `deleteSlides: true` the slides are deleted too (not allowed when they are every
|
|
385
388
|
* slide of the deck).
|
|
386
389
|
*/
|
|
387
|
-
export function prepareRemoveSection(
|
|
388
|
-
const sections = listSections(
|
|
389
|
-
const section = sectionAt(
|
|
390
|
+
export function prepareRemoveSection(presentation, sectionIndex, options = {}) {
|
|
391
|
+
const sections = listSections(presentation);
|
|
392
|
+
const section = sectionAt(presentation, sectionIndex);
|
|
390
393
|
const indices = Array.from({ length: section.count }, (_, offset) => section.start + offset);
|
|
391
|
-
if (options.deleteSlides) return { ...prepareRemoveSlides(
|
|
394
|
+
if (options.deleteSlides) return { ...prepareRemoveSlides(presentation, indices), action: "remove-section" };
|
|
392
395
|
const previous = sections[sectionIndex - 1];
|
|
393
396
|
const label = previous?.name;
|
|
394
|
-
const patches = indices.flatMap((index) => fieldPatch(
|
|
395
|
-
return finish(
|
|
397
|
+
const patches = indices.flatMap((index) => fieldPatch(presentation.slides[index], index, "section", label));
|
|
398
|
+
return finish(presentation, patches, { action: "remove-section", section: label, selection: indices });
|
|
396
399
|
}
|
|
397
400
|
export function removeSection(editor, sectionIndex, options = {}, meta) {
|
|
398
401
|
requireEditor(editor);
|
|
399
|
-
return apply(editor, prepareRemoveSection(editor.
|
|
402
|
+
return apply(editor, prepareRemoveSection(editor.presentation, sectionIndex, options), meta, "remove-section");
|
|
400
403
|
}
|
|
401
404
|
|
|
402
405
|
/**
|
|
403
406
|
* Compute moving a whole section to the gap `to` among the sections (0 to the section count, in the
|
|
404
407
|
* original order of `listSections`). The slides keep their labels.
|
|
405
408
|
*/
|
|
406
|
-
export function prepareMoveSection(
|
|
407
|
-
const sections = listSections(
|
|
408
|
-
const section = sectionAt(
|
|
409
|
+
export function prepareMoveSection(presentation, sectionIndex, to) {
|
|
410
|
+
const sections = listSections(presentation);
|
|
411
|
+
const section = sectionAt(presentation, sectionIndex);
|
|
409
412
|
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 ?
|
|
413
|
+
const gap = to === sections.length ? presentation.slides.length : sections[to].start;
|
|
411
414
|
const indices = Array.from({ length: section.count }, (_, offset) => section.start + offset);
|
|
412
|
-
const prepared = prepareMoveSlides(
|
|
415
|
+
const prepared = prepareMoveSlides(presentation, indices, gap, { section: "keep" });
|
|
413
416
|
return { ...prepared, action: "move-section", section: section.name };
|
|
414
417
|
}
|
|
415
418
|
export function moveSection(editor, sectionIndex, to, meta) {
|
|
416
419
|
requireEditor(editor);
|
|
417
|
-
return apply(editor, prepareMoveSection(editor.
|
|
420
|
+
return apply(editor, prepareMoveSection(editor.presentation, sectionIndex, to), meta, "move-section");
|
|
418
421
|
}
|
|
419
422
|
|
|
420
423
|
/** Every slide operation as a name to its `prepare` function, for hosts that map a command id to a change. */
|
package/dist/svelte.js
CHANGED
|
@@ -50,9 +50,9 @@ export function opfCatalogSelect(node, params) {
|
|
|
50
50
|
node.setAttribute?.("data-opf-catalog-kind", catalogKind);
|
|
51
51
|
if (label) node.setAttribute?.("aria-label", label);
|
|
52
52
|
replaceOptions(node, getCatalogOptions(catalogKind, {
|
|
53
|
+
editor,
|
|
53
54
|
catalogs: nextParams.catalogs,
|
|
54
|
-
|
|
55
|
-
presentation: nextParams.presentation ?? editor.document
|
|
55
|
+
presentation: nextParams.presentation ?? editor.presentation
|
|
56
56
|
}));
|
|
57
57
|
|
|
58
58
|
const sync = () => {
|
|
@@ -61,7 +61,6 @@ export function opfCatalogSelect(node, params) {
|
|
|
61
61
|
};
|
|
62
62
|
const onChange = () => setCatalogId(editor, path, catalogKind, node.value, {
|
|
63
63
|
catalogs: nextParams.catalogs,
|
|
64
|
-
catalogSources: nextParams.catalogSources,
|
|
65
64
|
presentation: nextParams.presentation,
|
|
66
65
|
source: "svelte-catalog-select"
|
|
67
66
|
});
|