@apollohg/react-native-prose-editor 0.4.0 → 0.4.1
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 +18 -0
- 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 +36 -1
- package/dist/NativeEditorBridge.js +173 -94
- package/dist/NativeRichTextEditor.d.ts +2 -0
- package/dist/NativeRichTextEditor.js +160 -53
- package/dist/YjsCollaboration.d.ts +2 -0
- package/dist/YjsCollaboration.js +142 -20
- 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;
|
|
@@ -137,6 +152,10 @@ export declare class NativeEditorBridge {
|
|
|
137
152
|
private _editorId;
|
|
138
153
|
private _destroyed;
|
|
139
154
|
private _lastSelection;
|
|
155
|
+
private _documentVersion;
|
|
156
|
+
private _cachedHtml;
|
|
157
|
+
private _cachedJsonString;
|
|
158
|
+
private _renderBlocksCache;
|
|
140
159
|
private constructor();
|
|
141
160
|
/** Create a new editor instance backed by the Rust engine. */
|
|
142
161
|
static create(config?: {
|
|
@@ -156,8 +175,14 @@ export declare class NativeEditorBridge {
|
|
|
156
175
|
getHtml(): string;
|
|
157
176
|
/** Set content from ProseMirror JSON. Returns render elements. */
|
|
158
177
|
setJson(doc: DocumentJSON): RenderElement[];
|
|
178
|
+
/** Set content from a serialized ProseMirror JSON string. Returns render elements. */
|
|
179
|
+
setJsonString(jsonString: string): RenderElement[];
|
|
180
|
+
/** Get content as raw ProseMirror JSON string. */
|
|
181
|
+
getJsonString(): string;
|
|
159
182
|
/** Get content as ProseMirror JSON. */
|
|
160
183
|
getJson(): DocumentJSON;
|
|
184
|
+
/** Get both HTML and JSON content in one native roundtrip. */
|
|
185
|
+
getContentSnapshot(): ContentSnapshot;
|
|
161
186
|
/** Insert text at a document position. Returns the full update. */
|
|
162
187
|
insertText(pos: number, text: string): EditorUpdate | null;
|
|
163
188
|
/** Delete a range [from, to). Returns the full update. */
|
|
@@ -184,6 +209,8 @@ export declare class NativeEditorBridge {
|
|
|
184
209
|
updateSelectionFromNative(anchor: number, head: number): void;
|
|
185
210
|
/** Get the current full state from Rust (render elements, selection, etc.). */
|
|
186
211
|
getCurrentState(): EditorUpdate | null;
|
|
212
|
+
/** Get the current selection-related state without render elements. */
|
|
213
|
+
getSelectionState(): EditorUpdate | null;
|
|
187
214
|
/** Split the block at a position (Enter key). */
|
|
188
215
|
splitBlock(pos: number): EditorUpdate | null;
|
|
189
216
|
/** Insert HTML content at the current selection. */
|
|
@@ -196,6 +223,8 @@ export declare class NativeEditorBridge {
|
|
|
196
223
|
replaceHtml(html: string): EditorUpdate | null;
|
|
197
224
|
/** Replace entire document with JSON via transaction (preserves undo history). */
|
|
198
225
|
replaceJson(doc: DocumentJSON): EditorUpdate | null;
|
|
226
|
+
/** Replace entire document with a serialized JSON transaction. */
|
|
227
|
+
replaceJsonString(jsonString: string): EditorUpdate | null;
|
|
199
228
|
/** Undo the last operation. Returns update or null if nothing to undo. */
|
|
200
229
|
undo(): EditorUpdate | null;
|
|
201
230
|
/** Redo the last undone operation. Returns update or null if nothing to redo. */
|
|
@@ -214,6 +243,10 @@ export declare class NativeEditorBridge {
|
|
|
214
243
|
outdentListItem(): EditorUpdate | null;
|
|
215
244
|
/** Insert a void node (e.g. 'horizontalRule') at the current selection. */
|
|
216
245
|
insertNode(nodeType: string): EditorUpdate | null;
|
|
246
|
+
parseUpdateJson(json: string): EditorUpdate | null;
|
|
247
|
+
private noteUpdate;
|
|
248
|
+
private parseAndNoteUpdate;
|
|
249
|
+
private invalidateContentCaches;
|
|
217
250
|
private assertNotDestroyed;
|
|
218
251
|
private currentScalarSelection;
|
|
219
252
|
}
|
|
@@ -224,6 +257,7 @@ export declare class NativeCollaborationBridge {
|
|
|
224
257
|
static create(config?: {
|
|
225
258
|
clientId?: number;
|
|
226
259
|
fragmentName?: string;
|
|
260
|
+
schema?: SchemaDefinition;
|
|
227
261
|
initialDocumentJson?: DocumentJSON;
|
|
228
262
|
initialEncodedState?: EncodedCollaborationStateInput;
|
|
229
263
|
localAwareness?: Record<string, unknown>;
|
|
@@ -244,3 +278,4 @@ export declare class NativeCollaborationBridge {
|
|
|
244
278
|
clearLocalAwareness(): CollaborationResult;
|
|
245
279
|
private assertNotDestroyed;
|
|
246
280
|
}
|
|
281
|
+
export {};
|
|
@@ -46,7 +46,7 @@ function parseRenderElements(json) {
|
|
|
46
46
|
throw new Error(ERR_NATIVE_RESPONSE);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
function parseEditorUpdateJson(json) {
|
|
49
|
+
function parseEditorUpdateJson(json, previousRenderBlocks) {
|
|
50
50
|
if (!json || json === '')
|
|
51
51
|
return null;
|
|
52
52
|
try {
|
|
@@ -54,14 +54,72 @@ function parseEditorUpdateJson(json) {
|
|
|
54
54
|
if ('error' in parsed) {
|
|
55
55
|
throw new Error(`NativeEditorBridge: ${parsed.error}`);
|
|
56
56
|
}
|
|
57
|
+
const renderBlocks = Array.isArray(parsed.renderBlocks)
|
|
58
|
+
? parsed.renderBlocks
|
|
59
|
+
: applyRenderBlocksPatch(previousRenderBlocks, parsed.renderPatch != null && typeof parsed.renderPatch === 'object'
|
|
60
|
+
? parsed.renderPatch
|
|
61
|
+
: undefined);
|
|
62
|
+
const renderPatch = parsed.renderPatch != null && typeof parsed.renderPatch === 'object'
|
|
63
|
+
? parsed.renderPatch
|
|
64
|
+
: undefined;
|
|
57
65
|
return {
|
|
58
|
-
renderElements: (parsed.renderElements
|
|
66
|
+
renderElements: Array.isArray(parsed.renderElements)
|
|
67
|
+
? parsed.renderElements
|
|
68
|
+
: flattenRenderBlocks(renderBlocks),
|
|
69
|
+
renderBlocks,
|
|
70
|
+
renderPatch,
|
|
59
71
|
selection: (parsed.selection ?? { type: 'text', anchor: 0, head: 0 }),
|
|
60
72
|
activeState: normalizeActiveState(parsed.activeState),
|
|
61
73
|
historyState: (parsed.historyState ?? {
|
|
62
74
|
canUndo: false,
|
|
63
75
|
canRedo: false,
|
|
64
76
|
}),
|
|
77
|
+
documentVersion: typeof parsed.documentVersion === 'number' ? parsed.documentVersion : undefined,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
if (e instanceof Error && e.message.startsWith('NativeEditorBridge:')) {
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
throw new Error(ERR_NATIVE_RESPONSE);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function flattenRenderBlocks(renderBlocks) {
|
|
88
|
+
if (!renderBlocks || renderBlocks.length === 0) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
return renderBlocks.flat();
|
|
92
|
+
}
|
|
93
|
+
function applyRenderBlocksPatch(previousRenderBlocks, renderPatch) {
|
|
94
|
+
if (!previousRenderBlocks || !renderPatch) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
const { startIndex, deleteCount, renderBlocks } = renderPatch;
|
|
98
|
+
if (!Number.isInteger(startIndex) ||
|
|
99
|
+
!Number.isInteger(deleteCount) ||
|
|
100
|
+
startIndex < 0 ||
|
|
101
|
+
deleteCount < 0 ||
|
|
102
|
+
startIndex > previousRenderBlocks.length ||
|
|
103
|
+
startIndex + deleteCount > previousRenderBlocks.length) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
return [
|
|
107
|
+
...previousRenderBlocks.slice(0, startIndex),
|
|
108
|
+
...renderBlocks,
|
|
109
|
+
...previousRenderBlocks.slice(startIndex + deleteCount),
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
function parseContentSnapshotJson(json) {
|
|
113
|
+
try {
|
|
114
|
+
const parsed = JSON.parse(json);
|
|
115
|
+
if ('error' in parsed) {
|
|
116
|
+
throw new Error(`NativeEditorBridge: ${parsed.error}`);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
html: typeof parsed.html === 'string' ? parsed.html : '',
|
|
120
|
+
json: parsed.json != null && typeof parsed.json === 'object'
|
|
121
|
+
? parsed.json
|
|
122
|
+
: {},
|
|
65
123
|
};
|
|
66
124
|
}
|
|
67
125
|
catch (e) {
|
|
@@ -220,6 +278,10 @@ class NativeEditorBridge {
|
|
|
220
278
|
constructor(editorId) {
|
|
221
279
|
this._destroyed = false;
|
|
222
280
|
this._lastSelection = { type: 'text', anchor: 0, head: 0 };
|
|
281
|
+
this._documentVersion = 0;
|
|
282
|
+
this._cachedHtml = null;
|
|
283
|
+
this._cachedJsonString = null;
|
|
284
|
+
this._renderBlocksCache = null;
|
|
223
285
|
this._editorId = editorId;
|
|
224
286
|
}
|
|
225
287
|
/** Create a new editor instance backed by the Rust engine. */
|
|
@@ -254,57 +316,88 @@ class NativeEditorBridge {
|
|
|
254
316
|
if (this._destroyed)
|
|
255
317
|
return;
|
|
256
318
|
this._destroyed = true;
|
|
319
|
+
this._renderBlocksCache = null;
|
|
257
320
|
getNativeModule().editorDestroy(this._editorId);
|
|
258
321
|
}
|
|
259
322
|
/** Set content from HTML. Returns render elements for display. */
|
|
260
323
|
setHtml(html) {
|
|
261
324
|
this.assertNotDestroyed();
|
|
325
|
+
this.invalidateContentCaches();
|
|
326
|
+
this._renderBlocksCache = null;
|
|
262
327
|
const json = getNativeModule().editorSetHtml(this._editorId, html);
|
|
263
328
|
return parseRenderElements(json);
|
|
264
329
|
}
|
|
265
330
|
/** Get content as HTML. */
|
|
266
331
|
getHtml() {
|
|
267
332
|
this.assertNotDestroyed();
|
|
268
|
-
|
|
333
|
+
if (this._cachedHtml?.version === this._documentVersion) {
|
|
334
|
+
return this._cachedHtml.value;
|
|
335
|
+
}
|
|
336
|
+
const html = getNativeModule().editorGetHtml(this._editorId);
|
|
337
|
+
this._cachedHtml = { version: this._documentVersion, value: html };
|
|
338
|
+
return html;
|
|
269
339
|
}
|
|
270
340
|
/** Set content from ProseMirror JSON. Returns render elements. */
|
|
271
341
|
setJson(doc) {
|
|
342
|
+
return this.setJsonString(JSON.stringify(doc));
|
|
343
|
+
}
|
|
344
|
+
/** Set content from a serialized ProseMirror JSON string. Returns render elements. */
|
|
345
|
+
setJsonString(jsonString) {
|
|
272
346
|
this.assertNotDestroyed();
|
|
273
|
-
|
|
347
|
+
this.invalidateContentCaches();
|
|
348
|
+
this._renderBlocksCache = null;
|
|
349
|
+
const json = getNativeModule().editorSetJson(this._editorId, jsonString);
|
|
274
350
|
return parseRenderElements(json);
|
|
275
351
|
}
|
|
352
|
+
/** Get content as raw ProseMirror JSON string. */
|
|
353
|
+
getJsonString() {
|
|
354
|
+
this.assertNotDestroyed();
|
|
355
|
+
if (this._cachedJsonString?.version === this._documentVersion) {
|
|
356
|
+
return this._cachedJsonString.value;
|
|
357
|
+
}
|
|
358
|
+
const json = getNativeModule().editorGetJson(this._editorId);
|
|
359
|
+
this._cachedJsonString = { version: this._documentVersion, value: json };
|
|
360
|
+
return json;
|
|
361
|
+
}
|
|
276
362
|
/** Get content as ProseMirror JSON. */
|
|
277
363
|
getJson() {
|
|
364
|
+
return parseDocumentJSON(this.getJsonString());
|
|
365
|
+
}
|
|
366
|
+
/** Get both HTML and JSON content in one native roundtrip. */
|
|
367
|
+
getContentSnapshot() {
|
|
278
368
|
this.assertNotDestroyed();
|
|
279
|
-
|
|
280
|
-
|
|
369
|
+
if (this._cachedHtml?.version === this._documentVersion &&
|
|
370
|
+
this._cachedJsonString?.version === this._documentVersion) {
|
|
371
|
+
return {
|
|
372
|
+
html: this._cachedHtml.value,
|
|
373
|
+
json: parseDocumentJSON(this._cachedJsonString.value),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
const snapshot = parseContentSnapshotJson(getNativeModule().editorGetContentSnapshot(this._editorId));
|
|
377
|
+
this._cachedHtml = { version: this._documentVersion, value: snapshot.html };
|
|
378
|
+
this._cachedJsonString = {
|
|
379
|
+
version: this._documentVersion,
|
|
380
|
+
value: JSON.stringify(snapshot.json),
|
|
381
|
+
};
|
|
382
|
+
return snapshot;
|
|
281
383
|
}
|
|
282
384
|
/** Insert text at a document position. Returns the full update. */
|
|
283
385
|
insertText(pos, text) {
|
|
284
386
|
this.assertNotDestroyed();
|
|
285
387
|
const json = getNativeModule().editorInsertText(this._editorId, pos, text);
|
|
286
|
-
|
|
287
|
-
if (update)
|
|
288
|
-
this._lastSelection = update.selection;
|
|
289
|
-
return update;
|
|
388
|
+
return this.parseAndNoteUpdate(json);
|
|
290
389
|
}
|
|
291
390
|
/** Delete a range [from, to). Returns the full update. */
|
|
292
391
|
deleteRange(from, to) {
|
|
293
392
|
this.assertNotDestroyed();
|
|
294
393
|
const json = getNativeModule().editorDeleteRange(this._editorId, from, to);
|
|
295
|
-
|
|
296
|
-
if (update)
|
|
297
|
-
this._lastSelection = update.selection;
|
|
298
|
-
return update;
|
|
394
|
+
return this.parseAndNoteUpdate(json);
|
|
299
395
|
}
|
|
300
396
|
/** Replace the current selection with text atomically. */
|
|
301
397
|
replaceSelectionText(text) {
|
|
302
398
|
this.assertNotDestroyed();
|
|
303
399
|
const json = getNativeModule().editorReplaceSelectionText(this._editorId, text);
|
|
304
|
-
|
|
305
|
-
if (update)
|
|
306
|
-
this._lastSelection = update.selection;
|
|
307
|
-
return update;
|
|
400
|
+
return this.parseAndNoteUpdate(json);
|
|
308
401
|
}
|
|
309
402
|
/** Toggle a mark (bold, italic, etc.) on the current selection. */
|
|
310
403
|
toggleMark(markType) {
|
|
@@ -313,10 +406,7 @@ class NativeEditorBridge {
|
|
|
313
406
|
const json = scalarSelection
|
|
314
407
|
? getNativeModule().editorToggleMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType)
|
|
315
408
|
: getNativeModule().editorToggleMark(this._editorId, markType);
|
|
316
|
-
|
|
317
|
-
if (update)
|
|
318
|
-
this._lastSelection = update.selection;
|
|
319
|
-
return update;
|
|
409
|
+
return this.parseAndNoteUpdate(json);
|
|
320
410
|
}
|
|
321
411
|
/** Set a mark with attrs on the current selection. */
|
|
322
412
|
setMark(markType, attrs) {
|
|
@@ -326,10 +416,7 @@ class NativeEditorBridge {
|
|
|
326
416
|
const json = scalarSelection
|
|
327
417
|
? getNativeModule().editorSetMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType, attrsJson)
|
|
328
418
|
: getNativeModule().editorSetMark(this._editorId, markType, attrsJson);
|
|
329
|
-
|
|
330
|
-
if (update)
|
|
331
|
-
this._lastSelection = update.selection;
|
|
332
|
-
return update;
|
|
419
|
+
return this.parseAndNoteUpdate(json);
|
|
333
420
|
}
|
|
334
421
|
/** Remove a mark from the current selection. */
|
|
335
422
|
unsetMark(markType) {
|
|
@@ -338,10 +425,7 @@ class NativeEditorBridge {
|
|
|
338
425
|
const json = scalarSelection
|
|
339
426
|
? getNativeModule().editorUnsetMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType)
|
|
340
427
|
: getNativeModule().editorUnsetMark(this._editorId, markType);
|
|
341
|
-
|
|
342
|
-
if (update)
|
|
343
|
-
this._lastSelection = update.selection;
|
|
344
|
-
return update;
|
|
428
|
+
return this.parseAndNoteUpdate(json);
|
|
345
429
|
}
|
|
346
430
|
/** Toggle blockquote wrapping for the current block selection. */
|
|
347
431
|
toggleBlockquote() {
|
|
@@ -350,10 +434,7 @@ class NativeEditorBridge {
|
|
|
350
434
|
const json = scalarSelection
|
|
351
435
|
? getNativeModule().editorToggleBlockquoteAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
|
|
352
436
|
: getNativeModule().editorToggleBlockquote(this._editorId);
|
|
353
|
-
|
|
354
|
-
if (update)
|
|
355
|
-
this._lastSelection = update.selection;
|
|
356
|
-
return update;
|
|
437
|
+
return this.parseAndNoteUpdate(json);
|
|
357
438
|
}
|
|
358
439
|
/** Toggle a heading level on the current block selection. */
|
|
359
440
|
toggleHeading(level) {
|
|
@@ -365,10 +446,7 @@ class NativeEditorBridge {
|
|
|
365
446
|
const json = scalarSelection
|
|
366
447
|
? getNativeModule().editorToggleHeadingAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, level)
|
|
367
448
|
: getNativeModule().editorToggleHeading(this._editorId, level);
|
|
368
|
-
|
|
369
|
-
if (update)
|
|
370
|
-
this._lastSelection = update.selection;
|
|
371
|
-
return update;
|
|
449
|
+
return this.parseAndNoteUpdate(json);
|
|
372
450
|
}
|
|
373
451
|
/** Set the document selection by anchor and head positions. */
|
|
374
452
|
setSelection(anchor, head) {
|
|
@@ -402,82 +480,65 @@ class NativeEditorBridge {
|
|
|
402
480
|
getCurrentState() {
|
|
403
481
|
this.assertNotDestroyed();
|
|
404
482
|
const json = getNativeModule().editorGetCurrentState(this._editorId);
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
483
|
+
return this.parseAndNoteUpdate(json);
|
|
484
|
+
}
|
|
485
|
+
/** Get the current selection-related state without render elements. */
|
|
486
|
+
getSelectionState() {
|
|
487
|
+
this.assertNotDestroyed();
|
|
488
|
+
const json = getNativeModule().editorGetSelectionState(this._editorId);
|
|
489
|
+
return this.parseAndNoteUpdate(json);
|
|
409
490
|
}
|
|
410
491
|
/** Split the block at a position (Enter key). */
|
|
411
492
|
splitBlock(pos) {
|
|
412
493
|
this.assertNotDestroyed();
|
|
413
494
|
const json = getNativeModule().editorSplitBlock(this._editorId, pos);
|
|
414
|
-
|
|
415
|
-
if (update)
|
|
416
|
-
this._lastSelection = update.selection;
|
|
417
|
-
return update;
|
|
495
|
+
return this.parseAndNoteUpdate(json);
|
|
418
496
|
}
|
|
419
497
|
/** Insert HTML content at the current selection. */
|
|
420
498
|
insertContentHtml(html) {
|
|
421
499
|
this.assertNotDestroyed();
|
|
422
500
|
const json = getNativeModule().editorInsertContentHtml(this._editorId, html);
|
|
423
|
-
|
|
424
|
-
if (update)
|
|
425
|
-
this._lastSelection = update.selection;
|
|
426
|
-
return update;
|
|
501
|
+
return this.parseAndNoteUpdate(json);
|
|
427
502
|
}
|
|
428
503
|
/** Insert JSON content at the current selection. */
|
|
429
504
|
insertContentJson(doc) {
|
|
430
505
|
this.assertNotDestroyed();
|
|
431
506
|
const json = getNativeModule().editorInsertContentJson(this._editorId, JSON.stringify(doc));
|
|
432
|
-
|
|
433
|
-
if (update)
|
|
434
|
-
this._lastSelection = update.selection;
|
|
435
|
-
return update;
|
|
507
|
+
return this.parseAndNoteUpdate(json);
|
|
436
508
|
}
|
|
437
509
|
/** Insert JSON content at an explicit scalar selection. */
|
|
438
510
|
insertContentJsonAtSelectionScalar(scalarAnchor, scalarHead, doc) {
|
|
439
511
|
this.assertNotDestroyed();
|
|
440
512
|
const json = getNativeModule().editorInsertContentJsonAtSelectionScalar(this._editorId, scalarAnchor, scalarHead, JSON.stringify(doc));
|
|
441
|
-
|
|
442
|
-
if (update)
|
|
443
|
-
this._lastSelection = update.selection;
|
|
444
|
-
return update;
|
|
513
|
+
return this.parseAndNoteUpdate(json);
|
|
445
514
|
}
|
|
446
515
|
/** Replace entire document with HTML via transaction (preserves undo history). */
|
|
447
516
|
replaceHtml(html) {
|
|
448
517
|
this.assertNotDestroyed();
|
|
449
518
|
const json = getNativeModule().editorReplaceHtml(this._editorId, html);
|
|
450
|
-
|
|
451
|
-
if (update)
|
|
452
|
-
this._lastSelection = update.selection;
|
|
453
|
-
return update;
|
|
519
|
+
return this.parseAndNoteUpdate(json);
|
|
454
520
|
}
|
|
455
521
|
/** Replace entire document with JSON via transaction (preserves undo history). */
|
|
456
522
|
replaceJson(doc) {
|
|
523
|
+
return this.replaceJsonString(JSON.stringify(doc));
|
|
524
|
+
}
|
|
525
|
+
/** Replace entire document with a serialized JSON transaction. */
|
|
526
|
+
replaceJsonString(jsonString) {
|
|
457
527
|
this.assertNotDestroyed();
|
|
458
|
-
const json = getNativeModule().editorReplaceJson(this._editorId,
|
|
459
|
-
|
|
460
|
-
if (update)
|
|
461
|
-
this._lastSelection = update.selection;
|
|
462
|
-
return update;
|
|
528
|
+
const json = getNativeModule().editorReplaceJson(this._editorId, jsonString);
|
|
529
|
+
return this.parseAndNoteUpdate(json);
|
|
463
530
|
}
|
|
464
531
|
/** Undo the last operation. Returns update or null if nothing to undo. */
|
|
465
532
|
undo() {
|
|
466
533
|
this.assertNotDestroyed();
|
|
467
534
|
const json = getNativeModule().editorUndo(this._editorId);
|
|
468
|
-
|
|
469
|
-
if (update)
|
|
470
|
-
this._lastSelection = update.selection;
|
|
471
|
-
return update;
|
|
535
|
+
return this.parseAndNoteUpdate(json);
|
|
472
536
|
}
|
|
473
537
|
/** Redo the last undone operation. Returns update or null if nothing to redo. */
|
|
474
538
|
redo() {
|
|
475
539
|
this.assertNotDestroyed();
|
|
476
540
|
const json = getNativeModule().editorRedo(this._editorId);
|
|
477
|
-
|
|
478
|
-
if (update)
|
|
479
|
-
this._lastSelection = update.selection;
|
|
480
|
-
return update;
|
|
541
|
+
return this.parseAndNoteUpdate(json);
|
|
481
542
|
}
|
|
482
543
|
/** Check if undo is available. */
|
|
483
544
|
canUndo() {
|
|
@@ -501,10 +562,7 @@ class NativeEditorBridge {
|
|
|
501
562
|
: scalarSelection
|
|
502
563
|
? getNativeModule().editorWrapInListAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, listType)
|
|
503
564
|
: getNativeModule().editorWrapInList(this._editorId, listType);
|
|
504
|
-
|
|
505
|
-
if (update)
|
|
506
|
-
this._lastSelection = update.selection;
|
|
507
|
-
return update;
|
|
565
|
+
return this.parseAndNoteUpdate(json);
|
|
508
566
|
}
|
|
509
567
|
/** Unwrap the current list item back to a paragraph. */
|
|
510
568
|
unwrapFromList() {
|
|
@@ -513,10 +571,7 @@ class NativeEditorBridge {
|
|
|
513
571
|
const json = scalarSelection
|
|
514
572
|
? getNativeModule().editorUnwrapFromListAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
|
|
515
573
|
: getNativeModule().editorUnwrapFromList(this._editorId);
|
|
516
|
-
|
|
517
|
-
if (update)
|
|
518
|
-
this._lastSelection = update.selection;
|
|
519
|
-
return update;
|
|
574
|
+
return this.parseAndNoteUpdate(json);
|
|
520
575
|
}
|
|
521
576
|
/** Indent the current list item into a nested list. */
|
|
522
577
|
indentListItem() {
|
|
@@ -525,10 +580,7 @@ class NativeEditorBridge {
|
|
|
525
580
|
const json = scalarSelection
|
|
526
581
|
? getNativeModule().editorIndentListItemAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
|
|
527
582
|
: getNativeModule().editorIndentListItem(this._editorId);
|
|
528
|
-
|
|
529
|
-
if (update)
|
|
530
|
-
this._lastSelection = update.selection;
|
|
531
|
-
return update;
|
|
583
|
+
return this.parseAndNoteUpdate(json);
|
|
532
584
|
}
|
|
533
585
|
/** Outdent the current list item to the parent list level. */
|
|
534
586
|
outdentListItem() {
|
|
@@ -537,10 +589,7 @@ class NativeEditorBridge {
|
|
|
537
589
|
const json = scalarSelection
|
|
538
590
|
? getNativeModule().editorOutdentListItemAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
|
|
539
591
|
: getNativeModule().editorOutdentListItem(this._editorId);
|
|
540
|
-
|
|
541
|
-
if (update)
|
|
542
|
-
this._lastSelection = update.selection;
|
|
543
|
-
return update;
|
|
592
|
+
return this.parseAndNoteUpdate(json);
|
|
544
593
|
}
|
|
545
594
|
/** Insert a void node (e.g. 'horizontalRule') at the current selection. */
|
|
546
595
|
insertNode(nodeType) {
|
|
@@ -549,11 +598,41 @@ class NativeEditorBridge {
|
|
|
549
598
|
const json = scalarSelection
|
|
550
599
|
? getNativeModule().editorInsertNodeAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, nodeType)
|
|
551
600
|
: getNativeModule().editorInsertNode(this._editorId, nodeType);
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
601
|
+
return this.parseAndNoteUpdate(json);
|
|
602
|
+
}
|
|
603
|
+
parseUpdateJson(json) {
|
|
604
|
+
this.assertNotDestroyed();
|
|
605
|
+
return this.parseAndNoteUpdate(json);
|
|
606
|
+
}
|
|
607
|
+
noteUpdate(update) {
|
|
608
|
+
if (!update) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
this._lastSelection = update.selection;
|
|
612
|
+
if (update.renderBlocks) {
|
|
613
|
+
this._renderBlocksCache = update.renderBlocks;
|
|
614
|
+
}
|
|
615
|
+
if (typeof update.documentVersion !== 'number') {
|
|
616
|
+
this.invalidateContentCaches();
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (update.documentVersion !== this._documentVersion) {
|
|
620
|
+
this._documentVersion = update.documentVersion;
|
|
621
|
+
this.invalidateContentCaches();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
parseAndNoteUpdate(json) {
|
|
625
|
+
let update = parseEditorUpdateJson(json, this._renderBlocksCache ?? undefined);
|
|
626
|
+
if (update?.renderPatch && !update.renderBlocks) {
|
|
627
|
+
update = parseEditorUpdateJson(getNativeModule().editorGetCurrentState(this._editorId), this._renderBlocksCache ?? undefined);
|
|
628
|
+
}
|
|
629
|
+
this.noteUpdate(update);
|
|
555
630
|
return update;
|
|
556
631
|
}
|
|
632
|
+
invalidateContentCaches() {
|
|
633
|
+
this._cachedHtml = null;
|
|
634
|
+
this._cachedJsonString = null;
|
|
635
|
+
}
|
|
557
636
|
assertNotDestroyed() {
|
|
558
637
|
if (this._destroyed) {
|
|
559
638
|
throw new Error(ERR_DESTROYED);
|
|
@@ -38,6 +38,8 @@ export interface NativeRichTextEditorProps {
|
|
|
38
38
|
value?: string;
|
|
39
39
|
/** Controlled ProseMirror JSON content. Ignored if value is set. */
|
|
40
40
|
valueJSON?: DocumentJSON;
|
|
41
|
+
/** Optional stable revision hint for `valueJSON` to avoid reserializing equal docs on rerender. */
|
|
42
|
+
valueJSONRevision?: string | number;
|
|
41
43
|
/** Schema definition. Defaults to tiptapSchema if not provided. */
|
|
42
44
|
schema?: SchemaDefinition;
|
|
43
45
|
/** Placeholder text shown when editor is empty. */
|