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