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.
- package/README.md +3 -9
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +1 -1
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +1 -1
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +0 -45
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +19 -2
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +3 -3
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +2 -0
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +36 -1
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +132 -11
- package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +4 -0
- package/android/src/main/java/com/swmansion/enriched/spans/utils/ForceRedrawSpan.kt +13 -0
- package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +2 -9
- package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +110 -3
- package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +57 -30
- package/android/src/main/java/com/swmansion/enriched/utils/AsyncDrawable.kt +91 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +16 -13
- package/android/src/main/java/com/swmansion/enriched/utils/ResourceManager.kt +26 -0
- package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +3 -0
- package/android/src/main/res/drawable/broken_image.xml +10 -0
- package/ios/EnrichedTextInputView.h +3 -0
- package/ios/EnrichedTextInputView.mm +97 -29
- package/ios/config/InputConfig.h +6 -0
- package/ios/config/InputConfig.mm +32 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +0 -45
- package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +20 -4
- package/ios/inputParser/InputParser.mm +147 -24
- package/ios/internals/EnrichedTextInputViewShadowNode.h +1 -0
- package/ios/internals/EnrichedTextInputViewShadowNode.mm +29 -17
- package/ios/styles/BlockQuoteStyle.mm +5 -26
- package/ios/styles/BoldStyle.mm +2 -0
- package/ios/styles/CodeBlockStyle.mm +228 -0
- package/ios/styles/H1Style.mm +1 -0
- package/ios/styles/H2Style.mm +1 -0
- package/ios/styles/H3Style.mm +1 -0
- package/ios/styles/ImageStyle.mm +158 -0
- package/ios/styles/InlineCodeStyle.mm +2 -0
- package/ios/styles/ItalicStyle.mm +2 -0
- package/ios/styles/LinkStyle.mm +8 -0
- package/ios/styles/MentionStyle.mm +133 -36
- package/ios/styles/OrderedListStyle.mm +2 -0
- package/ios/styles/StrikethroughStyle.mm +2 -0
- package/ios/styles/UnderlineStyle.mm +2 -0
- package/ios/styles/UnorderedListStyle.mm +2 -0
- package/ios/utils/BaseStyleProtocol.h +1 -0
- package/ios/utils/ImageData.h +10 -0
- package/ios/utils/ImageData.mm +4 -0
- package/ios/utils/LayoutManagerExtension.mm +118 -3
- package/ios/utils/OccurenceUtils.h +4 -0
- package/ios/utils/OccurenceUtils.mm +47 -0
- package/ios/utils/ParagraphAttributesUtils.h +1 -0
- package/ios/utils/ParagraphAttributesUtils.mm +87 -20
- package/ios/utils/StyleHeaders.h +12 -0
- package/ios/utils/ZeroWidthSpaceUtils.mm +22 -10
- package/lib/module/EnrichedTextInput.js +2 -2
- package/lib/module/EnrichedTextInput.js.map +1 -1
- package/lib/module/EnrichedTextInputNativeComponent.ts +6 -5
- package/lib/module/normalizeHtmlStyle.js +0 -4
- package/lib/module/normalizeHtmlStyle.js.map +1 -1
- package/lib/typescript/src/EnrichedTextInput.d.ts +1 -5
- package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +1 -5
- package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedTextInput.tsx +3 -7
- package/src/EnrichedTextInputNativeComponent.ts +6 -5
- 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
|
package/ios/styles/H1Style.mm
CHANGED
|
@@ -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];
|
package/ios/styles/H2Style.mm
CHANGED
|
@@ -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];
|
package/ios/styles/H3Style.mm
CHANGED
|
@@ -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
|
package/ios/styles/LinkStyle.mm
CHANGED
|
@@ -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
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
[
|
|
327
|
-
|
|
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
|
|
339
|
-
if(
|
|
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:
|
|
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]];
|