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.
Files changed (80) hide show
  1. package/README.md +3 -9
  2. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +4 -1
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +2 -1
  4. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.cpp +5 -0
  5. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +1 -45
  6. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +53 -12
  7. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +7 -56
  8. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +19 -22
  9. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +2 -0
  10. package/android/src/main/java/com/swmansion/enriched/MeasurementStore.kt +158 -0
  11. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +36 -1
  12. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +132 -11
  13. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +65 -46
  14. package/android/src/main/java/com/swmansion/enriched/spans/utils/ForceRedrawSpan.kt +13 -0
  15. package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +2 -9
  16. package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +1 -0
  17. package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +110 -3
  18. package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +75 -32
  19. package/android/src/main/java/com/swmansion/enriched/utils/AsyncDrawable.kt +91 -0
  20. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +38 -15
  21. package/android/src/main/java/com/swmansion/enriched/utils/ResourceManager.kt +26 -0
  22. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +3 -1
  23. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +1 -1
  24. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.cpp +15 -2
  25. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +1 -0
  26. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.cpp +1 -2
  27. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/conversions.h +27 -0
  28. package/android/src/main/res/drawable/broken_image.xml +10 -0
  29. package/ios/EnrichedTextInputView.h +3 -1
  30. package/ios/EnrichedTextInputView.mm +167 -68
  31. package/ios/config/InputConfig.h +6 -0
  32. package/ios/config/InputConfig.mm +32 -0
  33. package/ios/generated/RNEnrichedTextInputViewSpec/Props.cpp +5 -0
  34. package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +1 -45
  35. package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +20 -4
  36. package/ios/inputParser/InputParser.mm +179 -31
  37. package/ios/inputTextView/InputTextView.mm +3 -5
  38. package/ios/internals/EnrichedTextInputViewShadowNode.h +1 -0
  39. package/ios/internals/EnrichedTextInputViewShadowNode.mm +29 -17
  40. package/ios/styles/BlockQuoteStyle.mm +5 -26
  41. package/ios/styles/BoldStyle.mm +2 -0
  42. package/ios/styles/CodeBlockStyle.mm +228 -0
  43. package/ios/styles/H1Style.mm +1 -0
  44. package/ios/styles/H2Style.mm +1 -0
  45. package/ios/styles/H3Style.mm +1 -0
  46. package/ios/styles/ImageStyle.mm +158 -0
  47. package/ios/styles/InlineCodeStyle.mm +2 -0
  48. package/ios/styles/ItalicStyle.mm +2 -0
  49. package/ios/styles/LinkStyle.mm +15 -7
  50. package/ios/styles/MentionStyle.mm +133 -36
  51. package/ios/styles/OrderedListStyle.mm +5 -8
  52. package/ios/styles/StrikethroughStyle.mm +2 -0
  53. package/ios/styles/UnderlineStyle.mm +2 -0
  54. package/ios/styles/UnorderedListStyle.mm +5 -8
  55. package/ios/utils/BaseStyleProtocol.h +1 -0
  56. package/ios/utils/ImageData.h +10 -0
  57. package/ios/utils/ImageData.mm +4 -0
  58. package/ios/utils/LayoutManagerExtension.mm +118 -3
  59. package/ios/utils/OccurenceUtils.h +4 -0
  60. package/ios/utils/OccurenceUtils.mm +47 -0
  61. package/ios/utils/ParagraphAttributesUtils.h +1 -0
  62. package/ios/utils/ParagraphAttributesUtils.mm +87 -20
  63. package/ios/utils/StringExtension.h +1 -1
  64. package/ios/utils/StringExtension.mm +17 -8
  65. package/ios/utils/StyleHeaders.h +12 -0
  66. package/ios/utils/ZeroWidthSpaceUtils.mm +22 -10
  67. package/lib/module/EnrichedTextInput.js +4 -2
  68. package/lib/module/EnrichedTextInput.js.map +1 -1
  69. package/lib/module/EnrichedTextInputNativeComponent.ts +7 -5
  70. package/lib/module/normalizeHtmlStyle.js +0 -4
  71. package/lib/module/normalizeHtmlStyle.js.map +1 -1
  72. package/lib/typescript/src/EnrichedTextInput.d.ts +3 -6
  73. package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -1
  74. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +2 -5
  75. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -1
  76. package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +1 -1
  77. package/package.json +1 -1
  78. package/src/EnrichedTextInput.tsx +6 -7
  79. package/src/EnrichedTextInputNativeComponent.ts +7 -5
  80. 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] != 1) {
306
- RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedTextInputView", commandName, (int)[args count], 1);
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
- [componentView addImage:uri];
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
- // get styles that have begun: they are sorted in a ascending manner to properly keep tags' FILO order
196
- NSMutableSet<NSNumber *> *newStyles = [currentActiveStyles mutableCopy];
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
- [result appendString: [NSString stringWithFormat:@"<%@>", tagContent]];
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 numberWithInt:plainText.length]];
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
- NSMutableArray *tagEntry = [[NSMutableArray alloc] init];
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
- [plainText appendString:currentCharacterStr];
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, params.length - 7)];
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
- // some other external tags like span just don't get put into the processed styles
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 *escapedHtml = [NSString stringByEscapingHtml:[typedInput->parser parseToHtmlFromRange:typedInput->textView.selectedRange]];
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 : escapedHtml,
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
- // unescape the html
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) {
@@ -35,6 +35,7 @@ class EnrichedTextInputViewShadowNode : public ConcreteViewShadowNode<
35
35
 
36
36
  private:
37
37
  int localForceHeightRecalculationCounter_;
38
+ id setupMockTextInputView_() const;
38
39
  };
39
40
 
40
41
  } // namespace facebook::react
@@ -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
- // on the very first call there is no componentView that we can query for the component height
65
- // thus, a little heuristic: just put a height that is exactly height of letter "I" with default apple font and size from props
66
- // in a lot of cases it will be the desired height
67
- // in others, the jump on the second call will at least be smaller
68
- const auto props = this->getProps();
69
- const auto &typedProps = *std::static_pointer_cast<EnrichedTextInputViewProps const>(props);
70
- NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@"I" attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:typedProps.fontSize]}];
71
- CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrStr);
72
- const CGSize &suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(
73
- framesetter,
74
- CFRangeMake(0, 1),
75
- nullptr,
76
- CGSizeMake(layoutConstraints.maximumSize.width, DBL_MAX),
77
- nullptr
78
- );
79
-
80
- return {suggestedSize.width, suggestedSize.height};
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 = [self getProperColorRangesIn:paragraphRange];
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];
@@ -9,6 +9,8 @@
9
9
 
10
10
  + (StyleType)getStyleType { return Bold; }
11
11
 
12
+ + (BOOL)isParagraphStyle { return NO; }
13
+
12
14
  - (instancetype)initWithInput:(id)input {
13
15
  self = [super init];
14
16
  _input = (EnrichedTextInputView *)input;