@openeditor/react-native-prose-editor 0.0.9 → 0.0.11
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 +5 -9
- package/android/src/main/java/com/apollohg/editor/EditorTheme.kt +23 -0
- package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +2 -2
- package/android/src/main/java/com/apollohg/editor/NativeBlockEditorSurface.kt +476 -53
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +110 -169
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +1 -1
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +124 -337
- package/dist/EditorTheme.d.ts +8 -0
- package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/ios/Generated_editor_core.swift +15 -0
- package/ios/NativeBlockEditorSurface.swift +371 -60
- package/ios/NativeEditorExpoView.swift +114 -117
- package/ios/NativeInputSupport.swift +0 -302
- package/ios/PositionBridge.swift +22 -548
- package/ios/RichTextEditorView.swift +217 -4671
- package/ios/editor_coreFFI/editor_coreFFI.h +11 -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
- package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +31 -0
- package/android/src/main/java/com/apollohg/editor/CaretGeometry.kt +0 -50
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +0 -4068
- package/android/src/main/java/com/apollohg/editor/EditorInputConnection.kt +0 -1009
- package/android/src/main/java/com/apollohg/editor/InputSnapshotSupport.kt +0 -6
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
package com.openeditor.editor
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
-
import android.graphics.Canvas
|
|
5
4
|
import android.graphics.Color
|
|
6
|
-
import android.graphics.Paint
|
|
7
5
|
import android.graphics.Rect
|
|
8
6
|
import android.graphics.RectF
|
|
9
7
|
import android.graphics.Typeface
|
|
10
8
|
import android.graphics.drawable.GradientDrawable
|
|
11
9
|
import android.text.SpannableStringBuilder
|
|
12
10
|
import android.text.Spanned
|
|
11
|
+
import android.text.Editable
|
|
12
|
+
import android.text.TextWatcher
|
|
13
13
|
import android.text.style.BackgroundColorSpan
|
|
14
14
|
import android.text.style.ForegroundColorSpan
|
|
15
15
|
import android.text.style.StrikethroughSpan
|
|
@@ -19,19 +19,32 @@ import android.text.style.UnderlineSpan
|
|
|
19
19
|
import android.view.MotionEvent
|
|
20
20
|
import android.view.View
|
|
21
21
|
import android.view.ViewGroup
|
|
22
|
+
import android.view.inputmethod.BaseInputConnection
|
|
23
|
+
import android.view.inputmethod.EditorInfo
|
|
24
|
+
import android.view.inputmethod.InputConnection
|
|
25
|
+
import android.view.inputmethod.InputConnectionWrapper
|
|
26
|
+
import android.view.inputmethod.InputMethodManager
|
|
22
27
|
import android.util.TypedValue
|
|
28
|
+
import android.text.InputType
|
|
23
29
|
import android.widget.ImageView
|
|
24
30
|
import android.widget.LinearLayout
|
|
25
31
|
import android.widget.ScrollView
|
|
26
32
|
import android.widget.TextView
|
|
33
|
+
import androidx.appcompat.widget.AppCompatEditText
|
|
27
34
|
import org.json.JSONObject
|
|
28
35
|
import uniffi.editor_core.editorDocToScalar
|
|
36
|
+
import uniffi.editor_core.editorDeleteBackwardAtSelectionScalar
|
|
29
37
|
import uniffi.editor_core.editorGetSelectionState
|
|
38
|
+
import uniffi.editor_core.editorReplaceSelectionText
|
|
30
39
|
import uniffi.editor_core.editorResizeImageAtDocPos
|
|
31
40
|
import uniffi.editor_core.editorSetCellSelection
|
|
32
41
|
import uniffi.editor_core.editorSetNodeSelection
|
|
33
42
|
import uniffi.editor_core.editorSetSelection
|
|
43
|
+
import uniffi.editor_core.editorSplitBlock
|
|
34
44
|
import uniffi.editor_core.editorToggleTaskItemCheckedAtSelectionScalar
|
|
45
|
+
import uniffi.editor_core.editorUpdateNodeAttrs
|
|
46
|
+
|
|
47
|
+
internal data class SelectedImageGeometry(val docPos: Int, val rect: RectF)
|
|
35
48
|
|
|
36
49
|
class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
37
50
|
var isScrollingEnabled: Boolean = true
|
|
@@ -48,8 +61,61 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
48
61
|
val isAtom: Boolean
|
|
49
62
|
)
|
|
50
63
|
|
|
51
|
-
private class TextLeaf(context: Context) :
|
|
64
|
+
private class TextLeaf(context: Context) : AppCompatEditText(context) {
|
|
52
65
|
var positionSegments: List<PositionSegment> = emptyList()
|
|
66
|
+
var editorSurface: NativeBlockEditorSurface? = null
|
|
67
|
+
private var beforeText: String = ""
|
|
68
|
+
private var compositionBaselineText: String? = null
|
|
69
|
+
private var beforeSelectionStart: Int = 0
|
|
70
|
+
private var beforeSelectionEnd: Int = 0
|
|
71
|
+
|
|
72
|
+
init {
|
|
73
|
+
addTextChangedListener(object : TextWatcher {
|
|
74
|
+
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
|
75
|
+
beforeText = s?.toString().orEmpty()
|
|
76
|
+
if (editorSurface?.isApplyingRemoteUpdate != true && compositionBaselineText == null) {
|
|
77
|
+
compositionBaselineText = beforeText
|
|
78
|
+
}
|
|
79
|
+
beforeSelectionStart = selectionStart.coerceAtLeast(0)
|
|
80
|
+
beforeSelectionEnd = selectionEnd.coerceAtLeast(beforeSelectionStart)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
|
|
84
|
+
|
|
85
|
+
override fun afterTextChanged(s: Editable?) {
|
|
86
|
+
val surface = editorSurface ?: return
|
|
87
|
+
if (surface.isApplyingRemoteUpdate) return
|
|
88
|
+
if (s != null && BaseInputConnection.getComposingSpanStart(s) >= 0) return
|
|
89
|
+
val baseline = compositionBaselineText ?: beforeText
|
|
90
|
+
compositionBaselineText = null
|
|
91
|
+
surface.commitLeafTextMutation(this@TextLeaf, baseline, s?.toString().orEmpty())
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
|
|
97
|
+
super.onSelectionChanged(selStart, selEnd)
|
|
98
|
+
editorSurface?.selectionChanged(this, selStart, selEnd)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? {
|
|
102
|
+
val target = super.onCreateInputConnection(outAttrs) ?: return null
|
|
103
|
+
return object : InputConnectionWrapper(target, false) {
|
|
104
|
+
override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean {
|
|
105
|
+
if (beforeLength > 0 && selectionStart == 0 && selectionEnd == 0 &&
|
|
106
|
+
editorSurface?.deleteBackwardAtLeafBoundary(this@TextLeaf) == true
|
|
107
|
+
) return true
|
|
108
|
+
return super.deleteSurroundingText(beforeLength, afterLength)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
override fun deleteSurroundingTextInCodePoints(beforeLength: Int, afterLength: Int): Boolean {
|
|
112
|
+
if (beforeLength > 0 && selectionStart == 0 && selectionEnd == 0 &&
|
|
113
|
+
editorSurface?.deleteBackwardAtLeafBoundary(this@TextLeaf) == true
|
|
114
|
+
) return true
|
|
115
|
+
return super.deleteSurroundingTextInCodePoints(beforeLength, afterLength)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
53
119
|
|
|
54
120
|
fun docPositionForOffset(offset: Int): Int? {
|
|
55
121
|
val segment = positionSegments.firstOrNull {
|
|
@@ -82,8 +148,9 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
82
148
|
|
|
83
149
|
fun caretRectForDocPosition(docPosition: Int, caretWidth: Float): RectF? {
|
|
84
150
|
val textLayout = layout ?: return null
|
|
85
|
-
val
|
|
86
|
-
val
|
|
151
|
+
val textLength = text?.length ?: 0
|
|
152
|
+
val offset = nativeOffsetForDocPosition(docPosition)?.coerceIn(0, textLength) ?: return null
|
|
153
|
+
val lookupOffset = offset.coerceAtMost((textLength - 1).coerceAtLeast(0))
|
|
87
154
|
val line = textLayout.getLineForOffset(lookupOffset)
|
|
88
155
|
val left = compoundPaddingLeft + textLayout.getPrimaryHorizontal(offset)
|
|
89
156
|
return RectF(
|
|
@@ -96,8 +163,9 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
96
163
|
|
|
97
164
|
fun selectionRectsForDocRange(from: Int, to: Int): List<RectF> {
|
|
98
165
|
val textLayout = layout ?: return emptyList()
|
|
99
|
-
val
|
|
100
|
-
val
|
|
166
|
+
val textLength = text?.length ?: 0
|
|
167
|
+
val start = nativeOffsetForDocPosition(from)?.coerceIn(0, textLength) ?: return emptyList()
|
|
168
|
+
val end = nativeOffsetForDocPosition(to)?.coerceIn(start, textLength) ?: return emptyList()
|
|
101
169
|
if (start == end) return emptyList()
|
|
102
170
|
val firstLine = textLayout.getLineForOffset(start)
|
|
103
171
|
val lastLine = textLayout.getLineForOffset((end - 1).coerceAtLeast(start))
|
|
@@ -162,22 +230,117 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
162
230
|
private val textLeafViews = mutableListOf<TextLeaf>()
|
|
163
231
|
private var selectedNodePos: Int? = null
|
|
164
232
|
private var selectedTextPos: Int? = null
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
var
|
|
233
|
+
var onAppliedUpdate: ((String) -> Unit)? = null
|
|
234
|
+
var onSelectionChange: ((Int, Int) -> Unit)? = null
|
|
235
|
+
var onEditorUpdate: ((String) -> Unit)? = null
|
|
236
|
+
var onFocusChange: ((Boolean) -> Unit)? = null
|
|
237
|
+
var placeholder: String = ""
|
|
168
238
|
set(value) {
|
|
169
239
|
field = value
|
|
170
|
-
|
|
240
|
+
textLeafViews.forEach { updateLeafHint(it) }
|
|
171
241
|
}
|
|
172
|
-
|
|
173
|
-
var
|
|
174
|
-
var
|
|
242
|
+
private var configuredAutoCapitalize: String? = null
|
|
243
|
+
private var configuredAutoCorrect: Boolean? = null
|
|
244
|
+
private var configuredKeyboardType: String? = null
|
|
175
245
|
var isEditable: Boolean = true
|
|
176
246
|
set(value) {
|
|
177
247
|
field = value
|
|
248
|
+
textLeafViews.forEach {
|
|
249
|
+
it.isEnabled = value
|
|
250
|
+
it.isFocusable = value
|
|
251
|
+
it.isFocusableInTouchMode = value
|
|
252
|
+
it.setTextIsSelectable(value)
|
|
253
|
+
}
|
|
178
254
|
refreshImageSelection()
|
|
179
255
|
}
|
|
180
256
|
|
|
257
|
+
internal val activeEditText: AppCompatEditText?
|
|
258
|
+
get() = textLeafViews.firstOrNull { it.hasFocus() }
|
|
259
|
+
?: selectedTextPos?.let { position ->
|
|
260
|
+
textLeafViews.firstOrNull { it.nativeOffsetForDocPosition(position) != null }
|
|
261
|
+
}
|
|
262
|
+
?: textLeafViews.firstOrNull()
|
|
263
|
+
|
|
264
|
+
val isEditorFocused: Boolean get() = textLeafViews.any { it.hasFocus() }
|
|
265
|
+
|
|
266
|
+
fun focus(): Boolean {
|
|
267
|
+
val leaf = activeEditText ?: return false
|
|
268
|
+
val focused = leaf.requestFocus()
|
|
269
|
+
if (focused) {
|
|
270
|
+
context.getSystemService(InputMethodManager::class.java)
|
|
271
|
+
?.showSoftInput(leaf, InputMethodManager.SHOW_IMPLICIT)
|
|
272
|
+
}
|
|
273
|
+
return focused
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
fun blur() {
|
|
277
|
+
activeEditText?.clearFocus()
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
fun hasActiveComposition(): Boolean {
|
|
281
|
+
val editable = activeEditText?.text ?: return false
|
|
282
|
+
return BaseInputConnection.getComposingSpanStart(editable) >= 0
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
fun currentScalarSelection(): Pair<Int, Int>? {
|
|
286
|
+
val selection = activeDocSelection() ?: return null
|
|
287
|
+
if (currentEditorId == 0L) return null
|
|
288
|
+
return editorDocToScalar(currentEditorId.toULong(), selection.first.toUInt()).toInt() to
|
|
289
|
+
editorDocToScalar(currentEditorId.toULong(), selection.second.toUInt()).toInt()
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
fun activeDocPositionForUtf16Offset(offset: Int): Int? {
|
|
293
|
+
val leaf = activeEditText as? TextLeaf ?: return null
|
|
294
|
+
return leaf.docPositionForOffset(offset)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
fun setAutoCapitalize(value: String?) {
|
|
298
|
+
configuredAutoCapitalize = value
|
|
299
|
+
val flag = when (value) {
|
|
300
|
+
"none" -> 0
|
|
301
|
+
"words" -> InputType.TYPE_TEXT_FLAG_CAP_WORDS
|
|
302
|
+
"characters" -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
|
303
|
+
else -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
|
304
|
+
}
|
|
305
|
+
textLeafViews.forEach { leaf ->
|
|
306
|
+
leaf.inputType = (leaf.inputType and (
|
|
307
|
+
InputType.TYPE_TEXT_FLAG_CAP_WORDS or
|
|
308
|
+
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES or
|
|
309
|
+
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
|
310
|
+
).inv()) or flag
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
fun setAutoCorrect(enabled: Boolean?) {
|
|
315
|
+
configuredAutoCorrect = enabled
|
|
316
|
+
textLeafViews.forEach { leaf ->
|
|
317
|
+
leaf.inputType = if (enabled == true) {
|
|
318
|
+
(leaf.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS.inv()) or InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
|
|
319
|
+
} else {
|
|
320
|
+
(leaf.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT.inv()) or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
fun setKeyboardType(value: String?) {
|
|
326
|
+
configuredKeyboardType = value
|
|
327
|
+
val inputClass = when (value) {
|
|
328
|
+
"numeric", "number-pad", "decimal-pad" -> InputType.TYPE_CLASS_NUMBER
|
|
329
|
+
"phone-pad" -> InputType.TYPE_CLASS_PHONE
|
|
330
|
+
else -> InputType.TYPE_CLASS_TEXT
|
|
331
|
+
}
|
|
332
|
+
textLeafViews.forEach { it.inputType = (it.inputType and InputType.TYPE_MASK_CLASS.inv()) or inputClass }
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private fun activeDocSelection(): Pair<Int, Int>? {
|
|
336
|
+
val leaf = activeEditText as? TextLeaf ?: return null
|
|
337
|
+
val start = leaf.selectionStart.coerceAtLeast(0)
|
|
338
|
+
val end = leaf.selectionEnd.coerceAtLeast(start)
|
|
339
|
+
val anchor = leaf.docPositionForOffset(start) ?: return null
|
|
340
|
+
val head = leaf.docPositionForOffset(end) ?: return null
|
|
341
|
+
return anchor to head
|
|
342
|
+
}
|
|
343
|
+
|
|
181
344
|
init {
|
|
182
345
|
isFillViewport = false
|
|
183
346
|
clipToPadding = false
|
|
@@ -193,12 +356,6 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
193
356
|
override fun onTouchEvent(event: MotionEvent): Boolean =
|
|
194
357
|
isScrollingEnabled && super.onTouchEvent(event)
|
|
195
358
|
|
|
196
|
-
override fun dispatchDraw(canvas: Canvas) {
|
|
197
|
-
super.dispatchDraw(canvas)
|
|
198
|
-
if (!isEditable || !isInputFocused || selectedNodePos != null) return
|
|
199
|
-
selectedCaretRect()?.let { canvas.drawRect(it, caretPaint) }
|
|
200
|
-
}
|
|
201
|
-
|
|
202
359
|
fun bind(editorId: Long) {
|
|
203
360
|
currentEditorId = editorId
|
|
204
361
|
if (editorId == 0L) {
|
|
@@ -227,10 +384,26 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
227
384
|
val bottomInset = ((theme?.contentInsets?.bottom ?: 0f) * density).toInt()
|
|
228
385
|
val leftInset = ((theme?.contentInsets?.left ?: 0f) * density).toInt()
|
|
229
386
|
setPadding(leftInset, topInset, rightInset, bottomInset)
|
|
387
|
+
currentRoot?.let { root ->
|
|
388
|
+
val focusedSelection = activeDocSelection()
|
|
389
|
+
// Retire the old input connection before replacing themed leaves. Keeping
|
|
390
|
+
// a detached EditText as the IME target can make Android replay its text
|
|
391
|
+
// into the newly focused canonical leaf after a uiMode change.
|
|
392
|
+
if (focusedSelection != null) activeEditText?.clearFocus()
|
|
393
|
+
isApplyingRemoteUpdate = true
|
|
394
|
+
rebuildAll(root)
|
|
395
|
+
refreshTableCellRegistry()
|
|
396
|
+
isApplyingRemoteUpdate = false
|
|
397
|
+
focusedSelection?.let { applyNativeSelection(it.first, it.second, true) }
|
|
398
|
+
}
|
|
230
399
|
}
|
|
231
400
|
|
|
232
401
|
fun applyUpdateJSON(updateJSON: String, editorId: Long) {
|
|
402
|
+
val shouldPreserveTextFocus = textLeafViews.any { it.hasFocus() }
|
|
233
403
|
val update = try { JSONObject(updateJSON) } catch (_: Throwable) { return }
|
|
404
|
+
val textSelection = update.optJSONObject("selection")?.takeIf {
|
|
405
|
+
it.optString("type") == "text"
|
|
406
|
+
}?.let { it.optInt("anchor") to it.optInt("head") }
|
|
234
407
|
update.optJSONObject("selection")?.let { selection ->
|
|
235
408
|
selectedNodePos = if (selection.optString("type") == "node") {
|
|
236
409
|
selection.optInt("pos")
|
|
@@ -243,7 +416,11 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
243
416
|
) selection.optInt("anchor") else null
|
|
244
417
|
refreshImageSelection()
|
|
245
418
|
}
|
|
246
|
-
val root = update.optJSONObject("renderTree")?.optJSONObject("root")
|
|
419
|
+
val root = update.optJSONObject("renderTree")?.optJSONObject("root")
|
|
420
|
+
if (root == null) {
|
|
421
|
+
textSelection?.let { applyNativeSelection(it.first, it.second, shouldPreserveTextFocus) }
|
|
422
|
+
return
|
|
423
|
+
}
|
|
247
424
|
currentEditorId = editorId
|
|
248
425
|
isApplyingRemoteUpdate = true
|
|
249
426
|
val operations = update.optJSONObject("renderPatch")?.optJSONArray("operations")
|
|
@@ -256,6 +433,7 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
256
433
|
refreshImageSelection()
|
|
257
434
|
currentRoot = root
|
|
258
435
|
isApplyingRemoteUpdate = false
|
|
436
|
+
textSelection?.let { applyNativeSelection(it.first, it.second, shouldPreserveTextFocus) }
|
|
259
437
|
geometryRevision += 1
|
|
260
438
|
requestLayout()
|
|
261
439
|
}
|
|
@@ -345,6 +523,10 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
345
523
|
rebuildAll(root)
|
|
346
524
|
return
|
|
347
525
|
}
|
|
526
|
+
if (oldView is TextLeaf) {
|
|
527
|
+
configureTextLeaf(oldView, node)
|
|
528
|
+
return
|
|
529
|
+
}
|
|
348
530
|
val parent = oldView.parent as? ViewGroup ?: run {
|
|
349
531
|
rebuildAll(root)
|
|
350
532
|
return
|
|
@@ -429,6 +611,11 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
429
611
|
internal val topLevelViewCountForTesting: Int
|
|
430
612
|
get() = stack.childCount
|
|
431
613
|
|
|
614
|
+
internal fun textColorForTesting(value: String): Int? =
|
|
615
|
+
textLeafViews.firstOrNull { it.text?.toString() == value }?.currentTextColor
|
|
616
|
+
|
|
617
|
+
internal fun tableFillColorsForTesting(): List<Int> = tableCells.map { it.baseFillColor }
|
|
618
|
+
|
|
432
619
|
private fun viewForNode(node: JSONObject, depth: Int, path: List<Int>): View {
|
|
433
620
|
val rendered = when (node.optString("kind")) {
|
|
434
621
|
"list" -> listView(node, depth, path)
|
|
@@ -510,10 +697,6 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
510
697
|
orientation = LinearLayout.HORIZONTAL
|
|
511
698
|
isBaselineAligned = false
|
|
512
699
|
}
|
|
513
|
-
row.addView(
|
|
514
|
-
markerView(node, parentListType, index),
|
|
515
|
-
LinearLayout.LayoutParams((24 * density).toInt(), (24 * density).toInt())
|
|
516
|
-
)
|
|
517
700
|
val content = LinearLayout(context).apply {
|
|
518
701
|
orientation = LinearLayout.VERTICAL
|
|
519
702
|
setPadding((8 * density).toInt(), 0, 0, 0)
|
|
@@ -525,12 +708,26 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
525
708
|
if (content.childCount == 0) {
|
|
526
709
|
content.addView(textBlockView(node, depth + 1))
|
|
527
710
|
}
|
|
711
|
+
val marker = if (parentListType == "toggleList" || parentListType == "toggle_list") {
|
|
712
|
+
val open = node.optJSONObject("attrs")?.optBoolean("open", true) ?: true
|
|
713
|
+
setToggleContent(content, open)
|
|
714
|
+
toggleMarkerView(node, content, open)
|
|
715
|
+
} else {
|
|
716
|
+
markerView(node, parentListType, index)
|
|
717
|
+
}
|
|
718
|
+
row.addView(
|
|
719
|
+
marker,
|
|
720
|
+
LinearLayout.LayoutParams((24 * density).toInt(), (24 * density).toInt())
|
|
721
|
+
)
|
|
528
722
|
row.addView(content, LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f))
|
|
529
723
|
return row
|
|
530
724
|
}
|
|
531
725
|
|
|
532
726
|
private fun blockContainerView(node: JSONObject, depth: Int, path: List<Int>): View {
|
|
533
727
|
val density = resources.displayMetrics.density
|
|
728
|
+
if (node.optString("nodeType") == "callout") {
|
|
729
|
+
return calloutView(node, depth, path)
|
|
730
|
+
}
|
|
534
731
|
if (node.optString("nodeType") == "blockquote") {
|
|
535
732
|
val row = LinearLayout(context).apply {
|
|
536
733
|
orientation = LinearLayout.HORIZONTAL
|
|
@@ -569,11 +766,95 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
569
766
|
return container
|
|
570
767
|
}
|
|
571
768
|
|
|
769
|
+
private fun calloutView(node: JSONObject, depth: Int, path: List<Int>): View {
|
|
770
|
+
val density = resources.displayMetrics.density
|
|
771
|
+
val tone = node.optJSONObject("attrs")?.optString("tone", "info") ?: "info"
|
|
772
|
+
val accent = when (tone) {
|
|
773
|
+
"success" -> 0xFF16A34A.toInt()
|
|
774
|
+
"warning" -> 0xFFD97706.toInt()
|
|
775
|
+
"danger" -> 0xFFDC2626.toInt()
|
|
776
|
+
else -> 0xFF2563EB.toInt()
|
|
777
|
+
}
|
|
778
|
+
val container = LinearLayout(context).apply {
|
|
779
|
+
orientation = LinearLayout.VERTICAL
|
|
780
|
+
val padding = (12 * density).toInt()
|
|
781
|
+
setPadding(padding, padding, padding, padding)
|
|
782
|
+
background = roundedDrawable(
|
|
783
|
+
Color.argb(24, Color.red(accent), Color.green(accent), Color.blue(accent)),
|
|
784
|
+
Color.argb(140, Color.red(accent), Color.green(accent), Color.blue(accent)),
|
|
785
|
+
1f,
|
|
786
|
+
10f
|
|
787
|
+
)
|
|
788
|
+
}
|
|
789
|
+
container.addView(TextView(context).apply {
|
|
790
|
+
text = tone.replaceFirstChar { it.uppercase() }
|
|
791
|
+
setTextColor(accent)
|
|
792
|
+
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
|
|
793
|
+
setTypeface(typeface, Typeface.BOLD)
|
|
794
|
+
isClickable = this@NativeBlockEditorSurface.isEditable
|
|
795
|
+
setOnClickListener { cycleCalloutTone(node) }
|
|
796
|
+
})
|
|
797
|
+
val children = node.optJSONArray("children")
|
|
798
|
+
for (index in 0 until (children?.length() ?: 0)) {
|
|
799
|
+
children?.optJSONObject(index)?.let { container.addView(viewForNode(it, depth + 1, path + index)) }
|
|
800
|
+
}
|
|
801
|
+
return container
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
private fun toggleMarkerView(node: JSONObject, content: LinearLayout, initiallyOpen: Boolean): View =
|
|
805
|
+
TextView(context).apply {
|
|
806
|
+
var open = initiallyOpen
|
|
807
|
+
text = if (open) "⌄" else "›"
|
|
808
|
+
contentDescription = if (open) "Collapse toggle item" else "Expand toggle item"
|
|
809
|
+
gravity = android.view.Gravity.CENTER
|
|
810
|
+
setTextColor(currentTheme?.list?.markerColor ?: baseTextColor)
|
|
811
|
+
setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
|
|
812
|
+
setTypeface(typeface, Typeface.BOLD)
|
|
813
|
+
setOnClickListener {
|
|
814
|
+
open = !open
|
|
815
|
+
text = if (open) "⌄" else "›"
|
|
816
|
+
contentDescription = if (open) "Collapse toggle item" else "Expand toggle item"
|
|
817
|
+
setToggleContent(content, open)
|
|
818
|
+
if (isEditable) updateNodeAttrs(node, JSONObject().put("open", open))
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
private fun setToggleContent(content: LinearLayout, open: Boolean) {
|
|
823
|
+
for (index in 1 until content.childCount) {
|
|
824
|
+
content.getChildAt(index).visibility = if (open) View.VISIBLE else View.GONE
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
private fun cycleCalloutTone(node: JSONObject) {
|
|
829
|
+
if (!isEditable) return
|
|
830
|
+
val tones = listOf("info", "success", "warning", "danger")
|
|
831
|
+
val current = node.optJSONObject("attrs")?.optString("tone", "info") ?: "info"
|
|
832
|
+
val next = tones[(tones.indexOf(current).coerceAtLeast(0) + 1) % tones.size]
|
|
833
|
+
updateNodeAttrs(node, JSONObject().put("tone", next))
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
private fun updateNodeAttrs(node: JSONObject, patch: JSONObject) {
|
|
837
|
+
if (currentEditorId == 0L) return
|
|
838
|
+
val attrs = JSONObject(node.optJSONObject("attrs")?.toString() ?: "{}")
|
|
839
|
+
patch.keys().forEach { key -> attrs.put(key, patch.get(key)) }
|
|
840
|
+
val updateJSON = editorUpdateNodeAttrs(
|
|
841
|
+
currentEditorId.toULong(),
|
|
842
|
+
node.optInt("docPos", 0).toUInt(),
|
|
843
|
+
attrs.toString()
|
|
844
|
+
)
|
|
845
|
+
applyUpdateJSON(updateJSON, currentEditorId)
|
|
846
|
+
onAppliedUpdate?.invoke(updateJSON)
|
|
847
|
+
}
|
|
848
|
+
|
|
572
849
|
private fun tableView(node: JSONObject, path: List<Int>): View {
|
|
573
850
|
val tableId = nextTableId++
|
|
574
851
|
val table = LinearLayout(context).apply {
|
|
575
852
|
orientation = LinearLayout.VERTICAL
|
|
576
|
-
background = borderDrawable(
|
|
853
|
+
background = borderDrawable(
|
|
854
|
+
Color.TRANSPARENT,
|
|
855
|
+
currentTheme?.table?.borderColor ?: baseTextColor,
|
|
856
|
+
1f
|
|
857
|
+
)
|
|
577
858
|
}
|
|
578
859
|
val rows = node.optJSONArray("children")
|
|
579
860
|
for (index in 0 until (rows?.length() ?: 0)) {
|
|
@@ -599,7 +880,15 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
599
880
|
|
|
600
881
|
private fun cellView(node: JSONObject, tableId: Int, rowIndex: Int, columnIndex: Int, path: List<Int>): View {
|
|
601
882
|
val density = resources.displayMetrics.density
|
|
602
|
-
val fillColor = if (node.optString("nodeType") == "tableHeader")
|
|
883
|
+
val fillColor = if (node.optString("nodeType") == "tableHeader") {
|
|
884
|
+
currentTheme?.table?.headerBackgroundColor
|
|
885
|
+
?: currentTheme?.codeBlock?.backgroundColor
|
|
886
|
+
?: Color.TRANSPARENT
|
|
887
|
+
} else {
|
|
888
|
+
currentTheme?.table?.cellBackgroundColor
|
|
889
|
+
?: currentTheme?.backgroundColor
|
|
890
|
+
?: Color.TRANSPARENT
|
|
891
|
+
}
|
|
603
892
|
val cell = CellView(context).apply {
|
|
604
893
|
docPos = node.optInt("docPos", 0)
|
|
605
894
|
this.tableId = tableId
|
|
@@ -610,7 +899,7 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
610
899
|
setPadding((8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt())
|
|
611
900
|
background = borderDrawable(
|
|
612
901
|
fillColor,
|
|
613
|
-
|
|
902
|
+
currentTheme?.table?.borderColor ?: baseTextColor,
|
|
614
903
|
0.5f
|
|
615
904
|
)
|
|
616
905
|
isClickable = true
|
|
@@ -628,17 +917,29 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
628
917
|
}
|
|
629
918
|
|
|
630
919
|
private fun textBlockView(node: JSONObject, depth: Int): View {
|
|
920
|
+
return TextLeaf(context).also { configureTextLeaf(it, node) }
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
private fun configureTextLeaf(leaf: TextLeaf, node: JSONObject) {
|
|
631
924
|
val rendered = attributedInlineContent(node)
|
|
632
|
-
val
|
|
925
|
+
val hadFocus = leaf.hasFocus()
|
|
926
|
+
val previousSelectionStart = leaf.selectionStart.coerceAtLeast(0)
|
|
927
|
+
val previousSelectionEnd = leaf.selectionEnd.coerceAtLeast(previousSelectionStart)
|
|
928
|
+
leaf.apply leaf@ {
|
|
633
929
|
text = rendered.first
|
|
634
930
|
positionSegments = if (rendered.second.isEmpty()) {
|
|
635
|
-
listOf(PositionSegment(0, 0, "", node.optInt("docPos", 0)
|
|
931
|
+
listOf(PositionSegment(0, 0, "", node.optInt("docPos", 0) + if (node.optInt("docSize", 0) >= 2) 1 else 0, 0, false))
|
|
636
932
|
} else {
|
|
637
933
|
rendered.second
|
|
638
934
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
935
|
+
val style = currentTheme?.effectiveTextStyle(node.optString("nodeType"))
|
|
936
|
+
setTextColor(style?.color ?: baseTextColor)
|
|
937
|
+
setTextSize(
|
|
938
|
+
TypedValue.COMPLEX_UNIT_PX,
|
|
939
|
+
style?.fontSize?.let(::spToPx) ?: textSizeFor(node)
|
|
940
|
+
)
|
|
941
|
+
typeface = style?.let { Typeface.create(it.fontFamily, it.typefaceStyle()) }
|
|
942
|
+
?: typefaceFor(node)
|
|
642
943
|
minLines = 1
|
|
643
944
|
background = if (node.optString("nodeType") == "codeBlock" || node.optString("nodeType") == "code_block") {
|
|
644
945
|
roundedDrawable(
|
|
@@ -657,26 +958,59 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
657
958
|
} else {
|
|
658
959
|
setPadding(0, 0, 0, 0)
|
|
659
960
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
961
|
+
isEnabled = this@NativeBlockEditorSurface.isEditable
|
|
962
|
+
isFocusable = this@NativeBlockEditorSurface.isEditable
|
|
963
|
+
isFocusableInTouchMode = this@NativeBlockEditorSurface.isEditable
|
|
964
|
+
setTextIsSelectable(this@NativeBlockEditorSurface.isEditable)
|
|
965
|
+
isSingleLine = false
|
|
966
|
+
editorSurface = this@NativeBlockEditorSurface
|
|
967
|
+
updateLeafHint(this)
|
|
968
|
+
onFocusChangeListener = OnFocusChangeListener { _, focused ->
|
|
969
|
+
if (focused) onFocusChange?.invoke(true)
|
|
970
|
+
else if (!this@NativeBlockEditorSurface.isEditorFocused) onFocusChange?.invoke(false)
|
|
971
|
+
}
|
|
972
|
+
applyConfiguredInputTraits(this)
|
|
973
|
+
if (hadFocus) {
|
|
974
|
+
setSelection(
|
|
975
|
+
previousSelectionStart.coerceAtMost(length()),
|
|
976
|
+
previousSelectionEnd.coerceAtMost(length())
|
|
977
|
+
)
|
|
671
978
|
}
|
|
672
979
|
}
|
|
673
|
-
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
private fun updateLeafHint(leaf: TextLeaf) {
|
|
983
|
+
leaf.hint = if ((textLeafViews.firstOrNull() == null || textLeafViews.firstOrNull() === leaf) && leaf.text.isNullOrEmpty()) {
|
|
984
|
+
placeholder
|
|
985
|
+
} else {
|
|
986
|
+
null
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
private fun applyConfiguredInputTraits(leaf: TextLeaf) {
|
|
991
|
+
val capitalFlag = when (configuredAutoCapitalize) {
|
|
992
|
+
"none" -> 0
|
|
993
|
+
"words" -> InputType.TYPE_TEXT_FLAG_CAP_WORDS
|
|
994
|
+
"characters" -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
|
995
|
+
else -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
|
996
|
+
}
|
|
997
|
+
val inputClass = when (configuredKeyboardType) {
|
|
998
|
+
"numeric", "number-pad", "decimal-pad" -> InputType.TYPE_CLASS_NUMBER
|
|
999
|
+
"phone-pad" -> InputType.TYPE_CLASS_PHONE
|
|
1000
|
+
else -> InputType.TYPE_CLASS_TEXT
|
|
1001
|
+
}
|
|
1002
|
+
val suggestionFlag = if (configuredAutoCorrect == true) {
|
|
1003
|
+
InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
|
|
1004
|
+
} else {
|
|
1005
|
+
InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
|
1006
|
+
}
|
|
1007
|
+
leaf.inputType = inputClass or capitalFlag or suggestionFlag or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
|
674
1008
|
}
|
|
675
1009
|
|
|
676
1010
|
private fun positionSegmentsFor(node: JSONObject): List<PositionSegment> {
|
|
677
1011
|
val segments = attributedInlineContent(node).second
|
|
678
1012
|
return if (segments.isEmpty()) {
|
|
679
|
-
listOf(PositionSegment(0, 0, "", node.optInt("docPos", 0)
|
|
1013
|
+
listOf(PositionSegment(0, 0, "", node.optInt("docPos", 0) + if (node.optInt("docSize", 0) >= 2) 1 else 0, 0, false))
|
|
680
1014
|
} else {
|
|
681
1015
|
segments
|
|
682
1016
|
}
|
|
@@ -779,10 +1113,14 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
779
1113
|
}
|
|
780
1114
|
return TextView(context).apply {
|
|
781
1115
|
text = node.optString("label", node.optString("nodeType", "Block"))
|
|
782
|
-
setTextColor(
|
|
1116
|
+
setTextColor(currentTheme?.text?.color ?: baseTextColor)
|
|
783
1117
|
textSize = 14f
|
|
784
1118
|
setPadding(12, 8, 12, 8)
|
|
785
|
-
background = borderDrawable(
|
|
1119
|
+
background = borderDrawable(
|
|
1120
|
+
currentTheme?.codeBlock?.backgroundColor ?: Color.TRANSPARENT,
|
|
1121
|
+
Color.TRANSPARENT,
|
|
1122
|
+
0f
|
|
1123
|
+
)
|
|
786
1124
|
}
|
|
787
1125
|
}
|
|
788
1126
|
|
|
@@ -800,7 +1138,12 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
800
1138
|
?.let { (it * density).toInt() }
|
|
801
1139
|
scaleType = ImageView.ScaleType.FIT_CENTER
|
|
802
1140
|
adjustViewBounds = true
|
|
803
|
-
background = roundedDrawable(
|
|
1141
|
+
background = roundedDrawable(
|
|
1142
|
+
currentTheme?.codeBlock?.backgroundColor ?: Color.TRANSPARENT,
|
|
1143
|
+
Color.TRANSPARENT,
|
|
1144
|
+
0f,
|
|
1145
|
+
8f
|
|
1146
|
+
)
|
|
804
1147
|
isClickable = true
|
|
805
1148
|
setOnClickListener { selectImage(this) }
|
|
806
1149
|
}
|
|
@@ -834,13 +1177,13 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
834
1177
|
}
|
|
835
1178
|
}
|
|
836
1179
|
|
|
837
|
-
internal fun selectedImageGeometry():
|
|
1180
|
+
internal fun selectedImageGeometry(): SelectedImageGeometry? {
|
|
838
1181
|
val docPos = selectedNodePos ?: return null
|
|
839
1182
|
val imageView = imageViews.firstOrNull { it.docPos == docPos } ?: return null
|
|
840
1183
|
if (!isEditable || imageView.width <= 0 || imageView.height <= 0) return null
|
|
841
1184
|
val rect = Rect(0, 0, imageView.width, imageView.height)
|
|
842
1185
|
offsetDescendantRectToMyCoords(imageView, rect)
|
|
843
|
-
return
|
|
1186
|
+
return SelectedImageGeometry(docPos, RectF(rect))
|
|
844
1187
|
}
|
|
845
1188
|
|
|
846
1189
|
internal fun selectedCaretRect(): RectF? =
|
|
@@ -874,6 +1217,80 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
874
1217
|
}
|
|
875
1218
|
}
|
|
876
1219
|
|
|
1220
|
+
private fun applyNativeSelection(anchor: Int, head: Int, preservingFocus: Boolean) {
|
|
1221
|
+
val candidates = textLeafViews.filter {
|
|
1222
|
+
it.nativeOffsetForDocPosition(anchor) != null &&
|
|
1223
|
+
it.nativeOffsetForDocPosition(head) != null
|
|
1224
|
+
}
|
|
1225
|
+
val leaf = candidates.firstOrNull { it.hasFocus() }
|
|
1226
|
+
?: (if (anchor == head) candidates.lastOrNull() else candidates.firstOrNull())
|
|
1227
|
+
?: return
|
|
1228
|
+
val start = leaf.nativeOffsetForDocPosition(minOf(anchor, head)) ?: return
|
|
1229
|
+
val end = leaf.nativeOffsetForDocPosition(maxOf(anchor, head)) ?: return
|
|
1230
|
+
isApplyingRemoteUpdate = true
|
|
1231
|
+
try {
|
|
1232
|
+
leaf.setSelection(start.coerceIn(0, leaf.length()), end.coerceIn(0, leaf.length()))
|
|
1233
|
+
if (preservingFocus && !leaf.hasFocus()) leaf.requestFocus()
|
|
1234
|
+
} finally {
|
|
1235
|
+
isApplyingRemoteUpdate = false
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
private fun selectionChanged(leaf: TextLeaf, start: Int, end: Int) {
|
|
1240
|
+
if (isApplyingRemoteUpdate || !isEditable || currentEditorId == 0L) return
|
|
1241
|
+
val anchor = leaf.docPositionForOffset(start.coerceAtLeast(0)) ?: return
|
|
1242
|
+
val head = leaf.docPositionForOffset(end.coerceAtLeast(start)) ?: return
|
|
1243
|
+
editorSetSelection(currentEditorId.toULong(), anchor.toUInt(), head.toUInt())
|
|
1244
|
+
onSelectionChange?.invoke(anchor, head)
|
|
1245
|
+
onAppliedUpdate?.invoke(editorGetSelectionState(currentEditorId.toULong()))
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
private fun commitLeafTextMutation(leaf: TextLeaf, before: String, after: String) {
|
|
1249
|
+
if (isApplyingRemoteUpdate || !isEditable || currentEditorId == 0L || before == after) return
|
|
1250
|
+
var prefix = 0
|
|
1251
|
+
val prefixLimit = minOf(before.length, after.length)
|
|
1252
|
+
while (prefix < prefixLimit && before[prefix] == after[prefix]) prefix += 1
|
|
1253
|
+
var suffix = 0
|
|
1254
|
+
while (
|
|
1255
|
+
suffix < before.length - prefix &&
|
|
1256
|
+
suffix < after.length - prefix &&
|
|
1257
|
+
before[before.length - 1 - suffix] == after[after.length - 1 - suffix]
|
|
1258
|
+
) suffix += 1
|
|
1259
|
+
|
|
1260
|
+
val oldEnd = before.length - suffix
|
|
1261
|
+
val newEnd = after.length - suffix
|
|
1262
|
+
val anchor = leaf.docPositionForOffset(prefix) ?: return
|
|
1263
|
+
val head = leaf.docPositionForOffset(oldEnd) ?: return
|
|
1264
|
+
val replacement = after.substring(prefix, newEnd)
|
|
1265
|
+
editorSetSelection(currentEditorId.toULong(), anchor.toUInt(), head.toUInt())
|
|
1266
|
+
val update = if (replacement == "\n") {
|
|
1267
|
+
if (anchor != head) {
|
|
1268
|
+
editorReplaceSelectionText(currentEditorId.toULong(), "")
|
|
1269
|
+
}
|
|
1270
|
+
editorSplitBlock(currentEditorId.toULong(), anchor.toUInt())
|
|
1271
|
+
} else {
|
|
1272
|
+
editorReplaceSelectionText(currentEditorId.toULong(), replacement)
|
|
1273
|
+
}
|
|
1274
|
+
applyUpdateJSON(update, currentEditorId)
|
|
1275
|
+
onEditorUpdate?.invoke(update)
|
|
1276
|
+
onAppliedUpdate?.invoke(update)
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
private fun deleteBackwardAtLeafBoundary(leaf: TextLeaf): Boolean {
|
|
1280
|
+
if (isApplyingRemoteUpdate || !isEditable || currentEditorId == 0L) return false
|
|
1281
|
+
val docPosition = leaf.docPositionForOffset(0) ?: return false
|
|
1282
|
+
val scalar = editorDocToScalar(currentEditorId.toULong(), docPosition.toUInt())
|
|
1283
|
+
val update = editorDeleteBackwardAtSelectionScalar(
|
|
1284
|
+
currentEditorId.toULong(),
|
|
1285
|
+
scalar,
|
|
1286
|
+
scalar
|
|
1287
|
+
)
|
|
1288
|
+
applyUpdateJSON(update, currentEditorId)
|
|
1289
|
+
onEditorUpdate?.invoke(update)
|
|
1290
|
+
onAppliedUpdate?.invoke(update)
|
|
1291
|
+
return true
|
|
1292
|
+
}
|
|
1293
|
+
|
|
877
1294
|
private fun descendantRectInSurface(descendant: View, local: RectF): RectF {
|
|
878
1295
|
val origin = Rect(0, 0, 0, 0)
|
|
879
1296
|
offsetDescendantRectToMyCoords(descendant, origin)
|
|
@@ -993,8 +1410,14 @@ class NativeBlockEditorSurface(context: Context) : ScrollView(context) {
|
|
|
993
1410
|
cell == anchor || cell == head
|
|
994
1411
|
}
|
|
995
1412
|
cell.background = borderDrawable(
|
|
996
|
-
if (selected)
|
|
997
|
-
|
|
1413
|
+
if (selected) {
|
|
1414
|
+
currentTheme?.table?.selectionBackgroundColor ?: cell.baseFillColor
|
|
1415
|
+
} else cell.baseFillColor,
|
|
1416
|
+
if (selected) {
|
|
1417
|
+
currentTheme?.table?.selectionBorderColor ?: baseTextColor
|
|
1418
|
+
} else {
|
|
1419
|
+
currentTheme?.table?.borderColor ?: baseTextColor
|
|
1420
|
+
},
|
|
998
1421
|
if (selected) 1.5f else 0.5f
|
|
999
1422
|
)
|
|
1000
1423
|
}
|