@apollohg/react-native-prose-editor 0.4.0 → 0.4.2
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 +21 -2
- package/android/build.gradle +23 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +502 -39
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +56 -28
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +6 -0
- package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +57 -27
- package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +147 -78
- package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +249 -71
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +7 -6
- package/dist/NativeEditorBridge.d.ts +37 -1
- package/dist/NativeEditorBridge.js +192 -97
- package/dist/NativeRichTextEditor.d.ts +3 -2
- package/dist/NativeRichTextEditor.js +164 -56
- package/dist/YjsCollaboration.d.ts +2 -0
- package/dist/YjsCollaboration.js +142 -20
- package/dist/schemas.d.ts +2 -0
- package/dist/schemas.js +63 -0
- package/ios/EditorCore.xcframework/Info.plist +5 -5
- package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/ios/EditorLayoutManager.swift +3 -3
- package/ios/Generated_editor_core.swift +41 -0
- package/ios/NativeEditorExpoView.swift +43 -11
- package/ios/NativeEditorModule.swift +6 -0
- package/ios/PositionBridge.swift +310 -75
- package/ios/RenderBridge.swift +362 -27
- package/ios/RichTextEditorView.swift +1983 -187
- package/ios/editor_coreFFI/editor_coreFFI.h +33 -0
- package/package.json +11 -2
- package/rust/android/arm64-v8a/libeditor_core.so +0 -0
- package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
- package/rust/android/x86_64/libeditor_core.so +0 -0
- package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +63 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SchemaDefinition } from './schemas';
|
|
1
2
|
export interface NativeEditorModule {
|
|
2
3
|
editorCreate(configJson: string): number;
|
|
3
4
|
editorDestroy(editorId: number): void;
|
|
@@ -17,6 +18,7 @@ export interface NativeEditorModule {
|
|
|
17
18
|
editorGetHtml(editorId: number): string;
|
|
18
19
|
editorSetJson(editorId: number, json: string): string;
|
|
19
20
|
editorGetJson(editorId: number): string;
|
|
21
|
+
editorGetContentSnapshot(editorId: number): string;
|
|
20
22
|
editorReplaceHtml(editorId: number, html: string): string;
|
|
21
23
|
editorReplaceJson(editorId: number, json: string): string;
|
|
22
24
|
editorInsertText(editorId: number, pos: number, text: string): string;
|
|
@@ -33,6 +35,7 @@ export interface NativeEditorModule {
|
|
|
33
35
|
editorToggleHeading(editorId: number, level: number): string;
|
|
34
36
|
editorSetSelection(editorId: number, anchor: number, head: number): void;
|
|
35
37
|
editorGetSelection(editorId: number): string;
|
|
38
|
+
editorGetSelectionState(editorId: number): string;
|
|
36
39
|
editorGetCurrentState(editorId: number): string;
|
|
37
40
|
editorInsertTextScalar(editorId: number, scalarPos: number, text: string): string;
|
|
38
41
|
editorDeleteScalarRange(editorId: number, scalarFrom: number, scalarTo: number): string;
|
|
@@ -92,6 +95,11 @@ export interface RenderElement {
|
|
|
92
95
|
attrs?: Record<string, unknown>;
|
|
93
96
|
listContext?: ListContext;
|
|
94
97
|
}
|
|
98
|
+
interface RenderBlocksPatch {
|
|
99
|
+
startIndex: number;
|
|
100
|
+
deleteCount: number;
|
|
101
|
+
renderBlocks: RenderElement[][];
|
|
102
|
+
}
|
|
95
103
|
export interface ActiveState {
|
|
96
104
|
marks: Record<string, boolean>;
|
|
97
105
|
markAttrs: Record<string, Record<string, unknown>>;
|
|
@@ -106,9 +114,16 @@ export interface HistoryState {
|
|
|
106
114
|
}
|
|
107
115
|
export interface EditorUpdate {
|
|
108
116
|
renderElements: RenderElement[];
|
|
117
|
+
renderBlocks?: RenderElement[][];
|
|
118
|
+
renderPatch?: RenderBlocksPatch;
|
|
109
119
|
selection: Selection;
|
|
110
120
|
activeState: ActiveState;
|
|
111
121
|
historyState: HistoryState;
|
|
122
|
+
documentVersion?: number;
|
|
123
|
+
}
|
|
124
|
+
export interface ContentSnapshot {
|
|
125
|
+
html: string;
|
|
126
|
+
json: DocumentJSON;
|
|
112
127
|
}
|
|
113
128
|
export interface DocumentJSON {
|
|
114
129
|
[key: string]: unknown;
|
|
@@ -127,7 +142,7 @@ export interface CollaborationResult {
|
|
|
127
142
|
}
|
|
128
143
|
export type EncodedCollaborationStateInput = Uint8Array | readonly number[] | string;
|
|
129
144
|
export declare function normalizeActiveState(raw: unknown): ActiveState;
|
|
130
|
-
export declare function parseEditorUpdateJson(json: string): EditorUpdate | null;
|
|
145
|
+
export declare function parseEditorUpdateJson(json: string, previousRenderBlocks?: RenderElement[][]): EditorUpdate | null;
|
|
131
146
|
export declare function encodeCollaborationStateBase64(encodedState: EncodedCollaborationStateInput): string;
|
|
132
147
|
export declare function decodeCollaborationStateBase64(base64: string): Uint8Array;
|
|
133
148
|
export declare function parseCollaborationResultJson(json: string): CollaborationResult;
|
|
@@ -135,8 +150,13 @@ export declare function parseCollaborationResultJson(json: string): Collaboratio
|
|
|
135
150
|
export declare function _resetNativeModuleCache(): void;
|
|
136
151
|
export declare class NativeEditorBridge {
|
|
137
152
|
private _editorId;
|
|
153
|
+
private _schema?;
|
|
138
154
|
private _destroyed;
|
|
139
155
|
private _lastSelection;
|
|
156
|
+
private _documentVersion;
|
|
157
|
+
private _cachedHtml;
|
|
158
|
+
private _cachedJsonString;
|
|
159
|
+
private _renderBlocksCache;
|
|
140
160
|
private constructor();
|
|
141
161
|
/** Create a new editor instance backed by the Rust engine. */
|
|
142
162
|
static create(config?: {
|
|
@@ -156,8 +176,14 @@ export declare class NativeEditorBridge {
|
|
|
156
176
|
getHtml(): string;
|
|
157
177
|
/** Set content from ProseMirror JSON. Returns render elements. */
|
|
158
178
|
setJson(doc: DocumentJSON): RenderElement[];
|
|
179
|
+
/** Set content from a serialized ProseMirror JSON string. Returns render elements. */
|
|
180
|
+
setJsonString(jsonString: string): RenderElement[];
|
|
181
|
+
/** Get content as raw ProseMirror JSON string. */
|
|
182
|
+
getJsonString(): string;
|
|
159
183
|
/** Get content as ProseMirror JSON. */
|
|
160
184
|
getJson(): DocumentJSON;
|
|
185
|
+
/** Get both HTML and JSON content in one native roundtrip. */
|
|
186
|
+
getContentSnapshot(): ContentSnapshot;
|
|
161
187
|
/** Insert text at a document position. Returns the full update. */
|
|
162
188
|
insertText(pos: number, text: string): EditorUpdate | null;
|
|
163
189
|
/** Delete a range [from, to). Returns the full update. */
|
|
@@ -184,6 +210,8 @@ export declare class NativeEditorBridge {
|
|
|
184
210
|
updateSelectionFromNative(anchor: number, head: number): void;
|
|
185
211
|
/** Get the current full state from Rust (render elements, selection, etc.). */
|
|
186
212
|
getCurrentState(): EditorUpdate | null;
|
|
213
|
+
/** Get the current selection-related state without render elements. */
|
|
214
|
+
getSelectionState(): EditorUpdate | null;
|
|
187
215
|
/** Split the block at a position (Enter key). */
|
|
188
216
|
splitBlock(pos: number): EditorUpdate | null;
|
|
189
217
|
/** Insert HTML content at the current selection. */
|
|
@@ -196,6 +224,8 @@ export declare class NativeEditorBridge {
|
|
|
196
224
|
replaceHtml(html: string): EditorUpdate | null;
|
|
197
225
|
/** Replace entire document with JSON via transaction (preserves undo history). */
|
|
198
226
|
replaceJson(doc: DocumentJSON): EditorUpdate | null;
|
|
227
|
+
/** Replace entire document with a serialized JSON transaction. */
|
|
228
|
+
replaceJsonString(jsonString: string): EditorUpdate | null;
|
|
199
229
|
/** Undo the last operation. Returns update or null if nothing to undo. */
|
|
200
230
|
undo(): EditorUpdate | null;
|
|
201
231
|
/** Redo the last undone operation. Returns update or null if nothing to redo. */
|
|
@@ -214,6 +244,10 @@ export declare class NativeEditorBridge {
|
|
|
214
244
|
outdentListItem(): EditorUpdate | null;
|
|
215
245
|
/** Insert a void node (e.g. 'horizontalRule') at the current selection. */
|
|
216
246
|
insertNode(nodeType: string): EditorUpdate | null;
|
|
247
|
+
parseUpdateJson(json: string): EditorUpdate | null;
|
|
248
|
+
private noteUpdate;
|
|
249
|
+
private parseAndNoteUpdate;
|
|
250
|
+
private invalidateContentCaches;
|
|
217
251
|
private assertNotDestroyed;
|
|
218
252
|
private currentScalarSelection;
|
|
219
253
|
}
|
|
@@ -224,6 +258,7 @@ export declare class NativeCollaborationBridge {
|
|
|
224
258
|
static create(config?: {
|
|
225
259
|
clientId?: number;
|
|
226
260
|
fragmentName?: string;
|
|
261
|
+
schema?: SchemaDefinition;
|
|
227
262
|
initialDocumentJson?: DocumentJSON;
|
|
228
263
|
initialEncodedState?: EncodedCollaborationStateInput;
|
|
229
264
|
localAwareness?: Record<string, unknown>;
|
|
@@ -244,3 +279,4 @@ export declare class NativeCollaborationBridge {
|
|
|
244
279
|
clearLocalAwareness(): CollaborationResult;
|
|
245
280
|
private assertNotDestroyed;
|
|
246
281
|
}
|
|
282
|
+
export {};
|
|
@@ -8,6 +8,7 @@ exports.decodeCollaborationStateBase64 = decodeCollaborationStateBase64;
|
|
|
8
8
|
exports.parseCollaborationResultJson = parseCollaborationResultJson;
|
|
9
9
|
exports._resetNativeModuleCache = _resetNativeModuleCache;
|
|
10
10
|
const expo_modules_core_1 = require("expo-modules-core");
|
|
11
|
+
const schemas_1 = require("./schemas");
|
|
11
12
|
const ERR_DESTROYED = 'NativeEditorBridge: editor has been destroyed';
|
|
12
13
|
const ERR_NATIVE_RESPONSE = 'NativeEditorBridge: invalid JSON response from native module';
|
|
13
14
|
const ERR_INVALID_ENCODED_STATE = 'NativeEditorBridge: invalid encoded collaboration state';
|
|
@@ -46,7 +47,7 @@ function parseRenderElements(json) {
|
|
|
46
47
|
throw new Error(ERR_NATIVE_RESPONSE);
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
|
-
function parseEditorUpdateJson(json) {
|
|
50
|
+
function parseEditorUpdateJson(json, previousRenderBlocks) {
|
|
50
51
|
if (!json || json === '')
|
|
51
52
|
return null;
|
|
52
53
|
try {
|
|
@@ -54,14 +55,72 @@ function parseEditorUpdateJson(json) {
|
|
|
54
55
|
if ('error' in parsed) {
|
|
55
56
|
throw new Error(`NativeEditorBridge: ${parsed.error}`);
|
|
56
57
|
}
|
|
58
|
+
const renderBlocks = Array.isArray(parsed.renderBlocks)
|
|
59
|
+
? parsed.renderBlocks
|
|
60
|
+
: applyRenderBlocksPatch(previousRenderBlocks, parsed.renderPatch != null && typeof parsed.renderPatch === 'object'
|
|
61
|
+
? parsed.renderPatch
|
|
62
|
+
: undefined);
|
|
63
|
+
const renderPatch = parsed.renderPatch != null && typeof parsed.renderPatch === 'object'
|
|
64
|
+
? parsed.renderPatch
|
|
65
|
+
: undefined;
|
|
57
66
|
return {
|
|
58
|
-
renderElements: (parsed.renderElements
|
|
67
|
+
renderElements: Array.isArray(parsed.renderElements)
|
|
68
|
+
? parsed.renderElements
|
|
69
|
+
: flattenRenderBlocks(renderBlocks),
|
|
70
|
+
renderBlocks,
|
|
71
|
+
renderPatch,
|
|
59
72
|
selection: (parsed.selection ?? { type: 'text', anchor: 0, head: 0 }),
|
|
60
73
|
activeState: normalizeActiveState(parsed.activeState),
|
|
61
74
|
historyState: (parsed.historyState ?? {
|
|
62
75
|
canUndo: false,
|
|
63
76
|
canRedo: false,
|
|
64
77
|
}),
|
|
78
|
+
documentVersion: typeof parsed.documentVersion === 'number' ? parsed.documentVersion : undefined,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
if (e instanceof Error && e.message.startsWith('NativeEditorBridge:')) {
|
|
83
|
+
throw e;
|
|
84
|
+
}
|
|
85
|
+
throw new Error(ERR_NATIVE_RESPONSE);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function flattenRenderBlocks(renderBlocks) {
|
|
89
|
+
if (!renderBlocks || renderBlocks.length === 0) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
return renderBlocks.flat();
|
|
93
|
+
}
|
|
94
|
+
function applyRenderBlocksPatch(previousRenderBlocks, renderPatch) {
|
|
95
|
+
if (!previousRenderBlocks || !renderPatch) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
const { startIndex, deleteCount, renderBlocks } = renderPatch;
|
|
99
|
+
if (!Number.isInteger(startIndex) ||
|
|
100
|
+
!Number.isInteger(deleteCount) ||
|
|
101
|
+
startIndex < 0 ||
|
|
102
|
+
deleteCount < 0 ||
|
|
103
|
+
startIndex > previousRenderBlocks.length ||
|
|
104
|
+
startIndex + deleteCount > previousRenderBlocks.length) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
return [
|
|
108
|
+
...previousRenderBlocks.slice(0, startIndex),
|
|
109
|
+
...renderBlocks,
|
|
110
|
+
...previousRenderBlocks.slice(startIndex + deleteCount),
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
function parseContentSnapshotJson(json) {
|
|
114
|
+
try {
|
|
115
|
+
const parsed = JSON.parse(json);
|
|
116
|
+
if ('error' in parsed) {
|
|
117
|
+
throw new Error(`NativeEditorBridge: ${parsed.error}`);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
html: typeof parsed.html === 'string' ? parsed.html : '',
|
|
121
|
+
json: parsed.json != null && typeof parsed.json === 'object'
|
|
122
|
+
? parsed.json
|
|
123
|
+
: {},
|
|
65
124
|
};
|
|
66
125
|
}
|
|
67
126
|
catch (e) {
|
|
@@ -175,6 +234,16 @@ function encodeCollaborationStateBase64(encodedState) {
|
|
|
175
234
|
function decodeCollaborationStateBase64(base64) {
|
|
176
235
|
return base64ToBytes(base64);
|
|
177
236
|
}
|
|
237
|
+
function normalizeDocumentJsonString(jsonString, schema) {
|
|
238
|
+
try {
|
|
239
|
+
const parsed = JSON.parse(jsonString);
|
|
240
|
+
const normalized = (0, schemas_1.normalizeDocumentJson)(parsed, schema);
|
|
241
|
+
return normalized === parsed ? jsonString : JSON.stringify(normalized);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return jsonString;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
178
247
|
function parseCollaborationResultJson(json) {
|
|
179
248
|
if (!json || json === '') {
|
|
180
249
|
return {
|
|
@@ -217,14 +286,20 @@ function _resetNativeModuleCache() {
|
|
|
217
286
|
_nativeModule = null;
|
|
218
287
|
}
|
|
219
288
|
class NativeEditorBridge {
|
|
220
|
-
constructor(editorId) {
|
|
289
|
+
constructor(editorId, schema) {
|
|
221
290
|
this._destroyed = false;
|
|
222
291
|
this._lastSelection = { type: 'text', anchor: 0, head: 0 };
|
|
292
|
+
this._documentVersion = 0;
|
|
293
|
+
this._cachedHtml = null;
|
|
294
|
+
this._cachedJsonString = null;
|
|
295
|
+
this._renderBlocksCache = null;
|
|
223
296
|
this._editorId = editorId;
|
|
297
|
+
this._schema = schema;
|
|
224
298
|
}
|
|
225
299
|
/** Create a new editor instance backed by the Rust engine. */
|
|
226
300
|
static create(config) {
|
|
227
301
|
const configObj = {};
|
|
302
|
+
let parsedSchema;
|
|
228
303
|
if (config?.maxLength != null)
|
|
229
304
|
configObj.maxLength = config.maxLength;
|
|
230
305
|
if (config?.allowBase64Images != null) {
|
|
@@ -232,14 +307,15 @@ class NativeEditorBridge {
|
|
|
232
307
|
}
|
|
233
308
|
if (config?.schemaJson != null) {
|
|
234
309
|
try {
|
|
235
|
-
|
|
310
|
+
parsedSchema = JSON.parse(config.schemaJson);
|
|
311
|
+
configObj.schema = parsedSchema;
|
|
236
312
|
}
|
|
237
313
|
catch {
|
|
238
314
|
// Fall back to the default schema when the provided JSON is invalid.
|
|
239
315
|
}
|
|
240
316
|
}
|
|
241
317
|
const id = getNativeModule().editorCreate(JSON.stringify(configObj));
|
|
242
|
-
return new NativeEditorBridge(id);
|
|
318
|
+
return new NativeEditorBridge(id, parsedSchema);
|
|
243
319
|
}
|
|
244
320
|
/** The underlying native editor ID. */
|
|
245
321
|
get editorId() {
|
|
@@ -254,57 +330,89 @@ class NativeEditorBridge {
|
|
|
254
330
|
if (this._destroyed)
|
|
255
331
|
return;
|
|
256
332
|
this._destroyed = true;
|
|
333
|
+
this._renderBlocksCache = null;
|
|
257
334
|
getNativeModule().editorDestroy(this._editorId);
|
|
258
335
|
}
|
|
259
336
|
/** Set content from HTML. Returns render elements for display. */
|
|
260
337
|
setHtml(html) {
|
|
261
338
|
this.assertNotDestroyed();
|
|
339
|
+
this.invalidateContentCaches();
|
|
340
|
+
this._renderBlocksCache = null;
|
|
262
341
|
const json = getNativeModule().editorSetHtml(this._editorId, html);
|
|
263
342
|
return parseRenderElements(json);
|
|
264
343
|
}
|
|
265
344
|
/** Get content as HTML. */
|
|
266
345
|
getHtml() {
|
|
267
346
|
this.assertNotDestroyed();
|
|
268
|
-
|
|
347
|
+
if (this._cachedHtml?.version === this._documentVersion) {
|
|
348
|
+
return this._cachedHtml.value;
|
|
349
|
+
}
|
|
350
|
+
const html = getNativeModule().editorGetHtml(this._editorId);
|
|
351
|
+
this._cachedHtml = { version: this._documentVersion, value: html };
|
|
352
|
+
return html;
|
|
269
353
|
}
|
|
270
354
|
/** Set content from ProseMirror JSON. Returns render elements. */
|
|
271
355
|
setJson(doc) {
|
|
356
|
+
return this.setJsonString(JSON.stringify((0, schemas_1.normalizeDocumentJson)(doc, this._schema)));
|
|
357
|
+
}
|
|
358
|
+
/** Set content from a serialized ProseMirror JSON string. Returns render elements. */
|
|
359
|
+
setJsonString(jsonString) {
|
|
272
360
|
this.assertNotDestroyed();
|
|
273
|
-
|
|
361
|
+
this.invalidateContentCaches();
|
|
362
|
+
this._renderBlocksCache = null;
|
|
363
|
+
const normalizedJsonString = normalizeDocumentJsonString(jsonString, this._schema);
|
|
364
|
+
const json = getNativeModule().editorSetJson(this._editorId, normalizedJsonString);
|
|
274
365
|
return parseRenderElements(json);
|
|
275
366
|
}
|
|
367
|
+
/** Get content as raw ProseMirror JSON string. */
|
|
368
|
+
getJsonString() {
|
|
369
|
+
this.assertNotDestroyed();
|
|
370
|
+
if (this._cachedJsonString?.version === this._documentVersion) {
|
|
371
|
+
return this._cachedJsonString.value;
|
|
372
|
+
}
|
|
373
|
+
const json = getNativeModule().editorGetJson(this._editorId);
|
|
374
|
+
this._cachedJsonString = { version: this._documentVersion, value: json };
|
|
375
|
+
return json;
|
|
376
|
+
}
|
|
276
377
|
/** Get content as ProseMirror JSON. */
|
|
277
378
|
getJson() {
|
|
379
|
+
return parseDocumentJSON(this.getJsonString());
|
|
380
|
+
}
|
|
381
|
+
/** Get both HTML and JSON content in one native roundtrip. */
|
|
382
|
+
getContentSnapshot() {
|
|
278
383
|
this.assertNotDestroyed();
|
|
279
|
-
|
|
280
|
-
|
|
384
|
+
if (this._cachedHtml?.version === this._documentVersion &&
|
|
385
|
+
this._cachedJsonString?.version === this._documentVersion) {
|
|
386
|
+
return {
|
|
387
|
+
html: this._cachedHtml.value,
|
|
388
|
+
json: parseDocumentJSON(this._cachedJsonString.value),
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
const snapshot = parseContentSnapshotJson(getNativeModule().editorGetContentSnapshot(this._editorId));
|
|
392
|
+
this._cachedHtml = { version: this._documentVersion, value: snapshot.html };
|
|
393
|
+
this._cachedJsonString = {
|
|
394
|
+
version: this._documentVersion,
|
|
395
|
+
value: JSON.stringify(snapshot.json),
|
|
396
|
+
};
|
|
397
|
+
return snapshot;
|
|
281
398
|
}
|
|
282
399
|
/** Insert text at a document position. Returns the full update. */
|
|
283
400
|
insertText(pos, text) {
|
|
284
401
|
this.assertNotDestroyed();
|
|
285
402
|
const json = getNativeModule().editorInsertText(this._editorId, pos, text);
|
|
286
|
-
|
|
287
|
-
if (update)
|
|
288
|
-
this._lastSelection = update.selection;
|
|
289
|
-
return update;
|
|
403
|
+
return this.parseAndNoteUpdate(json);
|
|
290
404
|
}
|
|
291
405
|
/** Delete a range [from, to). Returns the full update. */
|
|
292
406
|
deleteRange(from, to) {
|
|
293
407
|
this.assertNotDestroyed();
|
|
294
408
|
const json = getNativeModule().editorDeleteRange(this._editorId, from, to);
|
|
295
|
-
|
|
296
|
-
if (update)
|
|
297
|
-
this._lastSelection = update.selection;
|
|
298
|
-
return update;
|
|
409
|
+
return this.parseAndNoteUpdate(json);
|
|
299
410
|
}
|
|
300
411
|
/** Replace the current selection with text atomically. */
|
|
301
412
|
replaceSelectionText(text) {
|
|
302
413
|
this.assertNotDestroyed();
|
|
303
414
|
const json = getNativeModule().editorReplaceSelectionText(this._editorId, text);
|
|
304
|
-
|
|
305
|
-
if (update)
|
|
306
|
-
this._lastSelection = update.selection;
|
|
307
|
-
return update;
|
|
415
|
+
return this.parseAndNoteUpdate(json);
|
|
308
416
|
}
|
|
309
417
|
/** Toggle a mark (bold, italic, etc.) on the current selection. */
|
|
310
418
|
toggleMark(markType) {
|
|
@@ -313,10 +421,7 @@ class NativeEditorBridge {
|
|
|
313
421
|
const json = scalarSelection
|
|
314
422
|
? getNativeModule().editorToggleMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType)
|
|
315
423
|
: getNativeModule().editorToggleMark(this._editorId, markType);
|
|
316
|
-
|
|
317
|
-
if (update)
|
|
318
|
-
this._lastSelection = update.selection;
|
|
319
|
-
return update;
|
|
424
|
+
return this.parseAndNoteUpdate(json);
|
|
320
425
|
}
|
|
321
426
|
/** Set a mark with attrs on the current selection. */
|
|
322
427
|
setMark(markType, attrs) {
|
|
@@ -326,10 +431,7 @@ class NativeEditorBridge {
|
|
|
326
431
|
const json = scalarSelection
|
|
327
432
|
? getNativeModule().editorSetMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType, attrsJson)
|
|
328
433
|
: getNativeModule().editorSetMark(this._editorId, markType, attrsJson);
|
|
329
|
-
|
|
330
|
-
if (update)
|
|
331
|
-
this._lastSelection = update.selection;
|
|
332
|
-
return update;
|
|
434
|
+
return this.parseAndNoteUpdate(json);
|
|
333
435
|
}
|
|
334
436
|
/** Remove a mark from the current selection. */
|
|
335
437
|
unsetMark(markType) {
|
|
@@ -338,10 +440,7 @@ class NativeEditorBridge {
|
|
|
338
440
|
const json = scalarSelection
|
|
339
441
|
? getNativeModule().editorUnsetMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType)
|
|
340
442
|
: getNativeModule().editorUnsetMark(this._editorId, markType);
|
|
341
|
-
|
|
342
|
-
if (update)
|
|
343
|
-
this._lastSelection = update.selection;
|
|
344
|
-
return update;
|
|
443
|
+
return this.parseAndNoteUpdate(json);
|
|
345
444
|
}
|
|
346
445
|
/** Toggle blockquote wrapping for the current block selection. */
|
|
347
446
|
toggleBlockquote() {
|
|
@@ -350,10 +449,7 @@ class NativeEditorBridge {
|
|
|
350
449
|
const json = scalarSelection
|
|
351
450
|
? getNativeModule().editorToggleBlockquoteAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
|
|
352
451
|
: getNativeModule().editorToggleBlockquote(this._editorId);
|
|
353
|
-
|
|
354
|
-
if (update)
|
|
355
|
-
this._lastSelection = update.selection;
|
|
356
|
-
return update;
|
|
452
|
+
return this.parseAndNoteUpdate(json);
|
|
357
453
|
}
|
|
358
454
|
/** Toggle a heading level on the current block selection. */
|
|
359
455
|
toggleHeading(level) {
|
|
@@ -365,10 +461,7 @@ class NativeEditorBridge {
|
|
|
365
461
|
const json = scalarSelection
|
|
366
462
|
? getNativeModule().editorToggleHeadingAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, level)
|
|
367
463
|
: getNativeModule().editorToggleHeading(this._editorId, level);
|
|
368
|
-
|
|
369
|
-
if (update)
|
|
370
|
-
this._lastSelection = update.selection;
|
|
371
|
-
return update;
|
|
464
|
+
return this.parseAndNoteUpdate(json);
|
|
372
465
|
}
|
|
373
466
|
/** Set the document selection by anchor and head positions. */
|
|
374
467
|
setSelection(anchor, head) {
|
|
@@ -402,82 +495,66 @@ class NativeEditorBridge {
|
|
|
402
495
|
getCurrentState() {
|
|
403
496
|
this.assertNotDestroyed();
|
|
404
497
|
const json = getNativeModule().editorGetCurrentState(this._editorId);
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
498
|
+
return this.parseAndNoteUpdate(json);
|
|
499
|
+
}
|
|
500
|
+
/** Get the current selection-related state without render elements. */
|
|
501
|
+
getSelectionState() {
|
|
502
|
+
this.assertNotDestroyed();
|
|
503
|
+
const json = getNativeModule().editorGetSelectionState(this._editorId);
|
|
504
|
+
return this.parseAndNoteUpdate(json);
|
|
409
505
|
}
|
|
410
506
|
/** Split the block at a position (Enter key). */
|
|
411
507
|
splitBlock(pos) {
|
|
412
508
|
this.assertNotDestroyed();
|
|
413
509
|
const json = getNativeModule().editorSplitBlock(this._editorId, pos);
|
|
414
|
-
|
|
415
|
-
if (update)
|
|
416
|
-
this._lastSelection = update.selection;
|
|
417
|
-
return update;
|
|
510
|
+
return this.parseAndNoteUpdate(json);
|
|
418
511
|
}
|
|
419
512
|
/** Insert HTML content at the current selection. */
|
|
420
513
|
insertContentHtml(html) {
|
|
421
514
|
this.assertNotDestroyed();
|
|
422
515
|
const json = getNativeModule().editorInsertContentHtml(this._editorId, html);
|
|
423
|
-
|
|
424
|
-
if (update)
|
|
425
|
-
this._lastSelection = update.selection;
|
|
426
|
-
return update;
|
|
516
|
+
return this.parseAndNoteUpdate(json);
|
|
427
517
|
}
|
|
428
518
|
/** Insert JSON content at the current selection. */
|
|
429
519
|
insertContentJson(doc) {
|
|
430
520
|
this.assertNotDestroyed();
|
|
431
521
|
const json = getNativeModule().editorInsertContentJson(this._editorId, JSON.stringify(doc));
|
|
432
|
-
|
|
433
|
-
if (update)
|
|
434
|
-
this._lastSelection = update.selection;
|
|
435
|
-
return update;
|
|
522
|
+
return this.parseAndNoteUpdate(json);
|
|
436
523
|
}
|
|
437
524
|
/** Insert JSON content at an explicit scalar selection. */
|
|
438
525
|
insertContentJsonAtSelectionScalar(scalarAnchor, scalarHead, doc) {
|
|
439
526
|
this.assertNotDestroyed();
|
|
440
527
|
const json = getNativeModule().editorInsertContentJsonAtSelectionScalar(this._editorId, scalarAnchor, scalarHead, JSON.stringify(doc));
|
|
441
|
-
|
|
442
|
-
if (update)
|
|
443
|
-
this._lastSelection = update.selection;
|
|
444
|
-
return update;
|
|
528
|
+
return this.parseAndNoteUpdate(json);
|
|
445
529
|
}
|
|
446
530
|
/** Replace entire document with HTML via transaction (preserves undo history). */
|
|
447
531
|
replaceHtml(html) {
|
|
448
532
|
this.assertNotDestroyed();
|
|
449
533
|
const json = getNativeModule().editorReplaceHtml(this._editorId, html);
|
|
450
|
-
|
|
451
|
-
if (update)
|
|
452
|
-
this._lastSelection = update.selection;
|
|
453
|
-
return update;
|
|
534
|
+
return this.parseAndNoteUpdate(json);
|
|
454
535
|
}
|
|
455
536
|
/** Replace entire document with JSON via transaction (preserves undo history). */
|
|
456
537
|
replaceJson(doc) {
|
|
538
|
+
return this.replaceJsonString(JSON.stringify((0, schemas_1.normalizeDocumentJson)(doc, this._schema)));
|
|
539
|
+
}
|
|
540
|
+
/** Replace entire document with a serialized JSON transaction. */
|
|
541
|
+
replaceJsonString(jsonString) {
|
|
457
542
|
this.assertNotDestroyed();
|
|
458
|
-
const
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
this._lastSelection = update.selection;
|
|
462
|
-
return update;
|
|
543
|
+
const normalizedJsonString = normalizeDocumentJsonString(jsonString, this._schema);
|
|
544
|
+
const json = getNativeModule().editorReplaceJson(this._editorId, normalizedJsonString);
|
|
545
|
+
return this.parseAndNoteUpdate(json);
|
|
463
546
|
}
|
|
464
547
|
/** Undo the last operation. Returns update or null if nothing to undo. */
|
|
465
548
|
undo() {
|
|
466
549
|
this.assertNotDestroyed();
|
|
467
550
|
const json = getNativeModule().editorUndo(this._editorId);
|
|
468
|
-
|
|
469
|
-
if (update)
|
|
470
|
-
this._lastSelection = update.selection;
|
|
471
|
-
return update;
|
|
551
|
+
return this.parseAndNoteUpdate(json);
|
|
472
552
|
}
|
|
473
553
|
/** Redo the last undone operation. Returns update or null if nothing to redo. */
|
|
474
554
|
redo() {
|
|
475
555
|
this.assertNotDestroyed();
|
|
476
556
|
const json = getNativeModule().editorRedo(this._editorId);
|
|
477
|
-
|
|
478
|
-
if (update)
|
|
479
|
-
this._lastSelection = update.selection;
|
|
480
|
-
return update;
|
|
557
|
+
return this.parseAndNoteUpdate(json);
|
|
481
558
|
}
|
|
482
559
|
/** Check if undo is available. */
|
|
483
560
|
canUndo() {
|
|
@@ -501,10 +578,7 @@ class NativeEditorBridge {
|
|
|
501
578
|
: scalarSelection
|
|
502
579
|
? getNativeModule().editorWrapInListAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, listType)
|
|
503
580
|
: getNativeModule().editorWrapInList(this._editorId, listType);
|
|
504
|
-
|
|
505
|
-
if (update)
|
|
506
|
-
this._lastSelection = update.selection;
|
|
507
|
-
return update;
|
|
581
|
+
return this.parseAndNoteUpdate(json);
|
|
508
582
|
}
|
|
509
583
|
/** Unwrap the current list item back to a paragraph. */
|
|
510
584
|
unwrapFromList() {
|
|
@@ -513,10 +587,7 @@ class NativeEditorBridge {
|
|
|
513
587
|
const json = scalarSelection
|
|
514
588
|
? getNativeModule().editorUnwrapFromListAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
|
|
515
589
|
: getNativeModule().editorUnwrapFromList(this._editorId);
|
|
516
|
-
|
|
517
|
-
if (update)
|
|
518
|
-
this._lastSelection = update.selection;
|
|
519
|
-
return update;
|
|
590
|
+
return this.parseAndNoteUpdate(json);
|
|
520
591
|
}
|
|
521
592
|
/** Indent the current list item into a nested list. */
|
|
522
593
|
indentListItem() {
|
|
@@ -525,10 +596,7 @@ class NativeEditorBridge {
|
|
|
525
596
|
const json = scalarSelection
|
|
526
597
|
? getNativeModule().editorIndentListItemAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
|
|
527
598
|
: getNativeModule().editorIndentListItem(this._editorId);
|
|
528
|
-
|
|
529
|
-
if (update)
|
|
530
|
-
this._lastSelection = update.selection;
|
|
531
|
-
return update;
|
|
599
|
+
return this.parseAndNoteUpdate(json);
|
|
532
600
|
}
|
|
533
601
|
/** Outdent the current list item to the parent list level. */
|
|
534
602
|
outdentListItem() {
|
|
@@ -537,10 +605,7 @@ class NativeEditorBridge {
|
|
|
537
605
|
const json = scalarSelection
|
|
538
606
|
? getNativeModule().editorOutdentListItemAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
|
|
539
607
|
: getNativeModule().editorOutdentListItem(this._editorId);
|
|
540
|
-
|
|
541
|
-
if (update)
|
|
542
|
-
this._lastSelection = update.selection;
|
|
543
|
-
return update;
|
|
608
|
+
return this.parseAndNoteUpdate(json);
|
|
544
609
|
}
|
|
545
610
|
/** Insert a void node (e.g. 'horizontalRule') at the current selection. */
|
|
546
611
|
insertNode(nodeType) {
|
|
@@ -549,11 +614,41 @@ class NativeEditorBridge {
|
|
|
549
614
|
const json = scalarSelection
|
|
550
615
|
? getNativeModule().editorInsertNodeAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, nodeType)
|
|
551
616
|
: getNativeModule().editorInsertNode(this._editorId, nodeType);
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
617
|
+
return this.parseAndNoteUpdate(json);
|
|
618
|
+
}
|
|
619
|
+
parseUpdateJson(json) {
|
|
620
|
+
this.assertNotDestroyed();
|
|
621
|
+
return this.parseAndNoteUpdate(json);
|
|
622
|
+
}
|
|
623
|
+
noteUpdate(update) {
|
|
624
|
+
if (!update) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
this._lastSelection = update.selection;
|
|
628
|
+
if (update.renderBlocks) {
|
|
629
|
+
this._renderBlocksCache = update.renderBlocks;
|
|
630
|
+
}
|
|
631
|
+
if (typeof update.documentVersion !== 'number') {
|
|
632
|
+
this.invalidateContentCaches();
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
if (update.documentVersion !== this._documentVersion) {
|
|
636
|
+
this._documentVersion = update.documentVersion;
|
|
637
|
+
this.invalidateContentCaches();
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
parseAndNoteUpdate(json) {
|
|
641
|
+
let update = parseEditorUpdateJson(json, this._renderBlocksCache ?? undefined);
|
|
642
|
+
if (update?.renderPatch && !update.renderBlocks) {
|
|
643
|
+
update = parseEditorUpdateJson(getNativeModule().editorGetCurrentState(this._editorId), this._renderBlocksCache ?? undefined);
|
|
644
|
+
}
|
|
645
|
+
this.noteUpdate(update);
|
|
555
646
|
return update;
|
|
556
647
|
}
|
|
648
|
+
invalidateContentCaches() {
|
|
649
|
+
this._cachedHtml = null;
|
|
650
|
+
this._cachedJsonString = null;
|
|
651
|
+
}
|
|
557
652
|
assertNotDestroyed() {
|
|
558
653
|
if (this._destroyed) {
|
|
559
654
|
throw new Error(ERR_DESTROYED);
|
|
@@ -4,8 +4,7 @@ import { type ActiveState, type DocumentJSON, type HistoryState, type Selection
|
|
|
4
4
|
import { type EditorToolbarHeadingLevel, type EditorToolbarItem } from './EditorToolbar';
|
|
5
5
|
import { type EditorTheme } from './EditorTheme';
|
|
6
6
|
import { type EditorAddons } from './addons';
|
|
7
|
-
import { type SchemaDefinition } from './schemas';
|
|
8
|
-
import { type ImageNodeAttributes } from './schemas';
|
|
7
|
+
import { type ImageNodeAttributes, type SchemaDefinition } from './schemas';
|
|
9
8
|
export type NativeRichTextEditorHeightBehavior = 'fixed' | 'autoGrow';
|
|
10
9
|
export type NativeRichTextEditorToolbarPlacement = 'keyboard' | 'inline';
|
|
11
10
|
export interface RemoteSelectionDecoration {
|
|
@@ -38,6 +37,8 @@ export interface NativeRichTextEditorProps {
|
|
|
38
37
|
value?: string;
|
|
39
38
|
/** Controlled ProseMirror JSON content. Ignored if value is set. */
|
|
40
39
|
valueJSON?: DocumentJSON;
|
|
40
|
+
/** Optional stable revision hint for `valueJSON` to avoid reserializing equal docs on rerender. */
|
|
41
|
+
valueJSONRevision?: string | number;
|
|
41
42
|
/** Schema definition. Defaults to tiptapSchema if not provided. */
|
|
42
43
|
schema?: SchemaDefinition;
|
|
43
44
|
/** Placeholder text shown when editor is empty. */
|