@apollohg/react-native-prose-editor 0.5.20 → 0.5.21
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 +23 -1
- package/android/build.gradle +5 -6
- package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +50 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +167 -16
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +539 -73
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +14 -0
- package/app.plugin.js +62 -0
- package/dist/EditorToolbar.d.ts +3 -2
- package/dist/EditorToolbar.js +41 -13
- package/dist/NativeRichTextEditor.d.ts +9 -0
- package/dist/NativeRichTextEditor.js +252 -81
- package/dist/YjsCollaboration.d.ts +5 -0
- package/dist/YjsCollaboration.js +44 -8
- package/dist/index.d.ts +1 -1
- 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/ios/NativeEditorExpoView.swift +49 -2
- package/package.json +5 -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
|
@@ -500,7 +500,17 @@ class NativeEditorModule : Module() {
|
|
|
500
500
|
Prop("editorUpdateRevision") { view: NativeEditorExpoView, editorUpdateRevision: Int ->
|
|
501
501
|
view.setPendingEditorUpdateRevision(editorUpdateRevision)
|
|
502
502
|
}
|
|
503
|
+
Prop("editorResetUpdateJson") { view: NativeEditorExpoView, editorResetUpdateJson: String? ->
|
|
504
|
+
view.setPendingEditorResetUpdateJson(editorResetUpdateJson)
|
|
505
|
+
}
|
|
506
|
+
Prop("editorResetUpdateEditorId") { view: NativeEditorExpoView, editorResetUpdateEditorId: Int? ->
|
|
507
|
+
view.setPendingEditorResetUpdateEditorId(editorResetUpdateEditorId?.let { nativeULong(it)?.toLong() })
|
|
508
|
+
}
|
|
509
|
+
Prop("editorResetUpdateRevision") { view: NativeEditorExpoView, editorResetUpdateRevision: Int ->
|
|
510
|
+
view.setPendingEditorResetUpdateRevision(editorResetUpdateRevision)
|
|
511
|
+
}
|
|
503
512
|
OnViewDidUpdateProps { view: NativeEditorExpoView ->
|
|
513
|
+
view.applyPendingEditorResetUpdateIfNeeded()
|
|
504
514
|
view.applyPendingEditorUpdateIfNeeded()
|
|
505
515
|
}
|
|
506
516
|
|
|
@@ -519,6 +529,10 @@ class NativeEditorModule : Module() {
|
|
|
519
529
|
view.applyEditorUpdate(updateJson)
|
|
520
530
|
}
|
|
521
531
|
|
|
532
|
+
AsyncFunction("applyEditorResetUpdate") { view: NativeEditorExpoView, updateJson: String ->
|
|
533
|
+
view.applyEditorResetUpdate(updateJson)
|
|
534
|
+
}
|
|
535
|
+
|
|
522
536
|
}
|
|
523
537
|
|
|
524
538
|
View(NativeProseViewerExpoView::class) {
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { createRequire } = require('module');
|
|
3
|
+
|
|
4
|
+
const pkg = require('./package.json');
|
|
5
|
+
|
|
6
|
+
const PACKAGING_EXCLUDES_PROPERTY = 'android.packagingOptions.excludes';
|
|
7
|
+
const LEGACY_JNA_ABI_EXCLUDES = [
|
|
8
|
+
'**/armeabi/libjnidispatch.so',
|
|
9
|
+
'**/mips/libjnidispatch.so',
|
|
10
|
+
'**/mips64/libjnidispatch.so',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function requireExpoConfigPlugins() {
|
|
14
|
+
try {
|
|
15
|
+
return require('expo/config-plugins');
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (error.code !== 'MODULE_NOT_FOUND') {
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const appRequire = createRequire(path.join(process.cwd(), 'package.json'));
|
|
22
|
+
return appRequire('expo/config-plugins');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function mergeCommaList(value, additions) {
|
|
27
|
+
const values = (value || '')
|
|
28
|
+
.split(',')
|
|
29
|
+
.map((item) => item.trim())
|
|
30
|
+
.filter(Boolean);
|
|
31
|
+
|
|
32
|
+
return Array.from(new Set([...values, ...additions])).join(',');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function withReactNativeProseEditor(config) {
|
|
36
|
+
const { withGradleProperties } = requireExpoConfigPlugins();
|
|
37
|
+
|
|
38
|
+
return withGradleProperties(config, (config) => {
|
|
39
|
+
const existing = config.modResults.find(
|
|
40
|
+
(property) => property.type === 'property' && property.key === PACKAGING_EXCLUDES_PROPERTY
|
|
41
|
+
);
|
|
42
|
+
const value = mergeCommaList(existing && existing.value, LEGACY_JNA_ABI_EXCLUDES);
|
|
43
|
+
|
|
44
|
+
if (existing) {
|
|
45
|
+
existing.value = value;
|
|
46
|
+
} else {
|
|
47
|
+
config.modResults.push({
|
|
48
|
+
type: 'property',
|
|
49
|
+
key: PACKAGING_EXCLUDES_PROPERTY,
|
|
50
|
+
value,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return config;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = requireExpoConfigPlugins().createRunOncePlugin(
|
|
59
|
+
withReactNativeProseEditor,
|
|
60
|
+
pkg.name,
|
|
61
|
+
pkg.version
|
|
62
|
+
);
|
package/dist/EditorToolbar.d.ts
CHANGED
|
@@ -107,9 +107,10 @@ interface EditorToolbarMentionState {
|
|
|
107
107
|
onSelectSuggestion: (suggestion: MentionSuggestion) => void;
|
|
108
108
|
}
|
|
109
109
|
export declare function isEditorToolbarFocusPreservationActive(): boolean;
|
|
110
|
-
export declare function
|
|
110
|
+
export declare function setActiveEditorToolbarFrameOwnerForEditor(ownerId: number, isActive: boolean): void;
|
|
111
|
+
export declare function useEditorToolbarFrames(ownerId: number): readonly EditorToolbarFrame[];
|
|
111
112
|
export declare function setEditorToolbarMentionState(ownerId: number, state: Omit<EditorToolbarMentionState, 'ownerId'> | null): void;
|
|
112
|
-
export declare function _setEditorToolbarFrameForTests(id: number, frame: EditorToolbarFrame | null): void;
|
|
113
|
+
export declare function _setEditorToolbarFrameForTests(id: number, frame: EditorToolbarFrame | null, ownerId?: number | null): void;
|
|
113
114
|
export declare function _resetEditorToolbarFrameRegistryForTests(): void;
|
|
114
115
|
export declare function _beginEditorToolbarInteractionForTests(): void;
|
|
115
116
|
export declare function _endEditorToolbarInteractionForTests(): void;
|
package/dist/EditorToolbar.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEFAULT_EDITOR_TOOLBAR_ITEMS = void 0;
|
|
4
4
|
exports.isEditorToolbarFocusPreservationActive = isEditorToolbarFocusPreservationActive;
|
|
5
|
+
exports.setActiveEditorToolbarFrameOwnerForEditor = setActiveEditorToolbarFrameOwnerForEditor;
|
|
5
6
|
exports.useEditorToolbarFrames = useEditorToolbarFrames;
|
|
6
7
|
exports.setEditorToolbarMentionState = setEditorToolbarMentionState;
|
|
7
8
|
exports._setEditorToolbarFrameForTests = _setEditorToolbarFrameForTests;
|
|
@@ -19,6 +20,7 @@ const editorToolbarMentionStateListeners = new Set();
|
|
|
19
20
|
let nextEditorToolbarRegistrationId = 1;
|
|
20
21
|
let activeEditorToolbarInteractions = 0;
|
|
21
22
|
let editorToolbarFocusPreserveUntil = 0;
|
|
23
|
+
let activeEditorToolbarFrameOwnerId = null;
|
|
22
24
|
let editorToolbarMentionState = null;
|
|
23
25
|
const EDITOR_TOOLBAR_FOCUS_PRESERVE_MS = 750;
|
|
24
26
|
function areToolbarFramesEqual(left, right) {
|
|
@@ -27,14 +29,23 @@ function areToolbarFramesEqual(left, right) {
|
|
|
27
29
|
left?.width === right?.width &&
|
|
28
30
|
left?.height === right?.height);
|
|
29
31
|
}
|
|
32
|
+
function areToolbarFrameListsEqual(left, right) {
|
|
33
|
+
if (left.length !== right.length) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return left.every((frame, index) => areToolbarFramesEqual(frame, right[index]));
|
|
37
|
+
}
|
|
30
38
|
function notifyEditorToolbarFrameListeners() {
|
|
31
39
|
editorToolbarFrameListeners.forEach((listener) => listener());
|
|
32
40
|
}
|
|
33
41
|
function notifyEditorToolbarMentionStateListeners() {
|
|
34
42
|
editorToolbarMentionStateListeners.forEach((listener) => listener());
|
|
35
43
|
}
|
|
36
|
-
function getEditorToolbarFramesSnapshot() {
|
|
37
|
-
return Array.from(editorToolbarFrames.values())
|
|
44
|
+
function getEditorToolbarFramesSnapshot(ownerId) {
|
|
45
|
+
return Array.from(editorToolbarFrames.values())
|
|
46
|
+
.filter((registration) => registration.ownerId === ownerId ||
|
|
47
|
+
(registration.ownerId == null && activeEditorToolbarFrameOwnerId === ownerId))
|
|
48
|
+
.map((registration) => registration.frame);
|
|
38
49
|
}
|
|
39
50
|
function subscribeEditorToolbarMentionState(listener) {
|
|
40
51
|
editorToolbarMentionStateListeners.add(listener);
|
|
@@ -48,18 +59,19 @@ function getEditorToolbarMentionStateSnapshot() {
|
|
|
48
59
|
function useEditorToolbarMentionState() {
|
|
49
60
|
return (0, react_1.useSyncExternalStore)(subscribeEditorToolbarMentionState, getEditorToolbarMentionStateSnapshot, getEditorToolbarMentionStateSnapshot);
|
|
50
61
|
}
|
|
51
|
-
function registerEditorToolbarFrame(id, frame) {
|
|
62
|
+
function registerEditorToolbarFrame(id, frame, ownerId) {
|
|
52
63
|
if (frame == null || frame.width <= 0 || frame.height <= 0) {
|
|
53
64
|
if (editorToolbarFrames.delete(id)) {
|
|
54
65
|
notifyEditorToolbarFrameListeners();
|
|
55
66
|
}
|
|
56
67
|
return;
|
|
57
68
|
}
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
69
|
+
const currentRegistration = editorToolbarFrames.get(id);
|
|
70
|
+
if (currentRegistration?.ownerId === ownerId &&
|
|
71
|
+
areToolbarFramesEqual(currentRegistration.frame, frame)) {
|
|
60
72
|
return;
|
|
61
73
|
}
|
|
62
|
-
editorToolbarFrames.set(id, frame);
|
|
74
|
+
editorToolbarFrames.set(id, { ownerId, frame });
|
|
63
75
|
notifyEditorToolbarFrameListeners();
|
|
64
76
|
}
|
|
65
77
|
function unregisterEditorToolbarFrame(id) {
|
|
@@ -81,16 +93,31 @@ function endEditorToolbarInteraction() {
|
|
|
81
93
|
function isEditorToolbarFocusPreservationActive() {
|
|
82
94
|
return activeEditorToolbarInteractions > 0 || Date.now() <= editorToolbarFocusPreserveUntil;
|
|
83
95
|
}
|
|
84
|
-
function
|
|
85
|
-
const
|
|
96
|
+
function setActiveEditorToolbarFrameOwnerForEditor(ownerId, isActive) {
|
|
97
|
+
const nextOwnerId = isActive
|
|
98
|
+
? ownerId
|
|
99
|
+
: activeEditorToolbarFrameOwnerId === ownerId
|
|
100
|
+
? null
|
|
101
|
+
: activeEditorToolbarFrameOwnerId;
|
|
102
|
+
if (activeEditorToolbarFrameOwnerId === nextOwnerId) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
activeEditorToolbarFrameOwnerId = nextOwnerId;
|
|
106
|
+
notifyEditorToolbarFrameListeners();
|
|
107
|
+
}
|
|
108
|
+
function useEditorToolbarFrames(ownerId) {
|
|
109
|
+
const [frames, setFrames] = (0, react_1.useState)(() => getEditorToolbarFramesSnapshot(ownerId));
|
|
86
110
|
(0, react_1.useEffect)(() => {
|
|
87
|
-
const listener = () =>
|
|
111
|
+
const listener = () => {
|
|
112
|
+
const nextFrames = getEditorToolbarFramesSnapshot(ownerId);
|
|
113
|
+
setFrames((currentFrames) => areToolbarFrameListsEqual(currentFrames, nextFrames) ? currentFrames : nextFrames);
|
|
114
|
+
};
|
|
88
115
|
editorToolbarFrameListeners.add(listener);
|
|
89
116
|
listener();
|
|
90
117
|
return () => {
|
|
91
118
|
editorToolbarFrameListeners.delete(listener);
|
|
92
119
|
};
|
|
93
|
-
}, []);
|
|
120
|
+
}, [ownerId]);
|
|
94
121
|
return frames;
|
|
95
122
|
}
|
|
96
123
|
function setEditorToolbarMentionState(ownerId, state) {
|
|
@@ -108,14 +135,15 @@ function setEditorToolbarMentionState(ownerId, state) {
|
|
|
108
135
|
};
|
|
109
136
|
notifyEditorToolbarMentionStateListeners();
|
|
110
137
|
}
|
|
111
|
-
function _setEditorToolbarFrameForTests(id, frame) {
|
|
112
|
-
registerEditorToolbarFrame(id, frame);
|
|
138
|
+
function _setEditorToolbarFrameForTests(id, frame, ownerId = null) {
|
|
139
|
+
registerEditorToolbarFrame(id, frame, ownerId);
|
|
113
140
|
}
|
|
114
141
|
function _resetEditorToolbarFrameRegistryForTests() {
|
|
115
142
|
editorToolbarFrames.clear();
|
|
116
143
|
editorToolbarMentionState = null;
|
|
117
144
|
activeEditorToolbarInteractions = 0;
|
|
118
145
|
editorToolbarFocusPreserveUntil = 0;
|
|
146
|
+
activeEditorToolbarFrameOwnerId = null;
|
|
119
147
|
notifyEditorToolbarFrameListeners();
|
|
120
148
|
notifyEditorToolbarMentionStateListeners();
|
|
121
149
|
}
|
|
@@ -518,7 +546,7 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
518
546
|
return;
|
|
519
547
|
}
|
|
520
548
|
toolbar.measureInWindow((x, y, width, height) => {
|
|
521
|
-
registerEditorToolbarFrame(registrationId, { x, y, width, height });
|
|
549
|
+
registerEditorToolbarFrame(registrationId, { x, y, width, height }, null);
|
|
522
550
|
});
|
|
523
551
|
}, [preserveEditorFocus]);
|
|
524
552
|
const cancelScheduledFramePublishes = (0, react_1.useCallback)(() => {
|
|
@@ -7,6 +7,7 @@ import { type EditorAddons } from './addons';
|
|
|
7
7
|
import { type ImageNodeAttributes, type SchemaDefinition } from './schemas';
|
|
8
8
|
export type NativeRichTextEditorHeightBehavior = 'fixed' | 'autoGrow';
|
|
9
9
|
export type NativeRichTextEditorToolbarPlacement = 'keyboard' | 'inline';
|
|
10
|
+
export type NativeRichTextEditorValueJSONUpdateMode = 'replace' | 'reset';
|
|
10
11
|
export type NativeRichTextEditorAutoCapitalize = 'none' | 'sentences' | 'words' | 'characters';
|
|
11
12
|
export type NativeRichTextEditorKeyboardType = 'default' | 'email-address' | 'numeric' | 'phone-pad' | 'ascii-capable' | 'numbers-and-punctuation' | 'url' | 'number-pad' | 'name-phone-pad' | 'decimal-pad' | 'twitter' | 'web-search' | 'visible-password' | 'ascii-capable-number-pad';
|
|
12
13
|
export interface RemoteSelectionDecoration {
|
|
@@ -41,6 +42,12 @@ export interface NativeRichTextEditorProps {
|
|
|
41
42
|
valueJSON?: DocumentJSON;
|
|
42
43
|
/** Optional stable revision hint for `valueJSON` to avoid reserializing equal docs on rerender. */
|
|
43
44
|
valueJSONRevision?: string | number;
|
|
45
|
+
/** Controls how external `valueJSON` changes are applied. Defaults to preserving undo history. */
|
|
46
|
+
valueJSONUpdateMode?: NativeRichTextEditorValueJSONUpdateMode;
|
|
47
|
+
/** When using reset-mode `valueJSON`, preserve the current local text selection after applying the reset. */
|
|
48
|
+
preserveSelectionOnValueJSONReset?: boolean;
|
|
49
|
+
/** Preferred selection to restore when applying reset-mode `valueJSON`; falls back to the live selection. */
|
|
50
|
+
selectionOnValueJSONReset?: Selection;
|
|
44
51
|
/** Schema definition. Defaults to tiptapSchema if not provided. */
|
|
45
52
|
schema?: SchemaDefinition;
|
|
46
53
|
/** Placeholder text shown when editor is empty. */
|
|
@@ -137,6 +144,8 @@ export interface NativeRichTextEditorRef {
|
|
|
137
144
|
setContent(html: string): void;
|
|
138
145
|
/** Replace entire document with JSON (preserves undo history). */
|
|
139
146
|
setContentJson(doc: DocumentJSON): void;
|
|
147
|
+
/** Clear the document to the active schema's empty text block. */
|
|
148
|
+
clearContent(): void;
|
|
140
149
|
/** Get the current HTML content. */
|
|
141
150
|
getContent(): string;
|
|
142
151
|
/** Get the current content as ProseMirror JSON. */
|