react-native-enriched 0.1.3 → 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 +36 -648
- package/ReactNativeEnriched.podspec +1 -1
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +8 -5
- package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +5 -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 +5 -2
- package/ios/styles/ItalicStyle.mm +5 -5
- 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/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/ZeroWidthSpaceUtils.mm +14 -6
- 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)
|
|
@@ -233,7 +236,7 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
233
236
|
|
|
234
237
|
fun setValue(value: CharSequence?) {
|
|
235
238
|
if (value == null) return
|
|
236
|
-
|
|
239
|
+
isDuringTransaction = true
|
|
237
240
|
|
|
238
241
|
val newText = parseText(value)
|
|
239
242
|
setText(newText)
|
|
@@ -244,7 +247,7 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
244
247
|
// Scroll to the last line of text
|
|
245
248
|
setSelection(text?.length ?: 0)
|
|
246
249
|
|
|
247
|
-
|
|
250
|
+
isDuringTransaction = false
|
|
248
251
|
}
|
|
249
252
|
|
|
250
253
|
fun setAutoFocus(autoFocus: Boolean) {
|
|
@@ -452,13 +455,13 @@ class EnrichedTextInputView : AppCompatEditText {
|
|
|
452
455
|
val end = selection?.end ?: 0
|
|
453
456
|
val lengthBefore = text?.length ?: 0
|
|
454
457
|
|
|
455
|
-
|
|
458
|
+
isDuringTransaction = true
|
|
456
459
|
val targetRange = getTargetRange(name)
|
|
457
460
|
val removed = removeStyle(style, targetRange.first, targetRange.second)
|
|
458
461
|
if (removed) {
|
|
459
462
|
spanState?.setStart(style, null)
|
|
460
463
|
}
|
|
461
|
-
|
|
464
|
+
isDuringTransaction = false
|
|
462
465
|
|
|
463
466
|
val lengthAfter = text?.length ?: 0
|
|
464
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
|
|
@@ -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
|
|
|
@@ -140,8 +140,11 @@
|
|
|
140
140
|
|
|
141
141
|
return detected;
|
|
142
142
|
} else {
|
|
143
|
-
|
|
144
|
-
|
|
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
|
+
];
|
|
145
148
|
}
|
|
146
149
|
}
|
|
147
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
|
|
|
@@ -130,16 +130,22 @@
|
|
|
130
130
|
[self removeAttributes:_input->textView.selectedRange];
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
// removing first list point by backspacing doesn't remove typing attributes because it doesn't run textViewDidChange
|
|
134
|
-
// so we try guessing that a point should be deleted here
|
|
135
133
|
- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
|
|
136
|
-
if([self detectStyle:_input->textView.selectedRange] &&
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
) {
|
|
134
|
+
if([self detectStyle:_input->textView.selectedRange] && text.length == 0) {
|
|
135
|
+
// backspace while the style is active
|
|
136
|
+
|
|
140
137
|
NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:_input->textView.selectedRange];
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
|
|
139
|
+
if(NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) {
|
|
140
|
+
// a backspace on the very first input's line list point
|
|
141
|
+
// it doesn't run textVieDidChange so we need to manually remove attributes
|
|
142
|
+
[self removeAttributes:paragraphRange];
|
|
143
|
+
return YES;
|
|
144
|
+
} else if(range.location == paragraphRange.location - 1) {
|
|
145
|
+
// same case in other lines; here, the removed range location will be exactly 1 less than paragraph range location
|
|
146
|
+
[self removeAttributes:paragraphRange];
|
|
147
|
+
return YES;
|
|
148
|
+
}
|
|
143
149
|
}
|
|
144
150
|
return NO;
|
|
145
151
|
}
|
|
@@ -187,22 +193,11 @@
|
|
|
187
193
|
}
|
|
188
194
|
];
|
|
189
195
|
} else {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
NSRange paragraphRange = NSMakeRange(0, 0);
|
|
197
|
-
NSRange inputRange = NSMakeRange(0, _input->textView.textStorage.length);
|
|
198
|
-
NSParagraphStyle *paragraph = [_input->textView.textStorage
|
|
199
|
-
attribute:NSParagraphStyleAttributeName
|
|
200
|
-
atIndex:searchLocation
|
|
201
|
-
longestEffectiveRange: ¶graphRange
|
|
202
|
-
inRange:inputRange
|
|
196
|
+
return [OccurenceUtils detect:NSParagraphStyleAttributeName withInput:_input atIndex:range.location checkPrevious:YES
|
|
197
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
198
|
+
return [self styleCondition:value :range];
|
|
199
|
+
}
|
|
203
200
|
];
|
|
204
|
-
|
|
205
|
-
return [self styleCondition:paragraph :NSMakeRange(0, 0)];
|
|
206
201
|
}
|
|
207
202
|
}
|
|
208
203
|
|
|
@@ -56,8 +56,11 @@
|
|
|
56
56
|
}
|
|
57
57
|
];
|
|
58
58
|
} else {
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
return [OccurenceUtils detect:NSStrikethroughStyleAttributeName withInput:_input atIndex:range.location checkPrevious:NO
|
|
60
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
61
|
+
return [self styleCondition:value :range];
|
|
62
|
+
}
|
|
63
|
+
];
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
|
|
@@ -88,8 +88,11 @@
|
|
|
88
88
|
}
|
|
89
89
|
];
|
|
90
90
|
} else {
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
return [OccurenceUtils detect:NSUnderlineStyleAttributeName withInput:_input atIndex:range.location checkPrevious:NO
|
|
92
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
93
|
+
return [self styleCondition:value :range];
|
|
94
|
+
}
|
|
95
|
+
];
|
|
93
96
|
}
|
|
94
97
|
}
|
|
95
98
|
|
|
@@ -130,16 +130,22 @@
|
|
|
130
130
|
[self removeAttributes:_input->textView.selectedRange];
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
// removing first list point by backspacing doesn't remove typing attributes because it doesn't run textViewDidChange
|
|
134
|
-
// so we try guessing that a point should be deleted here
|
|
135
133
|
- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
|
|
136
|
-
if([self detectStyle:_input->textView.selectedRange] &&
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
) {
|
|
134
|
+
if([self detectStyle:_input->textView.selectedRange] && text.length == 0) {
|
|
135
|
+
// backspace while the style is active
|
|
136
|
+
|
|
140
137
|
NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:_input->textView.selectedRange];
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
|
|
139
|
+
if(NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) {
|
|
140
|
+
// a backspace on the very first input's line list point
|
|
141
|
+
// it doesn't run textVieDidChange so we need to manually remove attributes
|
|
142
|
+
[self removeAttributes:paragraphRange];
|
|
143
|
+
return YES;
|
|
144
|
+
} else if(range.location == paragraphRange.location - 1) {
|
|
145
|
+
// same case in other lines; here, the removed range location will be exactly 1 less than paragraph range location
|
|
146
|
+
[self removeAttributes:paragraphRange];
|
|
147
|
+
return YES;
|
|
148
|
+
}
|
|
143
149
|
}
|
|
144
150
|
return NO;
|
|
145
151
|
}
|
|
@@ -187,22 +193,11 @@
|
|
|
187
193
|
}
|
|
188
194
|
];
|
|
189
195
|
} else {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
NSRange paragraphRange = NSMakeRange(0, 0);
|
|
197
|
-
NSRange inputRange = NSMakeRange(0, _input->textView.textStorage.length);
|
|
198
|
-
NSParagraphStyle *paragraph = [_input->textView.textStorage
|
|
199
|
-
attribute:NSParagraphStyleAttributeName
|
|
200
|
-
atIndex:searchLocation
|
|
201
|
-
longestEffectiveRange: ¶graphRange
|
|
202
|
-
inRange:inputRange
|
|
196
|
+
return [OccurenceUtils detect:NSParagraphStyleAttributeName withInput:_input atIndex:range.location checkPrevious:YES
|
|
197
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
198
|
+
return [self styleCondition:value :range];
|
|
199
|
+
}
|
|
203
200
|
];
|
|
204
|
-
|
|
205
|
-
return [self styleCondition:paragraph :NSMakeRange(0, 0)];
|
|
206
201
|
}
|
|
207
202
|
}
|
|
208
203
|
|