react-native-enriched 0.1.5 → 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 +4 -1
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +2 -1
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.cpp +5 -0
- package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +1 -45
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +53 -12
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +7 -56
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +19 -22
- package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +2 -0
- package/android/src/main/java/com/swmansion/enriched/MeasurementStore.kt +158 -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 +65 -46
- 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 +75 -32
- package/android/src/main/java/com/swmansion/enriched/utils/AsyncDrawable.kt +91 -0
- package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +38 -15
- 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 -1
- package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +1 -1
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.cpp +15 -2
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +1 -0
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.cpp +1 -2
- package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/conversions.h +27 -0
- package/android/src/main/res/drawable/broken_image.xml +10 -0
- package/ios/EnrichedTextInputView.h +3 -1
- package/ios/EnrichedTextInputView.mm +167 -68
- package/ios/config/InputConfig.h +6 -0
- package/ios/config/InputConfig.mm +32 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/Props.cpp +5 -0
- package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +1 -45
- package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +20 -4
- package/ios/inputParser/InputParser.mm +179 -31
- package/ios/inputTextView/InputTextView.mm +3 -5
- 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 +15 -7
- package/ios/styles/MentionStyle.mm +133 -36
- package/ios/styles/OrderedListStyle.mm +5 -8
- package/ios/styles/StrikethroughStyle.mm +2 -0
- package/ios/styles/UnderlineStyle.mm +2 -0
- package/ios/styles/UnorderedListStyle.mm +5 -8
- 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/StringExtension.h +1 -1
- package/ios/utils/StringExtension.mm +17 -8
- package/ios/utils/StyleHeaders.h +12 -0
- package/ios/utils/ZeroWidthSpaceUtils.mm +22 -10
- package/lib/module/EnrichedTextInput.js +4 -2
- package/lib/module/EnrichedTextInput.js.map +1 -1
- package/lib/module/EnrichedTextInputNativeComponent.ts +7 -5
- package/lib/module/normalizeHtmlStyle.js +0 -4
- package/lib/module/normalizeHtmlStyle.js.map +1 -1
- package/lib/typescript/src/EnrichedTextInput.d.ts +3 -6
- package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +2 -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 +6 -7
- package/src/EnrichedTextInputNativeComponent.ts +7 -5
- package/src/normalizeHtmlStyle.ts +0 -4
|
@@ -30,7 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
30
30
|
- (void)toggleOrderedList;
|
|
31
31
|
- (void)toggleUnorderedList;
|
|
32
32
|
- (void)addLink:(NSInteger)start end:(NSInteger)end text:(NSString *)text url:(NSString *)url;
|
|
33
|
-
- (void)addImage:(NSString *)uri;
|
|
33
|
+
- (void)addImage:(NSString *)uri width:(float)width height:(float)height;
|
|
34
34
|
- (void)startMention:(NSString *)indicator;
|
|
35
35
|
- (void)addMention:(NSString *)indicator text:(NSString *)text payload:(NSString *)payload;
|
|
36
36
|
@end
|
|
@@ -302,8 +302,8 @@ NSObject *arg3 = args[3];
|
|
|
302
302
|
|
|
303
303
|
if ([commandName isEqualToString:@"addImage"]) {
|
|
304
304
|
#if RCT_DEBUG
|
|
305
|
-
if ([args count] !=
|
|
306
|
-
RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedTextInputView", commandName, (int)[args count],
|
|
305
|
+
if ([args count] != 3) {
|
|
306
|
+
RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedTextInputView", commandName, (int)[args count], 3);
|
|
307
307
|
return;
|
|
308
308
|
}
|
|
309
309
|
#endif
|
|
@@ -316,7 +316,23 @@ if ([commandName isEqualToString:@"addImage"]) {
|
|
|
316
316
|
#endif
|
|
317
317
|
NSString * uri = (NSString *)arg0;
|
|
318
318
|
|
|
319
|
-
|
|
319
|
+
NSObject *arg1 = args[1];
|
|
320
|
+
#if RCT_DEBUG
|
|
321
|
+
if (!RCTValidateTypeOfViewCommandArgument(arg1, [NSNumber class], @"float", @"EnrichedTextInputView", commandName, @"2nd")) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
#endif
|
|
325
|
+
float width = [(NSNumber *)arg1 floatValue];
|
|
326
|
+
|
|
327
|
+
NSObject *arg2 = args[2];
|
|
328
|
+
#if RCT_DEBUG
|
|
329
|
+
if (!RCTValidateTypeOfViewCommandArgument(arg2, [NSNumber class], @"float", @"EnrichedTextInputView", commandName, @"3rd")) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
#endif
|
|
333
|
+
float height = [(NSNumber *)arg2 floatValue];
|
|
334
|
+
|
|
335
|
+
[componentView addImage:uri width:width height:height];
|
|
320
336
|
return;
|
|
321
337
|
}
|
|
322
338
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#import "StyleHeaders.h"
|
|
4
4
|
#import "UIView+React.h"
|
|
5
5
|
#import "TextInsertionUtils.h"
|
|
6
|
+
#import "StringExtension.h"
|
|
6
7
|
|
|
7
8
|
@implementation InputParser {
|
|
8
9
|
EnrichedTextInputView *_input;
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
BOOL inUnorderedList = NO;
|
|
29
30
|
BOOL inOrderedList = NO;
|
|
30
31
|
BOOL inBlockQuote = NO;
|
|
32
|
+
BOOL inCodeBlock = NO;
|
|
31
33
|
unichar lastCharacter = 0;
|
|
32
34
|
|
|
33
35
|
for(int i = 0; i < text.length; i++) {
|
|
@@ -84,6 +86,9 @@
|
|
|
84
86
|
|
|
85
87
|
// append closing tags
|
|
86
88
|
for(NSNumber *style in sortedEndedStyles) {
|
|
89
|
+
if ([style isEqualToNumber: @([ImageStyle getStyleType])]) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
87
92
|
NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:currentRange.location];
|
|
88
93
|
[result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
|
|
89
94
|
}
|
|
@@ -94,7 +99,8 @@
|
|
|
94
99
|
[previousActiveStyles containsObject:@([H1Style getStyleType])] ||
|
|
95
100
|
[previousActiveStyles containsObject:@([H2Style getStyleType])] ||
|
|
96
101
|
[previousActiveStyles containsObject:@([H3Style getStyleType])] ||
|
|
97
|
-
[previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]
|
|
102
|
+
[previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] ||
|
|
103
|
+
[previousActiveStyles containsObject:@([CodeBlockStyle getStyleType])]
|
|
98
104
|
) {
|
|
99
105
|
// do nothing, proper closing paragraph tags have been already appended
|
|
100
106
|
} else {
|
|
@@ -127,6 +133,11 @@
|
|
|
127
133
|
inBlockQuote = NO;
|
|
128
134
|
[result appendString:@"\n</blockquote>"];
|
|
129
135
|
}
|
|
136
|
+
// handle ending codeblock
|
|
137
|
+
if(inCodeBlock && ![currentActiveStyles containsObject:@([CodeBlockStyle getStyleType])]) {
|
|
138
|
+
inCodeBlock = NO;
|
|
139
|
+
[result appendString:@"\n</codeblock>"];
|
|
140
|
+
}
|
|
130
141
|
|
|
131
142
|
// handle starting unordered list
|
|
132
143
|
if(!inUnorderedList && [currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])]) {
|
|
@@ -143,6 +154,11 @@
|
|
|
143
154
|
inBlockQuote = YES;
|
|
144
155
|
[result appendString:@"\n<blockquote>"];
|
|
145
156
|
}
|
|
157
|
+
// handle starting codeblock
|
|
158
|
+
if(!inCodeBlock && [currentActiveStyles containsObject:@([CodeBlockStyle getStyleType])]) {
|
|
159
|
+
inCodeBlock = YES;
|
|
160
|
+
[result appendString:@"\n<codeblock>"];
|
|
161
|
+
}
|
|
146
162
|
|
|
147
163
|
// don't add the <p> tag if some paragraph styles are present
|
|
148
164
|
if([currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])] ||
|
|
@@ -150,7 +166,8 @@
|
|
|
150
166
|
[currentActiveStyles containsObject:@([H1Style getStyleType])] ||
|
|
151
167
|
[currentActiveStyles containsObject:@([H2Style getStyleType])] ||
|
|
152
168
|
[currentActiveStyles containsObject:@([H3Style getStyleType])] ||
|
|
153
|
-
[currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]
|
|
169
|
+
[currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] ||
|
|
170
|
+
[currentActiveStyles containsObject:@([CodeBlockStyle getStyleType])]
|
|
154
171
|
) {
|
|
155
172
|
[result appendString:@"\n"];
|
|
156
173
|
} else {
|
|
@@ -183,29 +200,55 @@
|
|
|
183
200
|
}
|
|
184
201
|
}
|
|
185
202
|
|
|
203
|
+
// if a style begins but there is a style inner to it that is (and was previously) active, it also should be closed and readded
|
|
204
|
+
|
|
205
|
+
// newly added styles
|
|
206
|
+
NSMutableSet *newStyles = [currentActiveStyles mutableCopy];
|
|
207
|
+
[newStyles minusSet: previousActiveStyles];
|
|
208
|
+
// styles that were and still are active
|
|
209
|
+
NSMutableSet *stillActiveStyles = [previousActiveStyles mutableCopy];
|
|
210
|
+
[stillActiveStyles intersectSet:currentActiveStyles];
|
|
211
|
+
|
|
212
|
+
for(NSNumber *style in newStyles) {
|
|
213
|
+
for(NSNumber *ongoingStyle in stillActiveStyles) {
|
|
214
|
+
if([ongoingStyle integerValue] > [style integerValue]) {
|
|
215
|
+
// the prev style is inner; needs to be closed and re-added later
|
|
216
|
+
[fixedEndedStyles addObject:ongoingStyle];
|
|
217
|
+
[stylesToBeReAdded addObject:ongoingStyle];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
186
222
|
// they are sorted in a descending order
|
|
187
223
|
NSArray<NSNumber*> *sortedEndedStyles = [fixedEndedStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:NO]]];
|
|
188
224
|
|
|
189
225
|
// append closing tags
|
|
190
226
|
for(NSNumber *style in sortedEndedStyles) {
|
|
227
|
+
if ([style isEqualToNumber: @([ImageStyle getStyleType])]) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
191
230
|
NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:currentRange.location];
|
|
192
231
|
[result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
|
|
193
232
|
}
|
|
194
233
|
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
[newStyles minusSet: previousActiveStyles];
|
|
234
|
+
// all styles that have begun: new styles + the ones that need to be re-added
|
|
235
|
+
// they are sorted in a ascending manner to properly keep tags' FILO order
|
|
198
236
|
[newStyles unionSet: stylesToBeReAdded];
|
|
199
237
|
NSArray<NSNumber*> *sortedNewStyles = [newStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:YES]]];
|
|
200
238
|
|
|
201
239
|
// append opening tags
|
|
202
240
|
for(NSNumber *style in sortedNewStyles) {
|
|
203
241
|
NSString *tagContent = [self tagContentForStyle:style openingTag:YES location:currentRange.location];
|
|
204
|
-
[
|
|
242
|
+
if ([style isEqualToNumber: @([ImageStyle getStyleType])]) {
|
|
243
|
+
[result appendString: [NSString stringWithFormat:@"<%@/>", tagContent]];
|
|
244
|
+
[currentActiveStyles removeObject:@([ImageStyle getStyleType])];
|
|
245
|
+
} else {
|
|
246
|
+
[result appendString: [NSString stringWithFormat:@"<%@>", tagContent]];
|
|
247
|
+
}
|
|
205
248
|
}
|
|
206
249
|
|
|
207
|
-
// append the letter
|
|
208
|
-
[result appendString:currentCharacterStr];
|
|
250
|
+
// append the letter and escape it if needed
|
|
251
|
+
[result appendString: [NSString stringByEscapingHtml:currentCharacterStr]];
|
|
209
252
|
|
|
210
253
|
// save current styles for next character's checks
|
|
211
254
|
previousActiveStyles = currentActiveStyles;
|
|
@@ -222,6 +265,9 @@
|
|
|
222
265
|
|
|
223
266
|
// append closing tags
|
|
224
267
|
for(NSNumber *style in sortedEndedStyles) {
|
|
268
|
+
if ([style isEqualToNumber: @([ImageStyle getStyleType])]) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
225
271
|
NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:_input->textView.textStorage.string.length - 1];
|
|
226
272
|
[result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
|
|
227
273
|
}
|
|
@@ -234,6 +280,8 @@
|
|
|
234
280
|
[result appendString:@"\n</ol>"];
|
|
235
281
|
} else if([previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]) {
|
|
236
282
|
[result appendString:@"\n</blockquote>"];
|
|
283
|
+
} else if([previousActiveStyles containsObject:@([CodeBlockStyle getStyleType])]) {
|
|
284
|
+
[result appendString:@"\n</codeblock>"];
|
|
237
285
|
} else if(
|
|
238
286
|
[previousActiveStyles containsObject:@([H1Style getStyleType])] ||
|
|
239
287
|
[previousActiveStyles containsObject:@([H2Style getStyleType])] ||
|
|
@@ -257,6 +305,10 @@
|
|
|
257
305
|
inBlockQuote = NO;
|
|
258
306
|
[result appendString:@"\n</blockquote>"];
|
|
259
307
|
}
|
|
308
|
+
if(inCodeBlock) {
|
|
309
|
+
inCodeBlock = NO;
|
|
310
|
+
[result appendString:@"\n</codeblock>"];
|
|
311
|
+
}
|
|
260
312
|
}
|
|
261
313
|
|
|
262
314
|
[result appendString: @"\n</html>"];
|
|
@@ -272,6 +324,19 @@
|
|
|
272
324
|
return @"b";
|
|
273
325
|
} else if([style isEqualToNumber: @([ItalicStyle getStyleType])]) {
|
|
274
326
|
return @"i";
|
|
327
|
+
} else if ([style isEqualToNumber: @([ImageStyle getStyleType])]) {
|
|
328
|
+
if(openingTag) {
|
|
329
|
+
ImageStyle *imageStyle = (ImageStyle *)_input->stylesDict[@([ImageStyle getStyleType])];
|
|
330
|
+
if(imageStyle != nullptr) {
|
|
331
|
+
ImageData *data = [imageStyle getImageDataAt:location];
|
|
332
|
+
if(data != nullptr && data.uri != nullptr) {
|
|
333
|
+
return [NSString stringWithFormat:@"img src=\"%@\" width=\"%f\" height=\"%f\"", data.uri, data.width, data.height];
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return @"img";
|
|
337
|
+
} else {
|
|
338
|
+
return @"";
|
|
339
|
+
}
|
|
275
340
|
} else if([style isEqualToNumber: @([UnderlineStyle getStyleType])]) {
|
|
276
341
|
return @"u";
|
|
277
342
|
} else if([style isEqualToNumber: @([StrikethroughStyle getStyleType])]) {
|
|
@@ -327,8 +392,8 @@
|
|
|
327
392
|
return @"h3";
|
|
328
393
|
} else if([style isEqualToNumber:@([UnorderedListStyle getStyleType])] || [style isEqualToNumber:@([OrderedListStyle getStyleType])]) {
|
|
329
394
|
return @"li";
|
|
330
|
-
} else if([style isEqualToNumber:@([BlockQuoteStyle getStyleType])]) {
|
|
331
|
-
// blockquotes use <p> tags the same way lists use <li>
|
|
395
|
+
} else if([style isEqualToNumber:@([BlockQuoteStyle getStyleType])] || [style isEqualToNumber:@([CodeBlockStyle getStyleType])]) {
|
|
396
|
+
// blockquotes and codeblock use <p> tags the same way lists use <li>
|
|
332
397
|
return @"p";
|
|
333
398
|
}
|
|
334
399
|
return @"";
|
|
@@ -392,6 +457,9 @@
|
|
|
392
457
|
} else if([styleType isEqualToNumber: @([MentionStyle getStyleType])]) {
|
|
393
458
|
MentionParams *params = (MentionParams *)stylePair.styleValue;
|
|
394
459
|
[((MentionStyle *)baseStyle) addMentionAtRange:styleRange params:params];
|
|
460
|
+
} else if([styleType isEqualToNumber: @([ImageStyle getStyleType])]) {
|
|
461
|
+
ImageData *imgData = (ImageData *)stylePair.styleValue;
|
|
462
|
+
[((ImageStyle *)baseStyle) addImageAtRange:styleRange imageData:imgData withSelection:NO];
|
|
395
463
|
} else {
|
|
396
464
|
[baseStyle addAttributes:styleRange];
|
|
397
465
|
}
|
|
@@ -446,6 +514,8 @@
|
|
|
446
514
|
fixedHtml = [self stringByAddingNewlinesToTag:@"</ol>" inString:fixedHtml leading:YES trailing:YES];
|
|
447
515
|
fixedHtml = [self stringByAddingNewlinesToTag:@"<blockquote>" inString:fixedHtml leading:YES trailing:YES];
|
|
448
516
|
fixedHtml = [self stringByAddingNewlinesToTag:@"</blockquote>" inString:fixedHtml leading:YES trailing:YES];
|
|
517
|
+
fixedHtml = [self stringByAddingNewlinesToTag:@"<codeblock>" inString:fixedHtml leading:YES trailing:YES];
|
|
518
|
+
fixedHtml = [self stringByAddingNewlinesToTag:@"</codeblock>" inString:fixedHtml leading:YES trailing:YES];
|
|
449
519
|
|
|
450
520
|
// line opening tags
|
|
451
521
|
fixedHtml = [self stringByAddingNewlinesToTag:@"<p>" inString:fixedHtml leading:YES trailing:NO];
|
|
@@ -480,6 +550,24 @@
|
|
|
480
550
|
return str;
|
|
481
551
|
}
|
|
482
552
|
|
|
553
|
+
- (void)finalizeTagEntry:(NSMutableString *)tagName ongoingTags:(NSMutableDictionary *)ongoingTags initiallyProcessedTags:(NSMutableArray *)processedTags plainText:(NSMutableString *)plainText
|
|
554
|
+
{
|
|
555
|
+
NSMutableArray *tagEntry = [[NSMutableArray alloc] init];
|
|
556
|
+
|
|
557
|
+
NSArray *tagData = ongoingTags[tagName];
|
|
558
|
+
NSInteger tagLocation = [((NSNumber *)tagData[0]) intValue];
|
|
559
|
+
NSRange tagRange = NSMakeRange(tagLocation, plainText.length - tagLocation);
|
|
560
|
+
|
|
561
|
+
[tagEntry addObject:[tagName copy]];
|
|
562
|
+
[tagEntry addObject:[NSValue valueWithRange:tagRange]];
|
|
563
|
+
if(tagData.count > 1) {
|
|
564
|
+
[tagEntry addObject:[(NSString *)tagData[1] copy]];
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
[processedTags addObject:tagEntry];
|
|
568
|
+
[ongoingTags removeObjectForKey:tagName];
|
|
569
|
+
}
|
|
570
|
+
|
|
483
571
|
- (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
|
|
484
572
|
NSMutableString *plainText = [[NSMutableString alloc] initWithString: @""];
|
|
485
573
|
NSMutableDictionary *ongoingTags = [[NSMutableDictionary alloc] init];
|
|
@@ -490,6 +578,7 @@
|
|
|
490
578
|
BOOL closingTag = NO;
|
|
491
579
|
NSMutableString *currentTagName = [[NSMutableString alloc] initWithString:@""];
|
|
492
580
|
NSMutableString *currentTagParams = [[NSMutableString alloc] initWithString:@""];
|
|
581
|
+
NSDictionary *htmlEntitiesDict = [NSString getEscapedCharactersInfoFrom:fixedHtml];
|
|
493
582
|
|
|
494
583
|
// firstly, extract text and initially processed tags
|
|
495
584
|
for(int i = 0; i < fixedHtml.length; i++) {
|
|
@@ -506,43 +595,42 @@
|
|
|
506
595
|
gettingTagName = NO;
|
|
507
596
|
gettingTagParams = NO;
|
|
508
597
|
|
|
598
|
+
BOOL isSelfClosing = NO;
|
|
599
|
+
|
|
600
|
+
// Check if params ended with '/' (e.g. <img src="" />)
|
|
601
|
+
if ([currentTagParams hasSuffix:@"/"]) {
|
|
602
|
+
[currentTagParams deleteCharactersInRange:NSMakeRange(currentTagParams.length - 1, 1)];
|
|
603
|
+
isSelfClosing = YES;
|
|
604
|
+
}
|
|
605
|
+
|
|
509
606
|
if([currentTagName isEqualToString:@"p"] || [currentTagName isEqualToString:@"br"] || [currentTagName isEqualToString:@"li"]) {
|
|
510
607
|
// do nothing, we don't include these tags in styles
|
|
511
608
|
} else if(!closingTag) {
|
|
512
609
|
// we finish opening tag - get its location and optionally params and put them under tag name key in ongoingTags
|
|
513
610
|
NSMutableArray *tagArr = [[NSMutableArray alloc] init];
|
|
514
|
-
[tagArr addObject:[NSNumber
|
|
611
|
+
[tagArr addObject:[NSNumber numberWithInteger:plainText.length]];
|
|
515
612
|
if(currentTagParams.length > 0) {
|
|
516
613
|
[tagArr addObject:[currentTagParams copy]];
|
|
517
614
|
}
|
|
518
615
|
ongoingTags[currentTagName] = tagArr;
|
|
519
616
|
|
|
520
617
|
// skip one newline after opening tags that are in separate lines intentionally
|
|
521
|
-
if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"]) {
|
|
618
|
+
if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"] || [currentTagName isEqualToString:@"codeblock"]) {
|
|
522
619
|
i += 1;
|
|
523
620
|
}
|
|
621
|
+
|
|
622
|
+
if (isSelfClosing) {
|
|
623
|
+
[self finalizeTagEntry:currentTagName ongoingTags:ongoingTags initiallyProcessedTags:initiallyProcessedTags plainText:plainText];
|
|
624
|
+
}
|
|
524
625
|
} else {
|
|
525
626
|
// we finish closing tags - pack tag name, tag range and optionally tag params into an entry that goes inside initiallyProcessedTags
|
|
526
627
|
|
|
527
628
|
// skip one newline that was added before some closing tags that are in separate lines
|
|
528
|
-
if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"]) {
|
|
629
|
+
if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"] || [currentTagName isEqualToString:@"codeblock"]) {
|
|
529
630
|
plainText = [[plainText substringWithRange: NSMakeRange(0, plainText.length - 1)] mutableCopy];
|
|
530
631
|
}
|
|
531
632
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
NSArray *tagData = ongoingTags[currentTagName];
|
|
535
|
-
NSInteger tagLocation = [((NSNumber *)tagData[0]) intValue];
|
|
536
|
-
NSRange tagRange = NSMakeRange(tagLocation, plainText.length - tagLocation);
|
|
537
|
-
|
|
538
|
-
[tagEntry addObject:[currentTagName copy]];
|
|
539
|
-
[tagEntry addObject:[NSValue valueWithRange:tagRange]];
|
|
540
|
-
if(tagData.count > 1) {
|
|
541
|
-
[tagEntry addObject:[(NSString *)tagData[1] copy]];
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
[initiallyProcessedTags addObject:tagEntry];
|
|
545
|
-
[ongoingTags removeObjectForKey:currentTagName];
|
|
633
|
+
[self finalizeTagEntry:currentTagName ongoingTags:ongoingTags initiallyProcessedTags:initiallyProcessedTags plainText:plainText];
|
|
546
634
|
}
|
|
547
635
|
// post-tag cleanup
|
|
548
636
|
closingTag = NO;
|
|
@@ -550,8 +638,19 @@
|
|
|
550
638
|
currentTagParams = [[NSMutableString alloc] initWithString:@""];
|
|
551
639
|
} else {
|
|
552
640
|
if(!insideTag) {
|
|
553
|
-
// no tags logic - just append text
|
|
554
|
-
|
|
641
|
+
// no tags logic - just append the right text
|
|
642
|
+
|
|
643
|
+
// html entity on the index; use unescaped character and forward iterator accordingly
|
|
644
|
+
NSArray *entityInfo = htmlEntitiesDict[@(i)];
|
|
645
|
+
if(entityInfo != nullptr) {
|
|
646
|
+
NSString *escaped = entityInfo[0];
|
|
647
|
+
NSString *unescaped = entityInfo[1];
|
|
648
|
+
[plainText appendString:unescaped];
|
|
649
|
+
// the iterator will forward by 1 itself
|
|
650
|
+
i += escaped.length - 1;
|
|
651
|
+
} else {
|
|
652
|
+
[plainText appendString:currentCharacterStr];
|
|
653
|
+
}
|
|
555
654
|
} else {
|
|
556
655
|
if(gettingTagName) {
|
|
557
656
|
if(currentCharacterChar == ' ') {
|
|
@@ -590,6 +689,41 @@
|
|
|
590
689
|
[styleArr addObject:@([BoldStyle getStyleType])];
|
|
591
690
|
} else if([tagName isEqualToString:@"i"]) {
|
|
592
691
|
[styleArr addObject:@([ItalicStyle getStyleType])];
|
|
692
|
+
} else if([tagName isEqualToString:@"img"]) {
|
|
693
|
+
NSRegularExpression *srcRegex = [NSRegularExpression regularExpressionWithPattern:@"src=\"([^\"]+)\""
|
|
694
|
+
options:0
|
|
695
|
+
error:nullptr
|
|
696
|
+
];
|
|
697
|
+
NSTextCheckingResult* match = [srcRegex firstMatchInString:params options:0 range: NSMakeRange(0, params.length)];
|
|
698
|
+
|
|
699
|
+
if(match == nullptr) {
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
NSRange srcRange = match.range;
|
|
704
|
+
[styleArr addObject:@([ImageStyle getStyleType])];
|
|
705
|
+
// cut only the uri from the src="..." string
|
|
706
|
+
NSString *uri = [params substringWithRange:NSMakeRange(srcRange.location + 5, srcRange.length - 6)];
|
|
707
|
+
ImageData *imageData = [[ImageData alloc] init];
|
|
708
|
+
imageData.uri = uri;
|
|
709
|
+
|
|
710
|
+
NSRegularExpression *widthRegex = [NSRegularExpression regularExpressionWithPattern:@"width=\"([0-9.]+)\"" options:0 error:nil];
|
|
711
|
+
NSTextCheckingResult *widthMatch = [widthRegex firstMatchInString:params options:0 range:NSMakeRange(0, params.length)];
|
|
712
|
+
|
|
713
|
+
if (widthMatch) {
|
|
714
|
+
NSString *widthString = [params substringWithRange:[widthMatch rangeAtIndex:1]];
|
|
715
|
+
imageData.width = [widthString floatValue];
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
NSRegularExpression *heightRegex = [NSRegularExpression regularExpressionWithPattern:@"height=\"([0-9.]+)\"" options:0 error:nil];
|
|
719
|
+
NSTextCheckingResult *heightMatch = [heightRegex firstMatchInString:params options:0 range:NSMakeRange(0, params.length)];
|
|
720
|
+
|
|
721
|
+
if (heightMatch) {
|
|
722
|
+
NSString *heightString = [params substringWithRange:[heightMatch rangeAtIndex:1]];
|
|
723
|
+
imageData.height = [heightString floatValue];
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
stylePair.styleValue = imageData;
|
|
593
727
|
} else if([tagName isEqualToString:@"u"]) {
|
|
594
728
|
[styleArr addObject:@([UnderlineStyle getStyleType])];
|
|
595
729
|
} else if([tagName isEqualToString:@"s"]) {
|
|
@@ -597,9 +731,21 @@
|
|
|
597
731
|
} else if([tagName isEqualToString:@"code"]) {
|
|
598
732
|
[styleArr addObject:@([InlineCodeStyle getStyleType])];
|
|
599
733
|
} else if([tagName isEqualToString:@"a"]) {
|
|
734
|
+
NSRegularExpression *hrefRegex = [NSRegularExpression regularExpressionWithPattern:@"href=\".+\""
|
|
735
|
+
options:0
|
|
736
|
+
error:nullptr
|
|
737
|
+
];
|
|
738
|
+
NSTextCheckingResult* match = [hrefRegex firstMatchInString:params options:0 range: NSMakeRange(0, params.length)];
|
|
739
|
+
|
|
740
|
+
if(match == nullptr) {
|
|
741
|
+
// same as on Android, no href (or empty href) equals no link style
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
NSRange hrefRange = match.range;
|
|
600
746
|
[styleArr addObject:@([LinkStyle getStyleType])];
|
|
601
747
|
// cut only the url from the href="..." string
|
|
602
|
-
NSString *url = [params substringWithRange:NSMakeRange(6,
|
|
748
|
+
NSString *url = [params substringWithRange:NSMakeRange(hrefRange.location + 6, hrefRange.length - 7)];
|
|
603
749
|
stylePair.styleValue = url;
|
|
604
750
|
} else if([tagName isEqualToString:@"mention"]) {
|
|
605
751
|
[styleArr addObject:@([MentionStyle getStyleType])];
|
|
@@ -643,8 +789,10 @@
|
|
|
643
789
|
[styleArr addObject:@([OrderedListStyle getStyleType])];
|
|
644
790
|
} else if([tagName isEqualToString:@"blockquote"]) {
|
|
645
791
|
[styleArr addObject:@([BlockQuoteStyle getStyleType])];
|
|
792
|
+
} else if([tagName isEqualToString:@"codeblock"]) {
|
|
793
|
+
[styleArr addObject:@([CodeBlockStyle getStyleType])];
|
|
646
794
|
} else {
|
|
647
|
-
|
|
795
|
+
// some other external tags like span just don't get put into the processed styles
|
|
648
796
|
continue;
|
|
649
797
|
}
|
|
650
798
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
NSString *plainText = [typedInput->textView.textStorage.string substringWithRange:typedInput->textView.selectedRange];
|
|
15
15
|
NSString *fixedPlainText = [plainText stringByReplacingOccurrencesOfString:@"\u200B" withString:@""];
|
|
16
16
|
|
|
17
|
-
NSString *
|
|
17
|
+
NSString *parsedHtml = [typedInput->parser parseToHtmlFromRange:typedInput->textView.selectedRange];
|
|
18
18
|
|
|
19
19
|
NSMutableAttributedString *attrStr = [[typedInput->textView.textStorage attributedSubstringFromRange:typedInput->textView.selectedRange] mutableCopy];
|
|
20
20
|
NSRange fullAttrStrRange = NSMakeRange(0, attrStr.length);
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
|
29
29
|
[pasteboard setItems:@[@{
|
|
30
30
|
UTTypeUTF8PlainText.identifier : fixedPlainText,
|
|
31
|
-
UTTypeHTML.identifier :
|
|
31
|
+
UTTypeHTML.identifier : parsedHtml,
|
|
32
32
|
UTTypeRTF.identifier : rtfData
|
|
33
33
|
}]];
|
|
34
34
|
}
|
|
@@ -53,9 +53,7 @@
|
|
|
53
53
|
htmlString = htmlValue;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
//
|
|
57
|
-
htmlString = [NSString stringByUnescapingHtml:htmlString];
|
|
58
|
-
// validate it
|
|
56
|
+
// validate the html
|
|
59
57
|
NSString *initiallyProcessedHtml = [typedInput->parser initiallyProcessHtml:htmlString];
|
|
60
58
|
|
|
61
59
|
if(initiallyProcessedHtml != nullptr) {
|
|
@@ -17,6 +17,18 @@ EnrichedTextInputViewShadowNode::EnrichedTextInputViewShadowNode(
|
|
|
17
17
|
localForceHeightRecalculationCounter_ = 0;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// mock input is used for the first measure calls that need to be done when the real input isn't defined yet
|
|
21
|
+
id EnrichedTextInputViewShadowNode::setupMockTextInputView_() const {
|
|
22
|
+
// it's rendered far away from the viewport
|
|
23
|
+
const int veryFarAway = 20000;
|
|
24
|
+
const int mockSize = 1000;
|
|
25
|
+
EnrichedTextInputView *mockTextInputView_ = [[EnrichedTextInputView alloc] initWithFrame:(CGRectMake(veryFarAway, veryFarAway, mockSize, mockSize))];
|
|
26
|
+
const auto props = this->getProps();
|
|
27
|
+
mockTextInputView_->blockEmitting = YES;
|
|
28
|
+
[mockTextInputView_ updateProps:props oldProps:nullptr];
|
|
29
|
+
return mockTextInputView_;
|
|
30
|
+
}
|
|
31
|
+
|
|
20
32
|
EnrichedTextInputViewShadowNode::EnrichedTextInputViewShadowNode(
|
|
21
33
|
const ShadowNode& sourceShadowNode,
|
|
22
34
|
const ShadowNodeFragment& fragment
|
|
@@ -61,23 +73,23 @@ Size EnrichedTextInputViewShadowNode::measureContent(const LayoutContext& layout
|
|
|
61
73
|
};
|
|
62
74
|
}
|
|
63
75
|
} else {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
__block CGSize estimatedSize;
|
|
77
|
+
|
|
78
|
+
// synchronously dispatch to main thread if needed
|
|
79
|
+
if([NSThread isMainThread]) {
|
|
80
|
+
EnrichedTextInputView *mockTextInputView = setupMockTextInputView_();
|
|
81
|
+
estimatedSize = [mockTextInputView measureSize:layoutConstraints.maximumSize.width];
|
|
82
|
+
} else {
|
|
83
|
+
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
84
|
+
EnrichedTextInputView *mockTextInputView = setupMockTextInputView_();
|
|
85
|
+
estimatedSize = [mockTextInputView measureSize:layoutConstraints.maximumSize.width];
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
estimatedSize.width,
|
|
91
|
+
MIN(estimatedSize.height, layoutConstraints.maximumSize.height)
|
|
92
|
+
};
|
|
81
93
|
}
|
|
82
94
|
|
|
83
95
|
return Size();
|
|
@@ -7,13 +7,17 @@
|
|
|
7
7
|
|
|
8
8
|
@implementation BlockQuoteStyle {
|
|
9
9
|
EnrichedTextInputView *_input;
|
|
10
|
+
NSArray *_stylesToExclude;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
+ (StyleType)getStyleType { return BlockQuote; }
|
|
13
14
|
|
|
15
|
+
+ (BOOL)isParagraphStyle { return YES; }
|
|
16
|
+
|
|
14
17
|
- (instancetype)initWithInput:(id)input {
|
|
15
18
|
self = [super init];
|
|
16
19
|
_input = (EnrichedTextInputView *)input;
|
|
20
|
+
_stylesToExclude = @[ @(InlineCode), @(Mention), @(Link) ];
|
|
17
21
|
return self;
|
|
18
22
|
}
|
|
19
23
|
|
|
@@ -175,31 +179,6 @@
|
|
|
175
179
|
];
|
|
176
180
|
}
|
|
177
181
|
|
|
178
|
-
// gets ranges that aren't link, mention or inline code
|
|
179
|
-
- (NSArray *)getProperColorRangesIn:(NSRange)range {
|
|
180
|
-
LinkStyle *linkStyle = _input->stylesDict[@([LinkStyle getStyleType])];
|
|
181
|
-
MentionStyle *mentionStyle = _input->stylesDict[@([MentionStyle getStyleType])];
|
|
182
|
-
InlineCodeStyle *codeStyle = _input->stylesDict[@([InlineCodeStyle getStyleType])];
|
|
183
|
-
|
|
184
|
-
NSMutableArray *newRanges = [[NSMutableArray alloc] init];
|
|
185
|
-
int lastRangeLocation = range.location;
|
|
186
|
-
|
|
187
|
-
for(int i = range.location; i < range.location + range.length; i++) {
|
|
188
|
-
NSRange currentRange = NSMakeRange(i, 1);
|
|
189
|
-
if([linkStyle detectStyle:currentRange] || [mentionStyle detectStyle:currentRange] || [codeStyle detectStyle:currentRange]) {
|
|
190
|
-
if(i - lastRangeLocation > 0) {
|
|
191
|
-
[newRanges addObject:[NSValue valueWithRange:NSMakeRange(lastRangeLocation, i - lastRangeLocation)]];
|
|
192
|
-
}
|
|
193
|
-
lastRangeLocation = i+1;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if(lastRangeLocation < range.location + range.length) {
|
|
197
|
-
[newRanges addObject:[NSValue valueWithRange:NSMakeRange(lastRangeLocation, range.location + range.length - lastRangeLocation)]];
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return newRanges;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
182
|
// general checkup correcting blockquote color
|
|
204
183
|
// since links, mentions and inline code affects coloring, the checkup gets done only outside of them
|
|
205
184
|
- (void)manageBlockquoteColor {
|
|
@@ -212,7 +191,7 @@
|
|
|
212
191
|
NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:wholeRange];
|
|
213
192
|
for(NSValue *pValue in paragraphs) {
|
|
214
193
|
NSRange paragraphRange = [pValue rangeValue];
|
|
215
|
-
NSArray *properRanges = [
|
|
194
|
+
NSArray *properRanges = [OccurenceUtils getRangesWithout:_stylesToExclude withInput:_input inRange:paragraphRange];
|
|
216
195
|
|
|
217
196
|
for(NSValue *value in properRanges) {
|
|
218
197
|
NSRange currRange = [value rangeValue];
|