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,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
|
+
}
|