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.
- package/LICENSE +20 -0
- package/README.md +869 -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 +669 -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,476 @@
|
|
|
1
|
+
#import "StyleHeaders.h"
|
|
2
|
+
#import "EnrichedTextInputView.h"
|
|
3
|
+
#import "OccurenceUtils.h"
|
|
4
|
+
#import "TextInsertionUtils.h"
|
|
5
|
+
#import "WordsUtils.h"
|
|
6
|
+
#import "UIView+React.h"
|
|
7
|
+
|
|
8
|
+
// custom NSAttributedStringKey to differentiate from links
|
|
9
|
+
static NSString *const MentionAttributeName = @"MentionAttributeName";
|
|
10
|
+
|
|
11
|
+
@implementation MentionStyle {
|
|
12
|
+
EnrichedTextInputView*_input;
|
|
13
|
+
NSValue *_activeMentionRange;
|
|
14
|
+
NSString *_activeMentionIndicator;
|
|
15
|
+
BOOL _blockMentionEditing;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
+ (StyleType)getStyleType { return Mention; }
|
|
19
|
+
|
|
20
|
+
- (instancetype)initWithInput:(id)input {
|
|
21
|
+
self = [super init];
|
|
22
|
+
_input = (EnrichedTextInputView *)input;
|
|
23
|
+
_activeMentionRange = nullptr;
|
|
24
|
+
_activeMentionIndicator = nullptr;
|
|
25
|
+
_blockMentionEditing = NO;
|
|
26
|
+
return self;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
- (void)applyStyle:(NSRange)range {
|
|
30
|
+
// no-op for mentions
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
- (void)addAttributes:(NSRange)range {
|
|
34
|
+
// no-op for mentions
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
- (void)addTypingAttributes {
|
|
38
|
+
// no-op for mentions
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// we have to make sure all mentions get removed properly
|
|
42
|
+
- (void)removeAttributes:(NSRange)range {
|
|
43
|
+
BOOL someMentionHadUnderline = NO;
|
|
44
|
+
|
|
45
|
+
NSArray<StylePair *> *mentions = [self findAllOccurences:range];
|
|
46
|
+
[_input->textView.textStorage beginEditing];
|
|
47
|
+
for(StylePair *pair in mentions) {
|
|
48
|
+
NSRange mentionRange = [self getFullMentionRangeAt:[pair.rangeValue rangeValue].location];
|
|
49
|
+
[_input->textView.textStorage removeAttribute:MentionAttributeName range:mentionRange];
|
|
50
|
+
[_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:[_input->config primaryColor] range:mentionRange];
|
|
51
|
+
[_input->textView.textStorage addAttribute:NSUnderlineColorAttributeName value:[_input->config primaryColor] range:mentionRange];
|
|
52
|
+
[_input->textView.textStorage addAttribute:NSStrikethroughColorAttributeName value:[_input->config primaryColor] range:mentionRange];
|
|
53
|
+
[_input->textView.textStorage removeAttribute:NSBackgroundColorAttributeName range:mentionRange];
|
|
54
|
+
|
|
55
|
+
if([self stylePropsWithParams:pair.styleValue].decorationLine == DecorationUnderline) {
|
|
56
|
+
[_input->textView.textStorage removeAttribute:NSUnderlineStyleAttributeName range:mentionRange];
|
|
57
|
+
someMentionHadUnderline = YES;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
[_input->textView.textStorage endEditing];
|
|
61
|
+
|
|
62
|
+
// remove typing attributes as well
|
|
63
|
+
NSMutableDictionary *newTypingAttrs = [_input->textView.typingAttributes mutableCopy];
|
|
64
|
+
newTypingAttrs[NSForegroundColorAttributeName] = [_input->config primaryColor];
|
|
65
|
+
newTypingAttrs[NSUnderlineColorAttributeName] = [_input->config primaryColor];
|
|
66
|
+
newTypingAttrs[NSStrikethroughColorAttributeName] = [_input->config primaryColor];
|
|
67
|
+
[newTypingAttrs removeObjectForKey:NSBackgroundColorAttributeName];
|
|
68
|
+
if(someMentionHadUnderline) {
|
|
69
|
+
[newTypingAttrs removeObjectForKey:NSUnderlineStyleAttributeName];
|
|
70
|
+
}
|
|
71
|
+
_input->textView.typingAttributes = newTypingAttrs;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// used for conflicts, we have to remove the whole mention
|
|
75
|
+
- (void)removeTypingAttributes {
|
|
76
|
+
NSRange mentionRange = [self getFullMentionRangeAt:_input->textView.selectedRange.location];
|
|
77
|
+
[_input->textView.textStorage beginEditing];
|
|
78
|
+
[_input->textView.textStorage removeAttribute:MentionAttributeName range:mentionRange];
|
|
79
|
+
[_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:[_input->config primaryColor] range:mentionRange];
|
|
80
|
+
[_input->textView.textStorage addAttribute:NSUnderlineColorAttributeName value:[_input->config primaryColor] range:mentionRange];
|
|
81
|
+
[_input->textView.textStorage addAttribute:NSStrikethroughColorAttributeName value:[_input->config primaryColor] range:mentionRange];
|
|
82
|
+
[_input->textView.textStorage removeAttribute:NSBackgroundColorAttributeName range:mentionRange];
|
|
83
|
+
|
|
84
|
+
MentionParams *params = [self getMentionParamsAt:mentionRange.location];
|
|
85
|
+
if([self stylePropsWithParams:params].decorationLine == DecorationUnderline) {
|
|
86
|
+
[_input->textView.textStorage removeAttribute:NSUnderlineStyleAttributeName range:mentionRange];
|
|
87
|
+
}
|
|
88
|
+
[_input->textView.textStorage endEditing];
|
|
89
|
+
|
|
90
|
+
// remove typing attributes as well
|
|
91
|
+
NSMutableDictionary *newTypingAttrs = [_input->textView.typingAttributes mutableCopy];
|
|
92
|
+
newTypingAttrs[NSForegroundColorAttributeName] = [_input->config primaryColor];
|
|
93
|
+
newTypingAttrs[NSUnderlineColorAttributeName] = [_input->config primaryColor];
|
|
94
|
+
newTypingAttrs[NSStrikethroughColorAttributeName] = [_input->config primaryColor];
|
|
95
|
+
[newTypingAttrs removeObjectForKey:NSBackgroundColorAttributeName];
|
|
96
|
+
if([self stylePropsWithParams:params].decorationLine == DecorationUnderline) {
|
|
97
|
+
[newTypingAttrs removeObjectForKey:NSUnderlineStyleAttributeName];
|
|
98
|
+
}
|
|
99
|
+
_input->textView.typingAttributes = newTypingAttrs;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
- (BOOL)styleCondition:(id _Nullable)value :(NSRange)range {
|
|
103
|
+
MentionParams *params = (MentionParams *)value;
|
|
104
|
+
return params != nullptr;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
- (BOOL)detectStyle:(NSRange)range {
|
|
108
|
+
if(range.length >= 1) {
|
|
109
|
+
return [OccurenceUtils detect:MentionAttributeName withInput:_input inRange:range
|
|
110
|
+
withCondition: ^BOOL(id _Nullable value, NSRange range) {
|
|
111
|
+
return [self styleCondition:value :range];
|
|
112
|
+
}
|
|
113
|
+
];
|
|
114
|
+
} else {
|
|
115
|
+
return [self getMentionParamsAt:range.location] != nullptr;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
- (BOOL)anyOccurence:(NSRange)range {
|
|
120
|
+
return [OccurenceUtils any:MentionAttributeName withInput:_input inRange:range
|
|
121
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
122
|
+
return [self styleCondition:value :range];
|
|
123
|
+
}
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
- (NSArray<StylePair *> *_Nullable)findAllOccurences:(NSRange)range {
|
|
128
|
+
return [OccurenceUtils all:MentionAttributeName withInput:_input inRange:range
|
|
129
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
130
|
+
return [self styleCondition:value :range];
|
|
131
|
+
}
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// MARK: - Public non-standard methods
|
|
136
|
+
|
|
137
|
+
- (void)addMention:(NSString *)indicator text:(NSString *)text attributes:(NSString *)attributes {
|
|
138
|
+
if(_activeMentionRange == nullptr) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// we block callbacks resulting from manageMentionEditing while we tamper with them here
|
|
143
|
+
_blockMentionEditing = YES;
|
|
144
|
+
|
|
145
|
+
MentionParams *params = [[MentionParams alloc] init];
|
|
146
|
+
params.text = text;
|
|
147
|
+
params.indicator = indicator;
|
|
148
|
+
params.attributes = attributes;
|
|
149
|
+
|
|
150
|
+
MentionStyleProps *styleProps = [_input->config mentionStylePropsForIndicator:indicator];
|
|
151
|
+
|
|
152
|
+
NSMutableDictionary *newAttrs = [@{
|
|
153
|
+
MentionAttributeName: params,
|
|
154
|
+
NSForegroundColorAttributeName: styleProps.color,
|
|
155
|
+
NSUnderlineColorAttributeName: styleProps.color,
|
|
156
|
+
NSStrikethroughColorAttributeName: styleProps.color,
|
|
157
|
+
NSBackgroundColorAttributeName: [styleProps.backgroundColor colorWithAlphaComponent:0.4],
|
|
158
|
+
} mutableCopy];
|
|
159
|
+
|
|
160
|
+
if(styleProps.decorationLine == DecorationUnderline) {
|
|
161
|
+
newAttrs[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// add a single space after the mention
|
|
165
|
+
NSString *newText = [NSString stringWithFormat:@"%@ ", text];
|
|
166
|
+
NSRange rangeToBeReplaced = [_activeMentionRange rangeValue];
|
|
167
|
+
[TextInsertionUtils replaceText:newText at:rangeToBeReplaced additionalAttributes:nullptr input:_input withSelection:YES];
|
|
168
|
+
|
|
169
|
+
// THEN, add the attributes to not apply them on the space
|
|
170
|
+
[_input->textView.textStorage addAttributes:newAttrs range:NSMakeRange(rangeToBeReplaced.location, text.length)];
|
|
171
|
+
|
|
172
|
+
// mention editing should finish
|
|
173
|
+
[self removeActiveMentionRange];
|
|
174
|
+
|
|
175
|
+
// unlock editing
|
|
176
|
+
_blockMentionEditing = NO;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
- (void)addMentionAtRange:(NSRange)range params:(MentionParams *)params {
|
|
180
|
+
_blockMentionEditing = YES;
|
|
181
|
+
|
|
182
|
+
MentionStyleProps *styleProps = [_input->config mentionStylePropsForIndicator:params.indicator];
|
|
183
|
+
|
|
184
|
+
NSMutableDictionary *newAttrs = [@{
|
|
185
|
+
MentionAttributeName: params,
|
|
186
|
+
NSForegroundColorAttributeName: styleProps.color,
|
|
187
|
+
NSUnderlineColorAttributeName: styleProps.color,
|
|
188
|
+
NSStrikethroughColorAttributeName: styleProps.color,
|
|
189
|
+
NSBackgroundColorAttributeName: [styleProps.backgroundColor colorWithAlphaComponent:0.4],
|
|
190
|
+
} mutableCopy];
|
|
191
|
+
|
|
192
|
+
if(styleProps.decorationLine == DecorationUnderline) {
|
|
193
|
+
newAttrs[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
[_input->textView.textStorage addAttributes:newAttrs range:range];
|
|
197
|
+
|
|
198
|
+
_blockMentionEditing = NO;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
- (void)startMentionWithIndicator:(NSString *)indicator {
|
|
202
|
+
NSRange currentRange = _input->textView.selectedRange;
|
|
203
|
+
|
|
204
|
+
BOOL addSpaceBefore = NO;
|
|
205
|
+
BOOL addSpaceAfter = NO;
|
|
206
|
+
|
|
207
|
+
if(currentRange.location > 0) {
|
|
208
|
+
unichar charBefore = [_input->textView.textStorage.string characterAtIndex:(currentRange.location - 1)];
|
|
209
|
+
if(![[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:charBefore]) {
|
|
210
|
+
addSpaceBefore = YES;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if(currentRange.location + currentRange.length < _input->textView.textStorage.string.length) {
|
|
215
|
+
unichar charAfter = [_input->textView.textStorage.string characterAtIndex:(currentRange.location + currentRange.length)];
|
|
216
|
+
if(![[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:charAfter]) {
|
|
217
|
+
addSpaceAfter = YES;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
NSString *finalString = [NSString stringWithFormat:@"%@%@%@",
|
|
222
|
+
addSpaceBefore ? @" " : @"",
|
|
223
|
+
indicator,
|
|
224
|
+
addSpaceAfter ? @" " : @""
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
NSRange newSelect = NSMakeRange(currentRange.location + finalString.length + (addSpaceAfter ? -1 : 0), 0);
|
|
228
|
+
|
|
229
|
+
if(currentRange.length == 0) {
|
|
230
|
+
[TextInsertionUtils insertText:finalString at:currentRange.location additionalAttributes:nullptr input:_input withSelection:NO];
|
|
231
|
+
} else {
|
|
232
|
+
[TextInsertionUtils replaceText:finalString at:currentRange additionalAttributes:nullptr input:_input withSelection:NO];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
[_input->textView reactFocus];
|
|
236
|
+
_input->textView.selectedRange = newSelect;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// handles removing no longer valid mentions
|
|
240
|
+
- (void)handleExistingMentions {
|
|
241
|
+
// unfortunately whole text needs to be checked for them
|
|
242
|
+
// checking the modified words doesn't work because mention's text can have any number of spaces, which makes one mention any number of words long
|
|
243
|
+
|
|
244
|
+
NSRange wholeText = NSMakeRange(0, _input->textView.textStorage.string.length);
|
|
245
|
+
// get menntions in ascending range.location order
|
|
246
|
+
NSArray<StylePair *> *mentions = [[self findAllOccurences:wholeText] sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
|
|
247
|
+
NSRange range1 = [((StylePair *)obj1).rangeValue rangeValue];
|
|
248
|
+
NSRange range2 = [((StylePair *)obj2).rangeValue rangeValue];
|
|
249
|
+
if(range1.location < range2.location) {
|
|
250
|
+
return NSOrderedAscending;
|
|
251
|
+
} else {
|
|
252
|
+
return NSOrderedDescending;
|
|
253
|
+
}
|
|
254
|
+
}];
|
|
255
|
+
|
|
256
|
+
// set of ranges to have their mentions removed - aren't valid anymore
|
|
257
|
+
NSMutableSet<NSValue *> *rangesToRemove = [[NSMutableSet alloc] init];
|
|
258
|
+
|
|
259
|
+
for(NSInteger i = 0; i < mentions.count; i++) {
|
|
260
|
+
StylePair *mention = mentions[i];
|
|
261
|
+
NSRange currentRange = [mention.rangeValue rangeValue];
|
|
262
|
+
NSString *currentText = ((MentionParams *)mention.styleValue).text;
|
|
263
|
+
// check locations with the previous mention if it exists - if they got merged they need to be removed
|
|
264
|
+
if(i > 0) {
|
|
265
|
+
NSRange prevRange = [((StylePair*)mentions[i-1]).rangeValue rangeValue];
|
|
266
|
+
// mentions merged - both need to go out
|
|
267
|
+
if(prevRange.location + prevRange.length == currentRange.location) {
|
|
268
|
+
[rangesToRemove addObject:[NSValue valueWithRange:prevRange]];
|
|
269
|
+
[rangesToRemove addObject:[NSValue valueWithRange:currentRange]];
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// check for text, any modifications to it makes mention invalid
|
|
275
|
+
NSString *existingText = [_input->textView.textStorage.string substringWithRange:currentRange];
|
|
276
|
+
if(![existingText isEqualToString:currentText]) {
|
|
277
|
+
[rangesToRemove addObject:[NSValue valueWithRange:currentRange]];
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
for(NSValue *value in rangesToRemove) {
|
|
282
|
+
[self removeAttributes:[value rangeValue]];
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// manages active mention range, which in turn emits proper onMention event
|
|
287
|
+
- (void)manageMentionEditing {
|
|
288
|
+
// no actions performed when block is active
|
|
289
|
+
if(_blockMentionEditing) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// we don't take longer selections into consideration
|
|
294
|
+
if(_input->textView.selectedRange.length > 0) {
|
|
295
|
+
[self removeActiveMentionRange];
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// get the current word if it exists
|
|
300
|
+
// we can be using current word only thanks to the fact that ongoing mentions are always one word (in contrast to ready, added mentions)
|
|
301
|
+
NSDictionary *currentWord = [WordsUtils getCurrentWord:_input->textView.textStorage.string range:_input->textView.selectedRange];
|
|
302
|
+
if(currentWord == nullptr) {
|
|
303
|
+
[self removeActiveMentionRange];
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// get word properties
|
|
308
|
+
NSString *wordText = (NSString *)[currentWord objectForKey:@"word"];
|
|
309
|
+
NSValue *wordRangeValue = (NSValue *)[currentWord objectForKey:@"range"];
|
|
310
|
+
if(wordText == nullptr || wordRangeValue == nullptr) {
|
|
311
|
+
[self removeActiveMentionRange];
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
NSRange wordRange = [wordRangeValue rangeValue];
|
|
315
|
+
|
|
316
|
+
// check for mentionIndicators - no sign of them means we shouldn't be editing a mention
|
|
317
|
+
unichar firstChar = [wordText characterAtIndex:0];
|
|
318
|
+
if(![[_input->config mentionIndicators] containsObject: @(firstChar)]) {
|
|
319
|
+
[self removeActiveMentionRange];
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// check for existing mentions - we don't edit them
|
|
324
|
+
if([self detectStyle:wordRange]) {
|
|
325
|
+
[self removeActiveMentionRange];
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// get conflicting style classes
|
|
330
|
+
LinkStyle* linkStyle = [_input->stylesDict objectForKey:@([LinkStyle getStyleType])];
|
|
331
|
+
InlineCodeStyle* inlineCodeStyle = [_input->stylesDict objectForKey:@([InlineCodeStyle getStyleType])];
|
|
332
|
+
if(linkStyle == nullptr || inlineCodeStyle == nullptr) {
|
|
333
|
+
[self removeActiveMentionRange];
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// if there is any sign of conflicting style classes, stop editing a mention
|
|
338
|
+
if([linkStyle anyOccurence:wordRange] || [inlineCodeStyle anyOccurence:wordRange]) {
|
|
339
|
+
[self removeActiveMentionRange];
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// everything checks out - we are indeed editing a mention
|
|
344
|
+
[self setActiveMentionRange:wordRange text:wordText];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// used to fix mentions' typing attributes
|
|
348
|
+
- (void)manageMentionTypingAttributes {
|
|
349
|
+
// same as with links, mentions' typing attributes need to be constantly removed whenever we are somewhere near
|
|
350
|
+
BOOL removeAttrs = NO;
|
|
351
|
+
MentionParams *params;
|
|
352
|
+
|
|
353
|
+
if(_input->textView.selectedRange.length == 0) {
|
|
354
|
+
// check before
|
|
355
|
+
if(_input->textView.selectedRange.location >= 1) {
|
|
356
|
+
if([self detectStyle:NSMakeRange(_input->textView.selectedRange.location - 1, 1)]) {
|
|
357
|
+
removeAttrs = YES;
|
|
358
|
+
params = [self getMentionParamsAt:_input->textView.selectedRange.location - 1];
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// check after
|
|
362
|
+
if(_input->textView.selectedRange.location < _input->textView.textStorage.length) {
|
|
363
|
+
if([self detectStyle:NSMakeRange(_input->textView.selectedRange.location, 1)]) {
|
|
364
|
+
removeAttrs = YES;
|
|
365
|
+
params = [self getMentionParamsAt:_input->textView.selectedRange.location];
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
if([self anyOccurence:_input->textView.selectedRange]) {
|
|
370
|
+
removeAttrs = YES;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if(removeAttrs) {
|
|
375
|
+
NSMutableDictionary *newTypingAttrs = [_input->textView.typingAttributes mutableCopy];
|
|
376
|
+
newTypingAttrs[NSForegroundColorAttributeName] = [_input->config primaryColor];
|
|
377
|
+
newTypingAttrs[NSUnderlineColorAttributeName] = [_input->config primaryColor];
|
|
378
|
+
newTypingAttrs[NSStrikethroughColorAttributeName] = [_input->config primaryColor];
|
|
379
|
+
[newTypingAttrs removeObjectForKey:NSBackgroundColorAttributeName];
|
|
380
|
+
if([self stylePropsWithParams:params].decorationLine == DecorationUnderline) {
|
|
381
|
+
[newTypingAttrs removeObjectForKey:NSUnderlineStyleAttributeName];
|
|
382
|
+
}
|
|
383
|
+
_input->textView.typingAttributes = newTypingAttrs;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// replacing whole input (that starts with a mention) with a manually typed letter improperly applies mention's attributes to all the following text
|
|
388
|
+
- (BOOL)handleLeadingMentionReplacement:(NSRange)range replacementText:(NSString *)text {
|
|
389
|
+
// whole textView range gets replaced with a single letter
|
|
390
|
+
if(_input->textView.textStorage.string.length > 0 && NSEqualRanges(range, NSMakeRange(0, _input->textView.textStorage.string.length)) && text.length == 1) {
|
|
391
|
+
// first character detection is enough for the removal to be done
|
|
392
|
+
if([self detectStyle:NSMakeRange(0, 1)]) {
|
|
393
|
+
[self removeAttributes:NSMakeRange(0, _input->textView.textStorage.string.length)];
|
|
394
|
+
// do the replacing manually
|
|
395
|
+
[TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:_input withSelection:YES];
|
|
396
|
+
return YES;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return NO;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// returns mention params if it exists
|
|
403
|
+
- (MentionParams *)getMentionParamsAt:(NSUInteger)location {
|
|
404
|
+
NSRange mentionRange = NSMakeRange(0, 0);
|
|
405
|
+
NSRange inputRange = NSMakeRange(0, _input->textView.textStorage.length);
|
|
406
|
+
|
|
407
|
+
// don't search at the very end of input
|
|
408
|
+
NSUInteger searchLocation = location;
|
|
409
|
+
if(searchLocation == _input->textView.textStorage.length) {
|
|
410
|
+
return nullptr;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
MentionParams *value = [_input->textView.textStorage
|
|
414
|
+
attribute:MentionAttributeName
|
|
415
|
+
atIndex:searchLocation
|
|
416
|
+
longestEffectiveRange: &mentionRange
|
|
417
|
+
inRange:inputRange
|
|
418
|
+
];
|
|
419
|
+
return value;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
- (NSValue *)getActiveMentionRange {
|
|
423
|
+
return _activeMentionRange;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// returns full range of a mention at some location
|
|
427
|
+
- (NSRange)getFullMentionRangeAt:(NSUInteger)location {
|
|
428
|
+
NSRange mentionRange = NSMakeRange(0, 0);
|
|
429
|
+
NSRange inputRange = NSMakeRange(0, _input->textView.textStorage.length);
|
|
430
|
+
|
|
431
|
+
// get the previous index if possible when at the very end of input
|
|
432
|
+
NSUInteger searchLocation = location;
|
|
433
|
+
if(searchLocation == _input->textView.textStorage.length) {
|
|
434
|
+
if(searchLocation == 0) {
|
|
435
|
+
return mentionRange;
|
|
436
|
+
} else {
|
|
437
|
+
searchLocation = searchLocation - 1;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
[_input->textView.textStorage
|
|
442
|
+
attribute:MentionAttributeName
|
|
443
|
+
atIndex:searchLocation
|
|
444
|
+
longestEffectiveRange: &mentionRange
|
|
445
|
+
inRange:inputRange
|
|
446
|
+
];
|
|
447
|
+
return mentionRange;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// MARK: - Private non-standard methods
|
|
451
|
+
|
|
452
|
+
- (MentionStyleProps *)stylePropsWithParams:(MentionParams *)params {
|
|
453
|
+
return [_input->config mentionStylePropsForIndicator:params.indicator];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// both used for setting the active mention range + indicator and fires proper onMention event
|
|
457
|
+
- (void)setActiveMentionRange:(NSRange)range text:(NSString *)text {
|
|
458
|
+
NSString *indicatorString = [NSString stringWithFormat:@"%C", [text characterAtIndex:0]];
|
|
459
|
+
NSString *textString = [text substringWithRange:NSMakeRange(1, text.length - 1)];
|
|
460
|
+
_activeMentionIndicator = indicatorString;
|
|
461
|
+
_activeMentionRange = [NSValue valueWithRange:range];
|
|
462
|
+
[_input emitOnMentionEvent:indicatorString text:textString];
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// removes stored mention range + indicator, which means that we no longer edit a mention and onMention event gets fired
|
|
466
|
+
- (void)removeActiveMentionRange {
|
|
467
|
+
if(_activeMentionIndicator != nullptr && _activeMentionRange != nullptr) {
|
|
468
|
+
NSString *indicatorCopy = [_activeMentionIndicator copy];
|
|
469
|
+
_activeMentionIndicator = nullptr;
|
|
470
|
+
_activeMentionRange = nullptr;
|
|
471
|
+
[_input emitOnMentionEvent:indicatorCopy text:nullptr];
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
@end
|
|
476
|
+
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#import "StyleHeaders.h"
|
|
2
|
+
#import "EnrichedTextInputView.h"
|
|
3
|
+
#import "FontExtension.h"
|
|
4
|
+
#import "OccurenceUtils.h"
|
|
5
|
+
#import "ParagraphsUtils.h"
|
|
6
|
+
#import "TextInsertionUtils.h"
|
|
7
|
+
|
|
8
|
+
@implementation OrderedListStyle {
|
|
9
|
+
EnrichedTextInputView *_input;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
+ (StyleType)getStyleType { return OrderedList; }
|
|
13
|
+
|
|
14
|
+
- (CGFloat)getHeadIndent {
|
|
15
|
+
// lists are drawn manually
|
|
16
|
+
// margin before marker + gap between marker and paragraph
|
|
17
|
+
return [_input->config orderedListMarginLeft] + [_input->config orderedListGapWidth];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
- (instancetype)initWithInput:(id)input {
|
|
21
|
+
self = [super init];
|
|
22
|
+
_input = (EnrichedTextInputView *)input;
|
|
23
|
+
return self;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
- (void)applyStyle:(NSRange)range {
|
|
27
|
+
BOOL isStylePresent = [self detectStyle:range];
|
|
28
|
+
if(range.length >= 1) {
|
|
29
|
+
isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
|
|
30
|
+
} else {
|
|
31
|
+
isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// we assume correct paragraph range is already given
|
|
36
|
+
- (void)addAttributes:(NSRange)range {
|
|
37
|
+
NSTextList *numberBullet = [[NSTextList alloc] initWithMarkerFormat:NSTextListMarkerDecimal options:0];
|
|
38
|
+
NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:range];
|
|
39
|
+
// if we fill empty lines with zero width spaces, we need to offset later ranges
|
|
40
|
+
NSInteger offset = 0;
|
|
41
|
+
// needed for range adjustments
|
|
42
|
+
NSRange preModificationRange = _input->textView.selectedRange;
|
|
43
|
+
|
|
44
|
+
// let's not emit some weird selection changes or text/html changes
|
|
45
|
+
_input->blockEmitting = YES;
|
|
46
|
+
|
|
47
|
+
for(NSValue *value in paragraphs) {
|
|
48
|
+
// take previous offsets into consideration
|
|
49
|
+
NSRange fixedRange = NSMakeRange([value rangeValue].location + offset, [value rangeValue].length);
|
|
50
|
+
|
|
51
|
+
// length 0 with first line, length 1 and newline with some empty lines in the middle
|
|
52
|
+
if(fixedRange.length == 0 ||
|
|
53
|
+
(fixedRange.length == 1 &&
|
|
54
|
+
[[NSCharacterSet newlineCharacterSet] characterIsMember: [_input->textView.textStorage.string characterAtIndex:fixedRange.location]])
|
|
55
|
+
) {
|
|
56
|
+
[TextInsertionUtils insertText:@"\u200B" at:fixedRange.location additionalAttributes:nullptr input:_input withSelection:NO];
|
|
57
|
+
fixedRange = NSMakeRange(fixedRange.location, fixedRange.length + 1);
|
|
58
|
+
offset += 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
[_input->textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:fixedRange options:0
|
|
62
|
+
usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
|
|
63
|
+
NSMutableParagraphStyle *pStyle = [(NSParagraphStyle *)value mutableCopy];
|
|
64
|
+
pStyle.textLists = @[numberBullet];
|
|
65
|
+
pStyle.headIndent = [self getHeadIndent];
|
|
66
|
+
pStyle.firstLineHeadIndent = [self getHeadIndent];
|
|
67
|
+
[_input->textView.textStorage addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
|
|
68
|
+
}
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// back to emitting
|
|
73
|
+
_input->blockEmitting = NO;
|
|
74
|
+
|
|
75
|
+
if(preModificationRange.length == 0) {
|
|
76
|
+
// fix selection if only one line was possibly made a list and filled with a space
|
|
77
|
+
_input->textView.selectedRange = preModificationRange;
|
|
78
|
+
} else {
|
|
79
|
+
// in other cases, fix the selection with newly made offsets
|
|
80
|
+
_input->textView.selectedRange = NSMakeRange(preModificationRange.location, preModificationRange.length + offset);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// also add typing attributes
|
|
84
|
+
NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy];
|
|
85
|
+
NSMutableParagraphStyle *pStyle = [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
|
|
86
|
+
pStyle.textLists = @[numberBullet];
|
|
87
|
+
pStyle.headIndent = [self getHeadIndent];
|
|
88
|
+
pStyle.firstLineHeadIndent = [self getHeadIndent];
|
|
89
|
+
typingAttrs[NSParagraphStyleAttributeName] = pStyle;
|
|
90
|
+
_input->textView.typingAttributes = typingAttrs;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// does pretty much the same as normal addAttributes, just need to get the range
|
|
94
|
+
- (void)addTypingAttributes {
|
|
95
|
+
[self addAttributes:_input->textView.selectedRange];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
- (void)removeAttributes:(NSRange)range {
|
|
99
|
+
NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:range];
|
|
100
|
+
|
|
101
|
+
[_input->textView.textStorage beginEditing];
|
|
102
|
+
|
|
103
|
+
for(NSValue *value in paragraphs) {
|
|
104
|
+
NSRange range = [value rangeValue];
|
|
105
|
+
[_input->textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:range options:0
|
|
106
|
+
usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
|
|
107
|
+
NSMutableParagraphStyle *pStyle = [(NSParagraphStyle *)value mutableCopy];
|
|
108
|
+
pStyle.textLists = @[];
|
|
109
|
+
pStyle.headIndent = 0;
|
|
110
|
+
pStyle.firstLineHeadIndent = 0;
|
|
111
|
+
[_input->textView.textStorage addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
|
|
112
|
+
}
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
[_input->textView.textStorage endEditing];
|
|
117
|
+
|
|
118
|
+
// also remove typing attributes
|
|
119
|
+
NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy];
|
|
120
|
+
NSMutableParagraphStyle *pStyle = [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
|
|
121
|
+
pStyle.textLists = @[];
|
|
122
|
+
pStyle.headIndent = 0;
|
|
123
|
+
pStyle.firstLineHeadIndent = 0;
|
|
124
|
+
typingAttrs[NSParagraphStyleAttributeName] = pStyle;
|
|
125
|
+
_input->textView.typingAttributes = typingAttrs;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// needed for the sake of style conflicts, needs to do exactly the same as removeAttribtues
|
|
129
|
+
- (void)removeTypingAttributes {
|
|
130
|
+
[self removeAttributes:_input->textView.selectedRange];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// removing first list point by backspacing doesn't remove typing attributes because it doesn't run textViewDidChange
|
|
134
|
+
// so we try guessing that a point should be deleted here
|
|
135
|
+
- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
|
|
136
|
+
if([self detectStyle:_input->textView.selectedRange] &&
|
|
137
|
+
NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0)) &&
|
|
138
|
+
[text isEqualToString:@""]
|
|
139
|
+
) {
|
|
140
|
+
NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:_input->textView.selectedRange];
|
|
141
|
+
[self removeAttributes:paragraphRange];
|
|
142
|
+
return YES;
|
|
143
|
+
}
|
|
144
|
+
return NO;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
- (BOOL)tryHandlingListShorcutInRange:(NSRange)range replacementText:(NSString *)text {
|
|
148
|
+
NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:range];
|
|
149
|
+
// a dot was added - check if we are both at the paragraph beginning + 1 character (which we want to be a dash)
|
|
150
|
+
if([text isEqualToString:@"."] && range.location - 1 == paragraphRange.location) {
|
|
151
|
+
unichar charBefore = [_input->textView.textStorage.string characterAtIndex:range.location - 1];
|
|
152
|
+
if(charBefore == '1') {
|
|
153
|
+
// we got a match - add a list if possible
|
|
154
|
+
if([_input handleStyleBlocksAndConflicts:[[self class] getStyleType] range:paragraphRange]) {
|
|
155
|
+
// don't emit some html updates during the replacing
|
|
156
|
+
BOOL prevEmitHtml = _input->emitHtml;
|
|
157
|
+
if(prevEmitHtml) {
|
|
158
|
+
_input->emitHtml = NO;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// remove the number
|
|
162
|
+
[TextInsertionUtils replaceText:@"" at:NSMakeRange(paragraphRange.location, 1) additionalAttributes:nullptr input:_input withSelection:YES];
|
|
163
|
+
|
|
164
|
+
if(prevEmitHtml) {
|
|
165
|
+
_input->emitHtml = YES;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// add attributes on the paragraph
|
|
169
|
+
[self addAttributes:NSMakeRange(paragraphRange.location, paragraphRange.length - 1)];
|
|
170
|
+
return YES;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return NO;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
- (BOOL)styleCondition:(id _Nullable)value :(NSRange)range {
|
|
178
|
+
NSParagraphStyle *paragraph = (NSParagraphStyle *)value;
|
|
179
|
+
return paragraph != nullptr && paragraph.textLists.count == 1 && paragraph.textLists.firstObject.markerFormat == NSTextListMarkerDecimal;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
- (BOOL)detectStyle:(NSRange)range {
|
|
183
|
+
if(range.length >= 1) {
|
|
184
|
+
return [OccurenceUtils detect:NSParagraphStyleAttributeName withInput:_input inRange:range
|
|
185
|
+
withCondition: ^BOOL(id _Nullable value, NSRange range) {
|
|
186
|
+
return [self styleCondition:value :range];
|
|
187
|
+
}
|
|
188
|
+
];
|
|
189
|
+
} else {
|
|
190
|
+
NSInteger searchLocation = range.location;
|
|
191
|
+
if(searchLocation == _input->textView.textStorage.length) {
|
|
192
|
+
NSParagraphStyle *pStyle = _input->textView.typingAttributes[NSParagraphStyleAttributeName];
|
|
193
|
+
return [self styleCondition:pStyle :NSMakeRange(0, 0)];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
NSRange paragraphRange = NSMakeRange(0, 0);
|
|
197
|
+
NSRange inputRange = NSMakeRange(0, _input->textView.textStorage.length);
|
|
198
|
+
NSParagraphStyle *paragraph = [_input->textView.textStorage
|
|
199
|
+
attribute:NSParagraphStyleAttributeName
|
|
200
|
+
atIndex:searchLocation
|
|
201
|
+
longestEffectiveRange: ¶graphRange
|
|
202
|
+
inRange:inputRange
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
return [self styleCondition:paragraph :NSMakeRange(0, 0)];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
- (BOOL)anyOccurence:(NSRange)range {
|
|
210
|
+
return [OccurenceUtils any:NSParagraphStyleAttributeName withInput:_input inRange:range
|
|
211
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
212
|
+
return [self styleCondition:value :range];
|
|
213
|
+
}
|
|
214
|
+
];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
- (NSArray<StylePair *> *_Nullable)findAllOccurences:(NSRange)range {
|
|
218
|
+
return [OccurenceUtils all:NSParagraphStyleAttributeName withInput:_input inRange:range
|
|
219
|
+
withCondition:^BOOL(id _Nullable value, NSRange range) {
|
|
220
|
+
return [self styleCondition:value :range];
|
|
221
|
+
}
|
|
222
|
+
];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
@end
|