@apollohg/react-native-rich-text-editor 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -116
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/android/build.gradle +3 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditTextImages.kt +25 -0
- package/android/src/main/java/com/apollohg/editor/EditorV2RenderValidation.kt +1 -0
- package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +101 -33
- package/android/src/main/java/com/apollohg/editor/RenderMediaSpans.kt +15 -3
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +14 -0
- package/android/src/main/java/com/apollohg/editor/viewer/AndroidProseLayoutEngine.kt +1 -1
- 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 +85 -38
- package/dist/EditorStyleSheetTypes.d.ts +1 -1
- 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/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/ios/EditorInteractionOverlays.swift +114 -122
- package/ios/EditorLayoutManager.swift +13 -3
- package/ios/EditorStyleSheet.swift +12 -1
- package/ios/EditorTextView+Images.swift +32 -10
- package/ios/EditorTextView.swift +1 -1
- package/ios/EditorV2Adapter+RenderValidation.swift +2 -2
- package/ios/NativeToolbar+ButtonStyle.swift +17 -5
- package/ios/NativeToolbar+Diagnostics.swift +15 -0
- package/ios/RenderBridge+Atoms.swift +0 -1
- package/ios/RenderBridge+Attachments.swift +0 -29
- package/ios/RichTextEditorView.swift +4 -12
- package/ios/Viewer/CoreTextProseLayoutEngine+AttributedText.swift +3 -2
- 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/EditorStyleSheetTypes.ts +1 -1
- 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
|
@@ -36,6 +36,7 @@ function sameFrames(
|
|
|
36
36
|
left.length === right.length &&
|
|
37
37
|
left.every((frame, index) => {
|
|
38
38
|
const candidate = right[index];
|
|
39
|
+
|
|
39
40
|
return (
|
|
40
41
|
frame.x === candidate.x &&
|
|
41
42
|
frame.y === candidate.y &&
|
|
@@ -52,9 +53,10 @@ function validFrame(
|
|
|
52
53
|
width: number,
|
|
53
54
|
height: number
|
|
54
55
|
): EditorToolbarFrame | null {
|
|
55
|
-
if (![x, y, width, height].every(Number.isFinite) || width <= 0 || height <= 0) {
|
|
56
|
+
if (![ x, y, width, height ].every(Number.isFinite) || width <= 0 || height <= 0) {
|
|
56
57
|
return null;
|
|
57
58
|
}
|
|
59
|
+
|
|
58
60
|
return { x, y, width, height };
|
|
59
61
|
}
|
|
60
62
|
|
|
@@ -66,38 +68,49 @@ export function useFocusPreservingFrames(
|
|
|
66
68
|
refresh: () => void;
|
|
67
69
|
} {
|
|
68
70
|
const normalizedRefs: readonly RichTextEditorFocusPreservingRef[] =
|
|
69
|
-
refs == null ? [] : Array.isArray(refs) ? refs : [refs as RichTextEditorFocusPreservingRef];
|
|
71
|
+
refs == null ? [] : Array.isArray(refs) ? refs : [ refs as RichTextEditorFocusPreservingRef ];
|
|
72
|
+
|
|
70
73
|
const stableRefs = useRef<readonly RichTextEditorFocusPreservingRef[]>(normalizedRefs);
|
|
74
|
+
|
|
71
75
|
if (!sameRefs(stableRefs.current, normalizedRefs)) {
|
|
72
76
|
stableRefs.current = normalizedRefs;
|
|
73
77
|
}
|
|
78
|
+
|
|
74
79
|
const currentRefs = stableRefs.current;
|
|
75
80
|
|
|
76
81
|
const generation = useRef(0);
|
|
82
|
+
|
|
77
83
|
const framesByRef = useRef(
|
|
78
84
|
new Map<RichTextEditorFocusPreservingRef, MeasuredFocusPreservingFrame>()
|
|
79
85
|
);
|
|
80
|
-
|
|
86
|
+
|
|
87
|
+
const [ frames, setFrames ] = useState<readonly EditorToolbarFrame[]>(EMPTY_FRAMES);
|
|
81
88
|
|
|
82
89
|
const publish = useCallback(() => {
|
|
83
|
-
const nextFrames = currentRefs.flatMap(
|
|
90
|
+
const nextFrames = currentRefs.flatMap(ref => {
|
|
84
91
|
const measurement = framesByRef.current.get(ref);
|
|
85
|
-
|
|
92
|
+
|
|
93
|
+
return measurement == null ? [] : [ measurement.frame ];
|
|
86
94
|
});
|
|
87
|
-
|
|
88
|
-
|
|
95
|
+
|
|
96
|
+
setFrames(current => (sameFrames(current, nextFrames) ? current : nextFrames));
|
|
97
|
+
}, [ currentRefs ]);
|
|
89
98
|
|
|
90
99
|
const refresh = useCallback(() => {
|
|
91
100
|
const callbackGeneration = ++generation.current;
|
|
101
|
+
|
|
92
102
|
if (!enabled) {
|
|
93
103
|
framesByRef.current.clear();
|
|
94
|
-
setFrames(
|
|
104
|
+
setFrames(current => (current.length === 0 ? current : EMPTY_FRAMES));
|
|
105
|
+
|
|
95
106
|
return;
|
|
96
107
|
}
|
|
97
108
|
|
|
98
109
|
const admittedRefs = new Set(currentRefs);
|
|
110
|
+
|
|
99
111
|
for (const ref of framesByRef.current.keys()) {
|
|
100
112
|
const measurement = framesByRef.current.get(ref);
|
|
113
|
+
|
|
101
114
|
if (
|
|
102
115
|
!admittedRefs.has(ref) ||
|
|
103
116
|
ref.current == null ||
|
|
@@ -106,49 +119,64 @@ export function useFocusPreservingFrames(
|
|
|
106
119
|
framesByRef.current.delete(ref);
|
|
107
120
|
}
|
|
108
121
|
}
|
|
122
|
+
|
|
109
123
|
publish();
|
|
110
124
|
|
|
111
|
-
currentRefs.forEach(
|
|
125
|
+
currentRefs.forEach(ref => {
|
|
112
126
|
const target = ref.current;
|
|
127
|
+
|
|
113
128
|
if (target == null || typeof target.measureInWindow !== 'function') {
|
|
114
129
|
framesByRef.current.delete(ref);
|
|
115
130
|
publish();
|
|
131
|
+
|
|
116
132
|
return;
|
|
117
133
|
}
|
|
118
134
|
|
|
119
135
|
try {
|
|
120
136
|
target.measureInWindow((x, y, width, height) => {
|
|
121
137
|
// An older measurement must not restore a removed or replaced target.
|
|
122
|
-
if (generation.current !== callbackGeneration || ref.current !== target)
|
|
138
|
+
if (generation.current !== callbackGeneration || ref.current !== target) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
123
142
|
const frame = validFrame(x, y, width, height);
|
|
143
|
+
|
|
124
144
|
if (frame == null) {
|
|
125
145
|
framesByRef.current.delete(ref);
|
|
126
146
|
} else {
|
|
127
147
|
framesByRef.current.set(ref, { target, frame });
|
|
128
148
|
}
|
|
149
|
+
|
|
129
150
|
publish();
|
|
130
151
|
});
|
|
131
152
|
} catch {
|
|
132
|
-
if (generation.current !== callbackGeneration)
|
|
153
|
+
if (generation.current !== callbackGeneration) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
133
157
|
framesByRef.current.delete(ref);
|
|
134
158
|
publish();
|
|
135
159
|
}
|
|
136
160
|
});
|
|
137
|
-
}, [currentRefs, enabled, publish]);
|
|
161
|
+
}, [ currentRefs, enabled, publish ]);
|
|
138
162
|
|
|
139
163
|
useLayoutEffect(() => {
|
|
140
164
|
refresh();
|
|
141
165
|
});
|
|
142
166
|
|
|
143
167
|
useEffect(() => {
|
|
144
|
-
if (!enabled)
|
|
168
|
+
if (!enabled) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
145
172
|
const subscriptions = [
|
|
146
173
|
Keyboard.addListener('keyboardDidShow', refresh),
|
|
147
174
|
Keyboard.addListener('keyboardDidHide', refresh),
|
|
148
175
|
Keyboard.addListener('keyboardDidChangeFrame', refresh),
|
|
149
176
|
];
|
|
150
|
-
|
|
151
|
-
|
|
177
|
+
|
|
178
|
+
return () => subscriptions.forEach(subscription => subscription.remove());
|
|
179
|
+
}, [ enabled, refresh ]);
|
|
152
180
|
|
|
153
181
|
useEffect(
|
|
154
182
|
() => () => {
|
package/src/useNativeEditor.ts
CHANGED
|
@@ -11,25 +11,7 @@ import {
|
|
|
11
11
|
} from './NativeEditorBridge';
|
|
12
12
|
import { NativeEditorOperationError } from './NativeEditorBoundaryError';
|
|
13
13
|
|
|
14
|
-
//
|
|
15
|
-
// Headless document/control binding over a shared NativeEditorDocumentHandle
|
|
16
|
-
// (one native session per handle; the collaboration controller attaches to
|
|
17
|
-
// the same one). It mirrors the editor's retained document API — controlled
|
|
18
|
-
// `value`/`valueJSON` with the replace/reset history policy, `setContent`,
|
|
19
|
-
// `setContentJson`, `getContent`, `getContentJson` — through the v2 engine:
|
|
20
|
-
//
|
|
21
|
-
// - `valueJSONUpdateMode="replace"` lowers to one undoable local-API
|
|
22
|
-
// replacement (`undoableBoundary`); `"reset"` clears history
|
|
23
|
-
// (`resetAndClear`). Imperative setContent/setContentJson are undoable;
|
|
24
|
-
// clearContent is a non-undoable reset.
|
|
25
|
-
// - A room document in `AwaitRemote` renders as not-ready; content getters
|
|
26
|
-
// return empty values until Rust promotes an accepted server Step 2.
|
|
27
|
-
// - Remote commits arrive through `revisionSignal` (the collaboration
|
|
28
|
-
// controller's rendered revision): the binding re-reads the engine and
|
|
29
|
-
// emits content callbacks. It never pushes document state anywhere.
|
|
30
|
-
// - Controlled applies carry the last rendered engine revision as their base;
|
|
31
|
-
// a REVISION_MISMATCH refreshes from the engine and the effect re-applies
|
|
32
|
-
// against the fresh revision — never against guessed positions.
|
|
14
|
+
// Controlled writes use the last rendered revision; conflicts refresh before retrying.
|
|
33
15
|
|
|
34
16
|
/** Configuration for {@link useNativeEditorDocument}. */
|
|
35
17
|
export interface UseNativeEditorDocumentOptions {
|
|
@@ -107,7 +89,7 @@ interface V2EngineView {
|
|
|
107
89
|
/**
|
|
108
90
|
* Headless binding to a document handle: the same controlled content,
|
|
109
91
|
* getters, and history the editor exposes, with no view attached. Use it to
|
|
110
|
-
* read or drive a document that is not currently mounted
|
|
92
|
+
* read or drive a document that is not currently mounted : a preview, a
|
|
111
93
|
* background save, a test.
|
|
112
94
|
*
|
|
113
95
|
* `RichTextEditor` uses this internally, so mounting an editor on the
|
|
@@ -137,7 +119,7 @@ export function useNativeEditorDocument(
|
|
|
137
119
|
const onLocalCommitRef = useRef(onLocalCommit);
|
|
138
120
|
onLocalCommitRef.current = onLocalCommit;
|
|
139
121
|
|
|
140
|
-
const [engineView, setEngineView] = useState<V2EngineView>({
|
|
122
|
+
const [ engineView, setEngineView ] = useState<V2EngineView>({
|
|
141
123
|
editorId,
|
|
142
124
|
ready: false,
|
|
143
125
|
documentState: null,
|
|
@@ -146,26 +128,32 @@ export function useNativeEditorDocument(
|
|
|
146
128
|
historyState: DEFAULT_V2_HISTORY_STATE,
|
|
147
129
|
contentKey: null,
|
|
148
130
|
});
|
|
131
|
+
|
|
149
132
|
const currentEditorIdRef = useRef(editorId);
|
|
150
133
|
currentEditorIdRef.current = editorId;
|
|
151
134
|
const readyRef = useRef({ editorId, ready: false });
|
|
135
|
+
|
|
152
136
|
const emittedRef = useRef({
|
|
153
137
|
editorId,
|
|
154
138
|
lastRevision: null as string | null,
|
|
155
139
|
lastContentKey: null as string | null,
|
|
156
140
|
lastHistoryKey: null as string | null,
|
|
157
141
|
});
|
|
142
|
+
|
|
158
143
|
const pendingControlledEchoesRef = useRef({
|
|
159
144
|
editorId,
|
|
160
145
|
keys: [] as string[],
|
|
161
146
|
});
|
|
147
|
+
|
|
162
148
|
const controlledKindRef = useRef<'html' | 'json' | null>(null);
|
|
163
149
|
controlledKindRef.current = value != null ? 'html' : valueJSON != null ? 'json' : null;
|
|
164
150
|
const ignoredControlledEchoRef = useRef<string | null>(null);
|
|
165
151
|
const lastControlledContentKeyRef = useRef<string | null>(null);
|
|
152
|
+
|
|
166
153
|
if (readyRef.current.editorId !== editorId) {
|
|
167
154
|
readyRef.current = { editorId, ready: false };
|
|
168
155
|
}
|
|
156
|
+
|
|
169
157
|
if (emittedRef.current.editorId !== editorId) {
|
|
170
158
|
emittedRef.current = {
|
|
171
159
|
editorId,
|
|
@@ -173,6 +161,7 @@ export function useNativeEditorDocument(
|
|
|
173
161
|
lastContentKey: null,
|
|
174
162
|
lastHistoryKey: null,
|
|
175
163
|
};
|
|
164
|
+
|
|
176
165
|
pendingControlledEchoesRef.current = { editorId, keys: [] };
|
|
177
166
|
ignoredControlledEchoRef.current = null;
|
|
178
167
|
lastControlledContentKeyRef.current = null;
|
|
@@ -181,26 +170,35 @@ export function useNativeEditorDocument(
|
|
|
181
170
|
const refresh = useCallback(
|
|
182
171
|
(emitContentCallbacks: boolean) => {
|
|
183
172
|
const refreshedEditorId = handle.editorId;
|
|
173
|
+
|
|
184
174
|
if (handle.isDestroyed || currentEditorIdRef.current !== refreshedEditorId) {
|
|
185
175
|
return;
|
|
186
176
|
}
|
|
177
|
+
|
|
187
178
|
const state = handle.bridge.getState();
|
|
188
179
|
const ready = state.documentState !== 'AwaitRemote';
|
|
189
180
|
let contentKey: string | null = null;
|
|
190
181
|
let snapshotHtml: string | null = null;
|
|
191
182
|
let snapshotJson: DocumentJSON | null = null;
|
|
183
|
+
|
|
192
184
|
if (ready) {
|
|
193
185
|
const snapshot = handle.bridge.getContentSnapshot();
|
|
194
186
|
snapshotHtml = snapshot.html;
|
|
195
187
|
snapshotJson = snapshot.json;
|
|
196
188
|
contentKey = JSON.stringify(snapshot.json);
|
|
197
189
|
}
|
|
198
|
-
|
|
190
|
+
|
|
191
|
+
if (currentEditorIdRef.current !== refreshedEditorId) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
199
195
|
const historyState: HistoryState = {
|
|
200
196
|
canUndo: state.canUndo,
|
|
201
197
|
canRedo: state.canRedo,
|
|
202
198
|
};
|
|
199
|
+
|
|
203
200
|
readyRef.current = { editorId: refreshedEditorId, ready };
|
|
201
|
+
|
|
204
202
|
setEngineView({
|
|
205
203
|
editorId: refreshedEditorId,
|
|
206
204
|
ready,
|
|
@@ -213,12 +211,14 @@ export function useNativeEditorDocument(
|
|
|
213
211
|
|
|
214
212
|
const historyKey = `${state.canUndo}/${state.canRedo}`;
|
|
215
213
|
const emitted = emittedRef.current;
|
|
214
|
+
|
|
216
215
|
if (
|
|
217
216
|
currentEditorIdRef.current !== refreshedEditorId ||
|
|
218
217
|
emitted.editorId !== refreshedEditorId
|
|
219
218
|
) {
|
|
220
219
|
return;
|
|
221
220
|
}
|
|
221
|
+
|
|
222
222
|
if (historyKey !== emitted.lastHistoryKey) {
|
|
223
223
|
emitted.lastHistoryKey = historyKey;
|
|
224
224
|
onHistoryStateChangeRef.current?.(historyState);
|
|
@@ -226,6 +226,7 @@ export function useNativeEditorDocument(
|
|
|
226
226
|
|
|
227
227
|
if (ready) {
|
|
228
228
|
const lastRevision = emitted.lastRevision;
|
|
229
|
+
|
|
229
230
|
if (
|
|
230
231
|
emitContentCallbacks &&
|
|
231
232
|
lastRevision != null &&
|
|
@@ -239,10 +240,15 @@ export function useNativeEditorDocument(
|
|
|
239
240
|
) {
|
|
240
241
|
const echoes = pendingControlledEchoesRef.current.keys;
|
|
241
242
|
echoes.push(`html:${snapshotHtml}`);
|
|
242
|
-
|
|
243
|
+
|
|
244
|
+
if (echoes.length > 32) {
|
|
245
|
+
echoes.shift();
|
|
246
|
+
}
|
|
243
247
|
}
|
|
248
|
+
|
|
244
249
|
onContentChangeRef.current?.(snapshotHtml);
|
|
245
250
|
}
|
|
251
|
+
|
|
246
252
|
if (snapshotJson != null) {
|
|
247
253
|
if (
|
|
248
254
|
controlledKindRef.current === 'json' &&
|
|
@@ -250,86 +256,121 @@ export function useNativeEditorDocument(
|
|
|
250
256
|
) {
|
|
251
257
|
const echoes = pendingControlledEchoesRef.current.keys;
|
|
252
258
|
echoes.push(`json:${contentKey}`);
|
|
253
|
-
|
|
259
|
+
|
|
260
|
+
if (echoes.length > 32) {
|
|
261
|
+
echoes.shift();
|
|
262
|
+
}
|
|
254
263
|
}
|
|
264
|
+
|
|
255
265
|
onContentChangeJSONRef.current?.(snapshotJson);
|
|
256
266
|
}
|
|
257
267
|
}
|
|
268
|
+
|
|
258
269
|
emitted.lastRevision = state.documentRevision;
|
|
259
270
|
emitted.lastContentKey = contentKey;
|
|
260
271
|
}
|
|
261
272
|
},
|
|
262
|
-
[handle]
|
|
273
|
+
[ handle ]
|
|
263
274
|
);
|
|
264
275
|
|
|
265
276
|
useEffect(() => {
|
|
266
277
|
refresh(true);
|
|
267
|
-
}, [refresh, revisionSignal]);
|
|
278
|
+
}, [ refresh, revisionSignal ]);
|
|
268
279
|
|
|
269
280
|
const refreshFromEngine = useCallback(() => {
|
|
270
281
|
refresh(true);
|
|
271
|
-
}, [refresh]);
|
|
282
|
+
}, [ refresh ]);
|
|
272
283
|
|
|
273
284
|
const serializedControlledJson = useMemo(
|
|
274
285
|
() => (value == null && valueJSON != null ? JSON.stringify(valueJSON) : null),
|
|
275
|
-
[value, valueJSON]
|
|
286
|
+
[ value, valueJSON ]
|
|
276
287
|
);
|
|
277
288
|
|
|
278
289
|
// Controlled document flow: external value/valueJSON changes lower to one
|
|
279
290
|
// local-API replacement carrying the rendered engine revision as its
|
|
280
291
|
// base. Content callbacks stay suppressed (controlled sync parity).
|
|
281
292
|
useEffect(() => {
|
|
282
|
-
if (engineView.editorId !== editorId || !engineView.ready || handle.isDestroyed)
|
|
283
|
-
|
|
293
|
+
if (engineView.editorId !== editorId || !engineView.ready || handle.isDestroyed) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (engineView.documentRevision == null) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
284
301
|
const wantsHtml = value != null;
|
|
285
302
|
const wantsJson = value == null && serializedControlledJson != null;
|
|
286
|
-
|
|
303
|
+
|
|
304
|
+
if (!wantsHtml && !wantsJson) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
287
307
|
|
|
288
308
|
const controlledContentKey = wantsHtml
|
|
289
309
|
? `html:${value}`
|
|
290
310
|
: `json:${serializedControlledJson}`;
|
|
311
|
+
|
|
291
312
|
const controlledContentChanged =
|
|
292
313
|
lastControlledContentKeyRef.current !== controlledContentKey;
|
|
314
|
+
|
|
293
315
|
if (controlledContentChanged) {
|
|
294
316
|
lastControlledContentKeyRef.current = controlledContentKey;
|
|
295
317
|
ignoredControlledEchoRef.current = null;
|
|
296
318
|
}
|
|
297
|
-
|
|
298
|
-
if (
|
|
319
|
+
|
|
320
|
+
if (ignoredControlledEchoRef.current === controlledContentKey) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!controlledContentChanged && pendingControlledEchoesRef.current.keys.length > 0) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
299
327
|
|
|
300
328
|
const snapshot = handle.bridge.getContentSnapshot();
|
|
329
|
+
|
|
301
330
|
if (currentEditorIdRef.current !== editorId || emittedRef.current.editorId !== editorId) {
|
|
302
331
|
return;
|
|
303
332
|
}
|
|
333
|
+
|
|
304
334
|
const currentJsonKey = JSON.stringify(snapshot.json);
|
|
335
|
+
|
|
305
336
|
if (wantsHtml && snapshot.html === value) {
|
|
306
337
|
const echoIndex = pendingControlledEchoesRef.current.keys.indexOf(controlledContentKey);
|
|
338
|
+
|
|
307
339
|
if (echoIndex >= 0) {
|
|
308
340
|
pendingControlledEchoesRef.current.keys.splice(0, echoIndex + 1);
|
|
309
341
|
}
|
|
342
|
+
|
|
310
343
|
emittedRef.current.lastRevision = engineView.documentRevision;
|
|
311
344
|
emittedRef.current.lastContentKey = currentJsonKey;
|
|
345
|
+
|
|
312
346
|
return;
|
|
313
347
|
}
|
|
348
|
+
|
|
314
349
|
if (wantsJson && currentJsonKey === serializedControlledJson) {
|
|
315
350
|
const echoIndex = pendingControlledEchoesRef.current.keys.indexOf(controlledContentKey);
|
|
351
|
+
|
|
316
352
|
if (echoIndex >= 0) {
|
|
317
353
|
pendingControlledEchoesRef.current.keys.splice(0, echoIndex + 1);
|
|
318
354
|
}
|
|
355
|
+
|
|
319
356
|
emittedRef.current.lastRevision = engineView.documentRevision;
|
|
320
357
|
emittedRef.current.lastContentKey = currentJsonKey;
|
|
358
|
+
|
|
321
359
|
return;
|
|
322
360
|
}
|
|
323
361
|
|
|
324
362
|
const echoIndex = pendingControlledEchoesRef.current.keys.indexOf(controlledContentKey);
|
|
363
|
+
|
|
325
364
|
if (echoIndex >= 0) {
|
|
326
365
|
pendingControlledEchoesRef.current.keys.splice(0, echoIndex + 1);
|
|
327
366
|
ignoredControlledEchoRef.current = controlledContentKey;
|
|
367
|
+
|
|
328
368
|
return;
|
|
329
369
|
}
|
|
330
370
|
|
|
331
371
|
try {
|
|
332
372
|
pendingControlledEchoesRef.current.keys = [];
|
|
373
|
+
|
|
333
374
|
handle.bridge.applyLocalApi({
|
|
334
375
|
...(wantsHtml
|
|
335
376
|
? { setHtml: value }
|
|
@@ -337,7 +378,11 @@ export function useNativeEditorDocument(
|
|
|
337
378
|
history: valueJSONUpdateMode === 'reset' ? 'resetAndClear' : 'undoableBoundary',
|
|
338
379
|
baseDocumentRevision: engineView.documentRevision,
|
|
339
380
|
});
|
|
340
|
-
|
|
381
|
+
|
|
382
|
+
if (currentEditorIdRef.current !== editorId) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
341
386
|
onLocalCommitRef.current?.();
|
|
342
387
|
refresh(false);
|
|
343
388
|
} catch (error) {
|
|
@@ -345,8 +390,10 @@ export function useNativeEditorDocument(
|
|
|
345
390
|
// Refresh from the engine; the effect re-applies against the
|
|
346
391
|
// fresh revision (never a guessed one).
|
|
347
392
|
refresh(false);
|
|
393
|
+
|
|
348
394
|
return;
|
|
349
395
|
}
|
|
396
|
+
|
|
350
397
|
throw error;
|
|
351
398
|
}
|
|
352
399
|
}, [
|
|
@@ -374,32 +421,36 @@ export function useNativeEditorDocument(
|
|
|
374
421
|
}
|
|
375
422
|
);
|
|
376
423
|
}
|
|
424
|
+
|
|
377
425
|
return;
|
|
378
426
|
}
|
|
427
|
+
|
|
379
428
|
operation();
|
|
380
|
-
|
|
429
|
+
|
|
430
|
+
if (currentEditorIdRef.current !== handle.editorId) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
381
434
|
onLocalCommitRef.current?.();
|
|
382
435
|
refresh(true);
|
|
383
436
|
},
|
|
384
|
-
[handle, refresh]
|
|
437
|
+
[ handle, refresh ]
|
|
385
438
|
);
|
|
386
439
|
|
|
387
440
|
const setContent = useCallback(
|
|
388
441
|
(html: string) => {
|
|
389
442
|
mutate(() =>
|
|
390
|
-
handle.bridge.replaceDocument({ setHtml: html, history: 'undoableBoundary' })
|
|
391
|
-
);
|
|
443
|
+
handle.bridge.replaceDocument({ setHtml: html, history: 'undoableBoundary' }));
|
|
392
444
|
},
|
|
393
|
-
[handle, mutate]
|
|
445
|
+
[ handle, mutate ]
|
|
394
446
|
);
|
|
395
447
|
|
|
396
448
|
const setContentJson = useCallback(
|
|
397
449
|
(doc: DocumentJSON) => {
|
|
398
450
|
mutate(() =>
|
|
399
|
-
handle.bridge.replaceDocument({ setJson: doc, history: 'undoableBoundary' })
|
|
400
|
-
);
|
|
451
|
+
handle.bridge.replaceDocument({ setJson: doc, history: 'undoableBoundary' }));
|
|
401
452
|
},
|
|
402
|
-
[handle, mutate]
|
|
453
|
+
[ handle, mutate ]
|
|
403
454
|
);
|
|
404
455
|
|
|
405
456
|
const clearContent = useCallback(() => {
|
|
@@ -409,15 +460,15 @@ export function useNativeEditorDocument(
|
|
|
409
460
|
history: 'resetAndClear',
|
|
410
461
|
});
|
|
411
462
|
});
|
|
412
|
-
}, [handle, mutate]);
|
|
463
|
+
}, [ handle, mutate ]);
|
|
413
464
|
|
|
414
465
|
const undo = useCallback(() => {
|
|
415
466
|
mutate(() => handle.bridge.undo());
|
|
416
|
-
}, [handle, mutate]);
|
|
467
|
+
}, [ handle, mutate ]);
|
|
417
468
|
|
|
418
469
|
const redo = useCallback(() => {
|
|
419
470
|
mutate(() => handle.bridge.redo());
|
|
420
|
-
}, [handle, mutate]);
|
|
471
|
+
}, [ handle, mutate ]);
|
|
421
472
|
|
|
422
473
|
const getContent = useCallback((): string => {
|
|
423
474
|
if (
|
|
@@ -427,8 +478,9 @@ export function useNativeEditorDocument(
|
|
|
427
478
|
) {
|
|
428
479
|
return '';
|
|
429
480
|
}
|
|
481
|
+
|
|
430
482
|
return handle.bridge.getDocumentHtml();
|
|
431
|
-
}, [handle]);
|
|
483
|
+
}, [ handle ]);
|
|
432
484
|
|
|
433
485
|
const getContentJson = useCallback((): DocumentJSON => {
|
|
434
486
|
if (
|
|
@@ -438,8 +490,9 @@ export function useNativeEditorDocument(
|
|
|
438
490
|
) {
|
|
439
491
|
return {};
|
|
440
492
|
}
|
|
493
|
+
|
|
441
494
|
return handle.bridge.getDocumentJson();
|
|
442
|
-
}, [handle]);
|
|
495
|
+
}, [ handle ]);
|
|
443
496
|
|
|
444
497
|
const getIsEmpty = useCallback((): boolean => {
|
|
445
498
|
if (
|
|
@@ -449,8 +502,9 @@ export function useNativeEditorDocument(
|
|
|
449
502
|
) {
|
|
450
503
|
return true;
|
|
451
504
|
}
|
|
505
|
+
|
|
452
506
|
return handle.bridge.renderUpdate().documentIsEmpty;
|
|
453
|
-
}, [handle]);
|
|
507
|
+
}, [ handle ]);
|
|
454
508
|
|
|
455
509
|
const getTextContent = useCallback((): string => {
|
|
456
510
|
if (
|
|
@@ -460,8 +514,9 @@ export function useNativeEditorDocument(
|
|
|
460
514
|
) {
|
|
461
515
|
return '';
|
|
462
516
|
}
|
|
517
|
+
|
|
463
518
|
return handle.bridge.getDocumentHtml().replace(/<[^>]+>/g, '');
|
|
464
|
-
}, [handle]);
|
|
519
|
+
}, [ handle ]);
|
|
465
520
|
|
|
466
521
|
const canUndo = useCallback((): boolean => {
|
|
467
522
|
if (
|
|
@@ -471,8 +526,9 @@ export function useNativeEditorDocument(
|
|
|
471
526
|
) {
|
|
472
527
|
return false;
|
|
473
528
|
}
|
|
529
|
+
|
|
474
530
|
return handle.bridge.getState().canUndo;
|
|
475
|
-
}, [handle]);
|
|
531
|
+
}, [ handle ]);
|
|
476
532
|
|
|
477
533
|
const canRedo = useCallback((): boolean => {
|
|
478
534
|
if (
|
|
@@ -482,10 +538,12 @@ export function useNativeEditorDocument(
|
|
|
482
538
|
) {
|
|
483
539
|
return false;
|
|
484
540
|
}
|
|
541
|
+
|
|
485
542
|
return handle.bridge.getState().canRedo;
|
|
486
|
-
}, [handle]);
|
|
543
|
+
}, [ handle ]);
|
|
487
544
|
|
|
488
545
|
let renderedEngineView = engineView;
|
|
546
|
+
|
|
489
547
|
if (engineView.editorId !== editorId) {
|
|
490
548
|
if (handle.isDestroyed) {
|
|
491
549
|
renderedEngineView = {
|
|
@@ -499,6 +557,7 @@ export function useNativeEditorDocument(
|
|
|
499
557
|
};
|
|
500
558
|
} else {
|
|
501
559
|
const state = handle.bridge.getState();
|
|
560
|
+
|
|
502
561
|
renderedEngineView = {
|
|
503
562
|
editorId,
|
|
504
563
|
ready: state.documentState !== 'AwaitRemote',
|
|
@@ -509,6 +568,7 @@ export function useNativeEditorDocument(
|
|
|
509
568
|
contentKey: null,
|
|
510
569
|
};
|
|
511
570
|
}
|
|
571
|
+
|
|
512
572
|
readyRef.current = { editorId, ready: renderedEngineView.ready };
|
|
513
573
|
}
|
|
514
574
|
|