Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.text.Spannable
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.Gravity
import android.view.MotionEvent
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.widget.AppCompatEditText
Expand Down Expand Up @@ -92,8 +93,8 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
isSingleLine = false
isHorizontalScrollBarEnabled = false
isVerticalScrollBarEnabled = true
gravity = android.view.Gravity.TOP or android.view.Gravity.START
inputType = android.text.InputType.TYPE_CLASS_TEXT or android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE
gravity = Gravity.TOP or Gravity.START
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE

setPadding(0, 0, 0, 0)
setBackgroundColor(Color.TRANSPARENT)
Expand Down Expand Up @@ -364,10 +365,56 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
layoutManager.invalidateLayout(text)
}

private fun removeStyle(name: String, start: Int, end: Int) {
when (name) {
EditorSpans.BOLD -> inlineStyles?.removeStyle(EditorSpans.BOLD, start, end)
EditorSpans.ITALIC -> inlineStyles?.removeStyle(EditorSpans.ITALIC, start, end)
EditorSpans.UNDERLINE -> inlineStyles?.removeStyle(EditorSpans.UNDERLINE, start, end)
EditorSpans.STRIKETHROUGH -> inlineStyles?.removeStyle(EditorSpans.STRIKETHROUGH, start, end)
EditorSpans.INLINE_CODE -> inlineStyles?.removeStyle(EditorSpans.INLINE_CODE, start, end)
EditorSpans.H1 -> paragraphStyles?.removeStyle(EditorSpans.H1, start, end)
EditorSpans.H2 -> paragraphStyles?.removeStyle(EditorSpans.H2, start, end)
EditorSpans.H3 -> paragraphStyles?.removeStyle(EditorSpans.H3, start, end)
EditorSpans.CODE_BLOCK -> paragraphStyles?.removeStyle(EditorSpans.CODE_BLOCK, start, end)
EditorSpans.BLOCK_QUOTE -> paragraphStyles?.removeStyle(EditorSpans.BLOCK_QUOTE, start, end)
EditorSpans.ORDERED_LIST -> listStyles?.removeStyle(EditorSpans.ORDERED_LIST, start, end)
EditorSpans.UNORDERED_LIST -> listStyles?.removeStyle(EditorSpans.UNORDERED_LIST, start, end)
EditorSpans.LINK -> parametrizedStyles?.removeStyle(EditorSpans.LINK, start, end)
EditorSpans.IMAGE -> parametrizedStyles?.removeStyle(EditorSpans.IMAGE, start, end)
EditorSpans.MENTION -> parametrizedStyles?.removeStyle(EditorSpans.MENTION, start, end)
else -> Log.w("ReactNativeRichTextEditorView", "Unknown style: $name")
}
}

private fun getTargetRange(name: String): Pair<Int, Int> {
val result = when (name) {
EditorSpans.BOLD -> inlineStyles?.getStyleRange()
EditorSpans.ITALIC -> inlineStyles?.getStyleRange()
EditorSpans.UNDERLINE -> inlineStyles?.getStyleRange()
EditorSpans.STRIKETHROUGH -> inlineStyles?.getStyleRange()
EditorSpans.INLINE_CODE -> inlineStyles?.getStyleRange()
EditorSpans.H1 -> paragraphStyles?.getStyleRange()
EditorSpans.H2 -> paragraphStyles?.getStyleRange()
EditorSpans.H3 -> paragraphStyles?.getStyleRange()
EditorSpans.CODE_BLOCK -> paragraphStyles?.getStyleRange()
EditorSpans.BLOCK_QUOTE -> paragraphStyles?.getStyleRange()
EditorSpans.ORDERED_LIST -> listStyles?.getStyleRange()
EditorSpans.UNORDERED_LIST -> listStyles?.getStyleRange()
EditorSpans.LINK -> parametrizedStyles?.getStyleRange()
EditorSpans.IMAGE -> parametrizedStyles?.getStyleRange()
EditorSpans.MENTION -> parametrizedStyles?.getStyleRange()
else -> Pair(0, 0)
}

return result ?: Pair(0, 0)
}

private fun verifyStyle(name: String): Boolean {
val mergingConfig = EditorSpans.mergingConfig[name] ?: return true
val conflictingStyles = mergingConfig.conflictingStyles
val blockingStyles = mergingConfig.blockingStyles
val isEnabling = spanState?.getStart(name) == null
if (!isEnabling) return true

for (style in blockingStyles) {
if (spanState?.getStart(style) != null) {
Expand All @@ -377,23 +424,22 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
}

for (style in conflictingStyles) {
if (spanState?.getStart(style) != null) {
val start = selection?.start ?: 0
val end = selection?.end ?: 0
val lengthBefore = text?.length ?: 0

toggleStyle(style)

val lengthAfter = text?.length ?: 0
val charactersRemoved = lengthBefore - lengthAfter
val finalEnd = if (charactersRemoved > 0 && end > start) {
end - charactersRemoved
} else {
end
}
val start = selection?.start ?: 0
val end = selection?.end ?: 0
val lengthBefore = text?.length ?: 0

selection?.onSelection(start, finalEnd)
val targetRange = getTargetRange(name)
removeStyle(style, targetRange.first, targetRange.second)

val lengthAfter = text?.length ?: 0
val charactersRemoved = lengthBefore - lengthAfter
val finalEnd = if (charactersRemoved > 0 && end > start) {
end - charactersRemoved
} else {
end
}

selection?.onSelection(start, finalEnd)
}

return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ object EditorSpans {
conflictingStyles = arrayOf(H1, H2, H3, CODE_BLOCK),
),
CODE_BLOCK to StylesMergingConfig(
conflictingStyles = arrayOf(H1, H2, H3, BOLD, ITALIC, UNDERLINE, STRIKETHROUGH, UNORDERED_LIST, ORDERED_LIST, BLOCK_QUOTE),
conflictingStyles = arrayOf(H1, H2, H3, BOLD, ITALIC, UNDERLINE, STRIKETHROUGH, UNORDERED_LIST, ORDERED_LIST, BLOCK_QUOTE, INLINE_CODE),
),
UNORDERED_LIST to StylesMergingConfig(
conflictingStyles = arrayOf(H1, H2, H3, ORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,17 @@ class InlineStyles(private val editorView: ReactNativeRichTextEditorView) {
setAndMergeSpans(spannable, type, start, end)
editorView.selection.validateStyles()
}

fun removeStyle(name: String, start: Int, end: Int) {
val config = EditorSpans.inlineSpans[name] ?: return
val spannable = editorView.text as Spannable
val spans = spannable.getSpans(start, end, config.clazz)
for (span in spans) {
spannable.removeSpan(span)
}
}

fun getStyleRange(): Pair<Int, Int> {
return editorView.selection?.getInlineSelection() ?: Pair(0, 0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,14 @@ class ListStyles(private val editorView: ReactNativeRichTextEditorView) {
handleAfterTextChanged(s, EditorSpans.ORDERED_LIST, endCursorPosition, previousTextLength)
handleAfterTextChanged(s, EditorSpans.UNORDERED_LIST, endCursorPosition, previousTextLength)
}

fun getStyleRange(): Pair<Int, Int> {
return editorView.selection?.getParagraphSelection() ?: Pair(0, 0)
}

fun removeStyle(name: String, start: Int, end: Int) {
val config = EditorSpans.listSpans[name] ?: return
val spannable = editorView.text as Spannable
removeSpansForRange(spannable, start, end, config.clazz)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,14 @@ class ParagraphStyles(private val editorView: ReactNativeRichTextEditorView) {
editorView.spanState?.setStart(name, start)
setAndMergeSpans(spannable, type, start, currentEnd)
}

fun getStyleRange(): Pair<Int, Int> {
return editorView.selection?.getParagraphSelection() ?: Pair(0, 0)
}

fun removeStyle(name: String, start: Int, end: Int) {
val config = EditorSpans.paragraphSpans[name] ?: return
val spannable = editorView.text as Spannable
removeSpansForRange(spannable, start, end, config.clazz)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,24 @@ import com.swmansion.reactnativerichtexteditor.ReactNativeRichTextEditorView
import com.swmansion.reactnativerichtexteditor.spans.EditorImageSpan
import com.swmansion.reactnativerichtexteditor.spans.EditorLinkSpan
import com.swmansion.reactnativerichtexteditor.spans.EditorMentionSpan
import com.swmansion.reactnativerichtexteditor.spans.EditorSpans
import com.swmansion.reactnativerichtexteditor.utils.getSafeSpanBoundaries
import java.io.File

class ParametrizedStyles(private val editorView: ReactNativeRichTextEditorView) {
private var mentionStart: Int? = null
var mentionIndicators: Array<String> = emptyArray<String>()

fun <T>removeSpansForRange(spannable: Spannable, start: Int, end: Int, clazz: Class<T>) {
val ssb = spannable as SpannableStringBuilder
ssb.replace(start, end, ssb.substring(start, end).replace("\u200B", ""))

val spans = ssb.getSpans(start, end, clazz)
for (span in spans) {
ssb.removeSpan(span)
}
}

fun setLinkSpan(start: Int, end: Int, text: String, url: String) {
val spannable = editorView.text as SpannableStringBuilder
val spans = spannable.getSpans(start, end, EditorLinkSpan::class.java)
Expand Down Expand Up @@ -167,4 +178,14 @@ class ParametrizedStyles(private val editorView: ReactNativeRichTextEditorView)

editorView.selection.validateStyles()
}

fun getStyleRange(): Pair<Int, Int> {
return editorView.selection?.getInlineSelection() ?: Pair(0, 0)
}

fun removeStyle(name: String, start: Int, end: Int) {
val config = EditorSpans.parametrizedStyles[name] ?: return
val spannable = editorView.text as Spannable
removeSpansForRange(spannable, start, end, config.clazz)
}
}
Loading