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