react-native-enriched 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +869 -0
  3. package/ReactNativeEnriched.podspec +27 -0
  4. package/android/build.gradle +101 -0
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +146 -0
  6. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +55 -0
  7. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
  8. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
  9. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
  10. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
  11. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
  12. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +577 -0
  13. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
  14. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
  15. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.cpp +16 -0
  16. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.h +20 -0
  17. package/android/gradle.properties +5 -0
  18. package/android/src/main/AndroidManifest.xml +3 -0
  19. package/android/src/main/AndroidManifestNew.xml +2 -0
  20. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +535 -0
  21. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +64 -0
  22. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +292 -0
  23. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +19 -0
  24. package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +40 -0
  25. package/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt +28 -0
  26. package/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt +29 -0
  27. package/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt +24 -0
  28. package/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt +30 -0
  29. package/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt +27 -0
  30. package/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt +27 -0
  31. package/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt +30 -0
  32. package/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt +29 -0
  33. package/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt +33 -0
  34. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +34 -0
  35. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt +10 -0
  36. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +38 -0
  37. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt +17 -0
  38. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt +17 -0
  39. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt +17 -0
  40. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +41 -0
  41. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +16 -0
  42. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt +10 -0
  43. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt +24 -0
  44. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt +36 -0
  45. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +71 -0
  46. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +111 -0
  47. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt +9 -0
  48. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt +9 -0
  49. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +49 -0
  50. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt +4 -0
  51. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt +4 -0
  52. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt +4 -0
  53. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt +4 -0
  54. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt +4 -0
  55. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt +5 -0
  56. package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +227 -0
  57. package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +146 -0
  58. package/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt +173 -0
  59. package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +186 -0
  60. package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +223 -0
  61. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +857 -0
  62. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +285 -0
  63. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt +204 -0
  64. package/android/src/main/java/com/swmansion/enriched/utils/Utils.kt +91 -0
  65. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +73 -0
  66. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +51 -0
  67. package/android/src/main/new_arch/CMakeLists.txt +56 -0
  68. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.cpp +22 -0
  69. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.h +26 -0
  70. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputComponentDescriptor.h +35 -0
  71. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.cpp +51 -0
  72. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +26 -0
  73. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.cpp +34 -0
  74. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.h +54 -0
  75. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.cpp +9 -0
  76. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.h +25 -0
  77. package/ios/EnrichedTextInputView.h +33 -0
  78. package/ios/EnrichedTextInputView.mm +1190 -0
  79. package/ios/EnrichedTextInputViewManager.mm +13 -0
  80. package/ios/config/InputConfig.h +67 -0
  81. package/ios/config/InputConfig.mm +382 -0
  82. package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
  83. package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
  84. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
  85. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
  86. package/ios/generated/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
  87. package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +577 -0
  88. package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +384 -0
  89. package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
  90. package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
  91. package/ios/generated/RNEnrichedTextInputViewSpec/States.cpp +16 -0
  92. package/ios/generated/RNEnrichedTextInputViewSpec/States.h +20 -0
  93. package/ios/inputParser/InputParser.h +11 -0
  94. package/ios/inputParser/InputParser.mm +669 -0
  95. package/ios/inputTextView/InputTextView.h +6 -0
  96. package/ios/inputTextView/InputTextView.mm +115 -0
  97. package/ios/internals/EnrichedTextInputViewComponentDescriptor.h +17 -0
  98. package/ios/internals/EnrichedTextInputViewShadowNode.h +40 -0
  99. package/ios/internals/EnrichedTextInputViewShadowNode.mm +83 -0
  100. package/ios/internals/EnrichedTextInputViewState.cpp +10 -0
  101. package/ios/internals/EnrichedTextInputViewState.h +20 -0
  102. package/ios/styles/BlockQuoteStyle.mm +248 -0
  103. package/ios/styles/BoldStyle.mm +122 -0
  104. package/ios/styles/H1Style.mm +10 -0
  105. package/ios/styles/H2Style.mm +10 -0
  106. package/ios/styles/H3Style.mm +10 -0
  107. package/ios/styles/HeadingStyleBase.mm +144 -0
  108. package/ios/styles/InlineCodeStyle.mm +163 -0
  109. package/ios/styles/ItalicStyle.mm +110 -0
  110. package/ios/styles/LinkStyle.mm +463 -0
  111. package/ios/styles/MentionStyle.mm +476 -0
  112. package/ios/styles/OrderedListStyle.mm +225 -0
  113. package/ios/styles/StrikethroughStyle.mm +80 -0
  114. package/ios/styles/UnderlineStyle.mm +112 -0
  115. package/ios/styles/UnorderedListStyle.mm +225 -0
  116. package/ios/utils/BaseStyleProtocol.h +16 -0
  117. package/ios/utils/ColorExtension.h +6 -0
  118. package/ios/utils/ColorExtension.mm +27 -0
  119. package/ios/utils/FontExtension.h +13 -0
  120. package/ios/utils/FontExtension.mm +91 -0
  121. package/ios/utils/LayoutManagerExtension.h +6 -0
  122. package/ios/utils/LayoutManagerExtension.mm +171 -0
  123. package/ios/utils/LinkData.h +9 -0
  124. package/ios/utils/LinkData.mm +4 -0
  125. package/ios/utils/MentionParams.h +9 -0
  126. package/ios/utils/MentionParams.mm +4 -0
  127. package/ios/utils/MentionStyleProps.h +13 -0
  128. package/ios/utils/MentionStyleProps.mm +56 -0
  129. package/ios/utils/OccurenceUtils.h +37 -0
  130. package/ios/utils/OccurenceUtils.mm +124 -0
  131. package/ios/utils/ParagraphsUtils.h +7 -0
  132. package/ios/utils/ParagraphsUtils.mm +54 -0
  133. package/ios/utils/StringExtension.h +15 -0
  134. package/ios/utils/StringExtension.mm +57 -0
  135. package/ios/utils/StyleHeaders.h +74 -0
  136. package/ios/utils/StylePair.h +9 -0
  137. package/ios/utils/StylePair.mm +4 -0
  138. package/ios/utils/StyleTypeEnum.h +22 -0
  139. package/ios/utils/TextDecorationLineEnum.h +6 -0
  140. package/ios/utils/TextDecorationLineEnum.mm +4 -0
  141. package/ios/utils/TextInsertionUtils.h +6 -0
  142. package/ios/utils/TextInsertionUtils.mm +48 -0
  143. package/ios/utils/WordsUtils.h +6 -0
  144. package/ios/utils/WordsUtils.mm +88 -0
  145. package/ios/utils/ZeroWidthSpaceUtils.h +7 -0
  146. package/ios/utils/ZeroWidthSpaceUtils.mm +164 -0
  147. package/lib/module/EnrichedTextInput.js +191 -0
  148. package/lib/module/EnrichedTextInput.js.map +1 -0
  149. package/lib/module/EnrichedTextInputNativeComponent.ts +235 -0
  150. package/lib/module/index.js +4 -0
  151. package/lib/module/index.js.map +1 -0
  152. package/lib/module/normalizeHtmlStyle.js +141 -0
  153. package/lib/module/normalizeHtmlStyle.js.map +1 -0
  154. package/lib/module/package.json +1 -0
  155. package/lib/typescript/package.json +1 -0
  156. package/lib/typescript/src/EnrichedTextInput.d.ts +113 -0
  157. package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -0
  158. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +160 -0
  159. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -0
  160. package/lib/typescript/src/index.d.ts +3 -0
  161. package/lib/typescript/src/index.d.ts.map +1 -0
  162. package/lib/typescript/src/normalizeHtmlStyle.d.ts +4 -0
  163. package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +1 -0
  164. package/package.json +172 -1
  165. package/react-native.config.js +13 -0
  166. package/src/EnrichedTextInput.tsx +358 -0
  167. package/src/EnrichedTextInputNativeComponent.ts +235 -0
  168. package/src/index.tsx +9 -0
  169. package/src/normalizeHtmlStyle.ts +188 -0
@@ -0,0 +1,669 @@
1
+ #import "InputParser.h"
2
+ #import "EnrichedTextInputView.h"
3
+ #import "StyleHeaders.h"
4
+ #import "UIView+React.h"
5
+ #import "TextInsertionUtils.h"
6
+
7
+ @implementation InputParser {
8
+ EnrichedTextInputView *_input;
9
+ }
10
+
11
+ - (instancetype)initWithInput:(id)input {
12
+ self = [super init];
13
+ _input = (EnrichedTextInputView *)input;
14
+ return self;
15
+ }
16
+
17
+ - (NSString *)parseToHtmlFromRange:(NSRange)range {
18
+ NSInteger offset = range.location;
19
+ NSString *text = [_input->textView.textStorage.string substringWithRange:range];
20
+
21
+ if(text.length == 0) {
22
+ return @"<html>\n<p></p>\n</html>";
23
+ }
24
+
25
+ NSMutableString *result = [[NSMutableString alloc] initWithString: @"<html>"];
26
+ NSSet<NSNumber *>*previousActiveStyles = [[NSSet<NSNumber *> alloc]init];
27
+ BOOL newLine = YES;
28
+ BOOL inUnorderedList = NO;
29
+ BOOL inOrderedList = NO;
30
+ BOOL inBlockQuote = NO;
31
+ unichar lastCharacter = 0;
32
+
33
+ for(int i = 0; i < text.length; i++) {
34
+ NSRange currentRange = NSMakeRange(offset + i, 1);
35
+ NSMutableSet<NSNumber *>*currentActiveStyles = [[NSMutableSet<NSNumber *> alloc]init];
36
+ NSMutableDictionary *currentActiveStylesBeginning = [[NSMutableDictionary alloc] init];
37
+
38
+ // check each existing style existence
39
+ for(NSNumber* type in _input->stylesDict) {
40
+ id<BaseStyleProtocol> style = _input->stylesDict[type];
41
+ if([style detectStyle:currentRange]) {
42
+ [currentActiveStyles addObject:type];
43
+
44
+ if(![previousActiveStyles member:type]) {
45
+ currentActiveStylesBeginning[type] = [NSNumber numberWithInt:i];
46
+ }
47
+ } else if([previousActiveStyles member:type]) {
48
+ [currentActiveStylesBeginning removeObjectForKey:type];
49
+ }
50
+ }
51
+
52
+ NSString *currentCharacterStr = [_input->textView.textStorage.string substringWithRange:currentRange];
53
+ unichar currentCharacterChar = [_input->textView.textStorage.string characterAtIndex:currentRange.location];
54
+
55
+ if([[NSCharacterSet newlineCharacterSet] characterIsMember:currentCharacterChar]) {
56
+ if(newLine) {
57
+ // we can either have an empty list item OR need to close the list and put a BR in such a situation
58
+ // the existence of the list must be checked on 0 length range, not on the newline character
59
+ if(inOrderedList) {
60
+ OrderedListStyle *oStyle = _input->stylesDict[@(OrderedList)];
61
+ BOOL detected = [oStyle detectStyle: NSMakeRange(currentRange.location, 0)];
62
+ if(detected) {
63
+ [result appendString:@"\n<li></li>"];
64
+ } else {
65
+ [result appendString:@"\n</ol>\n<br>"];
66
+ inOrderedList = NO;
67
+ }
68
+ } else if(inUnorderedList) {
69
+ UnorderedListStyle *uStyle = _input->stylesDict[@(UnorderedList)];
70
+ BOOL detected = [uStyle detectStyle: NSMakeRange(currentRange.location, 0)];
71
+ if(detected) {
72
+ [result appendString:@"\n<li></li>"];
73
+ } else {
74
+ [result appendString:@"\n</ul>\n<br>"];
75
+ inUnorderedList = NO;
76
+ }
77
+ } else {
78
+ [result appendString:@"\n<br>"];
79
+ }
80
+ } else {
81
+ // newline finishes a paragraph and all style tags need to be closed
82
+ // we use previous styles
83
+ NSArray<NSNumber*> *sortedEndedStyles = [previousActiveStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:NO]]];
84
+
85
+ // append closing tags
86
+ for(NSNumber *style in sortedEndedStyles) {
87
+ NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:currentRange.location];
88
+ [result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
89
+ }
90
+
91
+ // append closing paragraph tag
92
+ if([previousActiveStyles containsObject:@([UnorderedListStyle getStyleType])] ||
93
+ [previousActiveStyles containsObject:@([OrderedListStyle getStyleType])] ||
94
+ [previousActiveStyles containsObject:@([H1Style getStyleType])] ||
95
+ [previousActiveStyles containsObject:@([H2Style getStyleType])] ||
96
+ [previousActiveStyles containsObject:@([H3Style getStyleType])] ||
97
+ [previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]
98
+ ) {
99
+ // do nothing, proper closing paragraph tags have been already appended
100
+ } else {
101
+ [result appendString:@"</p>"];
102
+ }
103
+ }
104
+
105
+ // clear the previous styles
106
+ previousActiveStyles = [[NSSet<NSNumber *> alloc]init];
107
+
108
+ // next character opens new paragraph
109
+ newLine = YES;
110
+ } else {
111
+ // new line - open the paragraph
112
+ if(newLine) {
113
+ newLine = NO;
114
+
115
+ // handle ending unordered list
116
+ if(inUnorderedList && ![currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])]) {
117
+ inUnorderedList = NO;
118
+ [result appendString:@"\n</ul>"];
119
+ }
120
+ // handle ending ordered list
121
+ if(inOrderedList && ![currentActiveStyles containsObject:@([OrderedListStyle getStyleType])]) {
122
+ inOrderedList = NO;
123
+ [result appendString:@"\n</ol>"];
124
+ }
125
+ // handle ending blockquotes
126
+ if(inBlockQuote && ![currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]) {
127
+ inBlockQuote = NO;
128
+ [result appendString:@"\n</blockquote>"];
129
+ }
130
+
131
+ // handle starting unordered list
132
+ if(!inUnorderedList && [currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])]) {
133
+ inUnorderedList = YES;
134
+ [result appendString:@"\n<ul>"];
135
+ }
136
+ // handle starting ordered list
137
+ if(!inOrderedList && [currentActiveStyles containsObject:@([OrderedListStyle getStyleType])]) {
138
+ inOrderedList = YES;
139
+ [result appendString:@"\n<ol>"];
140
+ }
141
+ // handle starting blockquotes
142
+ if(!inBlockQuote && [currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]) {
143
+ inBlockQuote = YES;
144
+ [result appendString:@"\n<blockquote>"];
145
+ }
146
+
147
+ // don't add the <p> tag if some paragraph styles are present
148
+ if([currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])] ||
149
+ [currentActiveStyles containsObject:@([OrderedListStyle getStyleType])] ||
150
+ [currentActiveStyles containsObject:@([H1Style getStyleType])] ||
151
+ [currentActiveStyles containsObject:@([H2Style getStyleType])] ||
152
+ [currentActiveStyles containsObject:@([H3Style getStyleType])] ||
153
+ [currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]
154
+ ) {
155
+ [result appendString:@"\n"];
156
+ } else {
157
+ [result appendString:@"\n<p>"];
158
+ }
159
+ }
160
+
161
+ // get styles that have ended
162
+ NSMutableSet<NSNumber *> *endedStyles = [previousActiveStyles mutableCopy];
163
+ [endedStyles minusSet: currentActiveStyles];
164
+
165
+ // also finish styles that should be ended becasue they are nested in a style that ended
166
+ NSMutableSet *fixedEndedStyles = [endedStyles mutableCopy];
167
+ NSMutableSet *stylesToBeReAdded = [[NSMutableSet alloc] init];
168
+
169
+ for(NSNumber *style in endedStyles) {
170
+ NSInteger styleBeginning = [currentActiveStylesBeginning[style] integerValue];
171
+
172
+ for(NSNumber *activeStyle in currentActiveStyles) {
173
+ NSInteger activeStyleBeginning = [currentActiveStylesBeginning[activeStyle] integerValue];
174
+ // we end the styles that began after the currently ended style
175
+ // also the ones that ended in the exact same place but are "inner" in relation to them due to StyleTypeEnum integer values
176
+ // "activeStylesBeginning < i" is needed, so that we don't remove styles that have been freshly added now
177
+ if((activeStyleBeginning > styleBeginning) ||
178
+ (activeStyleBeginning == styleBeginning && activeStyleBeginning < i && [activeStyle integerValue] > [style integerValue])) {
179
+ [fixedEndedStyles addObject:activeStyle];
180
+ [stylesToBeReAdded addObject:activeStyle];
181
+ }
182
+ }
183
+ }
184
+
185
+ // they are sorted in a descending order
186
+ NSArray<NSNumber*> *sortedEndedStyles = [fixedEndedStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:NO]]];
187
+
188
+ // append closing tags
189
+ for(NSNumber *style in sortedEndedStyles) {
190
+ NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:currentRange.location];
191
+ [result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
192
+ }
193
+
194
+ // get styles that have begun: they are sorted in a ascending manner to properly keep tags' FILO order
195
+ NSMutableSet<NSNumber *> *newStyles = [currentActiveStyles mutableCopy];
196
+ [newStyles minusSet: previousActiveStyles];
197
+ [newStyles unionSet: stylesToBeReAdded];
198
+ NSArray<NSNumber*> *sortedNewStyles = [newStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:YES]]];
199
+
200
+ // append opening tags
201
+ for(NSNumber *style in sortedNewStyles) {
202
+ NSString *tagContent = [self tagContentForStyle:style openingTag:YES location:currentRange.location];
203
+ [result appendString: [NSString stringWithFormat:@"<%@>", tagContent]];
204
+ }
205
+
206
+ // append the letter
207
+ [result appendString:currentCharacterStr];
208
+
209
+ // save current styles for next character's checks
210
+ previousActiveStyles = currentActiveStyles;
211
+ }
212
+
213
+ // set last character
214
+ lastCharacter = currentCharacterChar;
215
+ }
216
+
217
+ if(![[NSCharacterSet newlineCharacterSet] characterIsMember:lastCharacter]) {
218
+ // not-newline character was last - finish the paragraph
219
+ // close all pending tags
220
+ NSArray<NSNumber*> *sortedEndedStyles = [previousActiveStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:NO]]];
221
+
222
+ // append closing tags
223
+ for(NSNumber *style in sortedEndedStyles) {
224
+ NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:_input->textView.textStorage.string.length - 1];
225
+ [result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
226
+ }
227
+
228
+ // finish the paragraph
229
+ // handle ending of some paragraph styles
230
+ if([previousActiveStyles containsObject:@([UnorderedListStyle getStyleType])]) {
231
+ [result appendString:@"\n</ul>"];
232
+ } else if([previousActiveStyles containsObject:@([OrderedListStyle getStyleType])]) {
233
+ [result appendString:@"\n</ol>"];
234
+ } else if([previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]) {
235
+ [result appendString:@"\n</blockquote>"];
236
+ } else if(
237
+ [previousActiveStyles containsObject:@([H1Style getStyleType])] ||
238
+ [previousActiveStyles containsObject:@([H2Style getStyleType])] ||
239
+ [previousActiveStyles containsObject:@([H3Style getStyleType])]
240
+ ) {
241
+ // do nothing, heading closing tag has already ben appended
242
+ } else {
243
+ [result appendString:@"</p>"];
244
+ }
245
+ } else {
246
+ // newline character was last - some paragraph styles need to be closed
247
+ if(inUnorderedList) {
248
+ inUnorderedList = NO;
249
+ [result appendString:@"\n</ul>"];
250
+ }
251
+ if(inOrderedList) {
252
+ inOrderedList = NO;
253
+ [result appendString:@"\n</ol>"];
254
+ }
255
+ if(inBlockQuote) {
256
+ inBlockQuote = NO;
257
+ [result appendString:@"\n</blockquote>"];
258
+ }
259
+ }
260
+
261
+ [result appendString: @"\n</html>"];
262
+
263
+ // remove zero width spaces in the very end
264
+ NSRange resultRange = NSMakeRange(0, result.length);
265
+ [result replaceOccurrencesOfString:@"\u200B" withString:@"" options:0 range:resultRange];
266
+ return result;
267
+ }
268
+
269
+ - (NSString *)tagContentForStyle:(NSNumber *)style openingTag:(BOOL)openingTag location:(NSInteger)location {
270
+ if([style isEqualToNumber: @([BoldStyle getStyleType])]) {
271
+ return @"b";
272
+ } else if([style isEqualToNumber: @([ItalicStyle getStyleType])]) {
273
+ return @"i";
274
+ } else if([style isEqualToNumber: @([UnderlineStyle getStyleType])]) {
275
+ return @"u";
276
+ } else if([style isEqualToNumber: @([StrikethroughStyle getStyleType])]) {
277
+ return @"s";
278
+ } else if([style isEqualToNumber: @([InlineCodeStyle getStyleType])]) {
279
+ return @"code";
280
+ } else if([style isEqualToNumber: @([LinkStyle getStyleType])]) {
281
+ if(openingTag) {
282
+ LinkStyle *linkStyle = (LinkStyle *)_input->stylesDict[@([LinkStyle getStyleType])];
283
+ if(linkStyle != nullptr) {
284
+ LinkData *data = [linkStyle getLinkDataAt: location];
285
+ if(data != nullptr && data.url != nullptr) {
286
+ return [NSString stringWithFormat:@"a href=\"%@\"", data.url];
287
+ }
288
+ }
289
+ return @"a";
290
+ } else {
291
+ return @"a";
292
+ }
293
+ } else if([style isEqualToNumber: @([MentionStyle getStyleType])]) {
294
+ if(openingTag) {
295
+ MentionStyle *mentionStyle = (MentionStyle *)_input->stylesDict[@([MentionStyle getStyleType])];
296
+ if(mentionStyle != nullptr) {
297
+ MentionParams *params = [mentionStyle getMentionParamsAt:location];
298
+ // attributes can theoretically be nullptr
299
+ if(params != nullptr && params.indicator != nullptr && params.text != nullptr) {
300
+ NSMutableString *attrsStr = [[NSMutableString alloc] initWithString: @""];
301
+ if(params.attributes != nullptr) {
302
+ // turn attributes to Data and then into dict
303
+ NSData *attrsData = [params.attributes dataUsingEncoding:NSUTF8StringEncoding];
304
+ NSError *jsonError;
305
+ NSDictionary *json = [NSJSONSerialization JSONObjectWithData:attrsData
306
+ options:0
307
+ error:&jsonError
308
+ ];
309
+ // format dict keys and values into string
310
+ [json enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
311
+ [attrsStr appendString: [NSString stringWithFormat:@" %@=\"%@\"", (NSString *)key, (NSString *)obj]];
312
+ }];
313
+ }
314
+ return [NSString stringWithFormat:@"mention text=\"%@\" indicator=\"%@\"%@", params.text, params.indicator, attrsStr];
315
+ }
316
+ }
317
+ return @"mention";
318
+ } else {
319
+ return @"mention";
320
+ }
321
+ } else if([style isEqualToNumber:@([H1Style getStyleType])]) {
322
+ return @"h1";
323
+ } else if([style isEqualToNumber:@([H2Style getStyleType])]) {
324
+ return @"h2";
325
+ } else if([style isEqualToNumber:@([H3Style getStyleType])]) {
326
+ return @"h3";
327
+ } else if([style isEqualToNumber:@([UnorderedListStyle getStyleType])] || [style isEqualToNumber:@([OrderedListStyle getStyleType])]) {
328
+ return @"li";
329
+ } else if([style isEqualToNumber:@([BlockQuoteStyle getStyleType])]) {
330
+ // blockquotes use <p> tags the same way lists use <li>
331
+ return @"p";
332
+ }
333
+ return @"";
334
+ }
335
+
336
+ - (void)replaceWholeFromHtml:(NSString * _Nonnull)html {
337
+ NSArray *processingResult = [self getTextAndStylesFromHtml:html];
338
+ NSString *plainText = (NSString *)processingResult[0];
339
+ NSArray *stylesInfo = (NSArray *)processingResult[1];
340
+
341
+ // reset the text first and reset typing attributes
342
+ _input->textView.text = @"";
343
+ _input->textView.typingAttributes = _input->defaultTypingAttributes;
344
+
345
+ // set new text
346
+ _input->textView.text = plainText;
347
+
348
+ // re-apply the styles
349
+ [self applyProcessedStyles:stylesInfo offsetFromBeginning:0];
350
+ }
351
+
352
+ - (void)replaceFromHtml:(NSString * _Nonnull)html range:(NSRange)range {
353
+ NSArray *processingResult = [self getTextAndStylesFromHtml:html];
354
+ NSString *plainText = (NSString *)processingResult[0];
355
+ NSArray *stylesInfo = (NSArray *)processingResult[1];
356
+
357
+ // we can use ready replace util
358
+ [TextInsertionUtils replaceText:plainText at:range additionalAttributes:nil input:_input withSelection:YES];
359
+
360
+ [self applyProcessedStyles:stylesInfo offsetFromBeginning:range.location];
361
+ }
362
+
363
+ - (void)insertFromHtml:(NSString * _Nonnull)html location:(NSInteger)location {
364
+ NSArray *processingResult = [self getTextAndStylesFromHtml:html];
365
+ NSString *plainText = (NSString *)processingResult[0];
366
+ NSArray *stylesInfo = (NSArray *)processingResult[1];
367
+
368
+ // same here, insertion utils got our back
369
+ [TextInsertionUtils insertText:plainText at:location additionalAttributes:nil input:_input withSelection:YES];
370
+
371
+ [self applyProcessedStyles:stylesInfo offsetFromBeginning:location];
372
+ }
373
+
374
+ - (void)applyProcessedStyles:(NSArray *)processedStyles offsetFromBeginning:(NSInteger)offset {
375
+ for(NSArray* arr in processedStyles) {
376
+ // unwrap all info from processed style
377
+ NSNumber *styleType = (NSNumber *)arr[0];
378
+ StylePair *stylePair = (StylePair *)arr[1];
379
+ id<BaseStyleProtocol> baseStyle = _input->stylesDict[styleType];
380
+ // range must be taking offest into consideration because processed styles' ranges are relative to only the new text
381
+ // while we need absolute ranges relative to the whole existing text
382
+ NSRange styleRange = NSMakeRange(offset + [stylePair.rangeValue rangeValue].location, [stylePair.rangeValue rangeValue].length);
383
+
384
+ // of course any changes here need to take blocks and conflicts into consideration
385
+ if([_input handleStyleBlocksAndConflicts:[[baseStyle class] getStyleType] range:styleRange]) {
386
+ if([styleType isEqualToNumber: @([LinkStyle getStyleType])]) {
387
+ NSString *text = [_input->textView.textStorage.string substringWithRange:styleRange];
388
+ NSString *url = (NSString *)stylePair.styleValue;
389
+ BOOL isManual = ![text isEqualToString:url];
390
+ [((LinkStyle *)baseStyle) addLink:text url:url range:styleRange manual:isManual];
391
+ } else if([styleType isEqualToNumber: @([MentionStyle getStyleType])]) {
392
+ MentionParams *params = (MentionParams *)stylePair.styleValue;
393
+ [((MentionStyle *)baseStyle) addMentionAtRange:styleRange params:params];
394
+ } else {
395
+ [baseStyle addAttributes:styleRange];
396
+ }
397
+ }
398
+ }
399
+ [_input anyTextMayHaveBeenModified];
400
+ }
401
+
402
+ - (NSString * _Nullable)initiallyProcessHtml:(NSString * _Nonnull)html {
403
+ NSString *fixedHtml = nullptr;
404
+
405
+ if(html.length >= 13) {
406
+ NSString *firstSix = [html substringWithRange:NSMakeRange(0, 6)];
407
+ NSString *lastSeven = [html substringWithRange:NSMakeRange(html.length-7, 7)];
408
+ NSInteger newlinesCount = [[html componentsSeparatedByString:@"\n"] count] - 1;
409
+
410
+ if([firstSix isEqualToString:@"<html>"] && [lastSeven isEqualToString:@"</html>"]) {
411
+ if(newlinesCount >= 2) {
412
+ // looks like our format
413
+ // we want to get the string without <html> and </html> and their newlines
414
+ // so we skip first 7 characters and get the string 7+8 = 15 characters shorter
415
+ fixedHtml = [html substringWithRange: NSMakeRange(7, html.length - 15)];
416
+ } else {
417
+ // most likely a valid html but with some newline differences
418
+ fixedHtml = [html copy];
419
+ // firstly remove newlined html tags if any:
420
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<html>\n" withString:@""];
421
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n</html>" withString:@""];
422
+ // fallback; remove html tags without their newlines
423
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<html>" withString:@""];
424
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"</html>" withString:@""];
425
+ }
426
+ } else {
427
+ // in other case we are most likely working with some external html - try getting the styles from between body tags
428
+ NSRange openingBodyRange = [html rangeOfString:@"<body>"];
429
+ NSRange closingBodyRange = [html rangeOfString:@"</body>"];
430
+
431
+ if(openingBodyRange.length != 0 && closingBodyRange.length != 0) {
432
+ NSInteger newStart = openingBodyRange.location + 7;
433
+ NSInteger newEnd = closingBodyRange.location - 1;
434
+ fixedHtml = [html substringWithRange:NSMakeRange(newStart, newEnd - newStart + 1)];
435
+ }
436
+ }
437
+ }
438
+
439
+ // second processing - try fixing htmls with wrong newlines' setup
440
+ if(fixedHtml != nullptr) {
441
+ NSInteger newlinesCount = [[fixedHtml componentsSeparatedByString:@"/n"] count] - 1;
442
+ if(newlinesCount == 0) {
443
+ // add <br> tag wherever needed
444
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<p></p>" withString:@"<br>"];
445
+
446
+ // remove <p> tags inside of <li>
447
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<li><p>" withString:@"<li>"];
448
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"</p></li>" withString:@"</li>"];
449
+
450
+ // tags that have to be in separate lines
451
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<br>" inString:fixedHtml leading:YES trailing:YES];
452
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<ul>" inString:fixedHtml leading:YES trailing:YES];
453
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</ul>" inString:fixedHtml leading:YES trailing:YES];
454
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<ol>" inString:fixedHtml leading:YES trailing:YES];
455
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</ol>" inString:fixedHtml leading:YES trailing:YES];
456
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<blockquote>" inString:fixedHtml leading:YES trailing:YES];
457
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</blockquote>" inString:fixedHtml leading:YES trailing:YES];
458
+
459
+ // line opening tags
460
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<p>" inString:fixedHtml leading:YES trailing:NO];
461
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<li>" inString:fixedHtml leading:YES trailing:NO];
462
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<h1>" inString:fixedHtml leading:YES trailing:NO];
463
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<h2>" inString:fixedHtml leading:YES trailing:NO];
464
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<h3>" inString:fixedHtml leading:YES trailing:NO];
465
+
466
+ // line closing tags
467
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</p>" inString:fixedHtml leading:NO trailing:YES];
468
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</li>" inString:fixedHtml leading:NO trailing:YES];
469
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</h1>" inString:fixedHtml leading:NO trailing:YES];
470
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</h2>" inString:fixedHtml leading:NO trailing:YES];
471
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</h3>" inString:fixedHtml leading:NO trailing:YES];
472
+ }
473
+ }
474
+
475
+ return fixedHtml;
476
+ }
477
+
478
+ - (NSString *)stringByAddingNewlinesToTag:(NSString *)tag inString:(NSString *)html leading:(BOOL)leading trailing:(BOOL)trailing {
479
+ NSString *str = [html copy];
480
+ if(leading) {
481
+ NSString *formattedTag = [NSString stringWithFormat:@">%@", tag];
482
+ NSString *formattedNewTag = [NSString stringWithFormat:@">\n%@", tag];
483
+ str = [str stringByReplacingOccurrencesOfString:formattedTag withString:formattedNewTag];
484
+ }
485
+ if(trailing) {
486
+ NSString *formattedTag = [NSString stringWithFormat:@"%@<", tag];
487
+ NSString *formattedNewTag = [NSString stringWithFormat:@"%@\n<", tag];
488
+ str = [str stringByReplacingOccurrencesOfString:formattedTag withString:formattedNewTag];
489
+ }
490
+ return str;
491
+ }
492
+
493
+ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
494
+ NSMutableString *plainText = [[NSMutableString alloc] initWithString: @""];
495
+ NSMutableDictionary *ongoingTags = [[NSMutableDictionary alloc] init];
496
+ NSMutableArray *initiallyProcessedTags = [[NSMutableArray alloc] init];
497
+ BOOL insideTag = NO;
498
+ BOOL gettingTagName = NO;
499
+ BOOL gettingTagParams = NO;
500
+ BOOL closingTag = NO;
501
+ NSMutableString *currentTagName = [[NSMutableString alloc] initWithString:@""];
502
+ NSMutableString *currentTagParams = [[NSMutableString alloc] initWithString:@""];
503
+
504
+ // firstly, extract text and initially processed tags
505
+ for(int i = 0; i < fixedHtml.length; i++) {
506
+ NSString *currentCharacterStr = [fixedHtml substringWithRange:NSMakeRange(i, 1)];
507
+ unichar currentCharacterChar = [fixedHtml characterAtIndex:i];
508
+
509
+ if(currentCharacterChar == '<') {
510
+ // opening the tag, mark that we are inside and getting its name
511
+ insideTag = YES;
512
+ gettingTagName = YES;
513
+ } else if(currentCharacterChar == '>') {
514
+ // finishing some tag, no longer marked as inside or getting its name/params
515
+ insideTag = NO;
516
+ gettingTagName = NO;
517
+ gettingTagParams = NO;
518
+
519
+ if([currentTagName isEqualToString:@"p"] || [currentTagName isEqualToString:@"br"] || [currentTagName isEqualToString:@"li"]) {
520
+ // do nothing, we don't include these tags in styles
521
+ } else if(!closingTag) {
522
+ // we finish opening tag - get its location and optionally params and put them under tag name key in ongoingTags
523
+ NSMutableArray *tagArr = [[NSMutableArray alloc] init];
524
+ [tagArr addObject:[NSNumber numberWithInt:plainText.length]];
525
+ if(currentTagParams.length > 0) {
526
+ [tagArr addObject:[currentTagParams copy]];
527
+ }
528
+ ongoingTags[currentTagName] = tagArr;
529
+
530
+ // skip one newline after opening tags that are in separate lines intentionally
531
+ if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"]) {
532
+ i += 1;
533
+ }
534
+ } else {
535
+ // we finish closing tags - pack tag name, tag range and optionally tag params into an entry that goes inside initiallyProcessedTags
536
+
537
+ // skip one newline that was added before some closing tags that are in separate lines
538
+ if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"]) {
539
+ plainText = [[plainText substringWithRange: NSMakeRange(0, plainText.length - 1)] mutableCopy];
540
+ }
541
+
542
+ NSMutableArray *tagEntry = [[NSMutableArray alloc] init];
543
+
544
+ NSArray *tagData = ongoingTags[currentTagName];
545
+ NSInteger tagLocation = [((NSNumber *)tagData[0]) intValue];
546
+ NSRange tagRange = NSMakeRange(tagLocation, plainText.length - tagLocation);
547
+
548
+ [tagEntry addObject:[currentTagName copy]];
549
+ [tagEntry addObject:[NSValue valueWithRange:tagRange]];
550
+ if(tagData.count > 1) {
551
+ [tagEntry addObject:[(NSString *)tagData[1] copy]];
552
+ }
553
+
554
+ [initiallyProcessedTags addObject:tagEntry];
555
+ [ongoingTags removeObjectForKey:currentTagName];
556
+ }
557
+ // post-tag cleanup
558
+ closingTag = NO;
559
+ currentTagName = [[NSMutableString alloc] initWithString:@""];
560
+ currentTagParams = [[NSMutableString alloc] initWithString:@""];
561
+ } else {
562
+ if(!insideTag) {
563
+ // no tags logic - just append text
564
+ [plainText appendString:currentCharacterStr];
565
+ } else {
566
+ if(gettingTagName) {
567
+ if(currentCharacterChar == ' ') {
568
+ // no longer getting tag name - switch to params
569
+ gettingTagName = NO;
570
+ gettingTagParams = YES;
571
+ } else if(currentCharacterChar == '/') {
572
+ // mark that the tag is closing
573
+ closingTag = YES;
574
+ } else {
575
+ // append next tag char
576
+ [currentTagName appendString:currentCharacterStr];
577
+ }
578
+ } else if(gettingTagParams) {
579
+ // append next tag params char
580
+ [currentTagParams appendString:currentCharacterStr];
581
+ }
582
+ }
583
+ }
584
+ }
585
+
586
+ // process tags into proper StyleType + StylePair values
587
+ NSMutableArray *processedStyles = [[NSMutableArray alloc] init];
588
+
589
+ for(NSArray* arr in initiallyProcessedTags) {
590
+ NSString *tagName = (NSString *)arr[0];
591
+ NSValue *tagRangeValue = (NSValue *)arr[1];
592
+ NSMutableString *params = [[NSMutableString alloc] initWithString:@""];
593
+ if(arr.count > 2) {
594
+ [params appendString:(NSString *)arr[2]];
595
+ }
596
+
597
+ NSMutableArray *styleArr = [[NSMutableArray alloc] init];
598
+ StylePair *stylePair = [[StylePair alloc] init];
599
+ if([tagName isEqualToString:@"b"]) {
600
+ [styleArr addObject:@([BoldStyle getStyleType])];
601
+ } else if([tagName isEqualToString:@"i"]) {
602
+ [styleArr addObject:@([ItalicStyle getStyleType])];
603
+ } else if([tagName isEqualToString:@"u"]) {
604
+ [styleArr addObject:@([UnderlineStyle getStyleType])];
605
+ } else if([tagName isEqualToString:@"s"]) {
606
+ [styleArr addObject:@([StrikethroughStyle getStyleType])];
607
+ } else if([tagName isEqualToString:@"code"]) {
608
+ [styleArr addObject:@([InlineCodeStyle getStyleType])];
609
+ } else if([tagName isEqualToString:@"a"]) {
610
+ [styleArr addObject:@([LinkStyle getStyleType])];
611
+ // cut only the url from the href="..." string
612
+ NSString *url = [params substringWithRange:NSMakeRange(6, params.length - 7)];
613
+ stylePair.styleValue = url;
614
+ } else if([tagName isEqualToString:@"mention"]) {
615
+ [styleArr addObject:@([MentionStyle getStyleType])];
616
+ // extract html expression into dict using some regex
617
+ NSMutableDictionary *paramsDict = [[NSMutableDictionary alloc] init];
618
+ NSString *pattern = @"(\\w+)=\"([^\"]*)\"";
619
+ NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];
620
+
621
+ [regex enumerateMatchesInString:params options:0 range:NSMakeRange(0,params.length)
622
+ usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
623
+ if(result.numberOfRanges == 3) {
624
+ NSString *key = [params substringWithRange:[result rangeAtIndex:1]];
625
+ NSString *value = [params substringWithRange:[result rangeAtIndex:2]];
626
+ paramsDict[key] = value;
627
+ }
628
+ }
629
+ ];
630
+
631
+ MentionParams *mentionParams = [[MentionParams alloc] init];
632
+ mentionParams.text = paramsDict[@"text"];
633
+ mentionParams.indicator = paramsDict[@"indicator"];
634
+
635
+ [paramsDict removeObjectsForKeys:@[@"text", @"indicator"]];
636
+ NSError *error;
637
+ NSData *attrsData = [NSJSONSerialization dataWithJSONObject:paramsDict options:0 error:&error];
638
+ NSString *formattedAttrsString = [[NSString alloc] initWithData:attrsData encoding:NSUTF8StringEncoding];
639
+ mentionParams.attributes = formattedAttrsString;
640
+
641
+ stylePair.styleValue = mentionParams;
642
+ } else if([[tagName substringWithRange:NSMakeRange(0, 1)] isEqualToString: @"h"]) {
643
+ if([tagName isEqualToString:@"h1"]) {
644
+ [styleArr addObject:@([H1Style getStyleType])];
645
+ } else if([tagName isEqualToString:@"h2"]) {
646
+ [styleArr addObject:@([H2Style getStyleType])];
647
+ } else if([tagName isEqualToString:@"h3"]) {
648
+ [styleArr addObject:@([H3Style getStyleType])];
649
+ }
650
+ } else if([tagName isEqualToString:@"ul"]) {
651
+ [styleArr addObject:@([UnorderedListStyle getStyleType])];
652
+ } else if([tagName isEqualToString:@"ol"]) {
653
+ [styleArr addObject:@([OrderedListStyle getStyleType])];
654
+ } else if([tagName isEqualToString:@"blockquote"]) {
655
+ [styleArr addObject:@([BlockQuoteStyle getStyleType])];
656
+ } else {
657
+ // some other external tags like span just don't get put into the processed styles
658
+ continue;
659
+ }
660
+
661
+ stylePair.rangeValue = tagRangeValue;
662
+ [styleArr addObject:stylePair];
663
+ [processedStyles addObject:styleArr];
664
+ }
665
+
666
+ return @[plainText, processedStyles];
667
+ }
668
+
669
+ @end
@@ -0,0 +1,6 @@
1
+ #pragma once
2
+ #import <UIkit/UIKit.h>
3
+
4
+ @interface InputTextView : UITextView
5
+ @property (nonatomic, weak) id input;
6
+ @end