react-native-enriched 0.1.6 → 0.2.1

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 (123) hide show
  1. package/README.md +4 -14
  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/EventEmitters.cpp +10 -0
  5. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.h +7 -0
  6. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +0 -45
  7. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +111 -2
  8. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +9 -3
  9. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +2 -0
  10. package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +1 -1
  11. package/android/src/main/java/com/swmansion/enriched/events/OnRequestHtmlResultEvent.kt +33 -0
  12. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +6 -0
  13. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt +6 -0
  14. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +42 -1
  15. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt +6 -0
  16. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt +6 -0
  17. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt +6 -0
  18. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +135 -9
  19. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +6 -0
  20. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt +5 -0
  21. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt +6 -0
  22. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt +6 -0
  23. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +6 -0
  24. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +13 -3
  25. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt +5 -0
  26. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt +5 -0
  27. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +6 -0
  28. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt +4 -0
  29. package/android/src/main/java/com/swmansion/enriched/spans/utils/ForceRedrawSpan.kt +13 -0
  30. package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +80 -9
  31. package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +1 -0
  32. package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +188 -5
  33. package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +57 -30
  34. package/android/src/main/java/com/swmansion/enriched/utils/AsyncDrawable.kt +91 -0
  35. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +24 -13
  36. package/android/src/main/java/com/swmansion/enriched/utils/ResourceManager.kt +26 -0
  37. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +3 -0
  38. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.cpp +6 -6
  39. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.h +6 -6
  40. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputComponentDescriptor.h +19 -19
  41. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.cpp +40 -51
  42. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +13 -15
  43. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.cpp +23 -21
  44. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.h +35 -36
  45. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.cpp +4 -4
  46. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.h +13 -14
  47. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/conversions.h +12 -13
  48. package/android/src/main/res/drawable/broken_image.xml +10 -0
  49. package/ios/EnrichedTextInputView.h +27 -12
  50. package/ios/EnrichedTextInputView.mm +906 -547
  51. package/ios/attachments/ImageAttachment.h +10 -0
  52. package/ios/attachments/ImageAttachment.mm +34 -0
  53. package/ios/attachments/MediaAttachment.h +23 -0
  54. package/ios/attachments/MediaAttachment.mm +31 -0
  55. package/ios/config/InputConfig.h +12 -6
  56. package/ios/config/InputConfig.mm +71 -33
  57. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.cpp +10 -0
  58. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.h +7 -0
  59. package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +0 -45
  60. package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +41 -4
  61. package/ios/inputParser/InputParser.h +5 -5
  62. package/ios/inputParser/InputParser.mm +867 -333
  63. package/ios/inputTextView/InputTextView.h +1 -1
  64. package/ios/inputTextView/InputTextView.mm +100 -59
  65. package/ios/internals/EnrichedTextInputViewComponentDescriptor.h +11 -9
  66. package/ios/internals/EnrichedTextInputViewShadowNode.h +28 -24
  67. package/ios/internals/EnrichedTextInputViewShadowNode.mm +64 -47
  68. package/ios/internals/EnrichedTextInputViewState.h +3 -1
  69. package/ios/styles/BlockQuoteStyle.mm +192 -142
  70. package/ios/styles/BoldStyle.mm +96 -62
  71. package/ios/styles/CodeBlockStyle.mm +304 -0
  72. package/ios/styles/H1Style.mm +10 -3
  73. package/ios/styles/H2Style.mm +10 -3
  74. package/ios/styles/H3Style.mm +10 -3
  75. package/ios/styles/HeadingStyleBase.mm +129 -84
  76. package/ios/styles/ImageStyle.mm +160 -0
  77. package/ios/styles/InlineCodeStyle.mm +149 -84
  78. package/ios/styles/ItalicStyle.mm +77 -51
  79. package/ios/styles/LinkStyle.mm +353 -224
  80. package/ios/styles/MentionStyle.mm +434 -220
  81. package/ios/styles/OrderedListStyle.mm +172 -105
  82. package/ios/styles/StrikethroughStyle.mm +53 -34
  83. package/ios/styles/UnderlineStyle.mm +69 -45
  84. package/ios/styles/UnorderedListStyle.mm +170 -105
  85. package/ios/utils/BaseStyleProtocol.h +3 -2
  86. package/ios/utils/ColorExtension.mm +7 -5
  87. package/ios/utils/FontExtension.mm +42 -27
  88. package/ios/utils/ImageData.h +10 -0
  89. package/ios/utils/ImageData.mm +4 -0
  90. package/ios/utils/LayoutManagerExtension.h +1 -1
  91. package/ios/utils/LayoutManagerExtension.mm +334 -109
  92. package/ios/utils/MentionParams.h +0 -1
  93. package/ios/utils/MentionStyleProps.h +1 -1
  94. package/ios/utils/MentionStyleProps.mm +27 -20
  95. package/ios/utils/OccurenceUtils.h +42 -38
  96. package/ios/utils/OccurenceUtils.mm +177 -107
  97. package/ios/utils/ParagraphAttributesUtils.h +6 -1
  98. package/ios/utils/ParagraphAttributesUtils.mm +152 -41
  99. package/ios/utils/ParagraphsUtils.h +2 -1
  100. package/ios/utils/ParagraphsUtils.mm +40 -26
  101. package/ios/utils/StringExtension.h +1 -1
  102. package/ios/utils/StringExtension.mm +19 -16
  103. package/ios/utils/StyleHeaders.h +35 -11
  104. package/ios/utils/TextInsertionUtils.h +13 -2
  105. package/ios/utils/TextInsertionUtils.mm +38 -20
  106. package/ios/utils/WordsUtils.h +2 -1
  107. package/ios/utils/WordsUtils.mm +32 -22
  108. package/ios/utils/ZeroWidthSpaceUtils.h +3 -1
  109. package/ios/utils/ZeroWidthSpaceUtils.mm +153 -75
  110. package/lib/module/EnrichedTextInput.js +41 -3
  111. package/lib/module/EnrichedTextInput.js.map +1 -1
  112. package/lib/module/EnrichedTextInputNativeComponent.ts +17 -5
  113. package/lib/module/normalizeHtmlStyle.js +0 -4
  114. package/lib/module/normalizeHtmlStyle.js.map +1 -1
  115. package/lib/typescript/src/EnrichedTextInput.d.ts +2 -5
  116. package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -1
  117. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +7 -5
  118. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -1
  119. package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +1 -1
  120. package/package.json +8 -1
  121. package/src/EnrichedTextInput.tsx +48 -7
  122. package/src/EnrichedTextInputNativeComponent.ts +17 -5
  123. package/src/normalizeHtmlStyle.ts +0 -4
@@ -1,9 +1,9 @@
1
1
  #import "InputParser.h"
2
2
  #import "EnrichedTextInputView.h"
3
+ #import "StringExtension.h"
3
4
  #import "StyleHeaders.h"
4
- #import "UIView+React.h"
5
5
  #import "TextInsertionUtils.h"
6
- #import "StringExtension.h"
6
+ #import "UIView+React.h"
7
7
 
8
8
  @implementation InputParser {
9
9
  EnrichedTextInputView *_input;
@@ -17,274 +17,445 @@
17
17
 
18
18
  - (NSString *)parseToHtmlFromRange:(NSRange)range {
19
19
  NSInteger offset = range.location;
20
- NSString *text = [_input->textView.textStorage.string substringWithRange:range];
20
+ NSString *text =
21
+ [_input->textView.textStorage.string substringWithRange:range];
21
22
 
22
- if(text.length == 0) {
23
+ if (text.length == 0) {
23
24
  return @"<html>\n<p></p>\n</html>";
24
25
  }
25
26
 
26
- NSMutableString *result = [[NSMutableString alloc] initWithString: @"<html>"];
27
- NSSet<NSNumber *>*previousActiveStyles = [[NSSet<NSNumber *> alloc]init];
27
+ NSMutableString *result = [[NSMutableString alloc] initWithString:@"<html>"];
28
+ NSSet<NSNumber *> *previousActiveStyles = [[NSSet<NSNumber *> alloc] init];
28
29
  BOOL newLine = YES;
29
30
  BOOL inUnorderedList = NO;
30
31
  BOOL inOrderedList = NO;
31
32
  BOOL inBlockQuote = NO;
33
+ BOOL inCodeBlock = NO;
32
34
  unichar lastCharacter = 0;
33
-
34
- for(int i = 0; i < text.length; i++) {
35
+
36
+ for (int i = 0; i < text.length; i++) {
35
37
  NSRange currentRange = NSMakeRange(offset + i, 1);
36
- NSMutableSet<NSNumber *>*currentActiveStyles = [[NSMutableSet<NSNumber *> alloc]init];
37
- NSMutableDictionary *currentActiveStylesBeginning = [[NSMutableDictionary alloc] init];
38
-
38
+ NSMutableSet<NSNumber *> *currentActiveStyles =
39
+ [[NSMutableSet<NSNumber *> alloc] init];
40
+ NSMutableDictionary *currentActiveStylesBeginning =
41
+ [[NSMutableDictionary alloc] init];
42
+
39
43
  // check each existing style existence
40
- for(NSNumber* type in _input->stylesDict) {
44
+ for (NSNumber *type in _input->stylesDict) {
41
45
  id<BaseStyleProtocol> style = _input->stylesDict[type];
42
- if([style detectStyle:currentRange]) {
46
+ if ([style detectStyle:currentRange]) {
43
47
  [currentActiveStyles addObject:type];
44
-
45
- if(![previousActiveStyles member:type]) {
48
+
49
+ if (![previousActiveStyles member:type]) {
46
50
  currentActiveStylesBeginning[type] = [NSNumber numberWithInt:i];
47
51
  }
48
- } else if([previousActiveStyles member:type]) {
52
+ } else if ([previousActiveStyles member:type]) {
49
53
  [currentActiveStylesBeginning removeObjectForKey:type];
50
54
  }
51
55
  }
52
-
53
- NSString *currentCharacterStr = [_input->textView.textStorage.string substringWithRange:currentRange];
54
- unichar currentCharacterChar = [_input->textView.textStorage.string characterAtIndex:currentRange.location];
55
-
56
- if([[NSCharacterSet newlineCharacterSet] characterIsMember:currentCharacterChar]) {
57
- if(newLine) {
58
- // we can either have an empty list item OR need to close the list and put a BR in such a situation
59
- // the existence of the list must be checked on 0 length range, not on the newline character
60
- if(inOrderedList) {
56
+
57
+ NSString *currentCharacterStr =
58
+ [_input->textView.textStorage.string substringWithRange:currentRange];
59
+ unichar currentCharacterChar = [_input->textView.textStorage.string
60
+ characterAtIndex:currentRange.location];
61
+
62
+ if ([[NSCharacterSet newlineCharacterSet]
63
+ characterIsMember:currentCharacterChar]) {
64
+ if (newLine) {
65
+ // we can either have an empty list item OR need to close the list and
66
+ // put a BR in such a situation the existence of the list must be
67
+ // checked on 0 length range, not on the newline character
68
+ if (inOrderedList) {
61
69
  OrderedListStyle *oStyle = _input->stylesDict[@(OrderedList)];
62
- BOOL detected = [oStyle detectStyle: NSMakeRange(currentRange.location, 0)];
63
- if(detected) {
70
+ BOOL detected =
71
+ [oStyle detectStyle:NSMakeRange(currentRange.location, 0)];
72
+ if (detected) {
64
73
  [result appendString:@"\n<li></li>"];
65
74
  } else {
66
75
  [result appendString:@"\n</ol>\n<br>"];
67
76
  inOrderedList = NO;
68
77
  }
69
- } else if(inUnorderedList) {
78
+ } else if (inUnorderedList) {
70
79
  UnorderedListStyle *uStyle = _input->stylesDict[@(UnorderedList)];
71
- BOOL detected = [uStyle detectStyle: NSMakeRange(currentRange.location, 0)];
72
- if(detected) {
80
+ BOOL detected =
81
+ [uStyle detectStyle:NSMakeRange(currentRange.location, 0)];
82
+ if (detected) {
73
83
  [result appendString:@"\n<li></li>"];
74
84
  } else {
75
85
  [result appendString:@"\n</ul>\n<br>"];
76
86
  inUnorderedList = NO;
77
87
  }
88
+ } else if (inBlockQuote) {
89
+ BlockQuoteStyle *bqStyle = _input->stylesDict[@(BlockQuote)];
90
+ BOOL detected =
91
+ [bqStyle detectStyle:NSMakeRange(currentRange.location, 0)];
92
+ if (detected) {
93
+ [result appendString:@"\n<br>"];
94
+ } else {
95
+ [result appendString:@"\n</blockquote>\n<br>"];
96
+ inBlockQuote = NO;
97
+ }
98
+ } else if (inCodeBlock) {
99
+ CodeBlockStyle *cbStyle = _input->stylesDict[@(CodeBlock)];
100
+ BOOL detected =
101
+ [cbStyle detectStyle:NSMakeRange(currentRange.location, 0)];
102
+ if (detected) {
103
+ [result appendString:@"\n<br>"];
104
+ } else {
105
+ [result appendString:@"\n</codeblock>\n<br>"];
106
+ inCodeBlock = NO;
107
+ }
78
108
  } else {
79
109
  [result appendString:@"\n<br>"];
80
110
  }
81
111
  } else {
82
112
  // newline finishes a paragraph and all style tags need to be closed
83
113
  // we use previous styles
84
- NSArray<NSNumber*> *sortedEndedStyles = [previousActiveStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:NO]]];
85
-
114
+ NSArray<NSNumber *> *sortedEndedStyles = [previousActiveStyles
115
+ sortedArrayUsingDescriptors:@[ [NSSortDescriptor
116
+ sortDescriptorWithKey:@"intValue"
117
+ ascending:NO] ]];
118
+
86
119
  // append closing tags
87
- for(NSNumber *style in sortedEndedStyles) {
88
- NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:currentRange.location];
89
- [result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
120
+ for (NSNumber *style in sortedEndedStyles) {
121
+ if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
122
+ continue;
123
+ }
124
+ NSString *tagContent =
125
+ [self tagContentForStyle:style
126
+ openingTag:NO
127
+ location:currentRange.location];
128
+ [result
129
+ appendString:[NSString stringWithFormat:@"</%@>", tagContent]];
90
130
  }
91
-
131
+
92
132
  // append closing paragraph tag
93
- if([previousActiveStyles containsObject:@([UnorderedListStyle getStyleType])] ||
94
- [previousActiveStyles containsObject:@([OrderedListStyle getStyleType])] ||
95
- [previousActiveStyles containsObject:@([H1Style getStyleType])] ||
96
- [previousActiveStyles containsObject:@([H2Style getStyleType])] ||
97
- [previousActiveStyles containsObject:@([H3Style getStyleType])] ||
98
- [previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]
99
- ) {
100
- // do nothing, proper closing paragraph tags have been already appended
133
+ if ([previousActiveStyles
134
+ containsObject:@([UnorderedListStyle getStyleType])] ||
135
+ [previousActiveStyles
136
+ containsObject:@([OrderedListStyle getStyleType])] ||
137
+ [previousActiveStyles containsObject:@([H1Style getStyleType])] ||
138
+ [previousActiveStyles containsObject:@([H2Style getStyleType])] ||
139
+ [previousActiveStyles containsObject:@([H3Style getStyleType])] ||
140
+ [previousActiveStyles
141
+ containsObject:@([BlockQuoteStyle getStyleType])] ||
142
+ [previousActiveStyles
143
+ containsObject:@([CodeBlockStyle getStyleType])]) {
144
+ // do nothing, proper closing paragraph tags have been already
145
+ // appended
101
146
  } else {
102
147
  [result appendString:@"</p>"];
103
148
  }
104
149
  }
105
-
150
+
106
151
  // clear the previous styles
107
- previousActiveStyles = [[NSSet<NSNumber *> alloc]init];
108
-
152
+ previousActiveStyles = [[NSSet<NSNumber *> alloc] init];
153
+
109
154
  // next character opens new paragraph
110
155
  newLine = YES;
111
156
  } else {
112
157
  // new line - open the paragraph
113
- if(newLine) {
158
+ if (newLine) {
114
159
  newLine = NO;
115
-
160
+
116
161
  // handle ending unordered list
117
- if(inUnorderedList && ![currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])]) {
162
+ if (inUnorderedList &&
163
+ ![currentActiveStyles
164
+ containsObject:@([UnorderedListStyle getStyleType])]) {
118
165
  inUnorderedList = NO;
119
166
  [result appendString:@"\n</ul>"];
120
167
  }
121
168
  // handle ending ordered list
122
- if(inOrderedList && ![currentActiveStyles containsObject:@([OrderedListStyle getStyleType])]) {
169
+ if (inOrderedList &&
170
+ ![currentActiveStyles
171
+ containsObject:@([OrderedListStyle getStyleType])]) {
123
172
  inOrderedList = NO;
124
173
  [result appendString:@"\n</ol>"];
125
174
  }
126
175
  // handle ending blockquotes
127
- if(inBlockQuote && ![currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]) {
176
+ if (inBlockQuote &&
177
+ ![currentActiveStyles
178
+ containsObject:@([BlockQuoteStyle getStyleType])]) {
128
179
  inBlockQuote = NO;
129
180
  [result appendString:@"\n</blockquote>"];
130
181
  }
131
-
182
+ // handle ending codeblock
183
+ if (inCodeBlock &&
184
+ ![currentActiveStyles
185
+ containsObject:@([CodeBlockStyle getStyleType])]) {
186
+ inCodeBlock = NO;
187
+ [result appendString:@"\n</codeblock>"];
188
+ }
189
+
132
190
  // handle starting unordered list
133
- if(!inUnorderedList && [currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])]) {
191
+ if (!inUnorderedList &&
192
+ [currentActiveStyles
193
+ containsObject:@([UnorderedListStyle getStyleType])]) {
134
194
  inUnorderedList = YES;
135
195
  [result appendString:@"\n<ul>"];
136
196
  }
137
197
  // handle starting ordered list
138
- if(!inOrderedList && [currentActiveStyles containsObject:@([OrderedListStyle getStyleType])]) {
198
+ if (!inOrderedList &&
199
+ [currentActiveStyles
200
+ containsObject:@([OrderedListStyle getStyleType])]) {
139
201
  inOrderedList = YES;
140
202
  [result appendString:@"\n<ol>"];
141
203
  }
142
204
  // handle starting blockquotes
143
- if(!inBlockQuote && [currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]) {
205
+ if (!inBlockQuote &&
206
+ [currentActiveStyles
207
+ containsObject:@([BlockQuoteStyle getStyleType])]) {
144
208
  inBlockQuote = YES;
145
209
  [result appendString:@"\n<blockquote>"];
146
210
  }
147
-
211
+ // handle starting codeblock
212
+ if (!inCodeBlock &&
213
+ [currentActiveStyles
214
+ containsObject:@([CodeBlockStyle getStyleType])]) {
215
+ inCodeBlock = YES;
216
+ [result appendString:@"\n<codeblock>"];
217
+ }
218
+
148
219
  // don't add the <p> tag if some paragraph styles are present
149
- if([currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])] ||
150
- [currentActiveStyles containsObject:@([OrderedListStyle getStyleType])] ||
151
- [currentActiveStyles containsObject:@([H1Style getStyleType])] ||
152
- [currentActiveStyles containsObject:@([H2Style getStyleType])] ||
153
- [currentActiveStyles containsObject:@([H3Style getStyleType])] ||
154
- [currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]
155
- ) {
220
+ if ([currentActiveStyles
221
+ containsObject:@([UnorderedListStyle getStyleType])] ||
222
+ [currentActiveStyles
223
+ containsObject:@([OrderedListStyle getStyleType])] ||
224
+ [currentActiveStyles containsObject:@([H1Style getStyleType])] ||
225
+ [currentActiveStyles containsObject:@([H2Style getStyleType])] ||
226
+ [currentActiveStyles containsObject:@([H3Style getStyleType])] ||
227
+ [currentActiveStyles
228
+ containsObject:@([BlockQuoteStyle getStyleType])] ||
229
+ [currentActiveStyles
230
+ containsObject:@([CodeBlockStyle getStyleType])]) {
156
231
  [result appendString:@"\n"];
157
232
  } else {
158
233
  [result appendString:@"\n<p>"];
159
234
  }
160
235
  }
161
-
236
+
162
237
  // get styles that have ended
163
- NSMutableSet<NSNumber *> *endedStyles = [previousActiveStyles mutableCopy];
164
- [endedStyles minusSet: currentActiveStyles];
165
-
166
- // also finish styles that should be ended becasue they are nested in a style that ended
238
+ NSMutableSet<NSNumber *> *endedStyles =
239
+ [previousActiveStyles mutableCopy];
240
+ [endedStyles minusSet:currentActiveStyles];
241
+
242
+ // also finish styles that should be ended becasue they are nested in a
243
+ // style that ended
167
244
  NSMutableSet *fixedEndedStyles = [endedStyles mutableCopy];
168
245
  NSMutableSet *stylesToBeReAdded = [[NSMutableSet alloc] init];
169
-
170
- for(NSNumber *style in endedStyles) {
171
- NSInteger styleBeginning = [currentActiveStylesBeginning[style] integerValue];
172
-
173
- for(NSNumber *activeStyle in currentActiveStyles) {
174
- NSInteger activeStyleBeginning = [currentActiveStylesBeginning[activeStyle] integerValue];
175
-
176
- // we end the styles that began after the currently ended style but not at the "i" (cause the old style ended at exactly "i-1"
177
- // also the ones that began in the exact same place but are "inner" in relation to them due to StyleTypeEnum integer values
178
-
179
- if((activeStyleBeginning > styleBeginning && activeStyleBeginning < i) ||
180
- (activeStyleBeginning == styleBeginning && activeStyleBeginning < i && [activeStyle integerValue] > [style integerValue])) {
246
+
247
+ for (NSNumber *style in endedStyles) {
248
+ NSInteger styleBeginning =
249
+ [currentActiveStylesBeginning[style] integerValue];
250
+
251
+ for (NSNumber *activeStyle in currentActiveStyles) {
252
+ NSInteger activeStyleBeginning =
253
+ [currentActiveStylesBeginning[activeStyle] integerValue];
254
+
255
+ // we end the styles that began after the currently ended style but
256
+ // not at the "i" (cause the old style ended at exactly "i-1" also the
257
+ // ones that began in the exact same place but are "inner" in relation
258
+ // to them due to StyleTypeEnum integer values
259
+
260
+ if ((activeStyleBeginning > styleBeginning &&
261
+ activeStyleBeginning < i) ||
262
+ (activeStyleBeginning == styleBeginning &&
263
+ activeStyleBeginning<
264
+ i && [activeStyle integerValue]>[style integerValue])) {
181
265
  [fixedEndedStyles addObject:activeStyle];
182
266
  [stylesToBeReAdded addObject:activeStyle];
183
267
  }
184
268
  }
185
269
  }
186
-
270
+
271
+ // if a style begins but there is a style inner to it that is (and was
272
+ // previously) active, it also should be closed and readded
273
+
274
+ // newly added styles
275
+ NSMutableSet *newStyles = [currentActiveStyles mutableCopy];
276
+ [newStyles minusSet:previousActiveStyles];
277
+ // styles that were and still are active
278
+ NSMutableSet *stillActiveStyles = [previousActiveStyles mutableCopy];
279
+ [stillActiveStyles intersectSet:currentActiveStyles];
280
+
281
+ for (NSNumber *style in newStyles) {
282
+ for (NSNumber *ongoingStyle in stillActiveStyles) {
283
+ if ([ongoingStyle integerValue] > [style integerValue]) {
284
+ // the prev style is inner; needs to be closed and re-added later
285
+ [fixedEndedStyles addObject:ongoingStyle];
286
+ [stylesToBeReAdded addObject:ongoingStyle];
287
+ }
288
+ }
289
+ }
290
+
187
291
  // they are sorted in a descending order
188
- NSArray<NSNumber*> *sortedEndedStyles = [fixedEndedStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:NO]]];
189
-
292
+ NSArray<NSNumber *> *sortedEndedStyles = [fixedEndedStyles
293
+ sortedArrayUsingDescriptors:@[ [NSSortDescriptor
294
+ sortDescriptorWithKey:@"intValue"
295
+ ascending:NO] ]];
296
+
190
297
  // append closing tags
191
- for(NSNumber *style in sortedEndedStyles) {
192
- NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:currentRange.location];
193
- [result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
298
+ for (NSNumber *style in sortedEndedStyles) {
299
+ if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
300
+ continue;
301
+ }
302
+ NSString *tagContent = [self tagContentForStyle:style
303
+ openingTag:NO
304
+ location:currentRange.location];
305
+ [result appendString:[NSString stringWithFormat:@"</%@>", tagContent]];
194
306
  }
195
-
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];
199
- [newStyles unionSet: stylesToBeReAdded];
200
- NSArray<NSNumber*> *sortedNewStyles = [newStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:YES]]];
201
-
307
+
308
+ // all styles that have begun: new styles + the ones that need to be
309
+ // re-added they are sorted in a ascending manner to properly keep tags'
310
+ // FILO order
311
+ [newStyles unionSet:stylesToBeReAdded];
312
+ NSArray<NSNumber *> *sortedNewStyles = [newStyles
313
+ sortedArrayUsingDescriptors:@[ [NSSortDescriptor
314
+ sortDescriptorWithKey:@"intValue"
315
+ ascending:YES] ]];
316
+
202
317
  // append opening tags
203
- for(NSNumber *style in sortedNewStyles) {
204
- NSString *tagContent = [self tagContentForStyle:style openingTag:YES location:currentRange.location];
205
- [result appendString: [NSString stringWithFormat:@"<%@>", tagContent]];
318
+ for (NSNumber *style in sortedNewStyles) {
319
+ NSString *tagContent = [self tagContentForStyle:style
320
+ openingTag:YES
321
+ location:currentRange.location];
322
+ if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
323
+ [result
324
+ appendString:[NSString stringWithFormat:@"<%@/>", tagContent]];
325
+ [currentActiveStyles removeObject:@([ImageStyle getStyleType])];
326
+ } else {
327
+ [result appendString:[NSString stringWithFormat:@"<%@>", tagContent]];
328
+ }
206
329
  }
207
-
330
+
208
331
  // append the letter and escape it if needed
209
- [result appendString: [NSString stringByEscapingHtml:currentCharacterStr]];
210
-
332
+ [result appendString:[NSString stringByEscapingHtml:currentCharacterStr]];
333
+
211
334
  // save current styles for next character's checks
212
335
  previousActiveStyles = currentActiveStyles;
213
336
  }
214
-
337
+
215
338
  // set last character
216
339
  lastCharacter = currentCharacterChar;
217
340
  }
218
-
219
- if(![[NSCharacterSet newlineCharacterSet] characterIsMember:lastCharacter]) {
341
+
342
+ if (![[NSCharacterSet newlineCharacterSet] characterIsMember:lastCharacter]) {
220
343
  // not-newline character was last - finish the paragraph
221
344
  // close all pending tags
222
- NSArray<NSNumber*> *sortedEndedStyles = [previousActiveStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:NO]]];
223
-
345
+ NSArray<NSNumber *> *sortedEndedStyles = [previousActiveStyles
346
+ sortedArrayUsingDescriptors:@[ [NSSortDescriptor
347
+ sortDescriptorWithKey:@"intValue"
348
+ ascending:NO] ]];
349
+
224
350
  // append closing tags
225
- for(NSNumber *style in sortedEndedStyles) {
226
- NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:_input->textView.textStorage.string.length - 1];
227
- [result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
351
+ for (NSNumber *style in sortedEndedStyles) {
352
+ if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
353
+ continue;
354
+ }
355
+ NSString *tagContent = [self
356
+ tagContentForStyle:style
357
+ openingTag:NO
358
+ location:_input->textView.textStorage.string.length - 1];
359
+ [result appendString:[NSString stringWithFormat:@"</%@>", tagContent]];
228
360
  }
229
-
361
+
230
362
  // finish the paragraph
231
363
  // handle ending of some paragraph styles
232
- if([previousActiveStyles containsObject:@([UnorderedListStyle getStyleType])]) {
364
+ if ([previousActiveStyles
365
+ containsObject:@([UnorderedListStyle getStyleType])]) {
233
366
  [result appendString:@"\n</ul>"];
234
- } else if([previousActiveStyles containsObject:@([OrderedListStyle getStyleType])]) {
367
+ } else if ([previousActiveStyles
368
+ containsObject:@([OrderedListStyle getStyleType])]) {
235
369
  [result appendString:@"\n</ol>"];
236
- } else if([previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]) {
370
+ } else if ([previousActiveStyles
371
+ containsObject:@([BlockQuoteStyle getStyleType])]) {
237
372
  [result appendString:@"\n</blockquote>"];
238
- } else if(
239
- [previousActiveStyles containsObject:@([H1Style getStyleType])] ||
240
- [previousActiveStyles containsObject:@([H2Style getStyleType])] ||
241
- [previousActiveStyles containsObject:@([H3Style getStyleType])]
242
- ) {
373
+ } else if ([previousActiveStyles
374
+ containsObject:@([CodeBlockStyle getStyleType])]) {
375
+ [result appendString:@"\n</codeblock>"];
376
+ } else if ([previousActiveStyles
377
+ containsObject:@([H1Style getStyleType])] ||
378
+ [previousActiveStyles
379
+ containsObject:@([H2Style getStyleType])] ||
380
+ [previousActiveStyles
381
+ containsObject:@([H3Style getStyleType])]) {
243
382
  // do nothing, heading closing tag has already ben appended
244
383
  } else {
245
384
  [result appendString:@"</p>"];
246
385
  }
247
386
  } else {
248
387
  // newline character was last - some paragraph styles need to be closed
249
- if(inUnorderedList) {
388
+ if (inUnorderedList) {
250
389
  inUnorderedList = NO;
251
390
  [result appendString:@"\n</ul>"];
252
391
  }
253
- if(inOrderedList) {
392
+ if (inOrderedList) {
254
393
  inOrderedList = NO;
255
394
  [result appendString:@"\n</ol>"];
256
395
  }
257
- if(inBlockQuote) {
396
+ if (inBlockQuote) {
258
397
  inBlockQuote = NO;
259
398
  [result appendString:@"\n</blockquote>"];
260
399
  }
400
+ if (inCodeBlock) {
401
+ inCodeBlock = NO;
402
+ [result appendString:@"\n</codeblock>"];
403
+ }
261
404
  }
262
-
263
- [result appendString: @"\n</html>"];
264
-
405
+
406
+ [result appendString:@"\n</html>"];
407
+
265
408
  // remove zero width spaces in the very end
266
- NSRange resultRange = NSMakeRange(0, result.length);
267
- [result replaceOccurrencesOfString:@"\u200B" withString:@"" options:0 range:resultRange];
409
+ [result replaceOccurrencesOfString:@"\u200B"
410
+ withString:@""
411
+ options:0
412
+ range:NSMakeRange(0, result.length)];
413
+
414
+ // replace empty <p></p> into <br> in the very end
415
+ [result replaceOccurrencesOfString:@"<p></p>"
416
+ withString:@"<br>"
417
+ options:0
418
+ range:NSMakeRange(0, result.length)];
419
+
268
420
  return result;
269
421
  }
270
422
 
271
- - (NSString *)tagContentForStyle:(NSNumber *)style openingTag:(BOOL)openingTag location:(NSInteger)location {
272
- if([style isEqualToNumber: @([BoldStyle getStyleType])]) {
423
+ - (NSString *)tagContentForStyle:(NSNumber *)style
424
+ openingTag:(BOOL)openingTag
425
+ location:(NSInteger)location {
426
+ if ([style isEqualToNumber:@([BoldStyle getStyleType])]) {
273
427
  return @"b";
274
- } else if([style isEqualToNumber: @([ItalicStyle getStyleType])]) {
428
+ } else if ([style isEqualToNumber:@([ItalicStyle getStyleType])]) {
275
429
  return @"i";
276
- } else if([style isEqualToNumber: @([UnderlineStyle getStyleType])]) {
430
+ } else if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
431
+ if (openingTag) {
432
+ ImageStyle *imageStyle =
433
+ (ImageStyle *)_input->stylesDict[@([ImageStyle getStyleType])];
434
+ if (imageStyle != nullptr) {
435
+ ImageData *data = [imageStyle getImageDataAt:location];
436
+ if (data != nullptr && data.uri != nullptr) {
437
+ return [NSString
438
+ stringWithFormat:@"img src=\"%@\" width=\"%f\" height=\"%f\"",
439
+ data.uri, data.width, data.height];
440
+ }
441
+ }
442
+ return @"img";
443
+ } else {
444
+ return @"";
445
+ }
446
+ } else if ([style isEqualToNumber:@([UnderlineStyle getStyleType])]) {
277
447
  return @"u";
278
- } else if([style isEqualToNumber: @([StrikethroughStyle getStyleType])]) {
448
+ } else if ([style isEqualToNumber:@([StrikethroughStyle getStyleType])]) {
279
449
  return @"s";
280
- } else if([style isEqualToNumber: @([InlineCodeStyle getStyleType])]) {
450
+ } else if ([style isEqualToNumber:@([InlineCodeStyle getStyleType])]) {
281
451
  return @"code";
282
- } else if([style isEqualToNumber: @([LinkStyle getStyleType])]) {
283
- if(openingTag) {
284
- LinkStyle *linkStyle = (LinkStyle *)_input->stylesDict[@([LinkStyle getStyleType])];
285
- if(linkStyle != nullptr) {
286
- LinkData *data = [linkStyle getLinkDataAt: location];
287
- if(data != nullptr && data.url != nullptr) {
452
+ } else if ([style isEqualToNumber:@([LinkStyle getStyleType])]) {
453
+ if (openingTag) {
454
+ LinkStyle *linkStyle =
455
+ (LinkStyle *)_input->stylesDict[@([LinkStyle getStyleType])];
456
+ if (linkStyle != nullptr) {
457
+ LinkData *data = [linkStyle getLinkDataAt:location];
458
+ if (data != nullptr && data.url != nullptr) {
288
459
  return [NSString stringWithFormat:@"a href=\"%@\"", data.url];
289
460
  }
290
461
  }
@@ -292,271 +463,559 @@
292
463
  } else {
293
464
  return @"a";
294
465
  }
295
- } else if([style isEqualToNumber: @([MentionStyle getStyleType])]) {
296
- if(openingTag) {
297
- MentionStyle *mentionStyle = (MentionStyle *)_input->stylesDict[@([MentionStyle getStyleType])];
298
- if(mentionStyle != nullptr) {
466
+ } else if ([style isEqualToNumber:@([MentionStyle getStyleType])]) {
467
+ if (openingTag) {
468
+ MentionStyle *mentionStyle =
469
+ (MentionStyle *)_input->stylesDict[@([MentionStyle getStyleType])];
470
+ if (mentionStyle != nullptr) {
299
471
  MentionParams *params = [mentionStyle getMentionParamsAt:location];
300
472
  // attributes can theoretically be nullptr
301
- if(params != nullptr && params.indicator != nullptr && params.text != nullptr) {
302
- NSMutableString *attrsStr = [[NSMutableString alloc] initWithString: @""];
303
- if(params.attributes != nullptr) {
473
+ if (params != nullptr && params.indicator != nullptr &&
474
+ params.text != nullptr) {
475
+ NSMutableString *attrsStr =
476
+ [[NSMutableString alloc] initWithString:@""];
477
+ if (params.attributes != nullptr) {
304
478
  // turn attributes to Data and then into dict
305
- NSData *attrsData = [params.attributes dataUsingEncoding:NSUTF8StringEncoding];
479
+ NSData *attrsData =
480
+ [params.attributes dataUsingEncoding:NSUTF8StringEncoding];
306
481
  NSError *jsonError;
307
- NSDictionary *json = [NSJSONSerialization JSONObjectWithData:attrsData
308
- options:0
309
- error:&jsonError
310
- ];
482
+ NSDictionary *json =
483
+ [NSJSONSerialization JSONObjectWithData:attrsData
484
+ options:0
485
+ error:&jsonError];
311
486
  // format dict keys and values into string
312
- [json enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
313
- [attrsStr appendString: [NSString stringWithFormat:@" %@=\"%@\"", (NSString *)key, (NSString *)obj]];
487
+ [json enumerateKeysAndObjectsUsingBlock:^(
488
+ id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
489
+ [attrsStr
490
+ appendString:[NSString stringWithFormat:@" %@=\"%@\"",
491
+ (NSString *)key,
492
+ (NSString *)obj]];
314
493
  }];
315
494
  }
316
- return [NSString stringWithFormat:@"mention text=\"%@\" indicator=\"%@\"%@", params.text, params.indicator, attrsStr];
495
+ return [NSString
496
+ stringWithFormat:@"mention text=\"%@\" indicator=\"%@\"%@",
497
+ params.text, params.indicator, attrsStr];
317
498
  }
318
499
  }
319
500
  return @"mention";
320
501
  } else {
321
502
  return @"mention";
322
503
  }
323
- } else if([style isEqualToNumber:@([H1Style getStyleType])]) {
504
+ } else if ([style isEqualToNumber:@([H1Style getStyleType])]) {
324
505
  return @"h1";
325
- } else if([style isEqualToNumber:@([H2Style getStyleType])]) {
506
+ } else if ([style isEqualToNumber:@([H2Style getStyleType])]) {
326
507
  return @"h2";
327
- } else if([style isEqualToNumber:@([H3Style getStyleType])]) {
508
+ } else if ([style isEqualToNumber:@([H3Style getStyleType])]) {
328
509
  return @"h3";
329
- } else if([style isEqualToNumber:@([UnorderedListStyle getStyleType])] || [style isEqualToNumber:@([OrderedListStyle getStyleType])]) {
510
+ } else if ([style isEqualToNumber:@([UnorderedListStyle getStyleType])] ||
511
+ [style isEqualToNumber:@([OrderedListStyle getStyleType])]) {
330
512
  return @"li";
331
- } else if([style isEqualToNumber:@([BlockQuoteStyle getStyleType])]) {
332
- // blockquotes use <p> tags the same way lists use <li>
513
+ } else if ([style isEqualToNumber:@([BlockQuoteStyle getStyleType])] ||
514
+ [style isEqualToNumber:@([CodeBlockStyle getStyleType])]) {
515
+ // blockquotes and codeblock use <p> tags the same way lists use <li>
333
516
  return @"p";
334
517
  }
335
518
  return @"";
336
519
  }
337
520
 
338
- - (void)replaceWholeFromHtml:(NSString * _Nonnull)html {
521
+ - (void)replaceWholeFromHtml:(NSString *_Nonnull)html {
339
522
  NSArray *processingResult = [self getTextAndStylesFromHtml:html];
340
523
  NSString *plainText = (NSString *)processingResult[0];
341
524
  NSArray *stylesInfo = (NSArray *)processingResult[1];
342
-
525
+
343
526
  // reset the text first and reset typing attributes
344
527
  _input->textView.text = @"";
345
528
  _input->textView.typingAttributes = _input->defaultTypingAttributes;
346
-
529
+
347
530
  // set new text
348
531
  _input->textView.text = plainText;
349
-
532
+
350
533
  // re-apply the styles
351
- [self applyProcessedStyles:stylesInfo offsetFromBeginning:0];
534
+ [self applyProcessedStyles:stylesInfo
535
+ offsetFromBeginning:0
536
+ plainTextLength:plainText.length];
352
537
  }
353
538
 
354
- - (void)replaceFromHtml:(NSString * _Nonnull)html range:(NSRange)range {
539
+ - (void)replaceFromHtml:(NSString *_Nonnull)html range:(NSRange)range {
355
540
  NSArray *processingResult = [self getTextAndStylesFromHtml:html];
356
541
  NSString *plainText = (NSString *)processingResult[0];
357
542
  NSArray *stylesInfo = (NSArray *)processingResult[1];
358
-
543
+
359
544
  // we can use ready replace util
360
- [TextInsertionUtils replaceText:plainText at:range additionalAttributes:nil input:_input withSelection:YES];
361
-
362
- [self applyProcessedStyles:stylesInfo offsetFromBeginning:range.location];
545
+ [TextInsertionUtils replaceText:plainText
546
+ at:range
547
+ additionalAttributes:nil
548
+ input:_input
549
+ withSelection:YES];
550
+
551
+ [self applyProcessedStyles:stylesInfo
552
+ offsetFromBeginning:range.location
553
+ plainTextLength:plainText.length];
363
554
  }
364
555
 
365
- - (void)insertFromHtml:(NSString * _Nonnull)html location:(NSInteger)location {
556
+ - (void)insertFromHtml:(NSString *_Nonnull)html location:(NSInteger)location {
366
557
  NSArray *processingResult = [self getTextAndStylesFromHtml:html];
367
558
  NSString *plainText = (NSString *)processingResult[0];
368
559
  NSArray *stylesInfo = (NSArray *)processingResult[1];
369
-
560
+
370
561
  // same here, insertion utils got our back
371
- [TextInsertionUtils insertText:plainText at:location additionalAttributes:nil input:_input withSelection:YES];
372
-
373
- [self applyProcessedStyles:stylesInfo offsetFromBeginning:location];
562
+ [TextInsertionUtils insertText:plainText
563
+ at:location
564
+ additionalAttributes:nil
565
+ input:_input
566
+ withSelection:YES];
567
+
568
+ [self applyProcessedStyles:stylesInfo
569
+ offsetFromBeginning:location
570
+ plainTextLength:plainText.length];
374
571
  }
375
572
 
376
- - (void)applyProcessedStyles:(NSArray *)processedStyles offsetFromBeginning:(NSInteger)offset {
377
- for(NSArray* arr in processedStyles) {
573
+ - (void)applyProcessedStyles:(NSArray *)processedStyles
574
+ offsetFromBeginning:(NSInteger)offset
575
+ plainTextLength:(NSUInteger)plainTextLength {
576
+ for (NSArray *arr in processedStyles) {
378
577
  // unwrap all info from processed style
379
578
  NSNumber *styleType = (NSNumber *)arr[0];
380
579
  StylePair *stylePair = (StylePair *)arr[1];
381
580
  id<BaseStyleProtocol> baseStyle = _input->stylesDict[styleType];
382
- // range must be taking offest into consideration because processed styles' ranges are relative to only the new text
383
- // while we need absolute ranges relative to the whole existing text
384
- NSRange styleRange = NSMakeRange(offset + [stylePair.rangeValue rangeValue].location, [stylePair.rangeValue rangeValue].length);
385
-
386
- // of course any changes here need to take blocks and conflicts into consideration
387
- if([_input handleStyleBlocksAndConflicts:[[baseStyle class] getStyleType] range:styleRange]) {
388
- if([styleType isEqualToNumber: @([LinkStyle getStyleType])]) {
389
- NSString *text = [_input->textView.textStorage.string substringWithRange:styleRange];
581
+ // range must be taking offest into consideration because processed styles'
582
+ // ranges are relative to only the new text while we need absolute ranges
583
+ // relative to the whole existing text
584
+ NSRange styleRange =
585
+ NSMakeRange(offset + [stylePair.rangeValue rangeValue].location,
586
+ [stylePair.rangeValue rangeValue].length);
587
+
588
+ // of course any changes here need to take blocks and conflicts into
589
+ // consideration
590
+ if ([_input handleStyleBlocksAndConflicts:[[baseStyle class] getStyleType]
591
+ range:styleRange]) {
592
+ if ([styleType isEqualToNumber:@([LinkStyle getStyleType])]) {
593
+ NSString *text =
594
+ [_input->textView.textStorage.string substringWithRange:styleRange];
390
595
  NSString *url = (NSString *)stylePair.styleValue;
391
596
  BOOL isManual = ![text isEqualToString:url];
392
- [((LinkStyle *)baseStyle) addLink:text url:url range:styleRange manual:isManual];
393
- } else if([styleType isEqualToNumber: @([MentionStyle getStyleType])]) {
597
+ [((LinkStyle *)baseStyle) addLink:text
598
+ url:url
599
+ range:styleRange
600
+ manual:isManual
601
+ withSelection:NO];
602
+ } else if ([styleType isEqualToNumber:@([MentionStyle getStyleType])]) {
394
603
  MentionParams *params = (MentionParams *)stylePair.styleValue;
395
- [((MentionStyle *)baseStyle) addMentionAtRange:styleRange params:params];
604
+ [((MentionStyle *)baseStyle) addMentionAtRange:styleRange
605
+ params:params];
606
+ } else if ([styleType isEqualToNumber:@([ImageStyle getStyleType])]) {
607
+ ImageData *imgData = (ImageData *)stylePair.styleValue;
608
+ [((ImageStyle *)baseStyle) addImageAtRange:styleRange
609
+ imageData:imgData
610
+ withSelection:NO];
396
611
  } else {
397
- [baseStyle addAttributes:styleRange];
612
+ BOOL shouldAddTypingAttr =
613
+ styleRange.location + styleRange.length == plainTextLength;
614
+ [baseStyle addAttributes:styleRange withTypingAttr:shouldAddTypingAttr];
398
615
  }
399
616
  }
400
617
  }
401
618
  [_input anyTextMayHaveBeenModified];
402
619
  }
403
620
 
404
- - (NSString * _Nullable)initiallyProcessHtml:(NSString * _Nonnull)html {
621
+ - (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html {
622
+ NSString *htmlWithoutSpaces = [self stripExtraWhiteSpacesAndNewlines:html];
405
623
  NSString *fixedHtml = nullptr;
406
-
407
- if(html.length >= 13) {
408
- NSString *firstSix = [html substringWithRange:NSMakeRange(0, 6)];
409
- NSString *lastSeven = [html substringWithRange:NSMakeRange(html.length-7, 7)];
410
-
411
- if([firstSix isEqualToString:@"<html>"] && [lastSeven isEqualToString:@"</html>"]) {
624
+
625
+ if (htmlWithoutSpaces.length >= 13) {
626
+ NSString *firstSix =
627
+ [htmlWithoutSpaces substringWithRange:NSMakeRange(0, 6)];
628
+ NSString *lastSeven = [htmlWithoutSpaces
629
+ substringWithRange:NSMakeRange(htmlWithoutSpaces.length - 7, 7)];
630
+
631
+ if ([firstSix isEqualToString:@"<html>"] &&
632
+ [lastSeven isEqualToString:@"</html>"]) {
412
633
  // remove html tags, might be with newlines or without them
413
- fixedHtml = [html copy];
634
+ fixedHtml = [htmlWithoutSpaces copy];
414
635
  // firstly remove newlined html tags if any:
415
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<html>\n" withString:@""];
416
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n</html>" withString:@""];
636
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<html>\n"
637
+ withString:@""];
638
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n</html>"
639
+ withString:@""];
417
640
  // fallback; remove html tags without their newlines
418
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<html>" withString:@""];
419
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"</html>" withString:@""];
641
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<html>"
642
+ withString:@""];
643
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"</html>"
644
+ withString:@""];
420
645
  } else {
421
- // in other case we are most likely working with some external html - try getting the styles from between body tags
422
- NSRange openingBodyRange = [html rangeOfString:@"<body>"];
423
- NSRange closingBodyRange = [html rangeOfString:@"</body>"];
424
-
425
- if(openingBodyRange.length != 0 && closingBodyRange.length != 0) {
646
+ // in other case we are most likely working with some external html - try
647
+ // getting the styles from between body tags
648
+ NSRange openingBodyRange = [htmlWithoutSpaces rangeOfString:@"<body>"];
649
+ NSRange closingBodyRange = [htmlWithoutSpaces rangeOfString:@"</body>"];
650
+
651
+ if (openingBodyRange.length != 0 && closingBodyRange.length != 0) {
426
652
  NSInteger newStart = openingBodyRange.location + 7;
427
653
  NSInteger newEnd = closingBodyRange.location - 1;
428
- fixedHtml = [html substringWithRange:NSMakeRange(newStart, newEnd - newStart + 1)];
654
+ fixedHtml = [htmlWithoutSpaces
655
+ substringWithRange:NSMakeRange(newStart, newEnd - newStart + 1)];
429
656
  }
430
657
  }
431
658
  }
432
-
659
+
433
660
  // second processing - try fixing htmls with wrong newlines' setup
434
- if(fixedHtml != nullptr) {
661
+ if (fixedHtml != nullptr) {
435
662
  // add <br> tag wherever needed
436
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<p></p>" withString:@"<br>"];
437
-
663
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<p></p>"
664
+ withString:@"<br>"];
665
+
438
666
  // remove <p> tags inside of <li>
439
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<li><p>" withString:@"<li>"];
440
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"</p></li>" withString:@"</li>"];
441
-
667
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<li><p>"
668
+ withString:@"<li>"];
669
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"</p></li>"
670
+ withString:@"</li>"];
671
+
672
+ // change <br/> to <br>
673
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<br/>"
674
+ withString:@"<br>"];
675
+
676
+ // remove <p> tags around <br>
677
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<p><br>"
678
+ withString:@"<br>"];
679
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<br></p>"
680
+ withString:@"<br>"];
681
+
442
682
  // tags that have to be in separate lines
443
- fixedHtml = [self stringByAddingNewlinesToTag:@"<br>" inString:fixedHtml leading:YES trailing:YES];
444
- fixedHtml = [self stringByAddingNewlinesToTag:@"<ul>" inString:fixedHtml leading:YES trailing:YES];
445
- fixedHtml = [self stringByAddingNewlinesToTag:@"</ul>" inString:fixedHtml leading:YES trailing:YES];
446
- fixedHtml = [self stringByAddingNewlinesToTag:@"<ol>" inString:fixedHtml leading:YES trailing:YES];
447
- fixedHtml = [self stringByAddingNewlinesToTag:@"</ol>" inString:fixedHtml leading:YES trailing:YES];
448
- fixedHtml = [self stringByAddingNewlinesToTag:@"<blockquote>" inString:fixedHtml leading:YES trailing:YES];
449
- fixedHtml = [self stringByAddingNewlinesToTag:@"</blockquote>" inString:fixedHtml leading:YES trailing:YES];
450
-
683
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<br>"
684
+ inString:fixedHtml
685
+ leading:YES
686
+ trailing:YES];
687
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<ul>"
688
+ inString:fixedHtml
689
+ leading:YES
690
+ trailing:YES];
691
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</ul>"
692
+ inString:fixedHtml
693
+ leading:YES
694
+ trailing:YES];
695
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<ol>"
696
+ inString:fixedHtml
697
+ leading:YES
698
+ trailing:YES];
699
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</ol>"
700
+ inString:fixedHtml
701
+ leading:YES
702
+ trailing:YES];
703
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<blockquote>"
704
+ inString:fixedHtml
705
+ leading:YES
706
+ trailing:YES];
707
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</blockquote>"
708
+ inString:fixedHtml
709
+ leading:YES
710
+ trailing:YES];
711
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<codeblock>"
712
+ inString:fixedHtml
713
+ leading:YES
714
+ trailing:YES];
715
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</codeblock>"
716
+ inString:fixedHtml
717
+ leading:YES
718
+ trailing:YES];
719
+
451
720
  // line opening tags
452
- fixedHtml = [self stringByAddingNewlinesToTag:@"<p>" inString:fixedHtml leading:YES trailing:NO];
453
- fixedHtml = [self stringByAddingNewlinesToTag:@"<li>" inString:fixedHtml leading:YES trailing:NO];
454
- fixedHtml = [self stringByAddingNewlinesToTag:@"<h1>" inString:fixedHtml leading:YES trailing:NO];
455
- fixedHtml = [self stringByAddingNewlinesToTag:@"<h2>" inString:fixedHtml leading:YES trailing:NO];
456
- fixedHtml = [self stringByAddingNewlinesToTag:@"<h3>" inString:fixedHtml leading:YES trailing:NO];
457
-
721
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<p>"
722
+ inString:fixedHtml
723
+ leading:YES
724
+ trailing:NO];
725
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<li>"
726
+ inString:fixedHtml
727
+ leading:YES
728
+ trailing:NO];
729
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<h1>"
730
+ inString:fixedHtml
731
+ leading:YES
732
+ trailing:NO];
733
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<h2>"
734
+ inString:fixedHtml
735
+ leading:YES
736
+ trailing:NO];
737
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<h3>"
738
+ inString:fixedHtml
739
+ leading:YES
740
+ trailing:NO];
741
+
458
742
  // line closing tags
459
- fixedHtml = [self stringByAddingNewlinesToTag:@"</p>" inString:fixedHtml leading:NO trailing:YES];
460
- fixedHtml = [self stringByAddingNewlinesToTag:@"</li>" inString:fixedHtml leading:NO trailing:YES];
461
- fixedHtml = [self stringByAddingNewlinesToTag:@"</h1>" inString:fixedHtml leading:NO trailing:YES];
462
- fixedHtml = [self stringByAddingNewlinesToTag:@"</h2>" inString:fixedHtml leading:NO trailing:YES];
463
- fixedHtml = [self stringByAddingNewlinesToTag:@"</h3>" inString:fixedHtml leading:NO trailing:YES];
743
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</p>"
744
+ inString:fixedHtml
745
+ leading:NO
746
+ trailing:YES];
747
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</li>"
748
+ inString:fixedHtml
749
+ leading:NO
750
+ trailing:YES];
751
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</h1>"
752
+ inString:fixedHtml
753
+ leading:NO
754
+ trailing:YES];
755
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</h2>"
756
+ inString:fixedHtml
757
+ leading:NO
758
+ trailing:YES];
759
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</h3>"
760
+ inString:fixedHtml
761
+ leading:NO
762
+ trailing:YES];
763
+
764
+ // this is more like a hack but for some reason the last <br> in
765
+ // <blockquote> and <codeblock> are not properly changed into zero width
766
+ // space so we do that manually here
767
+ fixedHtml = [fixedHtml
768
+ stringByReplacingOccurrencesOfString:@"<br>\n</blockquote>"
769
+ withString:@"<p>\u200B</p>\n</blockquote>"];
770
+ fixedHtml = [fixedHtml
771
+ stringByReplacingOccurrencesOfString:@"<br>\n</codeblock>"
772
+ withString:@"<p>\u200B</p>\n</codeblock>"];
773
+
774
+ // replace "<br>" at the end with "<br>\n" if input is not empty to properly
775
+ // handle last <br> in html
776
+ if ([fixedHtml hasSuffix:@"<br>"] && fixedHtml.length != 4) {
777
+ fixedHtml = [fixedHtml stringByAppendingString:@"\n"];
778
+ }
464
779
  }
465
-
780
+
466
781
  return fixedHtml;
467
782
  }
468
783
 
469
- - (NSString *)stringByAddingNewlinesToTag:(NSString *)tag inString:(NSString *)html leading:(BOOL)leading trailing:(BOOL)trailing {
784
+ /**
785
+ * Prepares HTML for the parser by stripping extraneous whitespace and newlines
786
+ * from structural tags, while preserving them within text content.
787
+ *
788
+ * APPROACH:
789
+ * This function treats the HTML as having two distinct states:
790
+ * 1. Structure Mode (Depth == 0): We are inside or between container tags (like
791
+ * <blockquote>, <ul>, <codeblock>). In this mode whitespace and newlines are
792
+ * considered layout artifacts and are REMOVED to prevent the parser from
793
+ * creating unwanted spaces.
794
+ * 2. Content Mode (Depth > 0): We are inside a text-containing tag (like <p>,
795
+ * <b>, <li>). In this mode, all whitespace is PRESERVED exactly as is, ensuring
796
+ * that sentences and inline formatting remain readable.
797
+ *
798
+ * The function iterates character-by-character, using a depth counter to track
799
+ * nesting levels of the specific tags defined in `textTags`.
800
+ *
801
+ * IMPORTANT:
802
+ * The `textTags` set acts as a whitelist for "Content Mode". If you add support
803
+ * for a new HTML tag that contains visible text (e.g., <h4>, <h5>, <h6>),
804
+ * you MUST add it to the `textTags` set below.
805
+ */
806
+ - (NSString *)stripExtraWhiteSpacesAndNewlines:(NSString *)html {
807
+ NSSet *textTags =
808
+ [NSSet setWithObjects:@"p", @"h1", @"h2", @"h3", @"li", @"b", @"a", @"s",
809
+ @"mention", @"code", @"u", @"i", nil];
810
+
811
+ NSMutableString *output = [NSMutableString stringWithCapacity:html.length];
812
+ NSMutableString *currentTagBuffer = [NSMutableString string];
813
+ NSCharacterSet *whitespaceAndNewlineSet =
814
+ [NSCharacterSet whitespaceAndNewlineCharacterSet];
815
+
816
+ BOOL isReadingTag = NO;
817
+ NSInteger textDepth = 0;
818
+
819
+ for (NSUInteger i = 0; i < html.length; i++) {
820
+ unichar c = [html characterAtIndex:i];
821
+
822
+ if (c == '<') {
823
+ isReadingTag = YES;
824
+ [currentTagBuffer setString:@""];
825
+ [output appendString:@"<"];
826
+ } else if (c == '>') {
827
+ isReadingTag = NO;
828
+ [output appendString:@">"];
829
+
830
+ NSString *fullTag = [currentTagBuffer lowercaseString];
831
+
832
+ NSString *cleanName = [fullTag
833
+ stringByTrimmingCharactersInSet:
834
+ [NSCharacterSet characterSetWithCharactersInString:@"/"]];
835
+ NSArray *parts =
836
+ [cleanName componentsSeparatedByCharactersInSet:
837
+ [NSCharacterSet whitespaceAndNewlineCharacterSet]];
838
+ NSString *tagName = parts.firstObject;
839
+
840
+ if (![textTags containsObject:tagName]) {
841
+ continue;
842
+ }
843
+
844
+ if ([fullTag hasPrefix:@"/"]) {
845
+ textDepth--;
846
+ if (textDepth < 0)
847
+ textDepth = 0;
848
+ } else {
849
+ // Opening tag (e.g. <h1>) -> Enter Text Mode
850
+ // (Ignore self-closing tags like <img/> if they happen to be in the
851
+ // list)
852
+ if (![fullTag hasSuffix:@"/"]) {
853
+ textDepth++;
854
+ }
855
+ }
856
+ } else {
857
+ if (isReadingTag) {
858
+ [currentTagBuffer appendFormat:@"%C", c];
859
+ [output appendFormat:@"%C", c];
860
+ continue;
861
+ }
862
+
863
+ if (textDepth > 0) {
864
+ [output appendFormat:@"%C", c];
865
+ } else {
866
+ if (![whitespaceAndNewlineSet characterIsMember:c]) {
867
+ [output appendFormat:@"%C", c];
868
+ }
869
+ }
870
+ }
871
+ }
872
+
873
+ return output;
874
+ }
875
+
876
+ - (NSString *)stringByAddingNewlinesToTag:(NSString *)tag
877
+ inString:(NSString *)html
878
+ leading:(BOOL)leading
879
+ trailing:(BOOL)trailing {
470
880
  NSString *str = [html copy];
471
- if(leading) {
881
+ if (leading) {
472
882
  NSString *formattedTag = [NSString stringWithFormat:@">%@", tag];
473
883
  NSString *formattedNewTag = [NSString stringWithFormat:@">\n%@", tag];
474
- str = [str stringByReplacingOccurrencesOfString:formattedTag withString:formattedNewTag];
884
+ str = [str stringByReplacingOccurrencesOfString:formattedTag
885
+ withString:formattedNewTag];
475
886
  }
476
- if(trailing) {
887
+ if (trailing) {
477
888
  NSString *formattedTag = [NSString stringWithFormat:@"%@<", tag];
478
889
  NSString *formattedNewTag = [NSString stringWithFormat:@"%@\n<", tag];
479
- str = [str stringByReplacingOccurrencesOfString:formattedTag withString:formattedNewTag];
890
+ str = [str stringByReplacingOccurrencesOfString:formattedTag
891
+ withString:formattedNewTag];
480
892
  }
481
893
  return str;
482
894
  }
483
895
 
896
+ - (void)finalizeTagEntry:(NSMutableString *)tagName
897
+ ongoingTags:(NSMutableDictionary *)ongoingTags
898
+ initiallyProcessedTags:(NSMutableArray *)processedTags
899
+ plainText:(NSMutableString *)plainText {
900
+ NSMutableArray *tagEntry = [[NSMutableArray alloc] init];
901
+
902
+ NSArray *tagData = ongoingTags[tagName];
903
+ NSInteger tagLocation = [((NSNumber *)tagData[0]) intValue];
904
+ NSRange tagRange = NSMakeRange(tagLocation, plainText.length - tagLocation);
905
+
906
+ [tagEntry addObject:[tagName copy]];
907
+ [tagEntry addObject:[NSValue valueWithRange:tagRange]];
908
+ if (tagData.count > 1) {
909
+ [tagEntry addObject:[(NSString *)tagData[1] copy]];
910
+ }
911
+
912
+ [processedTags addObject:tagEntry];
913
+ [ongoingTags removeObjectForKey:tagName];
914
+ }
915
+
484
916
  - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
485
- NSMutableString *plainText = [[NSMutableString alloc] initWithString: @""];
917
+ NSMutableString *plainText = [[NSMutableString alloc] initWithString:@""];
486
918
  NSMutableDictionary *ongoingTags = [[NSMutableDictionary alloc] init];
487
919
  NSMutableArray *initiallyProcessedTags = [[NSMutableArray alloc] init];
488
920
  BOOL insideTag = NO;
489
921
  BOOL gettingTagName = NO;
490
922
  BOOL gettingTagParams = NO;
491
923
  BOOL closingTag = NO;
492
- NSMutableString *currentTagName = [[NSMutableString alloc] initWithString:@""];
493
- NSMutableString *currentTagParams = [[NSMutableString alloc] initWithString:@""];
494
- NSDictionary *htmlEntitiesDict = [NSString getEscapedCharactersInfoFrom:fixedHtml];
495
-
924
+ NSMutableString *currentTagName =
925
+ [[NSMutableString alloc] initWithString:@""];
926
+ NSMutableString *currentTagParams =
927
+ [[NSMutableString alloc] initWithString:@""];
928
+ NSDictionary *htmlEntitiesDict =
929
+ [NSString getEscapedCharactersInfoFrom:fixedHtml];
930
+
496
931
  // firstly, extract text and initially processed tags
497
- for(int i = 0; i < fixedHtml.length; i++) {
498
- NSString *currentCharacterStr = [fixedHtml substringWithRange:NSMakeRange(i, 1)];
932
+ for (int i = 0; i < fixedHtml.length; i++) {
933
+ NSString *currentCharacterStr =
934
+ [fixedHtml substringWithRange:NSMakeRange(i, 1)];
499
935
  unichar currentCharacterChar = [fixedHtml characterAtIndex:i];
500
-
501
- if(currentCharacterChar == '<') {
936
+
937
+ if (currentCharacterChar == '<') {
502
938
  // opening the tag, mark that we are inside and getting its name
503
939
  insideTag = YES;
504
940
  gettingTagName = YES;
505
- } else if(currentCharacterChar == '>') {
506
- // finishing some tag, no longer marked as inside or getting its name/params
941
+ } else if (currentCharacterChar == '>') {
942
+ // finishing some tag, no longer marked as inside or getting its
943
+ // name/params
507
944
  insideTag = NO;
508
945
  gettingTagName = NO;
509
946
  gettingTagParams = NO;
510
-
511
- if([currentTagName isEqualToString:@"p"] || [currentTagName isEqualToString:@"br"] || [currentTagName isEqualToString:@"li"]) {
947
+
948
+ BOOL isSelfClosing = NO;
949
+
950
+ // Check if params ended with '/' (e.g. <img src="" />)
951
+ if ([currentTagParams hasSuffix:@"/"]) {
952
+ [currentTagParams
953
+ deleteCharactersInRange:NSMakeRange(currentTagParams.length - 1,
954
+ 1)];
955
+ isSelfClosing = YES;
956
+ }
957
+
958
+ if ([currentTagName isEqualToString:@"p"] ||
959
+ [currentTagName isEqualToString:@"br"] ||
960
+ [currentTagName isEqualToString:@"li"]) {
512
961
  // do nothing, we don't include these tags in styles
513
- } else if(!closingTag) {
514
- // we finish opening tag - get its location and optionally params and put them under tag name key in ongoingTags
962
+ } else if (!closingTag) {
963
+ // we finish opening tag - get its location and optionally params and
964
+ // put them under tag name key in ongoingTags
515
965
  NSMutableArray *tagArr = [[NSMutableArray alloc] init];
516
966
  [tagArr addObject:[NSNumber numberWithInteger:plainText.length]];
517
- if(currentTagParams.length > 0) {
967
+ if (currentTagParams.length > 0) {
518
968
  [tagArr addObject:[currentTagParams copy]];
519
969
  }
520
970
  ongoingTags[currentTagName] = tagArr;
521
-
522
- // skip one newline after opening tags that are in separate lines intentionally
523
- if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"]) {
971
+
972
+ // skip one newline after opening tags that are in separate lines
973
+ // intentionally
974
+ if ([currentTagName isEqualToString:@"ul"] ||
975
+ [currentTagName isEqualToString:@"ol"] ||
976
+ [currentTagName isEqualToString:@"blockquote"] ||
977
+ [currentTagName isEqualToString:@"codeblock"]) {
524
978
  i += 1;
525
979
  }
526
- } else {
527
- // we finish closing tags - pack tag name, tag range and optionally tag params into an entry that goes inside initiallyProcessedTags
528
-
529
- // 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"]) {
531
- plainText = [[plainText substringWithRange: NSMakeRange(0, plainText.length - 1)] mutableCopy];
980
+
981
+ if (isSelfClosing) {
982
+ [self finalizeTagEntry:currentTagName
983
+ ongoingTags:ongoingTags
984
+ initiallyProcessedTags:initiallyProcessedTags
985
+ plainText:plainText];
532
986
  }
533
-
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]];
987
+ } else {
988
+ // we finish closing tags - pack tag name, tag range and optionally tag
989
+ // params into an entry that goes inside initiallyProcessedTags
990
+
991
+ // skip one newline that was added before some closing tags that are in
992
+ // separate lines
993
+ if ([currentTagName isEqualToString:@"ul"] ||
994
+ [currentTagName isEqualToString:@"ol"] ||
995
+ [currentTagName isEqualToString:@"blockquote"] ||
996
+ [currentTagName isEqualToString:@"codeblock"]) {
997
+ plainText = [[plainText
998
+ substringWithRange:NSMakeRange(0, plainText.length - 1)]
999
+ mutableCopy];
544
1000
  }
545
-
546
- [initiallyProcessedTags addObject:tagEntry];
547
- [ongoingTags removeObjectForKey:currentTagName];
1001
+
1002
+ [self finalizeTagEntry:currentTagName
1003
+ ongoingTags:ongoingTags
1004
+ initiallyProcessedTags:initiallyProcessedTags
1005
+ plainText:plainText];
548
1006
  }
549
1007
  // post-tag cleanup
550
1008
  closingTag = NO;
551
1009
  currentTagName = [[NSMutableString alloc] initWithString:@""];
552
1010
  currentTagParams = [[NSMutableString alloc] initWithString:@""];
553
1011
  } else {
554
- if(!insideTag) {
1012
+ if (!insideTag) {
555
1013
  // no tags logic - just append the right text
556
-
557
- // html entity on the index; use unescaped character and forward iterator accordingly
1014
+
1015
+ // html entity on the index; use unescaped character and forward
1016
+ // iterator accordingly
558
1017
  NSArray *entityInfo = htmlEntitiesDict[@(i)];
559
- if(entityInfo != nullptr) {
1018
+ if (entityInfo != nullptr) {
560
1019
  NSString *escaped = entityInfo[0];
561
1020
  NSString *unescaped = entityInfo[1];
562
1021
  [plainText appendString:unescaped];
@@ -566,119 +1025,194 @@
566
1025
  [plainText appendString:currentCharacterStr];
567
1026
  }
568
1027
  } else {
569
- if(gettingTagName) {
570
- if(currentCharacterChar == ' ') {
1028
+ if (gettingTagName) {
1029
+ if (currentCharacterChar == ' ') {
571
1030
  // no longer getting tag name - switch to params
572
1031
  gettingTagName = NO;
573
1032
  gettingTagParams = YES;
574
- } else if(currentCharacterChar == '/') {
1033
+ } else if (currentCharacterChar == '/') {
575
1034
  // mark that the tag is closing
576
1035
  closingTag = YES;
577
1036
  } else {
578
1037
  // append next tag char
579
1038
  [currentTagName appendString:currentCharacterStr];
580
1039
  }
581
- } else if(gettingTagParams) {
1040
+ } else if (gettingTagParams) {
582
1041
  // append next tag params char
583
1042
  [currentTagParams appendString:currentCharacterStr];
584
1043
  }
585
1044
  }
586
1045
  }
587
1046
  }
588
-
1047
+
589
1048
  // process tags into proper StyleType + StylePair values
590
1049
  NSMutableArray *processedStyles = [[NSMutableArray alloc] init];
591
-
592
- for(NSArray* arr in initiallyProcessedTags) {
1050
+
1051
+ for (NSArray *arr in initiallyProcessedTags) {
593
1052
  NSString *tagName = (NSString *)arr[0];
594
1053
  NSValue *tagRangeValue = (NSValue *)arr[1];
595
1054
  NSMutableString *params = [[NSMutableString alloc] initWithString:@""];
596
- if(arr.count > 2) {
1055
+ if (arr.count > 2) {
597
1056
  [params appendString:(NSString *)arr[2]];
598
1057
  }
599
-
1058
+
600
1059
  NSMutableArray *styleArr = [[NSMutableArray alloc] init];
601
1060
  StylePair *stylePair = [[StylePair alloc] init];
602
- if([tagName isEqualToString:@"b"]) {
1061
+ if ([tagName isEqualToString:@"b"]) {
603
1062
  [styleArr addObject:@([BoldStyle getStyleType])];
604
- } else if([tagName isEqualToString:@"i"]) {
1063
+ } else if ([tagName isEqualToString:@"i"]) {
605
1064
  [styleArr addObject:@([ItalicStyle getStyleType])];
606
- } else if([tagName isEqualToString:@"u"]) {
1065
+ } else if ([tagName isEqualToString:@"img"]) {
1066
+ NSRegularExpression *srcRegex =
1067
+ [NSRegularExpression regularExpressionWithPattern:@"src=\"([^\"]+)\""
1068
+ options:0
1069
+ error:nullptr];
1070
+ NSTextCheckingResult *match =
1071
+ [srcRegex firstMatchInString:params
1072
+ options:0
1073
+ range:NSMakeRange(0, params.length)];
1074
+
1075
+ if (match == nullptr) {
1076
+ continue;
1077
+ }
1078
+
1079
+ NSRange srcRange = match.range;
1080
+ [styleArr addObject:@([ImageStyle getStyleType])];
1081
+ // cut only the uri from the src="..." string
1082
+ NSString *uri =
1083
+ [params substringWithRange:NSMakeRange(srcRange.location + 5,
1084
+ srcRange.length - 6)];
1085
+ ImageData *imageData = [[ImageData alloc] init];
1086
+ imageData.uri = uri;
1087
+
1088
+ NSRegularExpression *widthRegex = [NSRegularExpression
1089
+ regularExpressionWithPattern:@"width=\"([0-9.]+)\""
1090
+ options:0
1091
+ error:nil];
1092
+ NSTextCheckingResult *widthMatch =
1093
+ [widthRegex firstMatchInString:params
1094
+ options:0
1095
+ range:NSMakeRange(0, params.length)];
1096
+
1097
+ if (widthMatch) {
1098
+ NSString *widthString =
1099
+ [params substringWithRange:[widthMatch rangeAtIndex:1]];
1100
+ imageData.width = [widthString floatValue];
1101
+ }
1102
+
1103
+ NSRegularExpression *heightRegex = [NSRegularExpression
1104
+ regularExpressionWithPattern:@"height=\"([0-9.]+)\""
1105
+ options:0
1106
+ error:nil];
1107
+ NSTextCheckingResult *heightMatch =
1108
+ [heightRegex firstMatchInString:params
1109
+ options:0
1110
+ range:NSMakeRange(0, params.length)];
1111
+
1112
+ if (heightMatch) {
1113
+ NSString *heightString =
1114
+ [params substringWithRange:[heightMatch rangeAtIndex:1]];
1115
+ imageData.height = [heightString floatValue];
1116
+ }
1117
+
1118
+ stylePair.styleValue = imageData;
1119
+ } else if ([tagName isEqualToString:@"u"]) {
607
1120
  [styleArr addObject:@([UnderlineStyle getStyleType])];
608
- } else if([tagName isEqualToString:@"s"]) {
1121
+ } else if ([tagName isEqualToString:@"s"]) {
609
1122
  [styleArr addObject:@([StrikethroughStyle getStyleType])];
610
- } else if([tagName isEqualToString:@"code"]) {
1123
+ } else if ([tagName isEqualToString:@"code"]) {
611
1124
  [styleArr addObject:@([InlineCodeStyle getStyleType])];
612
- } else if([tagName isEqualToString:@"a"]) {
613
- NSRegularExpression *hrefRegex = [NSRegularExpression regularExpressionWithPattern:@"href=\".+\""
614
- options:0
615
- error:nullptr
616
- ];
617
- NSTextCheckingResult* match = [hrefRegex firstMatchInString:params options:0 range: NSMakeRange(0, params.length)];
618
-
619
- if(match == nullptr) {
1125
+ } else if ([tagName isEqualToString:@"a"]) {
1126
+ NSRegularExpression *hrefRegex =
1127
+ [NSRegularExpression regularExpressionWithPattern:@"href=\".+\""
1128
+ options:0
1129
+ error:nullptr];
1130
+ NSTextCheckingResult *match =
1131
+ [hrefRegex firstMatchInString:params
1132
+ options:0
1133
+ range:NSMakeRange(0, params.length)];
1134
+
1135
+ if (match == nullptr) {
620
1136
  // same as on Android, no href (or empty href) equals no link style
621
1137
  continue;
622
1138
  }
623
-
1139
+
624
1140
  NSRange hrefRange = match.range;
625
1141
  [styleArr addObject:@([LinkStyle getStyleType])];
626
1142
  // cut only the url from the href="..." string
627
- NSString *url = [params substringWithRange:NSMakeRange(hrefRange.location + 6, hrefRange.length - 7)];
1143
+ NSString *url =
1144
+ [params substringWithRange:NSMakeRange(hrefRange.location + 6,
1145
+ hrefRange.length - 7)];
628
1146
  stylePair.styleValue = url;
629
- } else if([tagName isEqualToString:@"mention"]) {
1147
+ } else if ([tagName isEqualToString:@"mention"]) {
630
1148
  [styleArr addObject:@([MentionStyle getStyleType])];
631
1149
  // extract html expression into dict using some regex
632
1150
  NSMutableDictionary *paramsDict = [[NSMutableDictionary alloc] init];
633
1151
  NSString *pattern = @"(\\w+)=\"([^\"]*)\"";
634
- NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];
635
-
636
- [regex enumerateMatchesInString:params options:0 range:NSMakeRange(0,params.length)
637
- usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
638
- if(result.numberOfRanges == 3) {
639
- NSString *key = [params substringWithRange:[result rangeAtIndex:1]];
640
- NSString *value = [params substringWithRange:[result rangeAtIndex:2]];
641
- paramsDict[key] = value;
642
- }
643
- }
644
- ];
645
-
1152
+ NSRegularExpression *regex =
1153
+ [NSRegularExpression regularExpressionWithPattern:pattern
1154
+ options:0
1155
+ error:nil];
1156
+
1157
+ [regex enumerateMatchesInString:params
1158
+ options:0
1159
+ range:NSMakeRange(0, params.length)
1160
+ usingBlock:^(NSTextCheckingResult *_Nullable result,
1161
+ NSMatchingFlags flags,
1162
+ BOOL *_Nonnull stop) {
1163
+ if (result.numberOfRanges == 3) {
1164
+ NSString *key = [params
1165
+ substringWithRange:[result rangeAtIndex:1]];
1166
+ NSString *value = [params
1167
+ substringWithRange:[result rangeAtIndex:2]];
1168
+ paramsDict[key] = value;
1169
+ }
1170
+ }];
1171
+
646
1172
  MentionParams *mentionParams = [[MentionParams alloc] init];
647
1173
  mentionParams.text = paramsDict[@"text"];
648
1174
  mentionParams.indicator = paramsDict[@"indicator"];
649
-
650
- [paramsDict removeObjectsForKeys:@[@"text", @"indicator"]];
1175
+
1176
+ [paramsDict removeObjectsForKeys:@[ @"text", @"indicator" ]];
651
1177
  NSError *error;
652
- NSData *attrsData = [NSJSONSerialization dataWithJSONObject:paramsDict options:0 error:&error];
653
- NSString *formattedAttrsString = [[NSString alloc] initWithData:attrsData encoding:NSUTF8StringEncoding];
1178
+ NSData *attrsData = [NSJSONSerialization dataWithJSONObject:paramsDict
1179
+ options:0
1180
+ error:&error];
1181
+ NSString *formattedAttrsString =
1182
+ [[NSString alloc] initWithData:attrsData
1183
+ encoding:NSUTF8StringEncoding];
654
1184
  mentionParams.attributes = formattedAttrsString;
655
-
1185
+
656
1186
  stylePair.styleValue = mentionParams;
657
- } else if([[tagName substringWithRange:NSMakeRange(0, 1)] isEqualToString: @"h"]) {
658
- if([tagName isEqualToString:@"h1"]) {
1187
+ } else if ([[tagName substringWithRange:NSMakeRange(0, 1)]
1188
+ isEqualToString:@"h"]) {
1189
+ if ([tagName isEqualToString:@"h1"]) {
659
1190
  [styleArr addObject:@([H1Style getStyleType])];
660
- } else if([tagName isEqualToString:@"h2"]) {
1191
+ } else if ([tagName isEqualToString:@"h2"]) {
661
1192
  [styleArr addObject:@([H2Style getStyleType])];
662
- } else if([tagName isEqualToString:@"h3"]) {
1193
+ } else if ([tagName isEqualToString:@"h3"]) {
663
1194
  [styleArr addObject:@([H3Style getStyleType])];
664
1195
  }
665
- } else if([tagName isEqualToString:@"ul"]) {
1196
+ } else if ([tagName isEqualToString:@"ul"]) {
666
1197
  [styleArr addObject:@([UnorderedListStyle getStyleType])];
667
- } else if([tagName isEqualToString:@"ol"]) {
1198
+ } else if ([tagName isEqualToString:@"ol"]) {
668
1199
  [styleArr addObject:@([OrderedListStyle getStyleType])];
669
- } else if([tagName isEqualToString:@"blockquote"]) {
1200
+ } else if ([tagName isEqualToString:@"blockquote"]) {
670
1201
  [styleArr addObject:@([BlockQuoteStyle getStyleType])];
1202
+ } else if ([tagName isEqualToString:@"codeblock"]) {
1203
+ [styleArr addObject:@([CodeBlockStyle getStyleType])];
671
1204
  } else {
672
- // some other external tags like span just don't get put into the processed styles
1205
+ // some other external tags like span just don't get put into the
1206
+ // processed styles
673
1207
  continue;
674
1208
  }
675
-
1209
+
676
1210
  stylePair.rangeValue = tagRangeValue;
677
1211
  [styleArr addObject:stylePair];
678
1212
  [processedStyles addObject:styleArr];
679
1213
  }
680
-
681
- return @[plainText, processedStyles];
1214
+
1215
+ return @[ plainText, processedStyles ];
682
1216
  }
683
1217
 
684
1218
  @end