react-native-enriched 0.1.2 → 0.1.4
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/README.md +49 -658
- package/ReactNativeEnriched.podspec +1 -1
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +11 -6
- package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +5 -0
- package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +2 -0
- package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +1 -1
- package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +2 -2
- package/android/src/main/new_arch/CMakeLists.txt +13 -9
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +2 -2
- package/ios/EnrichedTextInputView.mm +56 -53
- package/ios/internals/EnrichedTextInputViewShadowNode.mm +4 -1
- package/ios/styles/BlockQuoteStyle.mm +18 -23
- package/ios/styles/BoldStyle.mm +5 -2
- package/ios/styles/HeadingStyleBase.mm +37 -5
- package/ios/styles/InlineCodeStyle.mm +12 -8
- package/ios/styles/ItalicStyle.mm +5 -5
- package/ios/styles/MentionStyle.mm +3 -2
- package/ios/styles/OrderedListStyle.mm +18 -23
- package/ios/styles/StrikethroughStyle.mm +5 -2
- package/ios/styles/UnderlineStyle.mm +5 -2
- package/ios/styles/UnorderedListStyle.mm +18 -23
- package/ios/utils/ColorExtension.h +1 -0
- package/ios/utils/ColorExtension.mm +9 -0
- package/ios/utils/OccurenceUtils.h +6 -0
- package/ios/utils/OccurenceUtils.mm +31 -0
- package/ios/utils/ParagraphAttributesUtils.h +6 -0
- package/ios/utils/ParagraphAttributesUtils.mm +67 -0
- package/ios/utils/StyleHeaders.h +2 -0
- package/ios/utils/TextInsertionUtils.mm +2 -2
- package/ios/utils/ZeroWidthSpaceUtils.mm +16 -10
- package/package.json +4 -4
|
@@ -11,7 +11,7 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.authors = package["author"]
|
|
12
12
|
|
|
13
13
|
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
-
s.source = { :git => "https://github.com/software-mansion
|
|
14
|
+
s.source = { :git => "https://github.com/software-mansion/react-native-enriched.git", :tag => "#{s.version}" }
|
|
15
15
|
|
|
16
16
|
s.source_files = "ios/**/*.{h,m,mm,cpp}"
|
|
17
17
|
s.private_header_files = "ios/**/*.h"
|
|
@@ -51,7 +51,10 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
51
51
|
val paragraphStyles: ParagraphStyles? = ParagraphStyles(this)
|
|
52
52
|
val listStyles: ListStyles? = ListStyles(this)
|
|
53
53
|
val parametrizedStyles: ParametrizedStyles? = ParametrizedStyles(this)
|
|
54
|
-
|
|
54
|
+
// Sometimes setting up style triggers many changes in sequence
|
|
55
|
+
// Eg. removing conflicting styles -> changing text -> applying spans
|
|
56
|
+
// In such scenario we want to prevent from handling side effects (eg. onTextChanged)
|
|
57
|
+
var isDuringTransaction: Boolean = false
|
|
55
58
|
var isRemovingMany: Boolean = false
|
|
56
59
|
|
|
57
60
|
val mentionHandler: MentionHandler? = MentionHandler(this)
|
|
@@ -204,7 +207,9 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
204
207
|
}
|
|
205
208
|
}
|
|
206
209
|
|
|
207
|
-
|
|
210
|
+
// Currently, we do not support pasting images
|
|
211
|
+
if (item?.text == null) return
|
|
212
|
+
val finalText = currentText.mergeSpannables(start, end, item.text.toString())
|
|
208
213
|
setValue(finalText)
|
|
209
214
|
parametrizedStyles?.detectAllLinks()
|
|
210
215
|
}
|
|
@@ -231,7 +236,7 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
231
236
|
|
|
232
237
|
fun setValue(value: CharSequence?) {
|
|
233
238
|
if (value == null) return
|
|
234
|
-
|
|
239
|
+
isDuringTransaction = true
|
|
235
240
|
|
|
236
241
|
val newText = parseText(value)
|
|
237
242
|
setText(newText)
|
|
@@ -242,7 +247,7 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
242
247
|
// Scroll to the last line of text
|
|
243
248
|
setSelection(text?.length ?: 0)
|
|
244
249
|
|
|
245
|
-
|
|
250
|
+
isDuringTransaction = false
|
|
246
251
|
}
|
|
247
252
|
|
|
248
253
|
fun setAutoFocus(autoFocus: Boolean) {
|
|
@@ -450,13 +455,13 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
450
455
|
val end = selection?.end ?: 0
|
|
451
456
|
val lengthBefore = text?.length ?: 0
|
|
452
457
|
|
|
453
|
-
|
|
458
|
+
isDuringTransaction = true
|
|
454
459
|
val targetRange = getTargetRange(name)
|
|
455
460
|
val removed = removeStyle(style, targetRange.first, targetRange.second)
|
|
456
461
|
if (removed) {
|
|
457
462
|
spanState?.setStart(style, null)
|
|
458
463
|
}
|
|
459
|
-
|
|
464
|
+
isDuringTransaction = false
|
|
460
465
|
|
|
461
466
|
val lengthAfter = text?.length ?: 0
|
|
462
467
|
val charactersRemoved = lengthBefore - lengthAfter
|
|
@@ -8,6 +8,11 @@ class MentionHandler(private val view: EnrichedTextInputView) {
|
|
|
8
8
|
private var previousText: String? = null
|
|
9
9
|
private var previousIndicator: String? = null
|
|
10
10
|
|
|
11
|
+
fun reset() {
|
|
12
|
+
previousText = null
|
|
13
|
+
previousIndicator = null
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
fun endMention() {
|
|
12
17
|
val indicator = previousIndicator
|
|
13
18
|
if (indicator == null) return
|
|
@@ -154,6 +154,8 @@ class HtmlStyle {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
private fun withOpacity(color: Int, alpha: Int): Int {
|
|
157
|
+
// Do not apply opacity to transparent color
|
|
158
|
+
if (Color.alpha(color) == 0) return color
|
|
157
159
|
val a = alpha.coerceIn(0, 255)
|
|
158
160
|
return (color and 0x00FFFFFF) or (a shl 24)
|
|
159
161
|
}
|
|
@@ -201,6 +201,7 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
val start = mentionStart ?: return
|
|
204
|
+
view.isDuringTransaction = true
|
|
204
205
|
spannable.replace(start, selectionEnd, text)
|
|
205
206
|
|
|
206
207
|
val span = EnrichedMentionSpan(text, indicator, attributes, view.htmlStyle)
|
|
@@ -213,6 +214,8 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) {
|
|
|
213
214
|
spannable.insert(safeEnd, " ")
|
|
214
215
|
}
|
|
215
216
|
|
|
217
|
+
view.isDuringTransaction = false
|
|
218
|
+
view.mentionHandler?.reset()
|
|
216
219
|
view.selection.validateStyles()
|
|
217
220
|
}
|
|
218
221
|
|
|
@@ -39,7 +39,7 @@ class EnrichedSelection(private val view: EnrichedTextInputView) {
|
|
|
39
39
|
val finalStart = newStart.coerceAtMost(newEnd).coerceAtLeast(0).coerceAtMost(textLength)
|
|
40
40
|
val finalEnd = newEnd.coerceAtLeast(newStart).coerceAtLeast(0).coerceAtMost(textLength)
|
|
41
41
|
|
|
42
|
-
if (isZeroWidthSelection(finalStart, finalEnd) && !view.
|
|
42
|
+
if (isZeroWidthSelection(finalStart, finalEnd) && !view.isDuringTransaction) {
|
|
43
43
|
view.setSelection(finalStart + 1)
|
|
44
44
|
shouldValidateStyles = false
|
|
45
45
|
}
|
|
@@ -18,14 +18,14 @@ class EnrichedTextWatcher(private val view: EnrichedTextInputView) : TextWatcher
|
|
|
18
18
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
|
19
19
|
endCursorPosition = start + count
|
|
20
20
|
view.layoutManager.measureSize(s ?: "")
|
|
21
|
-
view.isRemovingMany = !view.
|
|
21
|
+
view.isRemovingMany = !view.isDuringTransaction && before > count + 1
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
override fun afterTextChanged(s: Editable?) {
|
|
25
25
|
if (s == null) return
|
|
26
26
|
emitEvents(s)
|
|
27
27
|
|
|
28
|
-
if (view.
|
|
28
|
+
if (view.isDuringTransaction) return
|
|
29
29
|
applyStyles(s)
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -39,15 +39,19 @@ target_link_libraries(
|
|
|
39
39
|
ReactAndroid::reactnative
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80)
|
|
43
|
+
target_compile_reactnative_options(${LIB_TARGET_NAME} PRIVATE)
|
|
44
|
+
else()
|
|
45
|
+
target_compile_options(
|
|
46
|
+
${LIB_TARGET_NAME}
|
|
47
|
+
PRIVATE
|
|
48
|
+
-DLOG_TAG=\"ReactNative\"
|
|
49
|
+
-fexceptions
|
|
50
|
+
-frtti
|
|
51
|
+
-Wall
|
|
52
|
+
-std=c++20
|
|
53
|
+
)
|
|
54
|
+
endif()
|
|
51
55
|
|
|
52
56
|
target_include_directories(
|
|
53
57
|
${CMAKE_PROJECT_NAME}
|
|
@@ -11,7 +11,7 @@ namespace facebook::react {
|
|
|
11
11
|
class EnrichedTextInputMeasurementManager {
|
|
12
12
|
public:
|
|
13
13
|
EnrichedTextInputMeasurementManager(
|
|
14
|
-
const
|
|
14
|
+
const std::shared_ptr<const ContextContainer>& contextContainer)
|
|
15
15
|
: contextContainer_(contextContainer) {}
|
|
16
16
|
|
|
17
17
|
Size measure(
|
|
@@ -20,7 +20,7 @@ namespace facebook::react {
|
|
|
20
20
|
LayoutConstraints layoutConstraints) const;
|
|
21
21
|
|
|
22
22
|
private:
|
|
23
|
-
const
|
|
23
|
+
const std::shared_ptr<const ContextContainer> contextContainer_;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
} // namespace facebook::react
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
#import "WordsUtils.h"
|
|
15
15
|
#import "LayoutManagerExtension.h"
|
|
16
16
|
#import "ZeroWidthSpaceUtils.h"
|
|
17
|
+
#import "ParagraphAttributesUtils.h"
|
|
17
18
|
|
|
18
19
|
using namespace facebook::react;
|
|
19
20
|
|
|
@@ -422,7 +423,7 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
|
422
423
|
}
|
|
423
424
|
|
|
424
425
|
// editable
|
|
425
|
-
if(newViewProps.editable !=
|
|
426
|
+
if(newViewProps.editable != textView.editable) {
|
|
426
427
|
textView.editable = newViewProps.editable;
|
|
427
428
|
}
|
|
428
429
|
|
|
@@ -564,7 +565,7 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
|
564
565
|
unichar lastChar = [currentStr.string characterAtIndex:currentStr.length-1];
|
|
565
566
|
if([[NSCharacterSet newlineCharacterSet] characterIsMember:lastChar]) {
|
|
566
567
|
[currentStr appendAttributedString:
|
|
567
|
-
[[NSAttributedString alloc] initWithString:@"I" attributes:
|
|
568
|
+
[[NSAttributedString alloc] initWithString:@"I" attributes:defaultTypingAttributes]
|
|
568
569
|
];
|
|
569
570
|
}
|
|
570
571
|
}
|
|
@@ -977,6 +978,22 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
|
977
978
|
[mentionStyleClass manageMentionTypingAttributes];
|
|
978
979
|
[mentionStyleClass manageMentionEditing];
|
|
979
980
|
}
|
|
981
|
+
|
|
982
|
+
// typing attributes for empty lines selection reset
|
|
983
|
+
NSString *currentString = [textView.textStorage.string copy];
|
|
984
|
+
if(textView.selectedRange.length == 0 && [_recentlyEmittedString isEqualToString:currentString]) {
|
|
985
|
+
// no string change means only a selection changed with no character changes
|
|
986
|
+
NSRange paragraphRange = [textView.textStorage.string paragraphRangeForRange:textView.selectedRange];
|
|
987
|
+
if(
|
|
988
|
+
paragraphRange.length == 0 ||
|
|
989
|
+
(paragraphRange.length == 1 &&
|
|
990
|
+
[[NSCharacterSet newlineCharacterSet] characterIsMember:[textView.textStorage.string characterAtIndex:paragraphRange.location]])
|
|
991
|
+
) {
|
|
992
|
+
// user changed selection to an empty line (or empty line with a newline)
|
|
993
|
+
// typing attributes need to be reset
|
|
994
|
+
textView.typingAttributes = defaultTypingAttributes;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
980
997
|
}
|
|
981
998
|
|
|
982
999
|
- (void)handleWordModificationBasedChanges:(NSString*)word inRange:(NSRange)range {
|
|
@@ -995,15 +1012,15 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
|
995
1012
|
return;
|
|
996
1013
|
}
|
|
997
1014
|
|
|
1015
|
+
// zero width space adding or removal
|
|
1016
|
+
[ZeroWidthSpaceUtils handleZeroWidthSpacesInInput:self];
|
|
1017
|
+
|
|
998
1018
|
// emptying input typing attributes management
|
|
999
1019
|
if(textView.textStorage.string.length == 0 && _recentlyEmittedString.length > 0) {
|
|
1000
1020
|
// reset typing attribtues
|
|
1001
1021
|
textView.typingAttributes = defaultTypingAttributes;
|
|
1002
1022
|
}
|
|
1003
1023
|
|
|
1004
|
-
// zero width space removal
|
|
1005
|
-
[ZeroWidthSpaceUtils handleZeroWidthSpacesInInput:self];
|
|
1006
|
-
|
|
1007
1024
|
// inline code on newlines fix
|
|
1008
1025
|
InlineCodeStyle *codeStyle = stylesDict[@([InlineCodeStyle getStyleType])];
|
|
1009
1026
|
if(codeStyle != nullptr) {
|
|
@@ -1016,6 +1033,16 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
|
1016
1033
|
[bqStyle manageBlockquoteColor];
|
|
1017
1034
|
}
|
|
1018
1035
|
|
|
1036
|
+
// improper headings fix
|
|
1037
|
+
H1Style *h1Style = stylesDict[@([H1Style getStyleType])];
|
|
1038
|
+
H2Style *h2Style = stylesDict[@([H2Style getStyleType])];
|
|
1039
|
+
H3Style *h3Style = stylesDict[@([H3Style getStyleType])];
|
|
1040
|
+
if(h1Style != nullptr && h2Style != nullptr && h3Style != nullptr) {
|
|
1041
|
+
[h1Style handleImproperHeadings];
|
|
1042
|
+
[h2Style handleImproperHeadings];
|
|
1043
|
+
[h3Style handleImproperHeadings];
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1019
1046
|
// placholder management
|
|
1020
1047
|
if(!_placeholderLabel.hidden && textView.textStorage.string.length > 0) {
|
|
1021
1048
|
[self setPlaceholderLabelShown:NO];
|
|
@@ -1102,61 +1129,37 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
|
1102
1129
|
- (bool)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
|
|
1103
1130
|
recentlyChangedRange = NSMakeRange(range.location, text.length);
|
|
1104
1131
|
|
|
1105
|
-
BOOL rejectTextChanges = NO;
|
|
1106
|
-
|
|
1107
1132
|
UnorderedListStyle *uStyle = stylesDict[@([UnorderedListStyle getStyleType])];
|
|
1108
|
-
if(uStyle != nullptr) {
|
|
1109
|
-
// removing first line list fix
|
|
1110
|
-
BOOL removedFirstLineList = [uStyle handleBackspaceInRange:range replacementText:text];
|
|
1111
|
-
|
|
1112
|
-
// creating unordered list from "- "
|
|
1113
|
-
BOOL addedShortcutList = [uStyle tryHandlingListShorcutInRange:range replacementText:text];
|
|
1114
|
-
|
|
1115
|
-
// any of these changes make us reject text changes
|
|
1116
|
-
rejectTextChanges = rejectTextChanges || removedFirstLineList || addedShortcutList;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
1133
|
OrderedListStyle *oStyle = stylesDict[@([OrderedListStyle getStyleType])];
|
|
1120
|
-
if(oStyle != nullptr) {
|
|
1121
|
-
// removing first line list fix
|
|
1122
|
-
BOOL removedFirstLineList = [oStyle handleBackspaceInRange:range replacementText:text];
|
|
1123
|
-
|
|
1124
|
-
// creating ordered list from "1."
|
|
1125
|
-
BOOL addedShortcutList = [oStyle tryHandlingListShorcutInRange:range replacementText:text];
|
|
1126
|
-
|
|
1127
|
-
// any of these changes make us reject text changes
|
|
1128
|
-
rejectTextChanges = rejectTextChanges || removedFirstLineList || addedShortcutList;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
1134
|
BlockQuoteStyle *bqStyle = stylesDict[@([BlockQuoteStyle getStyleType])];
|
|
1132
|
-
if(bqStyle != nullptr) {
|
|
1133
|
-
// removing first line quote fix
|
|
1134
|
-
BOOL removedFirstLineQuote = [bqStyle handleBackspaceInRange:range replacementText:text];
|
|
1135
|
-
rejectTextChanges = rejectTextChanges || removedFirstLineQuote;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
1135
|
LinkStyle *linkStyle = stylesDict[@([LinkStyle getStyleType])];
|
|
1139
|
-
if(linkStyle != nullptr) {
|
|
1140
|
-
// persisting link attributes fix
|
|
1141
|
-
BOOL fixedLeadingAttributes = [linkStyle handleLeadingLinkReplacement:range replacementText:text];
|
|
1142
|
-
rejectTextChanges = rejectTextChanges || fixedLeadingAttributes;
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
1136
|
MentionStyle *mentionStyle = stylesDict[@([MentionStyle getStyleType])];
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
//
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1137
|
+
H1Style *h1Style = stylesDict[@([H1Style getStyleType])];
|
|
1138
|
+
H2Style *h2Style = stylesDict[@([H2Style getStyleType])];
|
|
1139
|
+
H3Style *h3Style = stylesDict[@([H3Style getStyleType])];
|
|
1140
|
+
|
|
1141
|
+
// some of the changes these checks do could interfere with later checks and cause a crash
|
|
1142
|
+
// so here I rely on short circuiting evaluation of the logical expression
|
|
1143
|
+
// either way it's not possible to have two of them come off at the same time
|
|
1144
|
+
if(
|
|
1145
|
+
[uStyle handleBackspaceInRange:range replacementText:text] ||
|
|
1146
|
+
[uStyle tryHandlingListShorcutInRange:range replacementText:text] ||
|
|
1147
|
+
[oStyle handleBackspaceInRange:range replacementText:text] ||
|
|
1148
|
+
[oStyle tryHandlingListShorcutInRange:range replacementText:text] ||
|
|
1149
|
+
[bqStyle handleBackspaceInRange:range replacementText:text] ||
|
|
1150
|
+
[linkStyle handleLeadingLinkReplacement:range replacementText:text] ||
|
|
1151
|
+
[mentionStyle handleLeadingMentionReplacement:range replacementText:text] ||
|
|
1152
|
+
[h1Style handleNewlinesInRange:range replacementText:text] ||
|
|
1153
|
+
[h2Style handleNewlinesInRange:range replacementText:text] ||
|
|
1154
|
+
[h3Style handleNewlinesInRange:range replacementText:text] ||
|
|
1155
|
+
[ZeroWidthSpaceUtils handleBackspaceInRange:range replacementText:text input:self] ||
|
|
1156
|
+
[ParagraphAttributesUtils handleBackspaceInRange:range replacementText:text input:self]
|
|
1157
|
+
) {
|
|
1156
1158
|
[self anyTextMayHaveBeenModified];
|
|
1159
|
+
return NO;
|
|
1157
1160
|
}
|
|
1158
|
-
|
|
1159
|
-
return
|
|
1161
|
+
|
|
1162
|
+
return YES;
|
|
1160
1163
|
}
|
|
1161
1164
|
|
|
1162
1165
|
- (void)textViewDidChangeSelection:(UITextView *)textView {
|
|
@@ -55,7 +55,10 @@ Size EnrichedTextInputViewShadowNode::measureContent(const LayoutContext& layout
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
return {
|
|
58
|
+
return {
|
|
59
|
+
estimatedSize.width,
|
|
60
|
+
MIN(estimatedSize.height, layoutConstraints.maximumSize.height)
|
|
61
|
+
};
|
|
59
62
|
}
|
|
60
63
|
} else {
|
|
61
64
|
// on the very first call there is no componentView that we can query for the component height
|
|
@@ -118,16 +118,22 @@
|
|
|
118
118
|
[self removeAttributes:_input->textView.selectedRange];
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
// removing first quote line by backspacing doesn't remove typing attributes because it doesn't run textViewDidChange
|
|
122
|
-
// so we try guessing that a point should be deleted here
|
|
123
121
|
- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
|
|
124
|
-
if([self detectStyle:_input->textView.selectedRange] &&
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
) {
|
|
122
|
+
if([self detectStyle:_input->textView.selectedRange] && text.length == 0) {
|
|
123
|
+
// backspace while the style is active
|
|
124
|
+
|
|
128
125
|
NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:_input->textView.selectedRange];
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
|
|
127
|
+
if(NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) {
|
|
128
|
+
// a backspace on the very first input's line quote
|
|
129
|
+
// it doesn't run textVieDidChange so we need to manually remove attributes
|
|
130
|
+
[self removeAttributes:paragraphRange];
|
|
131
|
+
return YES;
|
|
132
|
+
} else if(range.location == paragraphRange.location - 1) {
|
|
133
|
+
// same case in other lines; here, the removed range location will be exactly 1 less than paragraph range location
|
|
134
|
+
[self removeAttributes:paragraphRange];
|
|
135
|
+
return YES;
|
|
136
|
+
}
|
|
131
137
|
}
|
|
132
138
|
return NO;
|
|
133
139
|
}
|
|
@@ -145,22 +151,11 @@
|
|
|
145
151
|
}
|
|
146
152
|
];
|
|
147
153
|
} else {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
NSRange paragraphRange = NSMakeRange(0, 0);
|
|
155
|
-
NSRange inputRange = NSMakeRange(0, _input->textView.textStorage.length);
|
|
156
|
-
NSParagraphStyle *paragraph = [_input->textView.textStorage
|
|
157
|
-
attribute:NSParagraphStyleAttributeName
|
|
158
|
-
atIndex:searchLocation
|
|
159
|
-
longestEffectiveRange: ¶graphRange
|
|
160
|
-
inRange:inputRange
|
|
154
|
+
return [OccurenceUtils detect:NSParagraphStyleAttributeName withInput:_input atIndex:range.location checkPrevious:YES
|
|
155
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
156
|
+
return [self styleCondition:value :range];
|
|
157
|
+
}
|
|
161
158
|
];
|
|
162
|
-
|
|
163
|
-
return [self styleCondition:paragraph :NSMakeRange(0, 0)];
|
|
164
159
|
}
|
|
165
160
|
}
|
|
166
161
|
|
package/ios/styles/BoldStyle.mm
CHANGED
|
@@ -98,8 +98,11 @@
|
|
|
98
98
|
}
|
|
99
99
|
];
|
|
100
100
|
} else {
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
return [OccurenceUtils detect:NSFontAttributeName withInput:_input atIndex:range.location checkPrevious:NO
|
|
102
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
103
|
+
return [self styleCondition:value :range];
|
|
104
|
+
}
|
|
105
|
+
];
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#import "EnrichedTextInputView.h"
|
|
3
3
|
#import "FontExtension.h"
|
|
4
4
|
#import "OccurenceUtils.h"
|
|
5
|
+
#import "TextInsertionUtils.h"
|
|
5
6
|
|
|
6
7
|
@implementation HeadingStyleBase
|
|
7
8
|
|
|
@@ -116,11 +117,11 @@
|
|
|
116
117
|
}
|
|
117
118
|
];
|
|
118
119
|
} else {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
return [OccurenceUtils detect:NSFontAttributeName withInput:[self typedInput] atIndex:range.location checkPrevious:YES
|
|
121
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
122
|
+
return [self styleCondition:value :range];
|
|
123
|
+
}
|
|
124
|
+
];
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
|
|
@@ -140,5 +141,36 @@
|
|
|
140
141
|
];
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
// used to make sure headings dont persist after a newline is placed
|
|
145
|
+
- (BOOL)handleNewlinesInRange:(NSRange)range replacementText:(NSString *)text {
|
|
146
|
+
// in a heading and a new text ends with a newline
|
|
147
|
+
if(
|
|
148
|
+
[self detectStyle:[self typedInput]->textView.selectedRange] &&
|
|
149
|
+
text.length > 0 &&
|
|
150
|
+
[[NSCharacterSet newlineCharacterSet] characterIsMember: [text characterAtIndex:text.length-1]]
|
|
151
|
+
) {
|
|
152
|
+
// do the replacement manually
|
|
153
|
+
[TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:[self typedInput] withSelection:YES];
|
|
154
|
+
// remove the attribtues at the new selection
|
|
155
|
+
[self removeAttributes:[self typedInput]->textView.selectedRange];
|
|
156
|
+
return YES;
|
|
157
|
+
}
|
|
158
|
+
return NO;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// backspacing a line after a heading "into" a heading will not result in the text attaining heading attributes
|
|
162
|
+
// so, we do it manually
|
|
163
|
+
- (void)handleImproperHeadings {
|
|
164
|
+
NSArray *occurences = [self findAllOccurences:NSMakeRange(0, [self typedInput]->textView.textStorage.string.length)];
|
|
165
|
+
for(StylePair *pair in occurences) {
|
|
166
|
+
NSRange occurenceRange = [pair.rangeValue rangeValue];
|
|
167
|
+
NSRange paragraphRange = [[self typedInput]->textView.textStorage.string paragraphRangeForRange:occurenceRange];
|
|
168
|
+
if(!NSEqualRanges(occurenceRange, paragraphRange)) {
|
|
169
|
+
// we have a heading but it does not span its whole paragraph - let's fix it
|
|
170
|
+
[self addAttributes:paragraphRange];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
143
175
|
@end
|
|
144
176
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#import "FontExtension.h"
|
|
4
4
|
#import "OccurenceUtils.h"
|
|
5
5
|
#import "ParagraphsUtils.h"
|
|
6
|
+
#import "ColorExtension.h"
|
|
6
7
|
|
|
7
8
|
@implementation InlineCodeStyle {
|
|
8
9
|
EnrichedTextInputView *_input;
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
NSRange currentRange = [value rangeValue];
|
|
34
35
|
[_input->textView.textStorage beginEditing];
|
|
35
36
|
|
|
36
|
-
[_input->textView.textStorage addAttribute:NSBackgroundColorAttributeName value:[[_input->config inlineCodeBgColor]
|
|
37
|
+
[_input->textView.textStorage addAttribute:NSBackgroundColorAttributeName value:[[_input->config inlineCodeBgColor] colorWithAlphaIfNotTransparent:0.4] range:currentRange];
|
|
37
38
|
[_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:[_input->config inlineCodeFgColor] range:currentRange];
|
|
38
39
|
[_input->textView.textStorage addAttribute:NSUnderlineColorAttributeName value:[_input->config inlineCodeFgColor] range:currentRange];
|
|
39
40
|
[_input->textView.textStorage addAttribute:NSStrikethroughColorAttributeName value:[_input->config inlineCodeFgColor] range:currentRange];
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
|
|
42
43
|
UIFont *font = (UIFont *)value;
|
|
43
44
|
if(font != nullptr) {
|
|
44
|
-
UIFont *newFont = [[_input->config monospacedFont] withFontTraits:font];
|
|
45
|
+
UIFont *newFont = [[[_input->config monospacedFont] withFontTraits:font] setSize:font.pointSize];
|
|
45
46
|
[_input->textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range];
|
|
46
47
|
}
|
|
47
48
|
}
|
|
@@ -53,13 +54,13 @@
|
|
|
53
54
|
|
|
54
55
|
- (void)addTypingAttributes {
|
|
55
56
|
NSMutableDictionary *newTypingAttrs = [_input->textView.typingAttributes mutableCopy];
|
|
56
|
-
newTypingAttrs[NSBackgroundColorAttributeName] = [[_input->config inlineCodeBgColor]
|
|
57
|
+
newTypingAttrs[NSBackgroundColorAttributeName] = [[_input->config inlineCodeBgColor] colorWithAlphaIfNotTransparent:0.4];
|
|
57
58
|
newTypingAttrs[NSForegroundColorAttributeName] = [_input->config inlineCodeFgColor];
|
|
58
59
|
newTypingAttrs[NSUnderlineColorAttributeName] = [_input->config inlineCodeFgColor];
|
|
59
60
|
newTypingAttrs[NSStrikethroughColorAttributeName] = [_input->config inlineCodeFgColor];
|
|
60
61
|
UIFont* currentFont = (UIFont *)newTypingAttrs[NSFontAttributeName];
|
|
61
62
|
if(currentFont != nullptr) {
|
|
62
|
-
newTypingAttrs[NSFontAttributeName] = [[_input->config monospacedFont] withFontTraits:currentFont];
|
|
63
|
+
newTypingAttrs[NSFontAttributeName] = [[[_input->config monospacedFont] withFontTraits:currentFont] setSize:currentFont.pointSize];
|
|
63
64
|
}
|
|
64
65
|
_input->textView.typingAttributes = newTypingAttrs;
|
|
65
66
|
}
|
|
@@ -75,7 +76,7 @@
|
|
|
75
76
|
usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
|
|
76
77
|
UIFont *font = (UIFont *)value;
|
|
77
78
|
if(font != nullptr) {
|
|
78
|
-
UIFont *newFont = [[_input->config primaryFont] withFontTraits:font];
|
|
79
|
+
UIFont *newFont = [[[_input->config primaryFont] withFontTraits:font] setSize:font.pointSize];
|
|
79
80
|
[_input->textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range];
|
|
80
81
|
}
|
|
81
82
|
}
|
|
@@ -92,7 +93,7 @@
|
|
|
92
93
|
newTypingAttrs[NSStrikethroughColorAttributeName] = [_input->config primaryColor];
|
|
93
94
|
UIFont* currentFont = (UIFont *)newTypingAttrs[NSFontAttributeName];
|
|
94
95
|
if(currentFont != nullptr) {
|
|
95
|
-
newTypingAttrs[NSFontAttributeName] = [[_input->config primaryFont] withFontTraits:currentFont];
|
|
96
|
+
newTypingAttrs[NSFontAttributeName] = [[[_input->config primaryFont] withFontTraits:currentFont] setSize:currentFont.pointSize];
|
|
96
97
|
}
|
|
97
98
|
_input->textView.typingAttributes = newTypingAttrs;
|
|
98
99
|
}
|
|
@@ -139,8 +140,11 @@
|
|
|
139
140
|
|
|
140
141
|
return detected;
|
|
141
142
|
} else {
|
|
142
|
-
|
|
143
|
-
|
|
143
|
+
return [OccurenceUtils detect:NSBackgroundColorAttributeName withInput:_input atIndex:range.location checkPrevious:NO
|
|
144
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
145
|
+
return [self styleCondition:value :range];
|
|
146
|
+
}
|
|
147
|
+
];
|
|
144
148
|
}
|
|
145
149
|
}
|
|
146
150
|
|
|
@@ -83,11 +83,11 @@
|
|
|
83
83
|
}
|
|
84
84
|
];
|
|
85
85
|
} else {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
return [OccurenceUtils detect:NSFontAttributeName withInput:_input atIndex:range.location checkPrevious:NO
|
|
87
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
88
|
+
return [self styleCondition:value :range];
|
|
89
|
+
}
|
|
90
|
+
];
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#import "TextInsertionUtils.h"
|
|
5
5
|
#import "WordsUtils.h"
|
|
6
6
|
#import "UIView+React.h"
|
|
7
|
+
#import "ColorExtension.h"
|
|
7
8
|
|
|
8
9
|
// custom NSAttributedStringKey to differentiate from links
|
|
9
10
|
static NSString *const MentionAttributeName = @"MentionAttributeName";
|
|
@@ -154,7 +155,7 @@ static NSString *const MentionAttributeName = @"MentionAttributeName";
|
|
|
154
155
|
NSForegroundColorAttributeName: styleProps.color,
|
|
155
156
|
NSUnderlineColorAttributeName: styleProps.color,
|
|
156
157
|
NSStrikethroughColorAttributeName: styleProps.color,
|
|
157
|
-
NSBackgroundColorAttributeName: [styleProps.backgroundColor
|
|
158
|
+
NSBackgroundColorAttributeName: [styleProps.backgroundColor colorWithAlphaIfNotTransparent:0.4],
|
|
158
159
|
} mutableCopy];
|
|
159
160
|
|
|
160
161
|
if(styleProps.decorationLine == DecorationUnderline) {
|
|
@@ -186,7 +187,7 @@ static NSString *const MentionAttributeName = @"MentionAttributeName";
|
|
|
186
187
|
NSForegroundColorAttributeName: styleProps.color,
|
|
187
188
|
NSUnderlineColorAttributeName: styleProps.color,
|
|
188
189
|
NSStrikethroughColorAttributeName: styleProps.color,
|
|
189
|
-
NSBackgroundColorAttributeName: [styleProps.backgroundColor
|
|
190
|
+
NSBackgroundColorAttributeName: [styleProps.backgroundColor colorWithAlphaIfNotTransparent:0.4],
|
|
190
191
|
} mutableCopy];
|
|
191
192
|
|
|
192
193
|
if(styleProps.decorationLine == DecorationUnderline) {
|