@apollohg/react-native-prose-editor 0.3.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 +515 -39
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +58 -28
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +25 -0
- package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +232 -62
- 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/EditorToolbar.d.ts +26 -6
- package/dist/EditorToolbar.js +299 -65
- package/dist/NativeEditorBridge.d.ts +40 -1
- package/dist/NativeEditorBridge.js +184 -90
- package/dist/NativeRichTextEditor.d.ts +5 -1
- package/dist/NativeRichTextEditor.js +201 -78
- package/dist/YjsCollaboration.d.ts +2 -0
- package/dist/YjsCollaboration.js +142 -20
- package/dist/index.d.ts +1 -1
- package/dist/schemas.js +12 -0
- package/dist/useNativeEditor.d.ts +2 -0
- package/dist/useNativeEditor.js +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/EditorLayoutManager.swift +3 -3
- package/ios/Generated_editor_core.swift +87 -0
- package/ios/NativeEditorExpoView.swift +488 -178
- package/ios/NativeEditorModule.swift +25 -0
- package/ios/PositionBridge.swift +310 -75
- package/ios/RenderBridge.swift +362 -27
- package/ios/RichTextEditorView.swift +2001 -189
- package/ios/editor_coreFFI/editor_coreFFI.h +55 -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 +128 -0
|
@@ -17,6 +17,43 @@ const DEV_NATIVE_VIEW_KEY = __DEV__
|
|
|
17
17
|
: 'native-editor';
|
|
18
18
|
const LINK_TOOLBAR_ACTION_KEY = '__native-editor-link__';
|
|
19
19
|
const IMAGE_TOOLBAR_ACTION_KEY = '__native-editor-image__';
|
|
20
|
+
function mapToolbarChildForNative(item, activeState, editable, onRequestLink, onRequestImage) {
|
|
21
|
+
if (item.type === 'link') {
|
|
22
|
+
return {
|
|
23
|
+
type: 'action',
|
|
24
|
+
key: LINK_TOOLBAR_ACTION_KEY,
|
|
25
|
+
label: item.label,
|
|
26
|
+
icon: item.icon,
|
|
27
|
+
isActive: activeState.marks.link === true,
|
|
28
|
+
isDisabled: !editable || !onRequestLink || !activeState.allowedMarks.includes('link'),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (item.type === 'image') {
|
|
32
|
+
return {
|
|
33
|
+
type: 'action',
|
|
34
|
+
key: IMAGE_TOOLBAR_ACTION_KEY,
|
|
35
|
+
label: item.label,
|
|
36
|
+
icon: item.icon,
|
|
37
|
+
isActive: false,
|
|
38
|
+
isDisabled: !editable || !onRequestImage || !activeState.insertableNodes.includes(schemas_2.IMAGE_NODE_NAME),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return item;
|
|
42
|
+
}
|
|
43
|
+
function mapToolbarItemsForNative(items, activeState, editable, onRequestLink, onRequestImage) {
|
|
44
|
+
return items.map((item) => {
|
|
45
|
+
if (item.type === 'group') {
|
|
46
|
+
return {
|
|
47
|
+
...item,
|
|
48
|
+
items: item.items.map((child) => mapToolbarChildForNative(child, activeState, editable, onRequestLink, onRequestImage)),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (item.type === 'separator') {
|
|
52
|
+
return item;
|
|
53
|
+
}
|
|
54
|
+
return mapToolbarChildForNative(item, activeState, editable, onRequestLink, onRequestImage);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
20
57
|
function isImageDataUrl(value) {
|
|
21
58
|
return /^data:image\//i.test(value.trim());
|
|
22
59
|
}
|
|
@@ -62,9 +99,43 @@ function serializeRemoteSelections(remoteSelections) {
|
|
|
62
99
|
if (!remoteSelections || remoteSelections.length === 0) {
|
|
63
100
|
return undefined;
|
|
64
101
|
}
|
|
65
|
-
return
|
|
102
|
+
return stringifyCachedJson(remoteSelections);
|
|
66
103
|
}
|
|
67
|
-
|
|
104
|
+
const serializedJsonCache = new WeakMap();
|
|
105
|
+
function stringifyCachedJson(value) {
|
|
106
|
+
if (value != null && typeof value === 'object') {
|
|
107
|
+
const cached = serializedJsonCache.get(value);
|
|
108
|
+
if (cached != null) {
|
|
109
|
+
return cached;
|
|
110
|
+
}
|
|
111
|
+
const serialized = JSON.stringify(value);
|
|
112
|
+
serializedJsonCache.set(value, serialized);
|
|
113
|
+
return serialized;
|
|
114
|
+
}
|
|
115
|
+
return JSON.stringify(value);
|
|
116
|
+
}
|
|
117
|
+
function useSerializedValue(value, serialize, revision) {
|
|
118
|
+
const cacheRef = (0, react_1.useRef)(null);
|
|
119
|
+
const hasRevision = revision !== undefined;
|
|
120
|
+
const cached = cacheRef.current;
|
|
121
|
+
if (cached) {
|
|
122
|
+
if (hasRevision && cached.hasRevision && Object.is(cached.revision, revision)) {
|
|
123
|
+
return cached.serialized;
|
|
124
|
+
}
|
|
125
|
+
if (Object.is(cached.value, value) && cached.hasRevision === hasRevision) {
|
|
126
|
+
return cached.serialized;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const serialized = value == null ? undefined : serialize(value);
|
|
130
|
+
cacheRef.current = {
|
|
131
|
+
value,
|
|
132
|
+
revision,
|
|
133
|
+
hasRevision,
|
|
134
|
+
serialized,
|
|
135
|
+
};
|
|
136
|
+
return serialized;
|
|
137
|
+
}
|
|
138
|
+
exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEditor({ initialContent, initialJSON, value, valueJSON, valueJSONRevision, schema, placeholder, editable = true, maxLength, autoFocus = false, heightBehavior = 'autoGrow', showToolbar = true, toolbarPlacement = 'keyboard', toolbarItems = EditorToolbar_1.DEFAULT_EDITOR_TOOLBAR_ITEMS, onToolbarAction, onRequestLink, onRequestImage, onContentChange, onContentChangeJSON, onSelectionChange, onActiveStateChange, onHistoryStateChange, onFocus, onBlur, style, containerStyle, theme, addons, remoteSelections, allowBase64Images = false, allowImageResizing = true, }, ref) {
|
|
68
139
|
const bridgeRef = (0, react_1.useRef)(null);
|
|
69
140
|
const nativeViewRef = (0, react_1.useRef)(null);
|
|
70
141
|
const [isReady, setIsReady] = (0, react_1.useState)(false);
|
|
@@ -92,7 +163,9 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
92
163
|
// Selection and rendered text length refs (non-rendering state)
|
|
93
164
|
const selectionRef = (0, react_1.useRef)({ type: 'text', anchor: 0, head: 0 });
|
|
94
165
|
const renderedTextLengthRef = (0, react_1.useRef)(0);
|
|
166
|
+
const documentVersionRef = (0, react_1.useRef)(null);
|
|
95
167
|
const toolbarRef = (0, react_1.useRef)(null);
|
|
168
|
+
const toolbarItemsSerializationCacheRef = (0, react_1.useRef)(null);
|
|
96
169
|
// Stable callback refs to avoid re-renders
|
|
97
170
|
const onContentChangeRef = (0, react_1.useRef)(onContentChange);
|
|
98
171
|
onContentChangeRef.current = onContentChange;
|
|
@@ -115,6 +188,12 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
115
188
|
: undefined;
|
|
116
189
|
const mentionSuggestionsByKeyRef = (0, react_1.useRef)(new Map());
|
|
117
190
|
mentionSuggestionsByKeyRef.current = new Map((addons?.mentions?.suggestions ?? []).map((suggestion) => [suggestion.key, suggestion]));
|
|
191
|
+
const serializedSchemaJson = useSerializedValue(addons?.mentions != null ? (0, addons_1.withMentionsSchema)(schema ?? schemas_1.tiptapSchema) : schema, (nextSchema) => stringifyCachedJson(nextSchema));
|
|
192
|
+
const serializedInitialJson = useSerializedValue(initialJSON, stringifyCachedJson);
|
|
193
|
+
const serializedValueJson = useSerializedValue(valueJSON, stringifyCachedJson, valueJSONRevision);
|
|
194
|
+
const themeJson = useSerializedValue(theme, EditorTheme_1.serializeEditorTheme);
|
|
195
|
+
const addonsJson = useSerializedValue(addons, addons_1.serializeEditorAddons);
|
|
196
|
+
const remoteSelectionsJson = useSerializedValue(remoteSelections, (selections) => serializeRemoteSelections(selections));
|
|
118
197
|
const syncStateFromUpdate = (0, react_1.useCallback)((update) => {
|
|
119
198
|
if (!update)
|
|
120
199
|
return;
|
|
@@ -122,6 +201,44 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
122
201
|
setHistoryState(update.historyState);
|
|
123
202
|
selectionRef.current = update.selection;
|
|
124
203
|
renderedTextLengthRef.current = computeRenderedTextLength(update.renderElements);
|
|
204
|
+
if (typeof update.documentVersion === 'number') {
|
|
205
|
+
documentVersionRef.current = update.documentVersion;
|
|
206
|
+
}
|
|
207
|
+
}, []);
|
|
208
|
+
const syncSelectionStateFromUpdate = (0, react_1.useCallback)((update) => {
|
|
209
|
+
if (!update)
|
|
210
|
+
return;
|
|
211
|
+
setActiveState(update.activeState);
|
|
212
|
+
setHistoryState(update.historyState);
|
|
213
|
+
selectionRef.current = update.selection;
|
|
214
|
+
if (typeof update.documentVersion === 'number') {
|
|
215
|
+
documentVersionRef.current = update.documentVersion;
|
|
216
|
+
}
|
|
217
|
+
}, []);
|
|
218
|
+
const emitContentCallbacksForUpdate = (0, react_1.useCallback)((update, previousDocumentVersion) => {
|
|
219
|
+
if (!update || !bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
220
|
+
return;
|
|
221
|
+
const wantsHtml = typeof onContentChangeRef.current === 'function';
|
|
222
|
+
const wantsJson = typeof onContentChangeJSONRef.current === 'function';
|
|
223
|
+
if (!wantsHtml && !wantsJson)
|
|
224
|
+
return;
|
|
225
|
+
if (previousDocumentVersion != null &&
|
|
226
|
+
typeof update.documentVersion === 'number' &&
|
|
227
|
+
update.documentVersion === previousDocumentVersion) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (wantsHtml && wantsJson) {
|
|
231
|
+
const snapshot = bridgeRef.current.getContentSnapshot();
|
|
232
|
+
onContentChangeRef.current?.(snapshot.html);
|
|
233
|
+
onContentChangeJSONRef.current?.(snapshot.json);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (wantsHtml) {
|
|
237
|
+
onContentChangeRef.current?.(bridgeRef.current.getHtml());
|
|
238
|
+
}
|
|
239
|
+
if (wantsJson) {
|
|
240
|
+
onContentChangeJSONRef.current?.(bridgeRef.current.getJson());
|
|
241
|
+
}
|
|
125
242
|
}, []);
|
|
126
243
|
// Warn if both value and valueJSON are set
|
|
127
244
|
if (__DEV__ && value != null && valueJSON != null) {
|
|
@@ -129,13 +246,8 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
129
246
|
'Only value will be used.');
|
|
130
247
|
}
|
|
131
248
|
const runAndApply = (0, react_1.useCallback)((mutate, options) => {
|
|
249
|
+
const previousDocumentVersion = documentVersionRef.current;
|
|
132
250
|
const preservedSelection = options?.preserveLiveTextSelection === true ? selectionRef.current : null;
|
|
133
|
-
const shouldCheckForNoopNativeApply = options?.skipNativeApplyIfContentUnchanged === true &&
|
|
134
|
-
bridgeRef.current != null &&
|
|
135
|
-
!bridgeRef.current.isDestroyed;
|
|
136
|
-
const htmlBefore = shouldCheckForNoopNativeApply
|
|
137
|
-
? bridgeRef.current.getHtml()
|
|
138
|
-
: null;
|
|
139
251
|
const update = mutate();
|
|
140
252
|
if (!update)
|
|
141
253
|
return null;
|
|
@@ -151,10 +263,10 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
151
263
|
head: preservedSelection.head,
|
|
152
264
|
};
|
|
153
265
|
}
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (!
|
|
266
|
+
const contentChanged = previousDocumentVersion == null ||
|
|
267
|
+
typeof update.documentVersion !== 'number' ||
|
|
268
|
+
update.documentVersion !== previousDocumentVersion;
|
|
269
|
+
if (!options?.skipNativeApplyIfContentUnchanged || contentChanged) {
|
|
158
270
|
const updateJson = JSON.stringify(update);
|
|
159
271
|
if (react_native_1.Platform.OS === 'android') {
|
|
160
272
|
setPendingNativeUpdate((current) => ({
|
|
@@ -180,23 +292,18 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
180
292
|
onActiveStateChangeRef.current?.(update.activeState);
|
|
181
293
|
onHistoryStateChangeRef.current?.(update.historyState);
|
|
182
294
|
if (!options?.suppressContentCallbacks) {
|
|
183
|
-
|
|
184
|
-
onContentChangeRef.current(bridgeRef.current.getHtml());
|
|
185
|
-
}
|
|
186
|
-
if (onContentChangeJSONRef.current && bridgeRef.current) {
|
|
187
|
-
onContentChangeJSONRef.current(bridgeRef.current.getJson());
|
|
188
|
-
}
|
|
295
|
+
emitContentCallbacksForUpdate(update, previousDocumentVersion);
|
|
189
296
|
}
|
|
190
297
|
onSelectionChangeRef.current?.(update.selection);
|
|
191
298
|
return update;
|
|
192
|
-
}, [syncStateFromUpdate]);
|
|
299
|
+
}, [emitContentCallbacksForUpdate, syncStateFromUpdate]);
|
|
193
300
|
(0, react_1.useEffect)(() => {
|
|
194
|
-
const
|
|
195
|
-
?
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
301
|
+
const bridgeConfig = maxLength != null || serializedSchemaJson || allowBase64Images
|
|
302
|
+
? {
|
|
303
|
+
maxLength,
|
|
304
|
+
schemaJson: serializedSchemaJson,
|
|
305
|
+
allowBase64Images,
|
|
306
|
+
}
|
|
200
307
|
: undefined;
|
|
201
308
|
const bridge = NativeEditorBridge_1.NativeEditorBridge.create(bridgeConfig);
|
|
202
309
|
bridgeRef.current = bridge;
|
|
@@ -205,11 +312,11 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
205
312
|
if (value != null) {
|
|
206
313
|
bridge.setHtml(value);
|
|
207
314
|
}
|
|
208
|
-
else if (
|
|
209
|
-
bridge.
|
|
315
|
+
else if (serializedValueJson != null) {
|
|
316
|
+
bridge.setJsonString(serializedValueJson);
|
|
210
317
|
}
|
|
211
|
-
else if (
|
|
212
|
-
bridge.
|
|
318
|
+
else if (serializedInitialJson != null) {
|
|
319
|
+
bridge.setJsonString(serializedInitialJson);
|
|
213
320
|
}
|
|
214
321
|
else if (initialContent) {
|
|
215
322
|
bridge.setHtml(initialContent);
|
|
@@ -224,7 +331,12 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
224
331
|
setIsReady(false);
|
|
225
332
|
};
|
|
226
333
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
227
|
-
}, [
|
|
334
|
+
}, [
|
|
335
|
+
maxLength,
|
|
336
|
+
syncStateFromUpdate,
|
|
337
|
+
allowBase64Images,
|
|
338
|
+
serializedSchemaJson,
|
|
339
|
+
]);
|
|
228
340
|
(0, react_1.useEffect)(() => {
|
|
229
341
|
if (value == null)
|
|
230
342
|
return;
|
|
@@ -239,19 +351,18 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
239
351
|
});
|
|
240
352
|
}, [value, runAndApply]);
|
|
241
353
|
(0, react_1.useEffect)(() => {
|
|
242
|
-
if (
|
|
354
|
+
if (serializedValueJson == null || value != null)
|
|
243
355
|
return;
|
|
244
356
|
if (!bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
245
357
|
return;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (JSON.stringify(currentJson) === JSON.stringify(valueJSON))
|
|
358
|
+
const currentJson = bridgeRef.current.getJsonString();
|
|
359
|
+
if (currentJson === serializedValueJson)
|
|
249
360
|
return;
|
|
250
|
-
runAndApply(() => bridgeRef.current.
|
|
361
|
+
runAndApply(() => bridgeRef.current.replaceJsonString(serializedValueJson), {
|
|
251
362
|
suppressContentCallbacks: true,
|
|
252
363
|
preserveLiveTextSelection: true,
|
|
253
364
|
});
|
|
254
|
-
}, [
|
|
365
|
+
}, [serializedValueJson, value, runAndApply]);
|
|
255
366
|
const updateToolbarFrame = (0, react_1.useCallback)(() => {
|
|
256
367
|
const toolbar = toolbarRef.current;
|
|
257
368
|
if (!toolbar) {
|
|
@@ -286,28 +397,24 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
286
397
|
if (!bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
287
398
|
return;
|
|
288
399
|
try {
|
|
289
|
-
const
|
|
400
|
+
const previousDocumentVersion = documentVersionRef.current;
|
|
401
|
+
const update = bridgeRef.current.parseUpdateJson(event.nativeEvent.updateJson);
|
|
290
402
|
if (!update)
|
|
291
403
|
return;
|
|
292
404
|
syncStateFromUpdate(update);
|
|
293
405
|
onActiveStateChangeRef.current?.(update.activeState);
|
|
294
406
|
onHistoryStateChangeRef.current?.(update.historyState);
|
|
295
|
-
|
|
296
|
-
onContentChangeRef.current(bridgeRef.current.getHtml());
|
|
297
|
-
}
|
|
298
|
-
if (onContentChangeJSONRef.current) {
|
|
299
|
-
onContentChangeJSONRef.current(bridgeRef.current.getJson());
|
|
300
|
-
}
|
|
407
|
+
emitContentCallbacksForUpdate(update, previousDocumentVersion);
|
|
301
408
|
onSelectionChangeRef.current?.(update.selection);
|
|
302
409
|
}
|
|
303
410
|
catch {
|
|
304
411
|
// Invalid JSON from native — skip
|
|
305
412
|
}
|
|
306
|
-
}, [syncStateFromUpdate]);
|
|
413
|
+
}, [emitContentCallbacksForUpdate, syncStateFromUpdate]);
|
|
307
414
|
const handleSelectionChange = (0, react_1.useCallback)((event) => {
|
|
308
415
|
if (!bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
309
416
|
return;
|
|
310
|
-
const { anchor, head } = event.nativeEvent;
|
|
417
|
+
const { anchor, head, stateJson } = event.nativeEvent;
|
|
311
418
|
let selection;
|
|
312
419
|
if (anchor === 0 &&
|
|
313
420
|
head >= renderedTextLengthRef.current &&
|
|
@@ -318,8 +425,19 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
318
425
|
selection = { type: 'text', anchor, head };
|
|
319
426
|
}
|
|
320
427
|
bridgeRef.current.updateSelectionFromNative(anchor, head);
|
|
321
|
-
|
|
322
|
-
|
|
428
|
+
let currentState = null;
|
|
429
|
+
if (typeof stateJson === 'string' && stateJson.length > 0) {
|
|
430
|
+
try {
|
|
431
|
+
currentState = bridgeRef.current.parseUpdateJson(stateJson);
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
currentState = bridgeRef.current.getSelectionState();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
currentState = bridgeRef.current.getSelectionState();
|
|
439
|
+
}
|
|
440
|
+
syncSelectionStateFromUpdate(currentState);
|
|
323
441
|
const nextSelection = selection.type === 'all' ? selection : (currentState?.selection ?? selection);
|
|
324
442
|
selectionRef.current = nextSelection;
|
|
325
443
|
if (currentState) {
|
|
@@ -327,7 +445,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
327
445
|
onHistoryStateChangeRef.current?.(currentState.historyState);
|
|
328
446
|
}
|
|
329
447
|
onSelectionChangeRef.current?.(nextSelection);
|
|
330
|
-
}, [
|
|
448
|
+
}, [syncSelectionStateFromUpdate]);
|
|
331
449
|
const handleFocusChange = (0, react_1.useCallback)((event) => {
|
|
332
450
|
const { isFocused: focused } = event.nativeEvent;
|
|
333
451
|
setIsFocused(focused);
|
|
@@ -481,6 +599,9 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
481
599
|
toggleBlockquote() {
|
|
482
600
|
runAndApply(() => bridgeRef.current?.toggleBlockquote() ?? null);
|
|
483
601
|
},
|
|
602
|
+
toggleHeading(level) {
|
|
603
|
+
runAndApply(() => bridgeRef.current?.toggleHeading(level) ?? null);
|
|
604
|
+
},
|
|
484
605
|
toggleList(listType) {
|
|
485
606
|
runAndApply(() => bridgeRef.current?.toggleList(listType) ?? null);
|
|
486
607
|
},
|
|
@@ -545,35 +666,37 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
545
666
|
}), [insertImage, runAndApply]);
|
|
546
667
|
if (!isReady)
|
|
547
668
|
return null;
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
669
|
+
const isLinkActive = activeState.marks.link === true;
|
|
670
|
+
const allowsLink = activeState.allowedMarks.includes('link');
|
|
671
|
+
const canInsertImage = activeState.insertableNodes.includes(schemas_2.IMAGE_NODE_NAME);
|
|
672
|
+
const canRequestLink = typeof onRequestLink === 'function';
|
|
673
|
+
const canRequestImage = typeof onRequestImage === 'function';
|
|
674
|
+
const cachedToolbarItems = toolbarItemsSerializationCacheRef.current;
|
|
675
|
+
let toolbarItemsJson;
|
|
676
|
+
if (cachedToolbarItems?.toolbarItems === toolbarItems &&
|
|
677
|
+
cachedToolbarItems.editable === editable &&
|
|
678
|
+
cachedToolbarItems.isLinkActive === isLinkActive &&
|
|
679
|
+
cachedToolbarItems.allowsLink === allowsLink &&
|
|
680
|
+
cachedToolbarItems.canRequestLink === canRequestLink &&
|
|
681
|
+
cachedToolbarItems.canRequestImage === canRequestImage &&
|
|
682
|
+
cachedToolbarItems.canInsertImage === canInsertImage) {
|
|
683
|
+
toolbarItemsJson = cachedToolbarItems.serialized;
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
const mappedItems = mapToolbarItemsForNative(toolbarItems, activeState, editable, onRequestLink, onRequestImage);
|
|
687
|
+
toolbarItemsJson = stringifyCachedJson(mappedItems);
|
|
688
|
+
toolbarItemsSerializationCacheRef.current = {
|
|
689
|
+
toolbarItems,
|
|
690
|
+
editable,
|
|
691
|
+
isLinkActive,
|
|
692
|
+
allowsLink,
|
|
693
|
+
canRequestLink,
|
|
694
|
+
canRequestImage,
|
|
695
|
+
canInsertImage,
|
|
696
|
+
mappedItems,
|
|
697
|
+
serialized: toolbarItemsJson,
|
|
698
|
+
};
|
|
699
|
+
}
|
|
577
700
|
const usesNativeKeyboardToolbar = toolbarPlacement === 'keyboard' && (react_native_1.Platform.OS === 'ios' || react_native_1.Platform.OS === 'android');
|
|
578
701
|
const shouldRenderJsToolbar = !usesNativeKeyboardToolbar && showToolbar && editable;
|
|
579
702
|
const inlineToolbarChrome = {
|
|
@@ -610,7 +733,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
610
733
|
: null,
|
|
611
734
|
], onLayout: updateToolbarFrame, children: (0, jsx_runtime_1.jsx)(EditorToolbar_1.EditorToolbar, { activeState: activeState, historyState: historyState, toolbarItems: toolbarItems, theme: theme?.toolbar, showTopBorder: false, onToggleMark: (mark) => runAndApply(() => bridgeRef.current?.toggleMark(mark) ?? null, {
|
|
612
735
|
skipNativeApplyIfContentUnchanged: true,
|
|
613
|
-
}), onToggleListType: (listType) => runAndApply(() => bridgeRef.current?.toggleList(listType) ?? null), onToggleBlockquote: () => runAndApply(() => bridgeRef.current?.toggleBlockquote() ?? null), onInsertNodeType: (nodeType) => runAndApply(() => bridgeRef.current?.insertNode(nodeType) ?? null), onRunCommand: (command) => {
|
|
736
|
+
}), onToggleListType: (listType) => runAndApply(() => bridgeRef.current?.toggleList(listType) ?? null), onToggleHeading: (level) => runAndApply(() => bridgeRef.current?.toggleHeading(level) ?? null), onToggleBlockquote: () => runAndApply(() => bridgeRef.current?.toggleBlockquote() ?? null), onInsertNodeType: (nodeType) => runAndApply(() => bridgeRef.current?.insertNode(nodeType) ?? null), onRunCommand: (command) => {
|
|
614
737
|
switch (command) {
|
|
615
738
|
case 'indentList':
|
|
616
739
|
runAndApply(() => bridgeRef.current?.indentListItem() ?? null);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type CollaborationPeer, type DocumentJSON, type EncodedCollaborationStateInput, type Selection } from './NativeEditorBridge';
|
|
2
2
|
import type { RemoteSelectionDecoration } from './NativeRichTextEditor';
|
|
3
|
+
import type { SchemaDefinition } from './schemas';
|
|
3
4
|
export type YjsTransportStatus = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'error';
|
|
4
5
|
export interface YjsRetryContext {
|
|
5
6
|
attempt: number;
|
|
@@ -35,6 +36,7 @@ export interface YjsCollaborationOptions {
|
|
|
35
36
|
connect?: boolean;
|
|
36
37
|
retryIntervalMs?: YjsRetryInterval | false;
|
|
37
38
|
fragmentName?: string;
|
|
39
|
+
schema?: SchemaDefinition;
|
|
38
40
|
initialDocumentJson?: DocumentJSON;
|
|
39
41
|
initialEncodedState?: EncodedCollaborationStateInput;
|
|
40
42
|
localAwareness: LocalAwarenessUser;
|