@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/content-actions.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// Content actions (RR-26): list levels, grouping, regions, images between content and design, and slide
|
|
1
|
+
// Content actions (RR-26): list levels, grouping, regions, images between content and design (background or watermark), and slide
|
|
2
2
|
// split and merge, as editor transactions. The pure transforms live in core (`@openpresentation/opf/convert`);
|
|
3
3
|
// each action here finds its target in the document, calls the transform, turns the result into a guarded patch
|
|
4
4
|
// (`test` of what was read, then `replace`, or per-slide `remove` and `add`), validates the document and, in the
|
|
5
|
-
// apply form, applies it as ONE undoable step. Every prepare function returns `{
|
|
5
|
+
// apply form, applies it as ONE undoable step. Every prepare function returns `{ presentation, patches, changed,
|
|
6
6
|
// lossless, loss, path }` without touching a session; a refusal throws `content-action-refused`.
|
|
7
7
|
import {
|
|
8
8
|
OPFConversionError,
|
|
@@ -19,9 +19,10 @@ import {
|
|
|
19
19
|
unwrapGroup,
|
|
20
20
|
wrapBlocks,
|
|
21
21
|
} from "@openpresentation/opf/convert";
|
|
22
|
-
import { applyJsonPatch, getValueAtPath, opfPathToJsonPointer, splitOpfPath
|
|
22
|
+
import { applyJsonPatch, getValueAtPath, opfPathToJsonPointer, splitOpfPath } from "./index.js";
|
|
23
23
|
import { readBlockContent } from "./block-convert.js";
|
|
24
24
|
import { checkedDocument, fail } from "./edit-helpers.js";
|
|
25
|
+
import { checkFormat } from "./checks.js";
|
|
25
26
|
|
|
26
27
|
const refuse = (message, details) => fail("content-action-refused", message, details);
|
|
27
28
|
|
|
@@ -39,14 +40,14 @@ function requireEditor(editor) {
|
|
|
39
40
|
if (!editor || typeof editor.applyPatch !== "function") throw fail("invalid-editor", "Expected an editor session created by createEditorSession.");
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
function slideParts(
|
|
43
|
+
function slideParts(presentation, path, what) {
|
|
43
44
|
let parts;
|
|
44
45
|
try {
|
|
45
46
|
parts = splitOpfPath(path);
|
|
46
47
|
} catch {
|
|
47
48
|
parts = undefined;
|
|
48
49
|
}
|
|
49
|
-
if (!parts || parts[0] !== "slides" || !/^(0|[1-9][0-9]*)$/.test(parts[1] ?? "") || !
|
|
50
|
+
if (!parts || parts[0] !== "slides" || !/^(0|[1-9][0-9]*)$/.test(parts[1] ?? "") || !presentation?.slides?.[Number(parts[1])]) throw refuse(`Choose ${what} on a slide.`, { path });
|
|
50
51
|
return { slideIndex: Number(parts[1]), relative: parts.slice(2), parts };
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -54,27 +55,27 @@ const asNumbers = (path) => path.map((part) => (/^(0|[1-9][0-9]*)$/.test(String(
|
|
|
54
55
|
const toDocumentPath = (slideIndex, relative) => ["slides", String(slideIndex), ...(relative ?? []).map(String)].join(".");
|
|
55
56
|
|
|
56
57
|
/** Replace one value (a slide, a block, a list) as `test` + `replace`, validated like every other edit. */
|
|
57
|
-
function replacePatches(
|
|
58
|
+
function replacePatches(presentation, parts, next) {
|
|
58
59
|
const pointer = opfPathToJsonPointer(parts);
|
|
59
60
|
return [
|
|
60
|
-
{ op: "test", path: pointer, value: structuredClone(getValueAtPath(
|
|
61
|
+
{ op: "test", path: pointer, value: structuredClone(getValueAtPath(presentation, parts)) },
|
|
61
62
|
{ op: "replace", path: pointer, value: next },
|
|
62
63
|
];
|
|
63
64
|
}
|
|
64
65
|
// `validate: false` skips the whole-document check, for a dry run that only needs the loss report (core has already
|
|
65
66
|
// validated the changed slide); applying always validates.
|
|
66
|
-
function finish(
|
|
67
|
-
if (!validate) return {
|
|
68
|
-
const before =
|
|
69
|
-
return {
|
|
67
|
+
function finish(presentation, patches, extra, validate = true) {
|
|
68
|
+
if (!validate) return { presentation: applyJsonPatch(presentation, patches), patches, ...extra };
|
|
69
|
+
const before = checkFormat(presentation);
|
|
70
|
+
return { presentation: checkedDocument(presentation, patches, before), patches, ...extra };
|
|
70
71
|
}
|
|
71
72
|
function apply(editor, prepared, meta, source) {
|
|
72
|
-
if (!prepared.changed) return { ...prepared,
|
|
73
|
+
if (!prepared.changed) return { ...prepared, presentation: editor.presentation, patches: [], inversePatches: [], validation: editor.validation };
|
|
73
74
|
const change = editor.applyPatch(prepared.patches, { ...meta, source: meta.source ?? source, path: prepared.path });
|
|
74
75
|
const extra = Object.fromEntries(["levels", "range", "slideCount", "pages"].filter((key) => prepared[key] !== undefined).map((key) => [key, prepared[key]]));
|
|
75
76
|
return { ...change, changed: true, lossless: prepared.lossless, loss: prepared.loss, path: prepared.path, ...extra };
|
|
76
77
|
}
|
|
77
|
-
const unchanged = (
|
|
78
|
+
const unchanged = (presentation, path, reason) => ({ presentation: structuredClone(presentation), patches: [], path, changed: false, lossless: true, loss: [], ...(reason ? { reason } : {}) });
|
|
78
79
|
|
|
79
80
|
// --- list levels -------------------------------------------------------------------------------
|
|
80
81
|
|
|
@@ -91,75 +92,75 @@ export function listItemIndexForSelection(selectedPath, blockPath) {
|
|
|
91
92
|
* item cannot be nested, and items under a moved item move with it (`options.withChildren`, default true).
|
|
92
93
|
* `changed` is false, with a `reason`, when nothing can move.
|
|
93
94
|
*/
|
|
94
|
-
export function prepareListShift(
|
|
95
|
-
const found = readBlockContent(
|
|
95
|
+
export function prepareListShift(presentation, blockPath, indices, delta, options = {}) {
|
|
96
|
+
const found = readBlockContent(presentation, blockPath);
|
|
96
97
|
if (!found || found.kind !== "list") throw refuse("Choose a list block.", { path: blockPath });
|
|
97
98
|
const result = core(() => shiftListLevels(found.owner, indices, delta, options));
|
|
98
99
|
const path = found.path.join(".");
|
|
99
|
-
if (!result.changed) return unchanged(
|
|
100
|
-
return finish(
|
|
100
|
+
if (!result.changed) return unchanged(presentation, path, result.reason);
|
|
101
|
+
return finish(presentation, replacePatches(presentation, found.path, result.payload), { path, changed: true, lossless: result.lossless, loss: result.loss, levels: result.levels }, options.validate !== false);
|
|
101
102
|
}
|
|
102
103
|
/** Nest or un-nest list items as one undoable step. Returns the session change plus `changed`, `reason`, `levels`, `lossless` and `loss`. */
|
|
103
104
|
export function shiftListItems(editor, blockPath, indices, delta, options = {}, meta = {}) {
|
|
104
105
|
requireEditor(editor);
|
|
105
|
-
return apply(editor, prepareListShift(editor.
|
|
106
|
+
return apply(editor, prepareListShift(editor.presentation, blockPath, indices, delta, options), meta, "list-level");
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
// --- structure inside one slide ----------------------------------------------------------------
|
|
109
110
|
|
|
110
111
|
/** One slide-level transform as a guarded replace of that slide. */
|
|
111
|
-
function slideAction(
|
|
112
|
-
const slide =
|
|
112
|
+
function slideAction(presentation, slideIndex, transform, targetPath, validate = true) {
|
|
113
|
+
const slide = presentation.slides[slideIndex];
|
|
113
114
|
const result = core(() => transform(slide));
|
|
114
115
|
const path = result.path ? toDocumentPath(slideIndex, result.path) : (targetPath ?? `slides.${slideIndex}`);
|
|
115
|
-
if (!result.changed) return unchanged(
|
|
116
|
-
const patches = replacePatches(
|
|
117
|
-
return finish(
|
|
116
|
+
if (!result.changed) return unchanged(presentation, path);
|
|
117
|
+
const patches = replacePatches(presentation, ["slides", String(slideIndex)], result.slide);
|
|
118
|
+
return finish(presentation, patches, { path, changed: true, lossless: result.lossless, loss: result.loss }, validate);
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
/** Wrap the blocks at `indices` of the container at `containerPath` (a slide `slides.0`, a group or a region group) in a new group. */
|
|
121
|
-
export function prepareGroupBlocks(
|
|
122
|
-
const { slideIndex, relative } = slideParts(
|
|
123
|
-
return slideAction(
|
|
122
|
+
export function prepareGroupBlocks(presentation, containerPath, indices, options = {}) {
|
|
123
|
+
const { slideIndex, relative } = slideParts(presentation, containerPath, "a container");
|
|
124
|
+
return slideAction(presentation, slideIndex, (slide) => wrapBlocks(slide, indices, { container: asNumbers(relative), ...(options.composition ? { composition: options.composition } : {}) }), undefined, options.validate !== false);
|
|
124
125
|
}
|
|
125
126
|
/** Replace the group at `groupPath` by its blocks. What the group carried (composition, id) is reported in `loss`. */
|
|
126
|
-
export function prepareUngroupBlock(
|
|
127
|
-
const { slideIndex, relative } = slideParts(
|
|
128
|
-
return slideAction(
|
|
127
|
+
export function prepareUngroupBlock(presentation, groupPath, options = {}) {
|
|
128
|
+
const { slideIndex, relative } = slideParts(presentation, groupPath, "a group");
|
|
129
|
+
return slideAction(presentation, slideIndex, (slide) => unwrapGroup(slide, asNumbers(relative)), undefined, options.validate !== false);
|
|
129
130
|
}
|
|
130
131
|
/** Place every block of a slide in its own named region: `regions[i]` is block i's region (`left`, `right`, `top:left`, ...). */
|
|
131
|
-
export function prepareBlocksToRegions(
|
|
132
|
-
slideParts(
|
|
133
|
-
return slideAction(
|
|
132
|
+
export function prepareBlocksToRegions(presentation, slideIndex, regions, options = {}) {
|
|
133
|
+
slideParts(presentation, `slides.${slideIndex}`, "a slide");
|
|
134
|
+
return slideAction(presentation, slideIndex, (slide) => blocksToRegions(slide, regions), undefined, options.validate !== false);
|
|
134
135
|
}
|
|
135
136
|
/** Turn the named regions of a slide into ordinary blocks in reading order. The regions' placement is reported in `loss`. */
|
|
136
|
-
export function prepareRegionsToBlocks(
|
|
137
|
-
slideParts(
|
|
138
|
-
return slideAction(
|
|
137
|
+
export function prepareRegionsToBlocks(presentation, slideIndex, options = {}) {
|
|
138
|
+
slideParts(presentation, `slides.${slideIndex}`, "a slide");
|
|
139
|
+
return slideAction(presentation, slideIndex, (slide) => regionsToBlocks(slide), undefined, options.validate !== false);
|
|
139
140
|
}
|
|
140
141
|
/** Move the content of region `from` to region `to`, or swap them when `to` is taken and `options.swap` is true. */
|
|
141
|
-
export function prepareMoveRegion(
|
|
142
|
-
slideParts(
|
|
143
|
-
return slideAction(
|
|
142
|
+
export function prepareMoveRegion(presentation, slideIndex, from, to, options = {}) {
|
|
143
|
+
slideParts(presentation, `slides.${slideIndex}`, "a slide");
|
|
144
|
+
return slideAction(presentation, slideIndex, (slide) => moveRegion(slide, from, to, options), undefined, options.validate !== false);
|
|
144
145
|
}
|
|
145
|
-
/** Move the image block at `blockPath` into the slide's design as
|
|
146
|
-
export function prepareImageToDesign(
|
|
147
|
-
const { slideIndex, relative } = slideParts(
|
|
148
|
-
return slideAction(
|
|
146
|
+
/** Move the image block at `blockPath` into the slide's design as its `background` or `watermark` (core's `promoteImage`). */
|
|
147
|
+
export function prepareImageToDesign(presentation, blockPath, target, options = {}) {
|
|
148
|
+
const { slideIndex, relative } = slideParts(presentation, blockPath, "an image block");
|
|
149
|
+
return slideAction(presentation, slideIndex, (slide) => promoteImage(slide, asNumbers(relative), target, options), `slides.${slideIndex}`, options.validate !== false);
|
|
149
150
|
}
|
|
150
|
-
/** Move a slide's own `
|
|
151
|
-
export function prepareImageToContent(
|
|
152
|
-
slideParts(
|
|
153
|
-
return slideAction(
|
|
151
|
+
/** Move a slide's own `background` image or `watermark` back into its content as an image block (core's `demoteImage`). */
|
|
152
|
+
export function prepareImageToContent(presentation, slideIndex, source, options = {}) {
|
|
153
|
+
slideParts(presentation, `slides.${slideIndex}`, "a slide");
|
|
154
|
+
return slideAction(presentation, slideIndex, (slide) => demoteImage(slide, source, options), undefined, options.validate !== false);
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
export const groupBlocks = (editor, containerPath, indices, options = {}, meta = {}) => (requireEditor(editor), apply(editor, prepareGroupBlocks(editor.
|
|
157
|
-
export const ungroupBlock = (editor, groupPath, meta = {}) => (requireEditor(editor), apply(editor, prepareUngroupBlock(editor.
|
|
158
|
-
export const placeBlocksInRegions = (editor, slideIndex, regions, meta = {}) => (requireEditor(editor), apply(editor, prepareBlocksToRegions(editor.
|
|
159
|
-
export const regionsAsBlocks = (editor, slideIndex, meta = {}) => (requireEditor(editor), apply(editor, prepareRegionsToBlocks(editor.
|
|
160
|
-
export const moveSlideRegion = (editor, slideIndex, from, to, options = {}, meta = {}) => (requireEditor(editor), apply(editor, prepareMoveRegion(editor.
|
|
161
|
-
export const moveImageToDesign = (editor, blockPath, target, options = {}, meta = {}) => (requireEditor(editor), apply(editor, prepareImageToDesign(editor.
|
|
162
|
-
export const moveImageToContent = (editor, slideIndex, source, options = {}, meta = {}) => (requireEditor(editor), apply(editor, prepareImageToContent(editor.
|
|
157
|
+
export const groupBlocks = (editor, containerPath, indices, options = {}, meta = {}) => (requireEditor(editor), apply(editor, prepareGroupBlocks(editor.presentation, containerPath, indices, options), meta, "group-blocks"));
|
|
158
|
+
export const ungroupBlock = (editor, groupPath, meta = {}) => (requireEditor(editor), apply(editor, prepareUngroupBlock(editor.presentation, groupPath), meta, "ungroup-block"));
|
|
159
|
+
export const placeBlocksInRegions = (editor, slideIndex, regions, meta = {}) => (requireEditor(editor), apply(editor, prepareBlocksToRegions(editor.presentation, slideIndex, regions), meta, "blocks-to-regions"));
|
|
160
|
+
export const regionsAsBlocks = (editor, slideIndex, meta = {}) => (requireEditor(editor), apply(editor, prepareRegionsToBlocks(editor.presentation, slideIndex), meta, "regions-to-blocks"));
|
|
161
|
+
export const moveSlideRegion = (editor, slideIndex, from, to, options = {}, meta = {}) => (requireEditor(editor), apply(editor, prepareMoveRegion(editor.presentation, slideIndex, from, to, options), meta, "move-region"));
|
|
162
|
+
export const moveImageToDesign = (editor, blockPath, target, options = {}, meta = {}) => (requireEditor(editor), apply(editor, prepareImageToDesign(editor.presentation, blockPath, target, options), meta, "image-to-design"));
|
|
163
|
+
export const moveImageToContent = (editor, slideIndex, source, options = {}, meta = {}) => (requireEditor(editor), apply(editor, prepareImageToContent(editor.presentation, slideIndex, source, options), meta, "image-to-content"));
|
|
163
164
|
|
|
164
165
|
// --- slides ------------------------------------------------------------------------------------
|
|
165
166
|
|
|
@@ -168,8 +169,8 @@ export const moveImageToContent = (editor, slideIndex, source, options = {}, met
|
|
|
168
169
|
* replaced slide, a `replace` for each one that changed, `remove` for the surplus old slides and `add` for the
|
|
169
170
|
* surplus new ones, so a split or merge stays one guarded, undoable transaction without rewriting the other slides.
|
|
170
171
|
*/
|
|
171
|
-
function slidePatches(
|
|
172
|
-
const old =
|
|
172
|
+
function slidePatches(presentation, range, slides) {
|
|
173
|
+
const old = presentation.slides.slice(range.start, range.start + range.deleteCount);
|
|
173
174
|
const patches = old.map((slide, offset) => ({ op: "test", path: `/slides/${range.start + offset}`, value: structuredClone(slide) }));
|
|
174
175
|
const common = Math.min(old.length, slides.length);
|
|
175
176
|
for (let offset = 0; offset < common; offset++) if (JSON.stringify(old[offset]) !== JSON.stringify(slides[offset])) patches.push({ op: "replace", path: `/slides/${range.start + offset}`, value: structuredClone(slides[offset]) });
|
|
@@ -177,31 +178,31 @@ function slidePatches(document, range, slides) {
|
|
|
177
178
|
for (let offset = common; offset < slides.length; offset++) patches.push({ op: "add", path: `/slides/${range.start + offset}`, value: structuredClone(slides[offset]) });
|
|
178
179
|
return patches;
|
|
179
180
|
}
|
|
180
|
-
function slidesAction(
|
|
181
|
+
function slidesAction(presentation, result, extra = {}) {
|
|
181
182
|
const path = `slides.${result.range.start}`;
|
|
182
|
-
if (!result.changed) return unchanged(
|
|
183
|
-
return finish(
|
|
183
|
+
if (!result.changed) return unchanged(presentation, path, "Nothing to change.");
|
|
184
|
+
return finish(presentation, slidePatches(presentation, result.range, result.slides), { path, changed: true, lossless: result.lossless, loss: result.loss, range: result.range, slideCount: result.slides.length, ...extra });
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
/** Split a slide by its blocks (`options.at`: block indexes where a new slide starts, or `options.each`). See core's `splitSlide`. */
|
|
187
|
-
export function prepareSplitSlide(
|
|
188
|
-
return slidesAction(
|
|
188
|
+
export function prepareSplitSlide(presentation, slideIndex, options = {}) {
|
|
189
|
+
return slidesAction(presentation, core(() => splitSlide(presentation, slideIndex, options)));
|
|
189
190
|
}
|
|
190
|
-
/** Split a slide that overflows into pages with the existing pagination (`options`: text measurement, fonts, as for `
|
|
191
|
-
export function prepareSplitSlideOnOverflow(
|
|
192
|
-
const result = core(() => splitSlideOnOverflowCore(
|
|
193
|
-
return slidesAction(
|
|
191
|
+
/** Split a slide that overflows into pages with the existing pagination (`options`: text measurement, fonts, as for core `paginate`). `pages` maps the new slides back for `prepareUnpaginate`. */
|
|
192
|
+
export function prepareSplitSlideOnOverflow(presentation, slideIndex, options = {}) {
|
|
193
|
+
const result = core(() => splitSlideOnOverflowCore(presentation, slideIndex, options));
|
|
194
|
+
return slidesAction(presentation, result, { pages: result.pages });
|
|
194
195
|
}
|
|
195
196
|
/** Merge `count` consecutive slides from `start`. The first slide's id, headings and design win; what is dropped is reported in `loss`. */
|
|
196
|
-
export function prepareMergeSlides(
|
|
197
|
-
return slidesAction(
|
|
197
|
+
export function prepareMergeSlides(presentation, start, count = 2) {
|
|
198
|
+
return slidesAction(presentation, core(() => mergeSlidesCore(presentation, start, count)));
|
|
198
199
|
}
|
|
199
|
-
/** Put the continuation slides of a pagination back into one slide each. `pages` is the mapping `splitSlideOnOverflow` or `
|
|
200
|
-
export function prepareUnpaginate(
|
|
201
|
-
return slidesAction(
|
|
200
|
+
/** Put the continuation slides of a pagination back into one slide each. `pages` is the mapping `splitSlideOnOverflow` or core `paginate` returned. */
|
|
201
|
+
export function prepareUnpaginate(presentation, pages, options = {}) {
|
|
202
|
+
return slidesAction(presentation, core(() => unpaginate(presentation, pages, options)));
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
export const splitSlideByBlocks = (editor, slideIndex, options = {}, meta = {}) => (requireEditor(editor), apply(editor, prepareSplitSlide(editor.
|
|
205
|
-
export const splitSlideOnOverflow = (editor, slideIndex, options = {}, meta = {}) => (requireEditor(editor), apply(editor, prepareSplitSlideOnOverflow(editor.
|
|
206
|
-
export const mergeSlides = (editor, start, count = 2, meta = {}) => (requireEditor(editor), apply(editor, prepareMergeSlides(editor.
|
|
207
|
-
export const unpaginateSlides = (editor, pages, options = {}, meta = {}) => (requireEditor(editor), apply(editor, prepareUnpaginate(editor.
|
|
205
|
+
export const splitSlideByBlocks = (editor, slideIndex, options = {}, meta = {}) => (requireEditor(editor), apply(editor, prepareSplitSlide(editor.presentation, slideIndex, options), meta, "split-slide"));
|
|
206
|
+
export const splitSlideOnOverflow = (editor, slideIndex, options = {}, meta = {}) => (requireEditor(editor), apply(editor, prepareSplitSlideOnOverflow(editor.presentation, slideIndex, options), meta, "split-slide-overflow"));
|
|
207
|
+
export const mergeSlides = (editor, start, count = 2, meta = {}) => (requireEditor(editor), apply(editor, prepareMergeSlides(editor.presentation, start, count), meta, "merge-slides"));
|
|
208
|
+
export const unpaginateSlides = (editor, pages, options = {}, meta = {}) => (requireEditor(editor), apply(editor, prepareUnpaginate(editor.presentation, pages, options), meta, "unpaginate-slides"));
|
package/dist/content-controls.js
CHANGED
|
@@ -36,20 +36,20 @@ export const REGION_LAYOUTS = Object.freeze({
|
|
|
36
36
|
4: [{ id: "grid", label: "Two by two", regions: ["top:left", "top:right", "bottom:left", "bottom:right"] }],
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Where an image block can go in the slide's design (OPF 0.15): its background (a full-bleed photo behind the content) or its
|
|
41
|
+
* watermark. Bleeding an image to one edge beside the content is not a move: it is the block's `placement` (image-options.js).
|
|
42
|
+
*/
|
|
40
43
|
export const IMAGE_DESTINATIONS = Object.freeze([
|
|
41
|
-
{ value: "
|
|
42
|
-
{ value: "slideImage:left", label: "Slide image, on the left", target: "slideImage", options: { position: "left" } },
|
|
43
|
-
{ value: "slideImage:top", label: "Slide image, along the top", target: "slideImage", options: { position: "top" } },
|
|
44
|
-
{ value: "slideImage:bottom", label: "Slide image, along the bottom", target: "slideImage", options: { position: "bottom" } },
|
|
45
|
-
{ value: "slideImage:background", label: "Slide image, full slide behind the content", target: "slideImage", options: { position: "background" } },
|
|
46
|
-
{ value: "background", label: "Slide background", target: "background", options: {} },
|
|
44
|
+
{ value: "background", label: "Slide background, full slide behind the content", target: "background", options: {} },
|
|
47
45
|
{ value: "watermark", label: "Watermark", target: "watermark", options: {} },
|
|
48
46
|
]);
|
|
49
47
|
|
|
50
48
|
const REGION_KEY = /^(?:(?:top|middle|bottom)(?:\+(?:top|middle|bottom))*(?::(?:left|center|right)(?:\+(?:left|center|right))*)?|(?:left|center|right)(?:\+(?:left|center|right))*)$/;
|
|
51
49
|
const regionKeysOfSlide = (slide) => Object.keys(slide).filter((key) => REGION_KEY.test(key));
|
|
52
|
-
const DESIGN_IMAGE_LABELS = {
|
|
50
|
+
const DESIGN_IMAGE_LABELS = { background: "background image", watermark: "watermark" };
|
|
51
|
+
// An image block's own framing fields (FA-22) do not stop it being "a block that holds only an image".
|
|
52
|
+
const IMAGE_BLOCK_FIELDS = new Set(["id", "extensions", "type", "fit", "focus", "aspectRatio", "shape", "cornerRadius", "border", "opacity", "recolor", "overlay", "placement"]);
|
|
53
53
|
const messageOf = (error) => error?.issues?.[0]?.message ?? error?.message ?? String(error);
|
|
54
54
|
const lossText = (prepared) => (prepared.loss.length ? `Not carried over: ${prepared.loss.join(", ")}.` : "Nothing is lost.");
|
|
55
55
|
|
|
@@ -104,7 +104,7 @@ export function createContentControls(ctx) {
|
|
|
104
104
|
const prepare = (delta) => (state) => {
|
|
105
105
|
const item = index(state);
|
|
106
106
|
if (item === undefined) return { changed: false, loss: [], reason: "Select a list item to change its level." };
|
|
107
|
-
return prepareListShift(editor.
|
|
107
|
+
return prepareListShift(editor.presentation, state.blockPath, [item], delta, { validate: false });
|
|
108
108
|
};
|
|
109
109
|
const apply = (delta) => () => {
|
|
110
110
|
const state = ctxState.current;
|
|
@@ -140,7 +140,7 @@ export function createContentControls(ctx) {
|
|
|
140
140
|
updaters.push((state) => {
|
|
141
141
|
metrics.wrap.hidden = !state.metricGroupPath;
|
|
142
142
|
if (!state.metricGroupPath) return;
|
|
143
|
-
const targets = blockConversionTargets(editor.
|
|
143
|
+
const targets = blockConversionTargets(editor.presentation, state.metricGroupPath);
|
|
144
144
|
metrics.setOptions(
|
|
145
145
|
targets.map((target) => ({
|
|
146
146
|
value: target.kind,
|
|
@@ -172,7 +172,7 @@ export function createContentControls(ctx) {
|
|
|
172
172
|
prepare: (state) => {
|
|
173
173
|
const indices = next(state);
|
|
174
174
|
if (!indices) return { changed: false, loss: [], reason: "There is no block after this one in its container." };
|
|
175
|
-
return prepareGroupBlocks(editor.
|
|
175
|
+
return prepareGroupBlocks(editor.presentation, containerPath(state), indices, { validate: false });
|
|
176
176
|
},
|
|
177
177
|
apply: () => {
|
|
178
178
|
const state = ctxState.current;
|
|
@@ -190,7 +190,7 @@ export function createContentControls(ctx) {
|
|
|
190
190
|
prepare: (state) => {
|
|
191
191
|
const path = parentGroup(state);
|
|
192
192
|
if (!path) return { changed: false, loss: [], reason: "This block is not inside a group." };
|
|
193
|
-
return prepareUngroupBlock(editor.
|
|
193
|
+
return prepareUngroupBlock(editor.presentation, path, { validate: false });
|
|
194
194
|
},
|
|
195
195
|
apply: () => ungroupBlock(editor, parentGroup(ctxState.current)),
|
|
196
196
|
selectAfter: (change) => change.path,
|
|
@@ -222,7 +222,7 @@ export function createContentControls(ctx) {
|
|
|
222
222
|
image.setOptions(
|
|
223
223
|
IMAGE_DESTINATIONS.map((entry) => {
|
|
224
224
|
try {
|
|
225
|
-
const prepared = prepareImageToDesign(editor.
|
|
225
|
+
const prepared = prepareImageToDesign(editor.presentation, state.blockPath, entry.target, { ...entry.options, validate: false });
|
|
226
226
|
return { value: entry.value, label: `${entry.label}${prepared.loss.length ? ` (loses ${prepared.loss.join(", ")})` : ""}` };
|
|
227
227
|
} catch (error) {
|
|
228
228
|
return { value: entry.value, label: `${entry.label} (unavailable)`, disabled: true, title: messageOf(error) };
|
|
@@ -251,21 +251,21 @@ export function createContentControls(ctx) {
|
|
|
251
251
|
});
|
|
252
252
|
const toBlocks = action("Turn regions into blocks", {
|
|
253
253
|
role: "regions-to-blocks",
|
|
254
|
-
prepare: () => prepareRegionsToBlocks(editor.
|
|
254
|
+
prepare: () => prepareRegionsToBlocks(editor.presentation, getSlide(), { validate: false }),
|
|
255
255
|
apply: () => regionsAsBlocks(editor, getSlide()),
|
|
256
256
|
done: "Regions are now blocks, in reading order.",
|
|
257
257
|
});
|
|
258
258
|
const designImages = Object.keys(DESIGN_IMAGE_LABELS).map((source) =>
|
|
259
259
|
action(`Move the ${DESIGN_IMAGE_LABELS[source]} into the content`, {
|
|
260
260
|
role: `back-${source}`,
|
|
261
|
-
prepare: () => prepareImageToContent(editor.
|
|
261
|
+
prepare: () => prepareImageToContent(editor.presentation, getSlide(), source, { validate: false }),
|
|
262
262
|
apply: () => moveImageToContent(editor, getSlide(), source),
|
|
263
263
|
done: `The ${DESIGN_IMAGE_LABELS[source]} is now an image block.`,
|
|
264
264
|
}),
|
|
265
265
|
);
|
|
266
266
|
slideBox.append(layouts.wrap, toBlocks.wrap, ...designImages.map((entry) => entry.wrap));
|
|
267
267
|
updaters.push((state) => {
|
|
268
|
-
const slide = editor.
|
|
268
|
+
const slide = editor.presentation.slides?.[state.slideIndex];
|
|
269
269
|
if (!slide) return;
|
|
270
270
|
const regions = regionKeysOfSlide(slide);
|
|
271
271
|
const count = blockCount(state.slideIndex);
|
|
@@ -275,7 +275,7 @@ export function createContentControls(ctx) {
|
|
|
275
275
|
layouts.setOptions(
|
|
276
276
|
choices.map((choice) => {
|
|
277
277
|
try {
|
|
278
|
-
const prepared = prepareBlocksToRegions(editor.
|
|
278
|
+
const prepared = prepareBlocksToRegions(editor.presentation, state.slideIndex, choice.regions, { validate: false });
|
|
279
279
|
return { value: choice.id, label: `${choice.label}${prepared.loss.length ? ` (loses ${prepared.loss.join(", ")})` : ""}` };
|
|
280
280
|
} catch (error) {
|
|
281
281
|
return { value: choice.id, label: `${choice.label} (unavailable)`, disabled: true, title: messageOf(error) };
|
|
@@ -296,7 +296,7 @@ export function createContentControls(ctx) {
|
|
|
296
296
|
|
|
297
297
|
/** The number of blocks a slide can place in regions: its `blocks`, or the content fields it holds inline. */
|
|
298
298
|
function blockCount(slideIndex) {
|
|
299
|
-
const slide = editor.
|
|
299
|
+
const slide = editor.presentation.slides?.[slideIndex];
|
|
300
300
|
if (!slide) return 0;
|
|
301
301
|
if (Array.isArray(slide.blocks)) return slide.blocks.length;
|
|
302
302
|
return ["text", "items", "bullets", "image", "video", "chart", "table", "code", "metric", "quote", "timeline"].filter((key) => slide[key] !== undefined).length;
|
|
@@ -309,9 +309,9 @@ export function createContentControls(ctx) {
|
|
|
309
309
|
slideNode: slideBox,
|
|
310
310
|
/** Update every control from the session and the current selection. */
|
|
311
311
|
sync(state) {
|
|
312
|
-
const slide = editor.
|
|
312
|
+
const slide = editor.presentation.slides?.[state.slideIndex];
|
|
313
313
|
const imageBlock = state.blockPath ? isImageBlock(editor, state.blockPath) : false;
|
|
314
|
-
const full = { ...state, imageBlock, metricGroupPath: state.selectedPath ? metricGroupForSelection(editor.
|
|
314
|
+
const full = { ...state, imageBlock, metricGroupPath: state.selectedPath ? metricGroupForSelection(editor.presentation, state.selectedPath) : undefined, slide };
|
|
315
315
|
ctxState.current = full;
|
|
316
316
|
for (const update of updaters) update(full);
|
|
317
317
|
},
|
|
@@ -321,6 +321,6 @@ export function createContentControls(ctx) {
|
|
|
321
321
|
function isImageBlock(editor, blockPath) {
|
|
322
322
|
const block = editor.get(blockPath);
|
|
323
323
|
if (!block || typeof block !== "object" || Array.isArray(block)) return false;
|
|
324
|
-
const keys = Object.keys(block).filter((key) => key
|
|
324
|
+
const keys = Object.keys(block).filter((key) => !IMAGE_BLOCK_FIELDS.has(key));
|
|
325
325
|
return keys.length === 1 && keys[0] === "image";
|
|
326
326
|
}
|
package/dist/data-grid.js
CHANGED
|
@@ -33,7 +33,6 @@ import {
|
|
|
33
33
|
setGridColumnFormat,
|
|
34
34
|
setGridHeader,
|
|
35
35
|
sortGridRows,
|
|
36
|
-
supportsChartTableData,
|
|
37
36
|
transposeGridData,
|
|
38
37
|
} from "./grid-model.js";
|
|
39
38
|
import { NUMBER_FORMATS, parseDelimited, resolveNumberFormat, toDelimited } from "./grid-text.js";
|
|
@@ -456,7 +455,7 @@ export function createDataGrid(container, options = {}) {
|
|
|
456
455
|
function resolveTarget() {
|
|
457
456
|
const path = getPath();
|
|
458
457
|
if (!path) return undefined;
|
|
459
|
-
return resolveDataGridTarget(editor.
|
|
458
|
+
return resolveDataGridTarget(editor.presentation, path);
|
|
460
459
|
}
|
|
461
460
|
|
|
462
461
|
function rebuild() {
|
|
@@ -486,7 +485,7 @@ export function createDataGrid(container, options = {}) {
|
|
|
486
485
|
grid = undefined;
|
|
487
486
|
return;
|
|
488
487
|
}
|
|
489
|
-
grid = describeDataGrid(editor.
|
|
488
|
+
grid = describeDataGrid(editor.presentation, next.path, opts());
|
|
490
489
|
buildTable();
|
|
491
490
|
buildDatasetRow();
|
|
492
491
|
buildMapping();
|
|
@@ -505,7 +504,7 @@ export function createDataGrid(container, options = {}) {
|
|
|
505
504
|
function dataSignature() {
|
|
506
505
|
if (!target || target.editable === false) return "";
|
|
507
506
|
try {
|
|
508
|
-
return JSON.stringify([target.path, editor.get(target.path), target.dataset ? editor.
|
|
507
|
+
return JSON.stringify([target.path, editor.get(target.path), target.dataset ? editor.presentation.datasets?.[target.dataset.id] : undefined, numberFormat]);
|
|
509
508
|
} catch {
|
|
510
509
|
return "";
|
|
511
510
|
}
|
|
@@ -551,7 +550,7 @@ export function createDataGrid(container, options = {}) {
|
|
|
551
550
|
// moves the selection before the field's change event fires, and the format typed for one column must not land on the next.
|
|
552
551
|
let formatBound = 0;
|
|
553
552
|
function updateFormatRow() {
|
|
554
|
-
const visible = Boolean(grid?.hasHeader) && target?.editable !== false
|
|
553
|
+
const visible = Boolean(grid?.hasHeader) && target?.editable !== false;
|
|
555
554
|
formatRow.hidden = !visible;
|
|
556
555
|
if (!visible) return;
|
|
557
556
|
if (doc.activeElement !== formatInput) {
|
|
@@ -613,11 +612,11 @@ export function createDataGrid(container, options = {}) {
|
|
|
613
612
|
|
|
614
613
|
function buildMapping() {
|
|
615
614
|
mappingBody.replaceChildren();
|
|
616
|
-
mappingBox.hidden = !grid || target?.kind !== "chart"
|
|
615
|
+
mappingBox.hidden = !grid || target?.kind !== "chart";
|
|
617
616
|
if (mappingBox.hidden) return;
|
|
618
617
|
let view;
|
|
619
618
|
try {
|
|
620
|
-
view = describeChartMapping(editor.
|
|
619
|
+
view = describeChartMapping(editor.presentation, target.path);
|
|
621
620
|
} catch {
|
|
622
621
|
mappingBox.hidden = true;
|
|
623
622
|
return;
|
|
@@ -694,7 +693,7 @@ export function createDataGrid(container, options = {}) {
|
|
|
694
693
|
}
|
|
695
694
|
|
|
696
695
|
function checkLive(area, u, c) {
|
|
697
|
-
const issues = gridCellIssues(editor.
|
|
696
|
+
const issues = gridCellIssues(editor.presentation, target.path, [{ ...addr(u, c), text: area.value }], opts());
|
|
698
697
|
if (issues.length) {
|
|
699
698
|
area.setAttribute("aria-invalid", "true");
|
|
700
699
|
complain(issues[0].message);
|
package/dist/data.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export {parseTabularData,
|
|
2
|
-
export type {TabularData, DataImportOptions,
|
|
1
|
+
export {parseTabularData, importData, OPFDataImportError} from "@openpresentation/opf/data";
|
|
2
|
+
export type {TabularData, DataImportOptions, ImportDataOptions} from "@openpresentation/opf/data";
|
|
3
3
|
import type { JsonPatchOperation } from "./index.js";
|
|
4
4
|
|
|
5
5
|
/** The id pattern of a top-level dataset (the assets id pattern). */
|
|
@@ -19,5 +19,5 @@ export interface DatasetImport {
|
|
|
19
19
|
/** True when a dataset of that id existed and its columns and rows were replaced. */
|
|
20
20
|
replaced: boolean;
|
|
21
21
|
}
|
|
22
|
-
/** Store imported data (the result of `
|
|
23
|
-
export declare function prepareDatasetImport(
|
|
22
|
+
/** Store imported data (the result of `importData`) as a shared dataset, as `opf import-data --dataset <id>` does. Throws `dataset-id-invalid`. */
|
|
23
|
+
export declare function prepareDatasetImport(presentation: unknown, content: { table: unknown } | { chart: { type: string; data: unknown } }, options: DatasetImportOptions): DatasetImport;
|
package/dist/data.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {parseTabularData,
|
|
1
|
+
export {parseTabularData, importData, OPFDataImportError} from "@openpresentation/opf/data";
|
|
2
2
|
import { opfPathToJsonPointer } from "./index.js";
|
|
3
3
|
import { fail } from "./edit-helpers.js";
|
|
4
4
|
|
|
@@ -8,19 +8,19 @@ export const DATASET_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
|
8
8
|
const isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Store imported data as a shared dataset (RR-54), as `opf import-data --dataset <id>` does. `content` is what `
|
|
11
|
+
* Store imported data as a shared dataset (RR-54), as `opf import-data --dataset <id>` does. `content` is what `importData` returns
|
|
12
12
|
* (`{ table }` or `{ chart: { type, data } }`). Returns `{ id, content, patches, replaced }`: `content` is the same table or chart holding
|
|
13
13
|
* `{ dataset: id }` instead of the rows, and `patches` write `datasets.<id>` (the imported columns and rows replace those of an existing
|
|
14
14
|
* dataset of that id, which keeps its title, description and source; `source` is merged into its `source`). Apply `patches` and the patch that
|
|
15
15
|
* places `content` in one `applyPatch` call so the import is one undoable edit. Throws `dataset-id-invalid`.
|
|
16
16
|
*/
|
|
17
|
-
export function prepareDatasetImport(
|
|
17
|
+
export function prepareDatasetImport(presentation, content, options = {}) {
|
|
18
18
|
const id = options.id;
|
|
19
19
|
if (typeof id !== "string" || !DATASET_ID_PATTERN.test(id)) throw fail("dataset-id-invalid", 'A dataset id starts with a letter or digit and holds letters, digits, ".", "_" or "-".', { id });
|
|
20
20
|
const isTable = isObject(content) && isObject(content.table);
|
|
21
|
-
if (!isTable && !(isObject(content) && isObject(content.chart) && isObject(content.chart.data))) throw fail("dataset-content-invalid", "Give the table or chart that
|
|
21
|
+
if (!isTable && !(isObject(content) && isObject(content.chart) && isObject(content.chart.data))) throw fail("dataset-content-invalid", "Give the table or chart that importData returned.", {});
|
|
22
22
|
const data = isTable ? content.table : content.chart.data;
|
|
23
|
-
const datasets = isObject(
|
|
23
|
+
const datasets = isObject(presentation) && isObject(presentation.datasets) ? presentation.datasets : undefined;
|
|
24
24
|
const previous = datasets && Object.hasOwn(datasets, id) && isObject(datasets[id]) ? datasets[id] : undefined;
|
|
25
25
|
const origin = isObject(options.source) && typeof options.source.src === "string" ? options.source : undefined;
|
|
26
26
|
const entry = {
|
package/dist/dataset-refs.d.ts
CHANGED
|
@@ -12,6 +12,6 @@ export interface DatasetItem {
|
|
|
12
12
|
/** The dataset reference test (core's `isDatasetRef`). */
|
|
13
13
|
export declare function isDatasetRef(value: unknown): boolean;
|
|
14
14
|
/** Visit every chart and table that references a dataset (slides, nested blocks and promoted regions). */
|
|
15
|
-
export declare function walkDatasetItems(
|
|
15
|
+
export declare function walkDatasetItems(presentation: unknown, visit: (entry: DatasetItem) => void): void;
|
|
16
16
|
/** The charts and tables that use dataset `id`, in document order. */
|
|
17
|
-
export declare function datasetUsage(
|
|
17
|
+
export declare function datasetUsage(presentation: unknown, id: string): Array<{ kind: "chart" | "table"; path: string; parts: string[] }>;
|
package/dist/dataset-refs.js
CHANGED
|
@@ -14,8 +14,8 @@ export const isDatasetRef = (value) => isObject(value) && typeof value.dataset =
|
|
|
14
14
|
* `item` is the chart or table object, `parts` its path, and `ref` the object that holds `dataset` and `fields`
|
|
15
15
|
* (`chart.data` for a chart, the table itself for a table) at `refParts`.
|
|
16
16
|
*/
|
|
17
|
-
export function walkDatasetItems(
|
|
18
|
-
if (!isObject(
|
|
17
|
+
export function walkDatasetItems(presentation, visit) {
|
|
18
|
+
if (!isObject(presentation) || !Array.isArray(presentation.slides)) return;
|
|
19
19
|
const walk = (payload, base, depth, ancestors) => {
|
|
20
20
|
if (isObject(payload.chart) && isDatasetRef(payload.chart.data))
|
|
21
21
|
visit({ kind: "chart", id: payload.chart.data.dataset, parts: [...base, "chart"], item: payload.chart, refParts: [...base, "chart", "data"], ref: payload.chart.data });
|
|
@@ -27,7 +27,7 @@ export function walkDatasetItems(document, visit) {
|
|
|
27
27
|
if (isObject(block)) walk(block, [...base, "blocks", String(index)], depth + 1, nested);
|
|
28
28
|
});
|
|
29
29
|
};
|
|
30
|
-
|
|
30
|
+
presentation.slides.forEach((slide, index) => {
|
|
31
31
|
if (!isObject(slide)) return;
|
|
32
32
|
const base = ["slides", String(index)];
|
|
33
33
|
walk(slide, base, 0, []);
|
|
@@ -36,9 +36,9 @@ export function walkDatasetItems(document, visit) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** The charts and tables that use dataset `id`: `[{ kind, path, parts }]` in document order. */
|
|
39
|
-
export function datasetUsage(
|
|
39
|
+
export function datasetUsage(presentation, id) {
|
|
40
40
|
const items = [];
|
|
41
|
-
walkDatasetItems(
|
|
41
|
+
walkDatasetItems(presentation, (entry) => {
|
|
42
42
|
if (entry.id === id) items.push({ kind: entry.kind, path: entry.parts.join("."), parts: entry.parts });
|
|
43
43
|
});
|
|
44
44
|
return items;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import type { Catalog } from "@openpresentation/opf";
|
|
1
2
|
import type { EditorSession } from "./index.js";
|
|
3
|
+
import type { SwitchVocabularies } from "./switches.js";
|
|
2
4
|
|
|
3
|
-
export type DesignControlSection = "look" | "background" | "
|
|
4
|
-
/**
|
|
5
|
+
export type DesignControlSection = "look" | "background" | "header-footer" | "brand" | "layout-options" | "info" | "selection" | "image" | "table" | "slide-content" | "catalog";
|
|
6
|
+
/**
|
|
7
|
+
* Every section, in panel order. `selection`, `image` (the selected image block's fit, focus, treatments and placement) and `table`
|
|
8
|
+
* follow the host's current selection; `slide-content` (RR-26: blocks and regions, images between content and design) follows the
|
|
9
|
+
* current slide; `catalog` lists the catalog reference findings and runs the explicit "Update from catalog".
|
|
10
|
+
*/
|
|
5
11
|
export declare const DESIGN_CONTROL_SECTIONS: readonly DesignControlSection[];
|
|
6
12
|
|
|
7
13
|
export interface DesignControlsOptions {
|
|
@@ -14,9 +20,10 @@ export interface DesignControlsOptions {
|
|
|
14
20
|
sections?: readonly DesignControlSection[];
|
|
15
21
|
/** Initial "Applies to" choice. */
|
|
16
22
|
scope?: "deck" | "slide";
|
|
17
|
-
/**
|
|
18
|
-
catalogs?:
|
|
19
|
-
|
|
23
|
+
/** Host catalogs for the pickers, merged after the session's registered ones. */
|
|
24
|
+
catalogs?: readonly Catalog[];
|
|
25
|
+
/** Display metadata for the engine vocabularies (languages, chart types, social platforms): `catalogDisplay` from `@openpresentation/opf/catalog`. */
|
|
26
|
+
vocabularies?: SwitchVocabularies;
|
|
20
27
|
/** Called after every committed change (never for a refused one). */
|
|
21
28
|
onChange?: (change: unknown) => void;
|
|
22
29
|
/** Called after a content conversion or replacement with the path to keep selected (the block, or the inline payload field), since the old selection path may no longer exist. */
|