@openeditor/react-native-prose-editor 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +35 -0
- package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +2 -2
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +138 -597
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +13 -3
- package/android/src/main/java/com/apollohg/editor/InputSnapshotSupport.kt +6 -0
- package/android/src/main/java/com/apollohg/editor/NativeBlockEditorSurface.kt +1086 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +1 -1
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +26 -50
- package/android/src/main/java/com/apollohg/editor/NativeImageLoader.kt +89 -0
- package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +31 -66
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +98 -33
- package/dist/EditorToolbar.d.ts +1 -1
- package/dist/EditorToolbar.js +19 -0
- package/dist/NativeEditorBridge.d.ts +62 -35
- package/dist/NativeEditorBridge.js +122 -79
- package/dist/NativeProseViewer.d.ts +13 -34
- package/dist/NativeProseViewer.js +40 -465
- package/dist/NativeRichTextEditor.d.ts +10 -1
- package/dist/NativeRichTextEditor.js +72 -34
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/schemas.d.ts +11 -0
- package/dist/schemas.js +149 -0
- package/dist/useNativeEditor.d.ts +3 -3
- package/dist/useNativeEditor.js +3 -3
- 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/Generated_editor_core.swift +97 -2
- package/ios/NativeBlockEditorSurface.swift +1393 -0
- package/ios/NativeEditorExpoView.swift +3 -3
- package/ios/NativeEditorModule.swift +30 -45
- package/ios/NativeInputSupport.swift +396 -0
- package/ios/PositionBridge.swift +4 -4
- package/ios/RichTextEditorView.swift +333 -786
- package/ios/editor_coreFFI/editor_coreFFI.h +147 -70
- package/package.json +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_64/libeditor_core.so +0 -0
- package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +2087 -1399
- package/android/src/androidTest/AndroidManifest.xml +0 -8
- package/android/src/androidTest/java/com/apollohg/editor/MeasureHeightInstrumentedTest.kt +0 -53
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceCollaborationInitialSyncTest.kt +0 -241
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceImeRegressionTest.kt +0 -338
- package/android/src/androidTest/java/com/apollohg/editor/NativeDeviceOutsideTapTest.kt +0 -789
- package/android/src/androidTest/java/com/apollohg/editor/NativeDevicePerformanceTest.kt +0 -350
- package/android/src/androidTest/java/com/apollohg/editor/NativeEditorOutsideTapActivity.kt +0 -5
- package/android/src/main/java/com/apollohg/editor/NativeProseViewerExpoView.kt +0 -315
- package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +0 -2172
- package/android/src/sharedTest/java/com/apollohg/editor/NativePerformanceSupport.kt +0 -482
- package/android/src/test/java/com/apollohg/editor/CaretGeometryTest.kt +0 -137
- package/android/src/test/java/com/apollohg/editor/EditorEditTextHardwareKeyTest.kt +0 -321
- package/android/src/test/java/com/apollohg/editor/EditorInputConnectionTest.kt +0 -4202
- package/android/src/test/java/com/apollohg/editor/NativeEditorExpoViewTest.kt +0 -2581
- package/android/src/test/java/com/apollohg/editor/NativeEditorModuleTest.kt +0 -27
- package/android/src/test/java/com/apollohg/editor/NativePerformanceTest.kt +0 -197
- package/android/src/test/java/com/apollohg/editor/NativeProseViewerExpoViewTest.kt +0 -84
- package/android/src/test/java/com/apollohg/editor/NativeToolbarTest.kt +0 -544
- package/android/src/test/java/com/apollohg/editor/PositionBridgeTest.kt +0 -461
- package/android/src/test/java/com/apollohg/editor/RenderBridgeTest.kt +0 -2054
- package/android/src/test/java/com/apollohg/editor/RichTextEditorViewTest.kt +0 -1367
- package/ios/EditorLayoutManager.swift +0 -774
- package/ios/NativeProseViewerExpoView.swift +0 -276
- package/ios/RenderBridge.swift +0 -1708
- package/ios/Tests/NativePerformanceTests.swift +0 -687
- package/ios/Tests/PositionBridgeTests.swift +0 -706
- package/ios/Tests/RenderBridgeTests.swift +0 -2236
- package/ios/Tests/RichTextEditorViewTests.swift +0 -6548
|
@@ -3,478 +3,53 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.NativeProseViewer = NativeProseViewer;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const react_1 = require("react");
|
|
6
|
-
const expo_modules_core_1 = require("expo-modules-core");
|
|
7
6
|
const react_native_1 = require("react-native");
|
|
8
|
-
const
|
|
9
|
-
const EditorTheme_1 = require("./EditorTheme");
|
|
10
|
-
const heightCache_1 = require("./heightCache");
|
|
7
|
+
const NativeRichTextEditor_1 = require("./NativeRichTextEditor");
|
|
11
8
|
const schemas_1 = require("./schemas");
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (!nativeProseViewerModule) {
|
|
16
|
-
nativeProseViewerModule =
|
|
17
|
-
(0, expo_modules_core_1.requireNativeModule)('NativeEditor');
|
|
18
|
-
}
|
|
19
|
-
return nativeProseViewerModule;
|
|
20
|
-
}
|
|
21
|
-
const serializedJsonCache = new WeakMap();
|
|
22
|
-
const EMPTY_TEXT_BLOCK_PLACEHOLDER = '\u200B';
|
|
23
|
-
function stringifyCachedJson(value) {
|
|
24
|
-
if (value != null && typeof value === 'object') {
|
|
25
|
-
const cached = serializedJsonCache.get(value);
|
|
26
|
-
if (cached != null) {
|
|
27
|
-
return cached;
|
|
28
|
-
}
|
|
29
|
-
const serialized = JSON.stringify(value);
|
|
30
|
-
serializedJsonCache.set(value, serialized);
|
|
31
|
-
return serialized;
|
|
32
|
-
}
|
|
33
|
-
return JSON.stringify(value);
|
|
34
|
-
}
|
|
35
|
-
function looksLikeRenderElementsJson(json) {
|
|
36
|
-
for (let index = 0; index < json.length; index += 1) {
|
|
37
|
-
const char = json[index];
|
|
38
|
-
if (char === ' ' || char === '\n' || char === '\r' || char === '\t') {
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
return char === '[';
|
|
42
|
-
}
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
function unicodeScalarLength(text) {
|
|
46
|
-
let length = 0;
|
|
47
|
-
for (const _char of text) {
|
|
48
|
-
length += 1;
|
|
49
|
-
}
|
|
50
|
-
return length;
|
|
51
|
-
}
|
|
52
|
-
function normalizeMentionAttrs(node) {
|
|
53
|
-
if (node == null || typeof node !== 'object') {
|
|
54
|
-
return {};
|
|
55
|
-
}
|
|
56
|
-
const attrs = node.attrs;
|
|
57
|
-
if (attrs == null || typeof attrs !== 'object' || Array.isArray(attrs)) {
|
|
58
|
-
return {};
|
|
59
|
-
}
|
|
60
|
-
return attrs;
|
|
61
|
-
}
|
|
62
|
-
function baseMentionLabelFromAttrs(attrs) {
|
|
63
|
-
const label = attrs.label;
|
|
64
|
-
return typeof label === 'string' && label.length > 0 ? label : 'mention';
|
|
65
|
-
}
|
|
66
|
-
function resolveConfiguredMentionPrefix(prefix, mention) {
|
|
67
|
-
const rawPrefix = typeof prefix === 'function' ? prefix(mention) : prefix;
|
|
68
|
-
return typeof rawPrefix === 'string' && rawPrefix.length > 0 ? rawPrefix : undefined;
|
|
69
|
-
}
|
|
70
|
-
function mentionTriggerFromAttrs(attrs) {
|
|
71
|
-
const trigger = attrs.mentionSuggestionChar;
|
|
72
|
-
return typeof trigger === 'string' && trigger.length > 0 ? trigger : undefined;
|
|
73
|
-
}
|
|
74
|
-
function applyMentionPrefix(label, prefix) {
|
|
75
|
-
if (!prefix || label.startsWith(prefix)) {
|
|
76
|
-
return label;
|
|
77
|
-
}
|
|
78
|
-
return `${prefix}${label}`;
|
|
79
|
-
}
|
|
80
|
-
function resolveMentionRenderedLabel(mentionContext, prefix, trigger) {
|
|
81
|
-
if (prefix !== undefined) {
|
|
82
|
-
return applyMentionPrefix(mentionContext.label, resolveConfiguredMentionPrefix(prefix, mentionContext));
|
|
83
|
-
}
|
|
84
|
-
return applyMentionPrefix(mentionContext.label, trigger ?? mentionTriggerFromAttrs(mentionContext.attrs));
|
|
85
|
-
}
|
|
86
|
-
function collectMentionPayloadsByDocPos(document, mentionsAddon) {
|
|
87
|
-
const mentions = new Map();
|
|
88
|
-
const effectiveMentionPrefix = mentionsAddon?.prefix;
|
|
89
|
-
const effectiveResolveMentionTheme = mentionsAddon?.resolveTheme;
|
|
90
|
-
const defaultMentionTheme = mentionsAddon?.theme;
|
|
91
|
-
const trigger = mentionsAddon?.trigger?.trim() || undefined;
|
|
92
|
-
const visit = (node, pos, isRoot = false) => {
|
|
93
|
-
if (node == null || typeof node !== 'object') {
|
|
94
|
-
return pos;
|
|
95
|
-
}
|
|
96
|
-
const nodeRecord = node;
|
|
97
|
-
const nodeType = typeof nodeRecord.type === 'string' ? nodeRecord.type : '';
|
|
98
|
-
const content = Array.isArray(nodeRecord.content) ? nodeRecord.content : [];
|
|
99
|
-
if (nodeType === 'text') {
|
|
100
|
-
const text = typeof nodeRecord.text === 'string' ? nodeRecord.text : '';
|
|
101
|
-
return pos + unicodeScalarLength(text);
|
|
102
|
-
}
|
|
103
|
-
if (nodeType === 'mention') {
|
|
104
|
-
const attrs = normalizeMentionAttrs(nodeRecord);
|
|
105
|
-
const label = baseMentionLabelFromAttrs(attrs);
|
|
106
|
-
const mentionContext = { docPos: pos, label, attrs };
|
|
107
|
-
const renderedLabel = resolveMentionRenderedLabel(mentionContext, effectiveMentionPrefix, trigger);
|
|
108
|
-
const resolvedMentionTheme = effectiveResolveMentionTheme?.(mentionContext) ?? undefined;
|
|
109
|
-
const mentionTheme = defaultMentionTheme || resolvedMentionTheme
|
|
110
|
-
? {
|
|
111
|
-
...(defaultMentionTheme ?? {}),
|
|
112
|
-
...(resolvedMentionTheme ?? {}),
|
|
113
|
-
}
|
|
114
|
-
: undefined;
|
|
115
|
-
mentions.set(pos, {
|
|
116
|
-
...mentionContext,
|
|
117
|
-
renderedLabel,
|
|
118
|
-
mentionTheme,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
if (isRoot && nodeType === 'doc') {
|
|
122
|
-
let nextPos = pos;
|
|
123
|
-
for (const child of content) {
|
|
124
|
-
nextPos = visit(child, nextPos);
|
|
125
|
-
}
|
|
126
|
-
return nextPos;
|
|
127
|
-
}
|
|
128
|
-
if (content.length === 0) {
|
|
129
|
-
return pos + 1;
|
|
130
|
-
}
|
|
131
|
-
let nextPos = pos + 1;
|
|
132
|
-
for (const child of content) {
|
|
133
|
-
nextPos = visit(child, nextPos);
|
|
134
|
-
}
|
|
135
|
-
return nextPos + 1;
|
|
136
|
-
};
|
|
137
|
-
visit(document, 0, true);
|
|
138
|
-
return mentions;
|
|
139
|
-
}
|
|
140
|
-
function applyResolvedMentionRendering(renderJson, mentionPayloadsByDocPos) {
|
|
141
|
-
if (mentionPayloadsByDocPos.size === 0) {
|
|
142
|
-
return renderJson;
|
|
143
|
-
}
|
|
144
|
-
let parsedElements;
|
|
9
|
+
function parseDocument(content) {
|
|
10
|
+
if (typeof content !== 'string')
|
|
11
|
+
return content;
|
|
145
12
|
try {
|
|
146
|
-
|
|
13
|
+
const parsed = JSON.parse(content);
|
|
14
|
+
return parsed != null && typeof parsed === 'object'
|
|
15
|
+
? parsed
|
|
16
|
+
: { type: 'doc', content: [] };
|
|
147
17
|
}
|
|
148
18
|
catch {
|
|
149
|
-
return
|
|
150
|
-
}
|
|
151
|
-
if (!Array.isArray(parsedElements)) {
|
|
152
|
-
return renderJson;
|
|
19
|
+
return { type: 'doc', content: [] };
|
|
153
20
|
}
|
|
154
|
-
let didChange = false;
|
|
155
|
-
const nextElements = parsedElements.map((element) => {
|
|
156
|
-
if (element == null || typeof element !== 'object' || Array.isArray(element)) {
|
|
157
|
-
return element;
|
|
158
|
-
}
|
|
159
|
-
const renderElement = element;
|
|
160
|
-
if (renderElement.type !== 'opaqueInlineAtom' ||
|
|
161
|
-
renderElement.nodeType !== 'mention' ||
|
|
162
|
-
typeof renderElement.docPos !== 'number') {
|
|
163
|
-
return element;
|
|
164
|
-
}
|
|
165
|
-
const mention = mentionPayloadsByDocPos.get(renderElement.docPos);
|
|
166
|
-
if (!mention) {
|
|
167
|
-
return element;
|
|
168
|
-
}
|
|
169
|
-
let nextElement = renderElement;
|
|
170
|
-
if (renderElement.label !== mention.renderedLabel) {
|
|
171
|
-
nextElement = { ...nextElement, label: mention.renderedLabel };
|
|
172
|
-
didChange = true;
|
|
173
|
-
}
|
|
174
|
-
if (mention.mentionTheme && Object.keys(mention.mentionTheme).length > 0) {
|
|
175
|
-
nextElement =
|
|
176
|
-
nextElement === renderElement ? { ...nextElement } : nextElement;
|
|
177
|
-
nextElement.mentionTheme = mention.mentionTheme;
|
|
178
|
-
didChange = true;
|
|
179
|
-
}
|
|
180
|
-
return nextElement;
|
|
181
|
-
});
|
|
182
|
-
return didChange ? JSON.stringify(nextElements) : renderJson;
|
|
183
|
-
}
|
|
184
|
-
function isTopLevelSingleElementBlock(element) {
|
|
185
|
-
return element.type === 'voidBlock' || element.type === 'opaqueBlockAtom';
|
|
186
|
-
}
|
|
187
|
-
function isEmptyParagraphPlaceholderText(text) {
|
|
188
|
-
if (text.length === 0) {
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
return Array.from(text).every((char) => char === EMPTY_TEXT_BLOCK_PLACEHOLDER);
|
|
192
|
-
}
|
|
193
|
-
function isCollapsibleEmptyParagraphText(text) {
|
|
194
|
-
return Array.from(text).every((char) => char === EMPTY_TEXT_BLOCK_PLACEHOLDER);
|
|
195
21
|
}
|
|
196
|
-
function
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
parsedElements = JSON.parse(renderJson);
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
if (!Array.isArray(parsedElements)) {
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
if (parsedElements.length === 0) {
|
|
22
|
+
function isEmptyDocument(document) {
|
|
23
|
+
const root = document;
|
|
24
|
+
if (!Array.isArray(root.content) || root.content.length === 0)
|
|
208
25
|
return true;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const renderElement = element;
|
|
217
|
-
switch (renderElement.type) {
|
|
218
|
-
case 'blockStart':
|
|
219
|
-
if (paragraphIsOpen ||
|
|
220
|
-
renderElement.nodeType !== 'paragraph' ||
|
|
221
|
-
renderElement.depth !== 0) {
|
|
222
|
-
return false;
|
|
223
|
-
}
|
|
224
|
-
paragraphIsOpen = true;
|
|
225
|
-
hasParagraph = true;
|
|
226
|
-
break;
|
|
227
|
-
case 'textRun':
|
|
228
|
-
if (!paragraphIsOpen ||
|
|
229
|
-
typeof renderElement.text !== 'string' ||
|
|
230
|
-
!isCollapsibleEmptyParagraphText(renderElement.text)) {
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
break;
|
|
234
|
-
case 'blockEnd':
|
|
235
|
-
if (!paragraphIsOpen) {
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
paragraphIsOpen = false;
|
|
239
|
-
break;
|
|
240
|
-
default:
|
|
241
|
-
return false;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return hasParagraph && !paragraphIsOpen;
|
|
245
|
-
}
|
|
246
|
-
function isTrailingEmptyParagraphRange(elements, start, endExclusive) {
|
|
247
|
-
const startElement = elements[start];
|
|
248
|
-
const endElement = elements[endExclusive - 1];
|
|
249
|
-
if (startElement?.type !== 'blockStart' ||
|
|
250
|
-
startElement.nodeType !== 'paragraph' ||
|
|
251
|
-
startElement.depth !== 0 ||
|
|
252
|
-
endElement?.type !== 'blockEnd') {
|
|
253
|
-
return false;
|
|
254
|
-
}
|
|
255
|
-
const innerElements = elements.slice(start + 1, endExclusive - 1);
|
|
256
|
-
return (innerElements.length > 0 &&
|
|
257
|
-
innerElements.every((element) => element.type === 'textRun' &&
|
|
258
|
-
typeof element.text === 'string' &&
|
|
259
|
-
isEmptyParagraphPlaceholderText(element.text)));
|
|
260
|
-
}
|
|
261
|
-
function collapseTrailingEmptyParagraphRenderElements(renderJson) {
|
|
262
|
-
let parsedElements;
|
|
263
|
-
try {
|
|
264
|
-
parsedElements = JSON.parse(renderJson);
|
|
265
|
-
}
|
|
266
|
-
catch {
|
|
267
|
-
return renderJson;
|
|
268
|
-
}
|
|
269
|
-
if (!Array.isArray(parsedElements)) {
|
|
270
|
-
return renderJson;
|
|
271
|
-
}
|
|
272
|
-
const elements = parsedElements;
|
|
273
|
-
const topLevelRanges = [];
|
|
274
|
-
for (let index = 0; index < elements.length; index += 1) {
|
|
275
|
-
const element = elements[index];
|
|
276
|
-
if (!element || typeof element !== 'object' || Array.isArray(element)) {
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
if (element.type === 'blockStart' && element.depth === 0) {
|
|
280
|
-
let nestingDepth = 1;
|
|
281
|
-
let cursor = index + 1;
|
|
282
|
-
while (cursor < elements.length && nestingDepth > 0) {
|
|
283
|
-
const current = elements[cursor];
|
|
284
|
-
if (current?.type === 'blockStart') {
|
|
285
|
-
nestingDepth += 1;
|
|
286
|
-
}
|
|
287
|
-
else if (current?.type === 'blockEnd') {
|
|
288
|
-
nestingDepth -= 1;
|
|
289
|
-
}
|
|
290
|
-
cursor += 1;
|
|
291
|
-
}
|
|
292
|
-
if (nestingDepth !== 0) {
|
|
293
|
-
return renderJson;
|
|
294
|
-
}
|
|
295
|
-
topLevelRanges.push({ start: index, endExclusive: cursor });
|
|
296
|
-
index = cursor - 1;
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
if (isTopLevelSingleElementBlock(element)) {
|
|
300
|
-
topLevelRanges.push({ start: index, endExclusive: index + 1 });
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
if (topLevelRanges.length <= 1) {
|
|
304
|
-
return renderJson;
|
|
305
|
-
}
|
|
306
|
-
let trimStart = null;
|
|
307
|
-
for (let rangeIndex = topLevelRanges.length - 1; rangeIndex >= 1; rangeIndex -= 1) {
|
|
308
|
-
const range = topLevelRanges[rangeIndex];
|
|
309
|
-
if (!isTrailingEmptyParagraphRange(elements, range.start, range.endExclusive)) {
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
trimStart = range.start;
|
|
313
|
-
}
|
|
314
|
-
if (trimStart == null) {
|
|
315
|
-
return renderJson;
|
|
316
|
-
}
|
|
317
|
-
return JSON.stringify(elements.slice(0, trimStart));
|
|
318
|
-
}
|
|
319
|
-
function serializeDocumentInput(document, schema) {
|
|
320
|
-
if (typeof document === 'string') {
|
|
321
|
-
try {
|
|
322
|
-
const parsed = JSON.parse(document);
|
|
323
|
-
const normalizedDocument = (0, schemas_1.normalizeDocumentJson)(parsed, schema);
|
|
324
|
-
return {
|
|
325
|
-
normalizedDocument,
|
|
326
|
-
serializedContentJson: stringifyCachedJson(normalizedDocument),
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
catch {
|
|
330
|
-
return {
|
|
331
|
-
normalizedDocument: null,
|
|
332
|
-
serializedContentJson: document,
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
const normalizedDocument = (0, schemas_1.normalizeDocumentJson)(document, schema);
|
|
337
|
-
return {
|
|
338
|
-
normalizedDocument,
|
|
339
|
-
serializedContentJson: stringifyCachedJson(normalizedDocument),
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
function extractRenderError(json) {
|
|
343
|
-
try {
|
|
344
|
-
const parsed = JSON.parse(json);
|
|
345
|
-
if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
346
|
-
return null;
|
|
347
|
-
}
|
|
348
|
-
const error = parsed.error;
|
|
349
|
-
return typeof error === 'string' ? error : null;
|
|
350
|
-
}
|
|
351
|
-
catch {
|
|
352
|
-
return null;
|
|
353
|
-
}
|
|
26
|
+
return root.content.every((node) => {
|
|
27
|
+
if (node == null || typeof node !== 'object')
|
|
28
|
+
return true;
|
|
29
|
+
const record = node;
|
|
30
|
+
return record.type === 'paragraph' &&
|
|
31
|
+
(!Array.isArray(record.content) || record.content.length === 0);
|
|
32
|
+
});
|
|
354
33
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const configJson = JSON.stringify({
|
|
377
|
-
schema: documentSchema,
|
|
378
|
-
...(allowBase64Images ? { allowBase64Images } : {}),
|
|
379
|
-
});
|
|
380
|
-
const nextRenderJson = serializedContentJson != null
|
|
381
|
-
? getNativeProseViewerModule().renderDocumentJson(configJson, serializedContentJson)
|
|
382
|
-
: getNativeProseViewerModule().renderDocumentHtml(configJson, contentHTML ?? '');
|
|
383
|
-
const renderError = extractRenderError(nextRenderJson);
|
|
384
|
-
if (renderError != null) {
|
|
385
|
-
console.error(`NativeProseViewer: ${renderError}`);
|
|
386
|
-
return '[]';
|
|
387
|
-
}
|
|
388
|
-
if (looksLikeRenderElementsJson(nextRenderJson)) {
|
|
389
|
-
const collapsedRenderJson = collapseTrailingEmptyParagraphs
|
|
390
|
-
? collapseTrailingEmptyParagraphRenderElements(nextRenderJson)
|
|
391
|
-
: nextRenderJson;
|
|
392
|
-
return applyResolvedMentionRendering(collapsedRenderJson, mentionPayloadsByDocPos);
|
|
393
|
-
}
|
|
394
|
-
console.error('NativeProseViewer: native renderDocumentJson returned an invalid payload.');
|
|
395
|
-
return '[]';
|
|
396
|
-
}, [
|
|
397
|
-
allowBase64Images,
|
|
398
|
-
collapseTrailingEmptyParagraphs,
|
|
399
|
-
contentHTML,
|
|
400
|
-
documentSchema,
|
|
401
|
-
mentionPayloadsByDocPos,
|
|
402
|
-
serializedContentJson,
|
|
403
|
-
]);
|
|
404
|
-
const renderJsonIsCollapsedEmpty = (0, react_1.useMemo)(() => collapseTrailingEmptyParagraphs &&
|
|
405
|
-
renderElementsJsonContainsOnlyEmptyParagraphs(renderJson), [collapseTrailingEmptyParagraphs, renderJson]);
|
|
406
|
-
const [contentHeight, setContentHeight] = (0, react_1.useState)(null);
|
|
407
|
-
(0, react_1.useEffect)(() => {
|
|
408
|
-
setContentHeight(null);
|
|
409
|
-
}, [contentId]);
|
|
410
|
-
const renderJsonHash = (0, react_1.useMemo)(() => (0, heightCache_1.computeRenderJsonHash)(renderJson), [renderJson]);
|
|
411
|
-
const layoutContextKey = (0, react_1.useMemo)(() => containerWidth != null ? (0, heightCache_1.computeLayoutContextKey)(themeJson, containerWidth) : null, [themeJson, containerWidth]);
|
|
412
|
-
const preMeasuredHeight = (0, react_1.useMemo)(() => {
|
|
413
|
-
if (!contentId || layoutContextKey == null || containerWidth == null) {
|
|
414
|
-
return null;
|
|
415
|
-
}
|
|
416
|
-
const cached = (0, heightCache_1.getHeightCache)(contentId, layoutContextKey, renderJsonHash);
|
|
417
|
-
if (cached != null)
|
|
418
|
-
return cached;
|
|
419
|
-
const measured = getNativeProseViewerModule().measureContentHeight(renderJson, themeJson, containerWidth);
|
|
420
|
-
if (measured > 0) {
|
|
421
|
-
(0, heightCache_1.setHeightCache)(contentId, layoutContextKey, renderJsonHash, measured);
|
|
422
|
-
}
|
|
423
|
-
return measured > 0 ? measured : null;
|
|
424
|
-
}, [contentId, containerWidth, renderJson, themeJson, layoutContextKey, renderJsonHash]);
|
|
425
|
-
const handleContentHeightChange = (0, react_1.useCallback)((event) => {
|
|
426
|
-
const density = react_native_1.Platform.OS === 'android' ? react_native_1.PixelRatio.get() : 1;
|
|
427
|
-
const nextHeight = Math.ceil(event.nativeEvent.contentHeight / density);
|
|
428
|
-
if (nextHeight < 0)
|
|
429
|
-
return;
|
|
430
|
-
if (nextHeight === 0 && !renderJsonIsCollapsedEmpty)
|
|
431
|
-
return;
|
|
432
|
-
if (nextHeight === 0) {
|
|
433
|
-
setContentHeight((currentHeight) => currentHeight === 0 ? currentHeight : 0);
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
setContentHeight((currentHeight) => currentHeight === nextHeight ? currentHeight : nextHeight);
|
|
437
|
-
if (contentId && layoutContextKey != null) {
|
|
438
|
-
(0, heightCache_1.setHeightCache)(contentId, layoutContextKey, renderJsonHash, nextHeight);
|
|
439
|
-
}
|
|
440
|
-
}, [renderJsonIsCollapsedEmpty, contentId, layoutContextKey, renderJsonHash]);
|
|
441
|
-
const handlePressMention = (0, react_1.useCallback)((event) => {
|
|
442
|
-
if (!mentionPressHandler)
|
|
443
|
-
return;
|
|
444
|
-
const { docPos, label } = event.nativeEvent;
|
|
445
|
-
const resolvedMention = mentionPayloadsByDocPos.get(docPos);
|
|
446
|
-
mentionPressHandler({
|
|
447
|
-
docPos,
|
|
448
|
-
label: resolvedMention?.renderedLabel ?? label,
|
|
449
|
-
attrs: resolvedMention?.attrs ?? {},
|
|
450
|
-
});
|
|
451
|
-
}, [mentionPayloadsByDocPos, mentionPressHandler]);
|
|
452
|
-
const handlePressLink = (0, react_1.useCallback)((event) => {
|
|
453
|
-
if (!onPressLink)
|
|
454
|
-
return;
|
|
455
|
-
onPressLink({
|
|
456
|
-
href: event.nativeEvent.href,
|
|
457
|
-
text: event.nativeEvent.text,
|
|
458
|
-
});
|
|
459
|
-
}, [onPressLink]);
|
|
460
|
-
const nativeStyle = (0, react_1.useMemo)(() => {
|
|
461
|
-
let measuredStyle = null;
|
|
462
|
-
if (renderJsonIsCollapsedEmpty) {
|
|
463
|
-
measuredStyle = { height: 0, minHeight: 0 };
|
|
464
|
-
}
|
|
465
|
-
else if (contentHeight != null && contentHeight > 0) {
|
|
466
|
-
measuredStyle = { minHeight: contentHeight };
|
|
467
|
-
}
|
|
468
|
-
else if (preMeasuredHeight != null && preMeasuredHeight > 0) {
|
|
469
|
-
measuredStyle = { minHeight: preMeasuredHeight };
|
|
470
|
-
}
|
|
471
|
-
return [
|
|
472
|
-
{ minHeight: renderJsonIsCollapsedEmpty ? 0 : 1 },
|
|
473
|
-
style,
|
|
474
|
-
measuredStyle,
|
|
475
|
-
];
|
|
476
|
-
}, [contentHeight, preMeasuredHeight, renderJsonIsCollapsedEmpty, style]);
|
|
477
|
-
return ((0, jsx_runtime_1.jsx)(NativeProseViewerView, { style: nativeStyle, renderJson: renderJson, themeJson: themeJson, collapsesWhenEmpty: collapseTrailingEmptyParagraphs, enableLinkTaps: enableLinkTaps, interceptLinkTaps: typeof onPressLink === 'function', onContentHeightChange: handleContentHeightChange, onPressLink: typeof onPressLink === 'function' ? handlePressLink : undefined, onPressMention: typeof mentionPressHandler === 'function'
|
|
478
|
-
? handlePressMention
|
|
479
|
-
: undefined }));
|
|
34
|
+
/**
|
|
35
|
+
* Read-only presentation of the canonical block-editor surface.
|
|
36
|
+
*
|
|
37
|
+
* The viewer deliberately shares the editor's document parser, render tree,
|
|
38
|
+
* layout surface, theme implementation, and native views. Keeping it as a
|
|
39
|
+
* configured editor prevents the read-only product from becoming a second
|
|
40
|
+
* rendering engine with subtly different schema behavior.
|
|
41
|
+
*/
|
|
42
|
+
function NativeProseViewer(props) {
|
|
43
|
+
const { schema = schemas_1.tiptapSchema, theme, style, allowBase64Images = false, } = props;
|
|
44
|
+
const documentSchema = schema;
|
|
45
|
+
const document = (0, react_1.useMemo)(() => 'contentJSON' in props && props.contentJSON != null
|
|
46
|
+
? (0, schemas_1.normalizeDocumentJson)(parseDocument(props.contentJSON), documentSchema)
|
|
47
|
+
: undefined, [documentSchema, props.contentJSON, props.revision]);
|
|
48
|
+
if ('collapseWhenEmpty' in props &&
|
|
49
|
+
props.collapseWhenEmpty &&
|
|
50
|
+
document &&
|
|
51
|
+
isEmptyDocument(document)) {
|
|
52
|
+
return (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [style, { height: 0, minHeight: 0 }] });
|
|
53
|
+
}
|
|
54
|
+
return ((0, jsx_runtime_1.jsx)(NativeRichTextEditor_1.NativeRichTextEditor, { value: 'contentHTML' in props ? props.contentHTML : undefined, valueJSON: document, valueJSONRevision: props.revision, schema: documentSchema, editable: false, showToolbar: false, heightBehavior: "autoGrow", allowBase64Images: allowBase64Images, allowImageResizing: false, theme: theme, containerStyle: style }));
|
|
480
55
|
}
|
|
@@ -4,7 +4,7 @@ import { type ActiveState, type DocumentJSON, type HistoryState, type Selection
|
|
|
4
4
|
import { type EditorToolbarHeadingLevel, type EditorToolbarItem } from './EditorToolbar';
|
|
5
5
|
import { type EditorTheme } from './EditorTheme';
|
|
6
6
|
import { type EditorAddons } from './addons';
|
|
7
|
-
import { type ImageNodeAttributes, type SchemaDefinition } from './schemas';
|
|
7
|
+
import { type ColumnsFragmentOptions, type ImageNodeAttributes, type SchemaDefinition, type TableFragmentOptions } from './schemas';
|
|
8
8
|
export type NativeRichTextEditorHeightBehavior = 'fixed' | 'autoGrow';
|
|
9
9
|
export type NativeRichTextEditorToolbarPlacement = 'keyboard' | 'inline';
|
|
10
10
|
export type NativeRichTextEditorValueJSONUpdateMode = 'replace' | 'reset';
|
|
@@ -137,6 +137,15 @@ export interface NativeRichTextEditorRef {
|
|
|
137
137
|
insertNode(nodeType: string): void;
|
|
138
138
|
/** Insert a block image node with the given source and optional metadata. */
|
|
139
139
|
insertImage(src: string, attrs?: Omit<ImageNodeAttributes, 'src'>): void;
|
|
140
|
+
/** Insert a structured columns layout at the current selection. */
|
|
141
|
+
insertColumns(options?: ColumnsFragmentOptions): void;
|
|
142
|
+
/** Insert a structured table at the current selection. */
|
|
143
|
+
insertTable(options?: TableFragmentOptions): void;
|
|
144
|
+
insertTableRow(after?: boolean): void;
|
|
145
|
+
deleteTableRow(): void;
|
|
146
|
+
insertTableColumn(after?: boolean): void;
|
|
147
|
+
deleteTableColumn(): void;
|
|
148
|
+
moveTableCell(forward?: boolean): void;
|
|
140
149
|
/** Insert text at the current cursor position. */
|
|
141
150
|
insertText(text: string): void;
|
|
142
151
|
/** Insert HTML content at the current selection. */
|