react-native-enriched 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +3 -9
  2. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +1 -1
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +1 -1
  4. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +0 -45
  5. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +19 -2
  6. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +3 -3
  7. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +2 -0
  8. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +36 -1
  9. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +132 -11
  10. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +4 -0
  11. package/android/src/main/java/com/swmansion/enriched/spans/utils/ForceRedrawSpan.kt +13 -0
  12. package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +2 -9
  13. package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +1 -0
  14. package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +110 -3
  15. package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +57 -30
  16. package/android/src/main/java/com/swmansion/enriched/utils/AsyncDrawable.kt +91 -0
  17. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +16 -13
  18. package/android/src/main/java/com/swmansion/enriched/utils/ResourceManager.kt +26 -0
  19. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +3 -0
  20. package/android/src/main/res/drawable/broken_image.xml +10 -0
  21. package/ios/EnrichedTextInputView.h +3 -0
  22. package/ios/EnrichedTextInputView.mm +97 -29
  23. package/ios/config/InputConfig.h +6 -0
  24. package/ios/config/InputConfig.mm +32 -0
  25. package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +0 -45
  26. package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +20 -4
  27. package/ios/inputParser/InputParser.mm +147 -24
  28. package/ios/internals/EnrichedTextInputViewShadowNode.h +1 -0
  29. package/ios/internals/EnrichedTextInputViewShadowNode.mm +29 -17
  30. package/ios/styles/BlockQuoteStyle.mm +5 -26
  31. package/ios/styles/BoldStyle.mm +2 -0
  32. package/ios/styles/CodeBlockStyle.mm +228 -0
  33. package/ios/styles/H1Style.mm +1 -0
  34. package/ios/styles/H2Style.mm +1 -0
  35. package/ios/styles/H3Style.mm +1 -0
  36. package/ios/styles/ImageStyle.mm +158 -0
  37. package/ios/styles/InlineCodeStyle.mm +2 -0
  38. package/ios/styles/ItalicStyle.mm +2 -0
  39. package/ios/styles/LinkStyle.mm +8 -0
  40. package/ios/styles/MentionStyle.mm +133 -36
  41. package/ios/styles/OrderedListStyle.mm +2 -0
  42. package/ios/styles/StrikethroughStyle.mm +2 -0
  43. package/ios/styles/UnderlineStyle.mm +2 -0
  44. package/ios/styles/UnorderedListStyle.mm +2 -0
  45. package/ios/utils/BaseStyleProtocol.h +1 -0
  46. package/ios/utils/ImageData.h +10 -0
  47. package/ios/utils/ImageData.mm +4 -0
  48. package/ios/utils/LayoutManagerExtension.mm +118 -3
  49. package/ios/utils/OccurenceUtils.h +4 -0
  50. package/ios/utils/OccurenceUtils.mm +47 -0
  51. package/ios/utils/ParagraphAttributesUtils.h +1 -0
  52. package/ios/utils/ParagraphAttributesUtils.mm +87 -20
  53. package/ios/utils/StyleHeaders.h +12 -0
  54. package/ios/utils/ZeroWidthSpaceUtils.mm +22 -10
  55. package/lib/module/EnrichedTextInput.js +2 -2
  56. package/lib/module/EnrichedTextInput.js.map +1 -1
  57. package/lib/module/EnrichedTextInputNativeComponent.ts +6 -5
  58. package/lib/module/normalizeHtmlStyle.js +0 -4
  59. package/lib/module/normalizeHtmlStyle.js.map +1 -1
  60. package/lib/typescript/src/EnrichedTextInput.d.ts +1 -5
  61. package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -1
  62. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +1 -5
  63. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -1
  64. package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +1 -1
  65. package/package.json +1 -1
  66. package/src/EnrichedTextInput.tsx +3 -7
  67. package/src/EnrichedTextInputNativeComponent.ts +6 -5
  68. package/src/normalizeHtmlStyle.ts +0 -4
@@ -36,6 +36,11 @@
36
36
  UIColor *_linkColor;
37
37
  TextDecorationLineEnum _linkDecorationLine;
38
38
  NSDictionary *_mentionProperties;
39
+ UIColor *_codeBlockFgColor;
40
+ CGFloat _codeBlockBorderRadius;
41
+ UIColor *_codeBlockBgColor;
42
+ CGFloat _imageWidth;
43
+ CGFloat _imageHeight;
39
44
  }
40
45
 
41
46
  - (instancetype) init {
@@ -79,6 +84,9 @@
79
84
  copy->_linkColor = [_linkColor copy];
80
85
  copy->_linkDecorationLine = [_linkDecorationLine copy];
81
86
  copy->_mentionProperties = [_mentionProperties mutableCopy];
87
+ copy->_codeBlockFgColor = [_codeBlockFgColor copy];
88
+ copy->_codeBlockBgColor = [_codeBlockBgColor copy];
89
+ copy->_codeBlockBorderRadius = _codeBlockBorderRadius;
82
90
  return copy;
83
91
  }
84
92
 
@@ -379,4 +387,28 @@
379
387
  return fallbackProps;
380
388
  }
381
389
 
390
+ - (UIColor *)codeBlockFgColor {
391
+ return _codeBlockFgColor;
392
+ }
393
+
394
+ - (void)setCodeBlockFgColor:(UIColor *)newValue {
395
+ _codeBlockFgColor = newValue;
396
+ }
397
+
398
+ - (UIColor *)codeBlockBgColor {
399
+ return _codeBlockBgColor;
400
+ }
401
+
402
+ - (void)setCodeBlockBgColor:(UIColor *)newValue {
403
+ _codeBlockBgColor = newValue;
404
+ }
405
+
406
+ - (CGFloat)codeBlockBorderRadius {
407
+ return _codeBlockBorderRadius;
408
+ }
409
+
410
+ - (void)setCodeBlockBorderRadius:(CGFloat)newValue {
411
+ _codeBlockBorderRadius = newValue;
412
+ }
413
+
382
414
  @end
@@ -309,45 +309,6 @@ static inline folly::dynamic toDynamic(const EnrichedTextInputViewHtmlStyleAStru
309
309
  }
310
310
  #endif
311
311
 
312
- struct EnrichedTextInputViewHtmlStyleImgStruct {
313
- Float width{0.0};
314
- Float height{0.0};
315
-
316
- #ifdef RN_SERIALIZABLE_STATE
317
- bool operator==(const EnrichedTextInputViewHtmlStyleImgStruct&) const = default;
318
-
319
- folly::dynamic toDynamic() const {
320
- folly::dynamic result = folly::dynamic::object();
321
- result["width"] = width;
322
- result["height"] = height;
323
- return result;
324
- }
325
- #endif
326
- };
327
-
328
- static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedTextInputViewHtmlStyleImgStruct &result) {
329
- auto map = (std::unordered_map<std::string, RawValue>)value;
330
-
331
- auto tmp_width = map.find("width");
332
- if (tmp_width != map.end()) {
333
- fromRawValue(context, tmp_width->second, result.width);
334
- }
335
- auto tmp_height = map.find("height");
336
- if (tmp_height != map.end()) {
337
- fromRawValue(context, tmp_height->second, result.height);
338
- }
339
- }
340
-
341
- static inline std::string toString(const EnrichedTextInputViewHtmlStyleImgStruct &value) {
342
- return "[Object EnrichedTextInputViewHtmlStyleImgStruct]";
343
- }
344
-
345
- #ifdef RN_SERIALIZABLE_STATE
346
- static inline folly::dynamic toDynamic(const EnrichedTextInputViewHtmlStyleImgStruct &value) {
347
- return value.toDynamic();
348
- }
349
- #endif
350
-
351
312
  struct EnrichedTextInputViewHtmlStyleOlStruct {
352
313
  Float gapWidth{0.0};
353
314
  Float marginLeft{0.0};
@@ -459,7 +420,6 @@ struct EnrichedTextInputViewHtmlStyleStruct {
459
420
  EnrichedTextInputViewHtmlStyleCodeStruct code{};
460
421
  EnrichedTextInputViewHtmlStyleAStruct a{};
461
422
  folly::dynamic mention{};
462
- EnrichedTextInputViewHtmlStyleImgStruct img{};
463
423
  EnrichedTextInputViewHtmlStyleOlStruct ol{};
464
424
  EnrichedTextInputViewHtmlStyleUlStruct ul{};
465
425
 
@@ -476,7 +436,6 @@ struct EnrichedTextInputViewHtmlStyleStruct {
476
436
  result["code"] = ::facebook::react::toDynamic(code);
477
437
  result["a"] = ::facebook::react::toDynamic(a);
478
438
  result["mention"] = mention;
479
- result["img"] = ::facebook::react::toDynamic(img);
480
439
  result["ol"] = ::facebook::react::toDynamic(ol);
481
440
  result["ul"] = ::facebook::react::toDynamic(ul);
482
441
  return result;
@@ -519,10 +478,6 @@ static inline void fromRawValue(const PropsParserContext& context, const RawValu
519
478
  if (tmp_mention != map.end()) {
520
479
  fromRawValue(context, tmp_mention->second, result.mention);
521
480
  }
522
- auto tmp_img = map.find("img");
523
- if (tmp_img != map.end()) {
524
- fromRawValue(context, tmp_img->second, result.img);
525
- }
526
481
  auto tmp_ol = map.find("ol");
527
482
  if (tmp_ol != map.end()) {
528
483
  fromRawValue(context, tmp_ol->second, result.ol);
@@ -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
 
@@ -29,6 +29,7 @@
29
29
  BOOL inUnorderedList = NO;
30
30
  BOOL inOrderedList = NO;
31
31
  BOOL inBlockQuote = NO;
32
+ BOOL inCodeBlock = NO;
32
33
  unichar lastCharacter = 0;
33
34
 
34
35
  for(int i = 0; i < text.length; i++) {
@@ -85,6 +86,9 @@
85
86
 
86
87
  // append closing tags
87
88
  for(NSNumber *style in sortedEndedStyles) {
89
+ if ([style isEqualToNumber: @([ImageStyle getStyleType])]) {
90
+ continue;
91
+ }
88
92
  NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:currentRange.location];
89
93
  [result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
90
94
  }
@@ -95,7 +99,8 @@
95
99
  [previousActiveStyles containsObject:@([H1Style getStyleType])] ||
96
100
  [previousActiveStyles containsObject:@([H2Style getStyleType])] ||
97
101
  [previousActiveStyles containsObject:@([H3Style getStyleType])] ||
98
- [previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]
102
+ [previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] ||
103
+ [previousActiveStyles containsObject:@([CodeBlockStyle getStyleType])]
99
104
  ) {
100
105
  // do nothing, proper closing paragraph tags have been already appended
101
106
  } else {
@@ -128,6 +133,11 @@
128
133
  inBlockQuote = NO;
129
134
  [result appendString:@"\n</blockquote>"];
130
135
  }
136
+ // handle ending codeblock
137
+ if(inCodeBlock && ![currentActiveStyles containsObject:@([CodeBlockStyle getStyleType])]) {
138
+ inCodeBlock = NO;
139
+ [result appendString:@"\n</codeblock>"];
140
+ }
131
141
 
132
142
  // handle starting unordered list
133
143
  if(!inUnorderedList && [currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])]) {
@@ -144,6 +154,11 @@
144
154
  inBlockQuote = YES;
145
155
  [result appendString:@"\n<blockquote>"];
146
156
  }
157
+ // handle starting codeblock
158
+ if(!inCodeBlock && [currentActiveStyles containsObject:@([CodeBlockStyle getStyleType])]) {
159
+ inCodeBlock = YES;
160
+ [result appendString:@"\n<codeblock>"];
161
+ }
147
162
 
148
163
  // don't add the <p> tag if some paragraph styles are present
149
164
  if([currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])] ||
@@ -151,7 +166,8 @@
151
166
  [currentActiveStyles containsObject:@([H1Style getStyleType])] ||
152
167
  [currentActiveStyles containsObject:@([H2Style getStyleType])] ||
153
168
  [currentActiveStyles containsObject:@([H3Style getStyleType])] ||
154
- [currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]
169
+ [currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] ||
170
+ [currentActiveStyles containsObject:@([CodeBlockStyle getStyleType])]
155
171
  ) {
156
172
  [result appendString:@"\n"];
157
173
  } else {
@@ -184,25 +200,51 @@
184
200
  }
185
201
  }
186
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
+
187
222
  // they are sorted in a descending order
188
223
  NSArray<NSNumber*> *sortedEndedStyles = [fixedEndedStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:NO]]];
189
224
 
190
225
  // append closing tags
191
226
  for(NSNumber *style in sortedEndedStyles) {
227
+ if ([style isEqualToNumber: @([ImageStyle getStyleType])]) {
228
+ continue;
229
+ }
192
230
  NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:currentRange.location];
193
231
  [result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
194
232
  }
195
233
 
196
- // get styles that have begun: they are sorted in a ascending manner to properly keep tags' FILO order
197
- NSMutableSet<NSNumber *> *newStyles = [currentActiveStyles mutableCopy];
198
- [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
199
236
  [newStyles unionSet: stylesToBeReAdded];
200
237
  NSArray<NSNumber*> *sortedNewStyles = [newStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:YES]]];
201
238
 
202
239
  // append opening tags
203
240
  for(NSNumber *style in sortedNewStyles) {
204
241
  NSString *tagContent = [self tagContentForStyle:style openingTag:YES location:currentRange.location];
205
- [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
+ }
206
248
  }
207
249
 
208
250
  // append the letter and escape it if needed
@@ -223,6 +265,9 @@
223
265
 
224
266
  // append closing tags
225
267
  for(NSNumber *style in sortedEndedStyles) {
268
+ if ([style isEqualToNumber: @([ImageStyle getStyleType])]) {
269
+ continue;
270
+ }
226
271
  NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:_input->textView.textStorage.string.length - 1];
227
272
  [result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
228
273
  }
@@ -235,6 +280,8 @@
235
280
  [result appendString:@"\n</ol>"];
236
281
  } else if([previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]) {
237
282
  [result appendString:@"\n</blockquote>"];
283
+ } else if([previousActiveStyles containsObject:@([CodeBlockStyle getStyleType])]) {
284
+ [result appendString:@"\n</codeblock>"];
238
285
  } else if(
239
286
  [previousActiveStyles containsObject:@([H1Style getStyleType])] ||
240
287
  [previousActiveStyles containsObject:@([H2Style getStyleType])] ||
@@ -258,6 +305,10 @@
258
305
  inBlockQuote = NO;
259
306
  [result appendString:@"\n</blockquote>"];
260
307
  }
308
+ if(inCodeBlock) {
309
+ inCodeBlock = NO;
310
+ [result appendString:@"\n</codeblock>"];
311
+ }
261
312
  }
262
313
 
263
314
  [result appendString: @"\n</html>"];
@@ -273,6 +324,19 @@
273
324
  return @"b";
274
325
  } else if([style isEqualToNumber: @([ItalicStyle getStyleType])]) {
275
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
+ }
276
340
  } else if([style isEqualToNumber: @([UnderlineStyle getStyleType])]) {
277
341
  return @"u";
278
342
  } else if([style isEqualToNumber: @([StrikethroughStyle getStyleType])]) {
@@ -328,8 +392,8 @@
328
392
  return @"h3";
329
393
  } else if([style isEqualToNumber:@([UnorderedListStyle getStyleType])] || [style isEqualToNumber:@([OrderedListStyle getStyleType])]) {
330
394
  return @"li";
331
- } else if([style isEqualToNumber:@([BlockQuoteStyle getStyleType])]) {
332
- // 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>
333
397
  return @"p";
334
398
  }
335
399
  return @"";
@@ -393,6 +457,9 @@
393
457
  } else if([styleType isEqualToNumber: @([MentionStyle getStyleType])]) {
394
458
  MentionParams *params = (MentionParams *)stylePair.styleValue;
395
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];
396
463
  } else {
397
464
  [baseStyle addAttributes:styleRange];
398
465
  }
@@ -447,6 +514,8 @@
447
514
  fixedHtml = [self stringByAddingNewlinesToTag:@"</ol>" inString:fixedHtml leading:YES trailing:YES];
448
515
  fixedHtml = [self stringByAddingNewlinesToTag:@"<blockquote>" inString:fixedHtml leading:YES trailing:YES];
449
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];
450
519
 
451
520
  // line opening tags
452
521
  fixedHtml = [self stringByAddingNewlinesToTag:@"<p>" inString:fixedHtml leading:YES trailing:NO];
@@ -481,6 +550,24 @@
481
550
  return str;
482
551
  }
483
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
+
484
571
  - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
485
572
  NSMutableString *plainText = [[NSMutableString alloc] initWithString: @""];
486
573
  NSMutableDictionary *ongoingTags = [[NSMutableDictionary alloc] init];
@@ -508,6 +595,14 @@
508
595
  gettingTagName = NO;
509
596
  gettingTagParams = NO;
510
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
+
511
606
  if([currentTagName isEqualToString:@"p"] || [currentTagName isEqualToString:@"br"] || [currentTagName isEqualToString:@"li"]) {
512
607
  // do nothing, we don't include these tags in styles
513
608
  } else if(!closingTag) {
@@ -520,31 +615,22 @@
520
615
  ongoingTags[currentTagName] = tagArr;
521
616
 
522
617
  // skip one newline after opening tags that are in separate lines intentionally
523
- if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"]) {
618
+ if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"] || [currentTagName isEqualToString:@"codeblock"]) {
524
619
  i += 1;
525
620
  }
621
+
622
+ if (isSelfClosing) {
623
+ [self finalizeTagEntry:currentTagName ongoingTags:ongoingTags initiallyProcessedTags:initiallyProcessedTags plainText:plainText];
624
+ }
526
625
  } else {
527
626
  // we finish closing tags - pack tag name, tag range and optionally tag params into an entry that goes inside initiallyProcessedTags
528
627
 
529
628
  // skip one newline that was added before some closing tags that are in separate lines
530
- if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"]) {
629
+ if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"] || [currentTagName isEqualToString:@"codeblock"]) {
531
630
  plainText = [[plainText substringWithRange: NSMakeRange(0, plainText.length - 1)] mutableCopy];
532
631
  }
533
632
 
534
- NSMutableArray *tagEntry = [[NSMutableArray alloc] init];
535
-
536
- NSArray *tagData = ongoingTags[currentTagName];
537
- NSInteger tagLocation = [((NSNumber *)tagData[0]) intValue];
538
- NSRange tagRange = NSMakeRange(tagLocation, plainText.length - tagLocation);
539
-
540
- [tagEntry addObject:[currentTagName copy]];
541
- [tagEntry addObject:[NSValue valueWithRange:tagRange]];
542
- if(tagData.count > 1) {
543
- [tagEntry addObject:[(NSString *)tagData[1] copy]];
544
- }
545
-
546
- [initiallyProcessedTags addObject:tagEntry];
547
- [ongoingTags removeObjectForKey:currentTagName];
633
+ [self finalizeTagEntry:currentTagName ongoingTags:ongoingTags initiallyProcessedTags:initiallyProcessedTags plainText:plainText];
548
634
  }
549
635
  // post-tag cleanup
550
636
  closingTag = NO;
@@ -603,6 +689,41 @@
603
689
  [styleArr addObject:@([BoldStyle getStyleType])];
604
690
  } else if([tagName isEqualToString:@"i"]) {
605
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;
606
727
  } else if([tagName isEqualToString:@"u"]) {
607
728
  [styleArr addObject:@([UnderlineStyle getStyleType])];
608
729
  } else if([tagName isEqualToString:@"s"]) {
@@ -668,6 +789,8 @@
668
789
  [styleArr addObject:@([OrderedListStyle getStyleType])];
669
790
  } else if([tagName isEqualToString:@"blockquote"]) {
670
791
  [styleArr addObject:@([BlockQuoteStyle getStyleType])];
792
+ } else if([tagName isEqualToString:@"codeblock"]) {
793
+ [styleArr addObject:@([CodeBlockStyle getStyleType])];
671
794
  } else {
672
795
  // some other external tags like span just don't get put into the processed styles
673
796
  continue;
@@ -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;