react-native-enriched 0.1.6 → 0.2.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.
Files changed (68) hide show
  1. package/README.md +3 -9
  2. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +1 -1
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +1 -1
  4. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +0 -45
  5. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +19 -2
  6. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +3 -3
  7. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +2 -0
  8. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +36 -1
  9. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +132 -11
  10. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +4 -0
  11. package/android/src/main/java/com/swmansion/enriched/spans/utils/ForceRedrawSpan.kt +13 -0
  12. package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +2 -9
  13. package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +1 -0
  14. package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +110 -3
  15. package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +57 -30
  16. package/android/src/main/java/com/swmansion/enriched/utils/AsyncDrawable.kt +91 -0
  17. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +16 -13
  18. package/android/src/main/java/com/swmansion/enriched/utils/ResourceManager.kt +26 -0
  19. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +3 -0
  20. package/android/src/main/res/drawable/broken_image.xml +10 -0
  21. package/ios/EnrichedTextInputView.h +3 -0
  22. package/ios/EnrichedTextInputView.mm +97 -29
  23. package/ios/config/InputConfig.h +6 -0
  24. package/ios/config/InputConfig.mm +32 -0
  25. package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +0 -45
  26. package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +20 -4
  27. package/ios/inputParser/InputParser.mm +147 -24
  28. package/ios/internals/EnrichedTextInputViewShadowNode.h +1 -0
  29. package/ios/internals/EnrichedTextInputViewShadowNode.mm +29 -17
  30. package/ios/styles/BlockQuoteStyle.mm +5 -26
  31. package/ios/styles/BoldStyle.mm +2 -0
  32. package/ios/styles/CodeBlockStyle.mm +228 -0
  33. package/ios/styles/H1Style.mm +1 -0
  34. package/ios/styles/H2Style.mm +1 -0
  35. package/ios/styles/H3Style.mm +1 -0
  36. package/ios/styles/ImageStyle.mm +158 -0
  37. package/ios/styles/InlineCodeStyle.mm +2 -0
  38. package/ios/styles/ItalicStyle.mm +2 -0
  39. package/ios/styles/LinkStyle.mm +8 -0
  40. package/ios/styles/MentionStyle.mm +133 -36
  41. package/ios/styles/OrderedListStyle.mm +2 -0
  42. package/ios/styles/StrikethroughStyle.mm +2 -0
  43. package/ios/styles/UnderlineStyle.mm +2 -0
  44. package/ios/styles/UnorderedListStyle.mm +2 -0
  45. package/ios/utils/BaseStyleProtocol.h +1 -0
  46. package/ios/utils/ImageData.h +10 -0
  47. package/ios/utils/ImageData.mm +4 -0
  48. package/ios/utils/LayoutManagerExtension.mm +118 -3
  49. package/ios/utils/OccurenceUtils.h +4 -0
  50. package/ios/utils/OccurenceUtils.mm +47 -0
  51. package/ios/utils/ParagraphAttributesUtils.h +1 -0
  52. package/ios/utils/ParagraphAttributesUtils.mm +87 -20
  53. package/ios/utils/StyleHeaders.h +12 -0
  54. package/ios/utils/ZeroWidthSpaceUtils.mm +22 -10
  55. package/lib/module/EnrichedTextInput.js +2 -2
  56. package/lib/module/EnrichedTextInput.js.map +1 -1
  57. package/lib/module/EnrichedTextInputNativeComponent.ts +6 -5
  58. package/lib/module/normalizeHtmlStyle.js +0 -4
  59. package/lib/module/normalizeHtmlStyle.js.map +1 -1
  60. package/lib/typescript/src/EnrichedTextInput.d.ts +1 -5
  61. package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -1
  62. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +1 -5
  63. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -1
  64. package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +1 -1
  65. package/package.json +1 -1
  66. package/src/EnrichedTextInput.tsx +3 -7
  67. package/src/EnrichedTextInputNativeComponent.ts +6 -5
  68. package/src/normalizeHtmlStyle.ts +0 -4
@@ -0,0 +1,228 @@
1
+ #import "StyleHeaders.h"
2
+ #import "EnrichedTextInputView.h"
3
+ #import "FontExtension.h"
4
+ #import "OccurenceUtils.h"
5
+ #import "ParagraphsUtils.h"
6
+ #import "TextInsertionUtils.h"
7
+ #import "ColorExtension.h"
8
+
9
+ @implementation CodeBlockStyle {
10
+ EnrichedTextInputView *_input;
11
+ NSArray *_stylesToExclude;
12
+ }
13
+
14
+ + (StyleType)getStyleType { return CodeBlock; }
15
+
16
+ + (BOOL)isParagraphStyle { return YES; }
17
+
18
+ - (instancetype)initWithInput:(id)input {
19
+ self = [super init];
20
+ _input = (EnrichedTextInputView *)input;
21
+ _stylesToExclude = @[ @(InlineCode), @(Mention), @(Link) ];
22
+ return self;
23
+ }
24
+
25
+ - (void)applyStyle:(NSRange)range {
26
+ BOOL isStylePresent = [self detectStyle:range];
27
+ if(range.length >= 1) {
28
+ isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
29
+ } else {
30
+ isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
31
+ }
32
+ }
33
+
34
+ - (void)addAttributes:(NSRange)range {
35
+ NSTextList *codeBlockList = [[NSTextList alloc] initWithMarkerFormat:@"codeblock" options:0];
36
+ NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:range];
37
+ // if we fill empty lines with zero width spaces, we need to offset later ranges
38
+ NSInteger offset = 0;
39
+ NSRange preModificationRange = _input->textView.selectedRange;
40
+
41
+ // to not emit any space filling selection/text changes
42
+ _input->blockEmitting = YES;
43
+
44
+ for (NSValue *value in paragraphs) {
45
+ NSRange pRange = NSMakeRange([value rangeValue].location + offset, [value rangeValue].length);
46
+ // length 0 with first line, length 1 and newline with some empty lines in the middle
47
+ if(pRange.length == 0 ||
48
+ (pRange.length == 1 &&
49
+ [[NSCharacterSet newlineCharacterSet] characterIsMember: [_input->textView.textStorage.string characterAtIndex:pRange.location]])
50
+ ) {
51
+ [TextInsertionUtils insertText:@"\u200B" at:pRange.location additionalAttributes:nullptr input:_input withSelection:NO];
52
+ pRange = NSMakeRange(pRange.location, pRange.length + 1);
53
+ offset += 1;
54
+ }
55
+
56
+ [_input->textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0
57
+ usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
58
+ NSMutableParagraphStyle *pStyle = [(NSParagraphStyle *)value mutableCopy];
59
+ pStyle.textLists = @[codeBlockList];
60
+ [_input->textView.textStorage addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
61
+ }
62
+ ];
63
+ }
64
+
65
+ // back to emitting
66
+ _input->blockEmitting = NO;
67
+
68
+ if(preModificationRange.length == 0) {
69
+ // fix selection if only one line was possibly made a list and filled with a space
70
+ _input->textView.selectedRange = preModificationRange;
71
+ } else {
72
+ // in other cases, fix the selection with newly made offsets
73
+ _input->textView.selectedRange = NSMakeRange(preModificationRange.location, preModificationRange.length + offset);
74
+ }
75
+
76
+ // also add typing attributes
77
+ NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy];
78
+ NSMutableParagraphStyle *pStyle = [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
79
+ pStyle.textLists = @[codeBlockList];
80
+ typingAttrs[NSParagraphStyleAttributeName] = pStyle;
81
+
82
+ _input->textView.typingAttributes = typingAttrs;
83
+ }
84
+
85
+ - (void)addTypingAttributes {
86
+ [self addAttributes:_input->textView.selectedRange];
87
+ }
88
+
89
+ - (void)removeAttributes:(NSRange)range {
90
+ NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:range];
91
+
92
+ [_input->textView.textStorage beginEditing];
93
+
94
+ for(NSValue *value in paragraphs) {
95
+ NSRange pRange = [value rangeValue];
96
+
97
+ [_input->textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0
98
+ usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
99
+ NSMutableParagraphStyle *pStyle = [(NSParagraphStyle *)value mutableCopy];
100
+ pStyle.textLists = @[];
101
+ [_input->textView.textStorage addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
102
+ }
103
+ ];
104
+ }
105
+
106
+ [_input->textView.textStorage endEditing];
107
+
108
+ // also remove typing attributes
109
+ NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy];
110
+ NSMutableParagraphStyle *pStyle = [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
111
+ pStyle.textLists = @[];
112
+
113
+ typingAttrs[NSParagraphStyleAttributeName] = pStyle;
114
+
115
+ _input->textView.typingAttributes = typingAttrs;
116
+ }
117
+
118
+ - (void)removeTypingAttributes {
119
+ [self removeAttributes:_input->textView.selectedRange];
120
+ }
121
+
122
+ - (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
123
+ if([self detectStyle:_input->textView.selectedRange] && text.length == 0) {
124
+ // backspace while the style is active
125
+
126
+ NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:_input->textView.selectedRange];
127
+
128
+ if(NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) {
129
+ // a backspace on the very first input's line quote
130
+ // it doesn't run textVieDidChange so we need to manually remove attributes
131
+ [self removeAttributes:paragraphRange];
132
+ return YES;
133
+ }
134
+ }
135
+ return NO;
136
+ }
137
+
138
+ - (BOOL)styleCondition:(id _Nullable)value :(NSRange)range {
139
+ NSParagraphStyle *paragraph = (NSParagraphStyle *)value;
140
+ return paragraph != nullptr && paragraph.textLists.count == 1 && [paragraph.textLists.firstObject.markerFormat isEqualToString:@"codeblock"];
141
+ }
142
+
143
+ - (BOOL)detectStyle:(NSRange)range {
144
+ if(range.length >= 1) {
145
+ return [OccurenceUtils detect:NSParagraphStyleAttributeName withInput:_input inRange:range
146
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
147
+ return [self styleCondition:value :range];
148
+ }
149
+ ];
150
+ } else {
151
+ return [OccurenceUtils detect:NSParagraphStyleAttributeName withInput:_input atIndex:range.location checkPrevious:YES
152
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
153
+ return [self styleCondition:value :range];
154
+ }
155
+ ];
156
+ }
157
+ }
158
+
159
+ - (BOOL)anyOccurence:(NSRange)range {
160
+ return [OccurenceUtils any:NSParagraphStyleAttributeName withInput:_input inRange:range
161
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
162
+ return [self styleCondition:value :range];
163
+ }
164
+ ];
165
+ }
166
+
167
+ - (NSArray<StylePair *> *_Nullable)findAllOccurences:(NSRange)range {
168
+ return [OccurenceUtils all:NSParagraphStyleAttributeName withInput:_input inRange:range
169
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
170
+ return [self styleCondition:value :range];
171
+ }
172
+ ];
173
+ }
174
+
175
+ - (void)manageCodeBlockFontAndColor {
176
+ if([[_input->config codeBlockFgColor] isEqualToColor:[_input->config primaryColor]]) {
177
+ return;
178
+ }
179
+
180
+ NSRange wholeRange = NSMakeRange(0, _input->textView.textStorage.string.length);
181
+ NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:wholeRange];
182
+
183
+ for(NSValue *pValue in paragraphs) {
184
+ NSRange paragraphRange = [pValue rangeValue];
185
+ NSArray *properRanges = [OccurenceUtils getRangesWithout:_stylesToExclude withInput:_input inRange:paragraphRange];
186
+
187
+ for(NSValue *value in properRanges) {
188
+ NSRange currRange = [value rangeValue];
189
+ BOOL selfDetected = [self detectStyle:currRange];
190
+
191
+ [_input->textView.textStorage enumerateAttribute:NSFontAttributeName inRange:currRange options:0
192
+ usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
193
+ UIFont *currentFont = (UIFont *)value;
194
+ UIFont *newFont = nullptr;
195
+
196
+ BOOL isCodeFont = [[currentFont familyName] isEqualToString:[[_input->config monospacedFont] familyName]];
197
+
198
+ if (isCodeFont && !selfDetected) {
199
+ newFont = [[[_input->config primaryFont] withFontTraits:currentFont] setSize:currentFont.pointSize];
200
+ } else if (!isCodeFont && selfDetected) {
201
+ newFont = [[[_input->config monospacedFont] withFontTraits:currentFont] setSize:currentFont.pointSize];
202
+ }
203
+
204
+ if (newFont != nullptr) {
205
+ [_input->textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range];
206
+ }
207
+ }];
208
+
209
+ [_input->textView.textStorage enumerateAttribute:NSForegroundColorAttributeName inRange:currRange options:0
210
+ usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
211
+ UIColor *newColor = nullptr;
212
+ BOOL colorApplied = [(UIColor *)value isEqualToColor:[_input->config codeBlockFgColor]];
213
+
214
+ if(colorApplied && !selfDetected) {
215
+ newColor = [_input->config primaryColor];
216
+ } else if(!colorApplied && selfDetected) {
217
+ newColor = [_input->config codeBlockFgColor];
218
+ }
219
+
220
+ if(newColor != nullptr) {
221
+ [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:newColor range:range];
222
+ }
223
+ }];
224
+ }
225
+ }
226
+ }
227
+
228
+ @end
@@ -3,6 +3,7 @@
3
3
 
4
4
  @implementation H1Style
5
5
  + (StyleType)getStyleType { return H1; }
6
+ + (BOOL)isParagraphStyle { return YES; }
6
7
  - (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h1FontSize]; }
7
8
  - (BOOL)isHeadingBold {
8
9
  return [((EnrichedTextInputView *)input)->config h1Bold];
@@ -3,6 +3,7 @@
3
3
 
4
4
  @implementation H2Style
5
5
  + (StyleType)getStyleType { return H2; }
6
+ + (BOOL)isParagraphStyle { return YES; }
6
7
  - (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h2FontSize]; }
7
8
  - (BOOL)isHeadingBold {
8
9
  return [((EnrichedTextInputView *)input)->config h2Bold];
@@ -3,6 +3,7 @@
3
3
 
4
4
  @implementation H3Style
5
5
  + (StyleType)getStyleType { return H3; }
6
+ + (BOOL)isParagraphStyle { return YES; }
6
7
  - (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h3FontSize]; }
7
8
  - (BOOL)isHeadingBold {
8
9
  return [((EnrichedTextInputView *)input)->config h3Bold];
@@ -0,0 +1,158 @@
1
+ #import "StyleHeaders.h"
2
+ #import "EnrichedTextInputView.h"
3
+ #import "OccurenceUtils.h"
4
+ #import "TextInsertionUtils.h"
5
+
6
+ // custom NSAttributedStringKey to differentiate the image
7
+ static NSString *const ImageAttributeName = @"ImageAttributeName";
8
+
9
+ @implementation ImageStyle {
10
+ EnrichedTextInputView *_input;
11
+ }
12
+
13
+ + (StyleType)getStyleType { return Image; }
14
+
15
+ + (BOOL)isParagraphStyle { return NO; }
16
+
17
+ - (instancetype)initWithInput:(id)input {
18
+ self = [super init];
19
+ _input = (EnrichedTextInputView *)input;
20
+ return self;
21
+ }
22
+
23
+ - (void)applyStyle:(NSRange)range {
24
+ // no-op for image
25
+ }
26
+
27
+ - (void)addAttributes:(NSRange)range {
28
+ // no-op for image
29
+ }
30
+
31
+ - (void)addTypingAttributes {
32
+ // no-op for image
33
+ }
34
+
35
+ - (void)removeAttributes:(NSRange)range {
36
+ [_input->textView.textStorage beginEditing];
37
+ [_input->textView.textStorage removeAttribute:ImageAttributeName range:range];
38
+ [_input->textView.textStorage removeAttribute:NSAttachmentAttributeName range:range];
39
+ [_input->textView.textStorage endEditing];
40
+ }
41
+
42
+ - (void)removeTypingAttributes {
43
+ NSMutableDictionary *currentAttributes = [_input->textView.typingAttributes mutableCopy];
44
+ [currentAttributes removeObjectForKey:ImageAttributeName];
45
+ [currentAttributes removeObjectForKey:NSAttachmentAttributeName];
46
+ _input->textView.typingAttributes = currentAttributes;
47
+ }
48
+
49
+ - (BOOL)styleCondition:(id _Nullable)value :(NSRange)range {
50
+ return [value isKindOfClass:[ImageData class]];
51
+ }
52
+
53
+ - (BOOL)anyOccurence:(NSRange)range {
54
+ return [OccurenceUtils any:ImageAttributeName withInput:_input inRange:range
55
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
56
+ return [self styleCondition:value :range];
57
+ }
58
+ ];
59
+ }
60
+
61
+ - (BOOL)detectStyle:(NSRange)range {
62
+ if (range.length >= 1) {
63
+ return [OccurenceUtils detect:ImageAttributeName withInput:_input inRange:range
64
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
65
+ return [self styleCondition:value :range];
66
+ }
67
+ ];
68
+ } else {
69
+ return [OccurenceUtils detect:ImageAttributeName withInput:_input atIndex:range.location checkPrevious:YES
70
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
71
+ return [self styleCondition:value :range];
72
+ }
73
+ ];
74
+ }
75
+ }
76
+
77
+ - (NSArray<StylePair *> * _Nullable)findAllOccurences:(NSRange)range {
78
+ return [OccurenceUtils all:ImageAttributeName withInput:_input inRange:range
79
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
80
+ return [self styleCondition:value :range];
81
+ }
82
+ ];
83
+ }
84
+
85
+ - (ImageData *)getImageDataAt:(NSUInteger)location
86
+ {
87
+ NSRange imageRange = NSMakeRange(0, 0);
88
+ NSRange inputRange = NSMakeRange(0, _input->textView.textStorage.length);
89
+
90
+ // don't search at the very end of input
91
+ NSUInteger searchLocation = location;
92
+ if(searchLocation == _input->textView.textStorage.length) {
93
+ return nullptr;
94
+ }
95
+
96
+ ImageData *imageData = [_input->textView.textStorage
97
+ attribute:ImageAttributeName
98
+ atIndex:searchLocation
99
+ longestEffectiveRange: &imageRange
100
+ inRange:inputRange
101
+ ];
102
+
103
+ return imageData;
104
+ }
105
+
106
+ - (void)addImageAtRange:(NSRange)range imageData:(ImageData *)imageData withSelection:(BOOL)withSelection
107
+ {
108
+ UIImage *img = [self prepareImageFromUri:imageData.uri];
109
+
110
+ NSDictionary *attributes = [@{
111
+ NSAttachmentAttributeName: [self prepareImageAttachement:img width:imageData.width height:imageData.height],
112
+ ImageAttributeName: imageData,
113
+ } mutableCopy];
114
+
115
+ // Use the Object Replacement Character for Image.
116
+ // This tells TextKit "something non-text goes here".
117
+ NSString *imagePlaceholder = @"\uFFFC";
118
+
119
+ if (range.length == 0) {
120
+ [TextInsertionUtils insertText:imagePlaceholder at:range.location additionalAttributes:attributes input:_input withSelection:withSelection];
121
+ } else {
122
+ [TextInsertionUtils replaceText:imagePlaceholder
123
+ at:range
124
+ additionalAttributes:attributes
125
+ input:_input
126
+ withSelection:withSelection];
127
+ }
128
+ }
129
+
130
+ - (void)addImage:(NSString *)uri width:(CGFloat)width height:(CGFloat)height {
131
+ ImageData *data = [[ImageData alloc] init];
132
+ data.uri = uri;
133
+ data.width = width;
134
+ data.height = height;
135
+
136
+ [self addImageAtRange:_input->textView.selectedRange imageData:data withSelection:YES];
137
+ }
138
+
139
+ -(NSTextAttachment *)prepareImageAttachement:(UIImage *)image width:(CGFloat)width height:(CGFloat)height
140
+ {
141
+
142
+ NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
143
+ attachment.image = image;
144
+ attachment.bounds = CGRectMake(0, 0, width, height);
145
+
146
+ return attachment;
147
+ }
148
+
149
+ - (UIImage *)prepareImageFromUri:(NSString *)uri
150
+ {
151
+ NSURL *url = [NSURL URLWithString:uri];
152
+ NSData *imgData = [NSData dataWithContentsOfURL:url];
153
+ UIImage *image = [UIImage imageWithData:imgData];
154
+
155
+ return image;
156
+ }
157
+
158
+ @end
@@ -11,6 +11,8 @@
11
11
 
12
12
  + (StyleType)getStyleType { return InlineCode; }
13
13
 
14
+ + (BOOL)isParagraphStyle { return NO; }
15
+
14
16
  - (instancetype)initWithInput:(id)input {
15
17
  self = [super init];
16
18
  _input = (EnrichedTextInputView *)input;
@@ -9,6 +9,8 @@
9
9
 
10
10
  + (StyleType)getStyleType { return Italic; }
11
11
 
12
+ + (BOOL)isParagraphStyle { return NO; }
13
+
12
14
  - (instancetype)initWithInput:(id)input {
13
15
  self = [super init];
14
16
  _input = (EnrichedTextInputView *)input;
@@ -15,6 +15,8 @@ static NSString *const AutomaticLinkAttributeName = @"AutomaticLinkAttributeName
15
15
 
16
16
  + (StyleType)getStyleType { return Link; }
17
17
 
18
+ + (BOOL)isParagraphStyle { return NO; }
19
+
18
20
  - (instancetype)initWithInput:(id)input {
19
21
  self = [super init];
20
22
  _input = (EnrichedTextInputView *)input;
@@ -280,6 +282,7 @@ static NSString *const AutomaticLinkAttributeName = @"AutomaticLinkAttributeName
280
282
  - (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange {
281
283
  InlineCodeStyle *inlineCodeStyle = [_input->stylesDict objectForKey:@([InlineCodeStyle getStyleType])];
282
284
  MentionStyle *mentionStyle = [_input->stylesDict objectForKey:@([MentionStyle getStyleType])];
285
+ CodeBlockStyle *codeBlockStyle = [_input->stylesDict objectForKey:@([CodeBlockStyle getStyleType])];
283
286
 
284
287
  if (inlineCodeStyle == nullptr || mentionStyle == nullptr) {
285
288
  return;
@@ -295,6 +298,11 @@ static NSString *const AutomaticLinkAttributeName = @"AutomaticLinkAttributeName
295
298
  return;
296
299
  }
297
300
 
301
+ // we don't recognize links in codeblocks
302
+ if ([codeBlockStyle anyOccurence:wordRange]) {
303
+ return;
304
+ }
305
+
298
306
  // remove connected different links
299
307
  [self removeConnectedLinksIfNeeded:word range:wordRange];
300
308
 
@@ -18,6 +18,8 @@ static NSString *const MentionAttributeName = @"MentionAttributeName";
18
18
 
19
19
  + (StyleType)getStyleType { return Mention; }
20
20
 
21
+ + (BOOL)isParagraphStyle { return NO; }
22
+
21
23
  - (instancetype)initWithInput:(id)input {
22
24
  self = [super init];
23
25
  _input = (EnrichedTextInputView *)input;
@@ -297,52 +299,37 @@ static NSString *const MentionAttributeName = @"MentionAttributeName";
297
299
  return;
298
300
  }
299
301
 
300
- // get the current word if it exists
301
- // we can be using current word only thanks to the fact that ongoing mentions are always one word (in contrast to ready, added mentions)
302
- NSDictionary *currentWord = [WordsUtils getCurrentWord:_input->textView.textStorage.string range:_input->textView.selectedRange];
303
- if(currentWord == nullptr) {
304
- [self removeActiveMentionRange];
305
- return;
306
- }
307
-
308
- // get word properties
309
- NSString *wordText = (NSString *)[currentWord objectForKey:@"word"];
310
- NSValue *wordRangeValue = (NSValue *)[currentWord objectForKey:@"range"];
311
- if(wordText == nullptr || wordRangeValue == nullptr) {
302
+ // get the text (and its range) that could be an editable mention
303
+ NSArray *mentionCandidate = [self getMentionCandidate];
304
+ if(mentionCandidate == nullptr) {
312
305
  [self removeActiveMentionRange];
313
306
  return;
314
307
  }
315
- NSRange wordRange = [wordRangeValue rangeValue];
316
-
317
- // check for mentionIndicators - no sign of them means we shouldn't be editing a mention
318
- unichar firstChar = [wordText characterAtIndex:0];
319
- if(![[_input->config mentionIndicators] containsObject: @(firstChar)]) {
320
- [self removeActiveMentionRange];
321
- return;
322
- }
323
-
324
- // check for existing mentions - we don't edit them
325
- if([self detectStyle:wordRange]) {
326
- [self removeActiveMentionRange];
327
- return;
328
- }
329
-
330
- // get conflicting style classes
331
- LinkStyle* linkStyle = [_input->stylesDict objectForKey:@([LinkStyle getStyleType])];
332
- InlineCodeStyle* inlineCodeStyle = [_input->stylesDict objectForKey:@([InlineCodeStyle getStyleType])];
333
- if(linkStyle == nullptr || inlineCodeStyle == nullptr) {
334
- [self removeActiveMentionRange];
335
- return;
308
+ NSString *candidateText = mentionCandidate[0];
309
+ NSRange candidateRange = [(NSValue *)mentionCandidate[1] rangeValue];
310
+
311
+ // get style classes that the mention shouldn't be recognized in, together with other mentions
312
+ NSArray *conflicts = _input->conflictingStyles[@([MentionStyle getStyleType])];
313
+ NSArray *blocks = _input->blockingStyles[@([MentionStyle getStyleType])];
314
+ NSArray *allConflicts = [[conflicts arrayByAddingObjectsFromArray:blocks] arrayByAddingObject:@([MentionStyle getStyleType])];
315
+ BOOL conflictingStyle = NO;
316
+
317
+ for(NSNumber *styleType in allConflicts) {
318
+ id<BaseStyleProtocol> styleClass = _input->stylesDict[styleType];
319
+ if(styleClass != nullptr && [styleClass anyOccurence:candidateRange]) {
320
+ conflictingStyle = YES;
321
+ break;
322
+ }
336
323
  }
337
324
 
338
- // if there is any sign of conflicting style classes, stop editing a mention
339
- if([linkStyle anyOccurence:wordRange] || [inlineCodeStyle anyOccurence:wordRange]) {
325
+ // if any of the conflicting styles were present, don't edit the mention
326
+ if(conflictingStyle) {
340
327
  [self removeActiveMentionRange];
341
328
  return;
342
329
  }
343
330
 
344
331
  // everything checks out - we are indeed editing a mention
345
- [self setActiveMentionRange:wordRange text:wordText];
332
+ [self setActiveMentionRange:candidateRange text:candidateText];
346
333
  }
347
334
 
348
335
  // used to fix mentions' typing attributes
@@ -454,6 +441,116 @@ static NSString *const MentionAttributeName = @"MentionAttributeName";
454
441
  return [_input->config mentionStylePropsForIndicator:params.indicator];
455
442
  }
456
443
 
444
+ // finds if any word/words around current selection are eligible to be edited as mentions
445
+ // since we allow for a single space inside an edited mention, we have take both current and the previous word into account
446
+ - (NSArray *)getMentionCandidate {
447
+ NSDictionary *currentWord, *previousWord;
448
+ NSString *currentWordText, *previousWordText, *finalText;
449
+ NSValue *currentWordRange, *previousWordRange;
450
+ NSRange finalRange;
451
+
452
+ // word at the current selection
453
+ currentWord = [WordsUtils getCurrentWord:_input->textView.textStorage.string range:_input->textView.selectedRange];
454
+ if(currentWord != nullptr ) {
455
+ currentWordText = (NSString *)[currentWord objectForKey:@"word"];
456
+ currentWordRange = (NSValue *)[currentWord objectForKey:@"range"];
457
+ }
458
+
459
+ if(currentWord != nullptr) {
460
+ // current word exists
461
+ unichar currentFirstChar = [currentWordText characterAtIndex:0];
462
+
463
+ if([[_input->config mentionIndicators] containsObject:@(currentFirstChar)]) {
464
+ // current word exists and has a mention indicator; no need to check for the previous word
465
+ finalText = currentWordText;
466
+ finalRange = [currentWordRange rangeValue];
467
+ } else {
468
+ // current word exists but no traces of mention indicator; get the previous word
469
+
470
+ NSInteger previousWordSearchLocation = [currentWordRange rangeValue].location - 1;
471
+ if(previousWordSearchLocation < 0) {
472
+ // previous word can't exist
473
+ return nullptr;
474
+ }
475
+
476
+ unichar separatorChar = [_input->textView.textStorage.string characterAtIndex:previousWordSearchLocation];
477
+ if(![[NSCharacterSet whitespaceCharacterSet] characterIsMember:separatorChar]) {
478
+ // we want to check for the previous word ONLY if the separating character was a space
479
+ // newlines don't make it
480
+ return nullptr;
481
+ }
482
+
483
+ previousWord = [WordsUtils getCurrentWord:_input->textView.textStorage.string range:NSMakeRange(previousWordSearchLocation, 0)];
484
+
485
+ if(previousWord != nullptr) {
486
+ // previous word exists; get its properties
487
+ previousWordText = (NSString *)[previousWord objectForKey:@"word"];
488
+ previousWordRange = (NSValue *)[previousWord objectForKey:@"range"];
489
+
490
+ // check for the mention indicators in the previous word
491
+ unichar previousFirstChar = [previousWordText characterAtIndex:0];
492
+
493
+ if([[_input->config mentionIndicators] containsObject:@(previousFirstChar) ]) {
494
+ // previous word has a proper mention indicator: treat both words as an editable mention
495
+ finalText = [NSString stringWithFormat:@"%@ %@", previousWordText, currentWordText];
496
+ // range length is both words' lengths + 1 for a space between them
497
+ finalRange = NSMakeRange(
498
+ [previousWordRange rangeValue].location,
499
+ [previousWordRange rangeValue].length + [currentWordRange rangeValue].length + 1
500
+ );
501
+ } else {
502
+ // neither current nor previous words have a mention indicator
503
+ return nullptr;
504
+ }
505
+ } else {
506
+ // previous word doesn't exist and no mention indicators in the current word
507
+ return nullptr;
508
+ }
509
+ }
510
+ } else {
511
+ // current word doesn't exist; try getting the previous one
512
+
513
+ NSInteger previousWordSearchLocation = _input->textView.selectedRange.location - 1;
514
+ if(previousWordSearchLocation < 0) {
515
+ // previous word can't exist
516
+ return nullptr;
517
+ }
518
+
519
+ unichar separatorChar = [_input->textView.textStorage.string characterAtIndex:previousWordSearchLocation];
520
+ if(![[NSCharacterSet whitespaceCharacterSet] characterIsMember:separatorChar]) {
521
+ // we want to check for the previous word ONLY if the separating character was a space
522
+ // newlines don't make it
523
+ return nullptr;
524
+ }
525
+
526
+ previousWord = [WordsUtils getCurrentWord:_input->textView.textStorage.string range:NSMakeRange(previousWordSearchLocation, 0)];
527
+
528
+ if(previousWord != nullptr) {
529
+ // previous word exists; get its properties
530
+ previousWordText = (NSString *)[previousWord objectForKey:@"word"];
531
+ previousWordRange = (NSValue *)[previousWord objectForKey:@"range"];
532
+
533
+ // check for the mention indicators in the previous word
534
+ unichar previousFirstChar = [previousWordText characterAtIndex:0];
535
+
536
+ if([[_input->config mentionIndicators] containsObject:@(previousFirstChar)]) {
537
+ // previous word has a proper mention indicator; treat previous word + a space as a editable mention
538
+ finalText = [NSString stringWithFormat:@"%@ ", previousWordText];
539
+ // the range length is previous word length + 1 for a space
540
+ finalRange = NSMakeRange([previousWordRange rangeValue].location, [previousWordRange rangeValue].length + 1);
541
+ } else {
542
+ // no current word, previous has no mention indicators
543
+ return nullptr;
544
+ }
545
+ } else {
546
+ // no current word, no previous word
547
+ return nullptr;
548
+ }
549
+ }
550
+
551
+ return @[finalText, [NSValue valueWithRange:finalRange]];
552
+ }
553
+
457
554
  // both used for setting the active mention range + indicator and fires proper onMention event
458
555
  - (void)setActiveMentionRange:(NSRange)range text:(NSString *)text {
459
556
  NSString *indicatorString = [NSString stringWithFormat:@"%C", [text characterAtIndex:0]];
@@ -11,6 +11,8 @@
11
11
 
12
12
  + (StyleType)getStyleType { return OrderedList; }
13
13
 
14
+ + (BOOL)isParagraphStyle { return YES; }
15
+
14
16
  - (CGFloat)getHeadIndent {
15
17
  // lists are drawn manually
16
18
  // margin before marker + gap between marker and paragraph
@@ -8,6 +8,8 @@
8
8
 
9
9
  + (StyleType)getStyleType { return Strikethrough; }
10
10
 
11
+ + (BOOL)isParagraphStyle { return NO; }
12
+
11
13
  - (instancetype)initWithInput:(id)input {
12
14
  self = [super init];
13
15
  _input = (EnrichedTextInputView *)input;