@apollohg/react-native-prose-editor 0.5.3 → 0.5.4
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/src/main/java/com/apollohg/editor/EditorTheme.kt +6 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +5 -0
- package/android/src/main/java/com/apollohg/editor/NativeProseViewerExpoView.kt +104 -5
- package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +25 -3
- package/dist/EditorTheme.d.ts +3 -0
- package/dist/EditorToolbar.js +11 -6
- package/dist/NativeProseViewer.js +78 -8
- package/dist/NativeRichTextEditor.js +189 -30
- 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/EditorTheme.swift +6 -0
- package/ios/NativeEditorModule.swift +4 -0
- package/ios/NativeProseViewerExpoView.swift +96 -9
- package/ios/RenderBridge.swift +5 -0
- 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
|
@@ -55,6 +55,7 @@ data class EditorTextStyle(
|
|
|
55
55
|
|
|
56
56
|
data class EditorListTheme(
|
|
57
57
|
val indent: Float? = null,
|
|
58
|
+
val baseIndentMultiplier: Float? = null,
|
|
58
59
|
val itemSpacing: Float? = null,
|
|
59
60
|
val markerColor: Int? = null,
|
|
60
61
|
val markerScale: Float? = null
|
|
@@ -64,6 +65,7 @@ data class EditorListTheme(
|
|
|
64
65
|
json ?: return null
|
|
65
66
|
return EditorListTheme(
|
|
66
67
|
indent = json.optNullableFloat("indent"),
|
|
68
|
+
baseIndentMultiplier = json.optNullableFloat("baseIndentMultiplier"),
|
|
67
69
|
itemSpacing = json.optNullableFloat("itemSpacing"),
|
|
68
70
|
markerColor = parseColor(json.optNullableString("markerColor")),
|
|
69
71
|
markerScale = json.optNullableFloat("markerScale")
|
|
@@ -194,6 +196,8 @@ data class EditorToolbarTheme(
|
|
|
194
196
|
val borderColor: Int? = null,
|
|
195
197
|
val borderWidth: Float? = null,
|
|
196
198
|
val borderRadius: Float? = null,
|
|
199
|
+
val marginTop: Float? = null,
|
|
200
|
+
val showTopBorder: Boolean? = null,
|
|
197
201
|
val keyboardOffset: Float? = null,
|
|
198
202
|
val horizontalInset: Float? = null,
|
|
199
203
|
val separatorColor: Int? = null,
|
|
@@ -222,6 +226,8 @@ data class EditorToolbarTheme(
|
|
|
222
226
|
borderColor = parseColor(json.optNullableString("borderColor")),
|
|
223
227
|
borderWidth = json.optNullableFloat("borderWidth"),
|
|
224
228
|
borderRadius = json.optNullableFloat("borderRadius"),
|
|
229
|
+
marginTop = json.optNullableFloat("marginTop"),
|
|
230
|
+
showTopBorder = if (json.has("showTopBorder")) json.optBoolean("showTopBorder") else null,
|
|
225
231
|
keyboardOffset = json.optNullableFloat("keyboardOffset"),
|
|
226
232
|
horizontalInset = json.optNullableFloat("horizontalInset"),
|
|
227
233
|
separatorColor = parseColor(json.optNullableString("separatorColor")),
|
|
@@ -386,6 +386,11 @@ class NativeEditorModule : Module() {
|
|
|
386
386
|
Prop("themeJson") { view: NativeProseViewerExpoView, themeJson: String? ->
|
|
387
387
|
view.setThemeJson(themeJson)
|
|
388
388
|
}
|
|
389
|
+
Prop("collapsesWhenEmpty") {
|
|
390
|
+
view: NativeProseViewerExpoView,
|
|
391
|
+
collapsesWhenEmpty: Boolean? ->
|
|
392
|
+
view.setCollapsesWhenEmpty(collapsesWhenEmpty)
|
|
393
|
+
}
|
|
389
394
|
Prop("enableLinkTaps") { view: NativeProseViewerExpoView, enableLinkTaps: Boolean? ->
|
|
390
395
|
view.setEnableLinkTaps(enableLinkTaps)
|
|
391
396
|
}
|
|
@@ -10,6 +10,7 @@ import android.view.ViewGroup
|
|
|
10
10
|
import expo.modules.kotlin.AppContext
|
|
11
11
|
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
12
12
|
import expo.modules.kotlin.views.ExpoView
|
|
13
|
+
import org.json.JSONArray
|
|
13
14
|
|
|
14
15
|
class NativeProseViewerExpoView(
|
|
15
16
|
context: Context,
|
|
@@ -26,6 +27,8 @@ class NativeProseViewerExpoView(
|
|
|
26
27
|
private var lastRenderJson: String? = null
|
|
27
28
|
private var lastThemeJson: String? = null
|
|
28
29
|
private var lastEmittedContentHeight = 0
|
|
30
|
+
private var collapsesWhenEmpty = true
|
|
31
|
+
private var isCollapsedEmptyContent = false
|
|
29
32
|
private var enableLinkTaps = true
|
|
30
33
|
private var interceptLinkTaps = false
|
|
31
34
|
|
|
@@ -84,7 +87,7 @@ class NativeProseViewerExpoView(
|
|
|
84
87
|
fun setRenderJson(renderJson: String?) {
|
|
85
88
|
if (lastRenderJson == renderJson) return
|
|
86
89
|
lastRenderJson = renderJson
|
|
87
|
-
|
|
90
|
+
applyRenderJson()
|
|
88
91
|
post {
|
|
89
92
|
requestLayout()
|
|
90
93
|
emitContentHeightIfNeeded(force = true)
|
|
@@ -95,13 +98,22 @@ class NativeProseViewerExpoView(
|
|
|
95
98
|
if (lastThemeJson == themeJson) return
|
|
96
99
|
lastThemeJson = themeJson
|
|
97
100
|
proseView.applyTheme(EditorTheme.fromJson(themeJson))
|
|
98
|
-
|
|
101
|
+
applyRenderJson()
|
|
99
102
|
post {
|
|
100
103
|
requestLayout()
|
|
101
104
|
emitContentHeightIfNeeded(force = true)
|
|
102
105
|
}
|
|
103
106
|
}
|
|
104
107
|
|
|
108
|
+
fun setCollapsesWhenEmpty(collapsesWhenEmpty: Boolean?) {
|
|
109
|
+
val nextValue = collapsesWhenEmpty ?: true
|
|
110
|
+
if (this.collapsesWhenEmpty == nextValue) return
|
|
111
|
+
this.collapsesWhenEmpty = nextValue
|
|
112
|
+
updateCollapsedEmptyState()
|
|
113
|
+
requestLayout()
|
|
114
|
+
emitContentHeightIfNeeded(force = true)
|
|
115
|
+
}
|
|
116
|
+
|
|
105
117
|
fun setEnableLinkTaps(enableLinkTaps: Boolean?) {
|
|
106
118
|
this.enableLinkTaps = enableLinkTaps ?: true
|
|
107
119
|
}
|
|
@@ -111,6 +123,11 @@ class NativeProseViewerExpoView(
|
|
|
111
123
|
}
|
|
112
124
|
|
|
113
125
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
126
|
+
if (isCollapsedEmptyContent) {
|
|
127
|
+
setMeasuredDimension(resolveSize(0, widthMeasureSpec), 0)
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
114
131
|
val childWidthSpec = getChildMeasureSpec(
|
|
115
132
|
widthMeasureSpec,
|
|
116
133
|
paddingLeft + paddingRight,
|
|
@@ -131,6 +148,12 @@ class NativeProseViewerExpoView(
|
|
|
131
148
|
}
|
|
132
149
|
|
|
133
150
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
151
|
+
if (isCollapsedEmptyContent) {
|
|
152
|
+
proseView.layout(paddingLeft, paddingTop, right - left - paddingRight, paddingTop)
|
|
153
|
+
emitContentHeightIfNeeded()
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
134
157
|
val childLeft = paddingLeft
|
|
135
158
|
val childTop = paddingTop
|
|
136
159
|
proseView.layout(
|
|
@@ -142,10 +165,25 @@ class NativeProseViewerExpoView(
|
|
|
142
165
|
emitContentHeightIfNeeded()
|
|
143
166
|
}
|
|
144
167
|
|
|
168
|
+
private fun applyRenderJson() {
|
|
169
|
+
updateCollapsedEmptyState()
|
|
170
|
+
proseView.applyRenderJSON(lastRenderJson ?: "[]")
|
|
171
|
+
proseView.visibility = if (isCollapsedEmptyContent) View.GONE else View.VISIBLE
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private fun updateCollapsedEmptyState() {
|
|
175
|
+
isCollapsedEmptyContent = collapsesWhenEmpty &&
|
|
176
|
+
renderJsonContainsOnlyEmptyParagraphs(lastRenderJson ?: "[]")
|
|
177
|
+
proseView.visibility = if (isCollapsedEmptyContent) View.GONE else View.VISIBLE
|
|
178
|
+
}
|
|
179
|
+
|
|
145
180
|
private fun emitContentHeightIfNeeded(force: Boolean = false) {
|
|
146
|
-
val contentHeight = (
|
|
147
|
-
|
|
148
|
-
|
|
181
|
+
val contentHeight = if (isCollapsedEmptyContent) {
|
|
182
|
+
0
|
|
183
|
+
} else {
|
|
184
|
+
(measureContentHeightPx() + paddingTop + paddingBottom).coerceAtLeast(0)
|
|
185
|
+
}
|
|
186
|
+
if (contentHeight <= 0 && !isCollapsedEmptyContent) {
|
|
149
187
|
return
|
|
150
188
|
}
|
|
151
189
|
if (!force && contentHeight == lastEmittedContentHeight) {
|
|
@@ -156,6 +194,10 @@ class NativeProseViewerExpoView(
|
|
|
156
194
|
}
|
|
157
195
|
|
|
158
196
|
private fun measureContentHeightPx(): Int {
|
|
197
|
+
if (isCollapsedEmptyContent) {
|
|
198
|
+
return 0
|
|
199
|
+
}
|
|
200
|
+
|
|
159
201
|
val currentMeasuredHeight = proseView.measuredHeight
|
|
160
202
|
if (currentMeasuredHeight > 0 && proseView.layout != null) {
|
|
161
203
|
return currentMeasuredHeight
|
|
@@ -191,4 +233,61 @@ class NativeProseViewerExpoView(
|
|
|
191
233
|
true
|
|
192
234
|
}.getOrDefault(false)
|
|
193
235
|
}
|
|
236
|
+
|
|
237
|
+
companion object {
|
|
238
|
+
private const val EMPTY_TEXT_BLOCK_PLACEHOLDER = '\u200B'
|
|
239
|
+
|
|
240
|
+
internal fun renderJsonContainsOnlyEmptyParagraphs(renderJson: String): Boolean {
|
|
241
|
+
val elements = try {
|
|
242
|
+
JSONArray(renderJson)
|
|
243
|
+
} catch (_: Exception) {
|
|
244
|
+
return false
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (elements.length() == 0) {
|
|
248
|
+
return true
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
var hasParagraph = false
|
|
252
|
+
var paragraphIsOpen = false
|
|
253
|
+
|
|
254
|
+
for (index in 0 until elements.length()) {
|
|
255
|
+
val element = elements.optJSONObject(index) ?: return false
|
|
256
|
+
when (element.optString("type", "")) {
|
|
257
|
+
"blockStart" -> {
|
|
258
|
+
if (
|
|
259
|
+
paragraphIsOpen ||
|
|
260
|
+
element.optString("nodeType", "") != "paragraph" ||
|
|
261
|
+
element.optInt("depth", 0) != 0
|
|
262
|
+
) {
|
|
263
|
+
return false
|
|
264
|
+
}
|
|
265
|
+
paragraphIsOpen = true
|
|
266
|
+
hasParagraph = true
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
"textRun" -> {
|
|
270
|
+
val text = element.optString("text", "")
|
|
271
|
+
if (
|
|
272
|
+
!paragraphIsOpen ||
|
|
273
|
+
!text.all { it == EMPTY_TEXT_BLOCK_PLACEHOLDER }
|
|
274
|
+
) {
|
|
275
|
+
return false
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
"blockEnd" -> {
|
|
280
|
+
if (!paragraphIsOpen) {
|
|
281
|
+
return false
|
|
282
|
+
}
|
|
283
|
+
paragraphIsOpen = false
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
else -> return false
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return hasParagraph && !paragraphIsOpen
|
|
291
|
+
}
|
|
292
|
+
}
|
|
194
293
|
}
|
|
@@ -1465,6 +1465,9 @@ object RenderBridge {
|
|
|
1465
1465
|
val indent = calculateIndent(currentBlock, blockStack, theme, density)
|
|
1466
1466
|
val markerWidth = calculateMarkerWidth(density)
|
|
1467
1467
|
val quoteDepth = blockquoteDepth(blockStack)
|
|
1468
|
+
val indentPerDepth = (theme?.list?.indent ?: LayoutConstants.INDENT_PER_DEPTH) * density
|
|
1469
|
+
val listBaseIndentAdjustment =
|
|
1470
|
+
calculateListBaseIndentAdjustment(currentBlock, theme, density)
|
|
1468
1471
|
val quoteStripeColor = if (quoteDepth > 0) {
|
|
1469
1472
|
theme?.blockquote?.borderColor ?: Color.argb(
|
|
1470
1473
|
(Color.alpha(resolveInlineTextColor(blockStack, Color.BLACK, theme)) * 0.3f).toInt(),
|
|
@@ -1486,8 +1489,9 @@ object RenderBridge {
|
|
|
1486
1489
|
) * density
|
|
1487
1490
|
val blockquoteIndentPx = (quoteDepth * quoteIndent).toInt()
|
|
1488
1491
|
val quoteBaseIndent = if (quoteDepth > 0) {
|
|
1489
|
-
((currentBlock.depth *
|
|
1490
|
-
- (quoteDepth *
|
|
1492
|
+
((currentBlock.depth * indentPerDepth)
|
|
1493
|
+
- (quoteDepth * indentPerDepth)
|
|
1494
|
+
+ listBaseIndentAdjustment
|
|
1491
1495
|
+ ((quoteDepth - 1f) * quoteIndent)).toInt()
|
|
1492
1496
|
} else {
|
|
1493
1497
|
0
|
|
@@ -1666,7 +1670,25 @@ object RenderBridge {
|
|
|
1666
1670
|
(theme?.blockquote?.markerGap ?: LayoutConstants.BLOCKQUOTE_MARKER_GAP) +
|
|
1667
1671
|
(theme?.blockquote?.borderWidth ?: LayoutConstants.BLOCKQUOTE_BORDER_WIDTH)
|
|
1668
1672
|
) * density
|
|
1669
|
-
|
|
1673
|
+
val listBaseIndentAdjustment = calculateListBaseIndentAdjustment(context, theme, density)
|
|
1674
|
+
return (context.depth * indentPerDepth) -
|
|
1675
|
+
(quoteDepth * indentPerDepth) +
|
|
1676
|
+
listBaseIndentAdjustment +
|
|
1677
|
+
(quoteDepth * quoteIndent)
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
private fun calculateListBaseIndentAdjustment(
|
|
1681
|
+
context: BlockContext,
|
|
1682
|
+
theme: EditorTheme?,
|
|
1683
|
+
density: Float
|
|
1684
|
+
): Float {
|
|
1685
|
+
if (context.listContext == null) {
|
|
1686
|
+
return 0f
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
val indentPerDepth = (theme?.list?.indent ?: LayoutConstants.INDENT_PER_DEPTH) * density
|
|
1690
|
+
val listBaseIndentMultiplier = maxOf(theme?.list?.baseIndentMultiplier ?: 1f, 0f)
|
|
1691
|
+
return (listBaseIndentMultiplier - 1f) * indentPerDepth
|
|
1670
1692
|
}
|
|
1671
1693
|
|
|
1672
1694
|
private fun effectiveBlockContext(blockStack: List<BlockContext>): BlockContext? {
|
package/dist/EditorTheme.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ export interface EditorHeadingTheme {
|
|
|
36
36
|
}
|
|
37
37
|
export interface EditorListTheme {
|
|
38
38
|
indent?: number;
|
|
39
|
+
baseIndentMultiplier?: number;
|
|
39
40
|
itemSpacing?: number;
|
|
40
41
|
markerColor?: string;
|
|
41
42
|
markerScale?: number;
|
|
@@ -59,6 +60,8 @@ export interface EditorToolbarTheme {
|
|
|
59
60
|
borderColor?: string;
|
|
60
61
|
borderWidth?: number;
|
|
61
62
|
borderRadius?: number;
|
|
63
|
+
marginTop?: number;
|
|
64
|
+
showTopBorder?: boolean;
|
|
62
65
|
keyboardOffset?: number;
|
|
63
66
|
horizontalInset?: number;
|
|
64
67
|
separatorColor?: string;
|
package/dist/EditorToolbar.js
CHANGED
|
@@ -109,7 +109,7 @@ const DEFAULT_MATERIAL_ICONS = {
|
|
|
109
109
|
undo: 'undo',
|
|
110
110
|
redo: 'redo',
|
|
111
111
|
};
|
|
112
|
-
function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic, onToggleUnderline, onToggleStrike, onToggleBulletList, onToggleHeading, onToggleBlockquote, onToggleOrderedList, onIndentList, onOutdentList, onInsertHorizontalRule, onInsertLineBreak, onUndo, onRedo, onToggleMark, onToggleListType, onInsertNodeType, onRunCommand, onToolbarAction, onRequestLink, onRequestImage, toolbarItems = exports.DEFAULT_EDITOR_TOOLBAR_ITEMS, theme, showTopBorder
|
|
112
|
+
function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic, onToggleUnderline, onToggleStrike, onToggleBulletList, onToggleHeading, onToggleBlockquote, onToggleOrderedList, onIndentList, onOutdentList, onInsertHorizontalRule, onInsertLineBreak, onUndo, onRedo, onToggleMark, onToggleListType, onInsertNodeType, onRunCommand, onToolbarAction, onRequestLink, onRequestImage, toolbarItems = exports.DEFAULT_EDITOR_TOOLBAR_ITEMS, theme, showTopBorder, }) {
|
|
113
113
|
const marks = activeState.marks ?? {};
|
|
114
114
|
const nodes = activeState.nodes ?? {};
|
|
115
115
|
const commands = activeState.commands ?? {};
|
|
@@ -371,6 +371,7 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
371
371
|
groupsByKey: nextGroups,
|
|
372
372
|
};
|
|
373
373
|
}, [expandedGroupKey, menuState?.groupKey, resolveButton, toolbarItems]);
|
|
374
|
+
const resolvedShowTopBorder = showTopBorder ?? theme?.showTopBorder ?? true;
|
|
374
375
|
(0, react_1.useEffect)(() => {
|
|
375
376
|
if (expandedGroupKey != null && !groupsByKey.has(expandedGroupKey)) {
|
|
376
377
|
setExpandedGroupKey(null);
|
|
@@ -414,7 +415,7 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
414
415
|
});
|
|
415
416
|
});
|
|
416
417
|
}, []);
|
|
417
|
-
const menuGroup = menuState != null ? groupsByKey.get(menuState.groupKey) ?? null : null;
|
|
418
|
+
const menuGroup = menuState != null ? (groupsByKey.get(menuState.groupKey) ?? null) : null;
|
|
418
419
|
const menuHeight = menuGroup ? menuGroup.children.length * 40 + 16 : 0;
|
|
419
420
|
const menuTop = menuState == null
|
|
420
421
|
? 0
|
|
@@ -426,7 +427,11 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
426
427
|
const activeColor = theme?.buttonActiveColor ?? ACTIVE_COLOR;
|
|
427
428
|
const defaultColor = theme?.buttonColor ?? DEFAULT_COLOR;
|
|
428
429
|
const disabledColor = theme?.buttonDisabledColor ?? DISABLED_COLOR;
|
|
429
|
-
const color = button.isActive
|
|
430
|
+
const color = button.isActive
|
|
431
|
+
? activeColor
|
|
432
|
+
: button.isDisabled
|
|
433
|
+
? disabledColor
|
|
434
|
+
: defaultColor;
|
|
430
435
|
const anchorGroupKey = options?.anchorGroupKey;
|
|
431
436
|
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { ref: anchorGroupKey == null
|
|
432
437
|
? undefined
|
|
@@ -457,15 +462,15 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
457
462
|
] }, key));
|
|
458
463
|
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
|
|
459
464
|
styles.container,
|
|
460
|
-
!
|
|
465
|
+
!resolvedShowTopBorder && styles.containerWithoutTopBorder,
|
|
461
466
|
theme?.backgroundColor != null ? { backgroundColor: theme.backgroundColor } : null,
|
|
462
467
|
theme?.borderColor != null
|
|
463
|
-
?
|
|
468
|
+
? resolvedShowTopBorder
|
|
464
469
|
? { borderTopColor: theme.borderColor }
|
|
465
470
|
: null
|
|
466
471
|
: null,
|
|
467
472
|
theme?.borderWidth != null
|
|
468
|
-
?
|
|
473
|
+
? resolvedShowTopBorder
|
|
469
474
|
? { borderTopWidth: theme.borderWidth }
|
|
470
475
|
: null
|
|
471
476
|
: null,
|
|
@@ -168,6 +168,59 @@ function isEmptyParagraphPlaceholderText(text) {
|
|
|
168
168
|
}
|
|
169
169
|
return Array.from(text).every((char) => char === EMPTY_TEXT_BLOCK_PLACEHOLDER);
|
|
170
170
|
}
|
|
171
|
+
function isCollapsibleEmptyParagraphText(text) {
|
|
172
|
+
return Array.from(text).every((char) => char === EMPTY_TEXT_BLOCK_PLACEHOLDER);
|
|
173
|
+
}
|
|
174
|
+
function renderElementsJsonContainsOnlyEmptyParagraphs(renderJson) {
|
|
175
|
+
let parsedElements;
|
|
176
|
+
try {
|
|
177
|
+
parsedElements = JSON.parse(renderJson);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
if (!Array.isArray(parsedElements)) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
if (parsedElements.length === 0) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
let hasParagraph = false;
|
|
189
|
+
let paragraphIsOpen = false;
|
|
190
|
+
for (const element of parsedElements) {
|
|
191
|
+
if (element == null || typeof element !== 'object' || Array.isArray(element)) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
const renderElement = element;
|
|
195
|
+
switch (renderElement.type) {
|
|
196
|
+
case 'blockStart':
|
|
197
|
+
if (paragraphIsOpen ||
|
|
198
|
+
renderElement.nodeType !== 'paragraph' ||
|
|
199
|
+
renderElement.depth !== 0) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
paragraphIsOpen = true;
|
|
203
|
+
hasParagraph = true;
|
|
204
|
+
break;
|
|
205
|
+
case 'textRun':
|
|
206
|
+
if (!paragraphIsOpen ||
|
|
207
|
+
typeof renderElement.text !== 'string' ||
|
|
208
|
+
!isCollapsibleEmptyParagraphText(renderElement.text)) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
case 'blockEnd':
|
|
213
|
+
if (!paragraphIsOpen) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
paragraphIsOpen = false;
|
|
217
|
+
break;
|
|
218
|
+
default:
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return hasParagraph && !paragraphIsOpen;
|
|
223
|
+
}
|
|
171
224
|
function isTrailingEmptyParagraphRange(elements, start, endExclusive) {
|
|
172
225
|
const startElement = elements[start];
|
|
173
226
|
const endElement = elements[endExclusive - 1];
|
|
@@ -325,6 +378,8 @@ function NativeProseViewer({ ...props }) {
|
|
|
325
378
|
mentionPayloadsByDocPos,
|
|
326
379
|
serializedContentJson,
|
|
327
380
|
]);
|
|
381
|
+
const renderJsonIsCollapsedEmpty = (0, react_1.useMemo)(() => collapseTrailingEmptyParagraphs &&
|
|
382
|
+
renderElementsJsonContainsOnlyEmptyParagraphs(renderJson), [collapseTrailingEmptyParagraphs, renderJson]);
|
|
328
383
|
const [contentHeight, setContentHeight] = (0, react_1.useState)(null);
|
|
329
384
|
const allowContentHeightShrinkRef = (0, react_1.useRef)(true);
|
|
330
385
|
(0, react_1.useEffect)(() => {
|
|
@@ -332,8 +387,14 @@ function NativeProseViewer({ ...props }) {
|
|
|
332
387
|
}, [resolvedContentRevision, renderJson, themeJson]);
|
|
333
388
|
const handleContentHeightChange = (0, react_1.useCallback)((event) => {
|
|
334
389
|
const nextHeight = event.nativeEvent.contentHeight;
|
|
335
|
-
if (nextHeight
|
|
390
|
+
if (nextHeight < 0)
|
|
391
|
+
return;
|
|
392
|
+
if (nextHeight === 0 && !renderJsonIsCollapsedEmpty)
|
|
393
|
+
return;
|
|
394
|
+
if (nextHeight === 0) {
|
|
395
|
+
setContentHeight((currentHeight) => currentHeight === 0 ? currentHeight : 0);
|
|
336
396
|
return;
|
|
397
|
+
}
|
|
337
398
|
setContentHeight((currentHeight) => currentHeight == null ||
|
|
338
399
|
nextHeight >= currentHeight ||
|
|
339
400
|
allowContentHeightShrinkRef.current
|
|
@@ -344,7 +405,7 @@ function NativeProseViewer({ ...props }) {
|
|
|
344
405
|
: nextHeight;
|
|
345
406
|
})()
|
|
346
407
|
: currentHeight);
|
|
347
|
-
}, []);
|
|
408
|
+
}, [renderJsonIsCollapsedEmpty]);
|
|
348
409
|
const handlePressMention = (0, react_1.useCallback)((event) => {
|
|
349
410
|
if (!onPressMention)
|
|
350
411
|
return;
|
|
@@ -364,10 +425,19 @@ function NativeProseViewer({ ...props }) {
|
|
|
364
425
|
text: event.nativeEvent.text,
|
|
365
426
|
});
|
|
366
427
|
}, [onPressLink]);
|
|
367
|
-
const nativeStyle = (0, react_1.useMemo)(() =>
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
428
|
+
const nativeStyle = (0, react_1.useMemo)(() => {
|
|
429
|
+
let measuredStyle = null;
|
|
430
|
+
if (renderJsonIsCollapsedEmpty) {
|
|
431
|
+
measuredStyle = { height: 0, minHeight: 0 };
|
|
432
|
+
}
|
|
433
|
+
else if (contentHeight != null && contentHeight > 0) {
|
|
434
|
+
measuredStyle = { minHeight: contentHeight };
|
|
435
|
+
}
|
|
436
|
+
return [
|
|
437
|
+
{ minHeight: renderJsonIsCollapsedEmpty ? 0 : 1 },
|
|
438
|
+
style,
|
|
439
|
+
measuredStyle,
|
|
440
|
+
];
|
|
441
|
+
}, [contentHeight, renderJsonIsCollapsedEmpty, style]);
|
|
442
|
+
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 onPressMention === 'function' ? handlePressMention : undefined }));
|
|
373
443
|
}
|
|
@@ -16,6 +16,9 @@ const DEV_NATIVE_VIEW_KEY = __DEV__
|
|
|
16
16
|
: 'native-editor';
|
|
17
17
|
const LINK_TOOLBAR_ACTION_KEY = '__native-editor-link__';
|
|
18
18
|
const IMAGE_TOOLBAR_ACTION_KEY = '__native-editor-image__';
|
|
19
|
+
const DEFAULT_MENTION_TRIGGER = '@';
|
|
20
|
+
const MAX_INLINE_MENTION_SUGGESTIONS = 8;
|
|
21
|
+
const INLINE_TOOLBAR_BORDER_COLOR = '#E5E5EA';
|
|
19
22
|
function mapToolbarChildForNative(item, activeState, editable, onRequestLink, onRequestImage) {
|
|
20
23
|
if (item.type === 'link') {
|
|
21
24
|
return {
|
|
@@ -34,7 +37,9 @@ function mapToolbarChildForNative(item, activeState, editable, onRequestLink, on
|
|
|
34
37
|
label: item.label,
|
|
35
38
|
icon: item.icon,
|
|
36
39
|
isActive: false,
|
|
37
|
-
isDisabled: !editable ||
|
|
40
|
+
isDisabled: !editable ||
|
|
41
|
+
!onRequestImage ||
|
|
42
|
+
!activeState.insertableNodes.includes(schemas_1.IMAGE_NODE_NAME),
|
|
38
43
|
};
|
|
39
44
|
}
|
|
40
45
|
return item;
|
|
@@ -65,6 +70,34 @@ function isPromiseLike(value) {
|
|
|
65
70
|
function isRecord(value) {
|
|
66
71
|
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
67
72
|
}
|
|
73
|
+
function resolveMentionTrigger(addons) {
|
|
74
|
+
return addons?.mentions?.trigger?.trim() || DEFAULT_MENTION_TRIGGER;
|
|
75
|
+
}
|
|
76
|
+
function resolveMentionSuggestionLabel(suggestion, trigger) {
|
|
77
|
+
return suggestion.label?.trim() || `${trigger}${suggestion.title}`;
|
|
78
|
+
}
|
|
79
|
+
function filterMentionSuggestions(suggestions, query, trigger) {
|
|
80
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
81
|
+
const filtered = normalizedQuery.length === 0
|
|
82
|
+
? suggestions
|
|
83
|
+
: suggestions.filter((suggestion) => {
|
|
84
|
+
const label = resolveMentionSuggestionLabel(suggestion, trigger);
|
|
85
|
+
return (suggestion.title.toLowerCase().includes(normalizedQuery) ||
|
|
86
|
+
label.toLowerCase().includes(normalizedQuery) ||
|
|
87
|
+
suggestion.subtitle?.toLowerCase().includes(normalizedQuery) === true);
|
|
88
|
+
});
|
|
89
|
+
return filtered.slice(0, MAX_INLINE_MENTION_SUGGESTIONS);
|
|
90
|
+
}
|
|
91
|
+
function resolveMentionSuggestionAttrs(suggestion, trigger) {
|
|
92
|
+
const attrs = { ...(suggestion.attrs ?? {}) };
|
|
93
|
+
if (!('label' in attrs)) {
|
|
94
|
+
attrs.label = resolveMentionSuggestionLabel(suggestion, trigger);
|
|
95
|
+
}
|
|
96
|
+
if (!('mentionSuggestionChar' in attrs)) {
|
|
97
|
+
attrs.mentionSuggestionChar = trigger;
|
|
98
|
+
}
|
|
99
|
+
return attrs;
|
|
100
|
+
}
|
|
68
101
|
const AUTO_LINK_URL_REGEX = /(?:https?:\/\/|www\.)\S+/giu;
|
|
69
102
|
const AUTO_LINK_INLINE_PLACEHOLDER = '\uFFFC';
|
|
70
103
|
const AUTO_LINK_LEADING_BOUNDARY_CHARS = new Set(['(', '[', '{', '<', '"', "'"]);
|
|
@@ -142,7 +175,8 @@ function trimAutoLinkTrailingPunctuation(value) {
|
|
|
142
175
|
result = chars.join('');
|
|
143
176
|
continue;
|
|
144
177
|
}
|
|
145
|
-
if ((lastChar === '"' || lastChar === "'") &&
|
|
178
|
+
if ((lastChar === '"' || lastChar === "'") &&
|
|
179
|
+
countOccurrences(result, lastChar) % 2 !== 0) {
|
|
146
180
|
chars.pop();
|
|
147
181
|
result = chars.join('');
|
|
148
182
|
continue;
|
|
@@ -169,7 +203,9 @@ function isAutoLinkBoundaryChar(char) {
|
|
|
169
203
|
if (!char) {
|
|
170
204
|
return true;
|
|
171
205
|
}
|
|
172
|
-
return /\s/u.test(char) ||
|
|
206
|
+
return (/\s/u.test(char) ||
|
|
207
|
+
char === AUTO_LINK_INLINE_PLACEHOLDER ||
|
|
208
|
+
AUTO_LINK_LEADING_BOUNDARY_CHARS.has(char));
|
|
173
209
|
}
|
|
174
210
|
function isAutoLinkTrailingDelimiterChar(char) {
|
|
175
211
|
if (!char) {
|
|
@@ -236,13 +272,15 @@ function findAutoLinkCandidateInInlineBlock(block, cursorDocPos) {
|
|
|
236
272
|
return null;
|
|
237
273
|
}
|
|
238
274
|
let localIndex = 0;
|
|
239
|
-
while (localIndex < block.docPositions.length &&
|
|
275
|
+
while (localIndex < block.docPositions.length &&
|
|
276
|
+
block.docPositions[localIndex] < cursorDocPos) {
|
|
240
277
|
localIndex += 1;
|
|
241
278
|
}
|
|
242
279
|
if (localIndex === 0) {
|
|
243
280
|
return null;
|
|
244
281
|
}
|
|
245
|
-
if (cursorDocPos < block.contentEnd &&
|
|
282
|
+
if (cursorDocPos < block.contentEnd &&
|
|
283
|
+
!isAutoLinkTrailingDelimiterChar(block.chars[localIndex - 1])) {
|
|
246
284
|
return null;
|
|
247
285
|
}
|
|
248
286
|
const prefixChars = block.chars.slice(0, localIndex);
|
|
@@ -444,11 +482,14 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
444
482
|
canUndo: false,
|
|
445
483
|
canRedo: false,
|
|
446
484
|
});
|
|
485
|
+
const [mentionQueryEvent, setMentionQueryEvent] = (0, react_1.useState)(null);
|
|
447
486
|
// Selection and rendered text length refs (non-rendering state)
|
|
448
487
|
const selectionRef = (0, react_1.useRef)({ type: 'text', anchor: 0, head: 0 });
|
|
449
488
|
const renderedTextLengthRef = (0, react_1.useRef)(0);
|
|
450
489
|
const documentVersionRef = (0, react_1.useRef)(null);
|
|
451
490
|
const toolbarRef = (0, react_1.useRef)(null);
|
|
491
|
+
const mentionQueryEventRef = (0, react_1.useRef)(null);
|
|
492
|
+
mentionQueryEventRef.current = mentionQueryEvent;
|
|
452
493
|
const toolbarItemsSerializationCacheRef = (0, react_1.useRef)(null);
|
|
453
494
|
// Stable callback refs to avoid re-renders
|
|
454
495
|
const onContentChangeRef = (0, react_1.useRef)(onContentChange);
|
|
@@ -672,12 +713,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
672
713
|
setIsReady(false);
|
|
673
714
|
};
|
|
674
715
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
675
|
-
}, [
|
|
676
|
-
maxLength,
|
|
677
|
-
syncStateFromUpdate,
|
|
678
|
-
allowBase64Images,
|
|
679
|
-
serializedSchemaJson,
|
|
680
|
-
]);
|
|
716
|
+
}, [maxLength, syncStateFromUpdate, allowBase64Images, serializedSchemaJson]);
|
|
681
717
|
(0, react_1.useEffect)(() => {
|
|
682
718
|
if (value == null)
|
|
683
719
|
return;
|
|
@@ -803,6 +839,9 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
803
839
|
const handleFocusChange = (0, react_1.useCallback)((event) => {
|
|
804
840
|
const { isFocused: focused } = event.nativeEvent;
|
|
805
841
|
setIsFocused(focused);
|
|
842
|
+
if (!focused) {
|
|
843
|
+
setMentionQueryEvent(null);
|
|
844
|
+
}
|
|
806
845
|
if (focused) {
|
|
807
846
|
onFocusRef.current?.();
|
|
808
847
|
}
|
|
@@ -810,6 +849,12 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
810
849
|
onBlurRef.current?.();
|
|
811
850
|
}
|
|
812
851
|
}, []);
|
|
852
|
+
(0, react_1.useEffect)(() => {
|
|
853
|
+
if (addons?.mentions != null) {
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
setMentionQueryEvent(null);
|
|
857
|
+
}, [addons?.mentions]);
|
|
813
858
|
const handleContentHeightChange = (0, react_1.useCallback)((event) => {
|
|
814
859
|
if (heightBehavior !== 'autoGrow')
|
|
815
860
|
return;
|
|
@@ -897,6 +942,46 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
897
942
|
}
|
|
898
943
|
onToolbarAction?.(event.nativeEvent.key);
|
|
899
944
|
}, [onToolbarAction, openImageRequest, openLinkRequest]);
|
|
945
|
+
const resolveMentionSelectionAttrs = (0, react_1.useCallback)((selectionEvent) => {
|
|
946
|
+
let resolvedAttrs;
|
|
947
|
+
try {
|
|
948
|
+
resolvedAttrs =
|
|
949
|
+
addonsRef.current?.mentions?.resolveSelectionAttrs?.(selectionEvent);
|
|
950
|
+
}
|
|
951
|
+
catch (error) {
|
|
952
|
+
if (__DEV__) {
|
|
953
|
+
console.error('NativeRichTextEditor: mentions.resolveSelectionAttrs threw', error);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return isRecord(resolvedAttrs)
|
|
957
|
+
? { ...selectionEvent.attrs, ...resolvedAttrs }
|
|
958
|
+
: selectionEvent.attrs;
|
|
959
|
+
}, []);
|
|
960
|
+
const handleInlineMentionSuggestionPress = (0, react_1.useCallback)((suggestion) => {
|
|
961
|
+
const mentionQuery = mentionQueryEventRef.current;
|
|
962
|
+
const mentions = addonsRef.current?.mentions;
|
|
963
|
+
if (!mentionQuery ||
|
|
964
|
+
!mentions ||
|
|
965
|
+
!bridgeRef.current ||
|
|
966
|
+
bridgeRef.current.isDestroyed) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const attrs = resolveMentionSelectionAttrs({
|
|
970
|
+
trigger: mentionQuery.trigger,
|
|
971
|
+
suggestion,
|
|
972
|
+
attrs: resolveMentionSuggestionAttrs(suggestion, mentionQuery.trigger),
|
|
973
|
+
range: mentionQuery.range,
|
|
974
|
+
});
|
|
975
|
+
const update = runAndApply(() => bridgeRef.current?.insertContentJsonAtSelectionScalar(mentionQuery.range.anchor, mentionQuery.range.head, (0, addons_1.buildMentionFragmentJson)(attrs)) ?? null);
|
|
976
|
+
if (update) {
|
|
977
|
+
setMentionQueryEvent(null);
|
|
978
|
+
mentions.onSelect?.({
|
|
979
|
+
trigger: mentionQuery.trigger,
|
|
980
|
+
suggestion,
|
|
981
|
+
attrs,
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
}, [resolveMentionSelectionAttrs, runAndApply]);
|
|
900
985
|
const handleAddonEvent = (0, react_1.useCallback)((event) => {
|
|
901
986
|
let parsed = null;
|
|
902
987
|
try {
|
|
@@ -908,11 +993,18 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
908
993
|
if (!parsed)
|
|
909
994
|
return;
|
|
910
995
|
if (parsed.type === 'mentionsQueryChange') {
|
|
911
|
-
|
|
996
|
+
const nextEvent = {
|
|
912
997
|
query: parsed.query,
|
|
913
998
|
trigger: parsed.trigger,
|
|
914
999
|
range: parsed.range,
|
|
915
1000
|
isActive: parsed.isActive,
|
|
1001
|
+
};
|
|
1002
|
+
setMentionQueryEvent(parsed.isActive ? nextEvent : null);
|
|
1003
|
+
addonsRef.current?.mentions?.onQueryChange?.({
|
|
1004
|
+
query: nextEvent.query,
|
|
1005
|
+
trigger: nextEvent.trigger,
|
|
1006
|
+
range: nextEvent.range,
|
|
1007
|
+
isActive: nextEvent.isActive,
|
|
916
1008
|
});
|
|
917
1009
|
return;
|
|
918
1010
|
}
|
|
@@ -926,19 +1018,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
926
1018
|
attrs: parsed.attrs,
|
|
927
1019
|
range: parsed.range,
|
|
928
1020
|
};
|
|
929
|
-
|
|
930
|
-
try {
|
|
931
|
-
resolvedAttrs =
|
|
932
|
-
addonsRef.current?.mentions?.resolveSelectionAttrs?.(selectionEvent);
|
|
933
|
-
}
|
|
934
|
-
catch (error) {
|
|
935
|
-
if (__DEV__) {
|
|
936
|
-
console.error('NativeRichTextEditor: mentions.resolveSelectionAttrs threw', error);
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
const finalAttrs = isRecord(resolvedAttrs)
|
|
940
|
-
? { ...parsed.attrs, ...resolvedAttrs }
|
|
941
|
-
: parsed.attrs;
|
|
1021
|
+
const finalAttrs = resolveMentionSelectionAttrs(selectionEvent);
|
|
942
1022
|
const update = runAndApply(() => bridgeRef.current?.insertContentJsonAtSelectionScalar(parsed.range.anchor, parsed.range.head, (0, addons_1.buildMentionFragmentJson)(finalAttrs)) ?? null);
|
|
943
1023
|
if (update) {
|
|
944
1024
|
addonsRef.current?.mentions?.onSelect?.({
|
|
@@ -959,7 +1039,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
959
1039
|
attrs: parsed.attrs,
|
|
960
1040
|
});
|
|
961
1041
|
}
|
|
962
|
-
}, [runAndApply]);
|
|
1042
|
+
}, [resolveMentionSelectionAttrs, runAndApply]);
|
|
963
1043
|
(0, react_1.useImperativeHandle)(ref, () => ({
|
|
964
1044
|
focus() {
|
|
965
1045
|
nativeViewRef.current?.focus?.();
|
|
@@ -1092,6 +1172,25 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1092
1172
|
borderWidth: theme?.toolbar?.borderWidth,
|
|
1093
1173
|
borderRadius: theme?.toolbar?.borderRadius,
|
|
1094
1174
|
};
|
|
1175
|
+
const inlineToolbarMarginTop = theme?.toolbar?.marginTop ?? 8;
|
|
1176
|
+
const inlineToolbarShowTopBorder = theme?.toolbar?.showTopBorder ?? false;
|
|
1177
|
+
const inlineToolbarMentionTheme = theme?.mentions ?? addons?.mentions?.theme;
|
|
1178
|
+
const inlineToolbarContentTopBorderStyle = inlineToolbarShowTopBorder
|
|
1179
|
+
? {
|
|
1180
|
+
borderTopWidth: theme?.toolbar?.borderWidth ?? react_native_1.StyleSheet.hairlineWidth,
|
|
1181
|
+
borderTopColor: theme?.toolbar?.borderColor ?? INLINE_TOOLBAR_BORDER_COLOR,
|
|
1182
|
+
}
|
|
1183
|
+
: null;
|
|
1184
|
+
const inlineMentionSuggestions = toolbarPlacement === 'inline' &&
|
|
1185
|
+
isFocused &&
|
|
1186
|
+
mentionQueryEvent != null &&
|
|
1187
|
+
addons?.mentions != null
|
|
1188
|
+
? filterMentionSuggestions(addons.mentions.suggestions ?? [], mentionQueryEvent.query, mentionQueryEvent.trigger || resolveMentionTrigger(addons))
|
|
1189
|
+
: [];
|
|
1190
|
+
const shouldShowInlineMentionSuggestions = shouldRenderJsToolbar &&
|
|
1191
|
+
toolbarPlacement === 'inline' &&
|
|
1192
|
+
isFocused &&
|
|
1193
|
+
inlineMentionSuggestions.length > 0;
|
|
1095
1194
|
const containerMinHeight = react_native_1.StyleSheet.flatten(containerStyle)?.minHeight;
|
|
1096
1195
|
const nativeViewStyleParts = [];
|
|
1097
1196
|
if (containerMinHeight != null) {
|
|
@@ -1106,6 +1205,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1106
1205
|
const nativeViewStyle = nativeViewStyleParts.length <= 1 ? nativeViewStyleParts[0] : nativeViewStyleParts;
|
|
1107
1206
|
const jsToolbar = ((0, jsx_runtime_1.jsx)(react_native_1.View, { ref: toolbarRef, testID: 'native-editor-js-toolbar', style: [
|
|
1108
1207
|
styles.inlineToolbar,
|
|
1208
|
+
{ marginTop: inlineToolbarMarginTop },
|
|
1109
1209
|
inlineToolbarChrome.backgroundColor != null
|
|
1110
1210
|
? { backgroundColor: inlineToolbarChrome.backgroundColor }
|
|
1111
1211
|
: null,
|
|
@@ -1118,7 +1218,43 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1118
1218
|
inlineToolbarChrome.borderRadius != null
|
|
1119
1219
|
? { borderRadius: inlineToolbarChrome.borderRadius }
|
|
1120
1220
|
: null,
|
|
1121
|
-
], onLayout: updateToolbarFrame, children: (0, jsx_runtime_1.jsx)(
|
|
1221
|
+
], onLayout: updateToolbarFrame, children: shouldShowInlineMentionSuggestions ? ((0, jsx_runtime_1.jsx)(react_native_1.View, { testID: 'native-editor-inline-mention-suggestions', style: [
|
|
1222
|
+
styles.inlineMentionSuggestionsContainer,
|
|
1223
|
+
inlineToolbarContentTopBorderStyle,
|
|
1224
|
+
], children: (0, jsx_runtime_1.jsx)(react_native_1.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, contentContainerStyle: styles.inlineMentionSuggestionsContent, keyboardShouldPersistTaps: 'always', children: inlineMentionSuggestions.map((suggestion) => {
|
|
1225
|
+
const label = resolveMentionSuggestionLabel(suggestion, mentionQueryEvent?.trigger ?? resolveMentionTrigger(addons));
|
|
1226
|
+
return ((0, jsx_runtime_1.jsx)(react_native_1.Pressable, { testID: `native-editor-inline-mention-suggestion-${suggestion.key}`, onPress: () => handleInlineMentionSuggestionPress(suggestion), accessibilityRole: 'button', accessibilityLabel: label, style: ({ pressed }) => [
|
|
1227
|
+
styles.inlineMentionSuggestion,
|
|
1228
|
+
{
|
|
1229
|
+
backgroundColor: pressed
|
|
1230
|
+
? (inlineToolbarMentionTheme?.optionHighlightedBackgroundColor ??
|
|
1231
|
+
'rgba(0, 122, 255, 0.12)')
|
|
1232
|
+
: (inlineToolbarMentionTheme?.backgroundColor ??
|
|
1233
|
+
'#F2F2F7'),
|
|
1234
|
+
borderColor: inlineToolbarMentionTheme?.borderColor ??
|
|
1235
|
+
'transparent',
|
|
1236
|
+
borderWidth: inlineToolbarMentionTheme?.borderWidth ?? 0,
|
|
1237
|
+
borderRadius: inlineToolbarMentionTheme?.borderRadius ?? 12,
|
|
1238
|
+
},
|
|
1239
|
+
], children: ({ pressed }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { numberOfLines: 1, style: [
|
|
1240
|
+
styles.inlineMentionSuggestionTitle,
|
|
1241
|
+
{
|
|
1242
|
+
color: pressed
|
|
1243
|
+
? (inlineToolbarMentionTheme?.optionHighlightedTextColor ??
|
|
1244
|
+
inlineToolbarMentionTheme?.optionTextColor ??
|
|
1245
|
+
'#000000')
|
|
1246
|
+
: (inlineToolbarMentionTheme?.optionTextColor ??
|
|
1247
|
+
inlineToolbarMentionTheme?.textColor ??
|
|
1248
|
+
'#000000'),
|
|
1249
|
+
},
|
|
1250
|
+
], children: label }), suggestion.subtitle ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { numberOfLines: 1, style: [
|
|
1251
|
+
styles.inlineMentionSuggestionSubtitle,
|
|
1252
|
+
{
|
|
1253
|
+
color: inlineToolbarMentionTheme?.optionSecondaryTextColor ??
|
|
1254
|
+
'#8E8E93',
|
|
1255
|
+
},
|
|
1256
|
+
], children: suggestion.subtitle })) : null] })) }, suggestion.key));
|
|
1257
|
+
}) }) })) : ((0, jsx_runtime_1.jsx)(EditorToolbar_1.EditorToolbar, { activeState: activeState, historyState: historyState, toolbarItems: toolbarItems, theme: theme?.toolbar, showTopBorder: inlineToolbarShowTopBorder, onToggleMark: (mark) => runAndApply(() => bridgeRef.current?.toggleMark(mark) ?? null, {
|
|
1122
1258
|
skipNativeApplyIfContentUnchanged: true,
|
|
1123
1259
|
}), onToggleListType: (listType) => runAndApply(() => bridgeRef.current?.toggleList(listType) ?? null), onToggleHeading: (level) => runAndApply(() => bridgeRef.current?.toggleHeading(level) ?? null), onToggleBlockquote: () => runAndApply(() => bridgeRef.current?.toggleBlockquote() ?? null), onInsertNodeType: (nodeType) => runAndApply(() => bridgeRef.current?.insertNode(nodeType) ?? null), onRunCommand: (command) => {
|
|
1124
1260
|
switch (command) {
|
|
@@ -1143,7 +1279,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
1143
1279
|
skipNativeApplyIfContentUnchanged: true,
|
|
1144
1280
|
}), onToggleStrike: () => runAndApply(() => bridgeRef.current?.toggleMark('strike') ?? null, {
|
|
1145
1281
|
skipNativeApplyIfContentUnchanged: true,
|
|
1146
|
-
}), onToggleBulletList: () => runAndApply(() => bridgeRef.current?.toggleList('bulletList') ?? null), onToggleOrderedList: () => runAndApply(() => bridgeRef.current?.toggleList('orderedList') ?? null), onIndentList: () => runAndApply(() => bridgeRef.current?.indentListItem() ?? null), onOutdentList: () => runAndApply(() => bridgeRef.current?.outdentListItem() ?? null), onInsertHorizontalRule: () => runAndApply(() => bridgeRef.current?.insertNode('horizontalRule') ?? null), onInsertLineBreak: () => runAndApply(() => bridgeRef.current?.insertNode('hardBreak') ?? null), onUndo: () => runAndApply(() => bridgeRef.current?.undo() ?? null), onRedo: () => runAndApply(() => bridgeRef.current?.redo() ?? null) }) }));
|
|
1282
|
+
}), onToggleBulletList: () => runAndApply(() => bridgeRef.current?.toggleList('bulletList') ?? null), onToggleOrderedList: () => runAndApply(() => bridgeRef.current?.toggleList('orderedList') ?? null), onIndentList: () => runAndApply(() => bridgeRef.current?.indentListItem() ?? null), onOutdentList: () => runAndApply(() => bridgeRef.current?.outdentListItem() ?? null), onInsertHorizontalRule: () => runAndApply(() => bridgeRef.current?.insertNode('horizontalRule') ?? null), onInsertLineBreak: () => runAndApply(() => bridgeRef.current?.insertNode('hardBreak') ?? null), onUndo: () => runAndApply(() => bridgeRef.current?.undo() ?? null), onRedo: () => runAndApply(() => bridgeRef.current?.redo() ?? null) })) }));
|
|
1147
1283
|
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, containerStyle], children: [(0, jsx_runtime_1.jsx)(NativeEditorView, { ref: nativeViewRef, style: nativeViewStyle, editorId: editorInstanceId, placeholder: placeholder, editable: editable, autoFocus: autoFocus, showToolbar: showToolbar, toolbarPlacement: toolbarPlacement, heightBehavior: heightBehavior, allowImageResizing: allowImageResizing, themeJson: themeJson, addonsJson: addonsJson, toolbarItemsJson: toolbarItemsJson, remoteSelectionsJson: remoteSelectionsJson, toolbarFrameJson: toolbarPlacement === 'inline' && isFocused ? toolbarFrameJson : undefined, editorUpdateJson: pendingNativeUpdate.json, editorUpdateRevision: pendingNativeUpdate.revision, onEditorUpdate: handleUpdate, onSelectionChange: handleSelectionChange, onFocusChange: handleFocusChange, onContentHeightChange: handleContentHeightChange, onToolbarAction: handleToolbarAction, onAddonEvent: handleAddonEvent }, DEV_NATIVE_VIEW_KEY), shouldRenderJsToolbar && jsToolbar] }));
|
|
1148
1284
|
});
|
|
1149
1285
|
const styles = react_native_1.StyleSheet.create({
|
|
@@ -1151,9 +1287,32 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
1151
1287
|
position: 'relative',
|
|
1152
1288
|
},
|
|
1153
1289
|
inlineToolbar: {
|
|
1154
|
-
marginTop: 8,
|
|
1155
1290
|
borderWidth: react_native_1.StyleSheet.hairlineWidth,
|
|
1156
|
-
borderColor:
|
|
1291
|
+
borderColor: INLINE_TOOLBAR_BORDER_COLOR,
|
|
1292
|
+
overflow: 'hidden',
|
|
1293
|
+
},
|
|
1294
|
+
inlineMentionSuggestionsContainer: {
|
|
1157
1295
|
overflow: 'hidden',
|
|
1158
1296
|
},
|
|
1297
|
+
inlineMentionSuggestionsContent: {
|
|
1298
|
+
paddingHorizontal: 12,
|
|
1299
|
+
paddingVertical: 8,
|
|
1300
|
+
alignItems: 'center',
|
|
1301
|
+
},
|
|
1302
|
+
inlineMentionSuggestion: {
|
|
1303
|
+
minWidth: 88,
|
|
1304
|
+
minHeight: 40,
|
|
1305
|
+
marginRight: 8,
|
|
1306
|
+
paddingHorizontal: 12,
|
|
1307
|
+
paddingVertical: 8,
|
|
1308
|
+
justifyContent: 'center',
|
|
1309
|
+
},
|
|
1310
|
+
inlineMentionSuggestionTitle: {
|
|
1311
|
+
fontSize: 14,
|
|
1312
|
+
fontWeight: '600',
|
|
1313
|
+
},
|
|
1314
|
+
inlineMentionSuggestionSubtitle: {
|
|
1315
|
+
marginTop: 1,
|
|
1316
|
+
fontSize: 12,
|
|
1317
|
+
},
|
|
1159
1318
|
});
|
|
@@ -8,32 +8,32 @@
|
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
9
|
<string>libeditor_core.a</string>
|
|
10
10
|
<key>LibraryIdentifier</key>
|
|
11
|
-
<string>ios-
|
|
11
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
12
12
|
<key>LibraryPath</key>
|
|
13
13
|
<string>libeditor_core.a</string>
|
|
14
14
|
<key>SupportedArchitectures</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>arm64</string>
|
|
17
|
+
<string>x86_64</string>
|
|
17
18
|
</array>
|
|
18
19
|
<key>SupportedPlatform</key>
|
|
19
20
|
<string>ios</string>
|
|
21
|
+
<key>SupportedPlatformVariant</key>
|
|
22
|
+
<string>simulator</string>
|
|
20
23
|
</dict>
|
|
21
24
|
<dict>
|
|
22
25
|
<key>BinaryPath</key>
|
|
23
26
|
<string>libeditor_core.a</string>
|
|
24
27
|
<key>LibraryIdentifier</key>
|
|
25
|
-
<string>ios-
|
|
28
|
+
<string>ios-arm64</string>
|
|
26
29
|
<key>LibraryPath</key>
|
|
27
30
|
<string>libeditor_core.a</string>
|
|
28
31
|
<key>SupportedArchitectures</key>
|
|
29
32
|
<array>
|
|
30
33
|
<string>arm64</string>
|
|
31
|
-
<string>x86_64</string>
|
|
32
34
|
</array>
|
|
33
35
|
<key>SupportedPlatform</key>
|
|
34
36
|
<string>ios</string>
|
|
35
|
-
<key>SupportedPlatformVariant</key>
|
|
36
|
-
<string>simulator</string>
|
|
37
37
|
</dict>
|
|
38
38
|
</array>
|
|
39
39
|
<key>CFBundlePackageType</key>
|
|
Binary file
|
|
Binary file
|
package/ios/EditorTheme.swift
CHANGED
|
@@ -80,12 +80,14 @@ struct EditorTextStyle {
|
|
|
80
80
|
|
|
81
81
|
struct EditorListTheme {
|
|
82
82
|
var indent: CGFloat?
|
|
83
|
+
var baseIndentMultiplier: CGFloat?
|
|
83
84
|
var itemSpacing: CGFloat?
|
|
84
85
|
var markerColor: UIColor?
|
|
85
86
|
var markerScale: CGFloat?
|
|
86
87
|
|
|
87
88
|
init(dictionary: [String: Any]) {
|
|
88
89
|
indent = EditorTheme.cgFloat(dictionary["indent"])
|
|
90
|
+
baseIndentMultiplier = EditorTheme.cgFloat(dictionary["baseIndentMultiplier"])
|
|
89
91
|
itemSpacing = EditorTheme.cgFloat(dictionary["itemSpacing"])
|
|
90
92
|
markerColor = EditorTheme.color(from: dictionary["markerColor"])
|
|
91
93
|
markerScale = EditorTheme.cgFloat(dictionary["markerScale"])
|
|
@@ -194,6 +196,8 @@ struct EditorToolbarTheme {
|
|
|
194
196
|
var borderColor: UIColor?
|
|
195
197
|
var borderWidth: CGFloat?
|
|
196
198
|
var borderRadius: CGFloat?
|
|
199
|
+
var marginTop: CGFloat?
|
|
200
|
+
var showTopBorder: Bool?
|
|
197
201
|
var keyboardOffset: CGFloat?
|
|
198
202
|
var horizontalInset: CGFloat?
|
|
199
203
|
var separatorColor: UIColor?
|
|
@@ -209,6 +213,8 @@ struct EditorToolbarTheme {
|
|
|
209
213
|
borderColor = EditorTheme.color(from: dictionary["borderColor"])
|
|
210
214
|
borderWidth = EditorTheme.cgFloat(dictionary["borderWidth"])
|
|
211
215
|
borderRadius = EditorTheme.cgFloat(dictionary["borderRadius"])
|
|
216
|
+
marginTop = EditorTheme.cgFloat(dictionary["marginTop"])
|
|
217
|
+
showTopBorder = dictionary["showTopBorder"] as? Bool
|
|
212
218
|
keyboardOffset = EditorTheme.cgFloat(dictionary["keyboardOffset"])
|
|
213
219
|
horizontalInset = EditorTheme.cgFloat(dictionary["horizontalInset"])
|
|
214
220
|
separatorColor = EditorTheme.color(from: dictionary["separatorColor"])
|
|
@@ -390,6 +390,10 @@ public class NativeEditorModule: Module {
|
|
|
390
390
|
Prop("themeJson") { (view: NativeProseViewerExpoView, themeJson: String?) in
|
|
391
391
|
view.setThemeJson(themeJson)
|
|
392
392
|
}
|
|
393
|
+
Prop("collapsesWhenEmpty") {
|
|
394
|
+
(view: NativeProseViewerExpoView, collapsesWhenEmpty: Bool?) in
|
|
395
|
+
view.setCollapsesWhenEmpty(collapsesWhenEmpty)
|
|
396
|
+
}
|
|
393
397
|
Prop("enableLinkTaps") { (view: NativeProseViewerExpoView, enableLinkTaps: Bool?) in
|
|
394
398
|
view.setEnableLinkTaps(enableLinkTaps)
|
|
395
399
|
}
|
|
@@ -12,6 +12,8 @@ final class NativeProseViewerExpoView: ExpoView {
|
|
|
12
12
|
private var lastEmittedContentHeight: CGFloat = 0
|
|
13
13
|
private var lastMeasuredWidth: CGFloat = 0
|
|
14
14
|
private var allowContentHeightShrink = true
|
|
15
|
+
private var collapsesWhenEmpty = true
|
|
16
|
+
private var isCollapsedEmptyContent = false
|
|
15
17
|
private var enableLinkTaps = true
|
|
16
18
|
private var interceptLinkTaps = false
|
|
17
19
|
|
|
@@ -51,6 +53,16 @@ final class NativeProseViewerExpoView: ExpoView {
|
|
|
51
53
|
interceptLinkTaps = intercept ?? false
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
func setCollapsesWhenEmpty(_ collapses: Bool?) {
|
|
57
|
+
let nextValue = collapses ?? true
|
|
58
|
+
guard collapsesWhenEmpty != nextValue else { return }
|
|
59
|
+
collapsesWhenEmpty = nextValue
|
|
60
|
+
allowContentHeightShrink = true
|
|
61
|
+
updateCollapsedEmptyState()
|
|
62
|
+
setNeedsLayout()
|
|
63
|
+
emitContentHeightIfNeeded(force: true)
|
|
64
|
+
}
|
|
65
|
+
|
|
54
66
|
func setRenderJson(_ renderJson: String?) {
|
|
55
67
|
guard lastRenderJSON != renderJson else { return }
|
|
56
68
|
lastRenderJSON = renderJson
|
|
@@ -71,6 +83,9 @@ final class NativeProseViewerExpoView: ExpoView {
|
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
override var intrinsicContentSize: CGSize {
|
|
86
|
+
if isCollapsedEmptyContent {
|
|
87
|
+
return CGSize(width: UIView.noIntrinsicMetric, height: 0)
|
|
88
|
+
}
|
|
74
89
|
guard lastEmittedContentHeight > 0 else {
|
|
75
90
|
return CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric)
|
|
76
91
|
}
|
|
@@ -79,8 +94,13 @@ final class NativeProseViewerExpoView: ExpoView {
|
|
|
79
94
|
|
|
80
95
|
override func layoutSubviews() {
|
|
81
96
|
super.layoutSubviews()
|
|
82
|
-
|
|
83
|
-
|
|
97
|
+
if isCollapsedEmptyContent {
|
|
98
|
+
textView.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 0)
|
|
99
|
+
textView.updateAutoGrowHostHeight(0)
|
|
100
|
+
} else {
|
|
101
|
+
textView.frame = bounds
|
|
102
|
+
textView.updateAutoGrowHostHeight(bounds.height)
|
|
103
|
+
}
|
|
84
104
|
|
|
85
105
|
let currentWidth = ceil(bounds.width)
|
|
86
106
|
guard abs(currentWidth - lastMeasuredWidth) > 0.5 else { return }
|
|
@@ -89,21 +109,36 @@ final class NativeProseViewerExpoView: ExpoView {
|
|
|
89
109
|
}
|
|
90
110
|
|
|
91
111
|
private func applyRenderJSON() {
|
|
112
|
+
updateCollapsedEmptyState()
|
|
92
113
|
textView.applyRenderJSON(lastRenderJSON ?? "[]")
|
|
114
|
+
textView.isHidden = isCollapsedEmptyContent
|
|
115
|
+
invalidateIntrinsicContentSize()
|
|
116
|
+
setNeedsLayout()
|
|
93
117
|
emitContentHeightIfNeeded(force: true)
|
|
94
118
|
}
|
|
95
119
|
|
|
120
|
+
private func updateCollapsedEmptyState() {
|
|
121
|
+
isCollapsedEmptyContent = collapsesWhenEmpty
|
|
122
|
+
&& Self.renderJsonContainsOnlyEmptyParagraphs(lastRenderJSON ?? "[]")
|
|
123
|
+
textView.isHidden = isCollapsedEmptyContent
|
|
124
|
+
}
|
|
125
|
+
|
|
96
126
|
private func emitContentHeightIfNeeded(
|
|
97
127
|
measuredHeight: CGFloat? = nil,
|
|
98
128
|
force: Bool = false
|
|
99
129
|
) {
|
|
100
|
-
let
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
130
|
+
let contentHeight: CGFloat
|
|
131
|
+
if isCollapsedEmptyContent {
|
|
132
|
+
contentHeight = 0
|
|
133
|
+
} else {
|
|
134
|
+
let resolvedWidth = bounds.width > 0
|
|
135
|
+
? bounds.width
|
|
136
|
+
: (superview?.bounds.width ?? UIScreen.main.bounds.width)
|
|
137
|
+
let fittedHeight = measuredHeight
|
|
138
|
+
?? textView.measuredAutoGrowHeightForTesting(width: resolvedWidth)
|
|
139
|
+
contentHeight = ceil(fittedHeight)
|
|
140
|
+
guard contentHeight > 0 else { return }
|
|
141
|
+
}
|
|
107
142
|
guard allowContentHeightShrink || contentHeight >= lastEmittedContentHeight else { return }
|
|
108
143
|
allowContentHeightShrink = false
|
|
109
144
|
guard force || abs(contentHeight - lastEmittedContentHeight) > 0.5 else { return }
|
|
@@ -194,4 +229,56 @@ final class NativeProseViewerExpoView: ExpoView {
|
|
|
194
229
|
guard let url = URL(string: href) else { return }
|
|
195
230
|
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
|
196
231
|
}
|
|
232
|
+
|
|
233
|
+
static func renderJsonContainsOnlyEmptyParagraphs(_ renderJson: String) -> Bool {
|
|
234
|
+
guard let data = renderJson.data(using: .utf8),
|
|
235
|
+
let elements = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]]
|
|
236
|
+
else {
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if elements.isEmpty {
|
|
241
|
+
return true
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
var hasParagraph = false
|
|
245
|
+
var paragraphIsOpen = false
|
|
246
|
+
|
|
247
|
+
for element in elements {
|
|
248
|
+
guard let type = element["type"] as? String else {
|
|
249
|
+
return false
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
switch type {
|
|
253
|
+
case "blockStart":
|
|
254
|
+
guard !paragraphIsOpen,
|
|
255
|
+
element["nodeType"] as? String == "paragraph",
|
|
256
|
+
(element["depth"] as? NSNumber)?.intValue == 0
|
|
257
|
+
else {
|
|
258
|
+
return false
|
|
259
|
+
}
|
|
260
|
+
paragraphIsOpen = true
|
|
261
|
+
hasParagraph = true
|
|
262
|
+
|
|
263
|
+
case "textRun":
|
|
264
|
+
guard paragraphIsOpen,
|
|
265
|
+
let text = element["text"] as? String,
|
|
266
|
+
text.allSatisfy({ $0 == "\u{200B}" })
|
|
267
|
+
else {
|
|
268
|
+
return false
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
case "blockEnd":
|
|
272
|
+
guard paragraphIsOpen else {
|
|
273
|
+
return false
|
|
274
|
+
}
|
|
275
|
+
paragraphIsOpen = false
|
|
276
|
+
|
|
277
|
+
default:
|
|
278
|
+
return false
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return hasParagraph && !paragraphIsOpen
|
|
283
|
+
}
|
|
197
284
|
}
|
package/ios/RenderBridge.swift
CHANGED
|
@@ -847,8 +847,13 @@ final class RenderBridge {
|
|
|
847
847
|
(theme?.blockquote?.markerGap ?? LayoutConstants.blockquoteMarkerGap)
|
|
848
848
|
+ (theme?.blockquote?.borderWidth ?? LayoutConstants.blockquoteBorderWidth)
|
|
849
849
|
)
|
|
850
|
+
let listBaseIndentMultiplier = max(theme?.list?.baseIndentMultiplier ?? 1, 0)
|
|
851
|
+
let listBaseIndentAdjustment = context.listContext != nil
|
|
852
|
+
? ((listBaseIndentMultiplier - 1) * indentPerDepth)
|
|
853
|
+
: 0
|
|
850
854
|
let baseIndent = (CGFloat(context.depth) * indentPerDepth)
|
|
851
855
|
- (quoteDepth * indentPerDepth)
|
|
856
|
+
+ listBaseIndentAdjustment
|
|
852
857
|
+ (quoteDepth * quoteIndent)
|
|
853
858
|
|
|
854
859
|
if context.listContext != nil {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apollohg/react-native-prose-editor",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Native rich text editor with Rust core for React Native",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/apollohg/react-native-prose-editor",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|