@apollohg/react-native-prose-editor 0.5.16 → 0.5.17
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/android/src/main/java/com/apollohg/editor/EditorEditText.kt +1396 -143
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +403 -59
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +1666 -79
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +209 -87
- package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +27 -0
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +58 -9
- package/dist/NativeEditorBridge.d.ts +34 -1
- package/dist/NativeEditorBridge.js +243 -83
- package/dist/NativeRichTextEditor.js +998 -137
- package/dist/addons.d.ts +7 -0
- 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/NativeEditorExpoView.swift +830 -17
- package/ios/NativeEditorModule.swift +304 -108
- package/ios/PositionBridge.swift +24 -1
- package/ios/RichTextEditorView.swift +715 -89
- package/package.json +2 -1
- 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
|
@@ -3,6 +3,7 @@ import { type SchemaDefinition } from './schemas';
|
|
|
3
3
|
export interface NativeEditorModule {
|
|
4
4
|
editorCreate(configJson: string): number;
|
|
5
5
|
editorDestroy(editorId: number): void;
|
|
6
|
+
editorPrepareForCommand?(editorId: number): string;
|
|
6
7
|
collaborationSessionCreate(configJson: string): number;
|
|
7
8
|
collaborationSessionDestroy(sessionId: number): void;
|
|
8
9
|
collaborationSessionGetDocumentJson(sessionId: number): string;
|
|
@@ -123,6 +124,10 @@ export interface EditorUpdate {
|
|
|
123
124
|
historyState: HistoryState;
|
|
124
125
|
documentVersion?: number;
|
|
125
126
|
}
|
|
127
|
+
export interface ParseUpdateOptions {
|
|
128
|
+
rejectSameDocumentVersion?: boolean;
|
|
129
|
+
rejectVersionlessAfterDocumentVersion?: boolean;
|
|
130
|
+
}
|
|
126
131
|
export interface ContentSnapshot {
|
|
127
132
|
html: string;
|
|
128
133
|
json: DocumentJSON;
|
|
@@ -142,6 +147,11 @@ export interface CollaborationResult {
|
|
|
142
147
|
peersChanged: boolean;
|
|
143
148
|
peers?: CollaborationPeer[];
|
|
144
149
|
}
|
|
150
|
+
export type CommandBlockedReason = 'composition' | 'detached' | 'pendingUpdate' | 'destroyed' | 'unknown';
|
|
151
|
+
export interface CommandBlockedInfo {
|
|
152
|
+
blocked: boolean;
|
|
153
|
+
reason: CommandBlockedReason | null;
|
|
154
|
+
}
|
|
145
155
|
export type EncodedCollaborationStateInput = Uint8Array | readonly number[] | string;
|
|
146
156
|
export declare function normalizeActiveState(raw: unknown): ActiveState;
|
|
147
157
|
export declare function parseEditorUpdateJson(json: string, previousRenderBlocks?: RenderElement[][]): EditorUpdate | null;
|
|
@@ -159,6 +169,11 @@ export declare class NativeEditorBridge {
|
|
|
159
169
|
private _cachedHtml;
|
|
160
170
|
private _cachedJsonString;
|
|
161
171
|
private _renderBlocksCache;
|
|
172
|
+
private _lastCommandBlocked;
|
|
173
|
+
private _lastCommandBlockedReason;
|
|
174
|
+
private _lastCommandPreflightUpdate;
|
|
175
|
+
private _lastAcceptedUpdateJson;
|
|
176
|
+
private _hasSeenDocumentVersion;
|
|
162
177
|
private constructor();
|
|
163
178
|
/** Create a new editor instance backed by the Rust engine. */
|
|
164
179
|
static create(config?: {
|
|
@@ -170,18 +185,29 @@ export declare class NativeEditorBridge {
|
|
|
170
185
|
get editorId(): number;
|
|
171
186
|
/** Whether this bridge has been destroyed. */
|
|
172
187
|
get isDestroyed(): boolean;
|
|
188
|
+
consumeLastCommandBlocked(): boolean;
|
|
189
|
+
consumeLastCommandBlockedReason(): CommandBlockedReason | null;
|
|
190
|
+
consumeLastCommandBlockedInfo(): CommandBlockedInfo;
|
|
191
|
+
consumeLastCommandPreflightUpdate(): EditorUpdate | null;
|
|
192
|
+
prepareForNativeCommand(): boolean;
|
|
173
193
|
/** Destroy the editor instance and free native resources. */
|
|
174
194
|
destroy(): void;
|
|
175
195
|
/** Set content from HTML. Returns render elements for display. */
|
|
176
196
|
setHtml(html: string): RenderElement[];
|
|
177
197
|
/** Get content as HTML. */
|
|
178
198
|
getHtml(): string;
|
|
199
|
+
/** Get cached HTML without making a native roundtrip. */
|
|
200
|
+
getCachedHtml(): string | null;
|
|
179
201
|
/** Set content from ProseMirror JSON. Returns render elements. */
|
|
180
202
|
setJson(doc: DocumentJSON): RenderElement[];
|
|
181
203
|
/** Set content from a serialized ProseMirror JSON string. Returns render elements. */
|
|
182
204
|
setJsonString(jsonString: string): RenderElement[];
|
|
183
205
|
/** Get content as raw ProseMirror JSON string. */
|
|
184
206
|
getJsonString(): string;
|
|
207
|
+
/** Get cached raw ProseMirror JSON without making a native roundtrip. */
|
|
208
|
+
getCachedJsonString(): string | null;
|
|
209
|
+
/** Get cached ProseMirror JSON without making a native roundtrip. */
|
|
210
|
+
getCachedJson(): DocumentJSON | null;
|
|
185
211
|
/** Get content as ProseMirror JSON. */
|
|
186
212
|
getJson(): DocumentJSON;
|
|
187
213
|
/** Get both HTML and JSON content in one native roundtrip. */
|
|
@@ -200,6 +226,8 @@ export declare class NativeEditorBridge {
|
|
|
200
226
|
setMarkAtSelectionScalar(scalarAnchor: number, scalarHead: number, markType: string, attrs: Record<string, unknown>): EditorUpdate | null;
|
|
201
227
|
/** Remove a mark from the current selection. */
|
|
202
228
|
unsetMark(markType: string): EditorUpdate | null;
|
|
229
|
+
/** Remove a mark at an explicit scalar selection. */
|
|
230
|
+
unsetMarkAtSelectionScalar(scalarAnchor: number, scalarHead: number, markType: string): EditorUpdate | null;
|
|
203
231
|
/** Toggle blockquote wrapping for the current block selection. */
|
|
204
232
|
toggleBlockquote(): EditorUpdate | null;
|
|
205
233
|
/** Toggle a heading level on the current block selection. */
|
|
@@ -228,6 +256,8 @@ export declare class NativeEditorBridge {
|
|
|
228
256
|
insertContentJson(doc: DocumentJSON): EditorUpdate | null;
|
|
229
257
|
/** Insert JSON content at an explicit scalar selection. */
|
|
230
258
|
insertContentJsonAtSelectionScalar(scalarAnchor: number, scalarHead: number, doc: DocumentJSON): EditorUpdate | null;
|
|
259
|
+
/** Insert lazily-built JSON content at an explicit scalar selection. */
|
|
260
|
+
insertContentJsonAtSelectionScalarLazy(scalarAnchor: number, scalarHead: number, resolveDoc: () => DocumentJSON): EditorUpdate | null;
|
|
231
261
|
/** Replace entire document with HTML via transaction (preserves undo history). */
|
|
232
262
|
replaceHtml(html: string): EditorUpdate | null;
|
|
233
263
|
/** Replace entire document with JSON via transaction (preserves undo history). */
|
|
@@ -252,9 +282,12 @@ export declare class NativeEditorBridge {
|
|
|
252
282
|
outdentListItem(): EditorUpdate | null;
|
|
253
283
|
/** Insert a void node (e.g. 'horizontalRule') at the current selection. */
|
|
254
284
|
insertNode(nodeType: string): EditorUpdate | null;
|
|
255
|
-
parseUpdateJson(json: string): EditorUpdate | null;
|
|
285
|
+
parseUpdateJson(json: string, options?: ParseUpdateOptions): EditorUpdate | null;
|
|
256
286
|
private noteUpdate;
|
|
287
|
+
private shouldRejectUpdate;
|
|
257
288
|
private parseAndNoteUpdate;
|
|
289
|
+
private prepareForCommand;
|
|
290
|
+
private runPreparedCommand;
|
|
258
291
|
private invalidateContentCaches;
|
|
259
292
|
private assertNotDestroyed;
|
|
260
293
|
private currentScalarSelection;
|
|
@@ -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 react_native_1 = require("react-native");
|
|
11
12
|
const schemas_1 = require("./schemas");
|
|
12
13
|
const ERR_DESTROYED = 'NativeEditorBridge: editor has been destroyed';
|
|
13
14
|
const ERR_NATIVE_RESPONSE = 'NativeEditorBridge: invalid JSON response from native module';
|
|
@@ -244,6 +245,34 @@ function normalizeDocumentJsonString(jsonString, schema) {
|
|
|
244
245
|
return jsonString;
|
|
245
246
|
}
|
|
246
247
|
}
|
|
248
|
+
function parseCommandPreparationJson(json) {
|
|
249
|
+
if (!json)
|
|
250
|
+
return { ready: true };
|
|
251
|
+
let parsed;
|
|
252
|
+
try {
|
|
253
|
+
parsed = JSON.parse(json);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
throw new Error(ERR_NATIVE_RESPONSE);
|
|
257
|
+
}
|
|
258
|
+
if (typeof parsed.error === 'string') {
|
|
259
|
+
throw new Error(`NativeEditorBridge: ${parsed.error}`);
|
|
260
|
+
}
|
|
261
|
+
const rawBlockedReason = parsed.blockedReason;
|
|
262
|
+
const blockedReason = rawBlockedReason === 'composition' ||
|
|
263
|
+
rawBlockedReason === 'detached' ||
|
|
264
|
+
rawBlockedReason === 'pendingUpdate' ||
|
|
265
|
+
rawBlockedReason === 'destroyed'
|
|
266
|
+
? rawBlockedReason
|
|
267
|
+
: rawBlockedReason === 'unknown'
|
|
268
|
+
? 'unknown'
|
|
269
|
+
: undefined;
|
|
270
|
+
return {
|
|
271
|
+
ready: parsed.ready !== false,
|
|
272
|
+
updateJSON: typeof parsed.updateJSON === 'string' ? parsed.updateJSON : undefined,
|
|
273
|
+
blockedReason,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
247
276
|
function parseCollaborationResultJson(json) {
|
|
248
277
|
if (!json || json === '') {
|
|
249
278
|
return {
|
|
@@ -293,6 +322,11 @@ class NativeEditorBridge {
|
|
|
293
322
|
this._cachedHtml = null;
|
|
294
323
|
this._cachedJsonString = null;
|
|
295
324
|
this._renderBlocksCache = null;
|
|
325
|
+
this._lastCommandBlocked = false;
|
|
326
|
+
this._lastCommandBlockedReason = null;
|
|
327
|
+
this._lastCommandPreflightUpdate = null;
|
|
328
|
+
this._lastAcceptedUpdateJson = null;
|
|
329
|
+
this._hasSeenDocumentVersion = false;
|
|
296
330
|
this._editorId = editorId;
|
|
297
331
|
this._schema = schema;
|
|
298
332
|
}
|
|
@@ -325,12 +359,45 @@ class NativeEditorBridge {
|
|
|
325
359
|
get isDestroyed() {
|
|
326
360
|
return this._destroyed;
|
|
327
361
|
}
|
|
362
|
+
consumeLastCommandBlocked() {
|
|
363
|
+
const blocked = this._lastCommandBlocked;
|
|
364
|
+
this._lastCommandBlocked = false;
|
|
365
|
+
this._lastCommandBlockedReason = null;
|
|
366
|
+
return blocked;
|
|
367
|
+
}
|
|
368
|
+
consumeLastCommandBlockedReason() {
|
|
369
|
+
const reason = this._lastCommandBlockedReason;
|
|
370
|
+
this._lastCommandBlocked = false;
|
|
371
|
+
this._lastCommandBlockedReason = null;
|
|
372
|
+
return reason;
|
|
373
|
+
}
|
|
374
|
+
consumeLastCommandBlockedInfo() {
|
|
375
|
+
const info = {
|
|
376
|
+
blocked: this._lastCommandBlocked,
|
|
377
|
+
reason: this._lastCommandBlockedReason,
|
|
378
|
+
};
|
|
379
|
+
this._lastCommandBlocked = false;
|
|
380
|
+
this._lastCommandBlockedReason = null;
|
|
381
|
+
return info;
|
|
382
|
+
}
|
|
383
|
+
consumeLastCommandPreflightUpdate() {
|
|
384
|
+
const update = this._lastCommandPreflightUpdate;
|
|
385
|
+
this._lastCommandPreflightUpdate = null;
|
|
386
|
+
return update;
|
|
387
|
+
}
|
|
388
|
+
prepareForNativeCommand() {
|
|
389
|
+
this.assertNotDestroyed();
|
|
390
|
+
return this.prepareForCommand();
|
|
391
|
+
}
|
|
328
392
|
/** Destroy the editor instance and free native resources. */
|
|
329
393
|
destroy() {
|
|
330
394
|
if (this._destroyed)
|
|
331
395
|
return;
|
|
332
396
|
this._destroyed = true;
|
|
333
397
|
this._renderBlocksCache = null;
|
|
398
|
+
this._lastCommandBlocked = false;
|
|
399
|
+
this._lastCommandBlockedReason = null;
|
|
400
|
+
this._lastCommandPreflightUpdate = null;
|
|
334
401
|
getNativeModule().editorDestroy(this._editorId);
|
|
335
402
|
}
|
|
336
403
|
/** Set content from HTML. Returns render elements for display. */
|
|
@@ -351,6 +418,11 @@ class NativeEditorBridge {
|
|
|
351
418
|
this._cachedHtml = { version: this._documentVersion, value: html };
|
|
352
419
|
return html;
|
|
353
420
|
}
|
|
421
|
+
/** Get cached HTML without making a native roundtrip. */
|
|
422
|
+
getCachedHtml() {
|
|
423
|
+
this.assertNotDestroyed();
|
|
424
|
+
return this._cachedHtml?.value ?? null;
|
|
425
|
+
}
|
|
354
426
|
/** Set content from ProseMirror JSON. Returns render elements. */
|
|
355
427
|
setJson(doc) {
|
|
356
428
|
return this.setJsonString(JSON.stringify((0, schemas_1.normalizeDocumentJson)(doc, this._schema)));
|
|
@@ -374,6 +446,16 @@ class NativeEditorBridge {
|
|
|
374
446
|
this._cachedJsonString = { version: this._documentVersion, value: json };
|
|
375
447
|
return json;
|
|
376
448
|
}
|
|
449
|
+
/** Get cached raw ProseMirror JSON without making a native roundtrip. */
|
|
450
|
+
getCachedJsonString() {
|
|
451
|
+
this.assertNotDestroyed();
|
|
452
|
+
return this._cachedJsonString?.value ?? null;
|
|
453
|
+
}
|
|
454
|
+
/** Get cached ProseMirror JSON without making a native roundtrip. */
|
|
455
|
+
getCachedJson() {
|
|
456
|
+
const json = this.getCachedJsonString();
|
|
457
|
+
return json == null ? null : parseDocumentJSON(json);
|
|
458
|
+
}
|
|
377
459
|
/** Get content as ProseMirror JSON. */
|
|
378
460
|
getJson() {
|
|
379
461
|
return parseDocumentJSON(this.getJsonString());
|
|
@@ -399,63 +481,68 @@ class NativeEditorBridge {
|
|
|
399
481
|
/** Insert text at a document position. Returns the full update. */
|
|
400
482
|
insertText(pos, text) {
|
|
401
483
|
this.assertNotDestroyed();
|
|
402
|
-
|
|
403
|
-
return this.parseAndNoteUpdate(json);
|
|
484
|
+
return this.runPreparedCommand(() => getNativeModule().editorInsertText(this._editorId, pos, text));
|
|
404
485
|
}
|
|
405
486
|
/** Delete a range [from, to). Returns the full update. */
|
|
406
487
|
deleteRange(from, to) {
|
|
407
488
|
this.assertNotDestroyed();
|
|
408
|
-
|
|
409
|
-
return this.parseAndNoteUpdate(json);
|
|
489
|
+
return this.runPreparedCommand(() => getNativeModule().editorDeleteRange(this._editorId, from, to));
|
|
410
490
|
}
|
|
411
491
|
/** Replace the current selection with text atomically. */
|
|
412
492
|
replaceSelectionText(text) {
|
|
413
493
|
this.assertNotDestroyed();
|
|
414
|
-
|
|
415
|
-
return this.parseAndNoteUpdate(json);
|
|
494
|
+
return this.runPreparedCommand(() => getNativeModule().editorReplaceSelectionText(this._editorId, text));
|
|
416
495
|
}
|
|
417
496
|
/** Toggle a mark (bold, italic, etc.) on the current selection. */
|
|
418
497
|
toggleMark(markType) {
|
|
419
498
|
this.assertNotDestroyed();
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
499
|
+
return this.runPreparedCommand(() => {
|
|
500
|
+
const scalarSelection = this.currentScalarSelection();
|
|
501
|
+
return scalarSelection
|
|
502
|
+
? getNativeModule().editorToggleMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType)
|
|
503
|
+
: getNativeModule().editorToggleMark(this._editorId, markType);
|
|
504
|
+
}, { refreshSelectionAfterPreflight: true });
|
|
425
505
|
}
|
|
426
506
|
/** Set a mark with attrs on the current selection. */
|
|
427
507
|
setMark(markType, attrs) {
|
|
428
508
|
this.assertNotDestroyed();
|
|
429
509
|
const attrsJson = JSON.stringify(attrs);
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
510
|
+
return this.runPreparedCommand(() => {
|
|
511
|
+
const scalarSelection = this.currentScalarSelection();
|
|
512
|
+
return scalarSelection
|
|
513
|
+
? getNativeModule().editorSetMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType, attrsJson)
|
|
514
|
+
: getNativeModule().editorSetMark(this._editorId, markType, attrsJson);
|
|
515
|
+
}, { refreshSelectionAfterPreflight: true });
|
|
435
516
|
}
|
|
436
517
|
/** Set a mark with attrs at an explicit scalar selection. */
|
|
437
518
|
setMarkAtSelectionScalar(scalarAnchor, scalarHead, markType, attrs) {
|
|
438
519
|
this.assertNotDestroyed();
|
|
439
|
-
|
|
440
|
-
return this.parseAndNoteUpdate(json);
|
|
520
|
+
return this.runPreparedCommand(() => getNativeModule().editorSetMarkAtSelectionScalar(this._editorId, scalarAnchor, scalarHead, markType, JSON.stringify(attrs)), { cancelIfPreflightUpdated: true });
|
|
441
521
|
}
|
|
442
522
|
/** Remove a mark from the current selection. */
|
|
443
523
|
unsetMark(markType) {
|
|
444
524
|
this.assertNotDestroyed();
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
525
|
+
return this.runPreparedCommand(() => {
|
|
526
|
+
const scalarSelection = this.currentScalarSelection();
|
|
527
|
+
return scalarSelection
|
|
528
|
+
? getNativeModule().editorUnsetMarkAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, markType)
|
|
529
|
+
: getNativeModule().editorUnsetMark(this._editorId, markType);
|
|
530
|
+
}, { refreshSelectionAfterPreflight: true });
|
|
531
|
+
}
|
|
532
|
+
/** Remove a mark at an explicit scalar selection. */
|
|
533
|
+
unsetMarkAtSelectionScalar(scalarAnchor, scalarHead, markType) {
|
|
534
|
+
this.assertNotDestroyed();
|
|
535
|
+
return this.runPreparedCommand(() => getNativeModule().editorUnsetMarkAtSelectionScalar(this._editorId, scalarAnchor, scalarHead, markType), { cancelIfPreflightUpdated: true });
|
|
450
536
|
}
|
|
451
537
|
/** Toggle blockquote wrapping for the current block selection. */
|
|
452
538
|
toggleBlockquote() {
|
|
453
539
|
this.assertNotDestroyed();
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
540
|
+
return this.runPreparedCommand(() => {
|
|
541
|
+
const scalarSelection = this.currentScalarSelection();
|
|
542
|
+
return scalarSelection
|
|
543
|
+
? getNativeModule().editorToggleBlockquoteAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
|
|
544
|
+
: getNativeModule().editorToggleBlockquote(this._editorId);
|
|
545
|
+
}, { refreshSelectionAfterPreflight: true });
|
|
459
546
|
}
|
|
460
547
|
/** Toggle a heading level on the current block selection. */
|
|
461
548
|
toggleHeading(level) {
|
|
@@ -463,11 +550,12 @@ class NativeEditorBridge {
|
|
|
463
550
|
if (!Number.isInteger(level) || level < 1 || level > 6) {
|
|
464
551
|
throw new Error('NativeEditorBridge: invalid heading level');
|
|
465
552
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
553
|
+
return this.runPreparedCommand(() => {
|
|
554
|
+
const scalarSelection = this.currentScalarSelection();
|
|
555
|
+
return scalarSelection
|
|
556
|
+
? getNativeModule().editorToggleHeadingAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, level)
|
|
557
|
+
: getNativeModule().editorToggleHeading(this._editorId, level);
|
|
558
|
+
}, { refreshSelectionAfterPreflight: true });
|
|
471
559
|
}
|
|
472
560
|
/** Set the document selection by anchor and head positions. */
|
|
473
561
|
setSelection(anchor, head) {
|
|
@@ -522,32 +610,31 @@ class NativeEditorBridge {
|
|
|
522
610
|
/** Split the block at a position (Enter key). */
|
|
523
611
|
splitBlock(pos) {
|
|
524
612
|
this.assertNotDestroyed();
|
|
525
|
-
|
|
526
|
-
return this.parseAndNoteUpdate(json);
|
|
613
|
+
return this.runPreparedCommand(() => getNativeModule().editorSplitBlock(this._editorId, pos));
|
|
527
614
|
}
|
|
528
615
|
/** Insert HTML content at the current selection. */
|
|
529
616
|
insertContentHtml(html) {
|
|
530
617
|
this.assertNotDestroyed();
|
|
531
|
-
|
|
532
|
-
return this.parseAndNoteUpdate(json);
|
|
618
|
+
return this.runPreparedCommand(() => getNativeModule().editorInsertContentHtml(this._editorId, html));
|
|
533
619
|
}
|
|
534
620
|
/** Insert JSON content at the current selection. */
|
|
535
621
|
insertContentJson(doc) {
|
|
536
622
|
this.assertNotDestroyed();
|
|
537
|
-
|
|
538
|
-
return this.parseAndNoteUpdate(json);
|
|
623
|
+
return this.runPreparedCommand(() => getNativeModule().editorInsertContentJson(this._editorId, JSON.stringify(doc)));
|
|
539
624
|
}
|
|
540
625
|
/** Insert JSON content at an explicit scalar selection. */
|
|
541
626
|
insertContentJsonAtSelectionScalar(scalarAnchor, scalarHead, doc) {
|
|
627
|
+
return this.insertContentJsonAtSelectionScalarLazy(scalarAnchor, scalarHead, () => doc);
|
|
628
|
+
}
|
|
629
|
+
/** Insert lazily-built JSON content at an explicit scalar selection. */
|
|
630
|
+
insertContentJsonAtSelectionScalarLazy(scalarAnchor, scalarHead, resolveDoc) {
|
|
542
631
|
this.assertNotDestroyed();
|
|
543
|
-
|
|
544
|
-
return this.parseAndNoteUpdate(json);
|
|
632
|
+
return this.runPreparedCommand(() => getNativeModule().editorInsertContentJsonAtSelectionScalar(this._editorId, scalarAnchor, scalarHead, JSON.stringify(resolveDoc())), { cancelIfPreflightUpdated: true });
|
|
545
633
|
}
|
|
546
634
|
/** Replace entire document with HTML via transaction (preserves undo history). */
|
|
547
635
|
replaceHtml(html) {
|
|
548
636
|
this.assertNotDestroyed();
|
|
549
|
-
|
|
550
|
-
return this.parseAndNoteUpdate(json);
|
|
637
|
+
return this.runPreparedCommand(() => getNativeModule().editorReplaceHtml(this._editorId, html));
|
|
551
638
|
}
|
|
552
639
|
/** Replace entire document with JSON via transaction (preserves undo history). */
|
|
553
640
|
replaceJson(doc) {
|
|
@@ -557,20 +644,17 @@ class NativeEditorBridge {
|
|
|
557
644
|
replaceJsonString(jsonString) {
|
|
558
645
|
this.assertNotDestroyed();
|
|
559
646
|
const normalizedJsonString = normalizeDocumentJsonString(jsonString, this._schema);
|
|
560
|
-
|
|
561
|
-
return this.parseAndNoteUpdate(json);
|
|
647
|
+
return this.runPreparedCommand(() => getNativeModule().editorReplaceJson(this._editorId, normalizedJsonString));
|
|
562
648
|
}
|
|
563
649
|
/** Undo the last operation. Returns update or null if nothing to undo. */
|
|
564
650
|
undo() {
|
|
565
651
|
this.assertNotDestroyed();
|
|
566
|
-
|
|
567
|
-
return this.parseAndNoteUpdate(json);
|
|
652
|
+
return this.runPreparedCommand(() => getNativeModule().editorUndo(this._editorId));
|
|
568
653
|
}
|
|
569
654
|
/** Redo the last undone operation. Returns update or null if nothing to redo. */
|
|
570
655
|
redo() {
|
|
571
656
|
this.assertNotDestroyed();
|
|
572
|
-
|
|
573
|
-
return this.parseAndNoteUpdate(json);
|
|
657
|
+
return this.runPreparedCommand(() => getNativeModule().editorRedo(this._editorId));
|
|
574
658
|
}
|
|
575
659
|
/** Check if undo is available. */
|
|
576
660
|
canUndo() {
|
|
@@ -585,56 +669,61 @@ class NativeEditorBridge {
|
|
|
585
669
|
/** Toggle a list type on the current selection. Wraps if not in list, unwraps if already in that list type. */
|
|
586
670
|
toggleList(listType) {
|
|
587
671
|
this.assertNotDestroyed();
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
?
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
672
|
+
return this.runPreparedCommand(() => {
|
|
673
|
+
const isActive = this.getCurrentState()?.activeState?.nodes?.[listType] === true;
|
|
674
|
+
const scalarSelection = this.currentScalarSelection();
|
|
675
|
+
return isActive
|
|
676
|
+
? scalarSelection
|
|
677
|
+
? getNativeModule().editorUnwrapFromListAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
|
|
678
|
+
: getNativeModule().editorUnwrapFromList(this._editorId)
|
|
679
|
+
: scalarSelection
|
|
680
|
+
? getNativeModule().editorWrapInListAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, listType)
|
|
681
|
+
: getNativeModule().editorWrapInList(this._editorId, listType);
|
|
682
|
+
}, { refreshSelectionAfterPreflight: true });
|
|
598
683
|
}
|
|
599
684
|
/** Unwrap the current list item back to a paragraph. */
|
|
600
685
|
unwrapFromList() {
|
|
601
686
|
this.assertNotDestroyed();
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
687
|
+
return this.runPreparedCommand(() => {
|
|
688
|
+
const scalarSelection = this.currentScalarSelection();
|
|
689
|
+
return scalarSelection
|
|
690
|
+
? getNativeModule().editorUnwrapFromListAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
|
|
691
|
+
: getNativeModule().editorUnwrapFromList(this._editorId);
|
|
692
|
+
}, { refreshSelectionAfterPreflight: true });
|
|
607
693
|
}
|
|
608
694
|
/** Indent the current list item into a nested list. */
|
|
609
695
|
indentListItem() {
|
|
610
696
|
this.assertNotDestroyed();
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
697
|
+
return this.runPreparedCommand(() => {
|
|
698
|
+
const scalarSelection = this.currentScalarSelection();
|
|
699
|
+
return scalarSelection
|
|
700
|
+
? getNativeModule().editorIndentListItemAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
|
|
701
|
+
: getNativeModule().editorIndentListItem(this._editorId);
|
|
702
|
+
}, { refreshSelectionAfterPreflight: true });
|
|
616
703
|
}
|
|
617
704
|
/** Outdent the current list item to the parent list level. */
|
|
618
705
|
outdentListItem() {
|
|
619
706
|
this.assertNotDestroyed();
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
707
|
+
return this.runPreparedCommand(() => {
|
|
708
|
+
const scalarSelection = this.currentScalarSelection();
|
|
709
|
+
return scalarSelection
|
|
710
|
+
? getNativeModule().editorOutdentListItemAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head)
|
|
711
|
+
: getNativeModule().editorOutdentListItem(this._editorId);
|
|
712
|
+
}, { refreshSelectionAfterPreflight: true });
|
|
625
713
|
}
|
|
626
714
|
/** Insert a void node (e.g. 'horizontalRule') at the current selection. */
|
|
627
715
|
insertNode(nodeType) {
|
|
628
716
|
this.assertNotDestroyed();
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
717
|
+
return this.runPreparedCommand(() => {
|
|
718
|
+
const scalarSelection = this.currentScalarSelection();
|
|
719
|
+
return scalarSelection
|
|
720
|
+
? getNativeModule().editorInsertNodeAtSelectionScalar(this._editorId, scalarSelection.anchor, scalarSelection.head, nodeType)
|
|
721
|
+
: getNativeModule().editorInsertNode(this._editorId, nodeType);
|
|
722
|
+
}, { refreshSelectionAfterPreflight: true });
|
|
634
723
|
}
|
|
635
|
-
parseUpdateJson(json) {
|
|
724
|
+
parseUpdateJson(json, options) {
|
|
636
725
|
this.assertNotDestroyed();
|
|
637
|
-
return this.parseAndNoteUpdate(json);
|
|
726
|
+
return this.parseAndNoteUpdate(json, options);
|
|
638
727
|
}
|
|
639
728
|
noteUpdate(update) {
|
|
640
729
|
if (!update) {
|
|
@@ -648,17 +737,88 @@ class NativeEditorBridge {
|
|
|
648
737
|
this.invalidateContentCaches();
|
|
649
738
|
return;
|
|
650
739
|
}
|
|
740
|
+
this._hasSeenDocumentVersion = true;
|
|
651
741
|
if (update.documentVersion !== this._documentVersion) {
|
|
652
742
|
this._documentVersion = update.documentVersion;
|
|
653
743
|
this.invalidateContentCaches();
|
|
654
744
|
}
|
|
655
745
|
}
|
|
656
|
-
|
|
746
|
+
shouldRejectUpdate(update, json, options) {
|
|
747
|
+
if (!update) {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
if (typeof update.documentVersion !== 'number') {
|
|
751
|
+
return (options?.rejectVersionlessAfterDocumentVersion === true &&
|
|
752
|
+
react_native_1.Platform.OS === 'android' &&
|
|
753
|
+
this._hasSeenDocumentVersion);
|
|
754
|
+
}
|
|
755
|
+
if (react_native_1.Platform.OS === 'android') {
|
|
756
|
+
this._hasSeenDocumentVersion = true;
|
|
757
|
+
}
|
|
758
|
+
if (update.documentVersion < this._documentVersion) {
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
if (options?.rejectSameDocumentVersion === true) {
|
|
762
|
+
return (update.documentVersion === this._documentVersion &&
|
|
763
|
+
json === this._lastAcceptedUpdateJson);
|
|
764
|
+
}
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
parseAndNoteUpdate(json, options) {
|
|
657
768
|
let update = parseEditorUpdateJson(json, this._renderBlocksCache ?? undefined);
|
|
769
|
+
if (this.shouldRejectUpdate(update, json, options)) {
|
|
770
|
+
return null;
|
|
771
|
+
}
|
|
658
772
|
if (update?.renderPatch && !update.renderBlocks) {
|
|
659
|
-
|
|
773
|
+
json = getNativeModule().editorGetCurrentState(this._editorId);
|
|
774
|
+
update = parseEditorUpdateJson(json, this._renderBlocksCache ?? undefined);
|
|
775
|
+
if (this.shouldRejectUpdate(update, json, options)) {
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
660
778
|
}
|
|
661
779
|
this.noteUpdate(update);
|
|
780
|
+
if (update) {
|
|
781
|
+
this._lastAcceptedUpdateJson = json;
|
|
782
|
+
}
|
|
783
|
+
return update;
|
|
784
|
+
}
|
|
785
|
+
prepareForCommand() {
|
|
786
|
+
this._lastCommandBlocked = false;
|
|
787
|
+
this._lastCommandBlockedReason = null;
|
|
788
|
+
this._lastCommandPreflightUpdate = null;
|
|
789
|
+
const nativeModule = getNativeModule();
|
|
790
|
+
const prepareForCommand = nativeModule.editorPrepareForCommand;
|
|
791
|
+
if (typeof prepareForCommand !== 'function') {
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
794
|
+
const preparation = parseCommandPreparationJson(prepareForCommand(this._editorId));
|
|
795
|
+
if (preparation.updateJSON) {
|
|
796
|
+
this._lastCommandPreflightUpdate = this.parseAndNoteUpdate(preparation.updateJSON, {
|
|
797
|
+
rejectVersionlessAfterDocumentVersion: true,
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
if (!preparation.ready) {
|
|
801
|
+
this._lastCommandBlocked = true;
|
|
802
|
+
this._lastCommandBlockedReason = preparation.blockedReason ?? 'unknown';
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
runPreparedCommand(mutate, options) {
|
|
808
|
+
if (!this.prepareForCommand()) {
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
if (options?.cancelIfPreflightUpdated === true &&
|
|
812
|
+
this._lastCommandPreflightUpdate != null) {
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
if (options?.refreshSelectionAfterPreflight === true && react_native_1.Platform.OS === 'android') {
|
|
816
|
+
this.getSelection();
|
|
817
|
+
}
|
|
818
|
+
const update = this.parseAndNoteUpdate(mutate());
|
|
819
|
+
if (update) {
|
|
820
|
+
this._lastCommandPreflightUpdate = null;
|
|
821
|
+
}
|
|
662
822
|
return update;
|
|
663
823
|
}
|
|
664
824
|
invalidateContentCaches() {
|