@openeditor/native 0.0.33 → 0.0.34
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 +47 -65
- package/dist/controller.d.ts +79 -0
- package/dist/controller.js +254 -0
- package/dist/index.d.ts +4 -101
- package/dist/index.js +2 -910
- package/dist/native-editor.d.ts +43 -0
- package/dist/native-editor.js +171 -0
- package/dist/toolbar-host.d.ts +17 -0
- package/dist/toolbar-host.js +206 -0
- package/dist/toolbar-items.d.ts +88 -0
- package/dist/toolbar-items.js +52 -0
- package/package.json +6 -18
- package/dist/document.d.ts +0 -7
- package/dist/document.js +0 -69
- package/dist/emoji-button.d.ts +0 -8
- package/dist/emoji-button.js +0 -14
- package/dist/emoji-data.d.ts +0 -44
- package/dist/emoji-data.js +0 -50
- package/dist/emoji-picker.d.ts +0 -12
- package/dist/emoji-picker.js +0 -67
- package/dist/selection.d.ts +0 -3
- package/dist/selection.js +0 -32
- package/dist/theme.d.ts +0 -2
- package/dist/theme.js +0 -39
- package/dist/toolbar.d.ts +0 -30
- package/dist/toolbar.js +0 -282
package/README.md
CHANGED
|
@@ -1,78 +1,60 @@
|
|
|
1
1
|
# `@openeditor/native`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React Native host for the offline OpenEditor embedded runtime.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The document, selection, history, and DOM NodeViews live inside one internally
|
|
6
|
+
scrollable WebView. React Native owns the keyboard toolbar and application
|
|
7
|
+
effects such as pickers, sheets, and navigation. The bridge sends commands and
|
|
8
|
+
lightweight state/revision events; document JSON only crosses it when the host
|
|
9
|
+
explicitly calls `getDocument()` or `flushDocument()`.
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
On iOS, the host hides WKWebView's default previous/next/Done accessory bar so
|
|
12
|
+
the OpenEditor toolbar is the only bar above the software keyboard. The toolbar
|
|
13
|
+
is mounted only while the editable WebView is focused and the software keyboard
|
|
14
|
+
is visible. Consumers can opt back into the system bar with
|
|
15
|
+
`webViewProps={{ hideKeyboardAccessoryView: false }}`.
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- `OpenEditorNativeViewer` for read-only rendering
|
|
12
|
-
- `OpenEditorNativeEmojiPicker` for standalone native emoji selection
|
|
13
|
-
- native document bridge helpers where renderer adaptation is required
|
|
17
|
+
The `theme` prop accepts the same OpenEditor theme-token contract used by the
|
|
18
|
+
web React surface.
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
opens the native picker and commits the selected Unicode value through editor
|
|
17
|
-
history. Inline emoji continue to use the device keyboard.
|
|
20
|
+
## Host integration
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- `@openeditor/react-native-prose-editor`
|
|
22
|
-
- `react`
|
|
23
|
-
- `react-native`
|
|
24
|
-
- `react-native-keyboard-controller` (optional but recommended for keyboard toolbar placement)
|
|
25
|
-
|
|
26
|
-
## Quick start
|
|
22
|
+
Install the `react-native-webview` peer dependency. The self-contained offline
|
|
23
|
+
runtime is packaged with the component, so Metro asset customization is not required:
|
|
27
24
|
|
|
28
25
|
```tsx
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Pass `enabledBlocks` to restrict authoring. Page and attachment controls are
|
|
52
|
-
automatically omitted until their required host runtimes are present, while
|
|
53
|
-
existing nodes remain readable.
|
|
54
|
-
|
|
55
|
-
## Standalone emoji picker
|
|
56
|
-
|
|
57
|
-
The editor wires this automatically for callouts and pages. Hosts can also use
|
|
58
|
-
the same picker for page headers or other product UI:
|
|
59
|
-
|
|
60
|
-
```tsx
|
|
61
|
-
import { OpenEditorNativeEmojiPicker } from "@openeditor/native";
|
|
62
|
-
|
|
63
|
-
<OpenEditorNativeEmojiPicker
|
|
64
|
-
visible={pickerOpen}
|
|
65
|
-
onEmojiSelect={setPageIcon}
|
|
66
|
-
onRequestClose={() => setPickerOpen(false)}
|
|
67
|
-
/>;
|
|
26
|
+
const editor = useRef<OpenEditorNativeController>(null);
|
|
27
|
+
|
|
28
|
+
<OpenEditorNative
|
|
29
|
+
ref={editor}
|
|
30
|
+
initialDocument={document}
|
|
31
|
+
contentInsets={{ top: transparentHeaderHeight, bottom: 16 }}
|
|
32
|
+
onDocumentChanged={({ documentRevision }) => {
|
|
33
|
+
scheduleAutosave(async () => {
|
|
34
|
+
const snapshot = await editor.current?.getDocument({
|
|
35
|
+
minimumRevision: documentRevision,
|
|
36
|
+
});
|
|
37
|
+
if (snapshot) await save(snapshot.document);
|
|
38
|
+
});
|
|
39
|
+
}}
|
|
40
|
+
nativeEffects={{
|
|
41
|
+
pickAttachment: pickUploadAndReturnDurableAttachment,
|
|
42
|
+
createPage: openCreatePageSheet,
|
|
43
|
+
updatePage: updatePageMetadata,
|
|
44
|
+
openPage: navigateToPage,
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
68
47
|
```
|
|
69
48
|
|
|
70
|
-
The
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
49
|
+
The block picker derives its available page, image, and attachment commands
|
|
50
|
+
from the supplied effect handlers. If the application cannot upload media to a
|
|
51
|
+
durable URL, omit the corresponding picker effect rather than returning a
|
|
52
|
+
temporary `file://` URI.
|
|
74
53
|
|
|
75
|
-
|
|
54
|
+
Do not wrap the component in a React Native `ScrollView`; give it a bounded
|
|
55
|
+
flex layout so the WebView remains the only document scroll owner. Before a
|
|
56
|
+
screen transition or background save, call `flushDocument()` to commit pending
|
|
57
|
+
composition and receive the current snapshot.
|
|
76
58
|
|
|
77
|
-
|
|
78
|
-
|
|
59
|
+
For a read-only surface, use
|
|
60
|
+
`<OpenEditorNative initialDocument={document} editable={false} showToolbar={false} />`.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { OpenEditorDocument } from "@openeditor/core";
|
|
2
|
+
import { type OpenEditorCommandAck, type OpenEditorNativeAttachmentResult, type OpenEditorNativeEffectRequest, type OpenEditorNativeEffectResult, type OpenEditorNativeImageResult, type OpenEditorNativePageResult, type OpenEditorRuntimeCommand, type OpenEditorRuntimeMessage, type OpenEditorRuntimeState } from "@openeditor/embedded-runtime";
|
|
3
|
+
export declare class OpenEditorNativeLifecycleCancellationError extends Error {
|
|
4
|
+
readonly code = "OPENEDITOR_NATIVE_LIFECYCLE_CANCELLED";
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
export declare const isOpenEditorNativeLifecycleCancellation: (error: unknown) => error is OpenEditorNativeLifecycleCancellationError;
|
|
8
|
+
export type OpenEditorNativeEffectHandlers = {
|
|
9
|
+
pickImage?: (effect: Extract<OpenEditorNativeEffectRequest["effect"], {
|
|
10
|
+
type: "pickImage";
|
|
11
|
+
}>) => Promise<OpenEditorNativeEffectResult<OpenEditorNativeImageResult>> | OpenEditorNativeEffectResult<OpenEditorNativeImageResult>;
|
|
12
|
+
pickAttachment?: (effect: Extract<OpenEditorNativeEffectRequest["effect"], {
|
|
13
|
+
type: "pickAttachment";
|
|
14
|
+
}>) => Promise<OpenEditorNativeEffectResult<OpenEditorNativeAttachmentResult>> | OpenEditorNativeEffectResult<OpenEditorNativeAttachmentResult>;
|
|
15
|
+
createPage?: (effect: Extract<OpenEditorNativeEffectRequest["effect"], {
|
|
16
|
+
type: "createPage";
|
|
17
|
+
}>) => Promise<OpenEditorNativeEffectResult<OpenEditorNativePageResult>> | OpenEditorNativeEffectResult<OpenEditorNativePageResult>;
|
|
18
|
+
updatePage?: (effect: Extract<OpenEditorNativeEffectRequest["effect"], {
|
|
19
|
+
type: "updatePage";
|
|
20
|
+
}>) => Promise<OpenEditorNativeEffectResult<OpenEditorNativePageResult>> | OpenEditorNativeEffectResult<OpenEditorNativePageResult>;
|
|
21
|
+
openPage?: (effect: Extract<OpenEditorNativeEffectRequest["effect"], {
|
|
22
|
+
type: "openPage";
|
|
23
|
+
}>) => Promise<OpenEditorNativeEffectResult<null>> | OpenEditorNativeEffectResult<null>;
|
|
24
|
+
openAttachment?: (effect: Extract<OpenEditorNativeEffectRequest["effect"], {
|
|
25
|
+
type: "openAttachment";
|
|
26
|
+
}>) => Promise<OpenEditorNativeEffectResult<null>> | OpenEditorNativeEffectResult<null>;
|
|
27
|
+
openUrl?: (effect: Extract<OpenEditorNativeEffectRequest["effect"], {
|
|
28
|
+
type: "openUrl";
|
|
29
|
+
}>) => Promise<OpenEditorNativeEffectResult<null>> | OpenEditorNativeEffectResult<null>;
|
|
30
|
+
};
|
|
31
|
+
export type OpenEditorNativeControllerEvent = Extract<OpenEditorRuntimeMessage, {
|
|
32
|
+
type: "ready" | "stateChanged" | "documentChanged" | "error";
|
|
33
|
+
}>;
|
|
34
|
+
export type OpenEditorDocumentSnapshot = {
|
|
35
|
+
document: OpenEditorDocument;
|
|
36
|
+
revision: number;
|
|
37
|
+
};
|
|
38
|
+
export type OpenEditorNativeController = {
|
|
39
|
+
readonly ready: boolean;
|
|
40
|
+
waitUntilReady: () => Promise<void>;
|
|
41
|
+
getDocument: (options?: {
|
|
42
|
+
minimumRevision?: number;
|
|
43
|
+
}) => Promise<OpenEditorDocumentSnapshot>;
|
|
44
|
+
flushDocument: () => Promise<OpenEditorDocumentSnapshot>;
|
|
45
|
+
setDocument: (document: OpenEditorDocument) => Promise<number>;
|
|
46
|
+
command: (command: OpenEditorRuntimeCommand) => Promise<OpenEditorCommandAck>;
|
|
47
|
+
focus: () => Promise<void>;
|
|
48
|
+
blur: () => Promise<void>;
|
|
49
|
+
subscribe: (listener: (event: OpenEditorNativeControllerEvent) => void) => () => void;
|
|
50
|
+
};
|
|
51
|
+
export type OpenEditorNativeBridgeOptions = {
|
|
52
|
+
sessionId: string;
|
|
53
|
+
requestTimeoutMs?: number;
|
|
54
|
+
send: (message: string) => void;
|
|
55
|
+
getEffectHandlers?: () => OpenEditorNativeEffectHandlers | undefined;
|
|
56
|
+
};
|
|
57
|
+
export declare class OpenEditorNativeBridge implements OpenEditorNativeController {
|
|
58
|
+
#private;
|
|
59
|
+
readonly sessionId: string;
|
|
60
|
+
constructor(options: OpenEditorNativeBridgeOptions);
|
|
61
|
+
get ready(): boolean;
|
|
62
|
+
get runtimeReady(): boolean;
|
|
63
|
+
waitUntilReady: () => Promise<void>;
|
|
64
|
+
getDocument: (options?: {
|
|
65
|
+
minimumRevision?: number;
|
|
66
|
+
}) => Promise<OpenEditorDocumentSnapshot>;
|
|
67
|
+
flushDocument: () => Promise<OpenEditorDocumentSnapshot>;
|
|
68
|
+
setDocument: (document: OpenEditorDocument) => Promise<number>;
|
|
69
|
+
command: (command: OpenEditorRuntimeCommand) => Promise<OpenEditorCommandAck>;
|
|
70
|
+
focus: () => Promise<void>;
|
|
71
|
+
blur: () => Promise<void>;
|
|
72
|
+
subscribe: (listener: (event: OpenEditorNativeControllerEvent) => void) => () => void;
|
|
73
|
+
initialize: (document: OpenEditorDocument, editable: boolean) => Promise<OpenEditorCommandAck>;
|
|
74
|
+
markInitialized: () => void;
|
|
75
|
+
receive: (raw: string) => boolean;
|
|
76
|
+
resetRuntime: () => void;
|
|
77
|
+
dispose: (reason?: string) => void;
|
|
78
|
+
}
|
|
79
|
+
export declare const emptyOpenEditorNativeState: OpenEditorRuntimeState;
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { OPENEDITOR_PROTOCOL, OPENEDITOR_PROTOCOL_VERSION, decodeOpenEditorRuntimeMessage, } from "@openeditor/embedded-runtime";
|
|
2
|
+
export class OpenEditorNativeLifecycleCancellationError extends Error {
|
|
3
|
+
code = "OPENEDITOR_NATIVE_LIFECYCLE_CANCELLED";
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "OpenEditorNativeLifecycleCancellationError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export const isOpenEditorNativeLifecycleCancellation = (error) => error instanceof OpenEditorNativeLifecycleCancellationError;
|
|
10
|
+
export class OpenEditorNativeBridge {
|
|
11
|
+
#counter = 0;
|
|
12
|
+
#disposed = false;
|
|
13
|
+
#documentRevision = 0;
|
|
14
|
+
#effectHandlers;
|
|
15
|
+
#listeners = new Set();
|
|
16
|
+
#pending = new Map();
|
|
17
|
+
#ready = false;
|
|
18
|
+
#readyWaiters = new Set();
|
|
19
|
+
#revisionWaiters = new Set();
|
|
20
|
+
#runtimeReady = false;
|
|
21
|
+
#requestTimeoutMs;
|
|
22
|
+
#send;
|
|
23
|
+
sessionId;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.sessionId = options.sessionId;
|
|
26
|
+
this.#send = options.send;
|
|
27
|
+
this.#effectHandlers = options.getEffectHandlers;
|
|
28
|
+
this.#requestTimeoutMs = options.requestTimeoutMs ?? 10_000;
|
|
29
|
+
}
|
|
30
|
+
get ready() { return this.#ready; }
|
|
31
|
+
get runtimeReady() { return this.#runtimeReady; }
|
|
32
|
+
waitUntilReady = () => this.#waitForReady();
|
|
33
|
+
getDocument = async (options) => {
|
|
34
|
+
await this.#waitForReady();
|
|
35
|
+
if ((options?.minimumRevision ?? 0) > this.#documentRevision)
|
|
36
|
+
await this.#waitForRevision(options.minimumRevision);
|
|
37
|
+
const result = await this.#sendCommand({ type: "requestDocument" }, true);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
flushDocument = async () => {
|
|
41
|
+
await this.blur();
|
|
42
|
+
return this.getDocument();
|
|
43
|
+
};
|
|
44
|
+
setDocument = async (document) => (await this.command({ type: "replaceDocument", document })).documentRevision;
|
|
45
|
+
command = async (command) => {
|
|
46
|
+
await this.#waitForReady();
|
|
47
|
+
return this.#sendCommand(command, false);
|
|
48
|
+
};
|
|
49
|
+
focus = async () => { await this.command({ type: "focus" }); };
|
|
50
|
+
blur = async () => { await this.command({ type: "blur" }); };
|
|
51
|
+
subscribe = (listener) => {
|
|
52
|
+
this.#listeners.add(listener);
|
|
53
|
+
return () => { this.#listeners.delete(listener); };
|
|
54
|
+
};
|
|
55
|
+
initialize = (document, editable) => this.#sendCommand({ type: "initialize", document, editable }, false);
|
|
56
|
+
markInitialized = () => {
|
|
57
|
+
if (this.#ready)
|
|
58
|
+
return;
|
|
59
|
+
this.#ready = true;
|
|
60
|
+
for (const waiter of this.#readyWaiters) {
|
|
61
|
+
clearTimeout(waiter.timer);
|
|
62
|
+
waiter.resolve();
|
|
63
|
+
}
|
|
64
|
+
this.#readyWaiters.clear();
|
|
65
|
+
};
|
|
66
|
+
receive = (raw) => {
|
|
67
|
+
if (this.#disposed)
|
|
68
|
+
return false;
|
|
69
|
+
let message;
|
|
70
|
+
try {
|
|
71
|
+
message = decodeOpenEditorRuntimeMessage(raw);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
if (message.sessionId !== this.sessionId)
|
|
77
|
+
return false;
|
|
78
|
+
if (message.type === "ready") {
|
|
79
|
+
this.#runtimeReady = true;
|
|
80
|
+
this.#updateRevision(message.state.documentRevision);
|
|
81
|
+
this.#emit(message);
|
|
82
|
+
}
|
|
83
|
+
else if (message.type === "ack")
|
|
84
|
+
this.#receiveAck(message);
|
|
85
|
+
else if (message.type === "documentSnapshot")
|
|
86
|
+
this.#receiveSnapshot(message);
|
|
87
|
+
else if (message.type === "nativeEffectRequest")
|
|
88
|
+
void this.#receiveEffectRequest(message);
|
|
89
|
+
else if (message.type === "error")
|
|
90
|
+
this.#receiveError(message);
|
|
91
|
+
else {
|
|
92
|
+
if (message.type === "stateChanged")
|
|
93
|
+
this.#updateRevision(message.state.documentRevision);
|
|
94
|
+
if (message.type === "documentChanged")
|
|
95
|
+
this.#updateRevision(message.documentRevision);
|
|
96
|
+
this.#emit(message);
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
};
|
|
100
|
+
resetRuntime = () => {
|
|
101
|
+
const replacesActiveRuntime = this.#runtimeReady || this.#ready;
|
|
102
|
+
this.#runtimeReady = false;
|
|
103
|
+
this.#ready = false;
|
|
104
|
+
this.#documentRevision = 0;
|
|
105
|
+
// onLoadStart is also emitted for the first, expected WebView load. Commands
|
|
106
|
+
// queued before that first load must remain waiting for initialization.
|
|
107
|
+
if (replacesActiveRuntime) {
|
|
108
|
+
this.#rejectPending(new OpenEditorNativeLifecycleCancellationError("OpenEditor runtime reloaded before responding."));
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
dispose = (reason = "OpenEditor native host was disposed.") => {
|
|
112
|
+
this.#disposed = true;
|
|
113
|
+
this.#runtimeReady = false;
|
|
114
|
+
this.#ready = false;
|
|
115
|
+
this.#rejectPending(new OpenEditorNativeLifecycleCancellationError(reason));
|
|
116
|
+
this.#listeners.clear();
|
|
117
|
+
};
|
|
118
|
+
#waitForReady() {
|
|
119
|
+
if (this.#disposed)
|
|
120
|
+
return Promise.reject(new Error("OpenEditor native host was disposed."));
|
|
121
|
+
if (this.#ready)
|
|
122
|
+
return Promise.resolve();
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const waiter = {
|
|
125
|
+
resolve,
|
|
126
|
+
reject,
|
|
127
|
+
timer: setTimeout(() => {
|
|
128
|
+
this.#readyWaiters.delete(waiter);
|
|
129
|
+
reject(new Error("OpenEditor runtime did not become ready."));
|
|
130
|
+
}, this.#requestTimeoutMs),
|
|
131
|
+
};
|
|
132
|
+
this.#readyWaiters.add(waiter);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
#waitForRevision(minimum) {
|
|
136
|
+
if (this.#documentRevision >= minimum)
|
|
137
|
+
return Promise.resolve();
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
const waiter = {
|
|
140
|
+
minimum,
|
|
141
|
+
resolve,
|
|
142
|
+
reject,
|
|
143
|
+
timer: setTimeout(() => {
|
|
144
|
+
this.#revisionWaiters.delete(waiter);
|
|
145
|
+
reject(new Error(`OpenEditor document revision ${minimum} was not reached.`));
|
|
146
|
+
}, this.#requestTimeoutMs),
|
|
147
|
+
};
|
|
148
|
+
this.#revisionWaiters.add(waiter);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
#nextId() { this.#counter += 1; return `rn:${this.#counter}`; }
|
|
152
|
+
#sendCommand(command, needsSnapshot) {
|
|
153
|
+
const id = this.#nextId();
|
|
154
|
+
const envelope = { protocol: OPENEDITOR_PROTOCOL, version: OPENEDITOR_PROTOCOL_VERSION, sessionId: this.sessionId, type: "command", id, command };
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
const timer = setTimeout(() => { this.#pending.delete(id); reject(new Error(`OpenEditor runtime command timed out: ${command.type}`)); }, this.#requestTimeoutMs);
|
|
157
|
+
this.#pending.set(id, { command: command.type, resolve: (value) => resolve(value), reject, timer });
|
|
158
|
+
this.#send(JSON.stringify(envelope));
|
|
159
|
+
if (!needsSnapshot)
|
|
160
|
+
return;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
#receiveAck(message) {
|
|
164
|
+
this.#updateRevision(message.documentRevision);
|
|
165
|
+
const pending = this.#pending.get(message.commandId);
|
|
166
|
+
if (!pending)
|
|
167
|
+
return;
|
|
168
|
+
pending.ack = message;
|
|
169
|
+
if (pending.command === "requestDocument")
|
|
170
|
+
this.#finishSnapshot(message.commandId, pending);
|
|
171
|
+
else
|
|
172
|
+
this.#finish(message.commandId, pending, message);
|
|
173
|
+
}
|
|
174
|
+
#receiveSnapshot(message) {
|
|
175
|
+
this.#updateRevision(message.documentRevision);
|
|
176
|
+
const pending = this.#pending.get(message.requestId);
|
|
177
|
+
if (!pending || pending.command !== "requestDocument")
|
|
178
|
+
return;
|
|
179
|
+
pending.snapshot = message;
|
|
180
|
+
this.#finishSnapshot(message.requestId, pending);
|
|
181
|
+
}
|
|
182
|
+
#finishSnapshot(id, pending) {
|
|
183
|
+
if (!pending.ack || !pending.snapshot)
|
|
184
|
+
return;
|
|
185
|
+
this.#finish(id, pending, { document: pending.snapshot.document, revision: pending.snapshot.documentRevision });
|
|
186
|
+
}
|
|
187
|
+
#finish(id, pending, value) {
|
|
188
|
+
this.#pending.delete(id);
|
|
189
|
+
clearTimeout(pending.timer);
|
|
190
|
+
pending.resolve(value);
|
|
191
|
+
}
|
|
192
|
+
#receiveError(message) {
|
|
193
|
+
if (message.commandId) {
|
|
194
|
+
const pending = this.#pending.get(message.commandId);
|
|
195
|
+
if (pending) {
|
|
196
|
+
this.#pending.delete(message.commandId);
|
|
197
|
+
clearTimeout(pending.timer);
|
|
198
|
+
pending.reject(new Error(`${message.code}: ${message.message}`));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
this.#emit(message);
|
|
202
|
+
}
|
|
203
|
+
async #receiveEffectRequest(message) {
|
|
204
|
+
const handler = this.#effectHandlers?.()?.[message.effect.type];
|
|
205
|
+
let result;
|
|
206
|
+
try {
|
|
207
|
+
result = handler ? await handler(message.effect) : { status: "error", code: "missingHandler", message: `No native handler for ${message.effect.type}.` };
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
result = { status: "error", code: "nativeEffectFailed", message: error instanceof Error ? error.message : "Native effect failed." };
|
|
211
|
+
}
|
|
212
|
+
const response = { protocol: OPENEDITOR_PROTOCOL, version: OPENEDITOR_PROTOCOL_VERSION, sessionId: this.sessionId, type: "nativeEffectResponse", id: this.#nextId(), requestId: message.id, effect: message.effect.type, result };
|
|
213
|
+
this.#send(JSON.stringify(response));
|
|
214
|
+
}
|
|
215
|
+
#updateRevision(revision) {
|
|
216
|
+
this.#documentRevision = Math.max(this.#documentRevision, revision);
|
|
217
|
+
for (const waiter of this.#revisionWaiters)
|
|
218
|
+
if (this.#documentRevision >= waiter.minimum) {
|
|
219
|
+
this.#revisionWaiters.delete(waiter);
|
|
220
|
+
clearTimeout(waiter.timer);
|
|
221
|
+
waiter.resolve();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
#emit(message) { for (const listener of this.#listeners)
|
|
225
|
+
listener(message); }
|
|
226
|
+
#rejectPending(error) {
|
|
227
|
+
for (const pending of this.#pending.values()) {
|
|
228
|
+
clearTimeout(pending.timer);
|
|
229
|
+
pending.reject(error);
|
|
230
|
+
}
|
|
231
|
+
this.#pending.clear();
|
|
232
|
+
for (const waiter of this.#readyWaiters) {
|
|
233
|
+
clearTimeout(waiter.timer);
|
|
234
|
+
waiter.reject(error);
|
|
235
|
+
}
|
|
236
|
+
this.#readyWaiters.clear();
|
|
237
|
+
for (const waiter of this.#revisionWaiters) {
|
|
238
|
+
clearTimeout(waiter.timer);
|
|
239
|
+
waiter.reject(error);
|
|
240
|
+
}
|
|
241
|
+
this.#revisionWaiters.clear();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
export const emptyOpenEditorNativeState = {
|
|
245
|
+
revision: 0,
|
|
246
|
+
documentRevision: 0,
|
|
247
|
+
activeMarks: [],
|
|
248
|
+
activeNodes: ["paragraph"],
|
|
249
|
+
canUndo: false,
|
|
250
|
+
canRedo: false,
|
|
251
|
+
editable: true,
|
|
252
|
+
focused: false,
|
|
253
|
+
selection: { type: "text", anchor: 1, head: 1 },
|
|
254
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,101 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export {
|
|
5
|
-
export { OpenEditorNativeEmojiPicker, type OpenEditorNativeEmojiPickerProps, type OpenEditorNativeEmojiPickerTheme, } from "./emoji-picker.js";
|
|
6
|
-
export { emojiForNativeSkinTone, getOpenEditorNativeEmojiSections, openEditorNativeEmojiSkinToneOptions, type OpenEditorNativeEmoji, type OpenEditorNativeEmojiSection, type OpenEditorNativeEmojiSkinTone, } from "./emoji-data.js";
|
|
7
|
-
export type OpenEditorNativeProps = OpenEditorAuthoringCapabilities & {
|
|
8
|
-
initialDocument?: OpenEditorDocument;
|
|
9
|
-
editable?: boolean;
|
|
10
|
-
placeholder?: string;
|
|
11
|
-
showToolbar?: boolean;
|
|
12
|
-
theme?: Partial<OpenEditorNativeTheme>;
|
|
13
|
-
toolbarPlacement?: NativeRichTextEditorToolbarPlacement;
|
|
14
|
-
onChange?: (document: OpenEditorDocument) => void;
|
|
15
|
-
pageRuntime?: OpenEditorPageRuntime;
|
|
16
|
-
attachmentRuntime?: OpenEditorAttachmentRuntime<any>;
|
|
17
|
-
};
|
|
18
|
-
export type OpenEditorNativeViewerProps = {
|
|
19
|
-
document: OpenEditorDocument;
|
|
20
|
-
theme?: Partial<OpenEditorNativeTheme>;
|
|
21
|
-
pageRuntime?: OpenEditorPageRuntime;
|
|
22
|
-
attachmentRuntime?: OpenEditorAttachmentRuntime<any>;
|
|
23
|
-
renderers?: Partial<Record<string, OpenEditorNativeViewerRenderer>>;
|
|
24
|
-
};
|
|
25
|
-
export type OpenEditorNativeController = {
|
|
26
|
-
getContent: () => OpenEditorDocument;
|
|
27
|
-
setContent: (document: OpenEditorDocument) => void;
|
|
28
|
-
undo: () => void;
|
|
29
|
-
redo: () => void;
|
|
30
|
-
};
|
|
31
|
-
export type OpenEditorNativeToolbarAction = {
|
|
32
|
-
key: string;
|
|
33
|
-
label: string;
|
|
34
|
-
onPress?: (key: string) => void;
|
|
35
|
-
};
|
|
36
|
-
export type OpenEditorNativeToolbarProps = {
|
|
37
|
-
actions?: readonly OpenEditorNativeToolbarAction[];
|
|
38
|
-
onActionPress?: (key: string) => void;
|
|
39
|
-
theme?: Partial<OpenEditorNativeTheme>;
|
|
40
|
-
};
|
|
41
|
-
export type OpenEditorNativeViewerRenderer = (context: {
|
|
42
|
-
node: ProseMirrorNode;
|
|
43
|
-
children: ReactNode;
|
|
44
|
-
theme: Required<OpenEditorNativeTheme>;
|
|
45
|
-
}) => ReactNode;
|
|
46
|
-
export type OpenEditorNativeToolbarTheme = {
|
|
47
|
-
height?: number;
|
|
48
|
-
};
|
|
49
|
-
export type OpenEditorNativeTheme = {
|
|
50
|
-
background: string;
|
|
51
|
-
surface: string;
|
|
52
|
-
surfaceMuted: string;
|
|
53
|
-
blockSurface?: string;
|
|
54
|
-
text: string;
|
|
55
|
-
textSoft?: string;
|
|
56
|
-
muted: string;
|
|
57
|
-
placeholder?: string;
|
|
58
|
-
border: string;
|
|
59
|
-
borderStrong?: string;
|
|
60
|
-
structuralLine?: string;
|
|
61
|
-
accent: string;
|
|
62
|
-
accentText: string;
|
|
63
|
-
accentStrong?: string;
|
|
64
|
-
primary: string;
|
|
65
|
-
primaryText: string;
|
|
66
|
-
codeBackground?: string;
|
|
67
|
-
codeText?: string;
|
|
68
|
-
debugBackground?: string;
|
|
69
|
-
debugText?: string;
|
|
70
|
-
debugHeading?: string;
|
|
71
|
-
debugBorder?: string;
|
|
72
|
-
debugButtonBackground?: string;
|
|
73
|
-
success?: string;
|
|
74
|
-
shadow?: string;
|
|
75
|
-
backdrop: string;
|
|
76
|
-
toolbar?: OpenEditorNativeToolbarTheme;
|
|
77
|
-
};
|
|
78
|
-
export declare const OpenEditorNativeToolbar: ({ actions, onActionPress, theme, }: OpenEditorNativeToolbarProps) => import("react").JSX.Element;
|
|
79
|
-
export type OpenEditorNativePageHeaderProps = {
|
|
80
|
-
page: OpenEditorPageSnapshot;
|
|
81
|
-
runtime: OpenEditorPageRuntime;
|
|
82
|
-
theme?: Partial<OpenEditorNativeTheme>;
|
|
83
|
-
onPageChange?: (page: OpenEditorPageSnapshot) => void;
|
|
84
|
-
};
|
|
85
|
-
/** Canonical native page-title and page-icon editor for opened page surfaces. */
|
|
86
|
-
export declare const OpenEditorNativePageHeader: ({ page, runtime, theme, onPageChange }: OpenEditorNativePageHeaderProps) => import("react").JSX.Element;
|
|
87
|
-
export declare const OpenEditorNative: import("react").ForwardRefExoticComponent<OpenEditorAuthoringCapabilities & {
|
|
88
|
-
initialDocument?: OpenEditorDocument;
|
|
89
|
-
editable?: boolean;
|
|
90
|
-
placeholder?: string;
|
|
91
|
-
showToolbar?: boolean;
|
|
92
|
-
theme?: Partial<OpenEditorNativeTheme>;
|
|
93
|
-
toolbarPlacement?: NativeRichTextEditorToolbarPlacement;
|
|
94
|
-
onChange?: (document: OpenEditorDocument) => void;
|
|
95
|
-
pageRuntime?: OpenEditorPageRuntime;
|
|
96
|
-
attachmentRuntime?: OpenEditorAttachmentRuntime<any>;
|
|
97
|
-
} & import("react").RefAttributes<OpenEditorNativeController>>;
|
|
98
|
-
export declare const OpenEditorNativeViewer: ({ document, theme, renderers, pageRuntime, attachmentRuntime, }: OpenEditorNativeViewerProps) => import("react").JSX.Element;
|
|
99
|
-
export { createNativeEditorDocument, fromNativeEditorDocument, sanitizeNativeEditorDocument, toNativeEditorDocument, } from "./document.js";
|
|
100
|
-
export { createOpenEditorNativeToolbarItems, defaultDocumentForToolbarAction, nativeToolbarParityCoverage, openEditorNativeToolbarActionKeys, openEditorNativeToolbarItems, } from "./toolbar.js";
|
|
101
|
-
export type { OpenEditorNativeToolbarActionKey } from "./toolbar.js";
|
|
1
|
+
export { OpenEditorNative, type OpenEditorNativeContentInsets, type OpenEditorNativeProps, type OpenEditorNativeTheme, } from "./native-editor.js";
|
|
2
|
+
export { type OpenEditorNativeController, type OpenEditorNativeControllerEvent, type OpenEditorNativeEffectHandlers, } from "./controller.js";
|
|
3
|
+
export { OpenEditorNativeToolbar, defaultOpenEditorNativeToolbarItems, openEditorNativeBlockPickerItems, type OpenEditorNativeToolbarItem, type OpenEditorNativeToolbarProps, } from "./toolbar-host.js";
|
|
4
|
+
export type { OpenEditorRuntimeCommand, OpenEditorRuntimeState } from "@openeditor/embedded-runtime";
|