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
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:

- name: Build example for Android
env:
JAVA_OPTS: "-XX:MaxHeapSize=6g"
JAVA_OPTS: '-XX:MaxHeapSize=6g'
run: |
yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}"

Expand All @@ -120,7 +120,10 @@ jobs:
- name: Set up Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.2'
xcode-version: '16.4'

- name: Reset build folder and pods
run: rm -rf example/build example/ios/Pods example/ios/Podfile.lock

- name: Cache turborepo for iOS
uses: actions/cache@v4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ require "json"
package = JSON.parse(File.read(File.join(__dir__, "package.json")))

Pod::Spec.new do |s|
s.name = "ReactNativeRichTextEditor"
s.name = "ReactNativeEnriched"
s.version = package["version"]
s.summary = package["description"]
s.homepage = package["homepage"]
Expand Down
10 changes: 5 additions & 5 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
buildscript {
ext.getExtOrDefault = {name ->
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ReactNativeRichTextEditor_' + name]
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['EnrichedTextInput_' + name]
}

repositories {
Expand All @@ -22,7 +22,7 @@ apply plugin: "kotlin-android"
apply plugin: "com.facebook.react"

def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ReactNativeRichTextEditor_" + name]).toInteger()
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["EnrichedTextInput_" + name]).toInteger()
}

def supportsNamespace() {
Expand All @@ -36,7 +36,7 @@ def supportsNamespace() {

android {
if (supportsNamespace()) {
namespace "com.swmansion.reactnativerichtexteditor"
namespace "com.swmansion.enriched"

sourceSets {
main {
Expand Down Expand Up @@ -96,6 +96,6 @@ dependencies {

react {
jsRootDir = file("../src/")
libraryName = "ReactNativeRichTextEditorView"
codegenJavaPackageName = "com.swmansion.reactnativerichtexteditor"
libraryName = "EnrichedTextInputView"
codegenJavaPackageName = "com.swmansion.enriched"
}
10 changes: 5 additions & 5 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ReactNativeRichTextEditor_kotlinVersion=2.0.21
ReactNativeRichTextEditor_minSdkVersion=24
ReactNativeRichTextEditor_targetSdkVersion=34
ReactNativeRichTextEditor_compileSdkVersion=35
ReactNativeRichTextEditor_ndkVersion=27.1.12297006
EnrichedTextInput_kotlinVersion=2.0.21
EnrichedTextInput_minSdkVersion=24
EnrichedTextInput_targetSdkVersion=34
EnrichedTextInput_compileSdkVersion=35
EnrichedTextInput__ndkVersion=27.1.12297006
2 changes: 1 addition & 1 deletion android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.swmansion.reactnativerichtexteditor">
package="com.swmansion.enriched">
</manifest>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.swmansion.reactnativerichtexteditor
package com.swmansion.enriched

import android.content.ClipData
import android.content.ClipboardManager
Expand All @@ -25,28 +25,28 @@ import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.views.text.ReactTypefaceUtils.applyStyles
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontStyle
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
import com.swmansion.reactnativerichtexteditor.events.MentionHandler
import com.swmansion.reactnativerichtexteditor.events.OnInputBlurEvent
import com.swmansion.reactnativerichtexteditor.events.OnInputFocusEvent
import com.swmansion.reactnativerichtexteditor.spans.EditorSpans
import com.swmansion.reactnativerichtexteditor.styles.InlineStyles
import com.swmansion.reactnativerichtexteditor.styles.ListStyles
import com.swmansion.reactnativerichtexteditor.styles.ParagraphStyles
import com.swmansion.reactnativerichtexteditor.styles.ParametrizedStyles
import com.swmansion.reactnativerichtexteditor.styles.RichTextStyle
import com.swmansion.reactnativerichtexteditor.utils.EditorParser
import com.swmansion.reactnativerichtexteditor.utils.EditorSelection
import com.swmansion.reactnativerichtexteditor.utils.EditorSpanState
import com.swmansion.reactnativerichtexteditor.utils.mergeSpannables
import com.swmansion.reactnativerichtexteditor.watchers.EditorSpanWatcher
import com.swmansion.reactnativerichtexteditor.watchers.EditorTextWatcher
import com.swmansion.enriched.events.MentionHandler
import com.swmansion.enriched.events.OnInputBlurEvent
import com.swmansion.enriched.events.OnInputFocusEvent
import com.swmansion.enriched.spans.EnrichedSpans
import com.swmansion.enriched.styles.InlineStyles
import com.swmansion.enriched.styles.ListStyles
import com.swmansion.enriched.styles.ParagraphStyles
import com.swmansion.enriched.styles.ParametrizedStyles
import com.swmansion.enriched.styles.HtmlStyle
import com.swmansion.enriched.utils.EnrichedParser
import com.swmansion.enriched.utils.EnrichedSelection
import com.swmansion.enriched.utils.EnrichedSpanState
import com.swmansion.enriched.utils.mergeSpannables
import com.swmansion.enriched.watchers.EnrichedSpanWatcher
import com.swmansion.enriched.watchers.EnrichedTextWatcher
import kotlin.math.ceil


class ReactNativeRichTextEditorView : AppCompatEditText {
class EnrichedTextInputView : AppCompatEditText {
var stateWrapper: StateWrapper? = null
val selection: EditorSelection? = EditorSelection(this)
val spanState: EditorSpanState? = EditorSpanState(this)
val selection: EnrichedSelection? = EnrichedSelection(this)
val spanState: EnrichedSpanState? = EnrichedSpanState(this)
val inlineStyles: InlineStyles? = InlineStyles(this)
val paragraphStyles: ParagraphStyles? = ParagraphStyles(this)
val listStyles: ListStyles? = ListStyles(this)
Expand All @@ -55,10 +55,9 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
var isRemovingMany: Boolean = false

val mentionHandler: MentionHandler? = MentionHandler(this)
var richTextStyle: RichTextStyle = RichTextStyle(this, null)
var spanWatcher: EditorSpanWatcher? = null
var layoutManager: ReactNativeRichTextEditorViewLayoutManager =
ReactNativeRichTextEditorViewLayoutManager(this)
var htmlStyle: HtmlStyle = HtmlStyle(this, null)
var spanWatcher: EnrichedSpanWatcher? = null
var layoutManager: EnrichedTextInputViewLayoutManager = EnrichedTextInputViewLayoutManager(this)

var fontSize: Float? = null
private var autoFocus = false
Expand Down Expand Up @@ -101,8 +100,8 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
setPadding(0, 0, 0, 0)
setBackgroundColor(Color.TRANSPARENT)

addSpanWatcher(EditorSpanWatcher(this))
addTextChangedListener(EditorTextWatcher(this))
addSpanWatcher(EnrichedSpanWatcher(this))
addTextChangedListener(EnrichedTextWatcher(this))
}

// https://github.com/facebook/react-native/blob/36df97f500aa0aa8031098caf7526db358b6ddc1/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt#L295C1-L296C1
Expand Down Expand Up @@ -175,7 +174,7 @@ class ReactNativeRichTextEditorView : AppCompatEditText {

if (start < end) {
val selectedText = spannable.subSequence(start, end) as Spannable
val selectedHtml = EditorParser.toHtml(selectedText)
val selectedHtml = EnrichedParser.toHtml(selectedText)

val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newHtmlText(CLIPBOARD_TAG, selectedText, selectedHtml)
Expand Down Expand Up @@ -216,7 +215,7 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
val isHtml = text.startsWith("<html>") && text.endsWith("</html>")
if (!isHtml) return text

val parsed = EditorParser.fromHtml(text.toString(), richTextStyle, null)
val parsed = EnrichedParser.fromHtml(text.toString(), htmlStyle, null)
val withoutLastNewLine = parsed.trimEnd('\n')
return withoutLastNewLine
}
Expand All @@ -229,7 +228,7 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
setText(newText)

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

// Scroll to the last line of text
setSelection(text?.length ?: 0)
Expand Down Expand Up @@ -291,7 +290,7 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
setTextSize(TypedValue.COMPLEX_UNIT_PX, sizeInt)

// This ensured that newly created spans will take the new font size into account
richTextStyle.invalidateStyles()
htmlStyle.invalidateStyles()
layoutManager.invalidateLayout(text)
}

Expand Down Expand Up @@ -359,41 +358,41 @@ class ReactNativeRichTextEditorView : AppCompatEditText {

private fun toggleStyle(name: String) {
when (name) {
EditorSpans.BOLD -> inlineStyles?.toggleStyle(EditorSpans.BOLD)
EditorSpans.ITALIC -> inlineStyles?.toggleStyle(EditorSpans.ITALIC)
EditorSpans.UNDERLINE -> inlineStyles?.toggleStyle(EditorSpans.UNDERLINE)
EditorSpans.STRIKETHROUGH -> inlineStyles?.toggleStyle(EditorSpans.STRIKETHROUGH)
EditorSpans.INLINE_CODE -> inlineStyles?.toggleStyle(EditorSpans.INLINE_CODE)
EditorSpans.H1 -> paragraphStyles?.toggleStyle(EditorSpans.H1)
EditorSpans.H2 -> paragraphStyles?.toggleStyle(EditorSpans.H2)
EditorSpans.H3 -> paragraphStyles?.toggleStyle(EditorSpans.H3)
EditorSpans.CODE_BLOCK -> paragraphStyles?.toggleStyle(EditorSpans.CODE_BLOCK)
EditorSpans.BLOCK_QUOTE -> paragraphStyles?.toggleStyle(EditorSpans.BLOCK_QUOTE)
EditorSpans.ORDERED_LIST -> listStyles?.toggleStyle(EditorSpans.ORDERED_LIST)
EditorSpans.UNORDERED_LIST -> listStyles?.toggleStyle(EditorSpans.UNORDERED_LIST)
else -> Log.w("ReactNativeRichTextEditorView", "Unknown style: $name")
EnrichedSpans.BOLD -> inlineStyles?.toggleStyle(EnrichedSpans.BOLD)
EnrichedSpans.ITALIC -> inlineStyles?.toggleStyle(EnrichedSpans.ITALIC)
EnrichedSpans.UNDERLINE -> inlineStyles?.toggleStyle(EnrichedSpans.UNDERLINE)
EnrichedSpans.STRIKETHROUGH -> inlineStyles?.toggleStyle(EnrichedSpans.STRIKETHROUGH)
EnrichedSpans.INLINE_CODE -> inlineStyles?.toggleStyle(EnrichedSpans.INLINE_CODE)
EnrichedSpans.H1 -> paragraphStyles?.toggleStyle(EnrichedSpans.H1)
EnrichedSpans.H2 -> paragraphStyles?.toggleStyle(EnrichedSpans.H2)
EnrichedSpans.H3 -> paragraphStyles?.toggleStyle(EnrichedSpans.H3)
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.toggleStyle(EnrichedSpans.CODE_BLOCK)
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.toggleStyle(EnrichedSpans.BLOCK_QUOTE)
EnrichedSpans.ORDERED_LIST -> listStyles?.toggleStyle(EnrichedSpans.ORDERED_LIST)
EnrichedSpans.UNORDERED_LIST -> listStyles?.toggleStyle(EnrichedSpans.UNORDERED_LIST)
else -> Log.w("EnrichedTextInputView", "Unknown style: $name")
}

layoutManager.invalidateLayout(text)
}

private fun removeStyle(name: String, start: Int, end: Int): Boolean {
val removed = 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)
EnrichedSpans.BOLD -> inlineStyles?.removeStyle(EnrichedSpans.BOLD, start, end)
EnrichedSpans.ITALIC -> inlineStyles?.removeStyle(EnrichedSpans.ITALIC, start, end)
EnrichedSpans.UNDERLINE -> inlineStyles?.removeStyle(EnrichedSpans.UNDERLINE, start, end)
EnrichedSpans.STRIKETHROUGH -> inlineStyles?.removeStyle(EnrichedSpans.STRIKETHROUGH, start, end)
EnrichedSpans.INLINE_CODE -> inlineStyles?.removeStyle(EnrichedSpans.INLINE_CODE, start, end)
EnrichedSpans.H1 -> paragraphStyles?.removeStyle(EnrichedSpans.H1, start, end)
EnrichedSpans.H2 -> paragraphStyles?.removeStyle(EnrichedSpans.H2, start, end)
EnrichedSpans.H3 -> paragraphStyles?.removeStyle(EnrichedSpans.H3, start, end)
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.removeStyle(EnrichedSpans.CODE_BLOCK, start, end)
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.removeStyle(EnrichedSpans.BLOCK_QUOTE, start, end)
EnrichedSpans.ORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.ORDERED_LIST, start, end)
EnrichedSpans.UNORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.UNORDERED_LIST, start, end)
EnrichedSpans.LINK -> parametrizedStyles?.removeStyle(EnrichedSpans.LINK, start, end)
EnrichedSpans.IMAGE -> parametrizedStyles?.removeStyle(EnrichedSpans.IMAGE, start, end)
EnrichedSpans.MENTION -> parametrizedStyles?.removeStyle(EnrichedSpans.MENTION, start, end)
else -> false
}

Expand All @@ -402,29 +401,29 @@ class ReactNativeRichTextEditorView : AppCompatEditText {

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()
EnrichedSpans.BOLD -> inlineStyles?.getStyleRange()
EnrichedSpans.ITALIC -> inlineStyles?.getStyleRange()
EnrichedSpans.UNDERLINE -> inlineStyles?.getStyleRange()
EnrichedSpans.STRIKETHROUGH -> inlineStyles?.getStyleRange()
EnrichedSpans.INLINE_CODE -> inlineStyles?.getStyleRange()
EnrichedSpans.H1 -> paragraphStyles?.getStyleRange()
EnrichedSpans.H2 -> paragraphStyles?.getStyleRange()
EnrichedSpans.H3 -> paragraphStyles?.getStyleRange()
EnrichedSpans.CODE_BLOCK -> paragraphStyles?.getStyleRange()
EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.getStyleRange()
EnrichedSpans.ORDERED_LIST -> listStyles?.getStyleRange()
EnrichedSpans.UNORDERED_LIST -> listStyles?.getStyleRange()
EnrichedSpans.LINK -> parametrizedStyles?.getStyleRange()
EnrichedSpans.IMAGE -> parametrizedStyles?.getStyleRange()
EnrichedSpans.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 mergingConfig = EnrichedSpans.mergingConfig[name] ?: return true
val conflictingStyles = mergingConfig.conflictingStyles
val blockingStyles = mergingConfig.blockingStyles
val isEnabling = spanState?.getStart(name) == null
Expand Down Expand Up @@ -465,7 +464,7 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
return true
}

private fun addSpanWatcher(watcher: EditorSpanWatcher) {
private fun addSpanWatcher(watcher: EnrichedSpanWatcher) {
val spannable = text as Spannable
spannable.setSpan(watcher, 0, spannable.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
spanWatcher = watcher
Expand All @@ -479,28 +478,28 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
}

fun addLink(start: Int, end: Int, text: String, url: String) {
val isValid = verifyStyle(EditorSpans.LINK)
val isValid = verifyStyle(EnrichedSpans.LINK)
if (!isValid) return

parametrizedStyles?.setLinkSpan(start, end, text, url)
}

fun addImage(src: String) {
val isValid = verifyStyle(EditorSpans.IMAGE)
val isValid = verifyStyle(EnrichedSpans.IMAGE)
if (!isValid) return

parametrizedStyles?.setImageSpan(src)
}

fun startMention(indicator: String) {
val isValid = verifyStyle(EditorSpans.MENTION)
val isValid = verifyStyle(EnrichedSpans.MENTION)
if (!isValid) return

parametrizedStyles?.startMention(indicator)
}

fun addMention(indicator: String, text: String, attributes: Map<String, String>) {
val isValid = verifyStyle(EditorSpans.MENTION)
val isValid = verifyStyle(EnrichedSpans.MENTION)
if (!isValid) return

parametrizedStyles?.setMentionSpan(text, indicator, attributes)
Expand All @@ -522,6 +521,6 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
}

companion object {
const val CLIPBOARD_TAG = "react-native-rich-text-editor-clipboard"
const val CLIPBOARD_TAG = "react-native-enriched-clipboard"
}
}
Loading
Loading