Skip to content

Commit 6860cca

Browse files
committed
fix: pasting text for non null input
1 parent 51d642f commit 6860cca

5 files changed

Lines changed: 86 additions & 46 deletions

File tree

android/src/main/java/com/swmansion/reactnativerichtexteditor/ReactNativeRichTextEditorView.kt

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import com.swmansion.reactnativerichtexteditor.styles.RichTextStyle
3737
import com.swmansion.reactnativerichtexteditor.utils.EditorParser
3838
import com.swmansion.reactnativerichtexteditor.utils.EditorSelection
3939
import com.swmansion.reactnativerichtexteditor.utils.EditorSpanState
40+
import com.swmansion.reactnativerichtexteditor.utils.mergeSpannables
4041
import com.swmansion.reactnativerichtexteditor.watchers.EditorSpanWatcher
4142
import com.swmansion.reactnativerichtexteditor.watchers.EditorTextWatcher
4243
import kotlin.math.ceil
@@ -188,13 +189,19 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
188189
val clip = clipboard.primaryClip
189190
val item = clip?.getItemAt(0)
190191
val htmlText = item?.htmlText
192+
val currentText = text as Spannable
193+
val start = selection?.start ?: 0
194+
val end = selection?.end ?: 0
191195

192196
if (htmlText != null) {
193-
setValue(htmlText)
197+
val parsedText = parseText(htmlText) as Spannable
198+
val finalText = currentText.mergeSpannables(start, end, parsedText)
199+
setValue(finalText)
194200
return
195201
}
196202

197-
setValue(item?.text.toString())
203+
val finalText = currentText.replaceRange(start, end, item?.text.toString())
204+
setValue(finalText)
198205
}
199206

200207
fun requestFocusProgrammatically() {
@@ -203,18 +210,21 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
203210
setSelection(selection?.start ?: text?.length ?: 0)
204211
}
205212

206-
fun setValue(value: String?) {
213+
private fun parseText(text: CharSequence): CharSequence {
214+
val isHtml = text.startsWith("<html>") && text.endsWith("</html>")
215+
if (!isHtml) return text
216+
217+
val parsed = EditorParser.fromHtml(text.toString(), richTextStyle, null)
218+
val withoutLastNewLine = parsed.trimEnd('\n')
219+
return withoutLastNewLine
220+
}
221+
222+
fun setValue(value: CharSequence?) {
207223
if (value == null) return
208224
isSettingValue = true
209225

210-
val isHtml = value.startsWith("<html>") && value.endsWith("</html>")
211-
if (isHtml) {
212-
val parsed = EditorParser.fromHtml(value, richTextStyle, null)
213-
val withoutLastNewLine = parsed.trimEnd('\n')
214-
setText(withoutLastNewLine)
215-
} else {
216-
setText(value)
217-
}
226+
val newText = parseText(value)
227+
setText(newText)
218228

219229
// Assign SpanWatcher one more time as our previous spannable has been replaced
220230
addSpanWatcher(EditorSpanWatcher(this))

android/src/main/java/com/swmansion/reactnativerichtexteditor/styles/ListStyles.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import com.swmansion.reactnativerichtexteditor.ReactNativeRichTextEditorView
88
import com.swmansion.reactnativerichtexteditor.spans.EditorOrderedListSpan
99
import com.swmansion.reactnativerichtexteditor.spans.EditorSpans
1010
import com.swmansion.reactnativerichtexteditor.spans.EditorUnorderedListSpan
11+
import com.swmansion.reactnativerichtexteditor.utils.getParagraphBounds
1112
import com.swmansion.reactnativerichtexteditor.utils.getSafeSpanBoundaries
1213

1314
class ListStyles(private val editorView: ReactNativeRichTextEditorView) {
1415
private fun <T>getPreviousParagraphSpan(spannable: Spannable, s: Int, type: Class<T>): T? {
1516
if (s <= 0) return null
16-
val selection = editorView.selection ?: return null
1717

18-
val (previousParagraphStart, previousParagraphEnd) = selection.getParagraphBounds(spannable, s - 1)
18+
val (previousParagraphStart, previousParagraphEnd) = spannable.getParagraphBounds(s - 1)
1919
val spans = spannable.getSpans(previousParagraphStart, previousParagraphEnd, type)
2020

2121
if (spans.isNotEmpty()) {
@@ -112,10 +112,9 @@ class ListStyles(private val editorView: ReactNativeRichTextEditorView) {
112112
}
113113

114114
private fun handleAfterTextChanged(s: Editable, name: String, endCursorPosition: Int, previousTextLength: Int) {
115-
val selection = editorView.selection ?: return
116115
val config = EditorSpans.listSpans[name] ?: return
117116
val cursorPosition = endCursorPosition.coerceAtMost(s.length)
118-
val (start, end) = selection.getParagraphBounds(s, cursorPosition)
117+
val (start, end) = s.getParagraphBounds(cursorPosition)
119118

120119
val isBackspace = previousTextLength > s.length
121120
val isNewLine = cursorPosition > 0 && s[cursorPosition - 1] == '\n'
@@ -132,15 +131,15 @@ class ListStyles(private val editorView: ReactNativeRichTextEditorView) {
132131
s.replace(start, cursorPosition, "\u200B")
133132
setSpan(s, name, start, start + 1)
134133
// Inform that new span has been added
135-
editorView.selection.validateStyles()
134+
editorView.selection?.validateStyles()
136135
return
137136
}
138137

139138
if (!isBackspace && isNewLine && isPreviousParagraphList(s, start, config.clazz)) {
140139
s.insert(cursorPosition, "\u200B")
141140
setSpan(s, name, start, end + 1)
142141
// Inform that new span has been added
143-
editorView.selection.validateStyles()
142+
editorView.selection?.validateStyles()
144143
return
145144
}
146145

android/src/main/java/com/swmansion/reactnativerichtexteditor/styles/ParagraphStyles.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.text.Spannable
55
import android.text.SpannableStringBuilder
66
import com.swmansion.reactnativerichtexteditor.ReactNativeRichTextEditorView
77
import com.swmansion.reactnativerichtexteditor.spans.EditorSpans
8+
import com.swmansion.reactnativerichtexteditor.utils.getParagraphBounds
89
import com.swmansion.reactnativerichtexteditor.utils.getSafeSpanBoundaries
910

1011
class ParagraphStyles(private val editorView: ReactNativeRichTextEditorView) {
@@ -90,7 +91,6 @@ class ParagraphStyles(private val editorView: ReactNativeRichTextEditorView) {
9091
}
9192

9293
fun afterTextChanged(s: Editable, endPosition: Int, previousTextLength: Int) {
93-
val selection = editorView.selection ?: return
9494
var endCursorPosition = endPosition
9595
val isBackspace = s.length < previousTextLength
9696
val isNewLine = endCursorPosition == 0 || endCursorPosition > 0 && s[endCursorPosition - 1] == '\n'
@@ -114,7 +114,7 @@ class ParagraphStyles(private val editorView: ReactNativeRichTextEditorView) {
114114
}
115115
}
116116

117-
var (start, end) = selection.getParagraphBounds(s, styleStart, endCursorPosition)
117+
var (start, end) = s.getParagraphBounds(styleStart, endCursorPosition)
118118
val isNotEndLineSpan = isSpanEnabledInNextLine(s, end, config.clazz)
119119
val spans = s.getSpans(start, end, config.clazz)
120120

android/src/main/java/com/swmansion/reactnativerichtexteditor/utils/EditorSelection.kt

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -106,36 +106,10 @@ class EditorSelection(private val editorView: ReactNativeRichTextEditorView) {
106106
return styleStart
107107
}
108108

109-
fun getParagraphBounds(spannable: Spannable, index: Int): Pair<Int, Int> {
110-
return getParagraphBounds(spannable, index, index)
111-
}
112-
113-
fun getParagraphBounds(spannable: Spannable, start: Int, end: Int): Pair<Int, Int> {
114-
var startPosition = start.coerceAtLeast(0).coerceAtMost(spannable.length)
115-
var endPosition = end.coerceAtLeast(0).coerceAtMost(spannable.length)
116-
117-
// Find the start of the paragraph
118-
while (startPosition > 0 && spannable[startPosition - 1] != '\n') {
119-
startPosition--
120-
}
121-
122-
// Find the end of the paragraph
123-
while (endPosition < spannable.length && spannable[endPosition] != '\n') {
124-
endPosition++
125-
}
126-
127-
if (startPosition >= endPosition) {
128-
// If the start position is equal or greater than the end position, return the same position
129-
startPosition = endPosition
130-
}
131-
132-
return Pair(startPosition, endPosition)
133-
}
134-
135109
fun getParagraphSelection(): Pair<Int, Int> {
136110
val (currentStart, currentEnd) = getInlineSelection()
137111
val spannable = editorView.text as Spannable
138-
return getParagraphBounds(spannable, currentStart, currentEnd)
112+
return spannable.getParagraphBounds(currentStart, currentEnd)
139113
}
140114

141115
private fun <T>getParagraphStyleStart(type: Class<T>): Int? {

android/src/main/java/com/swmansion/reactnativerichtexteditor/utils/Utils.kt

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package com.swmansion.reactnativerichtexteditor.utils
22

33
import android.text.Spannable
4+
import android.text.SpannableStringBuilder
45
import android.util.Log
6+
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorBlockSpan
7+
import com.swmansion.reactnativerichtexteditor.spans.interfaces.EditorParagraphSpan
58
import org.json.JSONObject
69

710
fun jsonStringToStringMap(json: String): Map<String, String> {
@@ -27,3 +30,57 @@ fun Spannable.getSafeSpanBoundaries(start: Int, end: Int): Pair<Int, Int> {
2730

2831
return Pair(safeStart, safeEnd)
2932
}
33+
34+
fun Spannable.getParagraphBounds(start: Int, end: Int): Pair<Int, Int> {
35+
var startPosition = start.coerceAtLeast(0).coerceAtMost(this.length)
36+
var endPosition = end.coerceAtLeast(0).coerceAtMost(this.length)
37+
38+
// Find the start of the paragraph
39+
while (startPosition > 0 && this[startPosition - 1] != '\n') {
40+
startPosition--
41+
}
42+
43+
// Find the end of the paragraph
44+
while (endPosition < this.length && this[endPosition] != '\n') {
45+
endPosition++
46+
}
47+
48+
if (startPosition >= endPosition) {
49+
// If the start position is equal or greater than the end position, return the same position
50+
startPosition = endPosition
51+
}
52+
53+
return Pair(startPosition, endPosition)
54+
}
55+
56+
fun Spannable.getParagraphBounds(index: Int): Pair<Int, Int> {
57+
return this.getParagraphBounds(index, index)
58+
}
59+
60+
fun Spannable.mergeSpannables(start: Int, end: Int, spannable: Spannable): Spannable {
61+
var finalStart = start
62+
var finalEnd = end
63+
64+
val builder = SpannableStringBuilder(this)
65+
val startBlockSpans = spannable.getSpans(0, 0, EditorBlockSpan::class.java)
66+
val startParagraphSpans = spannable.getSpans(0, 0, EditorParagraphSpan::class.java)
67+
val endBlockSpans = spannable.getSpans(this.length, this.length, EditorBlockSpan::class.java)
68+
val endParagraphSpans = spannable.getSpans(this.length, this.length, EditorParagraphSpan::class.java)
69+
val (paragraphStart, paragraphEnd) = this.getParagraphBounds(start, end)
70+
val isNewLineStart = startBlockSpans.isNotEmpty() || startParagraphSpans.isNotEmpty()
71+
val isNewLineEnd = endBlockSpans.isNotEmpty() || endParagraphSpans.isNotEmpty()
72+
73+
if (isNewLineStart && start != paragraphStart) {
74+
builder.insert(start, "\n")
75+
finalStart = start + 1
76+
finalEnd = end + 1
77+
}
78+
79+
if (isNewLineEnd && end != paragraphEnd) {
80+
builder.insert(finalEnd, "\n")
81+
}
82+
83+
builder.replace(finalStart, finalEnd, spannable)
84+
85+
return builder
86+
}

0 commit comments

Comments
 (0)