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,1190 @@
|
|
|
1
|
+
#import "EnrichedTextInputView.h"
|
|
2
|
+
#import "RCTFabricComponentsPlugins.h"
|
|
3
|
+
#import <ReactNativeEnriched/EnrichedTextInputViewComponentDescriptor.h>
|
|
4
|
+
#import <ReactNativeEnriched/EventEmitters.h>
|
|
5
|
+
#import <ReactNativeEnriched/Props.h>
|
|
6
|
+
#import <ReactNativeEnriched/RCTComponentViewHelpers.h>
|
|
7
|
+
#import <react/utils/ManagedObjectWrapper.h>
|
|
8
|
+
#import <folly/dynamic.h>
|
|
9
|
+
#import "UIView+React.h"
|
|
10
|
+
#import "StringExtension.h"
|
|
11
|
+
#import "CoreText/CoreText.h"
|
|
12
|
+
#import <React/RCTConversions.h>
|
|
13
|
+
#import "StyleHeaders.h"
|
|
14
|
+
#import "WordsUtils.h"
|
|
15
|
+
#import "LayoutManagerExtension.h"
|
|
16
|
+
#import "ZeroWidthSpaceUtils.h"
|
|
17
|
+
|
|
18
|
+
using namespace facebook::react;
|
|
19
|
+
|
|
20
|
+
@interface EnrichedTextInputView () <RCTEnrichedTextInputViewViewProtocol, UITextViewDelegate, NSObject>
|
|
21
|
+
|
|
22
|
+
@end
|
|
23
|
+
|
|
24
|
+
@implementation EnrichedTextInputView {
|
|
25
|
+
EnrichedTextInputViewShadowNode::ConcreteState::Shared _state;
|
|
26
|
+
int _componentViewHeightUpdateCounter;
|
|
27
|
+
NSMutableSet<NSNumber *> *_activeStyles;
|
|
28
|
+
NSDictionary<NSNumber *, NSArray<NSNumber *> *> *_conflictingStyles;
|
|
29
|
+
NSDictionary<NSNumber *, NSArray<NSNumber *> *> *_blockingStyles;
|
|
30
|
+
LinkData *_recentlyActiveLinkData;
|
|
31
|
+
NSRange _recentlyActiveLinkRange;
|
|
32
|
+
NSString *_recentlyEmittedString;
|
|
33
|
+
MentionParams *_recentlyActiveMentionParams;
|
|
34
|
+
NSRange _recentlyActiveMentionRange;
|
|
35
|
+
NSString *_recentlyEmittedHtml;
|
|
36
|
+
UILabel *_placeholderLabel;
|
|
37
|
+
UIColor *_placeholderColor;
|
|
38
|
+
BOOL _emitFocusBlur;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// MARK: - Component utils
|
|
42
|
+
|
|
43
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
44
|
+
return concreteComponentDescriptorProvider<EnrichedTextInputViewComponentDescriptor>();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
|
|
48
|
+
return EnrichedTextInputView.class;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
+ (BOOL)shouldBeRecycled {
|
|
52
|
+
return NO;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// MARK: - Init
|
|
56
|
+
|
|
57
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
58
|
+
if (self = [super initWithFrame:frame]) {
|
|
59
|
+
static const auto defaultProps = std::make_shared<const EnrichedTextInputViewProps>();
|
|
60
|
+
_props = defaultProps;
|
|
61
|
+
[self setDefaults];
|
|
62
|
+
[self setupTextView];
|
|
63
|
+
[self setupPlaceholderLabel];
|
|
64
|
+
self.contentView = textView;
|
|
65
|
+
}
|
|
66
|
+
return self;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
- (void)setDefaults {
|
|
70
|
+
_componentViewHeightUpdateCounter = 0;
|
|
71
|
+
_activeStyles = [[NSMutableSet alloc] init];
|
|
72
|
+
_recentlyActiveLinkRange = NSMakeRange(0, 0);
|
|
73
|
+
_recentlyActiveMentionRange = NSMakeRange(0, 0);
|
|
74
|
+
recentlyChangedRange = NSMakeRange(0, 0);
|
|
75
|
+
_recentlyEmittedString = @"";
|
|
76
|
+
_recentlyEmittedHtml = @"";
|
|
77
|
+
emitHtml = NO;
|
|
78
|
+
blockEmitting = NO;
|
|
79
|
+
_emitFocusBlur = YES;
|
|
80
|
+
|
|
81
|
+
defaultTypingAttributes = [[NSMutableDictionary<NSAttributedStringKey, id> alloc] init];
|
|
82
|
+
|
|
83
|
+
stylesDict = @{
|
|
84
|
+
@([BoldStyle getStyleType]) : [[BoldStyle alloc] initWithInput:self],
|
|
85
|
+
@([ItalicStyle getStyleType]): [[ItalicStyle alloc] initWithInput:self],
|
|
86
|
+
@([UnderlineStyle getStyleType]): [[UnderlineStyle alloc] initWithInput:self],
|
|
87
|
+
@([StrikethroughStyle getStyleType]): [[StrikethroughStyle alloc] initWithInput:self],
|
|
88
|
+
@([InlineCodeStyle getStyleType]): [[InlineCodeStyle alloc] initWithInput:self],
|
|
89
|
+
@([LinkStyle getStyleType]): [[LinkStyle alloc] initWithInput:self],
|
|
90
|
+
@([MentionStyle getStyleType]): [[MentionStyle alloc] initWithInput:self],
|
|
91
|
+
@([H1Style getStyleType]): [[H1Style alloc] initWithInput:self],
|
|
92
|
+
@([H2Style getStyleType]): [[H2Style alloc] initWithInput:self],
|
|
93
|
+
@([H3Style getStyleType]): [[H3Style alloc] initWithInput:self],
|
|
94
|
+
@([UnorderedListStyle getStyleType]): [[UnorderedListStyle alloc] initWithInput:self],
|
|
95
|
+
@([OrderedListStyle getStyleType]): [[OrderedListStyle alloc] initWithInput:self],
|
|
96
|
+
@([BlockQuoteStyle getStyleType]): [[BlockQuoteStyle alloc] initWithInput:self]
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
_conflictingStyles = @{
|
|
100
|
+
@([BoldStyle getStyleType]) : @[],
|
|
101
|
+
@([ItalicStyle getStyleType]) : @[],
|
|
102
|
+
@([UnderlineStyle getStyleType]) : @[],
|
|
103
|
+
@([StrikethroughStyle getStyleType]) : @[],
|
|
104
|
+
@([InlineCodeStyle getStyleType]) : @[@([LinkStyle getStyleType]), @([MentionStyle getStyleType])],
|
|
105
|
+
@([LinkStyle getStyleType]): @[@([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType]), @([MentionStyle getStyleType])],
|
|
106
|
+
@([MentionStyle getStyleType]): @[@([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType])],
|
|
107
|
+
@([H1Style getStyleType]): @[@([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])],
|
|
108
|
+
@([H2Style getStyleType]): @[@([H1Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])],
|
|
109
|
+
@([H3Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])],
|
|
110
|
+
@([UnorderedListStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])],
|
|
111
|
+
@([OrderedListStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])],
|
|
112
|
+
@([BlockQuoteStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType])]
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
_blockingStyles = @{
|
|
116
|
+
@([BoldStyle getStyleType]) : @[],
|
|
117
|
+
@([ItalicStyle getStyleType]) : @[],
|
|
118
|
+
@([UnderlineStyle getStyleType]) : @[],
|
|
119
|
+
@([StrikethroughStyle getStyleType]) : @[],
|
|
120
|
+
@([InlineCodeStyle getStyleType]) : @[],
|
|
121
|
+
@([LinkStyle getStyleType]): @[],
|
|
122
|
+
@([MentionStyle getStyleType]): @[],
|
|
123
|
+
@([H1Style getStyleType]): @[],
|
|
124
|
+
@([H2Style getStyleType]): @[],
|
|
125
|
+
@([H3Style getStyleType]): @[],
|
|
126
|
+
@([UnorderedListStyle getStyleType]): @[],
|
|
127
|
+
@([OrderedListStyle getStyleType]): @[],
|
|
128
|
+
@([BlockQuoteStyle getStyleType]): @[],
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
parser = [[InputParser alloc] initWithInput:self];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
- (void)setupTextView {
|
|
135
|
+
textView = [[InputTextView alloc] init];
|
|
136
|
+
textView.backgroundColor = UIColor.clearColor;
|
|
137
|
+
textView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0);
|
|
138
|
+
textView.textContainer.lineFragmentPadding = 0;
|
|
139
|
+
textView.delegate = self;
|
|
140
|
+
textView.input = self;
|
|
141
|
+
textView.layoutManager.input = self;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
- (void)setupPlaceholderLabel {
|
|
145
|
+
_placeholderLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
|
146
|
+
_placeholderLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
|
147
|
+
[textView addSubview:_placeholderLabel];
|
|
148
|
+
[NSLayoutConstraint activateConstraints: @[
|
|
149
|
+
[_placeholderLabel.leadingAnchor constraintEqualToAnchor:textView.leadingAnchor],
|
|
150
|
+
[_placeholderLabel.widthAnchor constraintEqualToAnchor:textView.widthAnchor],
|
|
151
|
+
[_placeholderLabel.topAnchor constraintEqualToAnchor:textView.topAnchor],
|
|
152
|
+
[_placeholderLabel.bottomAnchor constraintEqualToAnchor:textView.bottomAnchor]
|
|
153
|
+
]];
|
|
154
|
+
_placeholderLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
|
155
|
+
_placeholderLabel.text = @"";
|
|
156
|
+
_placeholderLabel.hidden = YES;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// MARK: - Props
|
|
160
|
+
|
|
161
|
+
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps {
|
|
162
|
+
const auto &oldViewProps = *std::static_pointer_cast<EnrichedTextInputViewProps const>(_props);
|
|
163
|
+
const auto &newViewProps = *std::static_pointer_cast<EnrichedTextInputViewProps const>(props);
|
|
164
|
+
BOOL isFirstMount = NO;
|
|
165
|
+
BOOL stylePropChanged = NO;
|
|
166
|
+
|
|
167
|
+
// initial config
|
|
168
|
+
if(config == nullptr) {
|
|
169
|
+
isFirstMount = YES;
|
|
170
|
+
config = [[InputConfig alloc] init];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// any style prop changes:
|
|
174
|
+
// firstly we create the new config for the changes
|
|
175
|
+
|
|
176
|
+
InputConfig *newConfig = [config copy];
|
|
177
|
+
|
|
178
|
+
if(newViewProps.color != oldViewProps.color) {
|
|
179
|
+
if(isColorMeaningful(newViewProps.color)) {
|
|
180
|
+
UIColor *uiColor = RCTUIColorFromSharedColor(newViewProps.color);
|
|
181
|
+
[newConfig setPrimaryColor:uiColor];
|
|
182
|
+
} else {
|
|
183
|
+
[newConfig setPrimaryColor:nullptr];
|
|
184
|
+
}
|
|
185
|
+
stylePropChanged = YES;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if(newViewProps.fontSize != oldViewProps.fontSize) {
|
|
189
|
+
if(newViewProps.fontSize) {
|
|
190
|
+
NSNumber* fontSize = @(newViewProps.fontSize);
|
|
191
|
+
[newConfig setPrimaryFontSize:fontSize];
|
|
192
|
+
} else {
|
|
193
|
+
[newConfig setPrimaryFontSize:nullptr];
|
|
194
|
+
}
|
|
195
|
+
stylePropChanged = YES;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if(newViewProps.fontWeight != oldViewProps.fontWeight) {
|
|
199
|
+
if(!newViewProps.fontWeight.empty()) {
|
|
200
|
+
[newConfig setPrimaryFontWeight:[NSString fromCppString:newViewProps.fontWeight]];
|
|
201
|
+
} else {
|
|
202
|
+
[newConfig setPrimaryFontWeight:nullptr];
|
|
203
|
+
}
|
|
204
|
+
stylePropChanged = YES;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if(newViewProps.fontFamily != oldViewProps.fontFamily) {
|
|
208
|
+
if(!newViewProps.fontFamily.empty()) {
|
|
209
|
+
[newConfig setPrimaryFontFamily:[NSString fromCppString:newViewProps.fontFamily]];
|
|
210
|
+
} else {
|
|
211
|
+
[newConfig setPrimaryFontFamily:nullptr];
|
|
212
|
+
}
|
|
213
|
+
stylePropChanged = YES;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// rich text style
|
|
217
|
+
|
|
218
|
+
if(newViewProps.htmlStyle.h1.fontSize != oldViewProps.htmlStyle.h1.fontSize) {
|
|
219
|
+
[newConfig setH1FontSize:newViewProps.htmlStyle.h1.fontSize];
|
|
220
|
+
stylePropChanged = YES;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if(newViewProps.htmlStyle.h1.bold != oldViewProps.htmlStyle.h1.bold) {
|
|
224
|
+
[newConfig setH1Bold:newViewProps.htmlStyle.h1.bold];
|
|
225
|
+
stylePropChanged = YES;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if(newViewProps.htmlStyle.h2.fontSize != oldViewProps.htmlStyle.h2.fontSize) {
|
|
229
|
+
[newConfig setH2FontSize:newViewProps.htmlStyle.h2.fontSize];
|
|
230
|
+
stylePropChanged = YES;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if(newViewProps.htmlStyle.h2.bold != oldViewProps.htmlStyle.h2.bold) {
|
|
234
|
+
[newConfig setH2Bold:newViewProps.htmlStyle.h2.bold];
|
|
235
|
+
stylePropChanged = YES;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if(newViewProps.htmlStyle.h3.fontSize != oldViewProps.htmlStyle.h3.fontSize) {
|
|
239
|
+
[newConfig setH3FontSize:newViewProps.htmlStyle.h3.fontSize];
|
|
240
|
+
stylePropChanged = YES;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if(newViewProps.htmlStyle.h3.bold != oldViewProps.htmlStyle.h3.bold) {
|
|
244
|
+
[newConfig setH3Bold:newViewProps.htmlStyle.h3.bold];
|
|
245
|
+
stylePropChanged = YES;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if(newViewProps.htmlStyle.blockquote.borderColor != oldViewProps.htmlStyle.blockquote.borderColor) {
|
|
249
|
+
if(isColorMeaningful(newViewProps.htmlStyle.blockquote.borderColor)) {
|
|
250
|
+
[newConfig setBlockquoteBorderColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.blockquote.borderColor)];
|
|
251
|
+
stylePropChanged = YES;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if(newViewProps.htmlStyle.blockquote.borderWidth != oldViewProps.htmlStyle.blockquote.borderWidth) {
|
|
256
|
+
[newConfig setBlockquoteBorderWidth:newViewProps.htmlStyle.blockquote.borderWidth];
|
|
257
|
+
stylePropChanged = YES;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if(newViewProps.htmlStyle.blockquote.gapWidth != oldViewProps.htmlStyle.blockquote.gapWidth) {
|
|
261
|
+
[newConfig setBlockquoteGapWidth:newViewProps.htmlStyle.blockquote.gapWidth];
|
|
262
|
+
stylePropChanged = YES;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// since this prop defaults to undefined on JS side, we need to force set the value on first mount
|
|
266
|
+
if(newViewProps.htmlStyle.blockquote.color != oldViewProps.htmlStyle.blockquote.color || isFirstMount) {
|
|
267
|
+
if(isColorMeaningful(newViewProps.htmlStyle.blockquote.color)) {
|
|
268
|
+
[newConfig setBlockquoteColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.blockquote.color)];
|
|
269
|
+
} else {
|
|
270
|
+
[newConfig setBlockquoteColor:[newConfig primaryColor]];
|
|
271
|
+
}
|
|
272
|
+
stylePropChanged = YES;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if(newViewProps.htmlStyle.code.color != oldViewProps.htmlStyle.code.color) {
|
|
276
|
+
if(isColorMeaningful(newViewProps.htmlStyle.code.color)) {
|
|
277
|
+
[newConfig setInlineCodeFgColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.code.color)];
|
|
278
|
+
stylePropChanged = YES;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if(newViewProps.htmlStyle.code.backgroundColor != oldViewProps.htmlStyle.code.backgroundColor) {
|
|
283
|
+
if(isColorMeaningful(newViewProps.htmlStyle.code.backgroundColor)) {
|
|
284
|
+
[newConfig setInlineCodeBgColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.code.backgroundColor)];
|
|
285
|
+
stylePropChanged = YES;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if(newViewProps.htmlStyle.ol.gapWidth != oldViewProps.htmlStyle.ol.gapWidth) {
|
|
290
|
+
[newConfig setOrderedListGapWidth:newViewProps.htmlStyle.ol.gapWidth];
|
|
291
|
+
stylePropChanged = YES;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if(newViewProps.htmlStyle.ol.marginLeft != oldViewProps.htmlStyle.ol.marginLeft) {
|
|
295
|
+
[newConfig setOrderedListMarginLeft:newViewProps.htmlStyle.ol.marginLeft];
|
|
296
|
+
stylePropChanged = YES;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// since this prop defaults to undefined on JS side, we need to force set the value on first mount
|
|
300
|
+
if(newViewProps.htmlStyle.ol.markerFontWeight != oldViewProps.htmlStyle.ol.markerFontWeight || isFirstMount) {
|
|
301
|
+
if(!newViewProps.htmlStyle.ol.markerFontWeight.empty()) {
|
|
302
|
+
[newConfig setOrderedListMarkerFontWeight:[NSString fromCppString: newViewProps.htmlStyle.ol.markerFontWeight]];
|
|
303
|
+
} else {
|
|
304
|
+
[newConfig setOrderedListMarkerFontWeight:[newConfig primaryFontWeight]];
|
|
305
|
+
}
|
|
306
|
+
stylePropChanged = YES;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// since this prop defaults to undefined on JS side, we need to force set the value on first mount
|
|
310
|
+
if(newViewProps.htmlStyle.ol.markerColor != oldViewProps.htmlStyle.ol.markerColor || isFirstMount) {
|
|
311
|
+
if(isColorMeaningful(newViewProps.htmlStyle.ol.markerColor)) {
|
|
312
|
+
[newConfig setOrderedListMarkerColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.ol.markerColor)];
|
|
313
|
+
} else {
|
|
314
|
+
[newConfig setOrderedListMarkerColor:[newConfig primaryColor]];
|
|
315
|
+
}
|
|
316
|
+
stylePropChanged = YES;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if(newViewProps.htmlStyle.ul.bulletColor != oldViewProps.htmlStyle.ul.bulletColor) {
|
|
320
|
+
if(isColorMeaningful(newViewProps.htmlStyle.ul.bulletColor)) {
|
|
321
|
+
[newConfig setUnorderedListBulletColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.ul.bulletColor)];
|
|
322
|
+
stylePropChanged = YES;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if(newViewProps.htmlStyle.ul.bulletSize != oldViewProps.htmlStyle.ul.bulletSize) {
|
|
327
|
+
[newConfig setUnorderedListBulletSize:newViewProps.htmlStyle.ul.bulletSize];
|
|
328
|
+
stylePropChanged = YES;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if(newViewProps.htmlStyle.ul.gapWidth != oldViewProps.htmlStyle.ul.gapWidth) {
|
|
332
|
+
[newConfig setUnorderedListGapWidth:newViewProps.htmlStyle.ul.gapWidth];
|
|
333
|
+
stylePropChanged = YES;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if(newViewProps.htmlStyle.ul.marginLeft != oldViewProps.htmlStyle.ul.marginLeft) {
|
|
337
|
+
[newConfig setUnorderedListMarginLeft:newViewProps.htmlStyle.ul.marginLeft];
|
|
338
|
+
stylePropChanged = YES;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if(newViewProps.htmlStyle.a.color != oldViewProps.htmlStyle.a.color) {
|
|
342
|
+
if(isColorMeaningful(newViewProps.htmlStyle.a.color)) {
|
|
343
|
+
[newConfig setLinkColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.a.color)];
|
|
344
|
+
stylePropChanged = YES;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if(newViewProps.htmlStyle.a.textDecorationLine != oldViewProps.htmlStyle.a.textDecorationLine) {
|
|
349
|
+
NSString *objcString = [NSString fromCppString:newViewProps.htmlStyle.a.textDecorationLine];
|
|
350
|
+
if([objcString isEqualToString:DecorationUnderline]) {
|
|
351
|
+
[newConfig setLinkDecorationLine:DecorationUnderline];
|
|
352
|
+
} else {
|
|
353
|
+
// both DecorationNone and a different, wrong value gets a DecorationNone here
|
|
354
|
+
[newConfig setLinkDecorationLine:DecorationNone];
|
|
355
|
+
}
|
|
356
|
+
stylePropChanged = YES;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
folly::dynamic oldMentionStyle = oldViewProps.htmlStyle.mention;
|
|
360
|
+
folly::dynamic newMentionStyle = newViewProps.htmlStyle.mention;
|
|
361
|
+
if(oldMentionStyle != newMentionStyle) {
|
|
362
|
+
bool newSingleProps = NO;
|
|
363
|
+
|
|
364
|
+
for(const auto& obj : newMentionStyle.items()) {
|
|
365
|
+
if(obj.second.isInt() || obj.second.isString()) {
|
|
366
|
+
// we are in just a single MentionStyleProps object
|
|
367
|
+
newSingleProps = YES;
|
|
368
|
+
break;
|
|
369
|
+
} else if(obj.second.isObject()) {
|
|
370
|
+
// we are in map of indicators to MentionStyleProps
|
|
371
|
+
newSingleProps = NO;
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if(newSingleProps) {
|
|
377
|
+
[newConfig setMentionStyleProps:[MentionStyleProps getSinglePropsFromFollyDynamic:newMentionStyle]];
|
|
378
|
+
} else {
|
|
379
|
+
[newConfig setMentionStyleProps:[MentionStyleProps getComplexPropsFromFollyDynamic:newMentionStyle]];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
stylePropChanged = YES;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if(stylePropChanged) {
|
|
386
|
+
// all the text needs to be rebuilt
|
|
387
|
+
// we get the current html using old config, then switch to new config and replace text using the html
|
|
388
|
+
// this way, the newest config attributes are being used!
|
|
389
|
+
|
|
390
|
+
// the html needs to be generated using the old config
|
|
391
|
+
NSString *currentHtml = [parser parseToHtmlFromRange:NSMakeRange(0, textView.textStorage.string.length)];
|
|
392
|
+
|
|
393
|
+
// now set the new config
|
|
394
|
+
config = newConfig;
|
|
395
|
+
|
|
396
|
+
// we don't want to emit these html changes in here
|
|
397
|
+
BOOL prevEmitHtml = emitHtml;
|
|
398
|
+
if(prevEmitHtml) {
|
|
399
|
+
emitHtml = NO;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// make sure everything is sound in the html
|
|
403
|
+
NSString *initiallyProcessedHtml = [parser initiallyProcessHtml:currentHtml];
|
|
404
|
+
if(initiallyProcessedHtml != nullptr) {
|
|
405
|
+
[parser replaceWholeFromHtml:initiallyProcessedHtml];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if(prevEmitHtml) {
|
|
409
|
+
emitHtml = YES;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// fill the typing attributes with style props
|
|
413
|
+
defaultTypingAttributes[NSForegroundColorAttributeName] = [config primaryColor];
|
|
414
|
+
defaultTypingAttributes[NSFontAttributeName] = [config primaryFont];
|
|
415
|
+
defaultTypingAttributes[NSUnderlineColorAttributeName] = [config primaryColor];
|
|
416
|
+
defaultTypingAttributes[NSStrikethroughColorAttributeName] = [config primaryColor];
|
|
417
|
+
defaultTypingAttributes[NSParagraphStyleAttributeName] = [[NSParagraphStyle alloc] init];
|
|
418
|
+
textView.typingAttributes = defaultTypingAttributes;
|
|
419
|
+
|
|
420
|
+
// update the placeholder as well
|
|
421
|
+
[self refreshPlaceholderLabelStyles];
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// editable
|
|
425
|
+
if(newViewProps.editable != oldViewProps.editable) {
|
|
426
|
+
textView.editable = newViewProps.editable;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// default value - must be set before placeholder to make sure it correctly shows on first mount
|
|
430
|
+
if(newViewProps.defaultValue != oldViewProps.defaultValue) {
|
|
431
|
+
NSString *newDefaultValue = [NSString fromCppString:newViewProps.defaultValue];
|
|
432
|
+
|
|
433
|
+
NSString *initiallyProcessedHtml = [parser initiallyProcessHtml:newDefaultValue];
|
|
434
|
+
if(initiallyProcessedHtml == nullptr) {
|
|
435
|
+
// just plain text
|
|
436
|
+
textView.text = newDefaultValue;
|
|
437
|
+
} else {
|
|
438
|
+
// we've got some seemingly proper html
|
|
439
|
+
[parser replaceWholeFromHtml:initiallyProcessedHtml];
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// placeholderTextColor
|
|
444
|
+
if(newViewProps.placeholderTextColor != oldViewProps.placeholderTextColor) {
|
|
445
|
+
// some real color
|
|
446
|
+
if(isColorMeaningful(newViewProps.placeholderTextColor)) {
|
|
447
|
+
_placeholderColor = RCTUIColorFromSharedColor(newViewProps.placeholderTextColor);
|
|
448
|
+
} else {
|
|
449
|
+
_placeholderColor = nullptr;
|
|
450
|
+
}
|
|
451
|
+
[self refreshPlaceholderLabelStyles];
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// placeholder
|
|
455
|
+
if(newViewProps.placeholder != oldViewProps.placeholder) {
|
|
456
|
+
_placeholderLabel.text = [NSString fromCppString:newViewProps.placeholder];
|
|
457
|
+
[self refreshPlaceholderLabelStyles];
|
|
458
|
+
// additionally show placeholder on first mount if it should be there
|
|
459
|
+
if(isFirstMount && textView.text.length == 0) {
|
|
460
|
+
[self setPlaceholderLabelShown:YES];
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// mention indicators
|
|
465
|
+
auto mismatchPair = std::mismatch(
|
|
466
|
+
newViewProps.mentionIndicators.begin(), newViewProps.mentionIndicators.end(),
|
|
467
|
+
oldViewProps.mentionIndicators.begin(), oldViewProps.mentionIndicators.end()
|
|
468
|
+
);
|
|
469
|
+
if(mismatchPair.first != newViewProps.mentionIndicators.end() || mismatchPair.second != oldViewProps.mentionIndicators.end()) {
|
|
470
|
+
NSMutableSet<NSNumber *> *newIndicators = [[NSMutableSet alloc] init];
|
|
471
|
+
for(const std::string &item : newViewProps.mentionIndicators) {
|
|
472
|
+
if(item.length() == 1) {
|
|
473
|
+
[newIndicators addObject:@(item[0])];
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
[config setMentionIndicators:newIndicators];
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// selection color sets both selection and cursor on iOS (just as in RN)
|
|
480
|
+
if(newViewProps.selectionColor != oldViewProps.selectionColor) {
|
|
481
|
+
if(isColorMeaningful(newViewProps.selectionColor)) {
|
|
482
|
+
textView.tintColor = RCTUIColorFromSharedColor(newViewProps.selectionColor);
|
|
483
|
+
} else {
|
|
484
|
+
textView.tintColor = nullptr;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// autoCapitalize
|
|
489
|
+
if(newViewProps.autoCapitalize != oldViewProps.autoCapitalize) {
|
|
490
|
+
NSString *str = [NSString fromCppString:newViewProps.autoCapitalize];
|
|
491
|
+
if([str isEqualToString: @"none"]) {
|
|
492
|
+
textView.autocapitalizationType = UITextAutocapitalizationTypeNone;
|
|
493
|
+
} else if([str isEqualToString: @"sentences"]) {
|
|
494
|
+
textView.autocapitalizationType = UITextAutocapitalizationTypeSentences;
|
|
495
|
+
} else if([str isEqualToString: @"words"]) {
|
|
496
|
+
textView.autocapitalizationType = UITextAutocapitalizationTypeWords;
|
|
497
|
+
} else if([str isEqualToString: @"characters"]) {
|
|
498
|
+
textView.autocapitalizationType = UITextAutocapitalizationTypeAllCharacters;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// textView needs to be refocused on autocapitalization type change and we don't want to emit these events
|
|
502
|
+
if([textView isFirstResponder]) {
|
|
503
|
+
_emitFocusBlur = NO;
|
|
504
|
+
[textView reactBlur];
|
|
505
|
+
[textView reactFocus];
|
|
506
|
+
_emitFocusBlur = YES;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// isOnChangeHtmlSet
|
|
511
|
+
emitHtml = newViewProps.isOnChangeHtmlSet;
|
|
512
|
+
|
|
513
|
+
[super updateProps:props oldProps:oldProps];
|
|
514
|
+
// mandatory text and height checks
|
|
515
|
+
[self anyTextMayHaveBeenModified];
|
|
516
|
+
[self tryUpdatingHeight];
|
|
517
|
+
|
|
518
|
+
// autofocus - needs to be done at the very end
|
|
519
|
+
if(isFirstMount && newViewProps.autoFocus) {
|
|
520
|
+
[textView reactFocus];
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
- (void)setPlaceholderLabelShown:(BOOL)shown {
|
|
525
|
+
if(shown) {
|
|
526
|
+
[self refreshPlaceholderLabelStyles];
|
|
527
|
+
_placeholderLabel.hidden = NO;
|
|
528
|
+
} else {
|
|
529
|
+
_placeholderLabel.hidden = YES;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
- (void)refreshPlaceholderLabelStyles {
|
|
534
|
+
NSMutableDictionary *newAttrs = [defaultTypingAttributes mutableCopy];
|
|
535
|
+
if(_placeholderColor != nullptr) {
|
|
536
|
+
newAttrs[NSForegroundColorAttributeName] = _placeholderColor;
|
|
537
|
+
}
|
|
538
|
+
NSAttributedString *newAttrStr = [[NSAttributedString alloc] initWithString:_placeholderLabel.text attributes: newAttrs];
|
|
539
|
+
_placeholderLabel.attributedText = newAttrStr;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// MARK: - Measuring and states
|
|
543
|
+
|
|
544
|
+
- (CGSize)measureSize:(CGFloat)maxWidth {
|
|
545
|
+
// copy the the whole attributed string
|
|
546
|
+
NSMutableAttributedString *currentStr = [[NSMutableAttributedString alloc] initWithAttributedString:textView.textStorage];
|
|
547
|
+
|
|
548
|
+
// edge case: empty input should still be of a height of a single line, so we add a mock "I" character
|
|
549
|
+
if([currentStr length] == 0 ) {
|
|
550
|
+
[currentStr appendAttributedString:
|
|
551
|
+
[[NSAttributedString alloc] initWithString:@"I" attributes:textView.typingAttributes]
|
|
552
|
+
];
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// edge case: input with only a zero width space should still be of a height of a single line, so we add a mock "I" character
|
|
556
|
+
if([currentStr length] == 1 && [[currentStr.string substringWithRange:NSMakeRange(0, 1)] isEqualToString:@"\u200B"]) {
|
|
557
|
+
[currentStr appendAttributedString:
|
|
558
|
+
[[NSAttributedString alloc] initWithString:@"I" attributes:textView.typingAttributes]
|
|
559
|
+
];
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// edge case: trailing newlines aren't counted towards height calculations, so we add a mock "I" character
|
|
563
|
+
if(currentStr.length > 0) {
|
|
564
|
+
unichar lastChar = [currentStr.string characterAtIndex:currentStr.length-1];
|
|
565
|
+
if([[NSCharacterSet newlineCharacterSet] characterIsMember:lastChar]) {
|
|
566
|
+
[currentStr appendAttributedString:
|
|
567
|
+
[[NSAttributedString alloc] initWithString:@"I" attributes:textView.typingAttributes]
|
|
568
|
+
];
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
CGRect boundingBox = [currentStr boundingRectWithSize:
|
|
573
|
+
CGSizeMake(maxWidth, CGFLOAT_MAX)
|
|
574
|
+
options: NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
|
575
|
+
context: nullptr
|
|
576
|
+
];
|
|
577
|
+
|
|
578
|
+
return CGSizeMake(maxWidth, ceil(boundingBox.size.height));
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// make sure the newest state is kept in _state property
|
|
582
|
+
- (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState {
|
|
583
|
+
_state = std::static_pointer_cast<const EnrichedTextInputViewShadowNode::ConcreteState>(state);
|
|
584
|
+
|
|
585
|
+
// first render with all the needed stuff already defined (state and componentView)
|
|
586
|
+
// so we need to run a single height calculation for any initial values
|
|
587
|
+
if(oldState == nullptr) {
|
|
588
|
+
[self tryUpdatingHeight];
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
- (void)tryUpdatingHeight {
|
|
593
|
+
if(_state == nullptr) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
_componentViewHeightUpdateCounter++;
|
|
597
|
+
auto selfRef = wrapManagedObjectWeakly(self);
|
|
598
|
+
_state->updateState(EnrichedTextInputViewState(_componentViewHeightUpdateCounter, selfRef));
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// MARK: - Active styles
|
|
602
|
+
|
|
603
|
+
- (void)tryUpdatingActiveStyles {
|
|
604
|
+
// style updates are emitted only if something differs from the previously active styles
|
|
605
|
+
BOOL updateNeeded = NO;
|
|
606
|
+
|
|
607
|
+
// data for onLinkDetected event
|
|
608
|
+
LinkData *detectedLinkData;
|
|
609
|
+
NSRange detectedLinkRange = NSMakeRange(0, 0);
|
|
610
|
+
|
|
611
|
+
// data for onMentionDetected event
|
|
612
|
+
MentionParams *detectedMentionParams;
|
|
613
|
+
NSRange detectedMentionRange = NSMakeRange(0, 0);
|
|
614
|
+
|
|
615
|
+
for (NSNumber* type in stylesDict) {
|
|
616
|
+
id<BaseStyleProtocol> style = stylesDict[type];
|
|
617
|
+
BOOL wasActive = [_activeStyles containsObject: type];
|
|
618
|
+
BOOL isActive = [style detectStyle:textView.selectedRange];
|
|
619
|
+
if(wasActive != isActive) {
|
|
620
|
+
updateNeeded = YES;
|
|
621
|
+
if(isActive) {
|
|
622
|
+
[_activeStyles addObject:type];
|
|
623
|
+
} else {
|
|
624
|
+
[_activeStyles removeObject:type];
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// onLinkDetected event
|
|
629
|
+
if(isActive && [type intValue] == [LinkStyle getStyleType]) {
|
|
630
|
+
// get the link data
|
|
631
|
+
LinkData *candidateLinkData;
|
|
632
|
+
NSRange candidateLinkRange = NSMakeRange(0, 0);
|
|
633
|
+
LinkStyle *linkStyleClass = (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
|
|
634
|
+
if(linkStyleClass != nullptr) {
|
|
635
|
+
candidateLinkData = [linkStyleClass getLinkDataAt:textView.selectedRange.location];
|
|
636
|
+
candidateLinkRange = [linkStyleClass getFullLinkRangeAt:textView.selectedRange.location];
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if(wasActive == NO) {
|
|
640
|
+
// we changed selection from non-link to a link
|
|
641
|
+
detectedLinkData = candidateLinkData;
|
|
642
|
+
detectedLinkRange = candidateLinkRange;
|
|
643
|
+
} else if(
|
|
644
|
+
![_recentlyActiveLinkData.url isEqualToString:candidateLinkData.url] ||
|
|
645
|
+
![_recentlyActiveLinkData.text isEqualToString:candidateLinkData.text] ||
|
|
646
|
+
!NSEqualRanges(_recentlyActiveLinkRange, candidateLinkRange)
|
|
647
|
+
) {
|
|
648
|
+
// we changed selection from one link to the other or modified current link's text
|
|
649
|
+
detectedLinkData = candidateLinkData;
|
|
650
|
+
detectedLinkRange = candidateLinkRange;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// onMentionDetected event
|
|
655
|
+
if(isActive && [type intValue] == [MentionStyle getStyleType]) {
|
|
656
|
+
// get mention data
|
|
657
|
+
MentionParams *candidateMentionParams;
|
|
658
|
+
NSRange candidateMentionRange = NSMakeRange(0, 0);
|
|
659
|
+
MentionStyle *mentionStyleClass = (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
|
|
660
|
+
if(mentionStyleClass != nullptr) {
|
|
661
|
+
candidateMentionParams = [mentionStyleClass getMentionParamsAt:textView.selectedRange.location];
|
|
662
|
+
candidateMentionRange = [mentionStyleClass getFullMentionRangeAt:textView.selectedRange.location];
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if(wasActive == NO) {
|
|
666
|
+
// selection was changed from a non-mention to a mention
|
|
667
|
+
detectedMentionParams = candidateMentionParams;
|
|
668
|
+
detectedMentionRange = candidateMentionRange;
|
|
669
|
+
} else if(
|
|
670
|
+
![_recentlyActiveMentionParams.text isEqualToString:candidateMentionParams.text] ||
|
|
671
|
+
![_recentlyActiveMentionParams.attributes isEqualToString:candidateMentionParams.attributes] ||
|
|
672
|
+
!NSEqualRanges(_recentlyActiveMentionRange, candidateMentionRange)
|
|
673
|
+
) {
|
|
674
|
+
// selection changed from one mention to another
|
|
675
|
+
detectedMentionParams = candidateMentionParams;
|
|
676
|
+
detectedMentionRange = candidateMentionRange;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if(updateNeeded) {
|
|
682
|
+
auto emitter = [self getEventEmitter];
|
|
683
|
+
if(emitter != nullptr) {
|
|
684
|
+
emitter->onChangeState({
|
|
685
|
+
.isBold = [_activeStyles containsObject: @([BoldStyle getStyleType])],
|
|
686
|
+
.isItalic = [_activeStyles containsObject: @([ItalicStyle getStyleType])],
|
|
687
|
+
.isUnderline = [_activeStyles containsObject: @([UnderlineStyle getStyleType])],
|
|
688
|
+
.isStrikeThrough = [_activeStyles containsObject: @([StrikethroughStyle getStyleType])],
|
|
689
|
+
.isInlineCode = [_activeStyles containsObject: @([InlineCodeStyle getStyleType])],
|
|
690
|
+
.isLink = [_activeStyles containsObject: @([LinkStyle getStyleType])],
|
|
691
|
+
.isMention = [_activeStyles containsObject: @([MentionStyle getStyleType])],
|
|
692
|
+
.isH1 = [_activeStyles containsObject: @([H1Style getStyleType])],
|
|
693
|
+
.isH2 = [_activeStyles containsObject: @([H2Style getStyleType])],
|
|
694
|
+
.isH3 = [_activeStyles containsObject: @([H3Style getStyleType])],
|
|
695
|
+
.isUnorderedList = [_activeStyles containsObject: @([UnorderedListStyle getStyleType])],
|
|
696
|
+
.isOrderedList = [_activeStyles containsObject: @([OrderedListStyle getStyleType])],
|
|
697
|
+
.isBlockQuote = [_activeStyles containsObject: @([BlockQuoteStyle getStyleType])],
|
|
698
|
+
.isCodeBlock = NO, // [_activeStyles containsObject: @([CodeBlockStyle getStyleType])],
|
|
699
|
+
.isImage = NO // [_activeStyles containsObject: @([ImageStyle getStyleType]])],
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if(detectedLinkData != nullptr) {
|
|
705
|
+
// emit onLinkeDetected event
|
|
706
|
+
[self emitOnLinkDetectedEvent:detectedLinkData.text url:detectedLinkData.url range:detectedLinkRange];
|
|
707
|
+
|
|
708
|
+
_recentlyActiveLinkData = detectedLinkData;
|
|
709
|
+
_recentlyActiveLinkRange = detectedLinkRange;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if(detectedMentionParams != nullptr) {
|
|
713
|
+
// emit onMentionDetected event
|
|
714
|
+
[self emitOnMentionDetectedEvent:detectedMentionParams.text indicator:detectedMentionParams.indicator attributes:detectedMentionParams.attributes];
|
|
715
|
+
|
|
716
|
+
_recentlyActiveMentionParams = detectedMentionParams;
|
|
717
|
+
_recentlyActiveMentionRange = detectedMentionRange;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// emit onChangeHtml event if needed
|
|
721
|
+
[self tryEmittingOnChangeHtmlEvent];
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// MARK: - Native commands and events
|
|
725
|
+
|
|
726
|
+
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
|
|
727
|
+
if([commandName isEqualToString:@"focus"]) {
|
|
728
|
+
[self focus];
|
|
729
|
+
} else if([commandName isEqualToString:@"blur"]) {
|
|
730
|
+
[self blur];
|
|
731
|
+
} else if([commandName isEqualToString:@"setValue"]) {
|
|
732
|
+
NSString *value = (NSString *)args[0];
|
|
733
|
+
[self setValue:value];
|
|
734
|
+
} else if([commandName isEqualToString:@"toggleBold"]) {
|
|
735
|
+
[self toggleRegularStyle: [BoldStyle getStyleType]];
|
|
736
|
+
} else if([commandName isEqualToString:@"toggleItalic"]) {
|
|
737
|
+
[self toggleRegularStyle: [ItalicStyle getStyleType]];
|
|
738
|
+
} else if([commandName isEqualToString:@"toggleUnderline"]) {
|
|
739
|
+
[self toggleRegularStyle: [UnderlineStyle getStyleType]];
|
|
740
|
+
} else if([commandName isEqualToString:@"toggleStrikeThrough"]) {
|
|
741
|
+
[self toggleRegularStyle: [StrikethroughStyle getStyleType]];
|
|
742
|
+
} else if([commandName isEqualToString:@"toggleInlineCode"]) {
|
|
743
|
+
[self toggleRegularStyle: [InlineCodeStyle getStyleType]];
|
|
744
|
+
} else if([commandName isEqualToString:@"addLink"]) {
|
|
745
|
+
NSInteger start = [((NSNumber*)args[0]) integerValue];
|
|
746
|
+
NSInteger end = [((NSNumber*)args[1]) integerValue];
|
|
747
|
+
NSString *text = (NSString *)args[2];
|
|
748
|
+
NSString *url = (NSString *)args[3];
|
|
749
|
+
[self addLinkAt:start end:end text:text url:url];
|
|
750
|
+
} else if([commandName isEqualToString:@"addMention"]) {
|
|
751
|
+
NSString *indicator = (NSString *)args[0];
|
|
752
|
+
NSString *text = (NSString *)args[1];
|
|
753
|
+
NSString *attributes = (NSString *)args[2];
|
|
754
|
+
[self addMention:indicator text:text attributes:attributes];
|
|
755
|
+
} else if([commandName isEqualToString:@"startMention"]) {
|
|
756
|
+
NSString *indicator = (NSString *)args[0];
|
|
757
|
+
[self startMentionWithIndicator:indicator];
|
|
758
|
+
} else if([commandName isEqualToString:@"toggleH1"]) {
|
|
759
|
+
[self toggleParagraphStyle:[H1Style getStyleType]];
|
|
760
|
+
} else if([commandName isEqualToString:@"toggleH2"]) {
|
|
761
|
+
[self toggleParagraphStyle:[H2Style getStyleType]];
|
|
762
|
+
} else if([commandName isEqualToString:@"toggleH3"]) {
|
|
763
|
+
[self toggleParagraphStyle:[H3Style getStyleType]];
|
|
764
|
+
} else if([commandName isEqualToString:@"toggleUnorderedList"]) {
|
|
765
|
+
[self toggleParagraphStyle:[UnorderedListStyle getStyleType]];
|
|
766
|
+
} else if([commandName isEqualToString:@"toggleOrderedList"]) {
|
|
767
|
+
[self toggleParagraphStyle:[OrderedListStyle getStyleType]];
|
|
768
|
+
} else if([commandName isEqualToString:@"toggleBlockQuote"]) {
|
|
769
|
+
[self toggleParagraphStyle:[BlockQuoteStyle getStyleType]];
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
- (std::shared_ptr<EnrichedTextInputViewEventEmitter>)getEventEmitter {
|
|
774
|
+
if(_eventEmitter != nullptr && !blockEmitting) {
|
|
775
|
+
auto emitter = static_cast<const EnrichedTextInputViewEventEmitter &>(*_eventEmitter);
|
|
776
|
+
return std::make_shared<EnrichedTextInputViewEventEmitter>(emitter);
|
|
777
|
+
} else {
|
|
778
|
+
return nullptr;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
- (void)blur {
|
|
783
|
+
[textView reactBlur];
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
- (void)focus {
|
|
787
|
+
[textView reactFocus];
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
- (void)setValue:(NSString *)value {
|
|
791
|
+
NSString *initiallyProcessedHtml = [parser initiallyProcessHtml:value];
|
|
792
|
+
if(initiallyProcessedHtml == nullptr) {
|
|
793
|
+
// just plain text
|
|
794
|
+
textView.text = value;
|
|
795
|
+
} else {
|
|
796
|
+
// we've got some seemingly proper html
|
|
797
|
+
[parser replaceWholeFromHtml:initiallyProcessedHtml];
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// set recentlyChangedRange and check for changes
|
|
801
|
+
recentlyChangedRange = NSMakeRange(0, textView.textStorage.string.length);
|
|
802
|
+
[self anyTextMayHaveBeenModified];
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
- (void)emitOnLinkDetectedEvent:(NSString *)text url:(NSString *)url range:(NSRange)range {
|
|
806
|
+
auto emitter = [self getEventEmitter];
|
|
807
|
+
if(emitter != nullptr) {
|
|
808
|
+
emitter->onLinkDetected({
|
|
809
|
+
.text = [text toCppString],
|
|
810
|
+
.url = [url toCppString],
|
|
811
|
+
.start = static_cast<int>(range.location),
|
|
812
|
+
.end = static_cast<int>(range.location + range.length),
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
- (void)emitOnMentionDetectedEvent:(NSString *)text indicator:(NSString *)indicator attributes:(NSString *)attributes {
|
|
818
|
+
auto emitter = [self getEventEmitter];
|
|
819
|
+
if(emitter != nullptr) {
|
|
820
|
+
emitter->onMentionDetected({
|
|
821
|
+
.text = [text toCppString],
|
|
822
|
+
.indicator = [indicator toCppString],
|
|
823
|
+
.payload = [attributes toCppString]
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
- (void)emitOnMentionEvent:(NSString *)indicator text:(NSString *)text {
|
|
829
|
+
auto emitter = [self getEventEmitter];
|
|
830
|
+
if(emitter != nullptr) {
|
|
831
|
+
if(text != nullptr) {
|
|
832
|
+
folly::dynamic fdStr = [text toCppString];
|
|
833
|
+
emitter->onMention({
|
|
834
|
+
.indicator = [indicator toCppString],
|
|
835
|
+
.text = fdStr
|
|
836
|
+
});
|
|
837
|
+
} else {
|
|
838
|
+
folly::dynamic nul = nullptr;
|
|
839
|
+
emitter->onMention({
|
|
840
|
+
.indicator = [indicator toCppString],
|
|
841
|
+
.text = nul
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
- (void)tryEmittingOnChangeHtmlEvent {
|
|
848
|
+
if(!emitHtml || textView.markedTextRange != nullptr) {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
auto emitter = [self getEventEmitter];
|
|
852
|
+
if(emitter != nullptr) {
|
|
853
|
+
NSString *htmlOutput = [parser parseToHtmlFromRange:NSMakeRange(0, textView.textStorage.string.length)];
|
|
854
|
+
// make sure html really changed
|
|
855
|
+
if(![htmlOutput isEqualToString:_recentlyEmittedHtml]) {
|
|
856
|
+
_recentlyEmittedHtml = htmlOutput;
|
|
857
|
+
emitter->onChangeHtml({
|
|
858
|
+
.value = [htmlOutput toCppString]
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// MARK: - Styles manipulation
|
|
865
|
+
|
|
866
|
+
- (void)toggleRegularStyle:(StyleType)type {
|
|
867
|
+
id<BaseStyleProtocol> styleClass = stylesDict[@(type)];
|
|
868
|
+
|
|
869
|
+
if([self handleStyleBlocksAndConflicts:type range:textView.selectedRange]) {
|
|
870
|
+
[styleClass applyStyle:textView.selectedRange];
|
|
871
|
+
[self anyTextMayHaveBeenModified];
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
- (void)toggleParagraphStyle:(StyleType)type {
|
|
876
|
+
id<BaseStyleProtocol> styleClass = stylesDict[@(type)];
|
|
877
|
+
// we always pass whole paragraph/s range to these styles
|
|
878
|
+
NSRange paragraphRange = [textView.textStorage.string paragraphRangeForRange:textView.selectedRange];
|
|
879
|
+
|
|
880
|
+
if([self handleStyleBlocksAndConflicts:type range:paragraphRange]) {
|
|
881
|
+
[styleClass applyStyle:paragraphRange];
|
|
882
|
+
[self anyTextMayHaveBeenModified];
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
- (void)addLinkAt:(NSInteger)start end:(NSInteger)end text:(NSString *)text url:(NSString *)url {
|
|
887
|
+
LinkStyle *linkStyleClass = (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
|
|
888
|
+
if(linkStyleClass == nullptr) { return; }
|
|
889
|
+
|
|
890
|
+
// translate the output start-end notation to range
|
|
891
|
+
NSRange linkRange = NSMakeRange(start, end - start);
|
|
892
|
+
if([self handleStyleBlocksAndConflicts:[LinkStyle getStyleType] range:linkRange]) {
|
|
893
|
+
[linkStyleClass addLink:text url:url range:linkRange manual:YES];
|
|
894
|
+
[self anyTextMayHaveBeenModified];
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
- (void)addMention:(NSString *)indicator text:(NSString *)text attributes:(NSString *)attributes {
|
|
899
|
+
MentionStyle *mentionStyleClass = (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
|
|
900
|
+
if(mentionStyleClass == nullptr) { return; }
|
|
901
|
+
if([mentionStyleClass getActiveMentionRange] == nullptr) { return; }
|
|
902
|
+
|
|
903
|
+
if([self handleStyleBlocksAndConflicts:[MentionStyle getStyleType] range:[[mentionStyleClass getActiveMentionRange] rangeValue]]) {
|
|
904
|
+
[mentionStyleClass addMention:indicator text:text attributes:attributes];
|
|
905
|
+
[self anyTextMayHaveBeenModified];
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
- (void)startMentionWithIndicator:(NSString *)indicator {
|
|
910
|
+
MentionStyle *mentionStyleClass = (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
|
|
911
|
+
if(mentionStyleClass == nullptr) { return; }
|
|
912
|
+
|
|
913
|
+
if([self handleStyleBlocksAndConflicts:[MentionStyle getStyleType] range:[[mentionStyleClass getActiveMentionRange] rangeValue]]) {
|
|
914
|
+
[mentionStyleClass startMentionWithIndicator:indicator];
|
|
915
|
+
[self anyTextMayHaveBeenModified];
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// returns false when style shouldn't be applied and true when it can be
|
|
920
|
+
- (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range {
|
|
921
|
+
// handle blocking styles: if any is present we do not apply the toggled style
|
|
922
|
+
NSArray<NSNumber *> *blocking = [self getPresentStyleTypesFrom: _blockingStyles[@(type)] range:range];
|
|
923
|
+
if(blocking.count != 0) {
|
|
924
|
+
return NO;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// handle conflicting styles: all of their occurences have to be removed
|
|
928
|
+
NSArray<NSNumber *> *conflicting = [self getPresentStyleTypesFrom: _conflictingStyles[@(type)] range:range];
|
|
929
|
+
if(conflicting.count != 0) {
|
|
930
|
+
for(NSNumber *style in conflicting) {
|
|
931
|
+
id<BaseStyleProtocol> styleClass = stylesDict[style];
|
|
932
|
+
|
|
933
|
+
if(range.length >= 1) {
|
|
934
|
+
// for ranges, we need to remove each occurence
|
|
935
|
+
NSArray<StylePair *> *allOccurences = [styleClass findAllOccurences:range];
|
|
936
|
+
|
|
937
|
+
for(StylePair* pair in allOccurences) {
|
|
938
|
+
[styleClass removeAttributes: [pair.rangeValue rangeValue]];
|
|
939
|
+
}
|
|
940
|
+
} else {
|
|
941
|
+
// with in-place selection, we just remove the adequate typing attributes
|
|
942
|
+
[styleClass removeTypingAttributes];
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
return YES;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
- (NSArray<NSNumber *> *)getPresentStyleTypesFrom:(NSArray<NSNumber *> *)types range:(NSRange)range {
|
|
950
|
+
NSMutableArray<NSNumber *> *resultArray = [[NSMutableArray<NSNumber *> alloc] init];
|
|
951
|
+
for(NSNumber *type in types) {
|
|
952
|
+
id<BaseStyleProtocol> styleClass = stylesDict[type];
|
|
953
|
+
|
|
954
|
+
if(range.length >= 1) {
|
|
955
|
+
if([styleClass anyOccurence:range]) {
|
|
956
|
+
[resultArray addObject:type];
|
|
957
|
+
}
|
|
958
|
+
} else {
|
|
959
|
+
if([styleClass detectStyle:range]) {
|
|
960
|
+
[resultArray addObject:type];
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return resultArray;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
- (void)manageSelectionBasedChanges {
|
|
968
|
+
// link typing attributes fix
|
|
969
|
+
LinkStyle *linkStyleClass = (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
|
|
970
|
+
if(linkStyleClass != nullptr) {
|
|
971
|
+
[linkStyleClass manageLinkTypingAttributes];
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// mention typing attribtues fix and active editing
|
|
975
|
+
MentionStyle *mentionStyleClass = (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
|
|
976
|
+
if(mentionStyleClass != nullptr) {
|
|
977
|
+
[mentionStyleClass manageMentionTypingAttributes];
|
|
978
|
+
[mentionStyleClass manageMentionEditing];
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
- (void)handleWordModificationBasedChanges:(NSString*)word inRange:(NSRange)range {
|
|
983
|
+
// manual links refreshing and automatic links detection handling
|
|
984
|
+
LinkStyle* linkStyle = [stylesDict objectForKey:@([LinkStyle getStyleType])];
|
|
985
|
+
if(linkStyle != nullptr) {
|
|
986
|
+
// manual links need to be handled first because they can block automatic links after being refreshed
|
|
987
|
+
[linkStyle handleManualLinks:word inRange:range];
|
|
988
|
+
[linkStyle handleAutomaticLinks:word inRange:range];
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
- (void)anyTextMayHaveBeenModified {
|
|
993
|
+
// we don't do no text changes when working with iOS marked text
|
|
994
|
+
if(textView.markedTextRange != nullptr) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// emptying input typing attributes management
|
|
999
|
+
if(textView.textStorage.string.length == 0 && _recentlyEmittedString.length > 0) {
|
|
1000
|
+
// reset typing attribtues
|
|
1001
|
+
textView.typingAttributes = defaultTypingAttributes;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// zero width space removal
|
|
1005
|
+
[ZeroWidthSpaceUtils handleZeroWidthSpacesInInput:self];
|
|
1006
|
+
|
|
1007
|
+
// inline code on newlines fix
|
|
1008
|
+
InlineCodeStyle *codeStyle = stylesDict[@([InlineCodeStyle getStyleType])];
|
|
1009
|
+
if(codeStyle != nullptr) {
|
|
1010
|
+
[codeStyle handleNewlines];
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// blockquote colors management
|
|
1014
|
+
BlockQuoteStyle *bqStyle = stylesDict[@([BlockQuoteStyle getStyleType])];
|
|
1015
|
+
if(bqStyle != nullptr) {
|
|
1016
|
+
[bqStyle manageBlockquoteColor];
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// placholder management
|
|
1020
|
+
if(!_placeholderLabel.hidden && textView.textStorage.string.length > 0) {
|
|
1021
|
+
[self setPlaceholderLabelShown:NO];
|
|
1022
|
+
} else if(textView.textStorage.string.length == 0 && _placeholderLabel.hidden) {
|
|
1023
|
+
[self setPlaceholderLabelShown:YES];
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if(![textView.textStorage.string isEqualToString:_recentlyEmittedString]) {
|
|
1027
|
+
// mentions removal management
|
|
1028
|
+
MentionStyle *mentionStyleClass = (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
|
|
1029
|
+
if(mentionStyleClass != nullptr) {
|
|
1030
|
+
[mentionStyleClass handleExistingMentions];
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// modified words handling
|
|
1034
|
+
NSArray *modifiedWords = [WordsUtils getAffectedWordsFromText:textView.textStorage.string modificationRange:recentlyChangedRange];
|
|
1035
|
+
if(modifiedWords != nullptr) {
|
|
1036
|
+
for(NSDictionary *wordDict in modifiedWords) {
|
|
1037
|
+
NSString *wordText = (NSString *)[wordDict objectForKey:@"word"];
|
|
1038
|
+
NSValue *wordRange = (NSValue *)[wordDict objectForKey:@"range"];
|
|
1039
|
+
|
|
1040
|
+
if(wordText == nullptr || wordRange == nullptr) {
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
[self handleWordModificationBasedChanges:wordText inRange:[wordRange rangeValue]];
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// emit string without zero width spaces
|
|
1049
|
+
NSString *stringToBeEmitted = [[textView.textStorage.string stringByReplacingOccurrencesOfString:@"\u200B" withString:@""] copy];
|
|
1050
|
+
|
|
1051
|
+
// emit onChangeText event
|
|
1052
|
+
auto emitter = [self getEventEmitter];
|
|
1053
|
+
if(emitter != nullptr) {
|
|
1054
|
+
emitter->onChangeText({
|
|
1055
|
+
.value = [stringToBeEmitted toCppString]
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// set the recently emitted string
|
|
1060
|
+
_recentlyEmittedString = stringToBeEmitted;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// update height on each character change
|
|
1064
|
+
[self tryUpdatingHeight];
|
|
1065
|
+
// update active styles as well
|
|
1066
|
+
[self tryUpdatingActiveStyles];
|
|
1067
|
+
// update drawing
|
|
1068
|
+
NSRange wholeRange = NSMakeRange(0, textView.textStorage.string.length);
|
|
1069
|
+
[textView.layoutManager invalidateLayoutForCharacterRange:wholeRange actualCharacterRange:nullptr];
|
|
1070
|
+
[textView.layoutManager invalidateDisplayForCharacterRange:wholeRange];
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// MARK: - UITextView delegate methods
|
|
1074
|
+
|
|
1075
|
+
- (void)textViewDidBeginEditing:(UITextView *)textView {
|
|
1076
|
+
auto emitter = [self getEventEmitter];
|
|
1077
|
+
if(emitter != nullptr) {
|
|
1078
|
+
//send onFocus event if allowed
|
|
1079
|
+
if(_emitFocusBlur) {
|
|
1080
|
+
emitter->onInputFocus({});
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
NSString *textAtSelection = [[[NSMutableString alloc] initWithString:textView.textStorage.string] substringWithRange: textView.selectedRange];
|
|
1084
|
+
emitter->onChangeSelection({
|
|
1085
|
+
.start = static_cast<int>(textView.selectedRange.location),
|
|
1086
|
+
.end = static_cast<int>(textView.selectedRange.location + textView.selectedRange.length),
|
|
1087
|
+
.text = [textAtSelection toCppString]
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
// manage selection changes since textViewDidChangeSelection sometimes doesn't run on focus
|
|
1091
|
+
[self manageSelectionBasedChanges];
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
- (void)textViewDidEndEditing:(UITextView *)textView {
|
|
1095
|
+
auto emitter = [self getEventEmitter];
|
|
1096
|
+
if(emitter != nullptr && _emitFocusBlur) {
|
|
1097
|
+
//send onBlur event
|
|
1098
|
+
emitter->onInputBlur({});
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
- (bool)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
|
|
1103
|
+
recentlyChangedRange = NSMakeRange(range.location, text.length);
|
|
1104
|
+
|
|
1105
|
+
BOOL rejectTextChanges = NO;
|
|
1106
|
+
|
|
1107
|
+
UnorderedListStyle *uStyle = stylesDict[@([UnorderedListStyle getStyleType])];
|
|
1108
|
+
if(uStyle != nullptr) {
|
|
1109
|
+
// removing first line list fix
|
|
1110
|
+
BOOL removedFirstLineList = [uStyle handleBackspaceInRange:range replacementText:text];
|
|
1111
|
+
|
|
1112
|
+
// creating unordered list from "- "
|
|
1113
|
+
BOOL addedShortcutList = [uStyle tryHandlingListShorcutInRange:range replacementText:text];
|
|
1114
|
+
|
|
1115
|
+
// any of these changes make us reject text changes
|
|
1116
|
+
rejectTextChanges = rejectTextChanges || removedFirstLineList || addedShortcutList;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
OrderedListStyle *oStyle = stylesDict[@([OrderedListStyle getStyleType])];
|
|
1120
|
+
if(oStyle != nullptr) {
|
|
1121
|
+
// removing first line list fix
|
|
1122
|
+
BOOL removedFirstLineList = [oStyle handleBackspaceInRange:range replacementText:text];
|
|
1123
|
+
|
|
1124
|
+
// creating ordered list from "1."
|
|
1125
|
+
BOOL addedShortcutList = [oStyle tryHandlingListShorcutInRange:range replacementText:text];
|
|
1126
|
+
|
|
1127
|
+
// any of these changes make us reject text changes
|
|
1128
|
+
rejectTextChanges = rejectTextChanges || removedFirstLineList || addedShortcutList;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
BlockQuoteStyle *bqStyle = stylesDict[@([BlockQuoteStyle getStyleType])];
|
|
1132
|
+
if(bqStyle != nullptr) {
|
|
1133
|
+
// removing first line quote fix
|
|
1134
|
+
BOOL removedFirstLineQuote = [bqStyle handleBackspaceInRange:range replacementText:text];
|
|
1135
|
+
rejectTextChanges = rejectTextChanges || removedFirstLineQuote;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
LinkStyle *linkStyle = stylesDict[@([LinkStyle getStyleType])];
|
|
1139
|
+
if(linkStyle != nullptr) {
|
|
1140
|
+
// persisting link attributes fix
|
|
1141
|
+
BOOL fixedLeadingAttributes = [linkStyle handleLeadingLinkReplacement:range replacementText:text];
|
|
1142
|
+
rejectTextChanges = rejectTextChanges || fixedLeadingAttributes;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
MentionStyle *mentionStyle = stylesDict[@([MentionStyle getStyleType])];
|
|
1146
|
+
if(mentionStyle != nullptr) {
|
|
1147
|
+
// persisting mention attributes fix
|
|
1148
|
+
BOOL fixedLeadingAttributes = [mentionStyle handleLeadingMentionReplacement:range replacementText:text];
|
|
1149
|
+
rejectTextChanges = rejectTextChanges || fixedLeadingAttributes;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// zero width space removal fix
|
|
1153
|
+
rejectTextChanges = rejectTextChanges || [ZeroWidthSpaceUtils handleBackspaceInRange:range replacementText:text input:self];
|
|
1154
|
+
|
|
1155
|
+
if(rejectTextChanges) {
|
|
1156
|
+
[self anyTextMayHaveBeenModified];
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
return !rejectTextChanges;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
- (void)textViewDidChangeSelection:(UITextView *)textView {
|
|
1163
|
+
// emit the event
|
|
1164
|
+
NSString *textAtSelection = [[[NSMutableString alloc] initWithString:textView.textStorage.string] substringWithRange: textView.selectedRange];
|
|
1165
|
+
|
|
1166
|
+
auto emitter = [self getEventEmitter];
|
|
1167
|
+
if(emitter != nullptr) {
|
|
1168
|
+
// iOS range works differently because it specifies location and length
|
|
1169
|
+
// here, start is the location, but end is the first index BEHIND the end. So a 0 length range will have equal start and end
|
|
1170
|
+
emitter->onChangeSelection({
|
|
1171
|
+
.start = static_cast<int>(textView.selectedRange.location),
|
|
1172
|
+
.end = static_cast<int>(textView.selectedRange.location + textView.selectedRange.length),
|
|
1173
|
+
.text = [textAtSelection toCppString]
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// manage selection changes
|
|
1178
|
+
[self manageSelectionBasedChanges];
|
|
1179
|
+
|
|
1180
|
+
// update active styles
|
|
1181
|
+
[self tryUpdatingActiveStyles];
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// this function isn't called always when some text changes (for example setting link or starting mention with indicator doesn't fire it)
|
|
1185
|
+
// so all the logic is in anyTextMayHaveBeenModified
|
|
1186
|
+
- (void)textViewDidChange:(UITextView *)textView {
|
|
1187
|
+
[self anyTextMayHaveBeenModified];
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
@end
|