@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/canvas.d.ts
CHANGED
|
@@ -1,55 +1,54 @@
|
|
|
1
1
|
import type { EditorSession } from "./index.js";
|
|
2
2
|
import type { RenderSvgOptions } from "@openpresentation/opf-render";
|
|
3
|
+
import type { BrowserFontsHandle } from "@openpresentation/opf-render/fonts-browser";
|
|
3
4
|
import type { SlideComposition } from "@openpresentation/opf/composition";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
run(document: unknown, handlers?: FontGateHandlers): Promise<void>;
|
|
12
|
-
}
|
|
13
|
-
export interface FontGateHandlers {
|
|
5
|
+
import type { Catalog } from "@openpresentation/opf";
|
|
6
|
+
/**
|
|
7
|
+
* The fonts the canvas, the export and `whenFontsReady` take: the renderer's handle from `loadFonts()` (`@openpresentation/opf-render/fonts-browser`).
|
|
8
|
+
* Only `textMeasurement` is required to measure; `pending` and `ensure` make the canvas load script and vendored faces before it draws.
|
|
9
|
+
*/
|
|
10
|
+
export type EditorFonts = Pick<BrowserFontsHandle, "textMeasurement"> & Partial<Pick<BrowserFontsHandle, "embeddedFonts" | "pending" | "ensure" | "registry">>;
|
|
11
|
+
export interface FontsReadyHandlers {
|
|
14
12
|
/** Return false once the document was superseded; the late result is then dropped. */
|
|
15
13
|
isCurrent?: () => boolean;
|
|
16
14
|
loading?: (pending: string[]) => void;
|
|
17
15
|
ready?: () => void;
|
|
18
16
|
failed?: (error: Error) => void;
|
|
19
|
-
/** The options the host renders with (`catalogs`, ...), passed to the
|
|
17
|
+
/** The options the host renders with (`catalogs`, ...), passed to the fonts handle. */
|
|
20
18
|
renderOptions?: RenderSvgOptions;
|
|
21
19
|
}
|
|
22
20
|
export declare const FONTS_PENDING: "fonts-pending";
|
|
23
21
|
export declare const FONTS_UNAVAILABLE: "fonts-unavailable";
|
|
24
|
-
/** Wrap a browser font registry (`loadBrowserFontRegistry`). Registries without the lazy loaders gate nothing. */
|
|
25
22
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*/
|
|
29
|
-
export declare function createFontGate(registry: object, options?: { renderOptions?: RenderSvgOptions | (() => RenderSvgOptions | undefined) }): FontGate;
|
|
30
|
-
/**
|
|
31
|
-
* Ensure fonts for a document, then run `ready` (synchronously when nothing is pending). The one
|
|
32
|
-
* "ensure fonts, then render" helper: load failures and exceptions from `ready` go to `failed`.
|
|
23
|
+
* Ensure fonts for a presentation, then run `ready` (synchronously when nothing is pending). The one
|
|
24
|
+
* "ensure fonts, then render" helper: load failures and exceptions from `ready` go to `failed`. `fonts` is the renderer's fonts handle.
|
|
33
25
|
*/
|
|
34
26
|
export declare function whenFontsReady(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
handlers?:
|
|
27
|
+
fonts: Pick<EditorFonts, "pending" | "ensure"> | undefined,
|
|
28
|
+
presentation: unknown,
|
|
29
|
+
handlers?: FontsReadyHandlers,
|
|
38
30
|
): Promise<void>;
|
|
39
31
|
export interface CanvasEditorOptions {
|
|
40
32
|
editor?: EditorSession;
|
|
41
|
-
|
|
33
|
+
presentation?: unknown;
|
|
34
|
+
/**
|
|
35
|
+
* The host's catalogs (core `Catalog[]`, for example `[defaultCatalog]` from `@openpresentation/opf/catalog`) for the session the
|
|
36
|
+
* canvas creates from `presentation`. With `editor`, register them on that session instead (`createEditorSession(doc, { catalogs })`);
|
|
37
|
+
* `renderOptions.catalogs` adds catalogs for drawing only, merged after the session's.
|
|
38
|
+
*/
|
|
39
|
+
catalogs?: readonly Catalog[];
|
|
42
40
|
slideIndex?: number;
|
|
43
41
|
/** Show keyboard-accessible dividers for resizing composition tracks. */
|
|
44
42
|
layoutEditing?: boolean;
|
|
45
43
|
/** Render options. The canvas draws the document as authored (`variables: false`: a template's `{{tokens}}` stay visible, so inline edits never overwrite them); pass `variables` to draw resolved values instead. */
|
|
46
44
|
renderOptions?: RenderSvgOptions;
|
|
47
45
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
46
|
+
* The renderer's fonts handle (`loadFonts()` from `@openpresentation/opf-render/fonts-browser`). Its `textMeasurement` lays the slide out and
|
|
47
|
+
* its faces draw it. With its `pending` and `ensure` the canvas never renders a document whose faces are still loading: it shows
|
|
48
|
+
* "Loading fonts…", loads them and then renders, on every path (editor changes, undo/redo, imports, dimension switches, slide
|
|
49
|
+
* changes, drafts). Without a handle every document renders at once, laid out with core's portable text estimate.
|
|
51
50
|
*/
|
|
52
|
-
fonts?:
|
|
51
|
+
fonts?: EditorFonts;
|
|
53
52
|
/**
|
|
54
53
|
* How a pointer enters text editing. "click" (default): a single press on editable text starts editing with the caret at the
|
|
55
54
|
* pressed character, and a press-drag selects a range. "dblclick": a click selects, a double-click enters with the caret at the
|
|
@@ -68,7 +67,7 @@ export interface CanvasEditorOptions {
|
|
|
68
67
|
editor: EditorSession;
|
|
69
68
|
}) => void;
|
|
70
69
|
onDraft?: (draft: {
|
|
71
|
-
|
|
70
|
+
presentation: unknown;
|
|
72
71
|
path: string;
|
|
73
72
|
value: unknown;
|
|
74
73
|
}) => void;
|
|
@@ -78,7 +77,7 @@ export interface CanvasEditorOptions {
|
|
|
78
77
|
/** Font loading progress of the canvas: `loading`, `error` (a face could not be loaded; the canvas offers a retry) and `ready`. */
|
|
79
78
|
onFonts?: (event: { state: "loading"; pending: string[] } | { state: "error"; error: Error } | { state: "ready" }) => void;
|
|
80
79
|
onRender?: (event: {
|
|
81
|
-
|
|
80
|
+
presentation: unknown;
|
|
82
81
|
slideIndex: number;
|
|
83
82
|
svg: SVGSVGElement;
|
|
84
83
|
geometry: SlideComposition;
|
|
@@ -94,7 +93,7 @@ export interface CanvasEditor {
|
|
|
94
93
|
/** True while the crop layer is open. */
|
|
95
94
|
readonly cropping: boolean;
|
|
96
95
|
/**
|
|
97
|
-
* Open the crop layer for the picture at `path` (an image block
|
|
96
|
+
* Open the crop layer for the picture at `path` (an image block or a slide's `image`); `tool: "focus"`
|
|
98
97
|
* starts with the focal point. Apply writes one undoable change (see `@openpresentation/opf-editor/image-crop`). Resolves to whether it opened.
|
|
99
98
|
*/
|
|
100
99
|
cropImage(path: string, options?: { tool?: "crop" | "focus" }): Promise<boolean>;
|
|
@@ -110,7 +109,7 @@ export interface CanvasEditor {
|
|
|
110
109
|
editProperties(path: string): boolean;
|
|
111
110
|
commit(): boolean;
|
|
112
111
|
cancel(): void;
|
|
113
|
-
render(
|
|
112
|
+
render(presentation?: unknown): void;
|
|
114
113
|
setSlide(index: number): boolean;
|
|
115
114
|
/**
|
|
116
115
|
* Select the content at `path`, or the closest enclosing content the canvas can select (a list item selects its list),
|
package/dist/canvas.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createEditorSession,
|
|
3
|
+
mergeCatalogs,
|
|
3
4
|
opfPathToJsonPointer,
|
|
4
5
|
applyJsonPatch,
|
|
5
|
-
validateOpfDocument,
|
|
6
6
|
getValueAtPath,
|
|
7
7
|
splitOpfPath,
|
|
8
8
|
} from "./index.js";
|
|
9
9
|
import {
|
|
10
|
-
|
|
10
|
+
renderSlideSvg,
|
|
11
11
|
resolvePresentation,
|
|
12
12
|
} from "@openpresentation/opf-render/svg";
|
|
13
13
|
import {
|
|
14
14
|
getEditableFields,
|
|
15
|
+
emptyLike,
|
|
16
|
+
fieldChoices,
|
|
15
17
|
parseCanvasValue,
|
|
16
18
|
createCanvasDraft,
|
|
17
19
|
} from "./canvas-fields.js";
|
|
@@ -21,9 +23,10 @@ import { createRichTextInput } from "./rich-text-input.js";
|
|
|
21
23
|
import { textInputOffsetAtPoint } from "./text-pointer.js";
|
|
22
24
|
import { createRichTextToolbar } from "./rich-text-toolbar.js";
|
|
23
25
|
import { createImageCropper } from "./image-cropper.js";
|
|
24
|
-
import { FONTS_PENDING, fontsPendingError, whenFontsReady } from "./font-gate.js";
|
|
26
|
+
import { FONTS_PENDING, fontGate, fontsPendingError, previewFonts, whenFontsReady } from "./font-gate.js";
|
|
27
|
+
import { checkFormat, firstErrorMessage } from "./checks.js";
|
|
25
28
|
export { getEditableFields } from "./canvas-fields.js";
|
|
26
|
-
export {
|
|
29
|
+
export { whenFontsReady, FONTS_PENDING, FONTS_UNAVAILABLE } from "./font-gate.js";
|
|
27
30
|
|
|
28
31
|
/** Allocated placeholder or internal-part bounds for the selection outline, not glyph ink. */
|
|
29
32
|
export function allocatedSelectionBox(node, item) {
|
|
@@ -65,10 +68,14 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
65
68
|
win = doc.defaultView;
|
|
66
69
|
const editor =
|
|
67
70
|
options.editor ??
|
|
68
|
-
createEditorSession(options.
|
|
69
|
-
// `options.fonts` is
|
|
70
|
-
//
|
|
71
|
-
|
|
71
|
+
createEditorSession(options.presentation, { rejectInvalid: true, catalogs: options.catalogs });
|
|
72
|
+
// `options.fonts` is the renderer's fonts handle (`loadFonts()`): it measures the text and draws the faces. A document whose faces
|
|
73
|
+
// are not loaded yet is never rendered: the canvas has the handle load them first and shows "Loading fonts…" meanwhile. Without a
|
|
74
|
+
// handle every document renders at once, with core's portable text estimate.
|
|
75
|
+
const handle = options.fonts, fonts = fontGate(handle);
|
|
76
|
+
// The renderer draws with the session's registered catalogs (plus any `renderOptions.catalogs`): the same list core resolves with.
|
|
77
|
+
const resolveOptions = () => ({ ...renderOptions, catalogs: mergeCatalogs(editor.catalogs, renderOptions.catalogs) });
|
|
78
|
+
const drawOptions = (extra) => ({ ...resolveOptions(), fonts: previewFonts(handle), ...extra });
|
|
72
79
|
let slideIndex = options.slideIndex ?? 0,
|
|
73
80
|
// RR-32: the canvas edits the document as authored, so a template's {{tokens}} stay visible and an inline edit never
|
|
74
81
|
// overwrites one with its resolved text. The Fill template panel previews the resolved deck. Pass `variables` to override.
|
|
@@ -120,7 +127,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
120
127
|
onTyping: beginEdit,
|
|
121
128
|
onProperties(path) { if (commit()) openProperties(path, editor.get(path)); },
|
|
122
129
|
validateChange(path, value) {
|
|
123
|
-
validateRender(createCanvasDraft(editor.
|
|
130
|
+
validateRender(createCanvasDraft(editor.presentation, path, value));
|
|
124
131
|
},
|
|
125
132
|
onChange(path) { clearNotice(); options.onCommit?.({path, editor}); }
|
|
126
133
|
});
|
|
@@ -144,7 +151,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
144
151
|
const blockControls = createBlockControls(root, {
|
|
145
152
|
editor, render: renderFor, beforeEdit: () => {if(!commit())return false;richToolbar.hide();return true;}, enabled: () => layoutHandles.enabled,
|
|
146
153
|
isEditing: () => !!active || !!layoutHandles.editingPath, slideIndex: () => slideIndex,
|
|
147
|
-
validate:
|
|
154
|
+
validate: presentation => validateRender(presentation),
|
|
148
155
|
clearError: clearNotice, onError: report,
|
|
149
156
|
onMove: path => choose(splitOpfPath(path).join(".")),
|
|
150
157
|
onCommit: value => options.onCommit?.(value),
|
|
@@ -175,20 +182,20 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
175
182
|
const pendingMessage = "Loading fonts for this document…";
|
|
176
183
|
// A synchronous render or validation of a document whose faces are still loading fails with a clear "fonts-pending"
|
|
177
184
|
// error and starts the load, so the next attempt succeeds. It never draws glyphs the registry cannot provide.
|
|
178
|
-
function requireFonts(
|
|
179
|
-
const pending = fonts?.pending(
|
|
185
|
+
function requireFonts(presentation) {
|
|
186
|
+
const pending = fonts?.pending(presentation, resolveOptions()) ?? [];
|
|
180
187
|
if (!pending.length) return;
|
|
181
188
|
notice.hidden = false;
|
|
182
189
|
notice.textContent = pendingMessage;
|
|
183
|
-
fonts.ensure(
|
|
190
|
+
fonts.ensure(presentation, { renderOptions: resolveOptions() }).then(
|
|
184
191
|
() => { if (!disposed && notice.textContent === pendingMessage) clearNotice(); },
|
|
185
192
|
(error) => { if (!disposed) report(error); },
|
|
186
193
|
);
|
|
187
194
|
throw fontsPendingError(pending);
|
|
188
195
|
}
|
|
189
|
-
function validateRender(
|
|
190
|
-
requireFonts(
|
|
191
|
-
return
|
|
196
|
+
function validateRender(presentation) {
|
|
197
|
+
requireFonts(presentation);
|
|
198
|
+
return renderSlideSvg(presentation, slideIndex, drawOptions());
|
|
192
199
|
}
|
|
193
200
|
function fontsState(kind, detail) {
|
|
194
201
|
fontsShown = true;
|
|
@@ -218,8 +225,8 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
218
225
|
function show(target) {
|
|
219
226
|
if (disposed) return;
|
|
220
227
|
const token = ++showToken;
|
|
221
|
-
return whenFontsReady(fonts, target ?? editor.
|
|
222
|
-
renderOptions,
|
|
228
|
+
return whenFontsReady(fonts, target ?? editor.presentation, {
|
|
229
|
+
renderOptions: resolveOptions(),
|
|
223
230
|
isCurrent: () => !disposed && token === showToken,
|
|
224
231
|
loading: (pending) => fontsState("loading", pending),
|
|
225
232
|
ready: () => {
|
|
@@ -234,27 +241,23 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
234
241
|
});
|
|
235
242
|
}
|
|
236
243
|
// Renders now when the document's fonts are loaded (errors throw to the caller), otherwise after loading them.
|
|
237
|
-
function renderFor(
|
|
238
|
-
if (fonts?.pending(
|
|
239
|
-
else render(
|
|
244
|
+
function renderFor(presentation) {
|
|
245
|
+
if (fonts?.pending(presentation ?? editor.presentation, resolveOptions()).length) show(presentation);
|
|
246
|
+
else render(presentation);
|
|
240
247
|
}
|
|
241
|
-
function render(
|
|
248
|
+
function render(presentation = editor.presentation) {
|
|
242
249
|
if (disposed) return;
|
|
243
|
-
requireFonts(
|
|
250
|
+
requireFonts(presentation);
|
|
244
251
|
showToken++;
|
|
245
|
-
const slides =
|
|
252
|
+
const slides = presentation.slides ?? [];
|
|
246
253
|
slideIndex = Math.max(0, Math.min(slideIndex, slides.length - 1));
|
|
247
|
-
const svgText =
|
|
248
|
-
...renderOptions,
|
|
249
|
-
slideIndex,
|
|
250
|
-
trace: true,
|
|
251
|
-
});
|
|
254
|
+
const svgText = renderSlideSvg(presentation, slideIndex, drawOptions({ trace: true }));
|
|
252
255
|
preview.innerHTML = svgText;
|
|
253
256
|
const svg = preview.querySelector("svg");
|
|
254
257
|
svg.setAttribute("role", "group");
|
|
255
258
|
svg.setAttribute("aria-label", "Editable slide");
|
|
256
259
|
svg.removeAttribute("aria-labelledby");
|
|
257
|
-
const geometry = resolvePresentation(
|
|
260
|
+
const geometry = resolvePresentation(presentation, drawOptions()).slides[
|
|
258
261
|
slideIndex
|
|
259
262
|
].geometry;
|
|
260
263
|
const candidates = [...svg.querySelectorAll("[data-opf-path]")];
|
|
@@ -262,7 +265,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
262
265
|
for (const node of candidates) {
|
|
263
266
|
const path = node.getAttribute("data-opf-path");
|
|
264
267
|
const item = geometry.items.find((item) => item.path === path);
|
|
265
|
-
const value = getValueAtPath(
|
|
268
|
+
const value = getValueAtPath(presentation, path);
|
|
266
269
|
if (
|
|
267
270
|
seen.has(path) ||
|
|
268
271
|
node.getAttribute('data-opf-generated') === 'true' ||
|
|
@@ -340,8 +343,8 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
340
343
|
});
|
|
341
344
|
if (path === selectedPath) node.setAttribute("data-canvas-selected", "");
|
|
342
345
|
}
|
|
343
|
-
layoutHandles.update(
|
|
344
|
-
blockControls.update(
|
|
346
|
+
layoutHandles.update(presentation, geometry);
|
|
347
|
+
blockControls.update(presentation, geometry);
|
|
345
348
|
imageCropper?.update();
|
|
346
349
|
if (active?.kind === "text") positionInput();
|
|
347
350
|
if (active?.kind === "rich-text") active.rich?.update();
|
|
@@ -350,7 +353,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
350
353
|
options.onFonts?.({ state: "ready" });
|
|
351
354
|
}
|
|
352
355
|
options.onRender?.({
|
|
353
|
-
|
|
356
|
+
presentation,
|
|
354
357
|
slideIndex,
|
|
355
358
|
svg,
|
|
356
359
|
geometry,
|
|
@@ -366,11 +369,11 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
366
369
|
}
|
|
367
370
|
// An in-progress edit whose text needs faces that are not loaded yet waits for them, then draws again.
|
|
368
371
|
function deferDraft(draft) {
|
|
369
|
-
if (!fonts?.pending(draft,
|
|
372
|
+
if (!fonts?.pending(draft, resolveOptions()).length) return false;
|
|
370
373
|
active.valid = false;
|
|
371
374
|
notice.hidden = false;
|
|
372
375
|
notice.textContent = pendingMessage;
|
|
373
|
-
fonts.ensure(draft, { renderOptions }).then(
|
|
376
|
+
fonts.ensure(draft, { renderOptions: resolveOptions() }).then(
|
|
374
377
|
() => {
|
|
375
378
|
if (disposed) return;
|
|
376
379
|
if (notice.textContent === pendingMessage) clearNotice();
|
|
@@ -392,7 +395,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
392
395
|
clearNotice();
|
|
393
396
|
render(draft);
|
|
394
397
|
options.onDraft?.({
|
|
395
|
-
|
|
398
|
+
presentation: draft,
|
|
396
399
|
path: active.path,
|
|
397
400
|
value: undefined,
|
|
398
401
|
});
|
|
@@ -403,13 +406,13 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
403
406
|
}
|
|
404
407
|
try {
|
|
405
408
|
const value = active.kind === "rich-text" ? active.rich.value : parseCanvasValue(active.input.value, active.type, active.originalValue);
|
|
406
|
-
const draft = createCanvasDraft(editor.
|
|
409
|
+
const draft = createCanvasDraft(editor.presentation, active.path, value);
|
|
407
410
|
if (deferDraft(draft)) return;
|
|
408
411
|
clearNotice();
|
|
409
412
|
active.valid = true;
|
|
410
413
|
active.draft = draft;
|
|
411
414
|
render(draft);
|
|
412
|
-
options.onDraft?.({
|
|
415
|
+
options.onDraft?.({ presentation: draft, path: active.path, value });
|
|
413
416
|
} catch (error) {
|
|
414
417
|
active.valid = false;
|
|
415
418
|
report(error);
|
|
@@ -429,8 +432,8 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
429
432
|
const font = win.getComputedStyle(text),
|
|
430
433
|
fontSize = parseFloat(font.fontSize),
|
|
431
434
|
geometry = resolvePresentation(
|
|
432
|
-
active.draft ?? editor.
|
|
433
|
-
|
|
435
|
+
active.draft ?? editor.presentation,
|
|
436
|
+
drawOptions(),
|
|
434
437
|
).slides[slideIndex].geometry;
|
|
435
438
|
const item = geometry.items.find((item) => item.path === active.path);
|
|
436
439
|
const metricLayout=geometry.items.find(item=>item.metricLayout?.parts.some(part=>part.path===active.path))?.metricLayout;
|
|
@@ -600,7 +603,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
600
603
|
// Only offer rich text where the canonical schema accepts TextRun[].
|
|
601
604
|
if (typeof value === "string") {
|
|
602
605
|
try {
|
|
603
|
-
createCanvasDraft(editor.
|
|
606
|
+
createCanvasDraft(editor.presentation, path, [value]);
|
|
604
607
|
const format = doc.createElement("button");
|
|
605
608
|
format.type = "button";
|
|
606
609
|
format.textContent = "Format text";
|
|
@@ -683,11 +686,11 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
683
686
|
}));
|
|
684
687
|
}
|
|
685
688
|
function propertyDraft(edit) {
|
|
686
|
-
const draft = applyJsonPatch(editor.
|
|
687
|
-
validation =
|
|
689
|
+
const draft = applyJsonPatch(editor.presentation, propertyPatches(edit)),
|
|
690
|
+
validation = checkFormat(draft);
|
|
688
691
|
if (!validation.valid)
|
|
689
692
|
throw new Error(
|
|
690
|
-
validation
|
|
693
|
+
firstErrorMessage(validation, "This change is not valid OPF."),
|
|
691
694
|
);
|
|
692
695
|
return draft;
|
|
693
696
|
}
|
|
@@ -706,10 +709,20 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
706
709
|
label.style.cssText =
|
|
707
710
|
"display:block;margin-top:10px;color:#756b85;font-size:11px";
|
|
708
711
|
label.textContent = field.label;
|
|
712
|
+
const choices = field.type === "string" ? fieldChoices(field) : undefined;
|
|
709
713
|
const input = doc.createElement(
|
|
710
|
-
field.type === "boolean" ? "input" : "textarea",
|
|
714
|
+
choices ? "select" : field.type === "boolean" ? "input" : "textarea",
|
|
711
715
|
);
|
|
712
|
-
if (
|
|
716
|
+
if (choices) {
|
|
717
|
+
// A fixed set of values (a timeline event's status) is a menu, not free text.
|
|
718
|
+
for (const choice of choices) {
|
|
719
|
+
const option = doc.createElement("option");
|
|
720
|
+
option.value = choice;
|
|
721
|
+
option.textContent = choice;
|
|
722
|
+
input.append(option);
|
|
723
|
+
}
|
|
724
|
+
input.value = field.value;
|
|
725
|
+
} else if (field.type === "boolean") {
|
|
713
726
|
input.type = "checkbox";
|
|
714
727
|
input.checked = field.value;
|
|
715
728
|
} else {
|
|
@@ -780,26 +793,9 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
780
793
|
if (!commit()) return;
|
|
781
794
|
try {
|
|
782
795
|
const values = editor.get(array.path);
|
|
783
|
-
const empty = (value, key = "") =>
|
|
784
|
-
Array.isArray(value)
|
|
785
|
-
? value.map((child) => empty(child))
|
|
786
|
-
: value && typeof value === "object"
|
|
787
|
-
? Object.fromEntries(
|
|
788
|
-
Object.entries(value).map(([key, child]) => [
|
|
789
|
-
key,
|
|
790
|
-
empty(child, key),
|
|
791
|
-
]),
|
|
792
|
-
)
|
|
793
|
-
: typeof value === "number"
|
|
794
|
-
? 0
|
|
795
|
-
: typeof value === "boolean"
|
|
796
|
-
? false
|
|
797
|
-
: ["type", "mode", "language"].includes(key)
|
|
798
|
-
? value
|
|
799
|
-
: "";
|
|
800
796
|
const next = remove
|
|
801
797
|
? values.slice(0, -1)
|
|
802
|
-
: [...values,
|
|
798
|
+
: [...values, emptyLike(values.at(-1) ?? "")];
|
|
803
799
|
editor.set(array.path, next, {
|
|
804
800
|
source: "canvas-collection",
|
|
805
801
|
rejectInvalid: true,
|
|
@@ -876,7 +872,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
876
872
|
value = parseCanvasValue(edit.input.value, edit.type, edit.originalValue);
|
|
877
873
|
else value = propertyDraft(edit);
|
|
878
874
|
if (edit.kind === "text" || edit.kind === "rich-text")
|
|
879
|
-
validateRender(createCanvasDraft(editor.
|
|
875
|
+
validateRender(createCanvasDraft(editor.presentation, edit.path, value));
|
|
880
876
|
committing = true;
|
|
881
877
|
if ((edit.kind === "text" || edit.kind === "rich-text") && JSON.stringify(value) !== edit.base)
|
|
882
878
|
editor.set(edit.path, value, { source: "canvas", rejectInvalid: true });
|
|
@@ -928,7 +924,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
928
924
|
options.onCancel?.({ path });
|
|
929
925
|
}
|
|
930
926
|
function setSlide(index) {
|
|
931
|
-
if (!Number.isInteger(index) || !editor.
|
|
927
|
+
if (!Number.isInteger(index) || !editor.presentation.slides?.[index])
|
|
932
928
|
throw new RangeError("Slide index is out of range.");
|
|
933
929
|
if (index === slideIndex) {
|
|
934
930
|
if (!active) renderFor();
|
|
@@ -1027,7 +1023,7 @@ export function createCanvasEditor(container, options = {}) {
|
|
|
1027
1023
|
get layoutEditing() { return layoutHandles.enabled; },
|
|
1028
1024
|
/** True while the crop layer is open. */
|
|
1029
1025
|
get cropping() { return !!imageCropper?.isOpen; },
|
|
1030
|
-
/** Open the crop layer for the picture at `path` (an image block
|
|
1026
|
+
/** Open the crop layer for the picture at `path` (an image block or a slide's `image`). `tool: "focus"` starts with the focal point. */
|
|
1031
1027
|
cropImage(path, cropOptions) {
|
|
1032
1028
|
if (!imageCropper) return Promise.resolve(false);
|
|
1033
1029
|
return imageCropper.open(path, cropOptions);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Catalog, CatalogKind, CatalogRecordChange, EmbeddedRecord, UnresolvedReferenceDiagnostic, ValidationReport } from "@openpresentation/opf";
|
|
2
|
+
import type { CatalogOption, EditorChange, EditorSession, JsonPatchOperation } from "./index.js";
|
|
3
|
+
|
|
4
|
+
/** One catalog list: the session's registration first, then each host list; the first catalog with a `source` wins. Throws `invalid-catalogs` for an entry without a source. */
|
|
5
|
+
export declare function mergeCatalogs(...lists: (readonly Catalog[] | Catalog | undefined | null)[]): readonly Catalog[];
|
|
6
|
+
/** The catalogs for one operation: `editor.catalogs`, then `options.catalogs`. */
|
|
7
|
+
export declare function catalogsFor(editor: EditorSession | undefined, options?: { catalogs?: readonly Catalog[] }): readonly Catalog[];
|
|
8
|
+
/** A record's picker label: its name, then its gallery label, then the id. */
|
|
9
|
+
export declare function catalogRecordLabel(record: Record<string, unknown> | undefined, id: string): string;
|
|
10
|
+
/** Core's `catalogRecords` as picker options: embedded records first, then registered ones; `id` is the reference to write. */
|
|
11
|
+
export declare function listCatalogRecords(presentation: unknown, kind: CatalogKind, options?: { catalogs?: readonly Catalog[] }): CatalogOption[];
|
|
12
|
+
|
|
13
|
+
export interface SavedDocument {
|
|
14
|
+
/** The self-contained document: every referenced record embedded once under its group, `x-*` stripped. */
|
|
15
|
+
document: unknown;
|
|
16
|
+
added: EmbeddedRecord[];
|
|
17
|
+
unresolved: UnresolvedReferenceDiagnostic[];
|
|
18
|
+
}
|
|
19
|
+
/** Core's `embed`: the document to save or hand on, which renders the same with no catalog registered. The input is not changed. */
|
|
20
|
+
export declare function prepareSave(presentation: unknown, options?: { catalogs?: readonly Catalog[] }): SavedDocument;
|
|
21
|
+
/** `prepareSave` of the session's document with its registered catalogs (and `options.catalogs`). The session is not changed. */
|
|
22
|
+
export declare function saveDocument(editor: EditorSession, options?: { catalogs?: readonly Catalog[] }): SavedDocument;
|
|
23
|
+
|
|
24
|
+
export interface CatalogUpdateCheck {
|
|
25
|
+
/** One per embedded record (under `default` or a named group) whose registered record differs. */
|
|
26
|
+
changes: CatalogRecordChange[];
|
|
27
|
+
/** RFC 6902; apply only after the author approves (`applyCatalogUpdate`). */
|
|
28
|
+
patch: JsonPatchOperation[];
|
|
29
|
+
}
|
|
30
|
+
/** The "Update from catalog" check (core's `updateFromCatalog`). Applies nothing. `refs` limits it to some references. */
|
|
31
|
+
export declare function checkCatalogUpdates(presentation: unknown, options?: { catalogs?: readonly Catalog[]; refs?: readonly { kind: CatalogKind; reference: string }[] }): CatalogUpdateCheck;
|
|
32
|
+
/** Apply the approved updates (`refs`) as ONE undoable step, recomputed against the session's current document. */
|
|
33
|
+
export declare function applyCatalogUpdate(
|
|
34
|
+
editor: EditorSession,
|
|
35
|
+
options: { refs: readonly { kind: CatalogKind; reference: string }[]; catalogs?: readonly Catalog[]; meta?: Record<string, unknown> },
|
|
36
|
+
): EditorChange & { changed: boolean; changes: CatalogRecordChange[] };
|
|
37
|
+
|
|
38
|
+
export declare const REFERENCE_FINDING_CODES: readonly ["opf/unresolved-reference", "opf/undeclared-catalog", "opf/catalog-record-not-in-source"];
|
|
39
|
+
/** The reference and embedded-record findings (`REFERENCE_FINDING_CODES`) of a report. */
|
|
40
|
+
export declare function referenceFindings(validation: ValidationReport | undefined): ValidationReport["findings"];
|
|
41
|
+
|
|
42
|
+
export interface CatalogRecordRef { group: string; kind: CatalogKind; id: string }
|
|
43
|
+
/** The record a `opf/catalog-record-not-in-source` finding path names (`/catalogs/<group>/<kind>/<id>`). */
|
|
44
|
+
export declare function catalogRecordAt(path: string): CatalogRecordRef | undefined;
|
|
45
|
+
/** Move a record embedded under `default` or a named group into `catalogs.custom`, rewriting the references that resolved to it. */
|
|
46
|
+
export declare function prepareMoveToCustom(presentation: unknown, target: CatalogRecordRef, options?: { catalogs?: readonly Catalog[] }): { presentation: unknown; patches: JsonPatchOperation[]; changed: boolean; from: CatalogRecordRef; to: CatalogRecordRef };
|
|
47
|
+
/** `prepareMoveToCustom` as one undoable step on the session. */
|
|
48
|
+
export declare function moveToCustom(editor: EditorSession, target: CatalogRecordRef, options?: { catalogs?: readonly Catalog[]; meta?: Record<string, unknown> }): EditorChange & { changed: boolean; from: CatalogRecordRef; to: CatalogRecordRef };
|