@paged-media/plugin-sdk 0.2.36-canary.0 → 0.2.38-canary.3
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/dist/index.d.ts +46 -7
- package/dist/index.js +376 -33
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PagedBundle, BindingProvider, Disposable, ActiveBindingProvider, BindingPropertyRequest, BindingReadResult, BindingPropertyWrite, BindingWriteResult, BindingCollectionRequest, BindingCollectionResult, MutationInput, FontFaceAsset, BundleHost, ClipboardPayload, ConsentResult, DataProviderRegistration, DataProviderHandle, DataProviderInfo, DataProviderSnapshot, JournalRecord, ShellSurface, WidgetSurface, SchemaPanelRenderer, SchemaPanelContribution, EditContextContribution, ObjectTypeContribution, Diagnostic, TextCaret, PagedEditor, PluginManifest, ToolContribution, PanelContribution, CommandContribution, KeybindingContribution, OverlayContribution, ImporterContribution, ExporterContribution, ToolPreviewShape, BindingsSurface, PanelProps, SchemaGate, WasmArtifact, CanvasPointerEvent, Mutation, MutationOutcome, BindingProviderHandle } from '@paged-media/plugin-api';
|
|
1
|
+
import { PagedBundle, BindingProvider, Disposable, ActiveBindingProvider, BindingPropertyRequest, BindingReadResult, BindingPropertyWrite, BindingWriteResult, BindingCollectionRequest, BindingCollectionResult, MutationInput, FontFaceAsset, BundleHost, ClipboardPayload, ConsentResult, DataProviderRegistration, DataProviderHandle, DataProviderInfo, DataProviderSnapshot, JournalRecord, ShellSurface, WidgetSurface, WillSaveEvent, ToolSettingValue, SchemaPanelRenderer, SchemaPanelContribution, EditContextContribution, ObjectTypeContribution, Diagnostic, TextCaret, PagedEditor, PluginManifest, ToolContribution, PanelContribution, CommandContribution, KeybindingContribution, OverlayContribution, ImporterContribution, ExporterContribution, ToolPreviewShape, BindingsSurface, PanelProps, SchemaGate, WasmArtifact, CanvasPointerEvent, Mutation, MutationOutcome, BindingProviderHandle } from '@paged-media/plugin-api';
|
|
2
2
|
import { ComponentType } from 'react';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -333,7 +333,19 @@ type BundleTrust = "first-party";
|
|
|
333
333
|
* omits `saveFile` still injects a valid backend and
|
|
334
334
|
* `supports("shell.saveFile@1")` reports the truth.
|
|
335
335
|
*/
|
|
336
|
-
type ShellBackend = Omit<ShellSurface, "saveFile"> & Partial<Pick<ShellSurface, "saveFile">>;
|
|
336
|
+
type ShellBackend = Omit<ShellSurface, "saveFile" | "enterEditContext"> & Partial<Pick<ShellSurface, "saveFile" | "enterEditContext">>;
|
|
337
|
+
/** The host's will-save registry (protocol 66 batch): the editor awaits
|
|
338
|
+
* every registered listener before it serialises the document. The SDK
|
|
339
|
+
* wraps each bundle listener so it is awaited and its errors contained. */
|
|
340
|
+
interface WillSaveBackend {
|
|
341
|
+
register(pluginId: string, listener: (e: WillSaveEvent) => Promise<void>): Disposable;
|
|
342
|
+
}
|
|
343
|
+
/** The host's tool-options store, read side: the values its tool-options
|
|
344
|
+
* UI holds per tool id, plus a change subscription. */
|
|
345
|
+
interface ToolSettingsBackend {
|
|
346
|
+
get(toolId: string): Record<string, ToolSettingValue>;
|
|
347
|
+
subscribe(toolId: string, listener: () => void): () => void;
|
|
348
|
+
}
|
|
337
349
|
interface CreateBundleHostOptions {
|
|
338
350
|
/**
|
|
339
351
|
* The host's trust assertion for this bundle (`plugin-trust-line.md`).
|
|
@@ -385,7 +397,14 @@ interface CreateBundleHostOptions {
|
|
|
385
397
|
* the editor's UI package and is injected here. When absent,
|
|
386
398
|
* `host.widgets` is the plain-textarea fallback and
|
|
387
399
|
* `supports("widgets.codeEditor@1")` answers false. */
|
|
388
|
-
widgets?: WidgetSurface
|
|
400
|
+
widgets?: Partial<WidgetSurface>;
|
|
401
|
+
/** The host's will-save registry. When absent, `document.onWillSave`
|
|
402
|
+
* holds listeners that never run and `supports("document.onWillSave@1")`
|
|
403
|
+
* is false. */
|
|
404
|
+
willSave?: WillSaveBackend;
|
|
405
|
+
/** The host's tool-options store. When absent, `host.tools.settings`
|
|
406
|
+
* answers `{}` and `supports("tools.settings@1")` is false. */
|
|
407
|
+
toolSettings?: ToolSettingsBackend;
|
|
389
408
|
/** Host-provided SCHEMA-PANEL renderer (W3.1): the editor's
|
|
390
409
|
* `SchemaPanelRenderer` that walks a `PanelSchema` through the
|
|
391
410
|
* catalog + subscribes to the bundle's bindings. When absent,
|
|
@@ -560,8 +579,20 @@ interface RecordedContribution {
|
|
|
560
579
|
id: string;
|
|
561
580
|
value: ToolContribution | PanelContribution | SchemaPanelContribution | CommandContribution | KeybindingContribution | OverlayContribution | EditContextContribution | ObjectTypeContribution | ImporterContribution | ExporterContribution;
|
|
562
581
|
}
|
|
563
|
-
interface HarnessOptions extends LoadHeadlessEngineOptions, Pick<CreateBundleHostOptions, "console" | "storage" | "capabilityMode" | "assetSource" | "blobStore" | "clipboard" | "secrets" | "bindingProviders"> {
|
|
582
|
+
interface HarnessOptions extends LoadHeadlessEngineOptions, Pick<CreateBundleHostOptions, "console" | "storage" | "capabilityMode" | "assetSource" | "blobStore" | "clipboard" | "secrets" | "bindingProviders" | "willSave" | "toolSettings"> {
|
|
564
583
|
}
|
|
584
|
+
/** A headless will-save registry: listeners register per plugin and
|
|
585
|
+
* `fire()` awaits them all, the way the editor's save path does — so a
|
|
586
|
+
* bundle's commit-before-save is assertable without an editor. */
|
|
587
|
+
declare function inMemoryWillSave(): WillSaveBackend & {
|
|
588
|
+
fire(): Promise<void>;
|
|
589
|
+
count(): number;
|
|
590
|
+
};
|
|
591
|
+
/** A headless tool-options store: what the editor's tool-options UI would
|
|
592
|
+
* hold, settable from a test with `set`. */
|
|
593
|
+
declare function inMemoryToolSettings(): ToolSettingsBackend & {
|
|
594
|
+
set(toolId: string, key: string, value: ToolSettingValue): void;
|
|
595
|
+
};
|
|
565
596
|
/** What `createHeadlessHost` resolves to: a real engine-backed host plus
|
|
566
597
|
* the conformance affordances (load an IDML, read the contribution log,
|
|
567
598
|
* load a bundle, dispose honestly). */
|
|
@@ -615,6 +646,13 @@ interface HeadlessHost {
|
|
|
615
646
|
* that a path it does not declare comes back as a typed refusal to
|
|
616
647
|
* fall through to core). */
|
|
617
648
|
readonly bindingProviders: BindingProviderBackend;
|
|
649
|
+
/** The will-save registry this host injected: `fire()` runs every
|
|
650
|
+
* bundle's `document.onWillSave` listener and awaits them, as a save
|
|
651
|
+
* would. */
|
|
652
|
+
readonly willSave: ReturnType<typeof inMemoryWillSave>;
|
|
653
|
+
/** The tool-options store this host injected: `set()` stands in for the
|
|
654
|
+
* editor's tool-options UI, and `host.tools` reads it back. */
|
|
655
|
+
readonly toolSettings: ReturnType<typeof inMemoryToolSettings>;
|
|
618
656
|
/** Load an IDML package into the headless document. Resolves to the
|
|
619
657
|
* loaded page ids (or throws on a parse failure). */
|
|
620
658
|
load(idml: Uint8Array): Promise<string[]>;
|
|
@@ -664,8 +702,9 @@ declare const API_VERSION = "0.2.0";
|
|
|
664
702
|
*/
|
|
665
703
|
declare function satisfiesApiVersion(range: string, version?: string): boolean;
|
|
666
704
|
|
|
667
|
-
/** The default widget catalog: a textarea CodeEditor
|
|
668
|
-
*
|
|
705
|
+
/** The default widget catalog: a textarea CodeEditor and a native colour
|
|
706
|
+
* input. Each member is replaced by the one the host app injects through
|
|
707
|
+
* `createBundleHost({ widgets })`; members it omits keep the fallback. */
|
|
669
708
|
declare const FALLBACK_WIDGETS: WidgetSurface;
|
|
670
709
|
|
|
671
710
|
/** One recorded `getFontFace` call. */
|
|
@@ -882,4 +921,4 @@ declare function contributeObjectType(host: BundleHost, contribution: ObjectType
|
|
|
882
921
|
*/
|
|
883
922
|
declare function contributeBindingProvider(host: BundleHost, contextType: string, provider: BindingProvider): BindingProviderHandle;
|
|
884
923
|
|
|
885
|
-
export { API_VERSION, ASSET_BUDGETS, BLOB_BUDGETS, type BindingProviderBackend, type BlobStore, type BundleAssetProvider, type BundleAssetSource, type BundleHostHandle, type BundleTrust, CANVAS_WASM_PKG, CLICK_DRAG_THRESHOLD_PX, type ClipboardBackend, type ConsentBackend, type CreateBundleHostOptions, type DataProviderBackend, type DiagnosticsSink, DisposableStore, FALLBACK_WIDGETS, HOST_FEATURES, type HarnessOptions, type HeadlessCanvasWorker, type HeadlessHost, type HeadlessHostHandle, type JournalSink, type LoadBundleWasmOptions, type LoadHeadlessEngineOptions, type LoadedBundle, type LoadedBundleWasm, type LoadedEngine, type NativeDocumentBackend, type PageDrag, PluginApiNotImplemented, PluginCapabilityError, type RecordableAssetSource, type RecordedContribution, type RecordedFontFaceRequest, type RegisteredBindingProvider, type SecretStoreBackend, type SeededFace, type ShellBackend, type SpawnedWorker, type StorageBacking, type TextCaretBackend, WASM_BUDGETS, WORKER_BUDGETS, type WorkerBackend, beginPageDrag, commitAndSelect, contributeBindingProvider, contributeEditContext, contributeObjectType, contributePanel, contributeSchemaPanel, contributeTool, createBindingProviderRegistry, createBundleHost, createDataProviderRegistry, createHeadlessHost, createRecordableAssetSource, defineBundle, endLocalFor, loadBundle, loadBundleWasm, loadHeadlessEngine, makeSchemaPanelComponent, protocolFromVersion, pxToPt, readVendoredWireVersion, resolveCanvasWasm, resolveGate, satisfiesApiVersion, toDisposable };
|
|
924
|
+
export { API_VERSION, ASSET_BUDGETS, BLOB_BUDGETS, type BindingProviderBackend, type BlobStore, type BundleAssetProvider, type BundleAssetSource, type BundleHostHandle, type BundleTrust, CANVAS_WASM_PKG, CLICK_DRAG_THRESHOLD_PX, type ClipboardBackend, type ConsentBackend, type CreateBundleHostOptions, type DataProviderBackend, type DiagnosticsSink, DisposableStore, FALLBACK_WIDGETS, HOST_FEATURES, type HarnessOptions, type HeadlessCanvasWorker, type HeadlessHost, type HeadlessHostHandle, type JournalSink, type LoadBundleWasmOptions, type LoadHeadlessEngineOptions, type LoadedBundle, type LoadedBundleWasm, type LoadedEngine, type NativeDocumentBackend, type PageDrag, PluginApiNotImplemented, PluginCapabilityError, type RecordableAssetSource, type RecordedContribution, type RecordedFontFaceRequest, type RegisteredBindingProvider, type SecretStoreBackend, type SeededFace, type ShellBackend, type SpawnedWorker, type StorageBacking, type TextCaretBackend, type ToolSettingsBackend, WASM_BUDGETS, WORKER_BUDGETS, type WillSaveBackend, type WorkerBackend, beginPageDrag, commitAndSelect, contributeBindingProvider, contributeEditContext, contributeObjectType, contributePanel, contributeSchemaPanel, contributeTool, createBindingProviderRegistry, createBundleHost, createDataProviderRegistry, createHeadlessHost, createRecordableAssetSource, defineBundle, endLocalFor, inMemoryToolSettings, inMemoryWillSave, loadBundle, loadBundleWasm, loadHeadlessEngine, makeSchemaPanelComponent, protocolFromVersion, pxToPt, readVendoredWireVersion, resolveCanvasWasm, resolveGate, satisfiesApiVersion, toDisposable };
|
package/dist/index.js
CHANGED
|
@@ -93,8 +93,31 @@ function TextareaCodeEditor(props) {
|
|
|
93
93
|
}
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
|
+
function NativeColorPicker(props) {
|
|
97
|
+
const createElement2 = requireCreateElement(
|
|
98
|
+
"plugin-sdk: host.widgets.ColorPicker fell back to <input type=color>, but React is not installed. Install react, or inject a widget catalog via createBundleHost({ widgets })."
|
|
99
|
+
);
|
|
100
|
+
return createElement2("input", {
|
|
101
|
+
type: "color",
|
|
102
|
+
value: props.value,
|
|
103
|
+
disabled: props.disabled,
|
|
104
|
+
"aria-label": props.ariaLabel,
|
|
105
|
+
"data-color-picker-fallback": "native",
|
|
106
|
+
onChange: (e) => props.onChange(e.currentTarget.value),
|
|
107
|
+
onBlur: (e) => props.onCommit?.(e.currentTarget.value),
|
|
108
|
+
style: {
|
|
109
|
+
width: 40,
|
|
110
|
+
height: 24,
|
|
111
|
+
padding: 0,
|
|
112
|
+
border: "1px solid var(--pg-border)",
|
|
113
|
+
borderRadius: "var(--radius-sm, 4px)",
|
|
114
|
+
background: "var(--pg-bg)"
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
96
118
|
var FALLBACK_WIDGETS = {
|
|
97
|
-
CodeEditor: TextareaCodeEditor
|
|
119
|
+
CodeEditor: TextareaCodeEditor,
|
|
120
|
+
ColorPicker: NativeColorPicker
|
|
98
121
|
};
|
|
99
122
|
|
|
100
123
|
// src/schema-panel.tsx
|
|
@@ -139,6 +162,34 @@ function makeSchemaPanelComponent(contribution, bindings, renderer) {
|
|
|
139
162
|
}
|
|
140
163
|
|
|
141
164
|
// src/host-impl.ts
|
|
165
|
+
function isBytesSlot(m) {
|
|
166
|
+
if (m.op !== "replaceImageBytes") return false;
|
|
167
|
+
const b = m.args.bytes;
|
|
168
|
+
return Array.isArray(b) && b.length === 0;
|
|
169
|
+
}
|
|
170
|
+
function hasBytesSlot(m) {
|
|
171
|
+
if (isBytesSlot(m)) return true;
|
|
172
|
+
if (m.op === "batch") return m.args.ops.some((op) => hasBytesSlot(op));
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
function spliceBytesSlot(m, bytes) {
|
|
176
|
+
let filled = false;
|
|
177
|
+
const visit = (node) => {
|
|
178
|
+
if (filled) return node;
|
|
179
|
+
if (isBytesSlot(node)) {
|
|
180
|
+
filled = true;
|
|
181
|
+
return { ...node, args: { ...node.args, bytes } };
|
|
182
|
+
}
|
|
183
|
+
if (node.op === "batch") {
|
|
184
|
+
return {
|
|
185
|
+
...node,
|
|
186
|
+
args: { ...node.args, ops: node.args.ops.map((op) => visit(op)) }
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return node;
|
|
190
|
+
};
|
|
191
|
+
return visit(m);
|
|
192
|
+
}
|
|
142
193
|
var HOST_FEATURES = [
|
|
143
194
|
"contribute.tool@1",
|
|
144
195
|
"contribute.panel@1",
|
|
@@ -200,6 +251,11 @@ var HOST_FEATURES = [
|
|
|
200
251
|
// v51) is the writer, so it is live whenever a bundle runs (the worker
|
|
201
252
|
// handshake guarantees a v51-protocol worker).
|
|
202
253
|
"storage.parts@1",
|
|
254
|
+
// Protocol 66 — `host.parts.delete`. Static for the same reason as @1:
|
|
255
|
+
// the pinned engine (the worker handshake refuses an older one) answers
|
|
256
|
+
// `deletePagedPart`, and the binary lane is an optimisation the SDK
|
|
257
|
+
// falls back from, not a different contract.
|
|
258
|
+
"storage.parts@2",
|
|
203
259
|
// B-22 (protocol v57) — the planar-region read door. STATIC, and the
|
|
204
260
|
// flag means exactly one thing: this SDK's document surface IMPLEMENTS
|
|
205
261
|
// `planarRegions` and forwards it (retiring the `host.editor.client`
|
|
@@ -464,6 +520,50 @@ function createBindingProviderRegistry() {
|
|
|
464
520
|
}
|
|
465
521
|
};
|
|
466
522
|
}
|
|
523
|
+
function checkImage(image) {
|
|
524
|
+
const { width, height, rgba } = image;
|
|
525
|
+
if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0 || rgba.length !== width * height * 4) {
|
|
526
|
+
throw new Error(
|
|
527
|
+
`contribute.sceneLayer().submitImage: ${rgba.length} bytes for a ${width}x${height} RGBA8 image (want width*height*4)`
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function imageLayer(image) {
|
|
532
|
+
const [x, y, w, h2] = image.dest;
|
|
533
|
+
return {
|
|
534
|
+
items: [
|
|
535
|
+
{
|
|
536
|
+
kind: "image",
|
|
537
|
+
rgba: Array.from(image.rgba),
|
|
538
|
+
width: image.width,
|
|
539
|
+
height: image.height,
|
|
540
|
+
x,
|
|
541
|
+
y,
|
|
542
|
+
w,
|
|
543
|
+
h: h2
|
|
544
|
+
}
|
|
545
|
+
]
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function checkTiles(image, tiles) {
|
|
549
|
+
for (const t of tiles) {
|
|
550
|
+
if (t.width <= 0 || t.height <= 0 || t.x < 0 || t.y < 0 || t.x + t.width > image.width || t.y + t.height > image.height || t.rgba.length !== t.width * t.height * 4) {
|
|
551
|
+
throw new Error(
|
|
552
|
+
`contribute.sceneLayer().submitImageTiles: tile ${t.x},${t.y} ${t.width}x${t.height} (${t.rgba.length} bytes) does not fit the ${image.width}x${image.height} image`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function patchImage(image, tiles) {
|
|
558
|
+
const stride = image.width * 4;
|
|
559
|
+
for (const t of tiles) {
|
|
560
|
+
const row = t.width * 4;
|
|
561
|
+
for (let j = 0; j < t.height; j++) {
|
|
562
|
+
const dst = (t.y + j) * stride + t.x * 4;
|
|
563
|
+
image.rgba.set(t.rgba.subarray(j * row, j * row + row), dst);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
467
567
|
function createBundleHost(getEditor, manifest, options) {
|
|
468
568
|
const store = new DisposableStore();
|
|
469
569
|
const sink = options?.console ?? console;
|
|
@@ -892,6 +992,8 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
892
992
|
'capabilities.rendering must include "sceneLayer"'
|
|
893
993
|
);
|
|
894
994
|
const submitted = /* @__PURE__ */ new Set();
|
|
995
|
+
const imaged = /* @__PURE__ */ new Map();
|
|
996
|
+
const retained = /* @__PURE__ */ new Map();
|
|
895
997
|
const channel = () => getEditor().sceneLayers;
|
|
896
998
|
const surface = {
|
|
897
999
|
async submit(elementId, layer) {
|
|
@@ -907,14 +1009,83 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
907
1009
|
},
|
|
908
1010
|
async clear(elementId) {
|
|
909
1011
|
submitted.delete(elementId);
|
|
1012
|
+
imaged.delete(elementId);
|
|
1013
|
+
retained.delete(elementId);
|
|
910
1014
|
await channel()?.clear(elementId);
|
|
911
1015
|
},
|
|
1016
|
+
// Protocol 66 — the binary scene-image lane, with the SDK owning
|
|
1017
|
+
// the fallback so a bundle calls one method on every host.
|
|
1018
|
+
async submitImage(elementId, image, options2) {
|
|
1019
|
+
const ch = channel();
|
|
1020
|
+
if (!ch) {
|
|
1021
|
+
log.warn(
|
|
1022
|
+
`contribute.sceneLayer().submitImage("${elementId}") ignored \u2014 the host wired no scene channel (probe supports("rendering.sceneLayer@1"))`
|
|
1023
|
+
);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
checkImage(image);
|
|
1027
|
+
submitted.add(elementId);
|
|
1028
|
+
imaged.set(elementId, { width: image.width, height: image.height });
|
|
1029
|
+
if (ch.submitImage && ch.submitImageTiles) {
|
|
1030
|
+
retained.delete(elementId);
|
|
1031
|
+
await ch.submitImage(
|
|
1032
|
+
elementId,
|
|
1033
|
+
image,
|
|
1034
|
+
manifest.id,
|
|
1035
|
+
options2?.transfer ?? false
|
|
1036
|
+
);
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
const copy = {
|
|
1040
|
+
rgba: new Uint8Array(image.rgba),
|
|
1041
|
+
width: image.width,
|
|
1042
|
+
height: image.height,
|
|
1043
|
+
dest: [...image.dest]
|
|
1044
|
+
};
|
|
1045
|
+
retained.set(elementId, copy);
|
|
1046
|
+
await ch.submit(elementId, imageLayer(copy), manifest.id);
|
|
1047
|
+
},
|
|
1048
|
+
async submitImageTiles(elementId, tiles, options2) {
|
|
1049
|
+
const ch = channel();
|
|
1050
|
+
if (!ch) {
|
|
1051
|
+
log.warn(
|
|
1052
|
+
`contribute.sceneLayer().submitImageTiles("${elementId}") ignored \u2014 the host wired no scene channel`
|
|
1053
|
+
);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
const size = imaged.get(elementId);
|
|
1057
|
+
if (!size) {
|
|
1058
|
+
throw new Error(
|
|
1059
|
+
`contribute.sceneLayer().submitImageTiles("${elementId}"): no image was submitted for this element \u2014 call submitImage first`
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
checkTiles(size, tiles);
|
|
1063
|
+
if (ch.submitImage && ch.submitImageTiles) {
|
|
1064
|
+
await ch.submitImageTiles(
|
|
1065
|
+
elementId,
|
|
1066
|
+
tiles,
|
|
1067
|
+
manifest.id,
|
|
1068
|
+
options2?.transfer ?? false
|
|
1069
|
+
);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
const image = retained.get(elementId);
|
|
1073
|
+
if (!image) {
|
|
1074
|
+
throw new Error(
|
|
1075
|
+
`contribute.sceneLayer().submitImageTiles("${elementId}"): the retained image is gone (the binary lane went away mid-session)`
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
patchImage(image, tiles);
|
|
1079
|
+
await ch.submit(elementId, imageLayer(image), manifest.id);
|
|
1080
|
+
},
|
|
912
1081
|
dispose() {
|
|
913
1082
|
const ch = channel();
|
|
914
1083
|
if (ch) {
|
|
915
1084
|
for (const id of submitted) void ch.clear(id);
|
|
916
1085
|
}
|
|
917
1086
|
submitted.clear();
|
|
1087
|
+
imaged.clear();
|
|
1088
|
+
retained.clear();
|
|
918
1089
|
}
|
|
919
1090
|
};
|
|
920
1091
|
return store.add(surface);
|
|
@@ -965,33 +1136,66 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
965
1136
|
walk(roots, null);
|
|
966
1137
|
return index;
|
|
967
1138
|
};
|
|
1139
|
+
const gateRawWrite = (mutation, door) => {
|
|
1140
|
+
const denied = denyWrite(hasDoc("write"), door, "capabilities.document.write");
|
|
1141
|
+
if (denied !== null) return { applied: false, error: denied };
|
|
1142
|
+
const foreign = foreignMetadataKey(mutation);
|
|
1143
|
+
if (foreign !== null) {
|
|
1144
|
+
const error = `setPluginMetadata key "${foreign}" is outside this plugin's namespace ("${metadataKey(manifest)}")`;
|
|
1145
|
+
log.warn(error);
|
|
1146
|
+
return { applied: false, error };
|
|
1147
|
+
}
|
|
1148
|
+
return null;
|
|
1149
|
+
};
|
|
1150
|
+
const mutateOutcome = (reply) => {
|
|
1151
|
+
if (reply.kind === "mutationApplied") {
|
|
1152
|
+
const minted = reply.payload.minted;
|
|
1153
|
+
return {
|
|
1154
|
+
applied: true,
|
|
1155
|
+
createdId: reply.payload.createdId ?? null,
|
|
1156
|
+
pageIds: reply.payload.pageIds,
|
|
1157
|
+
...minted !== void 0 ? { minted } : {}
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
return {
|
|
1161
|
+
applied: false,
|
|
1162
|
+
error: reply.kind === "mutationFailed" ? reply.payload : reply
|
|
1163
|
+
};
|
|
1164
|
+
};
|
|
968
1165
|
const document = {
|
|
969
1166
|
async mutate(mutation) {
|
|
970
|
-
const
|
|
971
|
-
|
|
972
|
-
"document.mutate",
|
|
973
|
-
"capabilities.document.write"
|
|
974
|
-
);
|
|
975
|
-
if (denied !== null) return { applied: false, error: denied };
|
|
976
|
-
const foreign = foreignMetadataKey(mutation);
|
|
977
|
-
if (foreign !== null) {
|
|
978
|
-
const error = `setPluginMetadata key "${foreign}" is outside this plugin's namespace ("${metadataKey(manifest)}")`;
|
|
979
|
-
log.warn(error);
|
|
980
|
-
return { applied: false, error };
|
|
981
|
-
}
|
|
1167
|
+
const gated = gateRawWrite(mutation, "document.mutate");
|
|
1168
|
+
if (gated !== null) return gated;
|
|
982
1169
|
try {
|
|
983
1170
|
const reply = await getEditor().client.mutate(mutation);
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1171
|
+
return mutateOutcome(reply);
|
|
1172
|
+
} catch (error) {
|
|
1173
|
+
return { applied: false, error };
|
|
1174
|
+
}
|
|
1175
|
+
},
|
|
1176
|
+
async mutateWithBytes(mutation, bytes, options2) {
|
|
1177
|
+
const gated = gateRawWrite(mutation, "document.mutateWithBytes");
|
|
1178
|
+
if (gated !== null) return gated;
|
|
1179
|
+
if (!hasBytesSlot(mutation)) {
|
|
991
1180
|
return {
|
|
992
1181
|
applied: false,
|
|
993
|
-
error:
|
|
1182
|
+
error: "mutateWithBytes: no replaceImageBytes with an empty `bytes` slot to fill"
|
|
994
1183
|
};
|
|
1184
|
+
}
|
|
1185
|
+
try {
|
|
1186
|
+
const editor = getEditor();
|
|
1187
|
+
if (typeof editor.mutateWithBytes === "function") {
|
|
1188
|
+
const reply2 = await editor.mutateWithBytes(
|
|
1189
|
+
mutation,
|
|
1190
|
+
bytes,
|
|
1191
|
+
options2?.transfer === true
|
|
1192
|
+
);
|
|
1193
|
+
return mutateOutcome(reply2);
|
|
1194
|
+
}
|
|
1195
|
+
const reply = await editor.client.mutate(
|
|
1196
|
+
spliceBytesSlot(mutation, Array.from(bytes))
|
|
1197
|
+
);
|
|
1198
|
+
return mutateOutcome(reply);
|
|
995
1199
|
} catch (error) {
|
|
996
1200
|
return { applied: false, error };
|
|
997
1201
|
}
|
|
@@ -1185,6 +1389,24 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1185
1389
|
}
|
|
1186
1390
|
});
|
|
1187
1391
|
return store.add(toDisposable(off));
|
|
1392
|
+
},
|
|
1393
|
+
// Protocol 66 batch — the save hook. The host awaits every
|
|
1394
|
+
// registered listener before it serialises the document; the wrapper
|
|
1395
|
+
// makes a bundle's failure a logged event, never a failed save.
|
|
1396
|
+
onWillSave(listener) {
|
|
1397
|
+
const backend = options?.willSave;
|
|
1398
|
+
if (!backend) {
|
|
1399
|
+
return store.add(toDisposable(() => {
|
|
1400
|
+
}));
|
|
1401
|
+
}
|
|
1402
|
+
const wrapped = async (e) => {
|
|
1403
|
+
try {
|
|
1404
|
+
await listener(e);
|
|
1405
|
+
} catch (err) {
|
|
1406
|
+
log.error(`document.onWillSave listener failed: ${String(err)}`);
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
return store.add(backend.register(manifest.id, wrapped));
|
|
1188
1410
|
}
|
|
1189
1411
|
};
|
|
1190
1412
|
const selection = {
|
|
@@ -1474,9 +1696,44 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1474
1696
|
`shell.saveFile("${saveOptions.suggestedName}") ignored \u2014 the host app wired no file saver (probe with supports("shell.saveFile@1"))`
|
|
1475
1697
|
);
|
|
1476
1698
|
return false;
|
|
1699
|
+
},
|
|
1700
|
+
// Programmatic entry into one of THIS bundle's edit contexts — the
|
|
1701
|
+
// same stack push a double-click performs. A foreign or unregistered
|
|
1702
|
+
// type is a bug in the bundle, so it throws; a host that cannot enter
|
|
1703
|
+
// answers false.
|
|
1704
|
+
async enterEditContext(type, elementId) {
|
|
1705
|
+
if (!registeredContexts.has(type)) {
|
|
1706
|
+
throw new Error(
|
|
1707
|
+
`plugin-api: shell.enterEditContext("${type}") \u2014 ${manifest.id} has registered no edit context of that type. A bundle enters only its own contexts: call contribute.editContext({ type: "${type}", \u2026 }) first.`
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
if (!shellBackend?.enterEditContext) {
|
|
1711
|
+
log.warn(
|
|
1712
|
+
`shell.enterEditContext("${type}") ignored \u2014 the host app wired no context entry (probe with supports("shell.enterEditContext@1"))`
|
|
1713
|
+
);
|
|
1714
|
+
return false;
|
|
1715
|
+
}
|
|
1716
|
+
return shellBackend.enterEditContext(type, elementId);
|
|
1477
1717
|
}
|
|
1478
1718
|
};
|
|
1479
|
-
const
|
|
1719
|
+
const toolSettingsBackend = options?.toolSettings;
|
|
1720
|
+
const tools = {
|
|
1721
|
+
settings(toolId) {
|
|
1722
|
+
assertNamespaced(toolId, "tools.settings tool");
|
|
1723
|
+
return { ...toolSettingsBackend?.get(toolId) ?? {} };
|
|
1724
|
+
},
|
|
1725
|
+
onDidChangeSettings(toolId, listener) {
|
|
1726
|
+
assertNamespaced(toolId, "tools.onDidChangeSettings tool");
|
|
1727
|
+
if (!toolSettingsBackend) return store.add(toDisposable(() => {
|
|
1728
|
+
}));
|
|
1729
|
+
const off = toolSettingsBackend.subscribe(
|
|
1730
|
+
toolId,
|
|
1731
|
+
() => listener({ ...toolSettingsBackend.get(toolId) })
|
|
1732
|
+
);
|
|
1733
|
+
return store.add(toDisposable(off));
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1736
|
+
const widgets = { ...FALLBACK_WIDGETS, ...options?.widgets };
|
|
1480
1737
|
const assetSource = options?.assetSource;
|
|
1481
1738
|
const assets = {
|
|
1482
1739
|
async getFontFace(family, style) {
|
|
@@ -1673,10 +1930,20 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1673
1930
|
};
|
|
1674
1931
|
const parts = {
|
|
1675
1932
|
async write(path, bytes) {
|
|
1933
|
+
const full = partsFull(path);
|
|
1934
|
+
const binary = getEditor().parts;
|
|
1935
|
+
if (binary) {
|
|
1936
|
+
try {
|
|
1937
|
+
await binary.write(full, bytes, manifest.id);
|
|
1938
|
+
} catch (err) {
|
|
1939
|
+
throw new Error(`host.parts.write("${path}"): ${String(err)}`);
|
|
1940
|
+
}
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1676
1943
|
const reply = await getEditor().client.send({
|
|
1677
1944
|
kind: "writePagedPart",
|
|
1678
1945
|
payload: {
|
|
1679
|
-
path:
|
|
1946
|
+
path: full,
|
|
1680
1947
|
bytes: Array.from(bytes),
|
|
1681
1948
|
// C-34 — DECLARE WHO IS WRITING, so the engine
|
|
1682
1949
|
// confines this to `paged/<id>/` instead of trusting that we
|
|
@@ -1692,14 +1959,6 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1692
1959
|
// probe needed — there is no degraded path to choose between.
|
|
1693
1960
|
caller: manifest.id
|
|
1694
1961
|
}
|
|
1695
|
-
// THE CAST, named rather than left to be discovered. `wire.d.ts`
|
|
1696
|
-
// is vendored from the PUBLISHED `@paged-media/canvas-wasm`
|
|
1697
|
-
// (0.61.0), whose `writePagedPart` has no `caller` — the field
|
|
1698
|
-
// exists in core today and reaches these types only when the
|
|
1699
|
-
// next canvas-wasm publishes and `sync-wire` runs. Same escape-hatch
|
|
1700
|
-
// shape the repo already uses elsewhere: a cast pointing at a
|
|
1701
|
-
// contract that EXISTS AND IS COMMITTED, not at a hope. It
|
|
1702
|
-
// deletes itself at the repin.
|
|
1703
1962
|
});
|
|
1704
1963
|
if (reply.kind !== "pagedPartWritten") {
|
|
1705
1964
|
const err = reply.kind === "pagedPartFailed" ? reply.payload.error : `unexpected ${reply.kind}`;
|
|
@@ -1707,9 +1966,12 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1707
1966
|
}
|
|
1708
1967
|
},
|
|
1709
1968
|
async read(path) {
|
|
1969
|
+
const full = partsFull(path);
|
|
1970
|
+
const binary = getEditor().parts;
|
|
1971
|
+
if (binary) return binary.read(full);
|
|
1710
1972
|
const reply = await getEditor().client.send({
|
|
1711
1973
|
kind: "readPagedPart",
|
|
1712
|
-
payload: { path:
|
|
1974
|
+
payload: { path: full }
|
|
1713
1975
|
});
|
|
1714
1976
|
if (reply.kind === "pagedPartRead") {
|
|
1715
1977
|
return reply.payload.found ? Uint8Array.from(reply.payload.bytes) : null;
|
|
@@ -1725,6 +1987,27 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1725
1987
|
return reply.payload.paths.map(
|
|
1726
1988
|
(p) => p.startsWith(partsBase) ? p.slice(partsBase.length) : p
|
|
1727
1989
|
);
|
|
1990
|
+
},
|
|
1991
|
+
// Protocol 66 — `deletePagedPart`. The engine tombstones a part the
|
|
1992
|
+
// loaded file carries, so it leaves the next save rather than
|
|
1993
|
+
// reappearing from the source container.
|
|
1994
|
+
async delete(path) {
|
|
1995
|
+
const full = partsFull(path);
|
|
1996
|
+
const binary = getEditor().parts;
|
|
1997
|
+
if (binary) {
|
|
1998
|
+
try {
|
|
1999
|
+
return await binary.delete(full, manifest.id);
|
|
2000
|
+
} catch (err2) {
|
|
2001
|
+
throw new Error(`host.parts.delete("${path}"): ${String(err2)}`);
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
const reply = await getEditor().client.send({
|
|
2005
|
+
kind: "deletePagedPart",
|
|
2006
|
+
payload: { path: full, caller: manifest.id }
|
|
2007
|
+
});
|
|
2008
|
+
if (reply.kind === "pagedPartDeleted") return reply.payload.existed;
|
|
2009
|
+
const err = reply.kind === "pagedPartFailed" ? reply.payload.error : `unexpected ${reply.kind}`;
|
|
2010
|
+
throw new Error(`host.parts.delete("${path}"): ${err}`);
|
|
1728
2011
|
}
|
|
1729
2012
|
};
|
|
1730
2013
|
const nativeDocBackend = options?.nativeDocument;
|
|
@@ -1972,9 +2255,30 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1972
2255
|
featureSet.add("shell.saveFile@1");
|
|
1973
2256
|
}
|
|
1974
2257
|
}
|
|
1975
|
-
if (options?.widgets) {
|
|
2258
|
+
if (options?.widgets?.CodeEditor) {
|
|
1976
2259
|
featureSet.add("widgets.codeEditor@1");
|
|
1977
2260
|
}
|
|
2261
|
+
if (options?.widgets?.ColorPicker) {
|
|
2262
|
+
featureSet.add("widgets.colorPicker@1");
|
|
2263
|
+
}
|
|
2264
|
+
if (options?.willSave) {
|
|
2265
|
+
featureSet.add("document.onWillSave@1");
|
|
2266
|
+
}
|
|
2267
|
+
if (typeof options?.shell?.enterEditContext === "function") {
|
|
2268
|
+
featureSet.add("shell.enterEditContext@1");
|
|
2269
|
+
}
|
|
2270
|
+
if (options?.toolSettings) {
|
|
2271
|
+
featureSet.add("tools.settings@1");
|
|
2272
|
+
}
|
|
2273
|
+
{
|
|
2274
|
+
const sl = getEditor().sceneLayers;
|
|
2275
|
+
if (typeof sl?.submitImage === "function" && typeof sl?.submitImageTiles === "function") {
|
|
2276
|
+
featureSet.add("rendering.sceneLayer.binary@1");
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
if (typeof getEditor().mutateWithBytes === "function") {
|
|
2280
|
+
featureSet.add("document.mutateBinary@1");
|
|
2281
|
+
}
|
|
1978
2282
|
if (options?.schemaPanelRenderer) {
|
|
1979
2283
|
featureSet.add("schemaPanel.renderer@1");
|
|
1980
2284
|
}
|
|
@@ -2040,6 +2344,7 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
2040
2344
|
workers,
|
|
2041
2345
|
secrets,
|
|
2042
2346
|
clipboard,
|
|
2347
|
+
tools,
|
|
2043
2348
|
supports: (feature) => featureSet.has(feature),
|
|
2044
2349
|
get editor() {
|
|
2045
2350
|
return getEditor();
|
|
@@ -2186,6 +2491,36 @@ async function loadHeadlessEngine(options = {}) {
|
|
|
2186
2491
|
}
|
|
2187
2492
|
|
|
2188
2493
|
// src/harness.ts
|
|
2494
|
+
function inMemoryWillSave() {
|
|
2495
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2496
|
+
return {
|
|
2497
|
+
register(_pluginId, listener) {
|
|
2498
|
+
listeners.add(listener);
|
|
2499
|
+
return { dispose: () => void listeners.delete(listener) };
|
|
2500
|
+
},
|
|
2501
|
+
async fire() {
|
|
2502
|
+
await Promise.all([...listeners].map((l) => l({ format: "paged" })));
|
|
2503
|
+
},
|
|
2504
|
+
count: () => listeners.size
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
function inMemoryToolSettings() {
|
|
2508
|
+
const values = /* @__PURE__ */ new Map();
|
|
2509
|
+
const subs = /* @__PURE__ */ new Map();
|
|
2510
|
+
return {
|
|
2511
|
+
get: (toolId) => ({ ...values.get(toolId) ?? {} }),
|
|
2512
|
+
subscribe(toolId, listener) {
|
|
2513
|
+
const set = subs.get(toolId) ?? /* @__PURE__ */ new Set();
|
|
2514
|
+
set.add(listener);
|
|
2515
|
+
subs.set(toolId, set);
|
|
2516
|
+
return () => void set.delete(listener);
|
|
2517
|
+
},
|
|
2518
|
+
set(toolId, key, value) {
|
|
2519
|
+
values.set(toolId, { ...values.get(toolId) ?? {}, [key]: value });
|
|
2520
|
+
for (const l of subs.get(toolId) ?? []) l();
|
|
2521
|
+
}
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2189
2524
|
function inMemoryBlobStore() {
|
|
2190
2525
|
const byPlugin = /* @__PURE__ */ new Map();
|
|
2191
2526
|
const dir = (id) => {
|
|
@@ -2437,6 +2772,8 @@ async function createHeadlessHost(options = {}) {
|
|
|
2437
2772
|
const clipboard = options.clipboard ?? inMemoryClipboard();
|
|
2438
2773
|
const secrets = options.secrets ?? inMemorySecretStore();
|
|
2439
2774
|
const bindingProviders = options.bindingProviders ?? createBindingProviderRegistry();
|
|
2775
|
+
const willSave = inMemoryWillSave();
|
|
2776
|
+
const toolSettings = inMemoryToolSettings();
|
|
2440
2777
|
const buildHost = (manifest, mode) => createBundleHost(() => editor, manifest, {
|
|
2441
2778
|
console: options.console,
|
|
2442
2779
|
storage: options.storage,
|
|
@@ -2444,6 +2781,8 @@ async function createHeadlessHost(options = {}) {
|
|
|
2444
2781
|
clipboard,
|
|
2445
2782
|
secrets,
|
|
2446
2783
|
bindingProviders,
|
|
2784
|
+
willSave: options.willSave ?? willSave,
|
|
2785
|
+
toolSettings: options.toolSettings ?? toolSettings,
|
|
2447
2786
|
capabilityMode: mode,
|
|
2448
2787
|
// W-06 — a recordable fake asset source the conformance harness
|
|
2449
2788
|
// can pass so a bundle's `@font-face` byte path is exercisable
|
|
@@ -2564,6 +2903,8 @@ async function createHeadlessHost(options = {}) {
|
|
|
2564
2903
|
return lastPreviews;
|
|
2565
2904
|
},
|
|
2566
2905
|
bindingProviders,
|
|
2906
|
+
willSave,
|
|
2907
|
+
toolSettings,
|
|
2567
2908
|
async load(idml) {
|
|
2568
2909
|
const raw = worker.loadDocumentDirect(seqCounter++, idml);
|
|
2569
2910
|
const reply = JSON.parse(raw);
|
|
@@ -2951,6 +3292,8 @@ export {
|
|
|
2951
3292
|
createRecordableAssetSource,
|
|
2952
3293
|
defineBundle,
|
|
2953
3294
|
endLocalFor,
|
|
3295
|
+
inMemoryToolSettings,
|
|
3296
|
+
inMemoryWillSave,
|
|
2954
3297
|
loadBundle,
|
|
2955
3298
|
loadBundleWasm,
|
|
2956
3299
|
loadHeadlessEngine,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paged-media/plugin-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.38-canary.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@paged-media/plugin-api": "0.2.
|
|
14
|
+
"@paged-media/plugin-api": "0.2.38-canary.3"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
17
|
"react": "^18.3.0"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@paged-media/canvas-wasm": "0.
|
|
25
|
+
"@paged-media/canvas-wasm": "0.66.0",
|
|
26
26
|
"@types/node": "20.19.39",
|
|
27
27
|
"@types/react": "^18.3.31",
|
|
28
28
|
"react": "^18.3.1",
|