@apollohg/react-native-prose-editor 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/android/build.gradle +23 -0
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +502 -39
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +56 -28
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +6 -0
- package/android/src/main/java/com/apollohg/editor/PositionBridge.kt +57 -27
- package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +147 -78
- package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +249 -71
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +7 -6
- package/dist/NativeEditorBridge.d.ts +36 -1
- package/dist/NativeEditorBridge.js +173 -94
- package/dist/NativeRichTextEditor.d.ts +2 -0
- package/dist/NativeRichTextEditor.js +160 -53
- package/dist/YjsCollaboration.d.ts +2 -0
- package/dist/YjsCollaboration.js +142 -20
- 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/EditorLayoutManager.swift +3 -3
- package/ios/Generated_editor_core.swift +41 -0
- package/ios/NativeEditorExpoView.swift +43 -11
- package/ios/NativeEditorModule.swift +6 -0
- package/ios/PositionBridge.swift +310 -75
- package/ios/RenderBridge.swift +362 -27
- package/ios/RichTextEditorView.swift +1983 -187
- package/ios/editor_coreFFI/editor_coreFFI.h +33 -0
- package/package.json +11 -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
- package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +63 -0
|
@@ -29,7 +29,9 @@ import org.json.JSONArray
|
|
|
29
29
|
import org.json.JSONObject
|
|
30
30
|
import java.lang.ref.WeakReference
|
|
31
31
|
import java.net.URL
|
|
32
|
-
import java.util.concurrent.
|
|
32
|
+
import java.util.concurrent.LinkedBlockingQueue
|
|
33
|
+
import java.util.concurrent.ThreadPoolExecutor
|
|
34
|
+
import java.util.concurrent.TimeUnit
|
|
33
35
|
|
|
34
36
|
object LayoutConstants {
|
|
35
37
|
/** Base indentation per depth level (pixels at base scale). */
|
|
@@ -79,6 +81,7 @@ data class BlockContext(
|
|
|
79
81
|
val nodeType: String,
|
|
80
82
|
val depth: Int,
|
|
81
83
|
val listContext: JSONObject?,
|
|
84
|
+
val topLevelChildIndex: Int? = null,
|
|
82
85
|
var markerPending: Boolean = false,
|
|
83
86
|
var renderStart: Int = 0
|
|
84
87
|
)
|
|
@@ -218,10 +221,30 @@ class HorizontalRuleSpan(
|
|
|
218
221
|
private val lineColor: Int,
|
|
219
222
|
private val lineHeight: Float = LayoutConstants.HORIZONTAL_RULE_HEIGHT,
|
|
220
223
|
private val verticalPadding: Float = LayoutConstants.HORIZONTAL_RULE_VERTICAL_PADDING
|
|
221
|
-
) : LeadingMarginSpan {
|
|
224
|
+
) : ReplacementSpan(), LeadingMarginSpan {
|
|
222
225
|
|
|
223
226
|
override fun getLeadingMargin(first: Boolean): Int = 0
|
|
224
227
|
|
|
228
|
+
override fun getSize(
|
|
229
|
+
paint: Paint,
|
|
230
|
+
text: CharSequence,
|
|
231
|
+
start: Int,
|
|
232
|
+
end: Int,
|
|
233
|
+
fm: Paint.FontMetricsInt?
|
|
234
|
+
): Int {
|
|
235
|
+
if (fm != null) {
|
|
236
|
+
val totalHeight = kotlin.math.ceil(lineHeight + (verticalPadding * 2)).toInt()
|
|
237
|
+
val halfHeight = totalHeight / 2
|
|
238
|
+
fm.ascent = -halfHeight
|
|
239
|
+
fm.top = fm.ascent
|
|
240
|
+
fm.descent = totalHeight - halfHeight
|
|
241
|
+
fm.bottom = fm.descent
|
|
242
|
+
}
|
|
243
|
+
// Keep the placeholder atom in the text model without reserving
|
|
244
|
+
// visible glyph width, so Android does not paint a tofu/OBJ box.
|
|
245
|
+
return 0
|
|
246
|
+
}
|
|
247
|
+
|
|
225
248
|
override fun drawLeadingMargin(
|
|
226
249
|
canvas: Canvas,
|
|
227
250
|
paint: Paint,
|
|
@@ -255,6 +278,21 @@ class HorizontalRuleSpan(
|
|
|
255
278
|
paint.color = savedColor
|
|
256
279
|
paint.style = savedStyle
|
|
257
280
|
}
|
|
281
|
+
|
|
282
|
+
override fun draw(
|
|
283
|
+
canvas: Canvas,
|
|
284
|
+
text: CharSequence,
|
|
285
|
+
start: Int,
|
|
286
|
+
end: Int,
|
|
287
|
+
x: Float,
|
|
288
|
+
top: Int,
|
|
289
|
+
y: Int,
|
|
290
|
+
bottom: Int,
|
|
291
|
+
paint: Paint
|
|
292
|
+
) {
|
|
293
|
+
// Intentionally empty: drawLeadingMargin renders the separator line,
|
|
294
|
+
// and ReplacementSpan suppresses drawing the underlying FFFC glyph.
|
|
295
|
+
}
|
|
258
296
|
}
|
|
259
297
|
|
|
260
298
|
internal object RenderImageDecoder {
|
|
@@ -365,14 +403,36 @@ internal object RenderImageDecoder {
|
|
|
365
403
|
}
|
|
366
404
|
}
|
|
367
405
|
|
|
368
|
-
|
|
406
|
+
internal object RenderImageLoader {
|
|
369
407
|
private val cache = object : LruCache<String, Bitmap>(32 * 1024 * 1024) {
|
|
370
408
|
override fun sizeOf(key: String, value: Bitmap): Int = value.byteCount
|
|
371
409
|
}
|
|
372
|
-
private val executor =
|
|
410
|
+
private val executor =
|
|
411
|
+
ThreadPoolExecutor(
|
|
412
|
+
2,
|
|
413
|
+
2,
|
|
414
|
+
30L,
|
|
415
|
+
TimeUnit.SECONDS,
|
|
416
|
+
LinkedBlockingQueue()
|
|
417
|
+
)
|
|
418
|
+
private val lock = Any()
|
|
419
|
+
private val inFlight = mutableMapOf<String, MutableList<(Bitmap?) -> Unit>>()
|
|
420
|
+
|
|
421
|
+
@Volatile
|
|
422
|
+
internal var decodeSourceOverride: ((String) -> Bitmap?)? = null
|
|
373
423
|
|
|
374
424
|
fun cached(source: String): Bitmap? = synchronized(cache) { cache.get(source) }
|
|
375
425
|
|
|
426
|
+
internal fun resetForTesting() {
|
|
427
|
+
synchronized(cache) {
|
|
428
|
+
cache.evictAll()
|
|
429
|
+
}
|
|
430
|
+
synchronized(lock) {
|
|
431
|
+
inFlight.clear()
|
|
432
|
+
}
|
|
433
|
+
decodeSourceOverride = null
|
|
434
|
+
}
|
|
435
|
+
|
|
376
436
|
fun load(source: String, onLoaded: (Bitmap?) -> Unit) {
|
|
377
437
|
cached(source)?.let {
|
|
378
438
|
onLoaded(it)
|
|
@@ -380,7 +440,7 @@ private object RenderImageLoader {
|
|
|
380
440
|
}
|
|
381
441
|
|
|
382
442
|
if (source.trim().startsWith("data:image/", ignoreCase = true)) {
|
|
383
|
-
val bitmap =
|
|
443
|
+
val bitmap = decode(source)
|
|
384
444
|
if (bitmap != null) {
|
|
385
445
|
synchronized(cache) {
|
|
386
446
|
cache.put(source, bitmap)
|
|
@@ -390,16 +450,32 @@ private object RenderImageLoader {
|
|
|
390
450
|
return
|
|
391
451
|
}
|
|
392
452
|
|
|
453
|
+
synchronized(lock) {
|
|
454
|
+
val existing = inFlight[source]
|
|
455
|
+
if (existing != null) {
|
|
456
|
+
existing += onLoaded
|
|
457
|
+
return
|
|
458
|
+
}
|
|
459
|
+
inFlight[source] = mutableListOf(onLoaded)
|
|
460
|
+
}
|
|
461
|
+
|
|
393
462
|
executor.execute {
|
|
394
|
-
val bitmap =
|
|
463
|
+
val bitmap = decode(source)
|
|
395
464
|
if (bitmap != null) {
|
|
396
465
|
synchronized(cache) {
|
|
397
466
|
cache.put(source, bitmap)
|
|
398
467
|
}
|
|
399
468
|
}
|
|
400
|
-
|
|
469
|
+
val callbacks = synchronized(lock) {
|
|
470
|
+
inFlight.remove(source) ?: mutableListOf()
|
|
471
|
+
}
|
|
472
|
+
callbacks.forEach { it(bitmap) }
|
|
401
473
|
}
|
|
402
474
|
}
|
|
475
|
+
|
|
476
|
+
private fun decode(source: String): Bitmap? {
|
|
477
|
+
return decodeSourceOverride?.invoke(source) ?: RenderImageDecoder.decodeSource(source)
|
|
478
|
+
}
|
|
403
479
|
}
|
|
404
480
|
|
|
405
481
|
internal class BlockImageSpan(
|
|
@@ -675,8 +751,17 @@ class CenteredBulletSpan(
|
|
|
675
751
|
|
|
676
752
|
object RenderBridge {
|
|
677
753
|
internal const val NATIVE_BLOCKQUOTE_ANNOTATION = "nativeBlockquote"
|
|
754
|
+
internal const val NATIVE_TOP_LEVEL_CHILD_INDEX_ANNOTATION = "nativeTopLevelChildIndex"
|
|
678
755
|
private const val NATIVE_SYNTHETIC_PLACEHOLDER_ANNOTATION = "nativeSyntheticPlaceholder"
|
|
679
756
|
|
|
757
|
+
private data class RenderBuildState(
|
|
758
|
+
val result: SpannableStringBuilder = SpannableStringBuilder(),
|
|
759
|
+
val blockStack: MutableList<BlockContext> = mutableListOf(),
|
|
760
|
+
val pendingLeadingMargins: MutableMap<Int, PendingLeadingMargin> = linkedMapOf(),
|
|
761
|
+
var isFirstBlock: Boolean = true,
|
|
762
|
+
var nextBlockSpacingBefore: Float? = null
|
|
763
|
+
)
|
|
764
|
+
|
|
680
765
|
fun buildSpannable(
|
|
681
766
|
json: String,
|
|
682
767
|
baseFontSize: Float,
|
|
@@ -702,12 +787,57 @@ object RenderBridge {
|
|
|
702
787
|
density: Float = 1f,
|
|
703
788
|
hostView: TextView? = null
|
|
704
789
|
): SpannableStringBuilder {
|
|
705
|
-
val
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
790
|
+
val state = RenderBuildState()
|
|
791
|
+
appendElements(
|
|
792
|
+
state = state,
|
|
793
|
+
elements = elements,
|
|
794
|
+
baseFontSize = baseFontSize,
|
|
795
|
+
textColor = textColor,
|
|
796
|
+
theme = theme,
|
|
797
|
+
density = density,
|
|
798
|
+
hostView = hostView
|
|
799
|
+
)
|
|
800
|
+
applyPendingLeadingMargins(state.result, state.pendingLeadingMargins)
|
|
801
|
+
return state.result
|
|
802
|
+
}
|
|
710
803
|
|
|
804
|
+
fun buildSpannableFromBlocks(
|
|
805
|
+
blocks: JSONArray,
|
|
806
|
+
startIndex: Int = 0,
|
|
807
|
+
baseFontSize: Float,
|
|
808
|
+
textColor: Int,
|
|
809
|
+
theme: EditorTheme? = null,
|
|
810
|
+
density: Float = 1f,
|
|
811
|
+
hostView: TextView? = null
|
|
812
|
+
): SpannableStringBuilder {
|
|
813
|
+
val state = RenderBuildState()
|
|
814
|
+
for (blockOffset in 0 until blocks.length()) {
|
|
815
|
+
val blockElements = blocks.optJSONArray(blockOffset) ?: continue
|
|
816
|
+
appendElements(
|
|
817
|
+
state = state,
|
|
818
|
+
elements = blockElements,
|
|
819
|
+
baseFontSize = baseFontSize,
|
|
820
|
+
textColor = textColor,
|
|
821
|
+
theme = theme,
|
|
822
|
+
density = density,
|
|
823
|
+
hostView = hostView,
|
|
824
|
+
topLevelChildIndex = startIndex + blockOffset
|
|
825
|
+
)
|
|
826
|
+
}
|
|
827
|
+
applyPendingLeadingMargins(state.result, state.pendingLeadingMargins)
|
|
828
|
+
return state.result
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
private fun appendElements(
|
|
832
|
+
state: RenderBuildState,
|
|
833
|
+
elements: JSONArray,
|
|
834
|
+
baseFontSize: Float,
|
|
835
|
+
textColor: Int,
|
|
836
|
+
theme: EditorTheme?,
|
|
837
|
+
density: Float,
|
|
838
|
+
hostView: TextView?,
|
|
839
|
+
topLevelChildIndex: Int? = null
|
|
840
|
+
) {
|
|
711
841
|
for (i in 0 until elements.length()) {
|
|
712
842
|
val element = elements.optJSONObject(i) ?: continue
|
|
713
843
|
val type = element.optString("type", "")
|
|
@@ -718,13 +848,13 @@ object RenderBridge {
|
|
|
718
848
|
val marksArray = element.optJSONArray("marks")
|
|
719
849
|
val marks = parseMarks(marksArray)
|
|
720
850
|
appendStyledText(
|
|
721
|
-
result,
|
|
851
|
+
state.result,
|
|
722
852
|
text,
|
|
723
853
|
marks,
|
|
724
854
|
baseFontSize,
|
|
725
855
|
textColor,
|
|
726
|
-
blockStack,
|
|
727
|
-
pendingLeadingMargins,
|
|
856
|
+
state.blockStack,
|
|
857
|
+
state.pendingLeadingMargins,
|
|
728
858
|
theme,
|
|
729
859
|
density
|
|
730
860
|
)
|
|
@@ -733,12 +863,12 @@ object RenderBridge {
|
|
|
733
863
|
"voidInline" -> {
|
|
734
864
|
val nodeType = element.optString("nodeType", "")
|
|
735
865
|
appendVoidInline(
|
|
736
|
-
result,
|
|
866
|
+
state.result,
|
|
737
867
|
nodeType,
|
|
738
868
|
baseFontSize,
|
|
739
869
|
textColor,
|
|
740
|
-
blockStack,
|
|
741
|
-
pendingLeadingMargins,
|
|
870
|
+
state.blockStack,
|
|
871
|
+
state.pendingLeadingMargins,
|
|
742
872
|
theme,
|
|
743
873
|
density
|
|
744
874
|
)
|
|
@@ -747,16 +877,22 @@ object RenderBridge {
|
|
|
747
877
|
"voidBlock" -> {
|
|
748
878
|
val nodeType = element.optString("nodeType", "")
|
|
749
879
|
val attrs = element.optJSONObject("attrs")
|
|
750
|
-
if (!isFirstBlock) {
|
|
751
|
-
val spacingPx = ((nextBlockSpacingBefore ?: 0f) * density).toInt()
|
|
752
|
-
appendInterBlockNewline(
|
|
880
|
+
if (!state.isFirstBlock) {
|
|
881
|
+
val spacingPx = ((state.nextBlockSpacingBefore ?: 0f) * density).toInt()
|
|
882
|
+
appendInterBlockNewline(
|
|
883
|
+
state.result,
|
|
884
|
+
baseFontSize,
|
|
885
|
+
textColor,
|
|
886
|
+
spacingPx,
|
|
887
|
+
topLevelChildIndex = topLevelChildIndex
|
|
888
|
+
)
|
|
753
889
|
}
|
|
754
|
-
isFirstBlock = false
|
|
890
|
+
state.isFirstBlock = false
|
|
755
891
|
val spacingBefore = theme?.effectiveTextStyle(nodeType)?.spacingAfter
|
|
756
892
|
?: theme?.list?.itemSpacing
|
|
757
|
-
nextBlockSpacingBefore = spacingBefore
|
|
893
|
+
state.nextBlockSpacingBefore = spacingBefore
|
|
758
894
|
appendVoidBlock(
|
|
759
|
-
result,
|
|
895
|
+
state.result,
|
|
760
896
|
nodeType,
|
|
761
897
|
attrs,
|
|
762
898
|
baseFontSize,
|
|
@@ -764,7 +900,8 @@ object RenderBridge {
|
|
|
764
900
|
theme,
|
|
765
901
|
density,
|
|
766
902
|
spacingBefore,
|
|
767
|
-
hostView
|
|
903
|
+
hostView,
|
|
904
|
+
topLevelChildIndex
|
|
768
905
|
)
|
|
769
906
|
}
|
|
770
907
|
|
|
@@ -772,13 +909,13 @@ object RenderBridge {
|
|
|
772
909
|
val nodeType = element.optString("nodeType", "")
|
|
773
910
|
val label = element.optString("label", "?")
|
|
774
911
|
appendOpaqueInlineAtom(
|
|
775
|
-
result,
|
|
912
|
+
state.result,
|
|
776
913
|
nodeType,
|
|
777
914
|
label,
|
|
778
915
|
baseFontSize,
|
|
779
916
|
textColor,
|
|
780
|
-
blockStack,
|
|
781
|
-
pendingLeadingMargins,
|
|
917
|
+
state.blockStack,
|
|
918
|
+
state.pendingLeadingMargins,
|
|
782
919
|
theme,
|
|
783
920
|
density
|
|
784
921
|
)
|
|
@@ -788,13 +925,28 @@ object RenderBridge {
|
|
|
788
925
|
val nodeType = element.optString("nodeType", "")
|
|
789
926
|
val label = element.optString("label", "?")
|
|
790
927
|
val blockSpacing = theme?.effectiveTextStyle(nodeType)?.spacingAfter
|
|
791
|
-
if (!isFirstBlock) {
|
|
792
|
-
val spacingPx = ((nextBlockSpacingBefore ?: 0f) * density).toInt()
|
|
793
|
-
appendInterBlockNewline(
|
|
928
|
+
if (!state.isFirstBlock) {
|
|
929
|
+
val spacingPx = ((state.nextBlockSpacingBefore ?: 0f) * density).toInt()
|
|
930
|
+
appendInterBlockNewline(
|
|
931
|
+
state.result,
|
|
932
|
+
baseFontSize,
|
|
933
|
+
textColor,
|
|
934
|
+
spacingPx,
|
|
935
|
+
topLevelChildIndex = topLevelChildIndex
|
|
936
|
+
)
|
|
794
937
|
}
|
|
795
|
-
isFirstBlock = false
|
|
796
|
-
nextBlockSpacingBefore = blockSpacing
|
|
797
|
-
appendOpaqueBlockAtom(
|
|
938
|
+
state.isFirstBlock = false
|
|
939
|
+
state.nextBlockSpacingBefore = blockSpacing
|
|
940
|
+
appendOpaqueBlockAtom(
|
|
941
|
+
state.result,
|
|
942
|
+
nodeType,
|
|
943
|
+
label,
|
|
944
|
+
baseFontSize,
|
|
945
|
+
textColor,
|
|
946
|
+
theme,
|
|
947
|
+
blockSpacing,
|
|
948
|
+
topLevelChildIndex
|
|
949
|
+
)
|
|
798
950
|
}
|
|
799
951
|
|
|
800
952
|
"blockStart" -> {
|
|
@@ -804,7 +956,7 @@ object RenderBridge {
|
|
|
804
956
|
val isListItemContainer = nodeType == "listItem" && listContext != null
|
|
805
957
|
val isTransparentContainer = nodeType == "blockquote"
|
|
806
958
|
val nestedListItemContainer =
|
|
807
|
-
isListItemContainer && blockStack.any { it.nodeType == "listItem" && it.listContext != null }
|
|
959
|
+
isListItemContainer && state.blockStack.any { it.nodeType == "listItem" && it.listContext != null }
|
|
808
960
|
val blockSpacing = if (isListItemContainer) {
|
|
809
961
|
null
|
|
810
962
|
} else {
|
|
@@ -813,44 +965,47 @@ object RenderBridge {
|
|
|
813
965
|
}
|
|
814
966
|
|
|
815
967
|
if (!isListItemContainer && !isTransparentContainer) {
|
|
816
|
-
if (!isFirstBlock) {
|
|
817
|
-
val spacingPx = ((nextBlockSpacingBefore ?: 0f) * density).toInt()
|
|
818
|
-
val nextBlockStack = blockStack + BlockContext(
|
|
968
|
+
if (!state.isFirstBlock) {
|
|
969
|
+
val spacingPx = ((state.nextBlockSpacingBefore ?: 0f) * density).toInt()
|
|
970
|
+
val nextBlockStack = state.blockStack + BlockContext(
|
|
819
971
|
nodeType = nodeType,
|
|
820
972
|
depth = depth,
|
|
821
973
|
listContext = listContext,
|
|
974
|
+
topLevelChildIndex = topLevelChildIndex,
|
|
822
975
|
markerPending = isListItemContainer,
|
|
823
|
-
renderStart = result.length
|
|
976
|
+
renderStart = state.result.length
|
|
824
977
|
)
|
|
825
978
|
val inBlockquoteSeparator =
|
|
826
|
-
blockquoteDepth(nextBlockStack) > 0f && trailingRenderedContentHasBlockquote(result)
|
|
979
|
+
blockquoteDepth(nextBlockStack) > 0f && trailingRenderedContentHasBlockquote(state.result)
|
|
827
980
|
appendInterBlockNewline(
|
|
828
|
-
result,
|
|
981
|
+
state.result,
|
|
829
982
|
baseFontSize,
|
|
830
983
|
textColor,
|
|
831
984
|
spacingPx,
|
|
832
|
-
inBlockquote = inBlockquoteSeparator
|
|
985
|
+
inBlockquote = inBlockquoteSeparator,
|
|
986
|
+
topLevelChildIndex = topLevelChildIndex
|
|
833
987
|
)
|
|
834
988
|
}
|
|
835
|
-
isFirstBlock = false
|
|
836
|
-
nextBlockSpacingBefore = blockSpacing
|
|
989
|
+
state.isFirstBlock = false
|
|
990
|
+
state.nextBlockSpacingBefore = blockSpacing
|
|
837
991
|
} else if (nestedListItemContainer && theme?.list?.itemSpacing != null) {
|
|
838
|
-
nextBlockSpacingBefore = theme.list.itemSpacing
|
|
992
|
+
state.nextBlockSpacingBefore = theme.list.itemSpacing
|
|
839
993
|
}
|
|
840
994
|
|
|
841
995
|
val ctx = BlockContext(
|
|
842
996
|
nodeType = nodeType,
|
|
843
997
|
depth = depth,
|
|
844
998
|
listContext = listContext,
|
|
999
|
+
topLevelChildIndex = topLevelChildIndex,
|
|
845
1000
|
markerPending = isListItemContainer,
|
|
846
|
-
renderStart = result.length
|
|
1001
|
+
renderStart = state.result.length
|
|
847
1002
|
)
|
|
848
|
-
blockStack.add(ctx)
|
|
1003
|
+
state.blockStack.add(ctx)
|
|
849
1004
|
|
|
850
1005
|
val markerListContext = when {
|
|
851
1006
|
isListItemContainer -> null
|
|
852
1007
|
listContext != null -> listContext
|
|
853
|
-
else -> consumePendingListMarker(blockStack, result.length)
|
|
1008
|
+
else -> consumePendingListMarker(state.blockStack, state.result.length)
|
|
854
1009
|
}
|
|
855
1010
|
|
|
856
1011
|
if (markerListContext != null) {
|
|
@@ -860,33 +1015,34 @@ object RenderBridge {
|
|
|
860
1015
|
resolveTextStyle(
|
|
861
1016
|
nodeType,
|
|
862
1017
|
theme,
|
|
863
|
-
blockquoteDepth(blockStack) > 0
|
|
1018
|
+
blockquoteDepth(state.blockStack) > 0
|
|
864
1019
|
).fontSize?.times(density) ?: baseFontSize
|
|
865
1020
|
val markerTextStyle = resolveTextStyle(
|
|
866
1021
|
nodeType,
|
|
867
1022
|
theme,
|
|
868
|
-
blockquoteDepth(blockStack) > 0
|
|
1023
|
+
blockquoteDepth(state.blockStack) > 0
|
|
869
1024
|
)
|
|
870
1025
|
appendStyledText(
|
|
871
|
-
result,
|
|
1026
|
+
state.result,
|
|
872
1027
|
marker,
|
|
873
1028
|
emptyList(),
|
|
874
1029
|
markerBaseSize,
|
|
875
1030
|
theme?.list?.markerColor ?: textColor,
|
|
876
|
-
blockStack,
|
|
877
|
-
pendingLeadingMargins,
|
|
1031
|
+
state.blockStack,
|
|
1032
|
+
state.pendingLeadingMargins,
|
|
878
1033
|
null,
|
|
879
1034
|
density,
|
|
880
1035
|
applyBlockSpans = false
|
|
881
1036
|
)
|
|
1037
|
+
val markerStart = state.result.length - marker.length
|
|
1038
|
+
val markerEnd = state.result.length
|
|
1039
|
+
annotateTopLevelChild(state.result, markerStart, markerEnd, topLevelChildIndex)
|
|
882
1040
|
if (!ordered) {
|
|
883
|
-
val markerStart = result.length - marker.length
|
|
884
|
-
val markerEnd = result.length
|
|
885
1041
|
val markerScale =
|
|
886
1042
|
theme?.list?.markerScale ?: LayoutConstants.UNORDERED_LIST_MARKER_FONT_SCALE
|
|
887
1043
|
val markerWidth = calculateMarkerWidth(density)
|
|
888
1044
|
val bulletRadius = ((markerBaseSize * markerScale) * 0.16f).coerceAtLeast(2f * density)
|
|
889
|
-
result.setSpan(
|
|
1045
|
+
state.result.setSpan(
|
|
890
1046
|
CenteredBulletSpan(
|
|
891
1047
|
textColor = theme?.list?.markerColor ?: textColor,
|
|
892
1048
|
markerWidthPx = markerWidth,
|
|
@@ -900,9 +1056,9 @@ object RenderBridge {
|
|
|
900
1056
|
)
|
|
901
1057
|
}
|
|
902
1058
|
applyLineHeightSpan(
|
|
903
|
-
builder = result,
|
|
904
|
-
start =
|
|
905
|
-
end =
|
|
1059
|
+
builder = state.result,
|
|
1060
|
+
start = markerStart,
|
|
1061
|
+
end = markerEnd,
|
|
906
1062
|
lineHeight = markerTextStyle.lineHeight,
|
|
907
1063
|
density = density
|
|
908
1064
|
)
|
|
@@ -910,28 +1066,25 @@ object RenderBridge {
|
|
|
910
1066
|
}
|
|
911
1067
|
|
|
912
1068
|
"blockEnd" -> {
|
|
913
|
-
if (blockStack.isNotEmpty()) {
|
|
914
|
-
val endedBlock = blockStack.removeAt(blockStack.lastIndex)
|
|
1069
|
+
if (state.blockStack.isNotEmpty()) {
|
|
1070
|
+
val endedBlock = state.blockStack.removeAt(state.blockStack.lastIndex)
|
|
915
1071
|
appendTrailingHardBreakPlaceholderIfNeeded(
|
|
916
|
-
builder = result,
|
|
1072
|
+
builder = state.result,
|
|
917
1073
|
endedBlock = endedBlock,
|
|
918
|
-
remainingBlockStack = blockStack,
|
|
1074
|
+
remainingBlockStack = state.blockStack,
|
|
919
1075
|
baseFontSize = baseFontSize,
|
|
920
1076
|
textColor = textColor,
|
|
921
1077
|
theme = theme,
|
|
922
1078
|
density = density,
|
|
923
|
-
pendingLeadingMargins = pendingLeadingMargins
|
|
1079
|
+
pendingLeadingMargins = state.pendingLeadingMargins
|
|
924
1080
|
)
|
|
925
1081
|
if (endedBlock.nodeType == "listItem" && endedBlock.listContext != null) {
|
|
926
|
-
nextBlockSpacingBefore = theme?.list?.itemSpacing
|
|
1082
|
+
state.nextBlockSpacingBefore = theme?.list?.itemSpacing
|
|
927
1083
|
}
|
|
928
1084
|
}
|
|
929
1085
|
}
|
|
930
1086
|
}
|
|
931
1087
|
}
|
|
932
|
-
|
|
933
|
-
applyPendingLeadingMargins(result, pendingLeadingMargins)
|
|
934
|
-
return result
|
|
935
1088
|
}
|
|
936
1089
|
|
|
937
1090
|
/**
|
|
@@ -1126,7 +1279,8 @@ object RenderBridge {
|
|
|
1126
1279
|
theme: EditorTheme?,
|
|
1127
1280
|
density: Float,
|
|
1128
1281
|
spacingBefore: Float?,
|
|
1129
|
-
hostView: TextView
|
|
1282
|
+
hostView: TextView?,
|
|
1283
|
+
topLevelChildIndex: Int?
|
|
1130
1284
|
) {
|
|
1131
1285
|
when (nodeType) {
|
|
1132
1286
|
"horizontalRule" -> {
|
|
@@ -1148,6 +1302,7 @@ object RenderBridge {
|
|
|
1148
1302
|
),
|
|
1149
1303
|
start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
1150
1304
|
)
|
|
1305
|
+
annotateTopLevelChild(builder, start, end, topLevelChildIndex)
|
|
1151
1306
|
}
|
|
1152
1307
|
"image" -> {
|
|
1153
1308
|
val source = if (attrs != null && attrs.has("src") && !attrs.isNull("src")) {
|
|
@@ -1174,9 +1329,12 @@ object RenderBridge {
|
|
|
1174
1329
|
),
|
|
1175
1330
|
start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
1176
1331
|
)
|
|
1332
|
+
annotateTopLevelChild(builder, start, end, topLevelChildIndex)
|
|
1177
1333
|
}
|
|
1178
1334
|
else -> {
|
|
1335
|
+
val start = builder.length
|
|
1179
1336
|
builder.append(LayoutConstants.OBJECT_REPLACEMENT_CHARACTER)
|
|
1337
|
+
annotateTopLevelChild(builder, start, builder.length, topLevelChildIndex)
|
|
1180
1338
|
}
|
|
1181
1339
|
}
|
|
1182
1340
|
}
|
|
@@ -1238,7 +1396,8 @@ object RenderBridge {
|
|
|
1238
1396
|
baseFontSize: Float,
|
|
1239
1397
|
textColor: Int,
|
|
1240
1398
|
theme: EditorTheme?,
|
|
1241
|
-
spacingBefore: Float
|
|
1399
|
+
spacingBefore: Float?,
|
|
1400
|
+
topLevelChildIndex: Int?
|
|
1242
1401
|
) {
|
|
1243
1402
|
val text = if (nodeType == "mention") label else "[$label]"
|
|
1244
1403
|
val start = builder.length
|
|
@@ -1256,6 +1415,7 @@ object RenderBridge {
|
|
|
1256
1415
|
Annotation("nativeVoidNodeType", nodeType),
|
|
1257
1416
|
start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
1258
1417
|
)
|
|
1418
|
+
annotateTopLevelChild(builder, start, end, topLevelChildIndex)
|
|
1259
1419
|
}
|
|
1260
1420
|
|
|
1261
1421
|
private fun applyBlockStyle(
|
|
@@ -1334,6 +1494,7 @@ object RenderBridge {
|
|
|
1334
1494
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
1335
1495
|
)
|
|
1336
1496
|
}
|
|
1497
|
+
annotateTopLevelChild(builder, start, end, currentBlock.topLevelChildIndex)
|
|
1337
1498
|
|
|
1338
1499
|
val lineHeight = resolveTextStyle(
|
|
1339
1500
|
currentBlock.nodeType,
|
|
@@ -1599,7 +1760,8 @@ object RenderBridge {
|
|
|
1599
1760
|
baseFontSize: Float,
|
|
1600
1761
|
textColor: Int,
|
|
1601
1762
|
spacingPx: Int = 0,
|
|
1602
|
-
inBlockquote: Boolean = false
|
|
1763
|
+
inBlockquote: Boolean = false,
|
|
1764
|
+
topLevelChildIndex: Int? = null
|
|
1603
1765
|
) {
|
|
1604
1766
|
val start = builder.length
|
|
1605
1767
|
builder.append("\n")
|
|
@@ -1619,6 +1781,7 @@ object RenderBridge {
|
|
|
1619
1781
|
start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
1620
1782
|
)
|
|
1621
1783
|
}
|
|
1784
|
+
annotateTopLevelChild(builder, start, end, topLevelChildIndex)
|
|
1622
1785
|
if (inBlockquote) {
|
|
1623
1786
|
builder.setSpan(
|
|
1624
1787
|
Annotation(NATIVE_BLOCKQUOTE_ANNOTATION, "1"),
|
|
@@ -1677,6 +1840,21 @@ object RenderBridge {
|
|
|
1677
1840
|
}
|
|
1678
1841
|
}
|
|
1679
1842
|
|
|
1843
|
+
private fun annotateTopLevelChild(
|
|
1844
|
+
builder: SpannableStringBuilder,
|
|
1845
|
+
start: Int,
|
|
1846
|
+
end: Int,
|
|
1847
|
+
topLevelChildIndex: Int?
|
|
1848
|
+
) {
|
|
1849
|
+
if (topLevelChildIndex == null || start >= end) return
|
|
1850
|
+
builder.setSpan(
|
|
1851
|
+
Annotation(NATIVE_TOP_LEVEL_CHILD_INDEX_ANNOTATION, topLevelChildIndex.toString()),
|
|
1852
|
+
start,
|
|
1853
|
+
end,
|
|
1854
|
+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
1855
|
+
)
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1680
1858
|
private fun trailingRenderedContentHasBlockquote(builder: Spanned): Boolean {
|
|
1681
1859
|
for (index in builder.length - 1 downTo 0) {
|
|
1682
1860
|
val ch = builder[index]
|
|
@@ -182,7 +182,8 @@ class RichTextEditorView @JvmOverloads constructor(
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
fun refreshRemoteSelections() {
|
|
185
|
-
remoteSelectionOverlayView.
|
|
185
|
+
if (!remoteSelectionOverlayView.hasSelectionsOrCachedGeometry()) return
|
|
186
|
+
remoteSelectionOverlayView.refreshGeometry()
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
fun imageResizeOverlayRectForTesting(): android.graphics.RectF? =
|
|
@@ -205,14 +206,14 @@ class RichTextEditorView @JvmOverloads constructor(
|
|
|
205
206
|
|
|
206
207
|
fun setContent(html: String) {
|
|
207
208
|
if (editorId == 0L) return
|
|
208
|
-
|
|
209
|
-
editorEditText.
|
|
209
|
+
editorSetHtml(editorId.toULong(), html)
|
|
210
|
+
editorEditText.applyUpdateJSON(editorGetCurrentState(editorId.toULong()), notifyListener = false)
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
fun setContent(json: org.json.JSONObject) {
|
|
213
214
|
if (editorId == 0L) return
|
|
214
|
-
|
|
215
|
-
editorEditText.
|
|
215
|
+
editorSetJson(editorId.toULong(), json.toString())
|
|
216
|
+
editorEditText.applyUpdateJSON(editorGetCurrentState(editorId.toULong()), notifyListener = false)
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
override fun onDetachedFromWindow() {
|
|
@@ -322,7 +323,7 @@ class RichTextEditorView @JvmOverloads constructor(
|
|
|
322
323
|
}
|
|
323
324
|
|
|
324
325
|
private fun refreshOverlays() {
|
|
325
|
-
remoteSelectionOverlayView.
|
|
326
|
+
remoteSelectionOverlayView.refreshGeometry()
|
|
326
327
|
imageResizeOverlayView.refresh()
|
|
327
328
|
}
|
|
328
329
|
}
|