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.
Files changed (32) hide show
  1. package/README.md +49 -658
  2. package/ReactNativeEnriched.podspec +1 -1
  3. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +11 -6
  4. package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +5 -0
  5. package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +2 -0
  6. package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +3 -0
  7. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +1 -1
  8. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +2 -2
  9. package/android/src/main/new_arch/CMakeLists.txt +13 -9
  10. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +2 -2
  11. package/ios/EnrichedTextInputView.mm +56 -53
  12. package/ios/internals/EnrichedTextInputViewShadowNode.mm +4 -1
  13. package/ios/styles/BlockQuoteStyle.mm +18 -23
  14. package/ios/styles/BoldStyle.mm +5 -2
  15. package/ios/styles/HeadingStyleBase.mm +37 -5
  16. package/ios/styles/InlineCodeStyle.mm +12 -8
  17. package/ios/styles/ItalicStyle.mm +5 -5
  18. package/ios/styles/MentionStyle.mm +3 -2
  19. package/ios/styles/OrderedListStyle.mm +18 -23
  20. package/ios/styles/StrikethroughStyle.mm +5 -2
  21. package/ios/styles/UnderlineStyle.mm +5 -2
  22. package/ios/styles/UnorderedListStyle.mm +18 -23
  23. package/ios/utils/ColorExtension.h +1 -0
  24. package/ios/utils/ColorExtension.mm +9 -0
  25. package/ios/utils/OccurenceUtils.h +6 -0
  26. package/ios/utils/OccurenceUtils.mm +31 -0
  27. package/ios/utils/ParagraphAttributesUtils.h +6 -0
  28. package/ios/utils/ParagraphAttributesUtils.mm +67 -0
  29. package/ios/utils/StyleHeaders.h +2 -0
  30. package/ios/utils/TextInsertionUtils.mm +2 -2
  31. package/ios/utils/ZeroWidthSpaceUtils.mm +16 -10
  32. package/package.json +4 -4
@@ -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
- NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0)) &&
138
- [text isEqualToString:@""]
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
- [self removeAttributes:paragraphRange];
142
- return YES;
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
- NSInteger searchLocation = range.location;
191
- if(searchLocation == _input->textView.textStorage.length) {
192
- NSParagraphStyle *pStyle = _input->textView.typingAttributes[NSParagraphStyleAttributeName];
193
- return [self styleCondition:pStyle :NSMakeRange(0, 0)];
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: &paragraphRange
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
- NSNumber *currenStrikethroughAttr = (NSNumber *)_input->textView.typingAttributes[NSStrikethroughStyleAttributeName];
60
- return currenStrikethroughAttr != nullptr;
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
- NSNumber *currentUnderlineAttr = (NSNumber *)_input->textView.typingAttributes[NSUnderlineStyleAttributeName];
92
- return [self styleCondition:currentUnderlineAttr :range];
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
- NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0)) &&
138
- [text isEqualToString:@""]
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
- [self removeAttributes:paragraphRange];
142
- return YES;
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
- NSInteger searchLocation = range.location;
191
- if(searchLocation == _input->textView.textStorage.length) {
192
- NSParagraphStyle *pStyle = _input->textView.typingAttributes[NSParagraphStyleAttributeName];
193
- return [self styleCondition:pStyle :NSMakeRange(0, 0)];
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: &paragraphRange
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
 
@@ -3,4 +3,5 @@
3
3
 
4
4
  @interface UIColor (ColorExtension)
5
5
  - (BOOL)isEqualToColor:(UIColor *)otherColor;
6
+ - (UIColor *)colorWithAlphaIfNotTransparent:(CGFloat)newAlpha;
6
7
  @end
@@ -24,4 +24,13 @@
24
24
 
25
25
  return [selfColor isEqual:otherColor];
26
26
  }
27
+
28
+ - (UIColor *)colorWithAlphaIfNotTransparent:(CGFloat)newAlpha {
29
+ CGFloat alpha = 0.0;
30
+ [self getRed:nil green:nil blue:nil alpha:&alpha];
31
+ if (alpha > 0.0) {
32
+ return [self colorWithAlphaComponent:newAlpha];
33
+ }
34
+ return self;
35
+ }
27
36
  @end
@@ -9,6 +9,12 @@
9
9
  withInput:(EnrichedTextInputView* _Nonnull)input
10
10
  inRange:(NSRange)range
11
11
  withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
12
+ + (BOOL)detect
13
+ :(NSAttributedStringKey _Nonnull)key
14
+ withInput:(EnrichedTextInputView* _Nonnull)input
15
+ atIndex:(NSUInteger)index
16
+ checkPrevious:(BOOL)check
17
+ withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
12
18
  + (BOOL)detectMultiple
13
19
  :(NSArray<NSAttributedStringKey> *_Nonnull)keys
14
20
  withInput:(EnrichedTextInputView* _Nonnull)input
@@ -19,6 +19,37 @@
19
19
  return totalLength == range.length;
20
20
  }
21
21
 
22
+ // checkPrevious flag is used for styles like lists or blockquotes
23
+ // it means that first character of paragraph will be checked instead if the detection is not in input's selected range and at the end of the input
24
+ + (BOOL)detect
25
+ :(NSAttributedStringKey _Nonnull)key
26
+ withInput:(EnrichedTextInputView* _Nonnull)input
27
+ atIndex:(NSUInteger)index
28
+ checkPrevious:(BOOL)checkPrev
29
+ withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition
30
+ {
31
+ NSRange detectionRange = NSMakeRange(index, 0);
32
+ id attrValue;
33
+ if(NSEqualRanges(input->textView.selectedRange, detectionRange)) {
34
+ attrValue = input->textView.typingAttributes[key];
35
+ } else if(index == input->textView.textStorage.string.length) {
36
+ if(checkPrev) {
37
+ NSRange paragraphRange = [input->textView.textStorage.string paragraphRangeForRange:detectionRange];
38
+ if(paragraphRange.location == detectionRange.location) {
39
+ return NO;
40
+ } else {
41
+ return [self detect:key withInput:input inRange:NSMakeRange(paragraphRange.location, 1) withCondition:condition];
42
+ }
43
+ } else {
44
+ return NO;
45
+ }
46
+ } else {
47
+ NSRange attrRange = NSMakeRange(0, 0);
48
+ attrValue = [input->textView.textStorage attribute:key atIndex:index effectiveRange:&attrRange];
49
+ }
50
+ return condition(attrValue, detectionRange);
51
+ }
52
+
22
53
  + (BOOL)detectMultiple
23
54
  :(NSArray<NSAttributedStringKey> *_Nonnull)keys
24
55
  withInput:(EnrichedTextInputView* _Nonnull)input
@@ -0,0 +1,6 @@
1
+ #import <UIKit/UIKit.h>
2
+ #pragma once
3
+
4
+ @interface ParagraphAttributesUtils : NSObject
5
+ + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text input:(id)input;
6
+ @end
@@ -0,0 +1,67 @@
1
+ #import "ParagraphAttributesUtils.h"
2
+ #import "EnrichedTextInputView.h"
3
+ #import "StyleHeaders.h"
4
+ #import "ParagraphsUtils.h"
5
+ #import "TextInsertionUtils.h"
6
+
7
+ @implementation ParagraphAttributesUtils
8
+
9
+ // if the user backspaces the last character in a line, the iOS applies typing attributes from the previous line
10
+ // in the case of some paragraph styles it works especially bad when a list point just appears
11
+ // hence the solution - reset typing attributes
12
+ + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text input:(id)input {
13
+ EnrichedTextInputView *typedInput = (EnrichedTextInputView *)input;
14
+ UnorderedListStyle *ulStyle = typedInput->stylesDict[@([UnorderedListStyle getStyleType])];
15
+ OrderedListStyle *olStyle = typedInput->stylesDict[@([OrderedListStyle getStyleType])];
16
+ BlockQuoteStyle *bqStyle = typedInput->stylesDict[@([BlockQuoteStyle getStyleType])];
17
+
18
+ if(typedInput == nullptr) {
19
+ return NO;
20
+ }
21
+
22
+ // we make sure it was a backspace (text with 0 length) and it deleted something (range longer than 0)
23
+ if(text.length > 0 || range.length == 0) {
24
+ return NO;
25
+ }
26
+
27
+ // find a non-newline range of the paragraph
28
+ NSRange paragraphRange = [typedInput->textView.textStorage.string paragraphRangeForRange:range];
29
+
30
+ NSArray *paragraphs = [ParagraphsUtils getNonNewlineRangesIn:typedInput->textView range:paragraphRange];
31
+ if(paragraphs.count == 0) {
32
+ return NO;
33
+ }
34
+
35
+ NSRange nonNewlineRange = [(NSValue *)paragraphs.firstObject rangeValue];
36
+
37
+ // if the backspace removes the whole content of a paragraph (possibly more but has to start where the paragraph starts), we remove the typing attributes
38
+ if(range.location == nonNewlineRange.location && range.length >= nonNewlineRange.length) {
39
+ // for lists and quotes we want to remove the characters but keep attribtues so that a zero width space appears here
40
+ // so we do the removing manually and reapply attributes
41
+ if([ulStyle detectStyle:nonNewlineRange]) {
42
+ [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES];
43
+ [ulStyle addAttributes:NSMakeRange(range.location, 0)];
44
+ return YES;
45
+ }
46
+ if([olStyle detectStyle:nonNewlineRange]) {
47
+ [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES];
48
+ [olStyle addAttributes:NSMakeRange(range.location, 0)];
49
+ return YES;
50
+ }
51
+ if([bqStyle detectStyle:nonNewlineRange]) {
52
+ [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES];
53
+ [bqStyle addAttributes:NSMakeRange(range.location, 0)];
54
+ return YES;
55
+ }
56
+
57
+ // do the replacement manually
58
+ [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES];
59
+ // reset typing attribtues
60
+ typedInput->textView.typingAttributes = typedInput->defaultTypingAttributes;
61
+ return YES;
62
+ }
63
+
64
+ return NO;
65
+ }
66
+
67
+ @end
@@ -47,6 +47,8 @@
47
47
  }
48
48
  - (CGFloat)getHeadingFontSize;
49
49
  - (BOOL)isHeadingBold;
50
+ - (BOOL)handleNewlinesInRange:(NSRange)range replacementText:(NSString *)text;
51
+ - (void)handleImproperHeadings;
50
52
  @end
51
53
 
52
54
  @interface H1Style : HeadingStyleBase
@@ -18,7 +18,7 @@
18
18
  [textView.textStorage insertAttributedString:newAttrStr atIndex:index];
19
19
 
20
20
  if(withSelection) {
21
- if(!textView.focused) {
21
+ if(![textView isFirstResponder]) {
22
22
  [textView reactFocus];
23
23
  }
24
24
  textView.selectedRange = NSMakeRange(index + text.length, 0);
@@ -38,7 +38,7 @@
38
38
  }
39
39
 
40
40
  if(withSelection) {
41
- if(!textView.focused) {
41
+ if(![textView isFirstResponder]) {
42
42
  [textView reactFocus];
43
43
  }
44
44
  textView.selectedRange = NSMakeRange(range.location + text.length, 0);
@@ -52,20 +52,23 @@
52
52
 
53
53
  // do the removing
54
54
  NSInteger offset = 0;
55
- NSInteger postRemoveOffset = 0;
55
+ NSInteger postRemoveLocationOffset = 0;
56
+ NSInteger postRemoveLengthOffset = 0;
56
57
  for(NSNumber *index in indexesToBeRemoved) {
57
58
  NSRange replaceRange = NSMakeRange([index integerValue] + offset, 1);
58
59
  [TextInsertionUtils replaceText:@"" at:replaceRange additionalAttributes:nullptr input:input withSelection:NO];
59
60
  offset -= 1;
60
61
  if([index integerValue] < preRemoveSelection.location) {
61
- postRemoveOffset -= 1;
62
+ postRemoveLocationOffset -= 1;
63
+ }
64
+ if([index integerValue] >= preRemoveSelection.location && [index integerValue] < NSMaxRange(preRemoveSelection)) {
65
+ postRemoveLengthOffset -= 1;
62
66
  }
63
67
  }
64
68
 
65
69
  // fix the selection if needed
66
- if(input->textView.focused) {
67
- [input->textView reactFocus];
68
- input->textView.selectedRange = NSMakeRange(preRemoveSelection.location + postRemoveOffset, preRemoveSelection.length);
70
+ if([input->textView isFirstResponder]) {
71
+ input->textView.selectedRange = NSMakeRange(preRemoveSelection.location + postRemoveLocationOffset, preRemoveSelection.length + postRemoveLengthOffset);
69
72
  }
70
73
  }
71
74
 
@@ -94,13 +97,17 @@
94
97
 
95
98
  // do the replacing
96
99
  NSInteger offset = 0;
97
- NSInteger postAddOffset = 0;
100
+ NSInteger postAddLocationOffset = 0;
101
+ NSInteger postAddLengthOffset = 0;
98
102
  for(NSNumber *index in indexesToBeInserted) {
99
103
  NSRange replaceRange = NSMakeRange([index integerValue] + offset, 1);
100
104
  [TextInsertionUtils replaceText:@"\u200B\n" at:replaceRange additionalAttributes:nullptr input:input withSelection:NO];
101
105
  offset += 1;
102
106
  if([index integerValue] < preAddSelection.location) {
103
- postAddOffset += 1;
107
+ postAddLocationOffset += 1;
108
+ }
109
+ if([index integerValue] >= preAddSelection.location && [index integerValue] < NSMaxRange(preAddSelection)) {
110
+ postAddLengthOffset += 1;
104
111
  }
105
112
  }
106
113
 
@@ -112,9 +119,8 @@
112
119
  }
113
120
 
114
121
  // fix the selection if needed
115
- if(input->textView.focused) {
116
- [input->textView reactFocus];
117
- input->textView.selectedRange = NSMakeRange(preAddSelection.location + postAddOffset, preAddSelection.length);
122
+ if([input->textView isFirstResponder]) {
123
+ input->textView.selectedRange = NSMakeRange(preAddSelection.location + postAddLocationOffset, preAddSelection.length + postAddLengthOffset);
118
124
  }
119
125
  }
120
126
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-enriched",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Rich Text Editor component for React Native",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/module/index.js",
@@ -48,14 +48,14 @@
48
48
  ],
49
49
  "repository": {
50
50
  "type": "git",
51
- "url": "git+https://github.com/software-mansion-labs/react-native-enriched.git"
51
+ "url": "git+https://github.com/software-mansion/react-native-enriched.git"
52
52
  },
53
53
  "author": "Software Mansion <contact@swmansion.com> (https://swmansion.com)",
54
54
  "license": "MIT",
55
55
  "bugs": {
56
- "url": "https://github.com/software-mansion-labs/react-native-enriched/issues"
56
+ "url": "https://github.com/software-mansion/react-native-enriched/issues"
57
57
  },
58
- "homepage": "https://github.com/software-mansion-labs/react-native-enriched#readme",
58
+ "homepage": "https://github.com/software-mansion/react-native-enriched#readme",
59
59
  "publishConfig": {
60
60
  "registry": "https://registry.npmjs.org/"
61
61
  },