@openeditor/react-native-prose-editor 0.0.24 → 0.0.26
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 +8 -0
- package/android/src/main/java/com/apollohg/editor/NativeBlockEditorSurface.kt +134 -52
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +15 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +5 -0
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +3 -0
- package/dist/EditorTheme.d.ts +4 -0
- package/dist/NativeEditorBridge.d.ts +3 -0
- package/dist/NativeEditorBridge.js +5 -0
- package/dist/NativeRichTextEditor.d.ts +9 -0
- package/dist/NativeRichTextEditor.js +15 -1
- package/dist/addons.d.ts +6 -0
- package/ios/EditorTheme.swift +28 -0
- package/ios/NativeBlockEditorSurface.swift +166 -56
- package/ios/NativeEditorExpoView.swift +12 -0
- package/ios/NativeEditorModule.swift +6 -0
- package/ios/RichTextEditorView.swift +4 -0
- package/package.json +1 -1
|
@@ -352,6 +352,10 @@ data class EditorTheme(
|
|
|
352
352
|
val toolbar: EditorToolbarTheme? = null,
|
|
353
353
|
val placeholderColor: Int? = null,
|
|
354
354
|
val backgroundColor: Int? = null,
|
|
355
|
+
val blockSurfaceColor: Int? = null,
|
|
356
|
+
val structuralLineColor: Int? = null,
|
|
357
|
+
val accentStrongColor: Int? = null,
|
|
358
|
+
val accentStrongTextColor: Int? = null,
|
|
355
359
|
val borderRadius: Float? = null,
|
|
356
360
|
val contentInsets: EditorContentInsets? = null
|
|
357
361
|
) {
|
|
@@ -386,6 +390,10 @@ data class EditorTheme(
|
|
|
386
390
|
toolbar = EditorToolbarTheme.fromJson(root.optJSONObject("toolbar")),
|
|
387
391
|
placeholderColor = parseColor(root.optNullableString("placeholderColor")),
|
|
388
392
|
backgroundColor = parseColor(root.optNullableString("backgroundColor")),
|
|
393
|
+
blockSurfaceColor = parseColor(root.optNullableString("blockSurfaceColor")),
|
|
394
|
+
structuralLineColor = parseColor(root.optNullableString("structuralLineColor")),
|
|
395
|
+
accentStrongColor = parseColor(root.optNullableString("accentStrongColor")),
|
|
396
|
+
accentStrongTextColor = parseColor(root.optNullableString("accentStrongTextColor")),
|
|
389
397
|
borderRadius = root.optNullableFloat("borderRadius"),
|
|
390
398
|
contentInsets = EditorContentInsets.fromJson(root.optJSONObject("contentInsets"))
|
|
391
399
|
)
|
|
@@ -2,6 +2,9 @@ package com.openeditor.editor
|
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.graphics.Color
|
|
5
|
+
import android.graphics.Canvas
|
|
6
|
+
import android.graphics.Paint
|
|
7
|
+
import android.graphics.Path
|
|
5
8
|
import android.graphics.Rect
|
|
6
9
|
import android.graphics.RectF
|
|
7
10
|
import android.graphics.Typeface
|
|
@@ -49,6 +52,7 @@ internal data class SelectedImageGeometry(val docPos: Int, val rect: RectF)
|
|
|
49
52
|
class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
50
53
|
var onPageOpen: ((String, String, String?, String?) -> Unit)? = null
|
|
51
54
|
var onAttachmentOpen: ((String?, String, String?, Long?, String?) -> Unit)? = null
|
|
55
|
+
var onEmojiEditRequest: ((String, Int, String, JSONObject) -> Unit)? = null
|
|
52
56
|
var isScrollingEnabled: Boolean = true
|
|
53
57
|
|
|
54
58
|
internal var geometryRevision: Long = 0
|
|
@@ -63,6 +67,33 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
63
67
|
val isAtom: Boolean
|
|
64
68
|
)
|
|
65
69
|
|
|
70
|
+
private class ToggleCaretView(context: Context) : View(context) {
|
|
71
|
+
var open: Boolean = true
|
|
72
|
+
set(value) {
|
|
73
|
+
field = value
|
|
74
|
+
invalidate()
|
|
75
|
+
}
|
|
76
|
+
var caretColor: Int = Color.GRAY
|
|
77
|
+
|
|
78
|
+
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
|
|
79
|
+
private val path = Path()
|
|
80
|
+
|
|
81
|
+
override fun onDraw(canvas: Canvas) {
|
|
82
|
+
super.onDraw(canvas)
|
|
83
|
+
val density = resources.displayMetrics.density
|
|
84
|
+
val centerX = width / 2f
|
|
85
|
+
val centerY = height / 2f
|
|
86
|
+
path.reset()
|
|
87
|
+
path.moveTo(centerX - 3f * density, centerY - 4f * density)
|
|
88
|
+
path.lineTo(centerX + 3f * density, centerY)
|
|
89
|
+
path.lineTo(centerX - 3f * density, centerY + 4f * density)
|
|
90
|
+
path.close()
|
|
91
|
+
paint.color = caretColor
|
|
92
|
+
if (open) canvas.rotate(90f, centerX, centerY)
|
|
93
|
+
canvas.drawPath(path, paint)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
66
97
|
private class TextLeaf(context: Context) : AppCompatEditText(context) {
|
|
67
98
|
var positionSegments: List<PositionSegment> = emptyList()
|
|
68
99
|
var editorSurface: NativeBlockEditorSurface? = null
|
|
@@ -204,11 +235,11 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
204
235
|
val available = MeasureSpec.getSize(widthMeasureSpec).coerceAtLeast(1)
|
|
205
236
|
val intrinsicWidth = drawable?.intrinsicWidth?.takeIf { it > 0 } ?: available
|
|
206
237
|
val intrinsicHeight = drawable?.intrinsicHeight?.takeIf { it > 0 } ?: (intrinsicWidth * 0.56f).toInt()
|
|
207
|
-
val
|
|
208
|
-
val
|
|
209
|
-
val
|
|
210
|
-
|
|
211
|
-
setMeasuredDimension(width,
|
|
238
|
+
val sourceWidth = (preferredWidthPx ?: intrinsicWidth).coerceAtLeast(1)
|
|
239
|
+
val sourceHeight = (preferredHeightPx ?: intrinsicHeight).coerceAtLeast(1)
|
|
240
|
+
val width = minOf(sourceWidth, available)
|
|
241
|
+
val height = (width.toFloat() / sourceWidth * sourceHeight).toInt().coerceAtLeast(1)
|
|
242
|
+
setMeasuredDimension(width, height)
|
|
212
243
|
}
|
|
213
244
|
}
|
|
214
245
|
|
|
@@ -221,6 +252,10 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
221
252
|
private var baseTextColor: Int = Color.BLACK
|
|
222
253
|
private var isApplyingRemoteUpdate = false
|
|
223
254
|
private var currentTheme: EditorTheme? = null
|
|
255
|
+
private val resolvedBlockSurfaceColor: Int
|
|
256
|
+
get() = currentTheme?.blockSurfaceColor ?: 0xFFEEEEEE.toInt()
|
|
257
|
+
private val resolvedStructuralLineColor: Int
|
|
258
|
+
get() = currentTheme?.structuralLineColor ?: 0xFFD4D4D4.toInt()
|
|
224
259
|
private var nextTableId = 1
|
|
225
260
|
private val tableCells = mutableListOf<CellView>()
|
|
226
261
|
private var selectionAnchorCell: CellView? = null
|
|
@@ -742,7 +777,7 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
742
777
|
setPadding((depth * 8 * density).toInt(), (6 * density).toInt(), 0, (6 * density).toInt())
|
|
743
778
|
}
|
|
744
779
|
row.addView(
|
|
745
|
-
View(context).apply { setBackgroundColor(currentTheme?.blockquote?.borderColor ?:
|
|
780
|
+
View(context).apply { setBackgroundColor(currentTheme?.blockquote?.borderColor ?: resolvedStructuralLineColor) },
|
|
746
781
|
LinearLayout.LayoutParams(
|
|
747
782
|
(((currentTheme?.blockquote?.borderWidth ?: 3f) * density).toInt()).coerceAtLeast(1),
|
|
748
783
|
ViewGroup.LayoutParams.MATCH_PARENT
|
|
@@ -779,18 +814,24 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
779
814
|
val container = LinearLayout(context).apply {
|
|
780
815
|
orientation = LinearLayout.HORIZONTAL
|
|
781
816
|
gravity = android.view.Gravity.TOP
|
|
782
|
-
val
|
|
783
|
-
|
|
817
|
+
val horizontalPadding = ((currentTheme?.codeBlock?.paddingHorizontal ?: 14f) * density).toInt()
|
|
818
|
+
val verticalPadding = ((currentTheme?.codeBlock?.paddingVertical ?: 12f) * density).toInt()
|
|
819
|
+
setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding)
|
|
784
820
|
background = roundedDrawable(
|
|
785
|
-
|
|
821
|
+
resolvedBlockSurfaceColor,
|
|
786
822
|
Color.TRANSPARENT,
|
|
787
823
|
0f,
|
|
788
|
-
|
|
824
|
+
12f
|
|
789
825
|
)
|
|
790
826
|
}
|
|
791
827
|
container.addView(TextView(context).apply {
|
|
792
|
-
|
|
828
|
+
val emoji = node.optJSONObject("attrs")?.optString("emoji", "💡") ?: "💡"
|
|
829
|
+
text = emoji
|
|
793
830
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 20f)
|
|
831
|
+
contentDescription = "Change callout emoji"
|
|
832
|
+
isClickable = isEditable
|
|
833
|
+
isFocusable = isEditable
|
|
834
|
+
if (isEditable) setOnClickListener { requestEmojiEdit(node, "callout", emoji) }
|
|
794
835
|
})
|
|
795
836
|
val content = LinearLayout(context).apply {
|
|
796
837
|
orientation = LinearLayout.VERTICAL
|
|
@@ -805,17 +846,14 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
805
846
|
}
|
|
806
847
|
|
|
807
848
|
private fun toggleMarkerView(node: JSONObject, content: LinearLayout, initiallyOpen: Boolean): View =
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
849
|
+
ToggleCaretView(context).apply {
|
|
850
|
+
open = initiallyOpen
|
|
851
|
+
caretColor = currentTheme?.list?.markerColor ?: baseTextColor
|
|
811
852
|
contentDescription = if (open) "Collapse toggle item" else "Expand toggle item"
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
|
|
815
|
-
setTypeface(typeface, Typeface.BOLD)
|
|
853
|
+
isClickable = true
|
|
854
|
+
isFocusable = true
|
|
816
855
|
setOnClickListener {
|
|
817
856
|
open = !open
|
|
818
|
-
text = if (open) "⌄" else "›"
|
|
819
857
|
contentDescription = if (open) "Collapse toggle item" else "Expand toggle item"
|
|
820
858
|
setToggleContent(content, open)
|
|
821
859
|
if (isEditable) updateNodeAttrs(node, JSONObject().put("open", open))
|
|
@@ -841,13 +879,23 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
841
879
|
onAppliedUpdate?.invoke(updateJSON)
|
|
842
880
|
}
|
|
843
881
|
|
|
882
|
+
private fun requestEmojiEdit(node: JSONObject, nodeType: String, emoji: String) {
|
|
883
|
+
if (!isEditable) return
|
|
884
|
+
onEmojiEditRequest?.invoke(
|
|
885
|
+
nodeType,
|
|
886
|
+
node.optInt("docPos", 0),
|
|
887
|
+
emoji,
|
|
888
|
+
JSONObject(node.optJSONObject("attrs")?.toString() ?: "{}"),
|
|
889
|
+
)
|
|
890
|
+
}
|
|
891
|
+
|
|
844
892
|
private fun tableView(node: JSONObject, path: List<Int>): View {
|
|
845
893
|
val tableId = nextTableId++
|
|
846
894
|
val table = LinearLayout(context).apply {
|
|
847
895
|
orientation = LinearLayout.VERTICAL
|
|
848
896
|
background = borderDrawable(
|
|
849
897
|
Color.TRANSPARENT,
|
|
850
|
-
currentTheme?.table?.borderColor ?:
|
|
898
|
+
currentTheme?.table?.borderColor ?: resolvedStructuralLineColor,
|
|
851
899
|
1f
|
|
852
900
|
)
|
|
853
901
|
}
|
|
@@ -877,12 +925,10 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
877
925
|
val density = resources.displayMetrics.density
|
|
878
926
|
val fillColor = if (node.optString("nodeType") == "tableHeader") {
|
|
879
927
|
currentTheme?.table?.headerBackgroundColor
|
|
880
|
-
?:
|
|
881
|
-
?: Color.TRANSPARENT
|
|
928
|
+
?: resolvedBlockSurfaceColor
|
|
882
929
|
} else {
|
|
883
930
|
currentTheme?.table?.cellBackgroundColor
|
|
884
|
-
?:
|
|
885
|
-
?: Color.TRANSPARENT
|
|
931
|
+
?: resolvedBlockSurfaceColor
|
|
886
932
|
}
|
|
887
933
|
val cell = CellView(context).apply {
|
|
888
934
|
docPos = node.optInt("docPos", 0)
|
|
@@ -894,7 +940,7 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
894
940
|
setPadding((8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt())
|
|
895
941
|
background = borderDrawable(
|
|
896
942
|
fillColor,
|
|
897
|
-
currentTheme?.table?.borderColor ?:
|
|
943
|
+
currentTheme?.table?.borderColor ?: resolvedStructuralLineColor,
|
|
898
944
|
0.5f
|
|
899
945
|
)
|
|
900
946
|
isClickable = true
|
|
@@ -926,28 +972,42 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
926
972
|
setPadding(horizontal + (depth * 8 * density).toInt(), vertical, horizontal, vertical)
|
|
927
973
|
}
|
|
928
974
|
row.addView(TextView(context).apply {
|
|
929
|
-
|
|
975
|
+
val icon = attrs?.optString("icon", "📄") ?: "📄"
|
|
976
|
+
text = icon
|
|
977
|
+
gravity = android.view.Gravity.CENTER
|
|
978
|
+
includeFontPadding = false
|
|
930
979
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
|
|
931
|
-
|
|
980
|
+
contentDescription = "Change page emoji"
|
|
981
|
+
isClickable = isEditable
|
|
982
|
+
isFocusable = isEditable
|
|
983
|
+
if (isEditable) setOnClickListener { requestEmojiEdit(node, "page", icon) }
|
|
984
|
+
}, LinearLayout.LayoutParams((24 * density).toInt(), (36 * density).toInt()))
|
|
932
985
|
val title = textBlockView(node, depth) as TextLeaf
|
|
933
|
-
title.
|
|
986
|
+
title.typeface = Typeface.create("sans-serif-medium", Typeface.NORMAL)
|
|
987
|
+
title.paintFlags = title.paintFlags or Paint.UNDERLINE_TEXT_FLAG
|
|
988
|
+
title.gravity = android.view.Gravity.CENTER_VERTICAL
|
|
989
|
+
title.includeFontPadding = false
|
|
990
|
+
title.keyListener = null
|
|
991
|
+
title.isCursorVisible = false
|
|
992
|
+
title.isFocusable = false
|
|
993
|
+
title.isFocusableInTouchMode = false
|
|
994
|
+
title.isClickable = false
|
|
995
|
+
title.setTextIsSelectable(false)
|
|
934
996
|
row.addView(title, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
val pageId = attrs?.optString("pageId").orEmpty()
|
|
943
|
-
if (pageId.isNotEmpty()) onPageOpen?.invoke(
|
|
997
|
+
val pageId = attrs?.optString("pageId").orEmpty()
|
|
998
|
+
if (pageId.isNotEmpty()) {
|
|
999
|
+
row.isClickable = true
|
|
1000
|
+
row.isFocusable = true
|
|
1001
|
+
row.contentDescription = "Open page"
|
|
1002
|
+
row.setOnClickListener {
|
|
1003
|
+
onPageOpen?.invoke(
|
|
944
1004
|
pageId,
|
|
945
1005
|
title.text?.toString().orEmpty().ifEmpty { "Untitled" },
|
|
946
1006
|
attrs?.optString("icon")?.takeIf(String::isNotEmpty),
|
|
947
1007
|
attrs?.optString("href")?.takeIf(String::isNotEmpty),
|
|
948
1008
|
)
|
|
949
1009
|
}
|
|
950
|
-
}
|
|
1010
|
+
}
|
|
951
1011
|
return row
|
|
952
1012
|
}
|
|
953
1013
|
|
|
@@ -959,12 +1019,22 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
959
1019
|
gravity = android.view.Gravity.CENTER_VERTICAL
|
|
960
1020
|
val padding = (10 * density).toInt()
|
|
961
1021
|
setPadding(padding + (depth * 8 * density).toInt(), padding, padding, padding)
|
|
962
|
-
background =
|
|
963
|
-
|
|
1022
|
+
background = roundedDrawable(Color.TRANSPARENT, currentTheme?.table?.borderColor ?: baseTextColor, 1f, 12f)
|
|
1023
|
+
val name = attrs?.optString("name").orEmpty().ifEmpty { "Attachment" }
|
|
1024
|
+
val type = attachmentTypeLabel(attrs?.optString("mimeType")?.takeIf(String::isNotEmpty), name)
|
|
1025
|
+
addView(TextView(context).apply {
|
|
1026
|
+
text = type
|
|
1027
|
+
gravity = android.view.Gravity.CENTER
|
|
1028
|
+
setTextColor(currentTheme?.placeholderColor ?: baseTextColor)
|
|
1029
|
+
setTextSize(TypedValue.COMPLEX_UNIT_SP, 9f)
|
|
1030
|
+
setTypeface(typeface, Typeface.BOLD)
|
|
1031
|
+
background = roundedDrawable(resolvedBlockSurfaceColor, Color.TRANSPARENT, 0f, 9f)
|
|
1032
|
+
}, LinearLayout.LayoutParams((42 * density).toInt(), (48 * density).toInt()).apply { marginEnd = (12 * density).toInt() })
|
|
964
1033
|
addView(LinearLayout(context).apply {
|
|
965
1034
|
orientation = LinearLayout.VERTICAL
|
|
966
|
-
addView(TextView(context).apply { text =
|
|
967
|
-
|
|
1035
|
+
addView(TextView(context).apply { text = name; setTextColor(baseTextColor); setTextSize(TypedValue.COMPLEX_UNIT_SP, 15f); setTypeface(typeface, Typeface.BOLD) })
|
|
1036
|
+
val size = attrs?.optLong("size")?.takeIf { attrs.has("size") && !attrs.isNull("size") }?.let(::formatAttachmentSize)
|
|
1037
|
+
addView(TextView(context).apply { text = listOfNotNull(size, type).joinToString(" · "); setTextColor(currentTheme?.placeholderColor ?: baseTextColor); setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) })
|
|
968
1038
|
}, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
|
|
969
1039
|
isClickable = true
|
|
970
1040
|
isFocusable = true
|
|
@@ -981,6 +1051,18 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
981
1051
|
}
|
|
982
1052
|
}
|
|
983
1053
|
|
|
1054
|
+
private fun attachmentTypeLabel(mimeType: String?, name: String): String {
|
|
1055
|
+
val subtype = mimeType?.substringAfterLast('/')?.substringBefore('+')?.takeIf(String::isNotBlank)
|
|
1056
|
+
val extension = name.substringAfterLast('.', "").takeIf(String::isNotBlank)
|
|
1057
|
+
return (subtype ?: extension ?: "FILE").take(4).uppercase()
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
private fun formatAttachmentSize(bytes: Long): String = when {
|
|
1061
|
+
bytes < 1_024 -> "$bytes B"
|
|
1062
|
+
bytes < 1_048_576 -> if (bytes < 10_240) "%.1f KB".format(bytes / 1_024.0) else "%.0f KB".format(bytes / 1_024.0)
|
|
1063
|
+
else -> if (bytes < 10_485_760) "%.1f MB".format(bytes / 1_048_576.0) else "%.0f MB".format(bytes / 1_048_576.0)
|
|
1064
|
+
}
|
|
1065
|
+
|
|
984
1066
|
private fun configureTextLeaf(leaf: TextLeaf, node: JSONObject) {
|
|
985
1067
|
val rendered = attributedInlineContent(node)
|
|
986
1068
|
val hadFocus = leaf.hasFocus()
|
|
@@ -1004,7 +1086,7 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
1004
1086
|
minLines = 1
|
|
1005
1087
|
background = if (node.optString("nodeType") == "codeBlock" || node.optString("nodeType") == "code_block") {
|
|
1006
1088
|
roundedDrawable(
|
|
1007
|
-
currentTheme?.codeBlock?.backgroundColor ?:
|
|
1089
|
+
currentTheme?.codeBlock?.backgroundColor ?: resolvedBlockSurfaceColor,
|
|
1008
1090
|
Color.TRANSPARENT,
|
|
1009
1091
|
0f,
|
|
1010
1092
|
currentTheme?.codeBlock?.borderRadius ?: 8f
|
|
@@ -1163,7 +1245,7 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
1163
1245
|
setPadding(0, margin, 0, margin)
|
|
1164
1246
|
addView(
|
|
1165
1247
|
View(context).apply {
|
|
1166
|
-
setBackgroundColor(currentTheme?.horizontalRule?.color ?:
|
|
1248
|
+
setBackgroundColor(currentTheme?.horizontalRule?.color ?: resolvedStructuralLineColor)
|
|
1167
1249
|
},
|
|
1168
1250
|
LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, thickness)
|
|
1169
1251
|
)
|
|
@@ -1174,11 +1256,11 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
1174
1256
|
}
|
|
1175
1257
|
return TextView(context).apply {
|
|
1176
1258
|
text = node.optString("label", node.optString("nodeType", "Block"))
|
|
1177
|
-
setTextColor(currentTheme?.
|
|
1259
|
+
setTextColor(currentTheme?.placeholderColor ?: baseTextColor)
|
|
1178
1260
|
textSize = 14f
|
|
1179
1261
|
setPadding(12, 8, 12, 8)
|
|
1180
1262
|
background = borderDrawable(
|
|
1181
|
-
|
|
1263
|
+
resolvedBlockSurfaceColor,
|
|
1182
1264
|
Color.TRANSPARENT,
|
|
1183
1265
|
0f
|
|
1184
1266
|
)
|
|
@@ -1200,7 +1282,7 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
1200
1282
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
|
1201
1283
|
adjustViewBounds = true
|
|
1202
1284
|
background = roundedDrawable(
|
|
1203
|
-
|
|
1285
|
+
resolvedBlockSurfaceColor,
|
|
1204
1286
|
Color.TRANSPARENT,
|
|
1205
1287
|
0f,
|
|
1206
1288
|
8f
|
|
@@ -1230,7 +1312,7 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
1230
1312
|
for (imageView in imageViews) {
|
|
1231
1313
|
val selected = isEditable && imageView.docPos == selectedNodePos
|
|
1232
1314
|
imageView.background = roundedDrawable(
|
|
1233
|
-
if (imageView.drawable == null)
|
|
1315
|
+
if (imageView.drawable == null) resolvedBlockSurfaceColor else Color.TRANSPARENT,
|
|
1234
1316
|
if (selected) 0xFF2F80ED.toInt() else Color.TRANSPARENT,
|
|
1235
1317
|
if (selected) 2f else 0f,
|
|
1236
1318
|
8f
|
|
@@ -1525,14 +1607,14 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
1525
1607
|
val checked = node.optJSONObject("attrs")?.optBoolean("checked", false) ?: false
|
|
1526
1608
|
return TextView(context).apply {
|
|
1527
1609
|
text = if (checked) "✓" else ""
|
|
1528
|
-
setTextColor(Color.WHITE)
|
|
1610
|
+
setTextColor(currentTheme?.accentStrongTextColor ?: Color.WHITE)
|
|
1529
1611
|
textSize = 12f
|
|
1530
1612
|
gravity = android.view.Gravity.CENTER
|
|
1531
1613
|
includeFontPadding = false
|
|
1532
1614
|
background = roundedDrawable(
|
|
1533
|
-
if (checked) (currentTheme?.
|
|
1534
|
-
|
|
1535
|
-
|
|
1615
|
+
if (checked) (currentTheme?.accentStrongColor ?: baseTextColor) else resolvedBlockSurfaceColor,
|
|
1616
|
+
Color.TRANSPARENT,
|
|
1617
|
+
0f,
|
|
1536
1618
|
4f
|
|
1537
1619
|
)
|
|
1538
1620
|
setOnClickListener { toggleTaskItem(node) }
|
|
@@ -741,6 +741,21 @@ class NativeEditorExpoView(
|
|
|
741
741
|
url?.let { event["url"] = it }
|
|
742
742
|
emitAddonEvent(mapOf("eventJson" to JSONObject(event).toString()))
|
|
743
743
|
}
|
|
744
|
+
richTextView.onEmojiEditRequest = { nodeType, docPos, emoji, attrs ->
|
|
745
|
+
emitAddonEvent(
|
|
746
|
+
mapOf(
|
|
747
|
+
"eventJson" to JSONObject(
|
|
748
|
+
mapOf(
|
|
749
|
+
"type" to "emojiEditRequest",
|
|
750
|
+
"nodeType" to nodeType,
|
|
751
|
+
"docPos" to docPos,
|
|
752
|
+
"emoji" to emoji,
|
|
753
|
+
"attrs" to attrs,
|
|
754
|
+
)
|
|
755
|
+
).toString()
|
|
756
|
+
)
|
|
757
|
+
)
|
|
758
|
+
}
|
|
744
759
|
richTextView.onBeforeDetachedFromWindow = {
|
|
745
760
|
prepareForDetachFromWindow()
|
|
746
761
|
}
|
|
@@ -357,6 +357,11 @@ class NativeEditorModule : Module() {
|
|
|
357
357
|
val editorId = nativeULong(id) ?: return@Function nativeArgumentError("editor id")
|
|
358
358
|
editorSetMark(editorId, markName, attrsJson)
|
|
359
359
|
}
|
|
360
|
+
Function("editorUpdateNodeAttrs") { id: Int, pos: Int, attrsJson: String ->
|
|
361
|
+
val editorId = nativeULong(id) ?: return@Function nativeArgumentError("editor id")
|
|
362
|
+
val position = nativeUInt(pos) ?: return@Function nativeArgumentError("position")
|
|
363
|
+
editorUpdateNodeAttrs(editorId, position, attrsJson)
|
|
364
|
+
}
|
|
360
365
|
Function("editorUnsetMark") { id: Int, markName: String ->
|
|
361
366
|
val editorId = nativeULong(id) ?: return@Function nativeArgumentError("editor id")
|
|
362
367
|
editorUnsetMark(editorId, markName)
|
|
@@ -8,6 +8,7 @@ import android.util.AttributeSet
|
|
|
8
8
|
import android.view.ViewGroup
|
|
9
9
|
import android.widget.FrameLayout
|
|
10
10
|
import android.widget.LinearLayout
|
|
11
|
+
import org.json.JSONObject
|
|
11
12
|
import uniffi.editor_core.*
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -40,6 +41,7 @@ class RichTextEditorView @JvmOverloads constructor(
|
|
|
40
41
|
var onFocusChange: ((Boolean) -> Unit)? = null
|
|
41
42
|
var onPageOpen: ((String, String, String?, String?) -> Unit)? = null
|
|
42
43
|
var onAttachmentOpen: ((String?, String, String?, Long?, String?) -> Unit)? = null
|
|
44
|
+
var onEmojiEditRequest: ((String, Int, String, JSONObject) -> Unit)? = null
|
|
43
45
|
|
|
44
46
|
var editorId: Long
|
|
45
47
|
get() = currentEditorId
|
|
@@ -64,6 +66,7 @@ class RichTextEditorView @JvmOverloads constructor(
|
|
|
64
66
|
blockSurface.onFocusChange = { focused -> onFocusChange?.invoke(focused) }
|
|
65
67
|
blockSurface.onPageOpen = { pageId, title, icon, href -> onPageOpen?.invoke(pageId, title, icon, href) }
|
|
66
68
|
blockSurface.onAttachmentOpen = { attachmentId, name, mimeType, size, url -> onAttachmentOpen?.invoke(attachmentId, name, mimeType, size, url) }
|
|
69
|
+
blockSurface.onEmojiEditRequest = { nodeType, docPos, emoji, attrs -> onEmojiEditRequest?.invoke(nodeType, docPos, emoji, attrs) }
|
|
67
70
|
updateAppearance()
|
|
68
71
|
}
|
|
69
72
|
|
package/dist/EditorTheme.d.ts
CHANGED
|
@@ -115,6 +115,10 @@ export interface EditorTheme {
|
|
|
115
115
|
toolbar?: EditorToolbarTheme;
|
|
116
116
|
placeholderColor?: string;
|
|
117
117
|
backgroundColor?: string;
|
|
118
|
+
blockSurfaceColor?: string;
|
|
119
|
+
structuralLineColor?: string;
|
|
120
|
+
accentStrongColor?: string;
|
|
121
|
+
accentStrongTextColor?: string;
|
|
118
122
|
borderRadius?: number;
|
|
119
123
|
contentInsets?: EditorContentInsets;
|
|
120
124
|
}
|
|
@@ -32,6 +32,7 @@ export interface NativeEditorModule {
|
|
|
32
32
|
editorInsertContentJsonAtSelectionScalar(editorId: number, scalarAnchor: number, scalarHead: number, json: string): string;
|
|
33
33
|
editorToggleMark(editorId: number, markName: string): string;
|
|
34
34
|
editorSetMark(editorId: number, markName: string, attrsJson: string): string;
|
|
35
|
+
editorUpdateNodeAttrs(editorId: number, pos: number, attrsJson: string): string;
|
|
35
36
|
editorUnsetMark(editorId: number, markName: string): string;
|
|
36
37
|
editorToggleBlockquote(editorId: number): string;
|
|
37
38
|
editorToggleHeading(editorId: number, level: number): string;
|
|
@@ -241,6 +242,8 @@ export declare class NativeEditorBridge {
|
|
|
241
242
|
deleteRange(from: number, to: number): EditorUpdate | null;
|
|
242
243
|
/** Replace the current selection with text atomically. */
|
|
243
244
|
replaceSelectionText(text: string): EditorUpdate | null;
|
|
245
|
+
/** Replace a node's attributes at its document position. */
|
|
246
|
+
updateNodeAttrs(pos: number, attrs: Record<string, unknown>): EditorUpdate | null;
|
|
244
247
|
/** Toggle a mark (bold, italic, etc.) on the current selection. */
|
|
245
248
|
toggleMark(markType: string): EditorUpdate | null;
|
|
246
249
|
/** Set a mark with attrs on the current selection. */
|
|
@@ -519,6 +519,11 @@ class NativeEditorBridge {
|
|
|
519
519
|
this.assertNotDestroyed();
|
|
520
520
|
return this.runPreparedCommand(() => getNativeModule().editorReplaceSelectionText(this._editorId, text));
|
|
521
521
|
}
|
|
522
|
+
/** Replace a node's attributes at its document position. */
|
|
523
|
+
updateNodeAttrs(pos, attrs) {
|
|
524
|
+
this.assertNotDestroyed();
|
|
525
|
+
return this.runPreparedCommand(() => getNativeModule().editorUpdateNodeAttrs(this._editorId, pos, JSON.stringify(attrs)));
|
|
526
|
+
}
|
|
522
527
|
/** Toggle a mark (bold, italic, etc.) on the current selection. */
|
|
523
528
|
toggleMark(markType) {
|
|
524
529
|
this.assertNotDestroyed();
|
|
@@ -96,6 +96,13 @@ export interface NativeRichTextEditorProps {
|
|
|
96
96
|
size: number | null;
|
|
97
97
|
url: string | null;
|
|
98
98
|
}) => void;
|
|
99
|
+
/** Called when an editable callout or page emoji is pressed. */
|
|
100
|
+
onRequestEmoji?: (request: {
|
|
101
|
+
nodeType: 'callout' | 'page';
|
|
102
|
+
docPos: number;
|
|
103
|
+
emoji: string;
|
|
104
|
+
attrs: Record<string, unknown>;
|
|
105
|
+
}) => void;
|
|
99
106
|
/** Whether plain URLs typed or pasted into the editor should be converted into link marks automatically. */
|
|
100
107
|
autoDetectLinks?: boolean;
|
|
101
108
|
/** Whether `data:image/...` sources are accepted for image insertion and HTML parsing. */
|
|
@@ -163,6 +170,8 @@ export interface NativeRichTextEditorRef {
|
|
|
163
170
|
moveTableCell(forward?: boolean): void;
|
|
164
171
|
/** Insert text at the current cursor position. */
|
|
165
172
|
insertText(text: string): void;
|
|
173
|
+
/** Replace a node's attributes at its document position. */
|
|
174
|
+
updateNodeAttrs(pos: number, attrs: Record<string, unknown>): void;
|
|
166
175
|
/** Insert HTML content at the current selection. */
|
|
167
176
|
insertContentHtml(html: string): void;
|
|
168
177
|
/** Insert JSON content at the current selection. */
|
|
@@ -559,7 +559,7 @@ function doesLiveMentionQueryConflictWithNativeSelectRequest(request, currentQue
|
|
|
559
559
|
currentQuery.range.anchor !== request.range.anchor ||
|
|
560
560
|
currentQuery.range.head !== request.range.head);
|
|
561
561
|
}
|
|
562
|
-
exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEditor({ initialContent, initialJSON, value, valueJSON, valueJSONRevision, valueJSONUpdateMode = 'replace', preserveSelectionOnValueJSONReset = false, selectionOnValueJSONReset, schema, placeholder, editable = true, maxLength, autoFocus = false, autoCapitalize, autoCorrect, keyboardType, keyboardAppearance, heightBehavior = 'autoGrow', showToolbar = true, toolbarPlacement = 'keyboard', toolbarItems = EditorToolbar_1.DEFAULT_EDITOR_TOOLBAR_ITEMS, onToolbarAction, onRequestLink, onRequestImage, onOpenPage, onOpenAttachment, autoDetectLinks = false, onContentChange, onContentChangeJSON, onSelectionChange, onActiveStateChange, onHistoryStateChange, onFocus, onBlur, style, containerStyle, theme, addons, remoteSelections, allowBase64Images = false, allowImageResizing = true, }, ref) {
|
|
562
|
+
exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEditor({ initialContent, initialJSON, value, valueJSON, valueJSONRevision, valueJSONUpdateMode = 'replace', preserveSelectionOnValueJSONReset = false, selectionOnValueJSONReset, schema, placeholder, editable = true, maxLength, autoFocus = false, autoCapitalize, autoCorrect, keyboardType, keyboardAppearance, heightBehavior = 'autoGrow', showToolbar = true, toolbarPlacement = 'keyboard', toolbarItems = EditorToolbar_1.DEFAULT_EDITOR_TOOLBAR_ITEMS, onToolbarAction, onRequestLink, onRequestImage, onOpenPage, onOpenAttachment, onRequestEmoji, autoDetectLinks = false, onContentChange, onContentChangeJSON, onSelectionChange, onActiveStateChange, onHistoryStateChange, onFocus, onBlur, style, containerStyle, theme, addons, remoteSelections, allowBase64Images = false, allowImageResizing = true, }, ref) {
|
|
563
563
|
const bridgeRef = (0, react_1.useRef)(null);
|
|
564
564
|
const nativeViewRef = (0, react_1.useRef)(null);
|
|
565
565
|
const [isReady, setIsReady] = (0, react_1.useState)(false);
|
|
@@ -2054,6 +2054,15 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
2054
2054
|
}
|
|
2055
2055
|
if (!parsed)
|
|
2056
2056
|
return;
|
|
2057
|
+
if (parsed.type === 'emojiEditRequest') {
|
|
2058
|
+
onRequestEmoji?.({
|
|
2059
|
+
nodeType: parsed.nodeType,
|
|
2060
|
+
docPos: parsed.docPos,
|
|
2061
|
+
emoji: parsed.emoji,
|
|
2062
|
+
attrs: parsed.attrs,
|
|
2063
|
+
});
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2057
2066
|
if (parsed.type === 'attachmentOpen') {
|
|
2058
2067
|
onOpenAttachment?.({
|
|
2059
2068
|
attachmentId: parsed.attachmentId ?? null,
|
|
@@ -2188,7 +2197,9 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
2188
2197
|
resolveMentionInsertionAttrs,
|
|
2189
2198
|
runAndApplyWithCommandRetry,
|
|
2190
2199
|
setMentionQueryEventState,
|
|
2200
|
+
onOpenAttachment,
|
|
2191
2201
|
onOpenPage,
|
|
2202
|
+
onRequestEmoji,
|
|
2192
2203
|
syncPreflightUpdateFromNativeEvent,
|
|
2193
2204
|
]);
|
|
2194
2205
|
(0, react_1.useImperativeHandle)(ref, () => ({
|
|
@@ -2259,6 +2270,9 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
2259
2270
|
insertText(text) {
|
|
2260
2271
|
runAndApply(() => bridgeRef.current?.replaceSelectionText(text) ?? null);
|
|
2261
2272
|
},
|
|
2273
|
+
updateNodeAttrs(pos, attrs) {
|
|
2274
|
+
runAndApply(() => bridgeRef.current?.updateNodeAttrs(pos, attrs) ?? null);
|
|
2275
|
+
},
|
|
2262
2276
|
insertContentHtml(html) {
|
|
2263
2277
|
runAndApply(() => bridgeRef.current?.insertContentHtml(html) ?? null);
|
|
2264
2278
|
},
|
package/dist/addons.d.ts
CHANGED
|
@@ -65,6 +65,12 @@ export interface SerializedEditorAddons {
|
|
|
65
65
|
mentions?: SerializedMentionsAddonConfig;
|
|
66
66
|
}
|
|
67
67
|
export type EditorAddonEvent = {
|
|
68
|
+
type: 'emojiEditRequest';
|
|
69
|
+
nodeType: 'callout' | 'page';
|
|
70
|
+
docPos: number;
|
|
71
|
+
emoji: string;
|
|
72
|
+
attrs: Record<string, unknown>;
|
|
73
|
+
} | {
|
|
68
74
|
type: 'attachmentOpen';
|
|
69
75
|
attachmentId?: string | null;
|
|
70
76
|
name: string;
|
package/ios/EditorTheme.swift
CHANGED
|
@@ -106,6 +106,22 @@ struct EditorHorizontalRuleTheme {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
struct EditorTableTheme {
|
|
110
|
+
var cellBackgroundColor: UIColor?
|
|
111
|
+
var headerBackgroundColor: UIColor?
|
|
112
|
+
var borderColor: UIColor?
|
|
113
|
+
var selectionBackgroundColor: UIColor?
|
|
114
|
+
var selectionBorderColor: UIColor?
|
|
115
|
+
|
|
116
|
+
init(dictionary: [String: Any]) {
|
|
117
|
+
cellBackgroundColor = EditorTheme.color(from: dictionary["cellBackgroundColor"])
|
|
118
|
+
headerBackgroundColor = EditorTheme.color(from: dictionary["headerBackgroundColor"])
|
|
119
|
+
borderColor = EditorTheme.color(from: dictionary["borderColor"])
|
|
120
|
+
selectionBackgroundColor = EditorTheme.color(from: dictionary["selectionBackgroundColor"])
|
|
121
|
+
selectionBorderColor = EditorTheme.color(from: dictionary["selectionBorderColor"])
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
109
125
|
struct EditorBlockquoteTheme {
|
|
110
126
|
var text: EditorTextStyle?
|
|
111
127
|
var indent: CGFloat?
|
|
@@ -316,12 +332,17 @@ struct EditorTheme {
|
|
|
316
332
|
var codeBlock: EditorCodeBlockTheme?
|
|
317
333
|
var headings: [String: EditorTextStyle] = [:]
|
|
318
334
|
var list: EditorListTheme?
|
|
335
|
+
var table: EditorTableTheme?
|
|
319
336
|
var horizontalRule: EditorHorizontalRuleTheme?
|
|
320
337
|
var mentions: EditorMentionTheme?
|
|
321
338
|
var links: EditorLinkTheme?
|
|
322
339
|
var toolbar: EditorToolbarTheme?
|
|
323
340
|
var placeholderColor: UIColor?
|
|
324
341
|
var backgroundColor: UIColor?
|
|
342
|
+
var blockSurfaceColor: UIColor?
|
|
343
|
+
var structuralLineColor: UIColor?
|
|
344
|
+
var accentStrongColor: UIColor?
|
|
345
|
+
var accentStrongTextColor: UIColor?
|
|
325
346
|
var borderRadius: CGFloat?
|
|
326
347
|
var contentInsets: EditorContentInsets?
|
|
327
348
|
|
|
@@ -358,6 +379,9 @@ struct EditorTheme {
|
|
|
358
379
|
if let list = dictionary["list"] as? [String: Any] {
|
|
359
380
|
self.list = EditorListTheme(dictionary: list)
|
|
360
381
|
}
|
|
382
|
+
if let table = dictionary["table"] as? [String: Any] {
|
|
383
|
+
self.table = EditorTableTheme(dictionary: table)
|
|
384
|
+
}
|
|
361
385
|
if let horizontalRule = dictionary["horizontalRule"] as? [String: Any] {
|
|
362
386
|
self.horizontalRule = EditorHorizontalRuleTheme(dictionary: horizontalRule)
|
|
363
387
|
}
|
|
@@ -372,6 +396,10 @@ struct EditorTheme {
|
|
|
372
396
|
}
|
|
373
397
|
placeholderColor = EditorTheme.color(from: dictionary["placeholderColor"])
|
|
374
398
|
backgroundColor = EditorTheme.color(from: dictionary["backgroundColor"])
|
|
399
|
+
blockSurfaceColor = EditorTheme.color(from: dictionary["blockSurfaceColor"])
|
|
400
|
+
structuralLineColor = EditorTheme.color(from: dictionary["structuralLineColor"])
|
|
401
|
+
accentStrongColor = EditorTheme.color(from: dictionary["accentStrongColor"])
|
|
402
|
+
accentStrongTextColor = EditorTheme.color(from: dictionary["accentStrongTextColor"])
|
|
375
403
|
borderRadius = EditorTheme.cgFloat(dictionary["borderRadius"])
|
|
376
404
|
if let contentInsets = dictionary["contentInsets"] as? [String: Any] {
|
|
377
405
|
self.contentInsets = EditorContentInsets(dictionary: contentInsets)
|
|
@@ -3,6 +3,7 @@ import UIKit
|
|
|
3
3
|
final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestureRecognizerDelegate {
|
|
4
4
|
var onPageOpen: ((String, String, String?, String?) -> Void)?
|
|
5
5
|
var onAttachmentOpen: ((String?, String, String?, NSNumber?, String?) -> Void)?
|
|
6
|
+
var onEmojiEditRequest: ((String, UInt32, String, [String: Any]) -> Void)?
|
|
6
7
|
private final class AttachmentRow: UIStackView {
|
|
7
8
|
var open: (() -> Void)?
|
|
8
9
|
override init(frame: CGRect) {
|
|
@@ -12,6 +13,29 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
12
13
|
}
|
|
13
14
|
required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
|
14
15
|
@objc private func handleOpen() { open?() }
|
|
16
|
+
override func accessibilityActivate() -> Bool {
|
|
17
|
+
open?()
|
|
18
|
+
return true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
private final class PageRow: UIStackView, UIGestureRecognizerDelegate {
|
|
22
|
+
var open: (() -> Void)?
|
|
23
|
+
override init(frame: CGRect) {
|
|
24
|
+
super.init(frame: frame)
|
|
25
|
+
isUserInteractionEnabled = true
|
|
26
|
+
let tap = UITapGestureRecognizer(target: self, action: #selector(handleOpen))
|
|
27
|
+
tap.delegate = self
|
|
28
|
+
addGestureRecognizer(tap)
|
|
29
|
+
}
|
|
30
|
+
required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
|
31
|
+
@objc private func handleOpen() { open?() }
|
|
32
|
+
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
|
33
|
+
!(touch.view is UIControl)
|
|
34
|
+
}
|
|
35
|
+
override func accessibilityActivate() -> Bool {
|
|
36
|
+
open?()
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
15
39
|
}
|
|
16
40
|
private final class TextLeafView: UITextView {
|
|
17
41
|
struct PositionSegment {
|
|
@@ -192,7 +216,7 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
192
216
|
var preferredWidth: CGFloat?
|
|
193
217
|
var preferredHeight: CGFloat?
|
|
194
218
|
weak var preferredWidthConstraint: NSLayoutConstraint?
|
|
195
|
-
|
|
219
|
+
private var aspectRatioConstraint: NSLayoutConstraint?
|
|
196
220
|
|
|
197
221
|
override var intrinsicContentSize: CGSize {
|
|
198
222
|
let ratio = image.flatMap { image -> CGFloat? in
|
|
@@ -206,7 +230,12 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
206
230
|
func refreshPreferredSizeConstraints() {
|
|
207
231
|
let size = intrinsicContentSize
|
|
208
232
|
preferredWidthConstraint?.constant = size.width
|
|
209
|
-
|
|
233
|
+
let ratio = size.height / max(1, size.width)
|
|
234
|
+
aspectRatioConstraint?.isActive = false
|
|
235
|
+
let constraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: ratio)
|
|
236
|
+
constraint.priority = .required
|
|
237
|
+
constraint.isActive = true
|
|
238
|
+
aspectRatioConstraint = constraint
|
|
210
239
|
}
|
|
211
240
|
}
|
|
212
241
|
|
|
@@ -215,6 +244,12 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
215
244
|
private var currentEditorId: UInt64 = 0
|
|
216
245
|
private var isApplyingRemoteUpdate = false
|
|
217
246
|
private var currentTheme: EditorTheme?
|
|
247
|
+
private var resolvedBlockSurfaceColor: UIColor {
|
|
248
|
+
currentTheme?.blockSurfaceColor ?? UIColor(white: 238 / 255, alpha: 1)
|
|
249
|
+
}
|
|
250
|
+
private var resolvedStructuralLineColor: UIColor {
|
|
251
|
+
currentTheme?.structuralLineColor ?? UIColor(white: 212 / 255, alpha: 1)
|
|
252
|
+
}
|
|
218
253
|
private var nextTableId = 1
|
|
219
254
|
private var tableCells: [CellView] = []
|
|
220
255
|
private weak var selectionAnchorCell: CellView?
|
|
@@ -836,10 +871,10 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
836
871
|
container.layoutMargins = UIEdgeInsets(top: 6, left: 12 + CGFloat(depth) * 8, bottom: 6, right: 0)
|
|
837
872
|
container.isLayoutMarginsRelativeArrangement = true
|
|
838
873
|
if (node["nodeType"] as? String) == "blockquote" {
|
|
839
|
-
container.layer.borderColor =
|
|
874
|
+
container.layer.borderColor = resolvedStructuralLineColor.cgColor
|
|
840
875
|
container.layer.borderWidth = 0
|
|
841
876
|
let border = BlockquoteStripeView()
|
|
842
|
-
border.backgroundColor = currentTheme?.blockquote?.borderColor ??
|
|
877
|
+
border.backgroundColor = currentTheme?.blockquote?.borderColor ?? resolvedStructuralLineColor
|
|
843
878
|
let row = UIStackView()
|
|
844
879
|
row.axis = .horizontal
|
|
845
880
|
row.spacing = currentTheme?.blockquote?.markerGap ?? 8
|
|
@@ -868,14 +903,22 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
868
903
|
container.axis = .horizontal
|
|
869
904
|
container.alignment = .top
|
|
870
905
|
container.spacing = 10
|
|
871
|
-
|
|
906
|
+
let horizontalPadding = currentTheme?.codeBlock?.paddingHorizontal ?? 14
|
|
907
|
+
let verticalPadding = currentTheme?.codeBlock?.paddingVertical ?? 12
|
|
908
|
+
container.layoutMargins = UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding)
|
|
872
909
|
container.isLayoutMarginsRelativeArrangement = true
|
|
873
|
-
container.backgroundColor =
|
|
874
|
-
container.layer.cornerRadius =
|
|
875
|
-
|
|
876
|
-
let emoji =
|
|
877
|
-
|
|
878
|
-
emoji.
|
|
910
|
+
container.backgroundColor = resolvedBlockSurfaceColor
|
|
911
|
+
container.layer.cornerRadius = 12
|
|
912
|
+
|
|
913
|
+
let emoji = UIButton(type: .system)
|
|
914
|
+
let emojiValue = stringAttr(node, "emoji") ?? "💡"
|
|
915
|
+
emoji.setTitle(emojiValue, for: .normal)
|
|
916
|
+
emoji.titleLabel?.font = .systemFont(ofSize: 20)
|
|
917
|
+
emoji.accessibilityLabel = "Change callout emoji"
|
|
918
|
+
emoji.isEnabled = isEditable
|
|
919
|
+
emoji.addAction(UIAction { [weak self] _ in
|
|
920
|
+
self?.requestEmojiEdit(for: node, nodeType: "callout", emoji: emojiValue)
|
|
921
|
+
}, for: .touchUpInside)
|
|
879
922
|
container.addArrangedSubview(emoji)
|
|
880
923
|
|
|
881
924
|
let content = UIStackView()
|
|
@@ -891,13 +934,14 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
891
934
|
private func toggleMarkerView(for node: [String: Any], content: UIStackView) -> UIView {
|
|
892
935
|
let open = boolAttr(node, "open") ?? true
|
|
893
936
|
let button = UIButton(type: .system)
|
|
894
|
-
button.
|
|
895
|
-
button.
|
|
937
|
+
button.setImage(toggleCaretImage(), for: .normal)
|
|
938
|
+
button.tintColor = currentTheme?.list?.markerColor ?? .secondaryLabel
|
|
939
|
+
button.imageView?.transform = open ? CGAffineTransform(rotationAngle: .pi / 2) : .identity
|
|
896
940
|
button.accessibilityLabel = open ? "Collapse toggle item" : "Expand toggle item"
|
|
897
941
|
button.addAction(UIAction { [weak self, weak button, weak content] _ in
|
|
898
942
|
guard let self, let button, let content else { return }
|
|
899
|
-
let nextOpen = button.
|
|
900
|
-
button.
|
|
943
|
+
let nextOpen = button.accessibilityLabel == "Expand toggle item"
|
|
944
|
+
button.imageView?.transform = nextOpen ? CGAffineTransform(rotationAngle: .pi / 2) : .identity
|
|
901
945
|
button.accessibilityLabel = nextOpen ? "Collapse toggle item" : "Expand toggle item"
|
|
902
946
|
self.setToggleContent(content, open: nextOpen)
|
|
903
947
|
if self.isEditable {
|
|
@@ -916,6 +960,19 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
916
960
|
return wrapper
|
|
917
961
|
}
|
|
918
962
|
|
|
963
|
+
private func toggleCaretImage() -> UIImage? {
|
|
964
|
+
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 8, height: 10))
|
|
965
|
+
return renderer.image { _ in
|
|
966
|
+
let path = UIBezierPath()
|
|
967
|
+
path.move(to: CGPoint(x: 1, y: 1))
|
|
968
|
+
path.addLine(to: CGPoint(x: 7, y: 5))
|
|
969
|
+
path.addLine(to: CGPoint(x: 1, y: 9))
|
|
970
|
+
path.close()
|
|
971
|
+
UIColor.white.setFill()
|
|
972
|
+
path.fill()
|
|
973
|
+
}.withRenderingMode(.alwaysTemplate)
|
|
974
|
+
}
|
|
975
|
+
|
|
919
976
|
private func setToggleContent(_ content: UIStackView, open: Bool) {
|
|
920
977
|
for (index, child) in content.arrangedSubviews.enumerated() where index > 0 {
|
|
921
978
|
child.isHidden = !open
|
|
@@ -935,6 +992,13 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
935
992
|
onAppliedUpdate?(updateJSON)
|
|
936
993
|
}
|
|
937
994
|
|
|
995
|
+
private func requestEmojiEdit(for node: [String: Any], nodeType: String, emoji: String) {
|
|
996
|
+
guard isEditable else { return }
|
|
997
|
+
let attrs = node["attrs"] as? [String: Any] ?? [:]
|
|
998
|
+
let pos = (node["docPos"] as? NSNumber)?.uint32Value ?? 0
|
|
999
|
+
onEmojiEditRequest?(nodeType, pos, emoji, attrs)
|
|
1000
|
+
}
|
|
1001
|
+
|
|
938
1002
|
func blockquoteStripeRectsForTesting() -> [CGRect] {
|
|
939
1003
|
layoutIfNeeded()
|
|
940
1004
|
return descendants(of: self)
|
|
@@ -980,7 +1044,7 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
980
1044
|
let table = UIStackView()
|
|
981
1045
|
table.axis = .vertical
|
|
982
1046
|
table.spacing = 0
|
|
983
|
-
table.layer.borderColor =
|
|
1047
|
+
table.layer.borderColor = (currentTheme?.table?.borderColor ?? resolvedStructuralLineColor).cgColor
|
|
984
1048
|
table.layer.borderWidth = 1
|
|
985
1049
|
table.layer.cornerRadius = 6
|
|
986
1050
|
table.clipsToBounds = true
|
|
@@ -1014,11 +1078,11 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1014
1078
|
cell.spacing = 4
|
|
1015
1079
|
cell.layoutMargins = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
|
|
1016
1080
|
cell.isLayoutMarginsRelativeArrangement = true
|
|
1017
|
-
cell.layer.borderColor =
|
|
1081
|
+
cell.layer.borderColor = (currentTheme?.table?.borderColor ?? resolvedStructuralLineColor).cgColor
|
|
1018
1082
|
cell.layer.borderWidth = 0.5
|
|
1019
1083
|
cell.baseFillColor = (node["nodeType"] as? String) == "tableHeader"
|
|
1020
|
-
?
|
|
1021
|
-
: (currentTheme?.
|
|
1084
|
+
? (currentTheme?.table?.headerBackgroundColor ?? resolvedBlockSurfaceColor)
|
|
1085
|
+
: (currentTheme?.table?.cellBackgroundColor ?? resolvedBlockSurfaceColor)
|
|
1022
1086
|
cell.backgroundColor = cell.baseFillColor
|
|
1023
1087
|
cell.isUserInteractionEnabled = true
|
|
1024
1088
|
let tap = UITapGestureRecognizer(target: self, action: #selector(handleCellTap(_:)))
|
|
@@ -1051,7 +1115,7 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1051
1115
|
}
|
|
1052
1116
|
|
|
1053
1117
|
private func pageView(for node: [String: Any], depth: Int) -> UIView {
|
|
1054
|
-
let row =
|
|
1118
|
+
let row = PageRow()
|
|
1055
1119
|
row.axis = .horizontal
|
|
1056
1120
|
row.alignment = .center
|
|
1057
1121
|
row.spacing = 8
|
|
@@ -1059,30 +1123,47 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1059
1123
|
row.isLayoutMarginsRelativeArrangement = true
|
|
1060
1124
|
row.layer.cornerRadius = 8
|
|
1061
1125
|
|
|
1062
|
-
let icon =
|
|
1063
|
-
|
|
1064
|
-
icon.
|
|
1126
|
+
let icon = UIButton(type: .system)
|
|
1127
|
+
let iconValue = stringAttr(node, "icon") ?? "📄"
|
|
1128
|
+
icon.setTitle(iconValue, for: .normal)
|
|
1129
|
+
icon.titleLabel?.font = .systemFont(ofSize: 18)
|
|
1130
|
+
icon.accessibilityLabel = "Change page emoji"
|
|
1131
|
+
icon.isEnabled = isEditable
|
|
1132
|
+
icon.addAction(UIAction { [weak self] _ in
|
|
1133
|
+
self?.requestEmojiEdit(for: node, nodeType: "page", emoji: iconValue)
|
|
1134
|
+
}, for: .touchUpInside)
|
|
1065
1135
|
icon.setContentHuggingPriority(.required, for: .horizontal)
|
|
1136
|
+
icon.widthAnchor.constraint(equalToConstant: 24).isActive = true
|
|
1066
1137
|
row.addArrangedSubview(icon)
|
|
1067
1138
|
|
|
1068
1139
|
let title = textBlockView(for: node, depth: depth)
|
|
1069
1140
|
if let leaf = title as? UITextView {
|
|
1070
|
-
|
|
1141
|
+
let titleText = NSMutableAttributedString(attributedString: leaf.attributedText)
|
|
1142
|
+
if titleText.length > 0 {
|
|
1143
|
+
titleText.addAttributes([
|
|
1144
|
+
.font: UIFont.systemFont(ofSize: 16, weight: .medium),
|
|
1145
|
+
.foregroundColor: baseTextColor,
|
|
1146
|
+
.underlineStyle: NSUnderlineStyle.single.rawValue,
|
|
1147
|
+
.underlineColor: baseTextColor.withAlphaComponent(0.28),
|
|
1148
|
+
], range: NSRange(location: 0, length: titleText.length))
|
|
1149
|
+
}
|
|
1150
|
+
leaf.attributedText = titleText
|
|
1151
|
+
leaf.textContainerInset = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
|
|
1071
1152
|
leaf.textColor = baseTextColor
|
|
1153
|
+
leaf.isEditable = false
|
|
1154
|
+
leaf.isSelectable = false
|
|
1155
|
+
leaf.isUserInteractionEnabled = false
|
|
1072
1156
|
}
|
|
1073
1157
|
row.addArrangedSubview(title)
|
|
1074
1158
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
open
|
|
1079
|
-
open.setContentHuggingPriority(.required, for: .horizontal)
|
|
1080
|
-
open.addAction(UIAction { [weak self, weak title] _ in
|
|
1159
|
+
row.isAccessibilityElement = true
|
|
1160
|
+
row.accessibilityTraits = .button
|
|
1161
|
+
row.accessibilityLabel = "Open page"
|
|
1162
|
+
row.open = { [weak self, weak title] in
|
|
1081
1163
|
guard let pageId = self?.stringAttr(node, "pageId"), !pageId.isEmpty else { return }
|
|
1082
1164
|
let pageTitle = (title as? UITextView)?.text ?? "Untitled"
|
|
1083
1165
|
self?.onPageOpen?(pageId, pageTitle, self?.stringAttr(node, "icon"), self?.stringAttr(node, "href"))
|
|
1084
|
-
}
|
|
1085
|
-
row.addArrangedSubview(open)
|
|
1166
|
+
}
|
|
1086
1167
|
return row
|
|
1087
1168
|
}
|
|
1088
1169
|
|
|
@@ -1095,10 +1176,23 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1095
1176
|
row.isLayoutMarginsRelativeArrangement = true
|
|
1096
1177
|
row.layer.cornerRadius = 10
|
|
1097
1178
|
row.layer.borderWidth = 1
|
|
1098
|
-
row.layer.borderColor =
|
|
1099
|
-
let
|
|
1100
|
-
icon
|
|
1101
|
-
icon.
|
|
1179
|
+
row.layer.borderColor = resolvedStructuralLineColor.cgColor
|
|
1180
|
+
let type = attachmentTypeLabel(mimeType: stringAttr(node, "mimeType"), name: stringAttr(node, "name") ?? "Attachment")
|
|
1181
|
+
let icon = UIView()
|
|
1182
|
+
icon.backgroundColor = resolvedBlockSurfaceColor
|
|
1183
|
+
icon.layer.cornerRadius = 9
|
|
1184
|
+
icon.widthAnchor.constraint(equalToConstant: 42).isActive = true
|
|
1185
|
+
icon.heightAnchor.constraint(equalToConstant: 48).isActive = true
|
|
1186
|
+
let typeLabel = UILabel()
|
|
1187
|
+
typeLabel.text = type
|
|
1188
|
+
typeLabel.font = .systemFont(ofSize: 9, weight: .bold)
|
|
1189
|
+
typeLabel.textColor = .secondaryLabel
|
|
1190
|
+
typeLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
1191
|
+
icon.addSubview(typeLabel)
|
|
1192
|
+
NSLayoutConstraint.activate([
|
|
1193
|
+
typeLabel.centerXAnchor.constraint(equalTo: icon.centerXAnchor),
|
|
1194
|
+
typeLabel.centerYAnchor.constraint(equalTo: icon.centerYAnchor),
|
|
1195
|
+
])
|
|
1102
1196
|
row.addArrangedSubview(icon)
|
|
1103
1197
|
let details = UIStackView()
|
|
1104
1198
|
details.axis = .vertical
|
|
@@ -1109,7 +1203,8 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1109
1203
|
name.textColor = baseTextColor
|
|
1110
1204
|
name.lineBreakMode = .byTruncatingMiddle
|
|
1111
1205
|
let meta = UILabel()
|
|
1112
|
-
|
|
1206
|
+
let size = formatAttachmentSize((node["attrs"] as? [String: Any])?["size"] as? NSNumber)
|
|
1207
|
+
meta.text = [size, type].compactMap { $0 }.joined(separator: " · ")
|
|
1113
1208
|
meta.font = .systemFont(ofSize: 12)
|
|
1114
1209
|
meta.textColor = .secondaryLabel
|
|
1115
1210
|
details.addArrangedSubview(name)
|
|
@@ -1129,6 +1224,20 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1129
1224
|
return row
|
|
1130
1225
|
}
|
|
1131
1226
|
|
|
1227
|
+
private func attachmentTypeLabel(mimeType: String?, name: String) -> String {
|
|
1228
|
+
let subtype = mimeType?.split(separator: "/").last?.split(separator: "+").first.map(String.init)
|
|
1229
|
+
let parts = name.split(separator: ".")
|
|
1230
|
+
let fileExtension = parts.count > 1 ? parts.last.map(String.init) : nil
|
|
1231
|
+
return String((subtype ?? fileExtension ?? "FILE").prefix(4)).uppercased()
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
private func formatAttachmentSize(_ size: NSNumber?) -> String? {
|
|
1235
|
+
guard let bytes = size?.doubleValue, bytes >= 0 else { return nil }
|
|
1236
|
+
if bytes < 1_024 { return "\(Int(bytes.rounded())) B" }
|
|
1237
|
+
if bytes < 1_048_576 { return String(format: bytes < 10_240 ? "%.1f KB" : "%.0f KB", bytes / 1_024) }
|
|
1238
|
+
return String(format: bytes < 10_485_760 ? "%.1f MB" : "%.0f MB", bytes / 1_048_576)
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1132
1241
|
private func configureTextLeaf(_ leaf: TextLeafView, for node: [String: Any]) {
|
|
1133
1242
|
let wasFirstResponder = leaf.isFirstResponder
|
|
1134
1243
|
let previousSelection = leaf.selectedRange
|
|
@@ -1174,7 +1283,7 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1174
1283
|
]
|
|
1175
1284
|
}
|
|
1176
1285
|
if isCodeBlock(node) {
|
|
1177
|
-
leaf.backgroundColor = currentTheme?.codeBlock?.backgroundColor ??
|
|
1286
|
+
leaf.backgroundColor = currentTheme?.codeBlock?.backgroundColor ?? resolvedBlockSurfaceColor
|
|
1178
1287
|
leaf.textContainerInset = UIEdgeInsets(
|
|
1179
1288
|
top: currentTheme?.codeBlock?.paddingVertical ?? 8,
|
|
1180
1289
|
left: currentTheme?.codeBlock?.paddingHorizontal ?? 12,
|
|
@@ -1301,7 +1410,7 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1301
1410
|
let margin = currentTheme?.horizontalRule?.verticalMargin ?? 16
|
|
1302
1411
|
let wrapper = UIView()
|
|
1303
1412
|
let line = UIView()
|
|
1304
|
-
line.backgroundColor = currentTheme?.horizontalRule?.color ??
|
|
1413
|
+
line.backgroundColor = currentTheme?.horizontalRule?.color ?? resolvedStructuralLineColor
|
|
1305
1414
|
wrapper.addSubview(line)
|
|
1306
1415
|
let thickness = currentTheme?.horizontalRule?.thickness ?? 1
|
|
1307
1416
|
wrapper.heightAnchor.constraint(equalToConstant: margin * 2 + thickness).isActive = true
|
|
@@ -1320,8 +1429,8 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1320
1429
|
let label = UILabel()
|
|
1321
1430
|
label.text = node["label"] as? String ?? node["nodeType"] as? String ?? "Block"
|
|
1322
1431
|
label.font = .systemFont(ofSize: 14, weight: .medium)
|
|
1323
|
-
label.textColor = .secondaryLabel
|
|
1324
|
-
label.backgroundColor =
|
|
1432
|
+
label.textColor = currentTheme?.placeholderColor ?? .secondaryLabel
|
|
1433
|
+
label.backgroundColor = resolvedBlockSurfaceColor
|
|
1325
1434
|
label.layer.cornerRadius = 6
|
|
1326
1435
|
label.clipsToBounds = true
|
|
1327
1436
|
label.numberOfLines = 0
|
|
@@ -1338,7 +1447,7 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1338
1447
|
imageView.contentMode = .scaleAspectFit
|
|
1339
1448
|
imageView.clipsToBounds = true
|
|
1340
1449
|
imageView.layer.cornerRadius = 8
|
|
1341
|
-
imageView.backgroundColor =
|
|
1450
|
+
imageView.backgroundColor = resolvedBlockSurfaceColor
|
|
1342
1451
|
imageView.isUserInteractionEnabled = true
|
|
1343
1452
|
imageView.addGestureRecognizer(
|
|
1344
1453
|
UITapGestureRecognizer(target: self, action: #selector(handleImageTap(_:)))
|
|
@@ -1357,19 +1466,15 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1357
1466
|
equalToConstant: imageView.intrinsicContentSize.width
|
|
1358
1467
|
)
|
|
1359
1468
|
preferredWidth.priority = .defaultHigh
|
|
1360
|
-
let preferredHeight = imageView.heightAnchor.constraint(
|
|
1361
|
-
equalToConstant: imageView.intrinsicContentSize.height
|
|
1362
|
-
)
|
|
1363
1469
|
imageView.preferredWidthConstraint = preferredWidth
|
|
1364
|
-
imageView.preferredHeightConstraint = preferredHeight
|
|
1365
1470
|
NSLayoutConstraint.activate([
|
|
1366
1471
|
imageView.leadingAnchor.constraint(equalTo: wrapper.leadingAnchor),
|
|
1367
1472
|
imageView.topAnchor.constraint(equalTo: wrapper.topAnchor),
|
|
1368
1473
|
imageView.bottomAnchor.constraint(equalTo: wrapper.bottomAnchor),
|
|
1369
1474
|
imageView.widthAnchor.constraint(lessThanOrEqualTo: wrapper.widthAnchor),
|
|
1370
|
-
preferredWidth
|
|
1371
|
-
preferredHeight
|
|
1475
|
+
preferredWidth
|
|
1372
1476
|
])
|
|
1477
|
+
imageView.refreshPreferredSizeConstraints()
|
|
1373
1478
|
return wrapper
|
|
1374
1479
|
}
|
|
1375
1480
|
|
|
@@ -1687,13 +1792,14 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1687
1792
|
let button = UIButton(type: .custom)
|
|
1688
1793
|
button.setTitle(checked ? "✓" : "", for: .normal)
|
|
1689
1794
|
button.titleLabel?.font = .systemFont(ofSize: 12, weight: .bold)
|
|
1690
|
-
button.setTitleColor(.white, for: .normal)
|
|
1691
|
-
button.backgroundColor = checked
|
|
1692
|
-
|
|
1693
|
-
|
|
1795
|
+
button.setTitleColor(currentTheme?.accentStrongTextColor ?? .white, for: .normal)
|
|
1796
|
+
button.backgroundColor = checked
|
|
1797
|
+
? (currentTheme?.accentStrongColor ?? baseTextColor)
|
|
1798
|
+
: resolvedBlockSurfaceColor
|
|
1799
|
+
button.layer.borderWidth = 0
|
|
1694
1800
|
button.layer.cornerRadius = 4
|
|
1695
|
-
button.widthAnchor.constraint(equalToConstant:
|
|
1696
|
-
button.heightAnchor.constraint(equalToConstant:
|
|
1801
|
+
button.widthAnchor.constraint(equalToConstant: 20).isActive = true
|
|
1802
|
+
button.heightAnchor.constraint(equalToConstant: 20).isActive = true
|
|
1697
1803
|
button.addAction(UIAction { [weak self] _ in
|
|
1698
1804
|
self?.toggleTaskItem(node)
|
|
1699
1805
|
}, for: .touchUpInside)
|
|
@@ -1831,8 +1937,12 @@ final class NativeBlockEditorSurface: UIScrollView, UITextViewDelegate, UIGestur
|
|
|
1831
1937
|
} else {
|
|
1832
1938
|
selected = cell === anchor || cell === head
|
|
1833
1939
|
}
|
|
1834
|
-
cell.backgroundColor = selected
|
|
1835
|
-
|
|
1940
|
+
cell.backgroundColor = selected
|
|
1941
|
+
? (currentTheme?.table?.selectionBackgroundColor ?? cell.baseFillColor)
|
|
1942
|
+
: cell.baseFillColor
|
|
1943
|
+
cell.layer.borderColor = selected
|
|
1944
|
+
? (currentTheme?.table?.selectionBorderColor ?? baseTextColor).cgColor
|
|
1945
|
+
: (currentTheme?.table?.borderColor ?? resolvedStructuralLineColor).cgColor
|
|
1836
1946
|
cell.layer.borderWidth = selected ? 1.5 : 0.5
|
|
1837
1947
|
}
|
|
1838
1948
|
}
|
|
@@ -2119,6 +2119,18 @@ class NativeEditorExpoView: ExpoView, UIGestureRecognizerDelegate {
|
|
|
2119
2119
|
let json = String(data: data, encoding: .utf8) else { return }
|
|
2120
2120
|
self?.onAddonEvent(["eventJson": json])
|
|
2121
2121
|
}
|
|
2122
|
+
richTextView.onEmojiEditRequest = { [weak self] nodeType, docPos, emoji, attrs in
|
|
2123
|
+
let event: [String: Any] = [
|
|
2124
|
+
"type": "emojiEditRequest",
|
|
2125
|
+
"nodeType": nodeType,
|
|
2126
|
+
"docPos": docPos,
|
|
2127
|
+
"emoji": emoji,
|
|
2128
|
+
"attrs": attrs,
|
|
2129
|
+
]
|
|
2130
|
+
guard let data = try? JSONSerialization.data(withJSONObject: event),
|
|
2131
|
+
let json = String(data: data, encoding: .utf8) else { return }
|
|
2132
|
+
self?.onAddonEvent(["eventJson": json])
|
|
2133
|
+
}
|
|
2122
2134
|
configureAccessoryToolbar()
|
|
2123
2135
|
|
|
2124
2136
|
addSubview(richTextView)
|
|
@@ -200,6 +200,12 @@ public class NativeEditorModule: Module {
|
|
|
200
200
|
guard let editorId = nativeUInt64(id) else { return nativeArgumentError("editor id") }
|
|
201
201
|
return editorSetMark(id: editorId, markName: markName, attrsJson: attrsJson)
|
|
202
202
|
}
|
|
203
|
+
Function("editorUpdateNodeAttrs") { (id: Int, pos: Int, attrsJson: String) -> String in
|
|
204
|
+
guard let editorId = nativeUInt64(id), let position = nativeUInt32(pos) else {
|
|
205
|
+
return nativeArgumentError("position")
|
|
206
|
+
}
|
|
207
|
+
return editorUpdateNodeAttrs(id: editorId, pos: position, attrsJson: attrsJson)
|
|
208
|
+
}
|
|
203
209
|
Function("editorUnsetMark") { (id: Int, markName: String) -> String in
|
|
204
210
|
guard let editorId = nativeUInt64(id) else { return nativeArgumentError("editor id") }
|
|
205
211
|
return editorUnsetMark(id: editorId, markName: markName)
|
|
@@ -648,6 +648,7 @@ final class RichTextEditorView: UIView {
|
|
|
648
648
|
var onFocusChange: ((Bool) -> Void)?
|
|
649
649
|
var onPageOpen: ((String, String, String?, String?) -> Void)?
|
|
650
650
|
var onAttachmentOpen: ((String?, String, String?, NSNumber?, String?) -> Void)?
|
|
651
|
+
var onEmojiEditRequest: ((String, UInt32, String, [String: Any]) -> Void)?
|
|
651
652
|
private var lastAutoGrowWidth: CGFloat = 0
|
|
652
653
|
private var cachedAutoGrowMeasuredHeight: CGFloat = 0
|
|
653
654
|
private var remoteSelections: [RemoteSelectionDecoration] = []
|
|
@@ -768,6 +769,9 @@ final class RichTextEditorView: UIView {
|
|
|
768
769
|
blockSurface.onAttachmentOpen = { [weak self] attachmentId, name, mimeType, size, url in
|
|
769
770
|
self?.onAttachmentOpen?(attachmentId, name, mimeType, size, url)
|
|
770
771
|
}
|
|
772
|
+
blockSurface.onEmojiEditRequest = { [weak self] nodeType, docPos, emoji, attrs in
|
|
773
|
+
self?.onEmojiEditRequest?(nodeType, docPos, emoji, attrs)
|
|
774
|
+
}
|
|
771
775
|
blockSurface.onContentHeightMayChange = { [weak self] measuredHeight in
|
|
772
776
|
guard let self, self.heightBehavior == .autoGrow else { return }
|
|
773
777
|
self.cachedAutoGrowMeasuredHeight = measuredHeight
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openeditor/react-native-prose-editor",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
4
4
|
"description": "OpenEditor-owned native rich text editor engine for React Native",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/EasyLink-HQ/openeditor",
|