@apollohg/react-native-rich-text-editor 2.0.0 → 2.0.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 +1 -1
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/android/build.gradle +3 -0
- package/app.plugin.js +42 -39
- package/dist/AtomHost.js +18 -11
- package/dist/EditorAddon.js +4 -2
- package/dist/EditorAddonNormalization.js +17 -9
- package/dist/EditorMentionThemeNormalization.js +6 -3
- package/dist/EditorMentionThemeValidation.js +28 -11
- package/dist/EditorStyleSheetNormalization.js +84 -37
- package/dist/EditorTheme.d.ts +4 -0
- package/dist/EditorTheme.js +5 -3
- package/dist/EditorToolbarComponent.d.ts +1 -1
- package/dist/EditorToolbarComponent.js +1 -1
- package/dist/EditorToolbarRegistry.js +5 -5
- package/dist/EditorToolbarTypes.d.ts +12 -12
- package/dist/EditorToolbarVisuals.js +1 -1
- package/dist/ExternalTextComposition.js +89 -43
- package/dist/ImageLoadingPolicy.d.ts +1 -1
- package/dist/NativeEditorBoundaryError.d.ts +7 -7
- package/dist/NativeEditorBoundaryError.js +9 -5
- package/dist/NativeEditorCollaborationTransport.d.ts +7 -7
- package/dist/NativeEditorCollaborationTransport.js +28 -15
- package/dist/NativeEditorCreateConfig.js +11 -6
- package/dist/NativeEditorCreateJson.js +51 -25
- package/dist/NativeEditorCreateValidation.js +11 -6
- package/dist/NativeEditorDocumentBridge.d.ts +1 -1
- package/dist/NativeEditorDocumentBridge.js +32 -18
- package/dist/NativeEditorDocumentHandle.d.ts +4 -4
- package/dist/NativeEditorDocumentHandle.js +1 -1
- package/dist/NativeEditorLocalAwareness.js +25 -13
- package/dist/NativeEditorNativeModule.d.ts +8 -1
- package/dist/NativeEditorNativeModule.js +7 -5
- package/dist/NativeEditorRenderNormalization.js +66 -25
- package/dist/NativeEditorResultNormalization.js +53 -29
- package/dist/NativeEditorTypes.d.ts +10 -10
- package/dist/NativeEditorV2Decimal.js +6 -3
- package/dist/NativeProseViewer.js +11 -3
- package/dist/ResourceLimits.d.ts +2 -2
- package/dist/ResourceLimits.js +7 -2
- package/dist/RichTextEditor.js +1 -1
- package/dist/RichTextEditorNativeTypes.d.ts +1 -1
- package/dist/RichTextEditorNativeView.d.ts +1 -3
- package/dist/RichTextEditorSerialization.js +24 -14
- package/dist/RichTextEditorTypes.d.ts +1 -1
- package/dist/YjsCollaboration.d.ts +7 -7
- package/dist/YjsCollaboration.js +42 -26
- package/dist/addons.d.ts +4 -4
- package/dist/addons.js +3 -3
- package/dist/atomInstances.js +6 -3
- package/dist/atomUpdates.js +7 -4
- package/dist/atoms.js +20 -12
- package/dist/attributeValidation.js +55 -24
- package/dist/contentExpression.js +80 -42
- package/dist/listMarkers.d.ts +1 -1
- package/dist/listMarkers.js +1 -1
- package/dist/schemaDefinition.d.ts +1 -1
- package/dist/schemaDefinition.js +23 -14
- package/dist/schemaDocument.js +1 -1
- package/dist/schemaDocumentConstruction.js +25 -14
- package/dist/schemaNormalization.js +51 -29
- package/dist/schemaPresets.d.ts +1 -1
- package/dist/schemaPresets.js +9 -4
- package/dist/schemaResolution.js +46 -25
- package/dist/specs/PreparedProseViewerNativeComponent.d.ts +4 -0
- package/dist/useEditorToolbarInteractions.d.ts +2 -2
- package/dist/useEditorToolbarInteractions.js +45 -23
- package/dist/useEditorToolbarItems.d.ts +1 -1
- package/dist/useEditorToolbarItems.js +4 -3
- package/dist/useEditorToolbarPresentation.d.ts +3 -3
- package/dist/useEditorToolbarPresentation.js +4 -4
- package/dist/useEditorToolbarState.d.ts +1 -1
- package/dist/useEditorToolbarState.js +1 -1
- package/dist/useFocusPreservingFrames.js +11 -8
- package/dist/useNativeEditor.d.ts +1 -1
- package/dist/useNativeEditor.js +21 -11
- package/dist/useRichTextEditorCommands.d.ts +2 -2
- package/dist/useRichTextEditorCommands.js +59 -29
- package/dist/useRichTextEditorEvents.d.ts +3 -3
- package/dist/useRichTextEditorEvents.js +75 -32
- package/dist/useRichTextEditorMentions.d.ts +4 -4
- package/dist/useRichTextEditorMentions.js +48 -29
- package/dist/useRichTextEditorPresentation.d.ts +4 -4
- package/dist/useRichTextEditorPresentation.js +25 -23
- package/dist/useRichTextEditorState.d.ts +3 -3
- package/dist/useRichTextEditorState.js +20 -15
- package/dist/useRichTextEditorUpdates.d.ts +1 -1
- package/dist/useRichTextEditorUpdates.js +99 -41
- package/dist/useViewerAtoms.js +63 -36
- 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/package.json +11 -3
- package/react-native.config.js +1 -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/libeditor_core.so +0 -0
- package/rust/android/x86_64/libeditor_core.so +0 -0
- package/src/AtomHost.tsx +58 -28
- package/src/EditorAddon.ts +18 -8
- package/src/EditorAddonNormalization.ts +40 -11
- package/src/EditorMentionThemeNormalization.ts +15 -4
- package/src/EditorMentionThemeValidation.ts +51 -14
- package/src/EditorStyleSheet.ts +1 -0
- package/src/EditorStyleSheetNormalization.ts +195 -64
- package/src/EditorTheme.ts +13 -5
- package/src/EditorToolbarComponent.tsx +2 -1
- package/src/EditorToolbarRegistry.tsx +23 -13
- package/src/EditorToolbarTypes.tsx +12 -12
- package/src/EditorToolbarVisuals.tsx +6 -3
- package/src/EditorUpdateRevision.ts +1 -0
- package/src/ExternalTextComposition.ts +259 -49
- package/src/ImageLoadingPolicy.ts +3 -1
- package/src/NativeEditorBoundaryError.ts +38 -11
- package/src/NativeEditorCollaborationTransport.ts +108 -27
- package/src/NativeEditorCreateConfig.ts +92 -19
- package/src/NativeEditorCreateJson.ts +163 -26
- package/src/NativeEditorCreateValidation.ts +33 -12
- package/src/NativeEditorDocumentBridge.ts +111 -19
- package/src/NativeEditorDocumentHandle.ts +9 -4
- package/src/NativeEditorLocalAwareness.ts +70 -15
- package/src/NativeEditorNativeModule.ts +13 -9
- package/src/NativeEditorRenderNormalization.ts +128 -29
- package/src/NativeEditorResultNormalization.ts +143 -32
- package/src/NativeEditorTypes.ts +12 -11
- package/src/NativeEditorV2Decimal.ts +12 -3
- package/src/NativeProseViewer.tsx +46 -16
- package/src/ResourceLimits.ts +15 -8
- package/src/RichTextEditor.tsx +3 -1
- package/src/RichTextEditorNativeTypes.tsx +1 -1
- package/src/RichTextEditorNativeView.tsx +2 -8
- package/src/RichTextEditorSerialization.tsx +70 -24
- package/src/RichTextEditorTypes.tsx +1 -1
- package/src/YjsCollaboration.ts +143 -47
- package/src/addons.ts +12 -8
- package/src/atomInstances.ts +25 -5
- package/src/atomUpdates.ts +23 -6
- package/src/atoms.ts +85 -18
- package/src/attributeValidation.ts +97 -33
- package/src/contentExpression.ts +218 -63
- package/src/listMarkers.ts +1 -1
- package/src/schemaDefinition.ts +82 -20
- package/src/schemaDocument.ts +15 -1
- package/src/schemaDocumentConstruction.ts +80 -16
- package/src/schemaNormalization.ts +155 -41
- package/src/schemaPresets.ts +37 -31
- package/src/schemaResolution.ts +126 -27
- package/src/specs/PreparedProseViewerNativeComponent.ts +4 -2
- package/src/useEditorToolbarInteractions.tsx +85 -36
- package/src/useEditorToolbarItems.tsx +50 -22
- package/src/useEditorToolbarPresentation.tsx +81 -58
- package/src/useEditorToolbarState.tsx +8 -6
- package/src/useFocusPreservingFrames.ts +43 -15
- package/src/useNativeEditor.ts +111 -51
- package/src/useRichTextEditorCommands.tsx +127 -54
- package/src/useRichTextEditorEvents.tsx +144 -42
- package/src/useRichTextEditorMentions.tsx +118 -37
- package/src/useRichTextEditorPresentation.tsx +128 -114
- package/src/useRichTextEditorState.tsx +76 -40
- package/src/useRichTextEditorUpdates.tsx +207 -56
- package/src/useViewerAtoms.tsx +162 -66
|
@@ -27,21 +27,34 @@ export function normalizeNativeEditorV2Result<T>(
|
|
|
27
27
|
raw: unknown,
|
|
28
28
|
normalizeValue: (value: unknown) => T | null
|
|
29
29
|
): NativeEditorResult<T> | null {
|
|
30
|
-
if (!isPlainRecord(raw))
|
|
30
|
+
if (!isPlainRecord(raw)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
31
34
|
const hasValue = raw.value !== null && raw.value !== undefined;
|
|
32
35
|
const hasError = raw.error !== null && raw.error !== undefined;
|
|
33
|
-
|
|
36
|
+
|
|
37
|
+
if (hasValue === hasError) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
34
41
|
if (hasError) {
|
|
35
42
|
const error = normalizeNativeEditorV2Error({ error: raw.error });
|
|
43
|
+
|
|
36
44
|
return error == null ? null : { ok: false, error };
|
|
37
45
|
}
|
|
46
|
+
|
|
38
47
|
const value = normalizeValue(raw.value);
|
|
48
|
+
|
|
39
49
|
return value == null ? null : { ok: true, value };
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
/** Parse a JSON-string result value; anything else is a contract violation. */
|
|
43
53
|
export function parseNativeEditorV2JsonValue(value: unknown): unknown | null {
|
|
44
|
-
if (typeof value !== 'string' || value === '')
|
|
54
|
+
if (typeof value !== 'string' || value === '') {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
try {
|
|
46
59
|
return JSON.parse(value) as unknown;
|
|
47
60
|
} catch {
|
|
@@ -54,11 +67,18 @@ export function parseNativeEditorV2JsonValue(value: unknown): unknown | null {
|
|
|
54
67
|
* purpose: the frozen v2 contract moves bytes as bytes.
|
|
55
68
|
*/
|
|
56
69
|
export function normalizeNativeEditorV2Bytes(value: unknown): Uint8Array | null {
|
|
57
|
-
if (value instanceof Uint8Array)
|
|
70
|
+
if (value instanceof Uint8Array) {
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
|
|
58
74
|
if (ArrayBuffer.isView(value)) {
|
|
59
75
|
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
60
76
|
}
|
|
61
|
-
|
|
77
|
+
|
|
78
|
+
if (value instanceof ArrayBuffer) {
|
|
79
|
+
return new Uint8Array(value);
|
|
80
|
+
}
|
|
81
|
+
|
|
62
82
|
return null;
|
|
63
83
|
}
|
|
64
84
|
|
|
@@ -96,9 +116,11 @@ export function nativeEditorV2U32(value: unknown): number | null {
|
|
|
96
116
|
/** Require one exact JavaScript/platform u32 value at the v2 boundary. */
|
|
97
117
|
export function requireNativeEditorV2U32(value: unknown, field: string): number {
|
|
98
118
|
const normalized = nativeEditorV2U32(value);
|
|
119
|
+
|
|
99
120
|
if (normalized == null) {
|
|
100
121
|
throw invalidV2RequestError(`NativeEditorBridge: invalid u32 ${field}`);
|
|
101
122
|
}
|
|
123
|
+
|
|
102
124
|
return normalized;
|
|
103
125
|
}
|
|
104
126
|
|
|
@@ -106,7 +128,7 @@ export function optionalBoolean(value: unknown): boolean | null {
|
|
|
106
128
|
return typeof value === 'boolean' ? value : null;
|
|
107
129
|
}
|
|
108
130
|
|
|
109
|
-
export const V2_DOCUMENT_STATES = ['LocalReady', 'AwaitRemote', 'RoomReady'] as const;
|
|
131
|
+
export const V2_DOCUMENT_STATES = [ 'LocalReady', 'AwaitRemote', 'RoomReady' ] as const;
|
|
110
132
|
|
|
111
133
|
/**
|
|
112
134
|
* Readiness of the engine document. `AwaitRemote` is a room document still
|
|
@@ -129,7 +151,7 @@ export const V2_TRANSPORT_STATES = [
|
|
|
129
151
|
/** Raw transport lifecycle. `YjsTransportStatus` is the friendlier projection of it. */
|
|
130
152
|
export type NativeEditorTransportState = (typeof V2_TRANSPORT_STATES)[number];
|
|
131
153
|
|
|
132
|
-
export const V2_RENDER_STATES = ['Loading', 'Ready'] as const;
|
|
154
|
+
export const V2_RENDER_STATES = [ 'Loading', 'Ready' ] as const;
|
|
133
155
|
|
|
134
156
|
/** Whether the engine has a render snapshot to draw. */
|
|
135
157
|
export type NativeEditorRenderState = (typeof V2_RENDER_STATES)[number];
|
|
@@ -170,7 +192,11 @@ export function normalizeNativeEditorV2StateValue(
|
|
|
170
192
|
value: unknown
|
|
171
193
|
): NativeEditorState | null {
|
|
172
194
|
const parsed = typeof value === 'string' ? parseNativeEditorV2JsonValue(value) : value;
|
|
173
|
-
|
|
195
|
+
|
|
196
|
+
if (!isPlainRecord(parsed)) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
174
200
|
const documentState = whitelisted(parsed.documentState, V2_DOCUMENT_STATES);
|
|
175
201
|
const transportState = whitelisted(parsed.transportState, V2_TRANSPORT_STATES);
|
|
176
202
|
const renderState = whitelisted(parsed.renderState, V2_RENDER_STATES);
|
|
@@ -179,6 +205,7 @@ export function normalizeNativeEditorV2StateValue(
|
|
|
179
205
|
const stateRevision = normalizeRevisionField(parsed, 'stateRevision');
|
|
180
206
|
const canUndo = optionalBoolean(parsed.canUndo);
|
|
181
207
|
const canRedo = optionalBoolean(parsed.canRedo);
|
|
208
|
+
|
|
182
209
|
if (
|
|
183
210
|
documentState == null ||
|
|
184
211
|
transportState == null ||
|
|
@@ -191,6 +218,7 @@ export function normalizeNativeEditorV2StateValue(
|
|
|
191
218
|
) {
|
|
192
219
|
return null;
|
|
193
220
|
}
|
|
221
|
+
|
|
194
222
|
return {
|
|
195
223
|
documentState,
|
|
196
224
|
transportState,
|
|
@@ -219,22 +247,33 @@ export function normalizeNativeEditorV2MutationOutcomeValue(
|
|
|
219
247
|
value: unknown
|
|
220
248
|
): NativeEditorMutationOutcome | null {
|
|
221
249
|
const parsed = parseNativeEditorV2JsonValue(value);
|
|
222
|
-
|
|
250
|
+
|
|
251
|
+
if (!isPlainRecord(parsed)) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
223
255
|
if (parsed.type === 'notApplicable') {
|
|
224
256
|
return { type: 'notApplicable' };
|
|
225
257
|
}
|
|
258
|
+
|
|
226
259
|
if (parsed.type === 'replacement') {
|
|
227
260
|
const changed = optionalBoolean(parsed.changed);
|
|
228
261
|
const documentRevision = normalizeRevisionField(parsed, 'documentRevision');
|
|
229
|
-
|
|
262
|
+
|
|
263
|
+
if (changed == null || documentRevision == null) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
230
267
|
return { type: 'replacement', changed, documentRevision };
|
|
231
268
|
}
|
|
269
|
+
|
|
232
270
|
if (parsed.type === 'transaction') {
|
|
233
271
|
const changed = optionalBoolean(parsed.changed);
|
|
234
272
|
const documentRevision = normalizeRevisionField(parsed, 'documentRevision');
|
|
235
273
|
const stateRevision = normalizeRevisionField(parsed, 'stateRevision');
|
|
236
274
|
const canUndo = optionalBoolean(parsed.canUndo);
|
|
237
275
|
const canRedo = optionalBoolean(parsed.canRedo);
|
|
276
|
+
|
|
238
277
|
if (
|
|
239
278
|
changed == null ||
|
|
240
279
|
documentRevision == null ||
|
|
@@ -244,6 +283,7 @@ export function normalizeNativeEditorV2MutationOutcomeValue(
|
|
|
244
283
|
) {
|
|
245
284
|
return null;
|
|
246
285
|
}
|
|
286
|
+
|
|
247
287
|
return {
|
|
248
288
|
type: 'transaction',
|
|
249
289
|
changed,
|
|
@@ -253,6 +293,7 @@ export function normalizeNativeEditorV2MutationOutcomeValue(
|
|
|
253
293
|
canRedo,
|
|
254
294
|
};
|
|
255
295
|
}
|
|
296
|
+
|
|
256
297
|
return null;
|
|
257
298
|
}
|
|
258
299
|
|
|
@@ -265,22 +306,38 @@ export function normalizeNativeEditorV2CommitValue(
|
|
|
265
306
|
value: unknown
|
|
266
307
|
): NativeEditorCommitInfo | null {
|
|
267
308
|
const parsed = parseNativeEditorV2JsonValue(value);
|
|
268
|
-
|
|
309
|
+
|
|
310
|
+
if (!isPlainRecord(parsed)) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
|
|
269
314
|
const changed = optionalBoolean(parsed.changed);
|
|
270
315
|
const documentRevision = normalizeRevisionField(parsed, 'documentRevision');
|
|
271
|
-
|
|
316
|
+
|
|
317
|
+
if (changed == null || documentRevision == null) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
|
|
272
321
|
return { changed, documentRevision };
|
|
273
322
|
}
|
|
274
323
|
|
|
275
324
|
export function normalizeNativeEditorV2ChangedValue(value: unknown): boolean | null {
|
|
276
325
|
const parsed = parseNativeEditorV2JsonValue(value);
|
|
277
|
-
|
|
326
|
+
|
|
327
|
+
if (!isPlainRecord(parsed)) {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
|
|
278
331
|
return optionalBoolean(parsed.changed);
|
|
279
332
|
}
|
|
280
333
|
|
|
281
334
|
export function normalizeNativeEditorV2HtmlValue(value: unknown): string | null {
|
|
282
335
|
const parsed = parseNativeEditorV2JsonValue(value);
|
|
283
|
-
|
|
336
|
+
|
|
337
|
+
if (!isPlainRecord(parsed) || typeof parsed.html !== 'string') {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
|
|
284
341
|
return parsed.html;
|
|
285
342
|
}
|
|
286
343
|
|
|
@@ -299,7 +356,8 @@ export function hasExactOwnKeys(
|
|
|
299
356
|
expected: readonly string[]
|
|
300
357
|
): boolean {
|
|
301
358
|
const actual = Object.keys(record).sort();
|
|
302
|
-
const sortedExpected = [...expected].sort();
|
|
359
|
+
const sortedExpected = [ ...expected ].sort();
|
|
360
|
+
|
|
303
361
|
return (
|
|
304
362
|
actual.length === sortedExpected.length &&
|
|
305
363
|
actual.every((key, index) => key === sortedExpected[index])
|
|
@@ -311,24 +369,37 @@ export function hasOnlyOwnKeys(
|
|
|
311
369
|
allowed: readonly string[]
|
|
312
370
|
): boolean {
|
|
313
371
|
const allowedKeys = new Set(allowed);
|
|
314
|
-
|
|
372
|
+
|
|
373
|
+
return Object.keys(record).every(key => allowedKeys.has(key));
|
|
315
374
|
}
|
|
316
375
|
|
|
317
376
|
export function booleanRecord(value: unknown): value is Record<string, boolean> {
|
|
318
377
|
return (
|
|
319
|
-
isPlainRecord(value) && Object.values(value).every(
|
|
378
|
+
isPlainRecord(value) && Object.values(value).every(entry => typeof entry === 'boolean')
|
|
320
379
|
);
|
|
321
380
|
}
|
|
322
381
|
|
|
323
382
|
export function stringArray(value: unknown): value is string[] {
|
|
324
|
-
return Array.isArray(value) && value.every(
|
|
383
|
+
return Array.isArray(value) && value.every(entry => typeof entry === 'string');
|
|
325
384
|
}
|
|
326
385
|
|
|
327
386
|
export function validJsonValue(value: unknown): boolean {
|
|
328
|
-
if (value === null)
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
387
|
+
if (value === null) {
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (typeof value === 'string' || typeof value === 'boolean') {
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (typeof value === 'number') {
|
|
396
|
+
return Number.isFinite(value);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (Array.isArray(value)) {
|
|
400
|
+
return value.every(validJsonValue);
|
|
401
|
+
}
|
|
402
|
+
|
|
332
403
|
return isPlainRecord(value) && Object.values(value).every(validJsonValue);
|
|
333
404
|
}
|
|
334
405
|
|
|
@@ -343,31 +414,56 @@ export function validRenderMark(value: unknown): value is RenderMark {
|
|
|
343
414
|
|
|
344
415
|
export function normalizeNativeEditorV2PeersValue(value: unknown): NativeEditorPeerInfo[] | null {
|
|
345
416
|
const parsed = typeof value === 'string' ? parseNativeEditorV2JsonValue(value) : value;
|
|
346
|
-
|
|
417
|
+
|
|
418
|
+
if (!isPlainRecord(parsed) || !Array.isArray(parsed.peers)) {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
|
|
347
422
|
const peers: NativeEditorPeerInfo[] = [];
|
|
423
|
+
|
|
348
424
|
for (const rawPeer of parsed.peers) {
|
|
349
|
-
if (!isPlainRecord(rawPeer))
|
|
425
|
+
if (!isPlainRecord(rawPeer)) {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
|
|
350
429
|
const clientId = normalizeNativeEditorV2DecimalId(rawPeer.clientId);
|
|
351
430
|
const clock = nativeEditorV2U32(rawPeer.clock);
|
|
352
431
|
const isLocal = optionalBoolean(rawPeer.isLocal);
|
|
353
|
-
|
|
354
|
-
if (
|
|
432
|
+
|
|
433
|
+
if (clientId == null || clock == null || isLocal == null) {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (rawPeer.state !== null && !isPlainRecord(rawPeer.state)) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
|
|
355
441
|
let cursor: NativeEditorPeerInfo['cursor'] = null;
|
|
442
|
+
|
|
356
443
|
if (rawPeer.cursor !== null && rawPeer.cursor !== undefined) {
|
|
357
|
-
if (!isPlainRecord(rawPeer.cursor))
|
|
444
|
+
if (!isPlainRecord(rawPeer.cursor)) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
|
|
358
448
|
const anchor = nativeEditorV2U32(rawPeer.cursor.anchor);
|
|
359
449
|
const head = nativeEditorV2U32(rawPeer.cursor.head);
|
|
360
|
-
|
|
450
|
+
|
|
451
|
+
if (anchor == null || head == null) {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
|
|
361
455
|
cursor = { anchor, head };
|
|
362
456
|
}
|
|
457
|
+
|
|
363
458
|
peers.push({
|
|
364
459
|
clientId,
|
|
365
460
|
clock,
|
|
366
461
|
isLocal,
|
|
367
|
-
state: (rawPeer.state
|
|
462
|
+
state: (rawPeer.state) ?? null,
|
|
368
463
|
cursor,
|
|
369
464
|
});
|
|
370
465
|
}
|
|
466
|
+
|
|
371
467
|
return peers;
|
|
372
468
|
}
|
|
373
469
|
|
|
@@ -376,12 +472,17 @@ export const REJECTED_V2_RECORD_PREVIEW_CHARS = 4_000;
|
|
|
376
472
|
/** Dev-only preview of a rejected boundary record, bounded so a large document never floods the log. */
|
|
377
473
|
export function describeRejectedV2Record(raw: unknown): string {
|
|
378
474
|
let serialized: string;
|
|
475
|
+
|
|
379
476
|
try {
|
|
380
477
|
serialized = JSON.stringify(raw);
|
|
381
478
|
} catch {
|
|
382
479
|
return `<unserializable ${typeof raw}>`;
|
|
383
480
|
}
|
|
384
|
-
|
|
481
|
+
|
|
482
|
+
if (serialized === undefined) {
|
|
483
|
+
return `<${typeof raw}>`;
|
|
484
|
+
}
|
|
485
|
+
|
|
385
486
|
return serialized.length > REJECTED_V2_RECORD_PREVIEW_CHARS
|
|
386
487
|
? `${serialized.slice(0, REJECTED_V2_RECORD_PREVIEW_CHARS)}… (${serialized.length} chars)`
|
|
387
488
|
: serialized;
|
|
@@ -437,33 +538,43 @@ export function unwrapNativeEditorV2Result<T>(
|
|
|
437
538
|
normalizeValue: (value: unknown) => T | null
|
|
438
539
|
): T {
|
|
439
540
|
const result = normalizeNativeEditorV2Result(raw, normalizeValue);
|
|
541
|
+
|
|
440
542
|
if (result == null) {
|
|
441
543
|
// The thrown error cannot carry the payload, so name the rejected
|
|
442
|
-
// record here
|
|
544
|
+
// record here : otherwise the failure is unattributable in the app.
|
|
443
545
|
if (__DEV__) {
|
|
444
546
|
console.error(
|
|
445
547
|
'NativeEditorBridge: native module returned a record this boundary rejected',
|
|
446
548
|
describeRejectedV2Record(raw)
|
|
447
549
|
);
|
|
448
550
|
}
|
|
551
|
+
|
|
449
552
|
throw invalidV2ResultError();
|
|
450
553
|
}
|
|
451
|
-
|
|
554
|
+
|
|
555
|
+
if (!result.ok) {
|
|
556
|
+
throw nativeEditorV2ErrorToException(result.error);
|
|
557
|
+
}
|
|
558
|
+
|
|
452
559
|
return result.value;
|
|
453
560
|
}
|
|
454
561
|
|
|
455
562
|
export function requireV2DecimalId(value: string, field: string): string {
|
|
456
563
|
const normalized = normalizeNativeEditorV2DecimalId(value);
|
|
564
|
+
|
|
457
565
|
if (normalized == null) {
|
|
458
566
|
throw invalidV2RequestError(`NativeEditorBridge: invalid ${field} for v2 request`);
|
|
459
567
|
}
|
|
568
|
+
|
|
460
569
|
return normalized;
|
|
461
570
|
}
|
|
462
571
|
|
|
463
572
|
export function requireV2Bytes(value: unknown, field: string): Uint8Array {
|
|
464
573
|
const normalized = normalizeNativeEditorV2Bytes(value);
|
|
574
|
+
|
|
465
575
|
if (normalized == null) {
|
|
466
576
|
throw invalidV2RequestError(`NativeEditorBridge: invalid ${field} for v2 request`);
|
|
467
577
|
}
|
|
578
|
+
|
|
468
579
|
return normalized;
|
|
469
580
|
}
|
package/src/NativeEditorTypes.ts
CHANGED
|
@@ -43,7 +43,8 @@ export class NativeEditorLocalAwarenessSelectionValue {
|
|
|
43
43
|
constructor(
|
|
44
44
|
readonly anchor: number,
|
|
45
45
|
readonly head: number
|
|
46
|
-
) {
|
|
46
|
+
) {
|
|
47
|
+
}
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
export type NativeEditorLocalAwarenessSelection = NativeEditorLocalAwarenessSelectionValue;
|
|
@@ -61,11 +62,11 @@ export interface NativeEditorLocalAwarenessIntent {
|
|
|
61
62
|
* How this intent treats the local cursor. Rust owns it as a sticky
|
|
62
63
|
* index, so the three cases are deliberately distinct:
|
|
63
64
|
*
|
|
64
|
-
* - **omitted**
|
|
65
|
+
* - **omitted** : retain the cursor already published. Use this for
|
|
65
66
|
* focus-only or state-only updates: restating a document position
|
|
66
67
|
* that the document has since invalidated would be refused.
|
|
67
|
-
* - **`null`**
|
|
68
|
-
* - **a factory selection**
|
|
68
|
+
* - **`null`** : publish presence with no cursor at all.
|
|
69
|
+
* - **a factory selection** : set the cursor to these positions, which
|
|
69
70
|
* must resolve against the current document.
|
|
70
71
|
*/
|
|
71
72
|
selection?: NativeEditorLocalAwarenessSelection | null;
|
|
@@ -79,7 +80,7 @@ export interface ListContext {
|
|
|
79
80
|
index: number;
|
|
80
81
|
/** Number of sibling items in the enclosing list. */
|
|
81
82
|
total: number;
|
|
82
|
-
/** The list's `start` attribute
|
|
83
|
+
/** The list's `start` attribute : the number the first item takes. */
|
|
83
84
|
start: number;
|
|
84
85
|
isFirst: boolean;
|
|
85
86
|
isLast: boolean;
|
|
@@ -133,7 +134,7 @@ export interface RenderBlocksPatch {
|
|
|
133
134
|
renderBlocks: RenderElement[][];
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
/** What the current selection can do
|
|
137
|
+
/** What the current selection can do : the state a toolbar renders from. */
|
|
137
138
|
export interface ActiveState {
|
|
138
139
|
/** Marks active at the selection, keyed by mark name. */
|
|
139
140
|
marks: Record<string, boolean>;
|
|
@@ -260,12 +261,12 @@ export interface NativeEditorRoomSnapshot {
|
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
/**
|
|
263
|
-
* How a document handle's content starts out. Fixed at creation
|
|
264
|
+
* How a document handle's content starts out. Fixed at creation : there is no
|
|
264
265
|
* prop equivalent.
|
|
265
266
|
*
|
|
266
|
-
* - `localEmpty`
|
|
267
|
-
* - `localJson` / `localHtml`
|
|
268
|
-
* - `room`
|
|
267
|
+
* - `localEmpty` : the schema's empty document.
|
|
268
|
+
* - `localJson` / `localHtml` : seeded from the given content.
|
|
269
|
+
* - `room` : a collaborative document. It renders nothing until the server's
|
|
269
270
|
* document arrives, unless a `snapshot` seeds it offline.
|
|
270
271
|
*/
|
|
271
272
|
export type NativeEditorInitialization =
|
|
@@ -336,7 +337,7 @@ export type NativeEditorPositionAffinity = 'before' | 'after';
|
|
|
336
337
|
|
|
337
338
|
/**
|
|
338
339
|
* Mirrors the Rust `PositionEnvelope`. `offset` is measured in the currency
|
|
339
|
-
* named by `kind`
|
|
340
|
+
* named by `kind` : scalar offsets are Unicode scalars, not document positions.
|
|
340
341
|
*/
|
|
341
342
|
export interface NativeEditorPositionEnvelope {
|
|
342
343
|
offset: number;
|
|
@@ -6,8 +6,17 @@ const V2_U64_MAX = '18446744073709551615';
|
|
|
6
6
|
* lexical after syntax validation, so JavaScript never coerces the value.
|
|
7
7
|
*/
|
|
8
8
|
export function normalizeNativeEditorV2U64(value: unknown): string | null {
|
|
9
|
-
if (typeof value !== 'string' || !CANONICAL_V2_U64.test(value))
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
if (typeof value !== 'string' || !CANONICAL_V2_U64.test(value)) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (value.length < V2_U64_MAX.length) {
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (value.length > V2_U64_MAX.length || value > V2_U64_MAX) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
return value;
|
|
13
22
|
}
|
|
@@ -135,12 +135,14 @@ const serializedJsonCache = new WeakMap<object, string>();
|
|
|
135
135
|
|
|
136
136
|
function stringifyCachedJson(value: DocumentJSON): string {
|
|
137
137
|
const cached = serializedJsonCache.get(value);
|
|
138
|
+
|
|
138
139
|
if (cached != null) {
|
|
139
140
|
return cached;
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
const serialized = JSON.stringify(value);
|
|
143
144
|
serializedJsonCache.set(value, serialized);
|
|
145
|
+
|
|
144
146
|
return serialized;
|
|
145
147
|
}
|
|
146
148
|
|
|
@@ -160,11 +162,11 @@ function resolveViewerConfiguration(
|
|
|
160
162
|
...(resourceLimits ? { limits: { resource: resourceLimits } } : {}),
|
|
161
163
|
...(mentions?.trigger || mentions?.prefix
|
|
162
164
|
? {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
mentions: {
|
|
166
|
+
...(mentions.trigger ? { trigger: mentions.trigger } : {}),
|
|
167
|
+
...(mentions.prefix ? { prefix: mentions.prefix } : {}),
|
|
168
|
+
},
|
|
169
|
+
}
|
|
168
170
|
: {}),
|
|
169
171
|
};
|
|
170
172
|
}
|
|
@@ -209,11 +211,14 @@ export function RichTextViewer(props: RichTextViewerProps) {
|
|
|
209
211
|
onError,
|
|
210
212
|
...viewProps
|
|
211
213
|
} = props;
|
|
212
|
-
|
|
214
|
+
|
|
215
|
+
const { mentions, codeHighlighting } = useMemo(() => normalizeEditorAddons(addons), [ addons ]);
|
|
216
|
+
|
|
213
217
|
const resolvedResourceLimits = useMemo(
|
|
214
218
|
() => (resourceLimits ? resolveEditorResourceLimits(resourceLimits) : undefined),
|
|
215
|
-
[resourceLimits]
|
|
219
|
+
[ resourceLimits ]
|
|
216
220
|
);
|
|
221
|
+
|
|
217
222
|
const configJson = useMemo(
|
|
218
223
|
() =>
|
|
219
224
|
serializePreparedProseViewerConfiguration(
|
|
@@ -226,25 +231,38 @@ export function RichTextViewer(props: RichTextViewerProps) {
|
|
|
226
231
|
codeHighlighting
|
|
227
232
|
)
|
|
228
233
|
),
|
|
229
|
-
[allowBase64Images,
|
|
234
|
+
[ allowBase64Images,
|
|
235
|
+
mentions,
|
|
236
|
+
resolvedResourceLimits,
|
|
237
|
+
schema,
|
|
238
|
+
atoms,
|
|
239
|
+
codeHighlighting ]
|
|
230
240
|
);
|
|
241
|
+
|
|
231
242
|
const themeJson = useMemo(
|
|
232
243
|
() => serializeEditorTheme(theme, mentions?.theme),
|
|
233
|
-
[mentions?.theme, theme]
|
|
244
|
+
[ mentions?.theme, theme ]
|
|
234
245
|
);
|
|
246
|
+
|
|
235
247
|
const imagePolicyJson = useMemo(
|
|
236
248
|
() => serializeEditorImageLoadingPolicy(imageLoadingPolicy),
|
|
237
|
-
[imageLoadingPolicy]
|
|
249
|
+
[ imageLoadingPolicy ]
|
|
238
250
|
);
|
|
251
|
+
|
|
239
252
|
const sourceKind = contentJSON === undefined ? 'html' : 'json';
|
|
253
|
+
|
|
240
254
|
const source = useMemo(() => {
|
|
241
255
|
if (contentJSON === undefined) {
|
|
242
256
|
return contentHTML ?? '';
|
|
243
257
|
}
|
|
258
|
+
|
|
244
259
|
return typeof contentJSON === 'string' ? contentJSON : stringifyCachedJson(contentJSON);
|
|
245
|
-
}, [contentHTML, contentJSON]);
|
|
260
|
+
}, [ contentHTML, contentJSON ]);
|
|
261
|
+
|
|
246
262
|
const atomIdentity = useMemo(
|
|
247
|
-
() => ({
|
|
263
|
+
() => ({
|
|
264
|
+
sourceKind, source, configJson, themeJson, imagePolicyJson, renderImages, collapseTrailingEmptyParagraphs, fontEnvironmentRevision,
|
|
265
|
+
}),
|
|
248
266
|
[
|
|
249
267
|
sourceKind,
|
|
250
268
|
source,
|
|
@@ -256,6 +274,7 @@ export function RichTextViewer(props: RichTextViewerProps) {
|
|
|
256
274
|
fontEnvironmentRevision,
|
|
257
275
|
]
|
|
258
276
|
);
|
|
277
|
+
|
|
259
278
|
const viewerAtoms = useViewerAtoms({
|
|
260
279
|
atoms,
|
|
261
280
|
identity: atomIdentity,
|
|
@@ -266,21 +285,25 @@ export function RichTextViewer(props: RichTextViewerProps) {
|
|
|
266
285
|
onUpdateAtomAttrs,
|
|
267
286
|
onError,
|
|
268
287
|
});
|
|
288
|
+
|
|
269
289
|
const handlePressLink = useCallback(
|
|
270
290
|
(event: NativeSyntheticEvent<RichTextViewerLinkPressNativeEvent>) => {
|
|
271
291
|
onPressLink?.(event.nativeEvent);
|
|
272
292
|
},
|
|
273
|
-
[onPressLink]
|
|
293
|
+
[ onPressLink ]
|
|
274
294
|
);
|
|
295
|
+
|
|
275
296
|
const handlePressMention = useCallback(
|
|
276
297
|
(event: NativeSyntheticEvent<RichTextViewerMentionPressNativeEvent>) => {
|
|
277
298
|
const { docPos, label, attrsJson } = event.nativeEvent;
|
|
278
299
|
let attrs: unknown;
|
|
300
|
+
|
|
279
301
|
try {
|
|
280
302
|
attrs = JSON.parse(attrsJson);
|
|
281
303
|
} catch {
|
|
282
304
|
attrs = null;
|
|
283
305
|
}
|
|
306
|
+
|
|
284
307
|
if (attrs === null || typeof attrs !== 'object' || Array.isArray(attrs)) {
|
|
285
308
|
onError?.({
|
|
286
309
|
domain: 'viewer',
|
|
@@ -288,17 +311,20 @@ export function RichTextViewer(props: RichTextViewerProps) {
|
|
|
288
311
|
message: 'The prepared mention attributes are not a JSON object.',
|
|
289
312
|
fatal: false,
|
|
290
313
|
});
|
|
314
|
+
|
|
291
315
|
return;
|
|
292
316
|
}
|
|
317
|
+
|
|
293
318
|
mentions?.onPress?.({ docPos, label, attrs: attrs as Record<string, unknown> });
|
|
294
319
|
},
|
|
295
|
-
[mentions, onError]
|
|
320
|
+
[ mentions, onError ]
|
|
296
321
|
);
|
|
322
|
+
|
|
297
323
|
const handleError = useCallback(
|
|
298
324
|
(event: NativeSyntheticEvent<RichTextViewerErrorEvent>) => {
|
|
299
325
|
onError?.(event.nativeEvent);
|
|
300
326
|
},
|
|
301
|
-
[onError]
|
|
327
|
+
[ onError ]
|
|
302
328
|
);
|
|
303
329
|
|
|
304
330
|
const nativeViewer = (
|
|
@@ -320,7 +346,11 @@ export function RichTextViewer(props: RichTextViewerProps) {
|
|
|
320
346
|
onError={onError ? handleError : undefined}
|
|
321
347
|
/>
|
|
322
348
|
);
|
|
323
|
-
|
|
349
|
+
|
|
350
|
+
if (!viewerAtoms.enabled) {
|
|
351
|
+
return nativeViewer;
|
|
352
|
+
}
|
|
353
|
+
|
|
324
354
|
return (
|
|
325
355
|
<View {...viewProps}>
|
|
326
356
|
<View style={{ alignSelf: 'stretch' }} onLayout={viewerAtoms.onContainerLayout}>
|