react-native-enriched 0.0.0 → 0.1.0
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/LICENSE +20 -0
- package/README.md +869 -0
- package/ReactNativeEnriched.podspec +27 -0
- package/android/build.gradle +101 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +146 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +55 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +577 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.cpp +16 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.h +20 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +535 -0
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +64 -0
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +292 -0
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +40 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt +28 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt +33 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +34 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +38 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +41 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +16 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt +24 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +71 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +111 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt +9 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt +9 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +49 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt +5 -0
- package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +227 -0
- package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +146 -0
- package/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt +173 -0
- package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +186 -0
- package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +223 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +857 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +285 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt +204 -0
- package/android/src/main/java/com/swmansion/enriched/utils/Utils.kt +91 -0
- package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +73 -0
- package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +51 -0
- package/android/src/main/new_arch/CMakeLists.txt +56 -0
- package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.cpp +22 -0
- package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.h +26 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputComponentDescriptor.h +35 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.cpp +51 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +26 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.cpp +34 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.h +54 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.cpp +9 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.h +25 -0
- package/ios/EnrichedTextInputView.h +33 -0
- package/ios/EnrichedTextInputView.mm +1190 -0
- package/ios/EnrichedTextInputViewManager.mm +13 -0
- package/ios/config/InputConfig.h +67 -0
- package/ios/config/InputConfig.mm +382 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +577 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +384 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/States.cpp +16 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/States.h +20 -0
- package/ios/inputParser/InputParser.h +11 -0
- package/ios/inputParser/InputParser.mm +669 -0
- package/ios/inputTextView/InputTextView.h +6 -0
- package/ios/inputTextView/InputTextView.mm +115 -0
- package/ios/internals/EnrichedTextInputViewComponentDescriptor.h +17 -0
- package/ios/internals/EnrichedTextInputViewShadowNode.h +40 -0
- package/ios/internals/EnrichedTextInputViewShadowNode.mm +83 -0
- package/ios/internals/EnrichedTextInputViewState.cpp +10 -0
- package/ios/internals/EnrichedTextInputViewState.h +20 -0
- package/ios/styles/BlockQuoteStyle.mm +248 -0
- package/ios/styles/BoldStyle.mm +122 -0
- package/ios/styles/H1Style.mm +10 -0
- package/ios/styles/H2Style.mm +10 -0
- package/ios/styles/H3Style.mm +10 -0
- package/ios/styles/HeadingStyleBase.mm +144 -0
- package/ios/styles/InlineCodeStyle.mm +163 -0
- package/ios/styles/ItalicStyle.mm +110 -0
- package/ios/styles/LinkStyle.mm +463 -0
- package/ios/styles/MentionStyle.mm +476 -0
- package/ios/styles/OrderedListStyle.mm +225 -0
- package/ios/styles/StrikethroughStyle.mm +80 -0
- package/ios/styles/UnderlineStyle.mm +112 -0
- package/ios/styles/UnorderedListStyle.mm +225 -0
- package/ios/utils/BaseStyleProtocol.h +16 -0
- package/ios/utils/ColorExtension.h +6 -0
- package/ios/utils/ColorExtension.mm +27 -0
- package/ios/utils/FontExtension.h +13 -0
- package/ios/utils/FontExtension.mm +91 -0
- package/ios/utils/LayoutManagerExtension.h +6 -0
- package/ios/utils/LayoutManagerExtension.mm +171 -0
- package/ios/utils/LinkData.h +9 -0
- package/ios/utils/LinkData.mm +4 -0
- package/ios/utils/MentionParams.h +9 -0
- package/ios/utils/MentionParams.mm +4 -0
- package/ios/utils/MentionStyleProps.h +13 -0
- package/ios/utils/MentionStyleProps.mm +56 -0
- package/ios/utils/OccurenceUtils.h +37 -0
- package/ios/utils/OccurenceUtils.mm +124 -0
- package/ios/utils/ParagraphsUtils.h +7 -0
- package/ios/utils/ParagraphsUtils.mm +54 -0
- package/ios/utils/StringExtension.h +15 -0
- package/ios/utils/StringExtension.mm +57 -0
- package/ios/utils/StyleHeaders.h +74 -0
- package/ios/utils/StylePair.h +9 -0
- package/ios/utils/StylePair.mm +4 -0
- package/ios/utils/StyleTypeEnum.h +22 -0
- package/ios/utils/TextDecorationLineEnum.h +6 -0
- package/ios/utils/TextDecorationLineEnum.mm +4 -0
- package/ios/utils/TextInsertionUtils.h +6 -0
- package/ios/utils/TextInsertionUtils.mm +48 -0
- package/ios/utils/WordsUtils.h +6 -0
- package/ios/utils/WordsUtils.mm +88 -0
- package/ios/utils/ZeroWidthSpaceUtils.h +7 -0
- package/ios/utils/ZeroWidthSpaceUtils.mm +164 -0
- package/lib/module/EnrichedTextInput.js +191 -0
- package/lib/module/EnrichedTextInput.js.map +1 -0
- package/lib/module/EnrichedTextInputNativeComponent.ts +235 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/normalizeHtmlStyle.js +141 -0
- package/lib/module/normalizeHtmlStyle.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/EnrichedTextInput.d.ts +113 -0
- package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -0
- package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +160 -0
- package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/normalizeHtmlStyle.d.ts +4 -0
- package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +1 -0
- package/package.json +172 -1
- package/react-native.config.js +13 -0
- package/src/EnrichedTextInput.tsx +358 -0
- package/src/EnrichedTextInputNativeComponent.ts +235 -0
- package/src/index.tsx +9 -0
- package/src/normalizeHtmlStyle.ts +188 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
package com.swmansion.enriched.styles
|
|
2
|
+
|
|
3
|
+
import android.graphics.Color
|
|
4
|
+
import com.facebook.react.bridge.ColorPropConverter
|
|
5
|
+
import com.facebook.react.bridge.ReactContext
|
|
6
|
+
import com.facebook.react.bridge.ReadableMap
|
|
7
|
+
import com.facebook.react.uimanager.PixelUtil
|
|
8
|
+
import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
|
|
9
|
+
import com.swmansion.enriched.EnrichedTextInputView
|
|
10
|
+
import kotlin.Float
|
|
11
|
+
import kotlin.Int
|
|
12
|
+
import kotlin.String
|
|
13
|
+
import kotlin.math.ceil
|
|
14
|
+
|
|
15
|
+
class HtmlStyle {
|
|
16
|
+
private var style: ReadableMap? = null
|
|
17
|
+
private var view: EnrichedTextInputView? = null
|
|
18
|
+
|
|
19
|
+
// Default values are ignored as they are specified on the JS side.
|
|
20
|
+
// They are specified only because they are required by the constructor.
|
|
21
|
+
// JS passes them as a prop - so they are initialized after the constructor is called.
|
|
22
|
+
var h1FontSize: Int = 72
|
|
23
|
+
var h1Bold: Boolean = false
|
|
24
|
+
|
|
25
|
+
var h2FontSize: Int = 64
|
|
26
|
+
var h2Bold: Boolean = false
|
|
27
|
+
|
|
28
|
+
var h3FontSize: Int = 56
|
|
29
|
+
var h3Bold: Boolean = false
|
|
30
|
+
|
|
31
|
+
var blockquoteColor: Int? = null
|
|
32
|
+
var blockquoteBorderColor: Int = Color.BLACK
|
|
33
|
+
var blockquoteStripeWidth: Int = 2
|
|
34
|
+
var blockquoteGapWidth: Int = 16
|
|
35
|
+
|
|
36
|
+
var olGapWidth: Int = 16
|
|
37
|
+
var olMarginLeft: Int = 24
|
|
38
|
+
var olMarkerFontWeight: Int? = null
|
|
39
|
+
var olMarkerColor: Int? = null
|
|
40
|
+
|
|
41
|
+
var ulGapWidth: Int = 16
|
|
42
|
+
var ulMarginLeft: Int = 24
|
|
43
|
+
var ulBulletSize: Int = 8
|
|
44
|
+
var ulBulletColor: Int = Color.BLACK
|
|
45
|
+
|
|
46
|
+
var imgWidth: Int = 200
|
|
47
|
+
var imgHeight: Int = 200
|
|
48
|
+
|
|
49
|
+
var aColor: Int = Color.BLACK
|
|
50
|
+
var aUnderline: Boolean = true
|
|
51
|
+
|
|
52
|
+
var codeBlockColor: Int = Color.BLACK
|
|
53
|
+
var codeBlockBackgroundColor: Int = Color.BLACK
|
|
54
|
+
var codeBlockRadius: Float = 4f
|
|
55
|
+
|
|
56
|
+
var inlineCodeColor: Int = Color.BLACK
|
|
57
|
+
var inlineCodeBackgroundColor: Int = Color.BLACK
|
|
58
|
+
|
|
59
|
+
var mentionsStyle: MutableMap<String, MentionStyle> = mutableMapOf()
|
|
60
|
+
|
|
61
|
+
constructor(view: EnrichedTextInputView?, style: ReadableMap?) {
|
|
62
|
+
this.view = view
|
|
63
|
+
this.style = style
|
|
64
|
+
|
|
65
|
+
invalidateStyles()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fun invalidateStyles() {
|
|
69
|
+
val style = this.style ?: return
|
|
70
|
+
|
|
71
|
+
val h1Style = style.getMap("h1")
|
|
72
|
+
h1FontSize = parseFloat(h1Style, "fontSize").toInt()
|
|
73
|
+
h1Bold = h1Style?.getBoolean("bold") == true
|
|
74
|
+
|
|
75
|
+
val h2Style = style.getMap("h2")
|
|
76
|
+
h2FontSize = parseFloat(h2Style, "fontSize").toInt()
|
|
77
|
+
h2Bold = h2Style?.getBoolean("bold") == true
|
|
78
|
+
|
|
79
|
+
val h3Style = style.getMap("h3")
|
|
80
|
+
h3FontSize = parseFloat(h3Style, "fontSize").toInt()
|
|
81
|
+
h3Bold = h3Style?.getBoolean("bold") == true
|
|
82
|
+
|
|
83
|
+
val blockquoteStyle = style.getMap("blockquote")
|
|
84
|
+
blockquoteColor = parseOptionalColor(blockquoteStyle, "color")
|
|
85
|
+
blockquoteBorderColor = parseColor(blockquoteStyle, "borderColor")
|
|
86
|
+
blockquoteGapWidth = parseFloat(blockquoteStyle, "gapWidth").toInt()
|
|
87
|
+
blockquoteStripeWidth = parseFloat(blockquoteStyle, "borderWidth").toInt()
|
|
88
|
+
|
|
89
|
+
val olStyle = style.getMap("ol")
|
|
90
|
+
val userDefinedMarginLeft = parseFloat(olStyle, "marginLeft").toInt()
|
|
91
|
+
val calculatedMarginLeft = calculateOlMarginLeft(view, userDefinedMarginLeft)
|
|
92
|
+
olMarginLeft = calculatedMarginLeft
|
|
93
|
+
olGapWidth = parseFloat(olStyle, "gapWidth").toInt()
|
|
94
|
+
olMarkerColor = parseOptionalColor(olStyle, "markerColor")
|
|
95
|
+
olMarkerFontWeight = parseOptionalFontWeight(olStyle, "markerFontWeight")
|
|
96
|
+
|
|
97
|
+
val ulStyle = style.getMap("ul")
|
|
98
|
+
ulBulletColor = parseColor(ulStyle, "bulletColor")
|
|
99
|
+
ulGapWidth = parseFloat(ulStyle, "gapWidth").toInt()
|
|
100
|
+
ulMarginLeft = parseFloat(ulStyle, "marginLeft").toInt()
|
|
101
|
+
ulBulletSize = parseFloat(ulStyle, "bulletSize").toInt()
|
|
102
|
+
|
|
103
|
+
val imgStyle = style.getMap("img")
|
|
104
|
+
imgWidth = parseFloat(imgStyle, "width").toInt()
|
|
105
|
+
imgHeight = parseFloat(imgStyle, "height").toInt()
|
|
106
|
+
|
|
107
|
+
val aStyle = style.getMap("a")
|
|
108
|
+
aColor = parseColor(aStyle, "color")
|
|
109
|
+
aUnderline = parseIsUnderline(aStyle)
|
|
110
|
+
|
|
111
|
+
val codeBlockStyle = style.getMap("codeblock")
|
|
112
|
+
codeBlockRadius = parseFloat(codeBlockStyle, "borderRadius")
|
|
113
|
+
codeBlockColor = parseColor(codeBlockStyle, "color")
|
|
114
|
+
codeBlockBackgroundColor = parseColorWithOpacity(codeBlockStyle, "backgroundColor", 80)
|
|
115
|
+
|
|
116
|
+
val inlineCodeStyle = style.getMap("code")
|
|
117
|
+
inlineCodeColor = parseColor(inlineCodeStyle, "color")
|
|
118
|
+
inlineCodeBackgroundColor = parseColorWithOpacity(inlineCodeStyle, "backgroundColor", 80)
|
|
119
|
+
|
|
120
|
+
val mentionStyle = style.getMap("mention")
|
|
121
|
+
mentionsStyle = parseMentionsStyle(mentionStyle)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private fun parseFloat(map: ReadableMap?, key: String): Float {
|
|
125
|
+
val safeMap = ensureValueIsSet(map, key)
|
|
126
|
+
|
|
127
|
+
val fontSize = safeMap.getDouble(key)
|
|
128
|
+
return ceil(PixelUtil.toPixelFromSP(fontSize))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private fun parseColorWithOpacity(map: ReadableMap?, key: String, opacity: Int): Int {
|
|
132
|
+
val color = parseColor(map, key)
|
|
133
|
+
return withOpacity(color, opacity)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private fun parseOptionalColor(map: ReadableMap?, key: String): Int? {
|
|
137
|
+
if (map == null) return null
|
|
138
|
+
if (!map.hasKey(key)) return null
|
|
139
|
+
if (map.isNull(key)) return null
|
|
140
|
+
|
|
141
|
+
return parseColor(map, key)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private fun parseColor(map: ReadableMap?, key: String): Int {
|
|
145
|
+
val safeMap = ensureValueIsSet(map, key)
|
|
146
|
+
|
|
147
|
+
val color = safeMap.getDouble(key)
|
|
148
|
+
val parsedColor = ColorPropConverter.getColor(color, view?.context as ReactContext)
|
|
149
|
+
if (parsedColor == null) {
|
|
150
|
+
throw Error("Specified color value is not supported: $color")
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return parsedColor
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private fun withOpacity(color: Int, alpha: Int): Int {
|
|
157
|
+
val a = alpha.coerceIn(0, 255)
|
|
158
|
+
return (color and 0x00FFFFFF) or (a shl 24)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private fun parseIsUnderline(map: ReadableMap?): Boolean {
|
|
162
|
+
val underline = map?.getString("textDecorationLine")
|
|
163
|
+
val isEnabled = underline == "underline"
|
|
164
|
+
val isDisabled = underline == "none"
|
|
165
|
+
|
|
166
|
+
if (isEnabled) return true
|
|
167
|
+
if (isDisabled) return false
|
|
168
|
+
|
|
169
|
+
throw Error("Specified textDecorationLine value is not supported: $underline. Supported values are 'underline' and 'none'.")
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private fun calculateOlMarginLeft(view: EnrichedTextInputView?, userMargin: Int): Int {
|
|
173
|
+
val fontSize = view?.fontSize?.toInt() ?: 0
|
|
174
|
+
val leadMargin = fontSize / 2
|
|
175
|
+
|
|
176
|
+
return leadMargin + userMargin
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private fun ensureValueIsSet(map: ReadableMap?, key: String): ReadableMap {
|
|
180
|
+
if (map == null) throw Error("Style map cannot be null")
|
|
181
|
+
|
|
182
|
+
if (!map.hasKey(key)) throw Error("Style map must contain key: $key")
|
|
183
|
+
|
|
184
|
+
if (map.isNull(key)) throw Error("Style map cannot contain null value for key: $key")
|
|
185
|
+
|
|
186
|
+
return map
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private fun parseMentionsStyle(mentionsStyle: ReadableMap?): MutableMap<String, MentionStyle> {
|
|
190
|
+
if (mentionsStyle == null) throw Error("Mentions style cannot be null")
|
|
191
|
+
|
|
192
|
+
val parsedMentionsStyle: MutableMap<String, MentionStyle> = mutableMapOf()
|
|
193
|
+
|
|
194
|
+
val iterator = mentionsStyle.keySetIterator()
|
|
195
|
+
while (iterator.hasNextKey()) {
|
|
196
|
+
val key = iterator.nextKey()
|
|
197
|
+
val value = mentionsStyle.getMap(key)
|
|
198
|
+
|
|
199
|
+
if (value == null) throw Error("Mention style for key '$key' cannot be null")
|
|
200
|
+
|
|
201
|
+
val color = parseColor(value, "color")
|
|
202
|
+
val backgroundColor = parseColorWithOpacity(value, "backgroundColor", 80)
|
|
203
|
+
val isUnderline = parseIsUnderline(value)
|
|
204
|
+
val parsedStyle = MentionStyle(color, backgroundColor, isUnderline)
|
|
205
|
+
parsedMentionsStyle.put(key, parsedStyle)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return parsedMentionsStyle
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private fun parseOptionalFontWeight(map: ReadableMap?, key: String): Int? {
|
|
212
|
+
if (map == null) return null
|
|
213
|
+
if (!map.hasKey(key)) return null
|
|
214
|
+
if (map.isNull(key)) return null
|
|
215
|
+
|
|
216
|
+
val fontWeight = map.getString(key) ?: return null
|
|
217
|
+
return parseFontWeight(fontWeight)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
companion object {
|
|
221
|
+
data class MentionStyle(
|
|
222
|
+
val color: Int,
|
|
223
|
+
val backgroundColor: Int,
|
|
224
|
+
val underline: Boolean
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
package com.swmansion.enriched.styles
|
|
2
|
+
|
|
3
|
+
import android.text.Editable
|
|
4
|
+
import android.text.Spannable
|
|
5
|
+
import com.swmansion.enriched.EnrichedTextInputView
|
|
6
|
+
import com.swmansion.enriched.spans.EnrichedSpans
|
|
7
|
+
import com.swmansion.enriched.utils.getSafeSpanBoundaries
|
|
8
|
+
|
|
9
|
+
class InlineStyles(private val view: EnrichedTextInputView) {
|
|
10
|
+
private fun <T>setSpan(spannable: Spannable, type: Class<T>, start: Int, end: Int) {
|
|
11
|
+
val previousSpanStart = (start - 1).coerceAtLeast(0)
|
|
12
|
+
val previousSpanEnd = previousSpanStart + 1
|
|
13
|
+
val nextSpanStart = (end + 1).coerceAtMost(spannable.length)
|
|
14
|
+
val nextSpanEnd = (nextSpanStart + 1).coerceAtMost(spannable.length)
|
|
15
|
+
val previousSpans = spannable.getSpans(previousSpanStart, previousSpanEnd, type)
|
|
16
|
+
val nextSpans = spannable.getSpans(nextSpanStart, nextSpanEnd, type)
|
|
17
|
+
var minimum = start
|
|
18
|
+
var maximum = end
|
|
19
|
+
|
|
20
|
+
for (span in previousSpans) {
|
|
21
|
+
val spanStart = spannable.getSpanStart(span)
|
|
22
|
+
minimum = spanStart.coerceAtMost(minimum)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (span in nextSpans) {
|
|
26
|
+
val spanEnd = spannable.getSpanEnd(span)
|
|
27
|
+
maximum = spanEnd.coerceAtLeast(maximum)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
val spans = spannable.getSpans(minimum, maximum, type)
|
|
31
|
+
for (span in spans) {
|
|
32
|
+
spannable.removeSpan(span)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle)
|
|
36
|
+
val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(minimum, maximum)
|
|
37
|
+
spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private fun <T>setAndMergeSpans(spannable: Spannable, type: Class<T>, start: Int, end: Int) {
|
|
41
|
+
val spans = spannable.getSpans(start, end, type)
|
|
42
|
+
|
|
43
|
+
// No spans setup for current selection, means we just need to assign new span
|
|
44
|
+
if (spans.isEmpty()) {
|
|
45
|
+
setSpan(spannable, type, start, end)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
var setSpanOnFinish = false
|
|
50
|
+
|
|
51
|
+
// Some spans are present, we have to remove spans and (optionally) apply new spans
|
|
52
|
+
for (span in spans) {
|
|
53
|
+
val spanStart = spannable.getSpanStart(span)
|
|
54
|
+
val spanEnd = spannable.getSpanEnd(span)
|
|
55
|
+
var finalStart: Int? = null
|
|
56
|
+
var finalEnd: Int? = null
|
|
57
|
+
|
|
58
|
+
spannable.removeSpan(span)
|
|
59
|
+
|
|
60
|
+
if (start == spanStart && end == spanEnd) {
|
|
61
|
+
setSpanOnFinish = false
|
|
62
|
+
} else if (start > spanStart && end < spanEnd) {
|
|
63
|
+
setSpan(spannable, type, spanStart, start)
|
|
64
|
+
setSpan(spannable, type, end, spanEnd)
|
|
65
|
+
} else if (start == spanStart && end < spanEnd) {
|
|
66
|
+
finalStart = end
|
|
67
|
+
finalEnd = spanEnd
|
|
68
|
+
} else if (start > spanStart && end == spanEnd) {
|
|
69
|
+
finalStart = spanStart
|
|
70
|
+
finalEnd = start
|
|
71
|
+
} else if (start > spanStart) {
|
|
72
|
+
finalStart = spanStart
|
|
73
|
+
finalEnd = end
|
|
74
|
+
} else if (start < spanStart && end < spanEnd) {
|
|
75
|
+
finalStart = start
|
|
76
|
+
finalEnd = spanEnd
|
|
77
|
+
} else {
|
|
78
|
+
setSpanOnFinish = true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!setSpanOnFinish && finalStart != null && finalEnd != null) {
|
|
82
|
+
setSpan(spannable, type, finalStart, finalEnd)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (setSpanOnFinish) {
|
|
87
|
+
setSpan(spannable, type, start, end)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fun afterTextChanged(s: Editable, endCursorPosition: Int) {
|
|
92
|
+
for ((style, config) in EnrichedSpans.inlineSpans) {
|
|
93
|
+
val start = view.spanState?.getStart(style) ?: continue
|
|
94
|
+
var end = endCursorPosition
|
|
95
|
+
val spans = s.getSpans(start, end, config.clazz)
|
|
96
|
+
|
|
97
|
+
for (span in spans) {
|
|
98
|
+
end = s.getSpanEnd(span).coerceAtLeast(end)
|
|
99
|
+
s.removeSpan(span)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setSpan(s, config.clazz, start, end)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fun toggleStyle(name: String) {
|
|
107
|
+
if (view.selection == null) return
|
|
108
|
+
val (start, end) = view.selection.getInlineSelection()
|
|
109
|
+
val config = EnrichedSpans.inlineSpans[name] ?: return
|
|
110
|
+
val type = config.clazz
|
|
111
|
+
|
|
112
|
+
// We either start or end current span
|
|
113
|
+
if (start == end) {
|
|
114
|
+
val styleStart = view.spanState?.getStart(name)
|
|
115
|
+
|
|
116
|
+
if (styleStart != null) {
|
|
117
|
+
view.spanState.setStart(name, null)
|
|
118
|
+
} else {
|
|
119
|
+
view.spanState?.setStart(name, start)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
val spannable = view.text as Spannable
|
|
126
|
+
setAndMergeSpans(spannable, type, start, end)
|
|
127
|
+
view.selection.validateStyles()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fun removeStyle(name: String, start: Int, end: Int): Boolean {
|
|
131
|
+
val config = EnrichedSpans.inlineSpans[name] ?: return false
|
|
132
|
+
val spannable = view.text as Spannable
|
|
133
|
+
val spans = spannable.getSpans(start, end, config.clazz)
|
|
134
|
+
if (spans.isEmpty()) return false
|
|
135
|
+
|
|
136
|
+
for (span in spans) {
|
|
137
|
+
spannable.removeSpan(span)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return true
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fun getStyleRange(): Pair<Int, Int> {
|
|
144
|
+
return view.selection?.getInlineSelection() ?: Pair(0, 0)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
package com.swmansion.enriched.styles
|
|
2
|
+
|
|
3
|
+
import android.text.Editable
|
|
4
|
+
import android.text.Spannable
|
|
5
|
+
import android.text.SpannableStringBuilder
|
|
6
|
+
import android.text.Spanned
|
|
7
|
+
import com.swmansion.enriched.EnrichedTextInputView
|
|
8
|
+
import com.swmansion.enriched.spans.EnrichedOrderedListSpan
|
|
9
|
+
import com.swmansion.enriched.spans.EnrichedSpans
|
|
10
|
+
import com.swmansion.enriched.spans.EnrichedUnorderedListSpan
|
|
11
|
+
import com.swmansion.enriched.utils.getParagraphBounds
|
|
12
|
+
import com.swmansion.enriched.utils.getSafeSpanBoundaries
|
|
13
|
+
|
|
14
|
+
class ListStyles(private val view: EnrichedTextInputView) {
|
|
15
|
+
private fun <T>getPreviousParagraphSpan(spannable: Spannable, s: Int, type: Class<T>): T? {
|
|
16
|
+
if (s <= 0) return null
|
|
17
|
+
|
|
18
|
+
val (previousParagraphStart, previousParagraphEnd) = spannable.getParagraphBounds(s - 1)
|
|
19
|
+
val spans = spannable.getSpans(previousParagraphStart, previousParagraphEnd, type)
|
|
20
|
+
|
|
21
|
+
if (spans.isNotEmpty()) {
|
|
22
|
+
return spans.last()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private fun <T>isPreviousParagraphList(spannable: Spannable, s: Int, type: Class<T>): Boolean {
|
|
29
|
+
val previousSpan = getPreviousParagraphSpan(spannable, s, type)
|
|
30
|
+
|
|
31
|
+
return previousSpan != null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private fun getOrderedListIndex(spannable: Spannable, s: Int): Int {
|
|
35
|
+
val span = getPreviousParagraphSpan(spannable, s, EnrichedOrderedListSpan::class.java)
|
|
36
|
+
val index = span?.getIndex() ?: 0
|
|
37
|
+
return index + 1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private fun setSpan(spannable: Spannable, name: String, start: Int, end: Int) {
|
|
41
|
+
val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end)
|
|
42
|
+
|
|
43
|
+
if (name == EnrichedSpans.UNORDERED_LIST) {
|
|
44
|
+
val span = EnrichedUnorderedListSpan(view.htmlStyle)
|
|
45
|
+
spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (name == EnrichedSpans.ORDERED_LIST) {
|
|
50
|
+
val index = getOrderedListIndex(spannable, safeStart)
|
|
51
|
+
val span = EnrichedOrderedListSpan(index, view.htmlStyle)
|
|
52
|
+
spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private fun <T>removeSpansForRange(spannable: Spannable, start: Int, end: Int, clazz: Class<T>): Boolean {
|
|
57
|
+
val ssb = spannable as SpannableStringBuilder
|
|
58
|
+
val spans = ssb.getSpans(start, end, clazz)
|
|
59
|
+
if (spans.isEmpty()) return false
|
|
60
|
+
|
|
61
|
+
ssb.replace(start, end, ssb.substring(start, end).replace("\u200B", ""))
|
|
62
|
+
|
|
63
|
+
for (span in spans) {
|
|
64
|
+
ssb.removeSpan(span)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return true
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fun updateOrderedListIndexes(text: Spannable, position: Int) {
|
|
71
|
+
val spans = text.getSpans(position + 1, text.length, EnrichedOrderedListSpan::class.java)
|
|
72
|
+
for (span in spans) {
|
|
73
|
+
val spanStart = text.getSpanStart(span)
|
|
74
|
+
val index = getOrderedListIndex(text, spanStart)
|
|
75
|
+
span.setIndex(index)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fun toggleStyle(name: String) {
|
|
80
|
+
if (view.selection == null) return
|
|
81
|
+
val config = EnrichedSpans.listSpans[name] ?: return
|
|
82
|
+
val spannable = view.text as SpannableStringBuilder
|
|
83
|
+
val (start, end) = view.selection.getParagraphSelection()
|
|
84
|
+
val styleStart = view.spanState?.getStart(name)
|
|
85
|
+
|
|
86
|
+
if (styleStart != null) {
|
|
87
|
+
view.spanState.setStart(name, null)
|
|
88
|
+
removeSpansForRange(spannable, start, end, config.clazz)
|
|
89
|
+
view.selection.validateStyles()
|
|
90
|
+
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (start == end) {
|
|
95
|
+
spannable.insert(start, "\u200B")
|
|
96
|
+
view.spanState?.setStart(name, start + 1)
|
|
97
|
+
removeSpansForRange(spannable, start, end, config.clazz)
|
|
98
|
+
setSpan(spannable, name, start, end + 1)
|
|
99
|
+
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
var currentStart = start
|
|
104
|
+
val paragraphs = spannable.substring(start, end).split("\n")
|
|
105
|
+
removeSpansForRange(spannable, start, end, config.clazz)
|
|
106
|
+
|
|
107
|
+
for (paragraph in paragraphs) {
|
|
108
|
+
spannable.insert(currentStart, "\u200B")
|
|
109
|
+
val currentEnd = currentStart + paragraph.length + 1
|
|
110
|
+
setSpan(spannable, name, currentStart, currentEnd)
|
|
111
|
+
|
|
112
|
+
currentStart = currentEnd + 1
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
view.spanState?.setStart(name, currentStart)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private fun handleAfterTextChanged(s: Editable, name: String, endCursorPosition: Int, previousTextLength: Int) {
|
|
119
|
+
val config = EnrichedSpans.listSpans[name] ?: return
|
|
120
|
+
val cursorPosition = endCursorPosition.coerceAtMost(s.length)
|
|
121
|
+
val (start, end) = s.getParagraphBounds(cursorPosition)
|
|
122
|
+
|
|
123
|
+
val isBackspace = previousTextLength > s.length
|
|
124
|
+
val isNewLine = cursorPosition > 0 && s[cursorPosition - 1] == '\n'
|
|
125
|
+
val isShortcut = s.substring(start, end).startsWith(config.shortcut)
|
|
126
|
+
val spans = s.getSpans(start, end, config.clazz)
|
|
127
|
+
|
|
128
|
+
// Remove spans if cursor is at the start of the paragraph and spans exist
|
|
129
|
+
if (isBackspace && start == cursorPosition && spans.isNotEmpty()) {
|
|
130
|
+
removeSpansForRange(s, start, end, config.clazz)
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!isBackspace && isShortcut) {
|
|
135
|
+
s.replace(start, cursorPosition, "\u200B")
|
|
136
|
+
setSpan(s, name, start, start + 1)
|
|
137
|
+
// Inform that new span has been added
|
|
138
|
+
view.selection?.validateStyles()
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!isBackspace && isNewLine && isPreviousParagraphList(s, start, config.clazz)) {
|
|
143
|
+
s.insert(cursorPosition, "\u200B")
|
|
144
|
+
setSpan(s, name, start, end + 1)
|
|
145
|
+
// Inform that new span has been added
|
|
146
|
+
view.selection?.validateStyles()
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (spans.isNotEmpty()) {
|
|
151
|
+
for (span in spans) {
|
|
152
|
+
s.removeSpan(span)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
setSpan(s, name, start, end)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
fun afterTextChanged(s: Editable, endCursorPosition: Int, previousTextLength: Int) {
|
|
160
|
+
handleAfterTextChanged(s, EnrichedSpans.ORDERED_LIST, endCursorPosition, previousTextLength)
|
|
161
|
+
handleAfterTextChanged(s, EnrichedSpans.UNORDERED_LIST, endCursorPosition, previousTextLength)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fun getStyleRange(): Pair<Int, Int> {
|
|
165
|
+
return view.selection?.getParagraphSelection() ?: Pair(0, 0)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fun removeStyle(name: String, start: Int, end: Int): Boolean {
|
|
169
|
+
val config = EnrichedSpans.listSpans[name] ?: return false
|
|
170
|
+
val spannable = view.text as Spannable
|
|
171
|
+
return removeSpansForRange(spannable, start, end, config.clazz)
|
|
172
|
+
}
|
|
173
|
+
}
|