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