react-native-enriched 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +869 -0
  3. package/ReactNativeEnriched.podspec +27 -0
  4. package/android/build.gradle +101 -0
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +146 -0
  6. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +55 -0
  7. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
  8. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
  9. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
  10. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
  11. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
  12. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +577 -0
  13. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
  14. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
  15. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.cpp +16 -0
  16. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.h +20 -0
  17. package/android/gradle.properties +5 -0
  18. package/android/src/main/AndroidManifest.xml +3 -0
  19. package/android/src/main/AndroidManifestNew.xml +2 -0
  20. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +535 -0
  21. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +64 -0
  22. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +292 -0
  23. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +19 -0
  24. package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +40 -0
  25. package/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt +28 -0
  26. package/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt +29 -0
  27. package/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt +24 -0
  28. package/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt +30 -0
  29. package/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt +27 -0
  30. package/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt +27 -0
  31. package/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt +30 -0
  32. package/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt +29 -0
  33. package/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt +33 -0
  34. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +34 -0
  35. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt +10 -0
  36. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +38 -0
  37. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt +17 -0
  38. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt +17 -0
  39. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt +17 -0
  40. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +41 -0
  41. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +16 -0
  42. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt +10 -0
  43. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt +24 -0
  44. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt +36 -0
  45. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +71 -0
  46. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +111 -0
  47. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt +9 -0
  48. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt +9 -0
  49. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +49 -0
  50. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt +4 -0
  51. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt +4 -0
  52. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt +4 -0
  53. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt +4 -0
  54. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt +4 -0
  55. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt +5 -0
  56. package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +227 -0
  57. package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +146 -0
  58. package/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt +173 -0
  59. package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +186 -0
  60. package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +223 -0
  61. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +857 -0
  62. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +285 -0
  63. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt +204 -0
  64. package/android/src/main/java/com/swmansion/enriched/utils/Utils.kt +91 -0
  65. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +73 -0
  66. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +51 -0
  67. package/android/src/main/new_arch/CMakeLists.txt +56 -0
  68. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.cpp +22 -0
  69. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.h +26 -0
  70. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputComponentDescriptor.h +35 -0
  71. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.cpp +51 -0
  72. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +26 -0
  73. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.cpp +34 -0
  74. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.h +54 -0
  75. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.cpp +9 -0
  76. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.h +25 -0
  77. package/ios/EnrichedTextInputView.h +33 -0
  78. package/ios/EnrichedTextInputView.mm +1190 -0
  79. package/ios/EnrichedTextInputViewManager.mm +13 -0
  80. package/ios/config/InputConfig.h +67 -0
  81. package/ios/config/InputConfig.mm +382 -0
  82. package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
  83. package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
  84. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
  85. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
  86. package/ios/generated/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
  87. package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +577 -0
  88. package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +384 -0
  89. package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
  90. package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
  91. package/ios/generated/RNEnrichedTextInputViewSpec/States.cpp +16 -0
  92. package/ios/generated/RNEnrichedTextInputViewSpec/States.h +20 -0
  93. package/ios/inputParser/InputParser.h +11 -0
  94. package/ios/inputParser/InputParser.mm +669 -0
  95. package/ios/inputTextView/InputTextView.h +6 -0
  96. package/ios/inputTextView/InputTextView.mm +115 -0
  97. package/ios/internals/EnrichedTextInputViewComponentDescriptor.h +17 -0
  98. package/ios/internals/EnrichedTextInputViewShadowNode.h +40 -0
  99. package/ios/internals/EnrichedTextInputViewShadowNode.mm +83 -0
  100. package/ios/internals/EnrichedTextInputViewState.cpp +10 -0
  101. package/ios/internals/EnrichedTextInputViewState.h +20 -0
  102. package/ios/styles/BlockQuoteStyle.mm +248 -0
  103. package/ios/styles/BoldStyle.mm +122 -0
  104. package/ios/styles/H1Style.mm +10 -0
  105. package/ios/styles/H2Style.mm +10 -0
  106. package/ios/styles/H3Style.mm +10 -0
  107. package/ios/styles/HeadingStyleBase.mm +144 -0
  108. package/ios/styles/InlineCodeStyle.mm +163 -0
  109. package/ios/styles/ItalicStyle.mm +110 -0
  110. package/ios/styles/LinkStyle.mm +463 -0
  111. package/ios/styles/MentionStyle.mm +476 -0
  112. package/ios/styles/OrderedListStyle.mm +225 -0
  113. package/ios/styles/StrikethroughStyle.mm +80 -0
  114. package/ios/styles/UnderlineStyle.mm +112 -0
  115. package/ios/styles/UnorderedListStyle.mm +225 -0
  116. package/ios/utils/BaseStyleProtocol.h +16 -0
  117. package/ios/utils/ColorExtension.h +6 -0
  118. package/ios/utils/ColorExtension.mm +27 -0
  119. package/ios/utils/FontExtension.h +13 -0
  120. package/ios/utils/FontExtension.mm +91 -0
  121. package/ios/utils/LayoutManagerExtension.h +6 -0
  122. package/ios/utils/LayoutManagerExtension.mm +171 -0
  123. package/ios/utils/LinkData.h +9 -0
  124. package/ios/utils/LinkData.mm +4 -0
  125. package/ios/utils/MentionParams.h +9 -0
  126. package/ios/utils/MentionParams.mm +4 -0
  127. package/ios/utils/MentionStyleProps.h +13 -0
  128. package/ios/utils/MentionStyleProps.mm +56 -0
  129. package/ios/utils/OccurenceUtils.h +37 -0
  130. package/ios/utils/OccurenceUtils.mm +124 -0
  131. package/ios/utils/ParagraphsUtils.h +7 -0
  132. package/ios/utils/ParagraphsUtils.mm +54 -0
  133. package/ios/utils/StringExtension.h +15 -0
  134. package/ios/utils/StringExtension.mm +57 -0
  135. package/ios/utils/StyleHeaders.h +74 -0
  136. package/ios/utils/StylePair.h +9 -0
  137. package/ios/utils/StylePair.mm +4 -0
  138. package/ios/utils/StyleTypeEnum.h +22 -0
  139. package/ios/utils/TextDecorationLineEnum.h +6 -0
  140. package/ios/utils/TextDecorationLineEnum.mm +4 -0
  141. package/ios/utils/TextInsertionUtils.h +6 -0
  142. package/ios/utils/TextInsertionUtils.mm +48 -0
  143. package/ios/utils/WordsUtils.h +6 -0
  144. package/ios/utils/WordsUtils.mm +88 -0
  145. package/ios/utils/ZeroWidthSpaceUtils.h +7 -0
  146. package/ios/utils/ZeroWidthSpaceUtils.mm +164 -0
  147. package/lib/module/EnrichedTextInput.js +191 -0
  148. package/lib/module/EnrichedTextInput.js.map +1 -0
  149. package/lib/module/EnrichedTextInputNativeComponent.ts +235 -0
  150. package/lib/module/index.js +4 -0
  151. package/lib/module/index.js.map +1 -0
  152. package/lib/module/normalizeHtmlStyle.js +141 -0
  153. package/lib/module/normalizeHtmlStyle.js.map +1 -0
  154. package/lib/module/package.json +1 -0
  155. package/lib/typescript/package.json +1 -0
  156. package/lib/typescript/src/EnrichedTextInput.d.ts +113 -0
  157. package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -0
  158. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +160 -0
  159. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -0
  160. package/lib/typescript/src/index.d.ts +3 -0
  161. package/lib/typescript/src/index.d.ts.map +1 -0
  162. package/lib/typescript/src/normalizeHtmlStyle.d.ts +4 -0
  163. package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +1 -0
  164. package/package.json +172 -1
  165. package/react-native.config.js +13 -0
  166. package/src/EnrichedTextInput.tsx +358 -0
  167. package/src/EnrichedTextInputNativeComponent.ts +235 -0
  168. package/src/index.tsx +9 -0
  169. package/src/normalizeHtmlStyle.ts +188 -0
@@ -0,0 +1,463 @@
1
+ #import "StyleHeaders.h"
2
+ #import "EnrichedTextInputView.h"
3
+ #import "OccurenceUtils.h"
4
+ #import "TextInsertionUtils.h"
5
+ #import "UIView+React.h"
6
+ #import "WordsUtils.h"
7
+
8
+ // custom NSAttributedStringKeys to differentiate manually added and automatically detected links
9
+ static NSString *const ManualLinkAttributeName = @"ManualLinkAttributeName";
10
+ static NSString *const AutomaticLinkAttributeName = @"AutomaticLinkAttributeName";
11
+
12
+ @implementation LinkStyle {
13
+ EnrichedTextInputView *_input;
14
+ }
15
+
16
+ + (StyleType)getStyleType { return Link; }
17
+
18
+ - (instancetype)initWithInput:(id)input {
19
+ self = [super init];
20
+ _input = (EnrichedTextInputView *)input;
21
+ return self;
22
+ }
23
+
24
+ - (void)applyStyle:(NSRange)range {
25
+ // no-op for links
26
+ }
27
+
28
+ - (void)addAttributes:(NSRange)range {
29
+ // no-op for links
30
+ }
31
+
32
+ - (void)addTypingAttributes {
33
+ // no-op for links
34
+ }
35
+
36
+ // we have to make sure all links in the range get fully removed here
37
+ - (void)removeAttributes:(NSRange)range {
38
+ NSArray<StylePair *> *links = [self findAllOccurences:range];
39
+ [_input->textView.textStorage beginEditing];
40
+ for(StylePair *pair in links) {
41
+ NSRange linkRange = [self getFullLinkRangeAt:[pair.rangeValue rangeValue].location];
42
+ [_input->textView.textStorage removeAttribute:ManualLinkAttributeName range:linkRange];
43
+ [_input->textView.textStorage removeAttribute:AutomaticLinkAttributeName range:linkRange];
44
+ [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:[_input->config primaryColor] range:linkRange];
45
+ [_input->textView.textStorage addAttribute:NSUnderlineColorAttributeName value:[_input->config primaryColor] range:linkRange];
46
+ [_input->textView.textStorage addAttribute:NSStrikethroughColorAttributeName value:[_input->config primaryColor] range:linkRange];
47
+ if([_input->config linkDecorationLine] == DecorationUnderline) {
48
+ [_input->textView.textStorage removeAttribute:NSUnderlineStyleAttributeName range:linkRange];
49
+ }
50
+ }
51
+ [_input->textView.textStorage endEditing];
52
+
53
+ // adjust typing attributes as well
54
+ NSMutableDictionary *newTypingAttrs = [_input->textView.typingAttributes mutableCopy];
55
+ newTypingAttrs[NSForegroundColorAttributeName] = [_input->config primaryColor];
56
+ newTypingAttrs[NSUnderlineColorAttributeName] = [_input->config primaryColor];
57
+ newTypingAttrs[NSStrikethroughColorAttributeName] = [_input->config primaryColor];
58
+ if([_input->config linkDecorationLine] == DecorationUnderline) {
59
+ [newTypingAttrs removeObjectForKey:NSUnderlineStyleAttributeName];
60
+ }
61
+ _input->textView.typingAttributes = newTypingAttrs;
62
+ }
63
+
64
+ // used for conflicts, we have to remove the whole link
65
+ - (void)removeTypingAttributes {
66
+ NSRange linkRange = [self getFullLinkRangeAt:_input->textView.selectedRange.location];
67
+ [_input->textView.textStorage beginEditing];
68
+ [_input->textView.textStorage removeAttribute:ManualLinkAttributeName range:linkRange];
69
+ [_input->textView.textStorage removeAttribute:AutomaticLinkAttributeName range:linkRange];
70
+ [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:[_input->config primaryColor] range:linkRange];
71
+ [_input->textView.textStorage addAttribute:NSUnderlineColorAttributeName value:[_input->config primaryColor] range:linkRange];
72
+ [_input->textView.textStorage addAttribute:NSStrikethroughColorAttributeName value:[_input->config primaryColor] range:linkRange];
73
+ if([_input->config linkDecorationLine] == DecorationUnderline) {
74
+ [_input->textView.textStorage removeAttribute:NSUnderlineStyleAttributeName range:linkRange];
75
+ }
76
+ [_input->textView.textStorage endEditing];
77
+
78
+ // adjust typing attributes as well
79
+ NSMutableDictionary *newTypingAttrs = [_input->textView.typingAttributes mutableCopy];
80
+ newTypingAttrs[NSForegroundColorAttributeName] = [_input->config primaryColor];
81
+ newTypingAttrs[NSUnderlineColorAttributeName] = [_input->config primaryColor];
82
+ newTypingAttrs[NSStrikethroughColorAttributeName] = [_input->config primaryColor];
83
+ if([_input->config linkDecorationLine] == DecorationUnderline) {
84
+ [newTypingAttrs removeObjectForKey:NSUnderlineStyleAttributeName];
85
+ }
86
+ _input->textView.typingAttributes = newTypingAttrs;
87
+ }
88
+
89
+ - (BOOL)styleCondition:(id _Nullable)value :(NSRange)range {
90
+ NSString *linkValue = (NSString *)value;
91
+ return linkValue != nullptr;
92
+ }
93
+
94
+ - (BOOL)detectStyle:(NSRange)range {
95
+ if(range.length >= 1) {
96
+ BOOL onlyLinks = [OccurenceUtils detectMultiple:@[ManualLinkAttributeName, AutomaticLinkAttributeName] withInput:_input inRange:range
97
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
98
+ return [self styleCondition:value :range];
99
+ }
100
+ ];
101
+ return onlyLinks ? [self isSingleLinkIn:range] : NO;
102
+ } else {
103
+ return [self getLinkDataAt:range.location] != nullptr;
104
+ }
105
+ }
106
+
107
+ - (BOOL)anyOccurence:(NSRange)range {
108
+ return [OccurenceUtils anyMultiple:@[ManualLinkAttributeName, AutomaticLinkAttributeName] withInput:_input inRange:range
109
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
110
+ return [self styleCondition:value :range];
111
+ }
112
+ ];
113
+ }
114
+
115
+ - (NSArray<StylePair *> *_Nullable)findAllOccurences:(NSRange)range {
116
+ return [OccurenceUtils allMultiple:@[ManualLinkAttributeName, AutomaticLinkAttributeName] withInput:_input inRange:range
117
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
118
+ return [self styleCondition:value :range];
119
+ }
120
+ ];
121
+ }
122
+
123
+ // MARK: - Public non-standard methods
124
+
125
+ - (void)addLink:(NSString*)text url:(NSString*)url range:(NSRange)range manual:(BOOL)manual {
126
+ NSString *currentText = [_input->textView.textStorage.string substringWithRange:range];
127
+
128
+ NSMutableDictionary<NSAttributedStringKey, id> *newAttrs = [[NSMutableDictionary<NSAttributedStringKey, id> alloc] init];
129
+ newAttrs[NSForegroundColorAttributeName] = [_input->config linkColor];
130
+ newAttrs[NSUnderlineColorAttributeName] = [_input->config linkColor];
131
+ newAttrs[NSStrikethroughColorAttributeName] = [_input->config linkColor];
132
+ if([_input->config linkDecorationLine] == DecorationUnderline) {
133
+ newAttrs[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle);
134
+ }
135
+ if(manual) {
136
+ newAttrs[ManualLinkAttributeName] = [url copy];
137
+ } else {
138
+ newAttrs[AutomaticLinkAttributeName] = [url copy];
139
+ }
140
+
141
+ if(range.length == 0) {
142
+ // insert link
143
+ [TextInsertionUtils insertText:text at:range.location additionalAttributes:newAttrs input:_input withSelection:YES];
144
+ } else if([currentText isEqualToString:text]) {
145
+ // apply link attributes
146
+ [_input->textView.textStorage addAttributes:newAttrs range:range];
147
+ // TextInsertionUtils take care of the selection but here we have to manually set it behind the link
148
+ // ONLY with manual links, automatic ones don't need the selection fix
149
+ if(manual) {
150
+ [_input->textView reactFocus];
151
+ _input->textView.selectedRange = NSMakeRange(range.location + text.length, 0);
152
+ }
153
+ } else {
154
+ // replace text with link
155
+ [TextInsertionUtils replaceText:text at:range additionalAttributes:newAttrs input:_input withSelection:YES];
156
+ }
157
+
158
+ // mandatory connected links check
159
+ NSDictionary *currentWord = [WordsUtils getCurrentWord:_input->textView.textStorage.string range:_input->textView.selectedRange];
160
+ if(currentWord != nullptr) {
161
+ // get word properties
162
+ NSString *wordText = (NSString *)[currentWord objectForKey:@"word"];
163
+ NSValue *wordRangeValue = (NSValue *)[currentWord objectForKey:@"range"];
164
+ if(wordText != nullptr && wordRangeValue != nullptr) {
165
+ [self removeConnectedLinksIfNeeded:wordText range:[wordRangeValue rangeValue]];
166
+ }
167
+ }
168
+
169
+ [self manageLinkTypingAttributes];
170
+ [_input anyTextMayHaveBeenModified];
171
+ }
172
+
173
+ // get exact link data at the given location if it exists
174
+ - (LinkData *)getLinkDataAt:(NSUInteger)location {
175
+ NSRange manualLinkRange = NSMakeRange(0, 0);
176
+ NSRange automaticLinkRange = NSMakeRange(0, 0);
177
+ NSRange inputRange = NSMakeRange(0, _input->textView.textStorage.length);
178
+
179
+ // don't search at the very end of input
180
+ NSUInteger searchLocation = location;
181
+ if(searchLocation == _input->textView.textStorage.length) {
182
+ return nullptr;
183
+ }
184
+
185
+ NSString *manualUrl = [_input->textView.textStorage
186
+ attribute:ManualLinkAttributeName
187
+ atIndex:searchLocation
188
+ longestEffectiveRange: &manualLinkRange
189
+ inRange:inputRange
190
+ ];
191
+ NSString *automaticUrl = [_input->textView.textStorage
192
+ attribute:AutomaticLinkAttributeName
193
+ atIndex:searchLocation
194
+ longestEffectiveRange: &automaticLinkRange
195
+ inRange:inputRange
196
+ ];
197
+
198
+ if((manualUrl == nullptr && automaticUrl == nullptr) || (manualLinkRange.length == 0 && automaticLinkRange.length == 0)) {
199
+ return nullptr;
200
+ }
201
+
202
+ NSString *linkUrl = manualUrl == nullptr ? automaticUrl : manualUrl;
203
+ NSRange linkRange = manualUrl == nullptr ? automaticLinkRange : manualLinkRange;
204
+
205
+ LinkData *data = [[LinkData alloc] init];
206
+ data.url = linkUrl;
207
+ data.text = [_input->textView.textStorage.string substringWithRange:linkRange];
208
+ return data;
209
+ }
210
+
211
+ // returns full range of a link at some location
212
+ - (NSRange)getFullLinkRangeAt:(NSUInteger)location {
213
+ NSRange manualLinkRange = NSMakeRange(0, 0);
214
+ NSRange automaticLinkRange = NSMakeRange(0, 0);
215
+ NSRange inputRange = NSMakeRange(0, _input->textView.textStorage.length);
216
+
217
+ // get the previous index if possible when at the very end of input
218
+ NSUInteger searchLocation = location;
219
+ if(searchLocation == _input->textView.textStorage.length) {
220
+ if(searchLocation == 0) {
221
+ return NSMakeRange(0, 0);
222
+ } else {
223
+ searchLocation = searchLocation - 1;
224
+ }
225
+ }
226
+
227
+ [_input->textView.textStorage
228
+ attribute:ManualLinkAttributeName
229
+ atIndex:searchLocation
230
+ longestEffectiveRange: &manualLinkRange
231
+ inRange:inputRange
232
+ ];
233
+ [_input->textView.textStorage
234
+ attribute:AutomaticLinkAttributeName
235
+ atIndex:searchLocation
236
+ longestEffectiveRange: &automaticLinkRange
237
+ inRange:inputRange
238
+ ];
239
+
240
+ return manualLinkRange.length == 0 ? automaticLinkRange : manualLinkRange;
241
+ }
242
+
243
+ - (void)manageLinkTypingAttributes {
244
+ // link's typing attribtues need to be removed at ALL times whenever we have some link around
245
+ BOOL removeAttrs = NO;
246
+
247
+ if(_input->textView.selectedRange.length == 0) {
248
+ // check before
249
+ if(_input->textView.selectedRange.location >= 1) {
250
+ if([self detectStyle:NSMakeRange(_input->textView.selectedRange.location - 1, 1)]) {
251
+ removeAttrs = YES;
252
+ }
253
+ }
254
+ // check after
255
+ if(_input->textView.selectedRange.location < _input->textView.textStorage.length) {
256
+ if([self detectStyle:NSMakeRange(_input->textView.selectedRange.location, 1)]) {
257
+ removeAttrs = YES;
258
+ }
259
+ }
260
+ } else {
261
+ if([self anyOccurence:_input->textView.selectedRange]) {
262
+ removeAttrs = YES;
263
+ }
264
+ }
265
+
266
+ if(removeAttrs) {
267
+ NSMutableDictionary *newTypingAttrs = [_input->textView.typingAttributes mutableCopy];
268
+ newTypingAttrs[NSForegroundColorAttributeName] = [_input->config primaryColor];
269
+ newTypingAttrs[NSUnderlineColorAttributeName] = [_input->config primaryColor];
270
+ newTypingAttrs[NSStrikethroughColorAttributeName] = [_input->config primaryColor];
271
+ if([_input->config linkDecorationLine] == DecorationUnderline) {
272
+ [newTypingAttrs removeObjectForKey:NSUnderlineStyleAttributeName];
273
+ }
274
+ _input->textView.typingAttributes = newTypingAttrs;
275
+ }
276
+ }
277
+
278
+ // handles detecting and removing automatic links
279
+ - (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange {
280
+ InlineCodeStyle *inlineCodeStyle = [_input->stylesDict objectForKey:@([InlineCodeStyle getStyleType])];
281
+ MentionStyle *mentionStyle = [_input->stylesDict objectForKey:@([MentionStyle getStyleType])];
282
+
283
+ if (inlineCodeStyle == nullptr || mentionStyle == nullptr) {
284
+ return;
285
+ }
286
+
287
+ // we don't recognize links along mentions
288
+ if ([mentionStyle anyOccurence:wordRange]) {
289
+ return;
290
+ }
291
+
292
+ // we don't recognize links among inline code
293
+ if ([inlineCodeStyle anyOccurence:wordRange]) {
294
+ return;
295
+ }
296
+
297
+ // remove connected different links
298
+ [self removeConnectedLinksIfNeeded:word range:wordRange];
299
+
300
+ // we don't recognize automatic links along manual ones
301
+ __block BOOL manualLinkPresent = NO;
302
+ [_input->textView.textStorage enumerateAttribute:ManualLinkAttributeName inRange:wordRange options:0
303
+ usingBlock:^(id value, NSRange range, BOOL *stop) {
304
+ NSString *urlValue = (NSString *)value;
305
+ if(urlValue != nullptr) {
306
+ manualLinkPresent = YES;
307
+ *stop = YES;
308
+ }
309
+ }];
310
+ if(manualLinkPresent) {
311
+ return;
312
+ }
313
+
314
+ NSRegularExpression *fullRegex = [NSRegularExpression regularExpressionWithPattern:@"http(s)?:\\/\\/www\\.[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)"
315
+ options:0
316
+ error:nullptr
317
+ ];
318
+ NSRegularExpression *wwwRegex = [NSRegularExpression regularExpressionWithPattern:@"www\\.[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)"
319
+ options:0
320
+ error:nullptr
321
+ ];
322
+ NSRegularExpression *bareRegex = [NSRegularExpression regularExpressionWithPattern:@"[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)"
323
+ options:0
324
+ error:nullptr
325
+ ];
326
+
327
+ NSString *regexPassedUrl = nullptr;
328
+
329
+ if ([fullRegex numberOfMatchesInString:word options:0 range:NSMakeRange(0, word.length)]) {
330
+ regexPassedUrl = word;
331
+ } else if ([wwwRegex numberOfMatchesInString:word options:0 range:NSMakeRange(0, word.length)]) {
332
+ regexPassedUrl = word;
333
+ } else if ([bareRegex numberOfMatchesInString:word options:0 range:NSMakeRange(0, word.length)]) {
334
+ regexPassedUrl = word;
335
+ } else if ([self anyOccurence:wordRange]) {
336
+ // there was some automatic link (because anyOccurence is true and we are sure there are no manual links)
337
+ // still, it didn't pass any regex - needs to be removed
338
+ [self removeAttributes:wordRange];
339
+ }
340
+
341
+ if(regexPassedUrl != nullptr) {
342
+ // add style only if needed
343
+ BOOL addStyle = YES;
344
+ if([self detectStyle:wordRange]) {
345
+ LinkData *currentData = [self getLinkDataAt:wordRange.location];
346
+ if(currentData != nullptr && currentData.url != nullptr && [currentData.url isEqualToString:regexPassedUrl]) {
347
+ addStyle = NO;
348
+ }
349
+ }
350
+ if(addStyle) {
351
+ [self addLink:word url:regexPassedUrl range:wordRange manual:NO];
352
+ }
353
+
354
+ // emit onLinkDetected
355
+ [_input emitOnLinkDetectedEvent:word url:regexPassedUrl range:wordRange];
356
+ }
357
+ }
358
+
359
+ // handles refreshing manual links
360
+ - (void)handleManualLinks:(NSString *)word inRange:(NSRange)wordRange {
361
+ // look for manual links within the word
362
+ __block NSString *manualLinkMinValue = @"";
363
+ __block NSString *manualLinkMaxValue = @"";
364
+ __block NSInteger manualLinkMinIdx = -1;
365
+ __block NSInteger manualLinkMaxIdx = -1;
366
+
367
+ [_input->textView.textStorage enumerateAttribute:ManualLinkAttributeName inRange:wordRange options:0
368
+ usingBlock:^(id value, NSRange range, BOOL *stop) {
369
+ NSString *urlValue = (NSString *)value;
370
+ if (urlValue != nullptr) {
371
+ NSInteger linkMin = range.location;
372
+ NSInteger linkMax = range.location + range.length - 1;
373
+ if (manualLinkMinIdx == -1 || linkMin < manualLinkMinIdx) {
374
+ manualLinkMinIdx = linkMin;
375
+ manualLinkMinValue = value;
376
+ }
377
+ if (manualLinkMaxIdx == -1 || linkMax > manualLinkMaxIdx) {
378
+ manualLinkMaxIdx = linkMax;
379
+ manualLinkMaxValue = value;
380
+ }
381
+ }
382
+ }];
383
+
384
+ // no manual links
385
+ if(manualLinkMinIdx == -1 || manualLinkMaxIdx == -1) {
386
+ return;
387
+ }
388
+
389
+ // heuristic for refreshing manual links:
390
+ // we update the Manual attribute between the bounds of existing ones
391
+ // we do that only if the bounds point to the same url
392
+ // this way manual link gets "extended" only if some characters were added inside it
393
+ if([manualLinkMinValue isEqualToString:manualLinkMaxValue]) {
394
+ NSRange newRange = NSMakeRange(manualLinkMinIdx, manualLinkMaxIdx - manualLinkMinIdx + 1);
395
+ [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:[_input->config linkColor] range:newRange];
396
+ [_input->textView.textStorage addAttribute:NSUnderlineColorAttributeName value:[_input->config linkColor] range:newRange];
397
+ [_input->textView.textStorage addAttribute:NSStrikethroughColorAttributeName value:[_input->config linkColor] range:newRange];
398
+ if([_input->config linkDecorationLine] == DecorationUnderline) {
399
+ [_input->textView.textStorage addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:newRange];
400
+ }
401
+ [_input->textView.textStorage addAttribute:ManualLinkAttributeName value:manualLinkMinValue range:newRange];
402
+ }
403
+
404
+ // link typing attributes need to be fixed after these changes
405
+ [self manageLinkTypingAttributes];
406
+ }
407
+
408
+ // replacing whole input (that starts with a link) with a manually typed letter improperly applies link's attributes to all the following text
409
+ - (BOOL)handleLeadingLinkReplacement:(NSRange)range replacementText:(NSString *)text {
410
+ // whole textView range gets replaced with a single letter
411
+ if(_input->textView.textStorage.string.length > 0 && NSEqualRanges(range, NSMakeRange(0, _input->textView.textStorage.string.length)) && text.length == 1) {
412
+ // first character detection is enough for the removal to be done
413
+ if([self detectStyle:NSMakeRange(0, 1)]) {
414
+ [self removeAttributes:NSMakeRange(0, _input->textView.textStorage.string.length)];
415
+ // do the replacing manually
416
+ [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:_input withSelection:YES];
417
+ return YES;
418
+ }
419
+ }
420
+ return NO;
421
+ }
422
+
423
+ // MARK: - Private non-standard methods
424
+
425
+ // determines whether a given range contains only links pointing to one url
426
+ // assumes the whole range is links only already
427
+ - (BOOL)isSingleLinkIn:(NSRange)range {
428
+ return [self findAllOccurences:range].count == 1;
429
+ }
430
+
431
+ - (void)removeConnectedLinksIfNeeded:(NSString *)word range:(NSRange)wordRange {
432
+ BOOL anyAutomatic = [OccurenceUtils any:AutomaticLinkAttributeName withInput:_input inRange:wordRange
433
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
434
+ return [self styleCondition:value :range];
435
+ }
436
+ ];
437
+ BOOL anyManual = [OccurenceUtils any:ManualLinkAttributeName withInput:_input inRange:wordRange
438
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
439
+ return [self styleCondition:value :range];
440
+ }
441
+ ];
442
+
443
+ // both manual and automatic links are somewhere - delete!
444
+ if(anyAutomatic && anyManual) {
445
+ [self removeAttributes:wordRange];
446
+ [self manageLinkTypingAttributes];
447
+ }
448
+
449
+ // we are now sure there is only one type of link there - and make sure it covers the whole word
450
+ BOOL onlyLinks = [OccurenceUtils detectMultiple:@[ManualLinkAttributeName, AutomaticLinkAttributeName] withInput:_input inRange:wordRange
451
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
452
+ return [self styleCondition:value :range];
453
+ }
454
+ ];
455
+
456
+ // only one link might be present!
457
+ if(onlyLinks && ![self isSingleLinkIn:wordRange]) {
458
+ [self removeAttributes:wordRange];
459
+ [self manageLinkTypingAttributes];
460
+ }
461
+ }
462
+
463
+ @end