react-native-enriched 0.0.0 → 0.1.1

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.
Files changed (169) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +875 -0
  3. package/ReactNativeEnriched.podspec +27 -0
  4. package/android/build.gradle +101 -0
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +146 -0
  6. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +55 -0
  7. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
  8. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
  9. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
  10. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
  11. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
  12. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +577 -0
  13. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
  14. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
  15. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.cpp +16 -0
  16. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.h +20 -0
  17. package/android/gradle.properties +5 -0
  18. package/android/src/main/AndroidManifest.xml +3 -0
  19. package/android/src/main/AndroidManifestNew.xml +2 -0
  20. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +535 -0
  21. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +64 -0
  22. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +292 -0
  23. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +19 -0
  24. package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +40 -0
  25. package/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt +28 -0
  26. package/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt +29 -0
  27. package/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt +24 -0
  28. package/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt +30 -0
  29. package/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt +27 -0
  30. package/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt +27 -0
  31. package/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt +30 -0
  32. package/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt +29 -0
  33. package/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt +33 -0
  34. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +34 -0
  35. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt +10 -0
  36. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +38 -0
  37. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt +17 -0
  38. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt +17 -0
  39. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt +17 -0
  40. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +41 -0
  41. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +16 -0
  42. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt +10 -0
  43. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt +24 -0
  44. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt +36 -0
  45. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +71 -0
  46. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +111 -0
  47. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt +9 -0
  48. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt +9 -0
  49. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +49 -0
  50. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt +4 -0
  51. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt +4 -0
  52. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt +4 -0
  53. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt +4 -0
  54. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt +4 -0
  55. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt +5 -0
  56. package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +227 -0
  57. package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +146 -0
  58. package/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt +173 -0
  59. package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +186 -0
  60. package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +223 -0
  61. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +857 -0
  62. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +285 -0
  63. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt +204 -0
  64. package/android/src/main/java/com/swmansion/enriched/utils/Utils.kt +91 -0
  65. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +73 -0
  66. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +51 -0
  67. package/android/src/main/new_arch/CMakeLists.txt +56 -0
  68. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.cpp +22 -0
  69. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.h +26 -0
  70. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputComponentDescriptor.h +35 -0
  71. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.cpp +51 -0
  72. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +26 -0
  73. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.cpp +34 -0
  74. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.h +54 -0
  75. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.cpp +9 -0
  76. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.h +25 -0
  77. package/ios/EnrichedTextInputView.h +33 -0
  78. package/ios/EnrichedTextInputView.mm +1190 -0
  79. package/ios/EnrichedTextInputViewManager.mm +13 -0
  80. package/ios/config/InputConfig.h +67 -0
  81. package/ios/config/InputConfig.mm +382 -0
  82. package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
  83. package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
  84. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
  85. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
  86. package/ios/generated/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
  87. package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +577 -0
  88. package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +384 -0
  89. package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
  90. package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
  91. package/ios/generated/RNEnrichedTextInputViewSpec/States.cpp +16 -0
  92. package/ios/generated/RNEnrichedTextInputViewSpec/States.h +20 -0
  93. package/ios/inputParser/InputParser.h +11 -0
  94. package/ios/inputParser/InputParser.mm +659 -0
  95. package/ios/inputTextView/InputTextView.h +6 -0
  96. package/ios/inputTextView/InputTextView.mm +115 -0
  97. package/ios/internals/EnrichedTextInputViewComponentDescriptor.h +17 -0
  98. package/ios/internals/EnrichedTextInputViewShadowNode.h +40 -0
  99. package/ios/internals/EnrichedTextInputViewShadowNode.mm +83 -0
  100. package/ios/internals/EnrichedTextInputViewState.cpp +10 -0
  101. package/ios/internals/EnrichedTextInputViewState.h +20 -0
  102. package/ios/styles/BlockQuoteStyle.mm +248 -0
  103. package/ios/styles/BoldStyle.mm +122 -0
  104. package/ios/styles/H1Style.mm +10 -0
  105. package/ios/styles/H2Style.mm +10 -0
  106. package/ios/styles/H3Style.mm +10 -0
  107. package/ios/styles/HeadingStyleBase.mm +144 -0
  108. package/ios/styles/InlineCodeStyle.mm +163 -0
  109. package/ios/styles/ItalicStyle.mm +110 -0
  110. package/ios/styles/LinkStyle.mm +463 -0
  111. package/ios/styles/MentionStyle.mm +476 -0
  112. package/ios/styles/OrderedListStyle.mm +225 -0
  113. package/ios/styles/StrikethroughStyle.mm +80 -0
  114. package/ios/styles/UnderlineStyle.mm +112 -0
  115. package/ios/styles/UnorderedListStyle.mm +225 -0
  116. package/ios/utils/BaseStyleProtocol.h +16 -0
  117. package/ios/utils/ColorExtension.h +6 -0
  118. package/ios/utils/ColorExtension.mm +27 -0
  119. package/ios/utils/FontExtension.h +13 -0
  120. package/ios/utils/FontExtension.mm +91 -0
  121. package/ios/utils/LayoutManagerExtension.h +6 -0
  122. package/ios/utils/LayoutManagerExtension.mm +171 -0
  123. package/ios/utils/LinkData.h +9 -0
  124. package/ios/utils/LinkData.mm +4 -0
  125. package/ios/utils/MentionParams.h +9 -0
  126. package/ios/utils/MentionParams.mm +4 -0
  127. package/ios/utils/MentionStyleProps.h +13 -0
  128. package/ios/utils/MentionStyleProps.mm +56 -0
  129. package/ios/utils/OccurenceUtils.h +37 -0
  130. package/ios/utils/OccurenceUtils.mm +124 -0
  131. package/ios/utils/ParagraphsUtils.h +7 -0
  132. package/ios/utils/ParagraphsUtils.mm +54 -0
  133. package/ios/utils/StringExtension.h +15 -0
  134. package/ios/utils/StringExtension.mm +57 -0
  135. package/ios/utils/StyleHeaders.h +74 -0
  136. package/ios/utils/StylePair.h +9 -0
  137. package/ios/utils/StylePair.mm +4 -0
  138. package/ios/utils/StyleTypeEnum.h +22 -0
  139. package/ios/utils/TextDecorationLineEnum.h +6 -0
  140. package/ios/utils/TextDecorationLineEnum.mm +4 -0
  141. package/ios/utils/TextInsertionUtils.h +6 -0
  142. package/ios/utils/TextInsertionUtils.mm +48 -0
  143. package/ios/utils/WordsUtils.h +6 -0
  144. package/ios/utils/WordsUtils.mm +88 -0
  145. package/ios/utils/ZeroWidthSpaceUtils.h +7 -0
  146. package/ios/utils/ZeroWidthSpaceUtils.mm +164 -0
  147. package/lib/module/EnrichedTextInput.js +191 -0
  148. package/lib/module/EnrichedTextInput.js.map +1 -0
  149. package/lib/module/EnrichedTextInputNativeComponent.ts +235 -0
  150. package/lib/module/index.js +4 -0
  151. package/lib/module/index.js.map +1 -0
  152. package/lib/module/normalizeHtmlStyle.js +141 -0
  153. package/lib/module/normalizeHtmlStyle.js.map +1 -0
  154. package/lib/module/package.json +1 -0
  155. package/lib/typescript/package.json +1 -0
  156. package/lib/typescript/src/EnrichedTextInput.d.ts +113 -0
  157. package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -0
  158. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +160 -0
  159. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -0
  160. package/lib/typescript/src/index.d.ts +3 -0
  161. package/lib/typescript/src/index.d.ts.map +1 -0
  162. package/lib/typescript/src/normalizeHtmlStyle.d.ts +4 -0
  163. package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +1 -0
  164. package/package.json +172 -1
  165. package/react-native.config.js +13 -0
  166. package/src/EnrichedTextInput.tsx +358 -0
  167. package/src/EnrichedTextInputNativeComponent.ts +235 -0
  168. package/src/index.tsx +9 -0
  169. package/src/normalizeHtmlStyle.ts +188 -0
@@ -0,0 +1,186 @@
1
+ package com.swmansion.enriched.styles
2
+
3
+ import android.text.Editable
4
+ import android.text.Spannable
5
+ import android.text.SpannableStringBuilder
6
+ import com.swmansion.enriched.EnrichedTextInputView
7
+ import com.swmansion.enriched.spans.EnrichedSpans
8
+ import com.swmansion.enriched.utils.getParagraphBounds
9
+ import com.swmansion.enriched.utils.getSafeSpanBoundaries
10
+
11
+ class ParagraphStyles(private val view: EnrichedTextInputView) {
12
+ private fun <T>setSpan(spannable: Spannable, type: Class<T>, start: Int, end: Int) {
13
+ val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle)
14
+ val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end)
15
+ spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
16
+ }
17
+
18
+ private fun <T>removeSpansForRange(spannable: Spannable, start: Int, end: Int, clazz: Class<T>): Boolean {
19
+ val ssb = spannable as SpannableStringBuilder
20
+ var finalStart = start
21
+ var finalEnd = end
22
+
23
+ val spans = ssb.getSpans(start, end, clazz)
24
+ if (spans.isEmpty()) return false
25
+
26
+ for (span in spans) {
27
+ finalStart = ssb.getSpanStart(span).coerceAtMost(finalStart)
28
+ finalEnd = ssb.getSpanEnd(span).coerceAtLeast(finalEnd)
29
+
30
+ ssb.removeSpan(span)
31
+ }
32
+
33
+ ssb.replace(finalStart, finalEnd, ssb.substring(finalStart, finalEnd).replace("\u200B", ""))
34
+ return true
35
+ }
36
+
37
+ private fun <T>setAndMergeSpans(spannable: Spannable, type: Class<T>, start: Int, end: Int) {
38
+ val spans = spannable.getSpans(start, end, type)
39
+
40
+ // No spans setup for current selection, means we just need to assign new span
41
+ if (spans.isEmpty()) {
42
+ setSpan(spannable, type, start, end)
43
+ return
44
+ }
45
+
46
+ var setSpanOnFinish = false
47
+
48
+ // Some spans are present, we have to remove spans and (optionally) apply new spans
49
+ for (span in spans) {
50
+ val spanStart = spannable.getSpanStart(span)
51
+ val spanEnd = spannable.getSpanEnd(span)
52
+ var finalStart: Int? = null
53
+ var finalEnd: Int? = null
54
+
55
+ spannable.removeSpan(span)
56
+
57
+ if (start == spanStart && end == spanEnd) {
58
+ setSpanOnFinish = false
59
+ } else if (start > spanStart && end < spanEnd) {
60
+ setSpan(spannable, type, spanStart, start)
61
+ setSpan(spannable, type, end, spanEnd)
62
+ } else if (start == spanStart && end < spanEnd) {
63
+ finalStart = end
64
+ finalEnd = spanEnd
65
+ } else if (start > spanStart && end == spanEnd) {
66
+ finalStart = spanStart
67
+ finalEnd = start
68
+ } else if (start > spanStart) {
69
+ finalStart = spanStart
70
+ finalEnd = end
71
+ } else if (start < spanStart && end < spanEnd) {
72
+ finalStart = start
73
+ finalEnd = spanEnd
74
+ } else {
75
+ setSpanOnFinish = true
76
+ }
77
+
78
+ if (!setSpanOnFinish && finalStart != null && finalEnd != null) {
79
+ setSpan(spannable, type, finalStart, finalEnd)
80
+ }
81
+ }
82
+
83
+ if (setSpanOnFinish) {
84
+ setSpan(spannable, type, start, end)
85
+ }
86
+ }
87
+
88
+ private fun <T>isSpanEnabledInNextLine(spannable: Spannable, index: Int, type: Class<T>): Boolean {
89
+ val selection = view.selection ?: return false
90
+ if (index + 1 >= spannable.length) return false
91
+ val (start, end) = selection.getParagraphSelection()
92
+
93
+ val spans = spannable.getSpans(start, end, type)
94
+ return spans.isNotEmpty()
95
+ }
96
+
97
+ fun afterTextChanged(s: Editable, endPosition: Int, previousTextLength: Int) {
98
+ var endCursorPosition = endPosition
99
+ val isBackspace = s.length < previousTextLength
100
+ val isNewLine = endCursorPosition == 0 || endCursorPosition > 0 && s[endCursorPosition - 1] == '\n'
101
+
102
+ for ((style, config) in EnrichedSpans.paragraphSpans) {
103
+ val spanState = view.spanState ?: continue
104
+ val styleStart = spanState.getStart(style) ?: continue
105
+
106
+ if (isNewLine) {
107
+ if (!config.isContinuous) {
108
+ spanState.setStart(style, null)
109
+ continue
110
+ }
111
+
112
+ if (isBackspace) {
113
+ endCursorPosition -= 1
114
+ view.spanState.setStart(style, null)
115
+ } else {
116
+ s.insert(endCursorPosition, "\u200B")
117
+ endCursorPosition += 1
118
+ }
119
+ }
120
+
121
+ var (start, end) = s.getParagraphBounds(styleStart, endCursorPosition)
122
+ val isNotEndLineSpan = isSpanEnabledInNextLine(s, end, config.clazz)
123
+ val spans = s.getSpans(start, end, config.clazz)
124
+
125
+ for (span in spans) {
126
+ if (isNotEndLineSpan) {
127
+ start = s.getSpanStart(span).coerceAtMost(start)
128
+ end = s.getSpanEnd(span).coerceAtLeast(end)
129
+ }
130
+
131
+ s.removeSpan(span)
132
+ }
133
+
134
+ setSpan(s, config.clazz, start, end)
135
+ }
136
+ }
137
+
138
+ fun toggleStyle(name: String) {
139
+ if (view.selection == null) return
140
+ val spannable = view.text as SpannableStringBuilder
141
+ val (start, end) = view.selection.getParagraphSelection()
142
+ val config = EnrichedSpans.paragraphSpans[name] ?: return
143
+ val type = config.clazz
144
+
145
+ val styleStart = view.spanState?.getStart(name)
146
+
147
+ if (styleStart != null) {
148
+ view.spanState.setStart(name, null)
149
+ removeSpansForRange(spannable, start, end, type)
150
+ view.selection.validateStyles()
151
+
152
+ return
153
+ }
154
+
155
+ if (start == end) {
156
+ spannable.insert(start, "\u200B")
157
+ view.spanState?.setStart(name, start + 1)
158
+ setAndMergeSpans(spannable, type, start, end + 1)
159
+
160
+ return
161
+ }
162
+
163
+ var currentStart = start
164
+ var currentEnd = currentStart
165
+ val paragraphs = spannable.substring(start, end).split("\n")
166
+
167
+ for (paragraph in paragraphs) {
168
+ spannable.insert(currentStart, "\u200B")
169
+ currentEnd = currentStart + paragraph.length + 1
170
+ currentStart = currentEnd + 1
171
+ }
172
+
173
+ view.spanState?.setStart(name, start)
174
+ setAndMergeSpans(spannable, type, start, currentEnd)
175
+ }
176
+
177
+ fun getStyleRange(): Pair<Int, Int> {
178
+ return view.selection?.getParagraphSelection() ?: Pair(0, 0)
179
+ }
180
+
181
+ fun removeStyle(name: String, start: Int, end: Int): Boolean {
182
+ val config = EnrichedSpans.paragraphSpans[name] ?: return false
183
+ val spannable = view.text as Spannable
184
+ return removeSpansForRange(spannable, start, end, config.clazz)
185
+ }
186
+ }
@@ -0,0 +1,223 @@
1
+ package com.swmansion.enriched.styles
2
+
3
+ import android.net.Uri
4
+ import android.text.Editable
5
+ import android.text.Spannable
6
+ import android.text.SpannableStringBuilder
7
+ import android.text.Spanned
8
+ import com.swmansion.enriched.EnrichedTextInputView
9
+ import com.swmansion.enriched.spans.EnrichedImageSpan
10
+ import com.swmansion.enriched.spans.EnrichedLinkSpan
11
+ import com.swmansion.enriched.spans.EnrichedMentionSpan
12
+ import com.swmansion.enriched.spans.EnrichedSpans
13
+ import com.swmansion.enriched.utils.getSafeSpanBoundaries
14
+ import java.io.File
15
+
16
+ class ParametrizedStyles(private val view: EnrichedTextInputView) {
17
+ private var mentionStart: Int? = null
18
+ private var isSettingLinkSpan = false
19
+
20
+ var mentionIndicators: Array<String> = emptyArray<String>()
21
+
22
+ fun <T>removeSpansForRange(spannable: Spannable, start: Int, end: Int, clazz: Class<T>): Boolean {
23
+ val ssb = spannable as SpannableStringBuilder
24
+ val spans = ssb.getSpans(start, end, clazz)
25
+ if (spans.isEmpty()) return false
26
+
27
+ ssb.replace(start, end, ssb.substring(start, end).replace("\u200B", ""))
28
+
29
+ for (span in spans) {
30
+ ssb.removeSpan(span)
31
+ }
32
+
33
+ return true
34
+ }
35
+
36
+ fun setLinkSpan(start: Int, end: Int, text: String, url: String) {
37
+ isSettingLinkSpan = true
38
+
39
+ val spannable = view.text as SpannableStringBuilder
40
+ val spans = spannable.getSpans(start, end, EnrichedLinkSpan::class.java)
41
+ for (span in spans) {
42
+ spannable.removeSpan(span)
43
+ }
44
+
45
+ if (start == end) {
46
+ spannable.insert(start, text)
47
+ } else {
48
+ spannable.replace(start, end, text)
49
+ }
50
+
51
+ val spanEnd = start + text.length
52
+ val span = EnrichedLinkSpan(url, view.htmlStyle)
53
+ val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, spanEnd)
54
+ spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
55
+
56
+ view.selection?.validateStyles()
57
+ isSettingLinkSpan = false
58
+ }
59
+
60
+ fun afterTextChanged(s: Editable, endCursorPosition: Int) {
61
+ val result = getWordAtIndex(s, endCursorPosition) ?: return
62
+
63
+ afterTextChangedLinks(result)
64
+ afterTextChangedMentions(result)
65
+ }
66
+
67
+ fun detectAllLinks() {
68
+ val spannable = view.text as Spannable
69
+
70
+ // TODO: Consider using more reliable regex, this one matches almost anything
71
+ val urlPattern = android.util.Patterns.WEB_URL.matcher(spannable)
72
+
73
+ val spans = spannable.getSpans(0, spannable.length, EnrichedLinkSpan::class.java)
74
+ for (span in spans) {
75
+ spannable.removeSpan(span)
76
+ }
77
+
78
+ while (urlPattern.find()) {
79
+ val word = urlPattern.group()
80
+ val start = urlPattern.start()
81
+ val end = urlPattern.end()
82
+ val span = EnrichedLinkSpan(word, view.htmlStyle)
83
+ val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end)
84
+ spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
85
+ }
86
+ }
87
+
88
+ private fun getWordAtIndex(s: Editable, index: Int): Triple<String, Int, Int>? {
89
+ if (index < 0 ) return null
90
+
91
+ var start = index
92
+ var end = index
93
+
94
+ while (start > 0 && !Character.isWhitespace(s[start - 1])) {
95
+ start--
96
+ }
97
+
98
+ while (end < s.length && !Character.isWhitespace(s[end])) {
99
+ end++
100
+ }
101
+
102
+ val result = s.subSequence(start, end).toString()
103
+
104
+ return Triple(result, start, end)
105
+ }
106
+
107
+ private fun afterTextChangedLinks(result: Triple<String, Int, Int>) {
108
+ // Do not detect link if it's applied manually
109
+ if (isSettingLinkSpan) return
110
+ val spannable = view.text as Spannable
111
+ val (word, start, end) = result
112
+
113
+ // TODO: Consider using more reliable regex, this one matches almost anything
114
+ val urlPattern = android.util.Patterns.WEB_URL.matcher(word)
115
+
116
+ val spans = spannable.getSpans(start, end, EnrichedLinkSpan::class.java)
117
+ for (span in spans) {
118
+ spannable.removeSpan(span)
119
+ }
120
+
121
+ if (urlPattern.matches()) {
122
+ val span = EnrichedLinkSpan(word, view.htmlStyle)
123
+ val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end)
124
+ spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
125
+ }
126
+ }
127
+
128
+ private fun afterTextChangedMentions(result: Triple<String, Int, Int>) {
129
+ val mentionHandler = view.mentionHandler ?: return
130
+ val spannable = view.text as Spannable
131
+ val (word, start, end) = result
132
+
133
+ val indicatorsPattern = mentionIndicators.joinToString("|") { Regex.escape(it) }
134
+ val mentionIndicatorRegex = Regex("^($indicatorsPattern)")
135
+ val mentionRegex= Regex("^($indicatorsPattern)\\w*")
136
+
137
+ val spans = spannable.getSpans(start, end, EnrichedMentionSpan::class.java)
138
+ for (span in spans) {
139
+ spannable.removeSpan(span)
140
+ }
141
+
142
+ if (mentionRegex.matches(word)) {
143
+ val indicator = mentionIndicatorRegex.find(word)?.value ?: ""
144
+ val text = word.replaceFirst(indicator, "")
145
+
146
+ // Means we are starting mention
147
+ if (text.isEmpty()) {
148
+ mentionStart = start
149
+ }
150
+
151
+ mentionHandler.onMention(indicator, word.replaceFirst(indicator, ""))
152
+ } else {
153
+ mentionHandler.endMention()
154
+ }
155
+ }
156
+
157
+ fun setImageSpan(src: String) {
158
+ if (view.selection == null) return
159
+
160
+ val spannable = view.text as SpannableStringBuilder
161
+ var (start, end) = view.selection.getInlineSelection()
162
+ val spans = spannable.getSpans(start, end, EnrichedImageSpan::class.java)
163
+
164
+ for (s in spans) {
165
+ spannable.removeSpan(s)
166
+ }
167
+
168
+ if (start == end) {
169
+ spannable.insert(start, "\uFFFC")
170
+ end++
171
+ }
172
+
173
+ val uri = Uri.fromFile(File(src))
174
+ val span = EnrichedImageSpan(view.context, uri, view.htmlStyle)
175
+ val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end)
176
+ spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
177
+ }
178
+
179
+ fun startMention(indicator: String) {
180
+ val selection = view.selection ?: return
181
+
182
+ val spannable = view.text as SpannableStringBuilder
183
+ val (start, end) = selection.getInlineSelection()
184
+
185
+ if (start == end) {
186
+ spannable.insert(start, indicator)
187
+ } else {
188
+ spannable.replace(start, end, indicator)
189
+ }
190
+ }
191
+
192
+ fun setMentionSpan(indicator: String, text: String, attributes: Map<String, String>) {
193
+ val selection = view.selection ?: return
194
+
195
+ val spannable = view.text as SpannableStringBuilder
196
+ val (selectionStart, selectionEnd) = selection.getInlineSelection()
197
+ val spans = spannable.getSpans(selectionStart, selectionEnd, EnrichedMentionSpan::class.java)
198
+
199
+ for (span in spans) {
200
+ spannable.removeSpan(span)
201
+ }
202
+
203
+ val start = mentionStart ?: return
204
+ spannable.replace(start, selectionEnd, text)
205
+
206
+ val span = EnrichedMentionSpan(text, indicator, attributes, view.htmlStyle)
207
+ val spanEnd = start + text.length
208
+ val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, spanEnd)
209
+ spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
210
+
211
+ view.selection.validateStyles()
212
+ }
213
+
214
+ fun getStyleRange(): Pair<Int, Int> {
215
+ return view.selection?.getInlineSelection() ?: Pair(0, 0)
216
+ }
217
+
218
+ fun removeStyle(name: String, start: Int, end: Int): Boolean {
219
+ val config = EnrichedSpans.parametrizedStyles[name] ?: return false
220
+ val spannable = view.text as Spannable
221
+ return removeSpansForRange(spannable, start, end, config.clazz)
222
+ }
223
+ }