diff --git a/apps/example/ios/Podfile.lock b/apps/example/ios/Podfile.lock
index 2abfd0c6b..0caf0c630 100644
--- a/apps/example/ios/Podfile.lock
+++ b/apps/example/ios/Podfile.lock
@@ -2763,7 +2763,7 @@ SPEC CHECKSUMS:
FBLazyVector: a293a88992c4c33f0aee184acab0b64a08ff9458
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
- hermes-engine: 0552e8a1c46c773b6888e9fe198c2272cc097193
+ hermes-engine: b45e3b4b9d7f8227a6c11c8342f81742829b8af8
RCT-Folly: 59ec0ac1f2f39672a0c6e6cecdd39383b764646f
RCTDeprecation: 2b70c6e3abe00396cefd8913efbf6a2db01a2b36
RCTRequired: f3540eee8094231581d40c5c6d41b0f170237a81
diff --git a/ios/EnrichedTextInputView.h b/ios/EnrichedTextInputView.h
index 346f1d36c..4beb8ff94 100644
--- a/ios/EnrichedTextInputView.h
+++ b/ios/EnrichedTextInputView.h
@@ -1,4 +1,5 @@
#pragma once
+#import "AttributesManager.h"
#import "BaseStyleProtocol.h"
#import "InputConfig.h"
#import "InputParser.h"
@@ -22,14 +23,18 @@ NS_ASSUME_NONNULL_BEGIN
InputConfig *config;
@public
InputParser *parser;
+@public
+ AttributesManager *attributesManager;
@public
NSMutableDictionary *defaultTypingAttributes;
@public
- NSDictionary> *stylesDict;
+ NSDictionary *stylesDict;
NSDictionary *> *conflictingStyles;
NSMutableDictionary *> *blockingStyles;
@public
BOOL blockEmitting;
+@public
+ NSValue *dotReplacementRange;
}
- (void)emitOnLinkDetectedEvent:(NSString *)text
url:(NSString *)url
diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm
index 6601ce1da..682e26804 100644
--- a/ios/EnrichedTextInputView.mm
+++ b/ios/EnrichedTextInputView.mm
@@ -1,5 +1,6 @@
#import "EnrichedTextInputView.h"
#import "CoreText/CoreText.h"
+#import "DotReplacementUtils.h"
#import "LayoutManagerExtension.h"
#import "ParagraphAttributesUtils.h"
#import "RCTFabricComponentsPlugins.h"
@@ -26,9 +27,9 @@
using namespace facebook::react;
-@interface EnrichedTextInputView ()
+@interface EnrichedTextInputView () <
+ RCTEnrichedTextInputViewViewProtocol, UITextViewDelegate,
+ UIGestureRecognizerDelegate, NSTextStorageDelegate, NSObject>
@end
@@ -92,165 +93,164 @@ - (void)setDefaults {
blockEmitting = NO;
_emitFocusBlur = YES;
_emitTextChange = NO;
+ dotReplacementRange = nullptr;
defaultTypingAttributes =
[[NSMutableDictionary alloc] init];
stylesDict = @{
- @([BoldStyle getStyleType]) : [[BoldStyle alloc] initWithInput:self],
- @([ItalicStyle getStyleType]) : [[ItalicStyle alloc] initWithInput:self],
- @([UnderlineStyle getStyleType]) :
- [[UnderlineStyle alloc] initWithInput:self],
- @([StrikethroughStyle getStyleType]) :
+ @([BoldStyle getType]) : [[BoldStyle alloc] initWithInput:self],
+ @([ItalicStyle getType]) : [[ItalicStyle alloc] initWithInput:self],
+ @([UnderlineStyle getType]) : [[UnderlineStyle alloc] initWithInput:self],
+ @([StrikethroughStyle getType]) :
[[StrikethroughStyle alloc] initWithInput:self],
- @([InlineCodeStyle getStyleType]) :
- [[InlineCodeStyle alloc] initWithInput:self],
- @([LinkStyle getStyleType]) : [[LinkStyle alloc] initWithInput:self],
- @([MentionStyle getStyleType]) : [[MentionStyle alloc] initWithInput:self],
- @([H1Style getStyleType]) : [[H1Style alloc] initWithInput:self],
- @([H2Style getStyleType]) : [[H2Style alloc] initWithInput:self],
- @([H3Style getStyleType]) : [[H3Style alloc] initWithInput:self],
- @([H4Style getStyleType]) : [[H4Style alloc] initWithInput:self],
- @([H5Style getStyleType]) : [[H5Style alloc] initWithInput:self],
- @([H6Style getStyleType]) : [[H6Style alloc] initWithInput:self],
- @([UnorderedListStyle getStyleType]) :
+ @([InlineCodeStyle getType]) : [[InlineCodeStyle alloc] initWithInput:self],
+ // @([LinkStyle getStyleType]) : [[LinkStyle alloc] initWithInput:self],
+ // @([MentionStyle getStyleType]) : [[MentionStyle alloc]
+ // initWithInput:self],
+ @([H1Style getType]) : [[H1Style alloc] initWithInput:self],
+ @([H2Style getType]) : [[H2Style alloc] initWithInput:self],
+ @([H3Style getType]) : [[H3Style alloc] initWithInput:self],
+ @([H4Style getType]) : [[H4Style alloc] initWithInput:self],
+ @([H5Style getType]) : [[H5Style alloc] initWithInput:self],
+ @([H6Style getType]) : [[H6Style alloc] initWithInput:self],
+ @([UnorderedListStyle getType]) :
[[UnorderedListStyle alloc] initWithInput:self],
- @([OrderedListStyle getStyleType]) :
+ @([OrderedListStyle getType]) :
[[OrderedListStyle alloc] initWithInput:self],
- @([CheckboxListStyle getStyleType]) :
+ @([CheckboxListStyle getType]) :
[[CheckboxListStyle alloc] initWithInput:self],
- @([BlockQuoteStyle getStyleType]) :
- [[BlockQuoteStyle alloc] initWithInput:self],
- @([CodeBlockStyle getStyleType]) :
- [[CodeBlockStyle alloc] initWithInput:self],
- @([ImageStyle getStyleType]) : [[ImageStyle alloc] initWithInput:self]
+ @([BlockQuoteStyle getType]) : [[BlockQuoteStyle alloc] initWithInput:self],
+ @([CodeBlockStyle getType]) : [[CodeBlockStyle alloc] initWithInput:self],
+ // @([ImageStyle getStyleType]) : [[ImageStyle alloc] initWithInput:self]
};
conflictingStyles = @{
- @([BoldStyle getStyleType]) : @[],
- @([ItalicStyle getStyleType]) : @[],
- @([UnderlineStyle getStyleType]) : @[],
- @([StrikethroughStyle getStyleType]) : @[],
- @([InlineCodeStyle getStyleType]) :
- @[ @([LinkStyle getStyleType]), @([MentionStyle getStyleType]) ],
- @([LinkStyle getStyleType]) : @[
- @([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType]),
- @([MentionStyle getStyleType])
+ @([BoldStyle getType]) : @[],
+ @([ItalicStyle getType]) : @[],
+ @([UnderlineStyle getType]) : @[],
+ @([StrikethroughStyle getType]) : @[],
+ @([InlineCodeStyle getType]) : @[
+ // @([LinkStyle getStyleType]), @([MentionStyle getStyleType])
],
- @([MentionStyle getStyleType]) :
- @[ @([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType]) ],
- @([H1Style getStyleType]) : @[
- @([H2Style getStyleType]), @([H3Style getStyleType]),
- @([H4Style getStyleType]), @([H5Style getStyleType]),
- @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]),
- @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]),
- @([CodeBlockStyle getStyleType]), @([CheckboxListStyle getStyleType])
+ // @([LinkStyle getStyleType]) : @[
+ // @([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType]),
+ // @([MentionStyle getStyleType])
+ // ],
+ // @([MentionStyle getStyleType]) :
+ // @[ @([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType])
+ // ],
+ @([H1Style getType]) : @[
+ @([H2Style getType]), @([H3Style getType]), @([H4Style getType]),
+ @([H5Style getType]), @([H6Style getType]),
+ @([UnorderedListStyle getType]), @([OrderedListStyle getType]),
+ @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]),
+ @([CheckboxListStyle getType])
],
- @([H2Style getStyleType]) : @[
- @([H1Style getStyleType]), @([H3Style getStyleType]),
- @([H4Style getStyleType]), @([H5Style getStyleType]),
- @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]),
- @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]),
- @([CodeBlockStyle getStyleType]), @([CheckboxListStyle getStyleType])
+ @([H2Style getType]) : @[
+ @([H1Style getType]), @([H3Style getType]), @([H4Style getType]),
+ @([H5Style getType]), @([H6Style getType]),
+ @([UnorderedListStyle getType]), @([OrderedListStyle getType]),
+ @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]),
+ @([CheckboxListStyle getType])
],
- @([H3Style getStyleType]) : @[
- @([H1Style getStyleType]), @([H2Style getStyleType]),
- @([H4Style getStyleType]), @([H5Style getStyleType]),
- @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]),
- @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]),
- @([CodeBlockStyle getStyleType]), @([CheckboxListStyle getStyleType])
+ @([H3Style getType]) : @[
+ @([H1Style getType]), @([H2Style getType]), @([H4Style getType]),
+ @([H5Style getType]), @([H6Style getType]),
+ @([UnorderedListStyle getType]), @([OrderedListStyle getType]),
+ @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]),
+ @([CheckboxListStyle getType])
],
- @([H4Style getStyleType]) : @[
- @([H1Style getStyleType]), @([H2Style getStyleType]),
- @([H3Style getStyleType]), @([H5Style getStyleType]),
- @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]),
- @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]),
- @([CodeBlockStyle getStyleType]), @([CheckboxListStyle getStyleType])
+ @([H4Style getType]) : @[
+ @([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
+ @([H5Style getType]), @([H6Style getType]),
+ @([UnorderedListStyle getType]), @([OrderedListStyle getType]),
+ @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]),
+ @([CheckboxListStyle getType])
],
- @([H5Style getStyleType]) : @[
- @([H1Style getStyleType]), @([H2Style getStyleType]),
- @([H3Style getStyleType]), @([H4Style getStyleType]),
- @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]),
- @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]),
- @([CodeBlockStyle getStyleType]), @([CheckboxListStyle getStyleType])
+ @([H5Style getType]) : @[
+ @([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
+ @([H4Style getType]), @([H6Style getType]),
+ @([UnorderedListStyle getType]), @([OrderedListStyle getType]),
+ @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]),
+ @([CheckboxListStyle getType])
],
- @([H6Style getStyleType]) : @[
- @([H1Style getStyleType]), @([H2Style getStyleType]),
- @([H3Style getStyleType]), @([H4Style getStyleType]),
- @([H5Style getStyleType]), @([UnorderedListStyle getStyleType]),
- @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]),
- @([CodeBlockStyle getStyleType]), @([CheckboxListStyle getStyleType])
+ @([H6Style getType]) : @[
+ @([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
+ @([H4Style getType]), @([H5Style getType]),
+ @([UnorderedListStyle getType]), @([OrderedListStyle getType]),
+ @([BlockQuoteStyle getType]), @([CodeBlockStyle getType]),
+ @([CheckboxListStyle getType])
],
- @([UnorderedListStyle getStyleType]) : @[
- @([H1Style getStyleType]), @([H2Style getStyleType]),
- @([H3Style getStyleType]), @([H4Style getStyleType]),
- @([H5Style getStyleType]), @([H6Style getStyleType]),
- @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]),
- @([CodeBlockStyle getStyleType]), @([CheckboxListStyle getStyleType])
+ @([UnorderedListStyle getType]) : @[
+ @([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
+ @([H4Style getType]), @([H5Style getType]), @([H6Style getType]),
+ @([OrderedListStyle getType]), @([BlockQuoteStyle getType]),
+ @([CodeBlockStyle getType]), @([CheckboxListStyle getType])
],
- @([OrderedListStyle getStyleType]) : @[
- @([H1Style getStyleType]), @([H2Style getStyleType]),
- @([H3Style getStyleType]), @([H4Style getStyleType]),
- @([H5Style getStyleType]), @([H6Style getStyleType]),
- @([UnorderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]),
- @([CodeBlockStyle getStyleType]), @([CheckboxListStyle getStyleType])
+ @([OrderedListStyle getType]) : @[
+ @([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
+ @([H4Style getType]), @([H5Style getType]), @([H6Style getType]),
+ @([UnorderedListStyle getType]), @([BlockQuoteStyle getType]),
+ @([CodeBlockStyle getType]), @([CheckboxListStyle getType])
],
- @([CheckboxListStyle getStyleType]) : @[
- @([H1Style getStyleType]), @([H2Style getStyleType]),
- @([H3Style getStyleType]), @([H4Style getStyleType]),
- @([H5Style getStyleType]), @([H6Style getStyleType]),
- @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]),
- @([BlockQuoteStyle getStyleType]), @([CodeBlockStyle getStyleType])
+ @([CheckboxListStyle getType]) : @[
+ @([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
+ @([H4Style getType]), @([H5Style getType]), @([H6Style getType]),
+ @([UnorderedListStyle getType]), @([OrderedListStyle getType]),
+ @([BlockQuoteStyle getType]), @([CodeBlockStyle getType])
],
- @([BlockQuoteStyle getStyleType]) : @[
- @([H1Style getStyleType]), @([H2Style getStyleType]),
- @([H3Style getStyleType]), @([H4Style getStyleType]),
- @([H5Style getStyleType]), @([H6Style getStyleType]),
- @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]),
- @([CodeBlockStyle getStyleType]), @([CheckboxListStyle getStyleType])
+ @([BlockQuoteStyle getType]) : @[
+ @([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
+ @([H4Style getType]), @([H5Style getType]), @([H6Style getType]),
+ @([UnorderedListStyle getType]), @([OrderedListStyle getType]),
+ @([CodeBlockStyle getType]), @([CheckboxListStyle getType])
],
- @([CodeBlockStyle getStyleType]) : @[
- @([H1Style getStyleType]), @([H2Style getStyleType]),
- @([H3Style getStyleType]), @([H4Style getStyleType]),
- @([H5Style getStyleType]), @([H6Style getStyleType]),
- @([BoldStyle getStyleType]), @([ItalicStyle getStyleType]),
- @([UnderlineStyle getStyleType]), @([StrikethroughStyle getStyleType]),
- @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]),
- @([BlockQuoteStyle getStyleType]), @([InlineCodeStyle getStyleType]),
- @([MentionStyle getStyleType]), @([LinkStyle getStyleType]),
- @([CheckboxListStyle getStyleType])
+ @([CodeBlockStyle getType]) : @[
+ @([H1Style getType]), @([H2Style getType]), @([H3Style getType]),
+ @([H4Style getType]), @([H5Style getType]), @([H6Style getType]),
+ @([BoldStyle getType]), @([UnderlineStyle getType]),
+ @([ItalicStyle getType]), @([StrikethroughStyle getType]),
+ @([UnorderedListStyle getType]), @([OrderedListStyle getType]),
+ @([BlockQuoteStyle getType]), @([InlineCodeStyle getType]),
+ // @([MentionStyle getStyleType]), @([LinkStyle getStyleType]),
+ @([CheckboxListStyle getType])
],
- @([ImageStyle getStyleType]) :
- @[ @([LinkStyle getStyleType]), @([MentionStyle getStyleType]) ]
+ // @([ImageStyle getStyleType]) :
+ // @[ @([LinkStyle getStyleType]), @([MentionStyle getStyleType]) ]
};
blockingStyles = [@{
- @([BoldStyle getStyleType]) : @[ @([CodeBlockStyle getStyleType]) ],
- @([ItalicStyle getStyleType]) : @[ @([CodeBlockStyle getStyleType]) ],
- @([UnderlineStyle getStyleType]) : @[ @([CodeBlockStyle getStyleType]) ],
- @([StrikethroughStyle getStyleType]) :
- @[ @([CodeBlockStyle getStyleType]) ],
- @([InlineCodeStyle getStyleType]) :
- @[ @([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType]) ],
- @([LinkStyle getStyleType]) :
- @[ @([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType]) ],
- @([MentionStyle getStyleType]) :
- @[ @([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType]) ],
- @([H1Style getStyleType]) : @[],
- @([H2Style getStyleType]) : @[],
- @([H3Style getStyleType]) : @[],
- @([H4Style getStyleType]) : @[],
- @([H5Style getStyleType]) : @[],
- @([H6Style getStyleType]) : @[],
- @([UnorderedListStyle getStyleType]) : @[],
- @([OrderedListStyle getStyleType]) : @[],
- @([CheckboxListStyle getStyleType]) : @[],
- @([BlockQuoteStyle getStyleType]) : @[],
- @([CodeBlockStyle getStyleType]) : @[],
- @([ImageStyle getStyleType]) : @[ @([InlineCodeStyle getStyleType]) ]
+ @([BoldStyle getType]) : @[ @([CodeBlockStyle getType]) ],
+ @([ItalicStyle getType]) : @[ @([CodeBlockStyle getType]) ],
+ @([UnderlineStyle getType]) : @[ @([CodeBlockStyle getType]) ],
+ @([StrikethroughStyle getType]) : @[ @([CodeBlockStyle getType]) ],
+ @([InlineCodeStyle getType]) : @[
+ @([CodeBlockStyle getType]),
+ // @([ImageStyle getStyleType])
+ ],
+ // @([LinkStyle getStyleType]) :
+ // @[ @([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType])
+ // ],
+ // @([MentionStyle getStyleType]) :
+ // @[ @([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType])
+ // ],
+ @([H1Style getType]) : @[],
+ @([H2Style getType]) : @[],
+ @([H3Style getType]) : @[],
+ @([H4Style getType]) : @[],
+ @([H5Style getType]) : @[],
+ @([H6Style getType]) : @[],
+ @([UnorderedListStyle getType]) : @[],
+ @([OrderedListStyle getType]) : @[],
+ @([CheckboxListStyle getType]) : @[],
+ @([BlockQuoteStyle getType]) : @[],
+ @([CodeBlockStyle getType]) : @[],
+ // @([ImageStyle getStyleType]) : @[ @([InlineCodeStyle getStyleType]) ]
} mutableCopy];
parser = [[InputParser alloc] initWithInput:self];
+ attributesManager = [[AttributesManager alloc] initWithInput:self];
}
- (void)setupTextView {
@@ -261,6 +261,8 @@ - (void)setupTextView {
textView.delegate = self;
textView.input = self;
textView.layoutManager.input = self;
+ textView.textStorage.delegate = self;
+
textView.autoresizingMask =
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
textView.adjustsFontForContentSizeCategory = YES;
@@ -901,19 +903,19 @@ - (void)tryUpdatingActiveStyles {
// separately
NSMutableSet *newBlockedStyles = [_blockedStyles mutableCopy];
- // data for onLinkDetected event
- LinkData *detectedLinkData;
- NSRange detectedLinkRange = NSMakeRange(0, 0);
-
- // data for onMentionDetected event
- MentionParams *detectedMentionParams;
- NSRange detectedMentionRange = NSMakeRange(0, 0);
+ // // data for onLinkDetected event
+ // LinkData *detectedLinkData;
+ // NSRange detectedLinkRange = NSMakeRange(0, 0);
+ //
+ // // data for onMentionDetected event
+ // MentionParams *detectedMentionParams;
+ // NSRange detectedMentionRange = NSMakeRange(0, 0);
for (NSNumber *type in stylesDict) {
- id style = stylesDict[type];
+ StyleBase *style = stylesDict[type];
BOOL wasActive = [newActiveStyles containsObject:type];
- BOOL isActive = [style detectStyle:textView.selectedRange];
+ BOOL isActive = [style detect:textView.selectedRange];
BOOL wasBlocked = [newBlockedStyles containsObject:type];
BOOL isBlocked = [self isStyle:(StyleType)[type integerValue]
@@ -938,65 +940,69 @@ - (void)tryUpdatingActiveStyles {
}
}
- // onLinkDetected event
- if (isActive && [type intValue] == [LinkStyle getStyleType]) {
- // get the link data
- LinkData *candidateLinkData;
- NSRange candidateLinkRange = NSMakeRange(0, 0);
- LinkStyle *linkStyleClass =
- (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
- if (linkStyleClass != nullptr) {
- candidateLinkData =
- [linkStyleClass getLinkDataAt:textView.selectedRange.location];
- candidateLinkRange =
- [linkStyleClass getFullLinkRangeAt:textView.selectedRange.location];
- }
-
- if (wasActive == NO) {
- // we changed selection from non-link to a link
- detectedLinkData = candidateLinkData;
- detectedLinkRange = candidateLinkRange;
- } else if (![_recentlyActiveLinkData.url
- isEqualToString:candidateLinkData.url] ||
- ![_recentlyActiveLinkData.text
- isEqualToString:candidateLinkData.text] ||
- !NSEqualRanges(_recentlyActiveLinkRange, candidateLinkRange)) {
- // we changed selection from one link to the other or modified current
- // link's text
- detectedLinkData = candidateLinkData;
- detectedLinkRange = candidateLinkRange;
- }
- }
-
- // onMentionDetected event
- if (isActive && [type intValue] == [MentionStyle getStyleType]) {
- // get mention data
- MentionParams *candidateMentionParams;
- NSRange candidateMentionRange = NSMakeRange(0, 0);
- MentionStyle *mentionStyleClass =
- (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
- if (mentionStyleClass != nullptr) {
- candidateMentionParams = [mentionStyleClass
- getMentionParamsAt:textView.selectedRange.location];
- candidateMentionRange = [mentionStyleClass
- getFullMentionRangeAt:textView.selectedRange.location];
- }
-
- if (wasActive == NO) {
- // selection was changed from a non-mention to a mention
- detectedMentionParams = candidateMentionParams;
- detectedMentionRange = candidateMentionRange;
- } else if (![_recentlyActiveMentionParams.text
- isEqualToString:candidateMentionParams.text] ||
- ![_recentlyActiveMentionParams.attributes
- isEqualToString:candidateMentionParams.attributes] ||
- !NSEqualRanges(_recentlyActiveMentionRange,
- candidateMentionRange)) {
- // selection changed from one mention to another
- detectedMentionParams = candidateMentionParams;
- detectedMentionRange = candidateMentionRange;
- }
- }
+ // // onLinkDetected event
+ // if (isActive && [type intValue] == [LinkStyle getStyleType]) {
+ // // get the link data
+ // LinkData *candidateLinkData;
+ // NSRange candidateLinkRange = NSMakeRange(0, 0);
+ // LinkStyle *linkStyleClass =
+ // (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
+ // if (linkStyleClass != nullptr) {
+ // candidateLinkData =
+ // [linkStyleClass
+ // getLinkDataAt:textView.selectedRange.location];
+ // candidateLinkRange =
+ // [linkStyleClass
+ // getFullLinkRangeAt:textView.selectedRange.location];
+ // }
+ //
+ // if (wasActive == NO) {
+ // // we changed selection from non-link to a link
+ // detectedLinkData = candidateLinkData;
+ // detectedLinkRange = candidateLinkRange;
+ // } else if (![_recentlyActiveLinkData.url
+ // isEqualToString:candidateLinkData.url] ||
+ // ![_recentlyActiveLinkData.text
+ // isEqualToString:candidateLinkData.text] ||
+ // !NSEqualRanges(_recentlyActiveLinkRange,
+ // candidateLinkRange)) {
+ // // we changed selection from one link to the other or modified
+ // current
+ // // link's text
+ // detectedLinkData = candidateLinkData;
+ // detectedLinkRange = candidateLinkRange;
+ // }
+ // }
+ //
+ // // onMentionDetected event
+ // if (isActive && [type intValue] == [MentionStyle getStyleType]) {
+ // // get mention data
+ // MentionParams *candidateMentionParams;
+ // NSRange candidateMentionRange = NSMakeRange(0, 0);
+ // MentionStyle *mentionStyleClass =
+ // (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
+ // if (mentionStyleClass != nullptr) {
+ // candidateMentionParams = [mentionStyleClass
+ // getMentionParamsAt:textView.selectedRange.location];
+ // candidateMentionRange = [mentionStyleClass
+ // getFullMentionRangeAt:textView.selectedRange.location];
+ // }
+ //
+ // if (wasActive == NO) {
+ // // selection was changed from a non-mention to a mention
+ // detectedMentionParams = candidateMentionParams;
+ // detectedMentionRange = candidateMentionRange;
+ // } else if (![_recentlyActiveMentionParams.text
+ // isEqualToString:candidateMentionParams.text] ||
+ // ![_recentlyActiveMentionParams.attributes
+ // isEqualToString:candidateMentionParams.attributes] ||
+ // !NSEqualRanges(_recentlyActiveMentionRange,
+ // candidateMentionRange)) {
+ // // selection changed from one mention to another
+ // detectedMentionParams = candidateMentionParams;
+ // detectedMentionRange = candidateMentionRange;
+ // }
+ // }
}
if (updateNeeded) {
@@ -1007,71 +1013,69 @@ - (void)tryUpdatingActiveStyles {
_blockedStyles = newBlockedStyles;
emitter->onChangeStateDeprecated(
- {.isBold = [self isStyleActive:[BoldStyle getStyleType]],
- .isItalic = [self isStyleActive:[ItalicStyle getStyleType]],
- .isUnderline = [self isStyleActive:[UnderlineStyle getStyleType]],
- .isStrikeThrough =
- [self isStyleActive:[StrikethroughStyle getStyleType]],
- .isInlineCode = [self isStyleActive:[InlineCodeStyle getStyleType]],
- .isLink = [self isStyleActive:[LinkStyle getStyleType]],
- .isMention = [self isStyleActive:[MentionStyle getStyleType]],
- .isH1 = [self isStyleActive:[H1Style getStyleType]],
- .isH2 = [self isStyleActive:[H2Style getStyleType]],
- .isH3 = [self isStyleActive:[H3Style getStyleType]],
- .isH4 = [self isStyleActive:[H4Style getStyleType]],
- .isH5 = [self isStyleActive:[H5Style getStyleType]],
- .isH6 = [self isStyleActive:[H6Style getStyleType]],
- .isUnorderedList =
- [self isStyleActive:[UnorderedListStyle getStyleType]],
- .isOrderedList =
- [self isStyleActive:[OrderedListStyle getStyleType]],
- .isBlockQuote = [self isStyleActive:[BlockQuoteStyle getStyleType]],
- .isCodeBlock = [self isStyleActive:[CodeBlockStyle getStyleType]],
- .isImage = [self isStyleActive:[ImageStyle getStyleType]],
- .isCheckboxList =
- [self isStyleActive:[CheckboxListStyle getStyleType]]});
+ {.isBold = [self isStyleActive:[BoldStyle getType]],
+ .isItalic = [self isStyleActive:[ItalicStyle getType]],
+ .isUnderline = [self isStyleActive:[UnderlineStyle getType]],
+ .isStrikeThrough = [self isStyleActive:[StrikethroughStyle getType]],
+ .isInlineCode = [self isStyleActive:[InlineCodeStyle getType]],
+ // .isLink = [self isStyleActive:[LinkStyle getStyleType]],
+ // .isMention = [self isStyleActive:[MentionStyle
+ // getStyleType]],
+ .isH1 = [self isStyleActive:[H1Style getType]],
+ .isH2 = [self isStyleActive:[H2Style getType]],
+ .isH3 = [self isStyleActive:[H3Style getType]],
+ .isH4 = [self isStyleActive:[H4Style getType]],
+ .isH5 = [self isStyleActive:[H5Style getType]],
+ .isH6 = [self isStyleActive:[H6Style getType]],
+ .isUnorderedList = [self isStyleActive:[UnorderedListStyle getType]],
+ .isOrderedList = [self isStyleActive:[OrderedListStyle getType]],
+ .isBlockQuote = [self isStyleActive:[BlockQuoteStyle getType]],
+ .isCodeBlock = [self isStyleActive:[CodeBlockStyle getType]],
+ // .isImage = [self isStyleActive:[ImageStyle
+ // getStyleType]],
+ .isCheckboxList = [self isStyleActive:[CheckboxListStyle getType]]});
emitter->onChangeState(
- {.bold = GET_STYLE_STATE([BoldStyle getStyleType]),
- .italic = GET_STYLE_STATE([ItalicStyle getStyleType]),
- .underline = GET_STYLE_STATE([UnderlineStyle getStyleType]),
- .strikeThrough = GET_STYLE_STATE([StrikethroughStyle getStyleType]),
- .inlineCode = GET_STYLE_STATE([InlineCodeStyle getStyleType]),
- .link = GET_STYLE_STATE([LinkStyle getStyleType]),
- .mention = GET_STYLE_STATE([MentionStyle getStyleType]),
- .h1 = GET_STYLE_STATE([H1Style getStyleType]),
- .h2 = GET_STYLE_STATE([H2Style getStyleType]),
- .h3 = GET_STYLE_STATE([H3Style getStyleType]),
- .h4 = GET_STYLE_STATE([H4Style getStyleType]),
- .h5 = GET_STYLE_STATE([H5Style getStyleType]),
- .h6 = GET_STYLE_STATE([H6Style getStyleType]),
- .unorderedList = GET_STYLE_STATE([UnorderedListStyle getStyleType]),
- .orderedList = GET_STYLE_STATE([OrderedListStyle getStyleType]),
- .blockQuote = GET_STYLE_STATE([BlockQuoteStyle getStyleType]),
- .codeBlock = GET_STYLE_STATE([CodeBlockStyle getStyleType]),
- .image = GET_STYLE_STATE([ImageStyle getStyleType]),
- .checkboxList = GET_STYLE_STATE([CheckboxListStyle getStyleType])});
+ {.bold = GET_STYLE_STATE([BoldStyle getType]),
+ .italic = GET_STYLE_STATE([ItalicStyle getType]),
+ .underline = GET_STYLE_STATE([UnderlineStyle getType]),
+ .strikeThrough = GET_STYLE_STATE([StrikethroughStyle getType]),
+ .inlineCode = GET_STYLE_STATE([InlineCodeStyle getType]),
+ // .link = GET_STYLE_STATE([LinkStyle getStyleType]),
+ // .mention = GET_STYLE_STATE([MentionStyle getStyleType]),
+ .h1 = GET_STYLE_STATE([H1Style getType]),
+ .h2 = GET_STYLE_STATE([H2Style getType]),
+ .h3 = GET_STYLE_STATE([H3Style getType]),
+ .h4 = GET_STYLE_STATE([H4Style getType]),
+ .h5 = GET_STYLE_STATE([H5Style getType]),
+ .h6 = GET_STYLE_STATE([H6Style getType]),
+ .unorderedList = GET_STYLE_STATE([UnorderedListStyle getType]),
+ .orderedList = GET_STYLE_STATE([OrderedListStyle getType]),
+ .blockQuote = GET_STYLE_STATE([BlockQuoteStyle getType]),
+ .codeBlock = GET_STYLE_STATE([CodeBlockStyle getType]),
+ // .image = GET_STYLE_STATE([ImageStyle getStyleType]),
+ .checkboxList = GET_STYLE_STATE([CheckboxListStyle getType])});
}
}
- if (detectedLinkData != nullptr) {
- // emit onLinkeDetected event
- [self emitOnLinkDetectedEvent:detectedLinkData.text
- url:detectedLinkData.url
- range:detectedLinkRange];
- }
-
- if (detectedMentionParams != nullptr) {
- // emit onMentionDetected event
- [self emitOnMentionDetectedEvent:detectedMentionParams.text
- indicator:detectedMentionParams.indicator
- attributes:detectedMentionParams.attributes];
-
- _recentlyActiveMentionParams = detectedMentionParams;
- _recentlyActiveMentionRange = detectedMentionRange;
- }
-
- // emit onChangeHtml event if needed
- [self tryEmittingOnChangeHtmlEvent];
+ // if (detectedLinkData != nullptr) {
+ // // emit onLinkeDetected event
+ // [self emitOnLinkDetectedEvent:detectedLinkData.text
+ // url:detectedLinkData.url
+ // range:detectedLinkRange];
+ // }
+ //
+ // if (detectedMentionParams != nullptr) {
+ // // emit onMentionDetected event
+ // [self emitOnMentionDetectedEvent:detectedMentionParams.text
+ // indicator:detectedMentionParams.indicator
+ // attributes:detectedMentionParams.attributes];
+ //
+ // _recentlyActiveMentionParams = detectedMentionParams;
+ // _recentlyActiveMentionRange = detectedMentionRange;
+ // }
+ //
+ // // emit onChangeHtml event if needed
+ // [self tryEmittingOnChangeHtmlEvent];
}
- (bool)isStyleActive:(StyleType)type {
@@ -1117,70 +1121,76 @@ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
[self focus];
} else if ([commandName isEqualToString:@"blur"]) {
[self blur];
- } else if ([commandName isEqualToString:@"setValue"]) {
- NSString *value = (NSString *)args[0];
- [self setValue:value];
- } else if ([commandName isEqualToString:@"toggleBold"]) {
- [self toggleRegularStyle:[BoldStyle getStyleType]];
+ }
+ // else if ([commandName isEqualToString:@"setValue"]) {
+ // NSString *value = (NSString *)args[0];
+ // [self setValue:value];
+ // }
+ else if ([commandName isEqualToString:@"toggleBold"]) {
+ [self toggleRegularStyle:[BoldStyle getType]];
} else if ([commandName isEqualToString:@"toggleItalic"]) {
- [self toggleRegularStyle:[ItalicStyle getStyleType]];
+ [self toggleRegularStyle:[ItalicStyle getType]];
} else if ([commandName isEqualToString:@"toggleUnderline"]) {
- [self toggleRegularStyle:[UnderlineStyle getStyleType]];
+ [self toggleRegularStyle:[UnderlineStyle getType]];
} else if ([commandName isEqualToString:@"toggleStrikeThrough"]) {
- [self toggleRegularStyle:[StrikethroughStyle getStyleType]];
+ [self toggleRegularStyle:[StrikethroughStyle getType]];
} else if ([commandName isEqualToString:@"toggleInlineCode"]) {
- [self toggleRegularStyle:[InlineCodeStyle getStyleType]];
- } else if ([commandName isEqualToString:@"addLink"]) {
- NSInteger start = [((NSNumber *)args[0]) integerValue];
- NSInteger end = [((NSNumber *)args[1]) integerValue];
- NSString *text = (NSString *)args[2];
- NSString *url = (NSString *)args[3];
- [self addLinkAt:start end:end text:text url:url];
- } else if ([commandName isEqualToString:@"addMention"]) {
- NSString *indicator = (NSString *)args[0];
- NSString *text = (NSString *)args[1];
- NSString *attributes = (NSString *)args[2];
- [self addMention:indicator text:text attributes:attributes];
- } else if ([commandName isEqualToString:@"startMention"]) {
- NSString *indicator = (NSString *)args[0];
- [self startMentionWithIndicator:indicator];
- } else if ([commandName isEqualToString:@"toggleH1"]) {
- [self toggleParagraphStyle:[H1Style getStyleType]];
+ [self toggleRegularStyle:[InlineCodeStyle getType]];
+ }
+ // else if ([commandName isEqualToString:@"addLink"]) {
+ // NSInteger start = [((NSNumber *)args[0]) integerValue];
+ // NSInteger end = [((NSNumber *)args[1]) integerValue];
+ // NSString *text = (NSString *)args[2];
+ // NSString *url = (NSString *)args[3];
+ // [self addLinkAt:start end:end text:text url:url];
+ // } else if ([commandName isEqualToString:@"addMention"]) {
+ // NSString *indicator = (NSString *)args[0];
+ // NSString *text = (NSString *)args[1];
+ // NSString *attributes = (NSString *)args[2];
+ // [self addMention:indicator text:text attributes:attributes];
+ // } else if ([commandName isEqualToString:@"startMention"]) {
+ // NSString *indicator = (NSString *)args[0];
+ // [self startMentionWithIndicator:indicator];
+ else if ([commandName isEqualToString:@"toggleH1"]) {
+ [self toggleRegularStyle:[H1Style getType]];
} else if ([commandName isEqualToString:@"toggleH2"]) {
- [self toggleParagraphStyle:[H2Style getStyleType]];
+ [self toggleRegularStyle:[H2Style getType]];
} else if ([commandName isEqualToString:@"toggleH3"]) {
- [self toggleParagraphStyle:[H3Style getStyleType]];
+ [self toggleRegularStyle:[H3Style getType]];
} else if ([commandName isEqualToString:@"toggleH4"]) {
- [self toggleParagraphStyle:[H4Style getStyleType]];
+ [self toggleRegularStyle:[H4Style getType]];
} else if ([commandName isEqualToString:@"toggleH5"]) {
- [self toggleParagraphStyle:[H5Style getStyleType]];
+ [self toggleRegularStyle:[H5Style getType]];
} else if ([commandName isEqualToString:@"toggleH6"]) {
- [self toggleParagraphStyle:[H6Style getStyleType]];
+ [self toggleRegularStyle:[H6Style getType]];
} else if ([commandName isEqualToString:@"toggleUnorderedList"]) {
- [self toggleParagraphStyle:[UnorderedListStyle getStyleType]];
+ [self toggleRegularStyle:[UnorderedListStyle getType]];
} else if ([commandName isEqualToString:@"toggleOrderedList"]) {
- [self toggleParagraphStyle:[OrderedListStyle getStyleType]];
+ [self toggleRegularStyle:[OrderedListStyle getType]];
} else if ([commandName isEqualToString:@"toggleCheckboxList"]) {
BOOL checked = [args[0] boolValue];
[self toggleCheckboxList:checked];
} else if ([commandName isEqualToString:@"toggleBlockQuote"]) {
- [self toggleParagraphStyle:[BlockQuoteStyle getStyleType]];
+ [self toggleRegularStyle:[BlockQuoteStyle getType]];
} else if ([commandName isEqualToString:@"toggleCodeBlock"]) {
- [self toggleParagraphStyle:[CodeBlockStyle getStyleType]];
- } else if ([commandName isEqualToString:@"addImage"]) {
- NSString *uri = (NSString *)args[0];
- CGFloat imgWidth = [(NSNumber *)args[1] floatValue];
- CGFloat imgHeight = [(NSNumber *)args[2] floatValue];
-
- [self addImage:uri width:imgWidth height:imgHeight];
- } else if ([commandName isEqualToString:@"setSelection"]) {
+ [self toggleRegularStyle:[CodeBlockStyle getType]];
+ }
+ // else if ([commandName isEqualToString:@"addImage"]) {
+ // NSString *uri = (NSString *)args[0];
+ // CGFloat imgWidth = [(NSNumber *)args[1] floatValue];
+ // CGFloat imgHeight = [(NSNumber *)args[2] floatValue];
+ //
+ // [self addImage:uri width:imgWidth height:imgHeight];
+ // }
+ else if ([commandName isEqualToString:@"setSelection"]) {
NSInteger start = [((NSNumber *)args[0]) integerValue];
NSInteger end = [((NSNumber *)args[1]) integerValue];
[self setCustomSelection:start end:end];
- } else if ([commandName isEqualToString:@"requestHTML"]) {
- NSInteger requestId = [((NSNumber *)args[0]) integerValue];
- [self requestHTML:requestId];
}
+ // else if ([commandName isEqualToString:@"requestHTML"]) {
+ // NSInteger requestId = [((NSNumber *)args[0]) integerValue];
+ // [self requestHTML:requestId];
+ // }
}
- (std::shared_ptr)getEventEmitter {
@@ -1201,21 +1211,21 @@ - (void)focus {
[textView reactFocus];
}
-- (void)setValue:(NSString *)value {
- NSString *initiallyProcessedHtml = [parser initiallyProcessHtml:value];
- if (initiallyProcessedHtml == nullptr) {
- // just plain text
- textView.text = value;
- } else {
- // we've got some seemingly proper html
- [parser replaceWholeFromHtml:initiallyProcessedHtml];
- }
-
- // set recentlyChangedRange and check for changes
- recentlyChangedRange = NSMakeRange(0, textView.textStorage.string.length);
- textView.selectedRange = NSRange(textView.textStorage.string.length, 0);
- [self anyTextMayHaveBeenModified];
-}
+//- (void)setValue:(NSString *)value {
+// NSString *initiallyProcessedHtml = [parser initiallyProcessHtml:value];
+// if (initiallyProcessedHtml == nullptr) {
+// // just plain text
+// textView.text = value;
+// } else {
+// // we've got some seemingly proper html
+// [parser replaceWholeFromHtml:initiallyProcessedHtml];
+// }
+//
+// // set recentlyChangedRange and check for changes
+// recentlyChangedRange = NSMakeRange(0, textView.textStorage.string.length);
+// textView.selectedRange = NSRange(textView.textStorage.string.length, 0);
+// [self anyTextMayHaveBeenModified];
+//}
- (void)setCustomSelection:(NSInteger)visibleStart end:(NSInteger)visibleEnd {
NSString *text = textView.textStorage.string;
@@ -1249,83 +1259,84 @@ - (NSUInteger)getActualIndex:(NSInteger)visibleIndex text:(NSString *)text {
return actualIndex;
}
-- (void)emitOnLinkDetectedEvent:(NSString *)text
- url:(NSString *)url
- range:(NSRange)range {
- auto emitter = [self getEventEmitter];
- if (emitter != nullptr) {
- // update recently active link info
- LinkData *newLinkData = [[LinkData alloc] init];
- newLinkData.text = text;
- newLinkData.url = url;
- _recentlyActiveLinkData = newLinkData;
- _recentlyActiveLinkRange = range;
-
- emitter->onLinkDetected({
- .text = [text toCppString],
- .url = [url toCppString],
- .start = static_cast(range.location),
- .end = static_cast(range.location + range.length),
- });
- }
-}
-
-- (void)emitOnMentionDetectedEvent:(NSString *)text
- indicator:(NSString *)indicator
- attributes:(NSString *)attributes {
- auto emitter = [self getEventEmitter];
- if (emitter != nullptr) {
- emitter->onMentionDetected({.text = [text toCppString],
- .indicator = [indicator toCppString],
- .payload = [attributes toCppString]});
- }
-}
-
-- (void)emitOnMentionEvent:(NSString *)indicator text:(NSString *)text {
- auto emitter = [self getEventEmitter];
- if (emitter != nullptr) {
- if (text != nullptr) {
- folly::dynamic fdStr = [text toCppString];
- emitter->onMention({.indicator = [indicator toCppString], .text = fdStr});
- } else {
- folly::dynamic nul = nullptr;
- emitter->onMention({.indicator = [indicator toCppString], .text = nul});
- }
- }
-}
-
-- (void)tryEmittingOnChangeHtmlEvent {
- if (!_emitHtml || textView.markedTextRange != nullptr) {
- return;
- }
- auto emitter = [self getEventEmitter];
- if (emitter != nullptr) {
- NSString *htmlOutput = [parser
- parseToHtmlFromRange:NSMakeRange(0,
- textView.textStorage.string.length)];
- // make sure html really changed
- if (![htmlOutput isEqualToString:_recentlyEmittedHtml]) {
- _recentlyEmittedHtml = htmlOutput;
- emitter->onChangeHtml({.value = [htmlOutput toCppString]});
- }
- }
-}
-
-- (void)requestHTML:(NSInteger)requestId {
- auto emitter = [self getEventEmitter];
- if (emitter != nullptr) {
- @try {
- NSString *htmlOutput = [parser
- parseToHtmlFromRange:NSMakeRange(0,
- textView.textStorage.string.length)];
- emitter->onRequestHtmlResult({.requestId = static_cast(requestId),
- .html = [htmlOutput toCppString]});
- } @catch (NSException *exception) {
- emitter->onRequestHtmlResult({.requestId = static_cast(requestId),
- .html = folly::dynamic(nullptr)});
- }
- }
-}
+//- (void)emitOnLinkDetectedEvent:(NSString *)text
+// url:(NSString *)url
+// range:(NSRange)range {
+// auto emitter = [self getEventEmitter];
+// if (emitter != nullptr) {
+// // update recently active link info
+// LinkData *newLinkData = [[LinkData alloc] init];
+// newLinkData.text = text;
+// newLinkData.url = url;
+// _recentlyActiveLinkData = newLinkData;
+// _recentlyActiveLinkRange = range;
+//
+// emitter->onLinkDetected({
+// .text = [text toCppString],
+// .url = [url toCppString],
+// .start = static_cast(range.location),
+// .end = static_cast(range.location + range.length),
+// });
+// }
+//}
+//
+//- (void)emitOnMentionDetectedEvent:(NSString *)text
+// indicator:(NSString *)indicator
+// attributes:(NSString *)attributes {
+// auto emitter = [self getEventEmitter];
+// if (emitter != nullptr) {
+// emitter->onMentionDetected({.text = [text toCppString],
+// .indicator = [indicator toCppString],
+// .payload = [attributes toCppString]});
+// }
+//}
+//
+//- (void)emitOnMentionEvent:(NSString *)indicator text:(NSString *)text {
+// auto emitter = [self getEventEmitter];
+// if (emitter != nullptr) {
+// if (text != nullptr) {
+// folly::dynamic fdStr = [text toCppString];
+// emitter->onMention({.indicator = [indicator toCppString], .text =
+// fdStr});
+// } else {
+// folly::dynamic nul = nullptr;
+// emitter->onMention({.indicator = [indicator toCppString], .text = nul});
+// }
+// }
+//}
+
+//- (void)tryEmittingOnChangeHtmlEvent {
+// if (!_emitHtml || textView.markedTextRange != nullptr) {
+// return;
+// }
+// auto emitter = [self getEventEmitter];
+// if (emitter != nullptr) {
+// NSString *htmlOutput = [parser
+// parseToHtmlFromRange:NSMakeRange(0,
+// textView.textStorage.string.length)];
+// // make sure html really changed
+// if (![htmlOutput isEqualToString:_recentlyEmittedHtml]) {
+// _recentlyEmittedHtml = htmlOutput;
+// emitter->onChangeHtml({.value = [htmlOutput toCppString]});
+// }
+// }
+//}
+
+//- (void)requestHTML:(NSInteger)requestId {
+// auto emitter = [self getEventEmitter];
+// if (emitter != nullptr) {
+// @try {
+// NSString *htmlOutput = [parser
+// parseToHtmlFromRange:NSMakeRange(0,
+// textView.textStorage.string.length)];
+// emitter->onRequestHtmlResult({.requestId = static_cast(requestId),
+// .html = [htmlOutput toCppString]});
+// } @catch (NSException *exception) {
+// emitter->onRequestHtmlResult({.requestId = static_cast(requestId),
+// .html = folly::dynamic(nullptr)});
+// }
+// }
+//}
- (void)emitOnKeyPressEvent:(NSString *)key {
auto emitter = [self getEventEmitter];
@@ -1337,115 +1348,115 @@ - (void)emitOnKeyPressEvent:(NSString *)key {
// MARK: - Styles manipulation
- (void)toggleRegularStyle:(StyleType)type {
- id styleClass = stylesDict[@(type)];
-
- if ([self handleStyleBlocksAndConflicts:type range:textView.selectedRange]) {
- [styleClass applyStyle:textView.selectedRange];
- [self anyTextMayHaveBeenModified];
+ StyleBase *style = stylesDict[@(type)];
+ NSRange range = textView.selectedRange;
+ if ([style isParagraph]) {
+ range = [textView.textStorage.string paragraphRangeForRange:range];
}
-}
-
-- (void)toggleParagraphStyle:(StyleType)type {
- id styleClass = stylesDict[@(type)];
- // we always pass whole paragraph/s range to these styles
- NSRange paragraphRange = [textView.textStorage.string
- paragraphRangeForRange:textView.selectedRange];
-
- if ([self handleStyleBlocksAndConflicts:type range:paragraphRange]) {
- [styleClass applyStyle:paragraphRange];
+ if ([self handleStyleBlocksAndConflicts:type range:range]) {
+ [style toggle:range];
[self anyTextMayHaveBeenModified];
}
}
+//- (void)toggleParagraphStyle:(StyleType)type {
+// id styleClass = stylesDict[@(type)];
+// // we always pass whole paragraph/s range to these styles
+// NSRange paragraphRange = [textView.textStorage.string
+// paragraphRangeForRange:textView.selectedRange];
+//
+// if ([self handleStyleBlocksAndConflicts:type range:paragraphRange]) {
+// [styleClass applyStyle:paragraphRange];
+// [self anyTextMayHaveBeenModified];
+// }
+//}
+
- (void)toggleCheckboxList:(BOOL)checked {
- CheckboxListStyle *checkboxListStyleClass =
- (CheckboxListStyle *)stylesDict[@([CheckboxListStyle getStyleType])];
- if (checkboxListStyleClass == nullptr) {
+ CheckboxListStyle *style =
+ (CheckboxListStyle *)stylesDict[@([CheckboxListStyle getType])];
+ if (style == nullptr) {
return;
}
- // we always pass whole paragraph/s range to these styles
- NSRange paragraphRange = [textView.textStorage.string
+ NSRange range = [textView.textStorage.string
paragraphRangeForRange:textView.selectedRange];
-
- if ([self handleStyleBlocksAndConflicts:[CheckboxListStyle getStyleType]
- range:paragraphRange]) {
- [checkboxListStyleClass applyStyleWithCheckedValue:checked
- inRange:paragraphRange];
- [self anyTextMayHaveBeenModified];
- }
-}
-
-- (void)addLinkAt:(NSInteger)start
- end:(NSInteger)end
- text:(NSString *)text
- url:(NSString *)url {
- LinkStyle *linkStyleClass =
- (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
- if (linkStyleClass == nullptr) {
- return;
- }
-
- // translate the output start-end notation to range
- NSRange linkRange = NSMakeRange(start, end - start);
- if ([self handleStyleBlocksAndConflicts:[LinkStyle getStyleType]
- range:linkRange]) {
- [linkStyleClass addLink:text
- url:url
- range:linkRange
- manual:YES
- withSelection:YES];
- [self anyTextMayHaveBeenModified];
- }
-}
-
-- (void)addMention:(NSString *)indicator
- text:(NSString *)text
- attributes:(NSString *)attributes {
- MentionStyle *mentionStyleClass =
- (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
- if (mentionStyleClass == nullptr) {
- return;
- }
- if ([mentionStyleClass getActiveMentionRange] == nullptr) {
- return;
- }
-
- if ([self handleStyleBlocksAndConflicts:[MentionStyle getStyleType]
- range:[[mentionStyleClass
- getActiveMentionRange]
- rangeValue]]) {
- [mentionStyleClass addMention:indicator text:text attributes:attributes];
+ if ([self handleStyleBlocksAndConflicts:[CheckboxListStyle getType]
+ range:range]) {
+ [style toggleWithChecked:checked range:range];
[self anyTextMayHaveBeenModified];
}
}
-- (void)addImage:(NSString *)uri width:(float)width height:(float)height {
- ImageStyle *imageStyleClass =
- (ImageStyle *)stylesDict[@([ImageStyle getStyleType])];
- if (imageStyleClass == nullptr) {
- return;
- }
-
- if ([self handleStyleBlocksAndConflicts:[ImageStyle getStyleType]
- range:textView.selectedRange]) {
- [imageStyleClass addImage:uri width:width height:height];
- [self anyTextMayHaveBeenModified];
- }
-}
-
-- (void)startMentionWithIndicator:(NSString *)indicator {
- MentionStyle *mentionStyleClass =
- (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
- if (mentionStyleClass == nullptr) {
- return;
- }
-
- if ([self handleStyleBlocksAndConflicts:[MentionStyle getStyleType]
- range:textView.selectedRange]) {
- [mentionStyleClass startMentionWithIndicator:indicator];
- [self anyTextMayHaveBeenModified];
- }
-}
+// - (void)addLinkAt:(NSInteger)start
+// end:(NSInteger)end
+// text:(NSString *)text
+// url:(NSString *)url {
+// LinkStyle *linkStyleClass =
+// (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
+// if (linkStyleClass == nullptr) {
+// return;
+// }
+
+// // translate the output start-end notation to range
+// NSRange linkRange = NSMakeRange(start, end - start);
+// if ([self handleStyleBlocksAndConflicts:[LinkStyle getStyleType]
+// range:linkRange]) {
+// [linkStyleClass addLink:text
+// url:url
+// range:linkRange
+// manual:YES
+// withSelection:YES];
+// [self anyTextMayHaveBeenModified];
+// }
+// }
+
+// - (void)addMention:(NSString *)indicator
+// text:(NSString *)text
+// attributes:(NSString *)attributes {
+// MentionStyle *mentionStyleClass =
+// (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
+// if (mentionStyleClass == nullptr) {
+// return;
+// }
+// if ([mentionStyleClass getActiveMentionRange] == nullptr) {
+// return;
+// }
+
+// if ([self handleStyleBlocksAndConflicts:[MentionStyle getStyleType]
+// range:[[mentionStyleClass
+// getActiveMentionRange]
+// rangeValue]]) {
+// [mentionStyleClass addMention:indicator text:text attributes:attributes];
+// [self anyTextMayHaveBeenModified];
+// }
+// }
+
+// - (void)addImage:(NSString *)uri width:(float)width height:(float)height {
+// ImageStyle *imageStyleClass =
+// (ImageStyle *)stylesDict[@([ImageStyle getStyleType])];
+// if (imageStyleClass == nullptr) {
+// return;
+// }
+
+// if ([self handleStyleBlocksAndConflicts:[ImageStyle getStyleType]
+// range:textView.selectedRange]) {
+// [imageStyleClass addImage:uri width:width height:height];
+// [self anyTextMayHaveBeenModified];
+// }
+// }
+
+// - (void)startMentionWithIndicator:(NSString *)indicator {
+// MentionStyle *mentionStyleClass =
+// (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
+// if (mentionStyleClass == nullptr) {
+// return;
+// }
+
+// if ([self handleStyleBlocksAndConflicts:[MentionStyle getStyleType]
+// range:textView.selectedRange]) {
+// [mentionStyleClass startMentionWithIndicator:indicator];
+// [self anyTextMayHaveBeenModified];
+// }
+// }
// returns false when style shouldn't be applied and true when it can be
- (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range {
@@ -1456,25 +1467,22 @@ - (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range {
return NO;
}
- // handle conflicting styles: all of their occurences have to be removed
+ // handle conflicting styles: remove styles within the range
NSArray *conflicting =
[self getPresentStyleTypesFrom:conflictingStyles[@(type)] range:range];
if (conflicting.count != 0) {
- for (NSNumber *style in conflicting) {
- id styleClass = stylesDict[style];
-
- if (range.length >= 1) {
- // for ranges, we need to remove each occurence
- NSArray *allOccurences =
- [styleClass findAllOccurences:range];
+ for (NSNumber *type in conflicting) {
+ StyleBase *style = stylesDict[type];
- for (StylePair *pair in allOccurences) {
- [styleClass removeAttributes:[pair.rangeValue rangeValue]];
- }
+ if ([style isParagraph]) {
+ // for paragraph styles we can just call remove since it will pick up
+ // proper paragraph range
+ [style remove:range withDirtyRange:YES];
} else {
- // with in-place selection, we just remove the adequate typing
- // attributes
- [styleClass removeTypingAttributes];
+ // for inline styles we have to differentiate betweeen normal and typing
+ // attributes removal
+ range.length >= 1 ? [style remove:range withDirtyRange:YES]
+ : [style removeTyping];
}
}
}
@@ -1486,14 +1494,14 @@ - (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range {
NSMutableArray *resultArray =
[[NSMutableArray alloc] init];
for (NSNumber *type in types) {
- id styleClass = stylesDict[type];
+ StyleBase *style = stylesDict[type];
if (range.length >= 1) {
- if ([styleClass anyOccurence:range]) {
+ if ([style any:range]) {
[resultArray addObject:type];
}
} else {
- if ([styleClass detectStyle:range]) {
+ if ([style detect:range]) {
[resultArray addObject:type];
}
}
@@ -1502,62 +1510,57 @@ - (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range {
}
- (void)manageSelectionBasedChanges {
- // link typing attributes fix
- LinkStyle *linkStyleClass =
- (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
- if (linkStyleClass != nullptr) {
- [linkStyleClass manageLinkTypingAttributes];
- }
+ // // link typing attributes fix
+ // LinkStyle *linkStyleClass =
+ // (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
+ // if (linkStyleClass != nullptr) {
+ // [linkStyleClass manageLinkTypingAttributes];
+ // }
NSString *currentString = [textView.textStorage.string copy];
- // mention typing attribtues fix and active editing
- MentionStyle *mentionStyleClass =
- (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
- if (mentionStyleClass != nullptr) {
- [mentionStyleClass manageMentionTypingAttributes];
-
- // mention editing runs if only a selection was done (no text change)
- // otherwise we would double-emit with a second call in the
- // anyTextMayHaveBeenModified method
- if ([_recentInputString isEqualToString:currentString]) {
- [mentionStyleClass manageMentionEditing];
- }
- }
-
- // typing attributes for empty lines selection reset
- if (textView.selectedRange.length == 0 &&
- [_recentInputString isEqualToString:currentString]) {
- // no string change means only a selection changed with no character changes
- NSRange paragraphRange = [textView.textStorage.string
- paragraphRangeForRange:textView.selectedRange];
- if (paragraphRange.length == 0 ||
- (paragraphRange.length == 1 &&
- [[NSCharacterSet newlineCharacterSet]
- characterIsMember:[textView.textStorage.string
- characterAtIndex:paragraphRange
- .location]])) {
- // user changed selection to an empty line (or empty line with a newline)
- // typing attributes need to be reset
- textView.typingAttributes = defaultTypingAttributes;
- }
- }
-
- // update active styles as well
+ // // mention typing attribtues fix and active editing
+ // MentionStyle *mentionStyleClass =
+ // (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
+ // if (mentionStyleClass != nullptr) {
+ // [mentionStyleClass manageMentionTypingAttributes];
+ //
+ // // mention editing runs if only a selection was done (no text change)
+ // // otherwise we would double-emit with a second call in the
+ // // anyTextMayHaveBeenModified method
+ // if ([_recentInputString isEqualToString:currentString]) {
+ // [mentionStyleClass manageMentionEditing];
+ // }
+ // }
+
+ // attributes manager handles proper typingAttributes at all times to properly
+ // extend meta-attributes
+ BOOL onlySelectionChanged =
+ textView.selectedRange.length == 0 &&
+ [_recentInputString isEqualToString:currentString];
+ // We want to remember which attributes were removed as long as we stay at the
+ // same position. This prevents a removed attribute from being re-applied from
+ // the preceding character right after we toggled it off
+ [attributesManager clearRemovedTypingAttributes];
+ [attributesManager
+ manageTypingAttributesWithOnlySelection:onlySelectionChanged];
+
+ // always update active styles
[self tryUpdatingActiveStyles];
}
-- (void)handleWordModificationBasedChanges:(NSString *)word
- inRange:(NSRange)range {
- // manual links refreshing and automatic links detection handling
- LinkStyle *linkStyle = [stylesDict objectForKey:@([LinkStyle getStyleType])];
-
- if (linkStyle != nullptr) {
- // manual links need to be handled first because they can block automatic
- // links after being refreshed
- [linkStyle handleManualLinks:word inRange:range];
- [linkStyle handleAutomaticLinks:word inRange:range];
- }
-}
+//- (void)handleWordModificationBasedChanges:(NSString *)word
+// inRange:(NSRange)range {
+// // manual links refreshing and automatic links detection handling
+// LinkStyle *linkStyle = [stylesDict objectForKey:@([LinkStyle
+// getStyleType])];
+//
+// if (linkStyle != nullptr) {
+// // manual links need to be handled first because they can block automatic
+// // links after being refreshed
+// [linkStyle handleManualLinks:word inRange:range];
+// [linkStyle handleAutomaticLinks:word inRange:range];
+// }
+//}
- (void)anyTextMayHaveBeenModified {
// we don't do no text changes when working with iOS marked text
@@ -1575,74 +1578,74 @@ - (void)anyTextMayHaveBeenModified {
textView.typingAttributes = defaultTypingAttributes;
}
- // inline code on newlines fix
- InlineCodeStyle *codeStyle = stylesDict[@([InlineCodeStyle getStyleType])];
- if (codeStyle != nullptr) {
- [codeStyle handleNewlines];
- }
-
- // blockquote colors management
- BlockQuoteStyle *bqStyle = stylesDict[@([BlockQuoteStyle getStyleType])];
- if (bqStyle != nullptr) {
- [bqStyle manageBlockquoteColor];
- }
-
- // codeblock font and color management
- CodeBlockStyle *codeBlockStyle = stylesDict[@([CodeBlockStyle getStyleType])];
- if (codeBlockStyle != nullptr) {
- [codeBlockStyle manageCodeBlockFontAndColor];
- }
-
- // improper headings fix
- H1Style *h1Style = stylesDict[@([H1Style getStyleType])];
- H2Style *h2Style = stylesDict[@([H2Style getStyleType])];
- H3Style *h3Style = stylesDict[@([H3Style getStyleType])];
- H4Style *h4Style = stylesDict[@([H4Style getStyleType])];
- H5Style *h5Style = stylesDict[@([H5Style getStyleType])];
- H6Style *h6Style = stylesDict[@([H6Style getStyleType])];
-
- bool headingStylesDefined = h1Style != nullptr && h2Style != nullptr &&
- h3Style != nullptr && h4Style != nullptr &&
- h5Style != nullptr && h6Style != nullptr;
-
- if (headingStylesDefined) {
- [h1Style handleImproperHeadings];
- [h2Style handleImproperHeadings];
- [h3Style handleImproperHeadings];
- [h4Style handleImproperHeadings];
- [h5Style handleImproperHeadings];
- [h6Style handleImproperHeadings];
- }
-
- // mentions management: removal and editing
- MentionStyle *mentionStyleClass =
- (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
- if (mentionStyleClass != nullptr) {
- [mentionStyleClass handleExistingMentions];
- [mentionStyleClass manageMentionEditing];
- }
+ // // inline code on newlines fix
+ // InlineCodeStyle *codeStyle = stylesDict[@([InlineCodeStyle
+ // getStyleType])]; if (codeStyle != nullptr) {
+ // [codeStyle handleNewlines];
+ // }
+ //
+ // // blockquote colors management
+ // BlockQuoteStyle *bqStyle = stylesDict[@([BlockQuoteStyle getStyleType])];
+ // if (bqStyle != nullptr) {
+ // [bqStyle manageBlockquoteColor];
+ // }
+ //
+ // // codeblock font and color management
+ // CodeBlockStyle *codeBlockStyle = stylesDict[@([CodeBlockStyle
+ // getStyleType])]; if (codeBlockStyle != nullptr) {
+ // [codeBlockStyle manageCodeBlockFontAndColor];
+ // }
+ //
+ // // improper headings fix
+ // H1Style *h1Style = stylesDict[@([H1Style getStyleType])];
+ // H2Style *h2Style = stylesDict[@([H2Style getStyleType])];
+ // H3Style *h3Style = stylesDict[@([H3Style getStyleType])];
+ // H4Style *h4Style = stylesDict[@([H4Style getStyleType])];
+ // H5Style *h5Style = stylesDict[@([H5Style getStyleType])];
+ // H6Style *h6Style = stylesDict[@([H6Style getStyleType])];
+ //
+ // bool headingStylesDefined = h1Style != nullptr && h2Style != nullptr &&
+ // h3Style != nullptr && h4Style != nullptr &&
+ // h5Style != nullptr && h6Style != nullptr;
+ //
+ // if (headingStylesDefined) {
+ // [h1Style handleImproperHeadings];
+ // [h2Style handleImproperHeadings];
+ // [h3Style handleImproperHeadings];
+ // [h4Style handleImproperHeadings];
+ // [h5Style handleImproperHeadings];
+ // [h6Style handleImproperHeadings];
+ // }
+ //
+ // // mentions management: removal and editing
+ // MentionStyle *mentionStyleClass =
+ // (MentionStyle *)stylesDict[@([MentionStyle getStyleType])];
+ // if (mentionStyleClass != nullptr) {
+ // [mentionStyleClass handleExistingMentions];
+ // [mentionStyleClass manageMentionEditing];
+ // }
// placholder management
[textView updatePlaceholderVisibility];
if (![textView.textStorage.string isEqualToString:_recentInputString]) {
- // modified words handling
- NSArray *modifiedWords =
- [WordsUtils getAffectedWordsFromText:textView.textStorage.string
- modificationRange:recentlyChangedRange];
- if (modifiedWords != nullptr) {
- for (NSDictionary *wordDict in modifiedWords) {
- NSString *wordText = (NSString *)[wordDict objectForKey:@"word"];
- NSValue *wordRange = (NSValue *)[wordDict objectForKey:@"range"];
-
- if (wordText == nullptr || wordRange == nullptr) {
- continue;
- }
-
- [self handleWordModificationBasedChanges:wordText
- inRange:[wordRange rangeValue]];
- }
- }
+ // // modified words handling
+ // NSArray *modifiedWords =
+ // [WordsUtils getAffectedWordsFromText:textView.textStorage.string
+ // modificationRange:recentlyChangedRange];
+ // if (modifiedWords != nullptr) {
+ // for (NSDictionary *wordDict in modifiedWords) {
+ // NSString *wordText = (NSString *)[wordDict objectForKey:@"word"];
+ // NSValue *wordRange = (NSValue *)[wordDict objectForKey:@"range"];
+ //
+ // if (wordText == nullptr || wordRange == nullptr) {
+ // continue;
+ // }
+ //
+ // [self handleWordModificationBasedChanges:wordText
+ // inRange:[wordRange rangeValue]];
+ // }
+ // }
// emit onChangeText event
auto emitter = [self getEventEmitter];
@@ -1658,12 +1661,14 @@ - (void)anyTextMayHaveBeenModified {
emitter->onChangeText({.value = [stringToBeEmitted toCppString]});
}
}
-
+ // all the visible (not meta) attributes handling in the ranges that could
+ // have changed
+ [attributesManager handleDirtyRangesStyling];
// update active styles as well
[self tryUpdatingActiveStyles];
}
-// MARK: - UITextView delegate methods
+// MARK: - Delegate methods
- (void)textViewDidBeginEditing:(UITextView *)textView {
auto emitter = [self getEventEmitter];
@@ -1719,58 +1724,74 @@ - (bool)textView:(UITextView *)textView
recentlyChangedRange = NSMakeRange(range.location, text.length);
[self handleKeyPressInRange:text range:range];
- UnorderedListStyle *uStyle = stylesDict[@([UnorderedListStyle getStyleType])];
- OrderedListStyle *oStyle = stylesDict[@([OrderedListStyle getStyleType])];
- CheckboxListStyle *cbLStyle = stylesDict[@([CheckboxListStyle getStyleType])];
- BlockQuoteStyle *bqStyle = stylesDict[@([BlockQuoteStyle getStyleType])];
- CodeBlockStyle *cbStyle = stylesDict[@([CodeBlockStyle getStyleType])];
- LinkStyle *linkStyle = stylesDict[@([LinkStyle getStyleType])];
- MentionStyle *mentionStyle = stylesDict[@([MentionStyle getStyleType])];
- H1Style *h1Style = stylesDict[@([H1Style getStyleType])];
- H2Style *h2Style = stylesDict[@([H2Style getStyleType])];
- H3Style *h3Style = stylesDict[@([H3Style getStyleType])];
- H4Style *h4Style = stylesDict[@([H4Style getStyleType])];
- H5Style *h5Style = stylesDict[@([H5Style getStyleType])];
- H6Style *h6Style = stylesDict[@([H6Style getStyleType])];
+ UnorderedListStyle *uStyle = stylesDict[@([UnorderedListStyle getType])];
+ OrderedListStyle *oStyle = stylesDict[@([OrderedListStyle getType])];
+ CheckboxListStyle *cbLStyle =
+ (CheckboxListStyle *)stylesDict[@([CheckboxListStyle getType])];
+ // BlockQuoteStyle *bqStyle = stylesDict[@([BlockQuoteStyle
+ // getStyleType])]; CodeBlockStyle *cbStyle = stylesDict[@([CodeBlockStyle
+ // getStyleType])]; LinkStyle *linkStyle = stylesDict[@([LinkStyle
+ // getStyleType])]; MentionStyle *mentionStyle = stylesDict[@([MentionStyle
+ // getStyleType])]; H1Style *h1Style = stylesDict[@([H1Style getStyleType])];
+ // H2Style *h2Style = stylesDict[@([H2Style getStyleType])];
+ // H3Style *h3Style = stylesDict[@([H3Style getStyleType])];
+ // H4Style *h4Style = stylesDict[@([H4Style getStyleType])];
+ // H5Style *h5Style = stylesDict[@([H5Style getStyleType])];
+ // H6Style *h6Style = stylesDict[@([H6Style getStyleType])];
// some of the changes these checks do could interfere with later checks and
// cause a crash so here I rely on short circuiting evaluation of the logical
// expression either way it's not possible to have two of them come off at the
// same time
- if ([uStyle handleBackspaceInRange:range replacementText:text] ||
- [uStyle tryHandlingListShorcutInRange:range replacementText:text] ||
- [oStyle handleBackspaceInRange:range replacementText:text] ||
- [oStyle tryHandlingListShorcutInRange:range replacementText:text] ||
- [cbLStyle handleBackspaceInRange:range replacementText:text] ||
- [cbLStyle handleNewlinesInRange:range replacementText:text] ||
- [bqStyle handleBackspaceInRange:range replacementText:text] ||
- [cbStyle handleBackspaceInRange:range replacementText:text] ||
- [linkStyle handleLeadingLinkReplacement:range replacementText:text] ||
- [mentionStyle handleLeadingMentionReplacement:range
- replacementText:text] ||
- [h1Style handleNewlinesInRange:range replacementText:text] ||
- [h2Style handleNewlinesInRange:range replacementText:text] ||
- [h3Style handleNewlinesInRange:range replacementText:text] ||
- [h4Style handleNewlinesInRange:range replacementText:text] ||
- [h5Style handleNewlinesInRange:range replacementText:text] ||
- [h6Style handleNewlinesInRange:range replacementText:text] ||
+ if (
+ // ZWS backspace handling for paragraph styles
[ZeroWidthSpaceUtils handleBackspaceInRange:range
replacementText:text
input:self] ||
+ [uStyle tryHandlingListShorcutInRange:range replacementText:text] ||
+ [oStyle tryHandlingListShorcutInRange:range replacementText:text] ||
+ [cbLStyle handleNewlinesInRange:range replacementText:text]
+ // || [bqStyle handleBackspaceInRange:range replacementText:text]
+ // || [cbStyle handleBackspaceInRange:range replacementText:text]
+ // || [linkStyle handleLeadingLinkReplacement:range
+ // replacementText:text] || [mentionStyle
+ // handleLeadingMentionReplacement:range
+ // replacementText:text]
+ || [(HeadingStyleBase *)stylesDict[@([H1Style getType])]
+ handleNewlinesInRange:range
+ replacementText:text] ||
+ [(HeadingStyleBase *)stylesDict[@([H2Style getType])]
+ handleNewlinesInRange:range
+ replacementText:text] ||
+ [(HeadingStyleBase *)stylesDict[@([H3Style getType])]
+ handleNewlinesInRange:range
+ replacementText:text] ||
+ [(HeadingStyleBase *)stylesDict[@([H4Style getType])]
+ handleNewlinesInRange:range
+ replacementText:text] ||
+ [(HeadingStyleBase *)stylesDict[@([H5Style getType])]
+ handleNewlinesInRange:range
+ replacementText:text] ||
+ [(HeadingStyleBase *)stylesDict[@([H6Style getType])]
+ handleNewlinesInRange:range
+ replacementText:text] ||
[ParagraphAttributesUtils handleBackspaceInRange:range
replacementText:text
input:self] ||
[ParagraphAttributesUtils handleResetTypingAttributesOnBackspace:range
replacementText:text
- input:self] ||
+ input:self]
+ // ||
// CRITICAL: This callback HAS TO be always evaluated last.
//
// This function is the "Generic Fallback": if no specific style claims
// the backspace action to change its state, only then do we proceed to
// physically delete the newline and merge paragraphs.
- [ParagraphAttributesUtils handleParagraphStylesMergeOnBackspace:range
- replacementText:text
- input:self]) {
+ // || [ParagraphAttributesUtils
+ // handleParagraphStylesMergeOnBackspace:range
+ // replacementText:text
+ // input:self]
+ ) {
[self anyTextMayHaveBeenModified];
return NO;
}
@@ -1857,7 +1878,7 @@ - (void)onTextBlockTap:(TextBlockTapGestureRecognizer *)gr {
case TextBlockTapKindCheckbox: {
CheckboxListStyle *checkboxStyle =
- (CheckboxListStyle *)stylesDict[@([CheckboxListStyle getStyleType])];
+ (CheckboxListStyle *)stylesDict[@([CheckboxListStyle getType])];
if (checkboxStyle) {
NSUInteger charIndex = (NSUInteger)gr.characterIndex;
@@ -1891,31 +1912,54 @@ - (void)onTextBlockTap:(TextBlockTapGestureRecognizer *)gr {
}
}
-// MARK: - Media attachments delegate
-
-- (void)mediaAttachmentDidUpdate:(NSTextAttachment *)attachment {
- NSTextStorage *storage = textView.textStorage;
- NSRange fullRange = NSMakeRange(0, storage.length);
-
- __block NSRange foundRange = NSMakeRange(NSNotFound, 0);
-
- [storage enumerateAttribute:NSAttachmentAttributeName
- inRange:fullRange
- options:0
- usingBlock:^(id value, NSRange range, BOOL *stop) {
- if (value == attachment) {
- foundRange = range;
- *stop = YES;
- }
- }];
-
- if (foundRange.location == NSNotFound) {
- return;
+- (void)textStorage:(NSTextStorage *)textStorage
+ didProcessEditing:(NSTextStorageEditActions)editedMask
+ range:(NSRange)editedRange
+ changeInLength:(NSInteger)delta {
+ // iOS replacing quick double space with ". " attributes fix.
+ [DotReplacementUtils handleDotReplacement:self
+ textStorage:textStorage
+ editedMask:editedMask
+ editedRange:editedRange
+ delta:delta];
+
+ // Needed dirty ranges adjustments happen on every character edition.
+ if ((editedMask & NSTextStorageEditedCharacters) != 0) {
+ // Always try shifting dirty ranges (happens only with delta != 0).
+ [attributesManager shiftDirtyRangesWithEditedRange:editedRange
+ changeInLength:delta];
+
+ // Always try adding new dirty range (happens only with editedRange.length >
+ // 0).
+ [attributesManager addDirtyRange:editedRange];
}
-
- [storage edited:NSTextStorageEditedAttributes
- range:foundRange
- changeInLength:0];
}
+// MARK: - Media attachments delegate
+
+//- (void)mediaAttachmentDidUpdate:(NSTextAttachment *)attachment {
+// NSTextStorage *storage = textView.textStorage;
+// NSRange fullRange = NSMakeRange(0, storage.length);
+//
+// __block NSRange foundRange = NSMakeRange(NSNotFound, 0);
+//
+// [storage enumerateAttribute:NSAttachmentAttributeName
+// inRange:fullRange
+// options:0
+// usingBlock:^(id value, NSRange range, BOOL *stop) {
+// if (value == attachment) {
+// foundRange = range;
+// *stop = YES;
+// }
+// }];
+//
+// if (foundRange.location == NSNotFound) {
+// return;
+// }
+//
+// [storage edited:NSTextStorageEditedAttributes
+// range:foundRange
+// changeInLength:0];
+//}
+
@end
diff --git a/ios/attributesManager/AttributesManager.h b/ios/attributesManager/AttributesManager.h
new file mode 100644
index 000000000..dc87e4199
--- /dev/null
+++ b/ios/attributesManager/AttributesManager.h
@@ -0,0 +1,16 @@
+#pragma once
+#import
+
+@class EnrichedTextInputView;
+
+@interface AttributesManager : NSObject
+@property(nonatomic, weak) EnrichedTextInputView *input;
+- (instancetype)initWithInput:(EnrichedTextInputView *)input;
+- (void)addDirtyRange:(NSRange)range;
+- (void)shiftDirtyRangesWithEditedRange:(NSRange)editedRange
+ changeInLength:(NSInteger)delta;
+- (void)didRemoveTypingAttribute:(NSString *)key;
+- (void)clearRemovedTypingAttributes;
+- (void)manageTypingAttributesWithOnlySelection:(BOOL)onlySelectionChanged;
+- (void)handleDirtyRangesStyling;
+@end
diff --git a/ios/attributesManager/AttributesManager.mm b/ios/attributesManager/AttributesManager.mm
new file mode 100644
index 000000000..4e233a07c
--- /dev/null
+++ b/ios/attributesManager/AttributesManager.mm
@@ -0,0 +1,187 @@
+#import "AttributesManager.h"
+#import "AttributeEntry.h"
+#import "EnrichedTextInputView.h"
+#import "RangeUtils.h"
+#import "StyleHeaders.h"
+
+@implementation AttributesManager {
+ NSMutableArray *_dirtyRanges;
+ NSSet *_customAttributesKeys;
+ NSMutableSet *_removedTypingAttributes;
+}
+
+- (instancetype)initWithInput:(EnrichedTextInputView *)input {
+ self = [super init];
+ _input = input;
+ _dirtyRanges = [[NSMutableArray alloc] init];
+ _removedTypingAttributes = [[NSMutableSet alloc] init];
+
+ // setup customAttributes
+ NSMutableSet *_customAttrsSet = [[NSMutableSet alloc] init];
+ for (StyleBase *style in _input->stylesDict.allValues) {
+ [_customAttrsSet addObject:[style getKey]];
+ }
+ _customAttributesKeys = _customAttrsSet;
+
+ return self;
+}
+
+- (void)addDirtyRange:(NSRange)range {
+ if (range.length == 0) {
+ return;
+ }
+ [_dirtyRanges addObject:[NSValue valueWithRange:range]];
+ _dirtyRanges = [[RangeUtils connectAndDedupeRanges:_dirtyRanges] mutableCopy];
+}
+
+- (void)shiftDirtyRangesWithEditedRange:(NSRange)editedRange
+ changeInLength:(NSInteger)delta {
+ if (delta == 0) {
+ return;
+ }
+ NSArray *shiftedRanges = [RangeUtils shiftRanges:_dirtyRanges
+ withEditedRange:editedRange
+ changeInLength:delta];
+ _dirtyRanges =
+ [[RangeUtils connectAndDedupeRanges:shiftedRanges] mutableCopy];
+}
+
+- (void)didRemoveTypingAttribute:(NSString *)key {
+ [_removedTypingAttributes addObject:key];
+}
+
+- (void)clearRemovedTypingAttributes {
+ [_removedTypingAttributes removeAllObjects];
+}
+
+- (void)handleDirtyRangesStyling {
+ for (NSValue *rangeObj in _dirtyRanges) {
+ NSRange dirtyRange = [rangeObj rangeValue];
+
+ // dirty range can sometimes be wrong because of apple doing some changes
+ // behind the scenes
+ if (dirtyRange.location + dirtyRange.length >
+ _input->textView.textStorage.string.length)
+ continue;
+
+ // firstly, get all styles' occurences in that dirty range
+ NSMutableDictionary *presentStyles = [[NSMutableDictionary alloc] init];
+ for (StyleBase *style in _input->stylesDict.allValues) {
+ // the dict has keys of StyleType NSNumber and values of an array of all
+ // occurences
+ presentStyles[@([[style class] getType])] = [style all:dirtyRange];
+ }
+
+ // now reset the attributes to default ones
+ [_input->textView.textStorage setAttributes:_input->defaultTypingAttributes
+ range:dirtyRange];
+
+ // Sort style types so paragraph styles come first. Their broad visual
+ // attributes (e.g. foreground color, font) are laid down before inline
+ // styles override them on their specific sub-ranges.
+ NSArray *sortedStyleTypes = [presentStyles.allKeys
+ sortedArrayUsingComparator:^NSComparisonResult(NSNumber *a,
+ NSNumber *b) {
+ BOOL aPara = [_input->stylesDict[a] isParagraph];
+ BOOL bPara = [_input->stylesDict[b] isParagraph];
+ if (aPara == bPara)
+ return NSOrderedSame;
+ return aPara ? NSOrderedAscending : NSOrderedDescending;
+ }];
+
+ // Apply visual styling and re-apply meta-attributes following the saved
+ // occurences.
+ for (NSNumber *styleType in sortedStyleTypes) {
+ StyleBase *style = _input->stylesDict[styleType];
+ if (style == nullptr)
+ continue;
+
+ for (StylePair *stylePair in presentStyles[styleType]) {
+ NSRange occurenceRange = [stylePair.rangeValue rangeValue];
+ [style applyStyling:occurenceRange];
+ [style reapplyAttributesFromStylePair:stylePair];
+ }
+ }
+ }
+ // do the typing attributes management, with no selection
+ [self manageTypingAttributesWithOnlySelection:NO];
+
+ [_dirtyRanges removeAllObjects];
+}
+
+- (void)manageTypingAttributesWithOnlySelection:(BOOL)onlySelectionChanged {
+ InputTextView *textView = _input->textView;
+ NSRange selectedRange = textView.selectedRange;
+
+ // Typing attributes get reset when only selection changed to an empty line
+ // (or empty line with newline).
+ if (onlySelectionChanged) {
+ NSRange paragraphRange =
+ [textView.textStorage.string paragraphRangeForRange:selectedRange];
+ // User changed selection to an empty line (or empty line with a newline).
+ if (paragraphRange.length == 0 ||
+ (paragraphRange.length == 1 &&
+ [[NSCharacterSet newlineCharacterSet]
+ characterIsMember:[textView.textStorage.string
+ characterAtIndex:paragraphRange
+ .location]])) {
+ textView.typingAttributes = _input->defaultTypingAttributes;
+ return;
+ }
+ }
+
+ // General typing attributes management.
+
+ // Firstly, we make sure only default + custom + paragraph typing attribtues
+ // are left.
+ NSMutableDictionary *newAttrs = [_input->defaultTypingAttributes mutableCopy];
+
+ for (NSString *key in _input->textView.typingAttributes.allKeys) {
+ if ([_customAttributesKeys containsObject:key]) {
+ if ([key isEqualToString:NSParagraphStyleAttributeName]) {
+ // NSParagraphStyle for paragraph styles -> only keep the textLists
+ // property
+ NSParagraphStyle *pStyle =
+ (NSParagraphStyle *)_input->textView
+ .typingAttributes[NSParagraphStyleAttributeName];
+ if (pStyle != nullptr && pStyle.textLists.count == 1) {
+ NSMutableParagraphStyle *newPStyle =
+ [[NSMutableParagraphStyle alloc] init];
+ newPStyle.textLists = pStyle.textLists;
+ newAttrs[NSParagraphStyleAttributeName] = newPStyle;
+ }
+ } else {
+ // Inline styles -> keep the key/value as a whole
+ newAttrs[key] = _input->textView.typingAttributes[key];
+ }
+ }
+ }
+
+ // Then, we add typingAttributes from present inline styles.
+ // We check for the previous character to naturally extend typing attributes.
+ // getEntryIfPresent properly returns nullptr for styles that we don't want to
+ // extend this way. Attributes from _removedTypingAttributes aren't added
+ // because they were just removed.
+ for (StyleBase *style in _input->stylesDict.allValues) {
+ if ([style isParagraph])
+ continue;
+ if ([_removedTypingAttributes containsObject:[style getKey]])
+ continue;
+
+ AttributeEntry *entry = nullptr;
+
+ if (selectedRange.location > 0) {
+ entry =
+ [style getEntryIfPresent:NSMakeRange(selectedRange.location - 1, 1)];
+ }
+
+ if (entry == nullptr)
+ continue;
+
+ newAttrs[entry.key] = entry.value;
+ }
+
+ textView.typingAttributes = newAttrs;
+}
+
+@end
diff --git a/ios/extensions/LayoutManagerExtension.mm b/ios/extensions/LayoutManagerExtension.mm
index 4de25c12d..9d647faf5 100644
--- a/ios/extensions/LayoutManagerExtension.mm
+++ b/ios/extensions/LayoutManagerExtension.mm
@@ -1,7 +1,7 @@
#import "LayoutManagerExtension.h"
#import "ColorExtension.h"
#import "EnrichedTextInputView.h"
-#import "ParagraphsUtils.h"
+#import "RangeUtils.h"
#import "StyleHeaders.h"
#import
@@ -66,13 +66,12 @@ - (void)drawCodeBlocks:(EnrichedTextInputView *)typedInput
origin:(CGPoint)origin
visibleCharRange:(NSRange)visibleCharRange {
CodeBlockStyle *codeBlockStyle =
- typedInput->stylesDict[@([CodeBlockStyle getStyleType])];
+ typedInput->stylesDict[@([CodeBlockStyle getType])];
if (codeBlockStyle == nullptr) {
return;
}
- NSArray *allCodeBlocks =
- [codeBlockStyle findAllOccurences:visibleCharRange];
+ NSArray *allCodeBlocks = [codeBlockStyle all:visibleCharRange];
NSArray *mergedCodeBlocks =
[self mergeContiguousStylePairs:allCodeBlocks];
UIColor *bgColor = [[typedInput->config codeBlockBgColor]
@@ -86,8 +85,8 @@ - (void)drawCodeBlocks:(EnrichedTextInputView *)typedInput
continue;
NSArray *paragraphs =
- [ParagraphsUtils getSeparateParagraphsRangesIn:typedInput->textView
- range:blockCharacterRange];
+ [RangeUtils getSeparateParagraphsRangesIn:typedInput->textView
+ range:blockCharacterRange];
if (paragraphs.count == 0)
continue;
@@ -205,12 +204,12 @@ - (void)drawBlockQuotes:(EnrichedTextInputView *)typedInput
origin:(CGPoint)origin
visibleCharRange:(NSRange)visibleCharRange {
BlockQuoteStyle *bqStyle =
- typedInput->stylesDict[@([BlockQuoteStyle getStyleType])];
+ typedInput->stylesDict[@([BlockQuoteStyle getType])];
if (bqStyle == nullptr) {
return;
}
- NSArray *allBlockquotes = [bqStyle findAllOccurences:visibleCharRange];
+ NSArray *allBlockquotes = [bqStyle all:visibleCharRange];
for (StylePair *pair in allBlockquotes) {
NSRange paragraphRange = [typedInput->textView.textStorage.string
@@ -246,19 +245,20 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput
origin:(CGPoint)origin
visibleCharRange:(NSRange)visibleCharRange {
UnorderedListStyle *ulStyle =
- typedInput->stylesDict[@([UnorderedListStyle getStyleType])];
+ typedInput->stylesDict[@([UnorderedListStyle getType])];
OrderedListStyle *olStyle =
- typedInput->stylesDict[@([OrderedListStyle getStyleType])];
+ typedInput->stylesDict[@([OrderedListStyle getType])];
CheckboxListStyle *cbStyle =
- typedInput->stylesDict[@([CheckboxListStyle getStyleType])];
+ typedInput->stylesDict[@([CheckboxListStyle getType])];
if (ulStyle == nullptr || olStyle == nullptr || cbStyle == nullptr) {
return;
}
NSMutableArray *allLists = [[NSMutableArray alloc] init];
- [allLists addObjectsFromArray:[ulStyle findAllOccurences:visibleCharRange]];
- [allLists addObjectsFromArray:[olStyle findAllOccurences:visibleCharRange]];
- [allLists addObjectsFromArray:[cbStyle findAllOccurences:visibleCharRange]];
+
+ [allLists addObjectsFromArray:[ulStyle all:visibleCharRange]];
+ [allLists addObjectsFromArray:[olStyle all:visibleCharRange]];
+ [allLists addObjectsFromArray:[cbStyle all:visibleCharRange]];
for (StylePair *pair in allLists) {
NSParagraphStyle *pStyle = (NSParagraphStyle *)pair.styleValue;
@@ -268,9 +268,9 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput
[typedInput->config orderedListMarkerColor]
};
- NSArray *paragraphs = [ParagraphsUtils
- getSeparateParagraphsRangesIn:typedInput->textView
- range:[pair.rangeValue rangeValue]];
+ NSArray *paragraphs =
+ [RangeUtils getSeparateParagraphsRangesIn:typedInput->textView
+ range:[pair.rangeValue rangeValue]];
for (NSValue *paragraph in paragraphs) {
NSRange paragraphGlyphRange =
@@ -287,8 +287,9 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput
pStyle.textLists.firstObject
.markerFormat;
- if (markerFormat ==
- NSTextListMarkerDecimal) {
+ if ([markerFormat
+ isEqualToString:
+ @"EnrichedOrderedList"]) {
NSString *marker = [self
getDecimalMarkerForList:typedInput
charIndex:
@@ -301,8 +302,10 @@ - (void)drawLists:(EnrichedTextInputView *)typedInput
markerAttributes:markerAttributes
origin:origin
usedRect:usedRect];
- } else if (markerFormat ==
- NSTextListMarkerDisc) {
+ } else if ([markerFormat
+ isEqualToString:
+ @"EnrichedUnorderedLis"
+ @"t"]) {
[self drawBullet:typedInput
origin:origin
usedRect:usedRect];
@@ -329,7 +332,7 @@ - (NSString *)getDecimalMarkerForList:(EnrichedTextInputView *)input
[fullText paragraphRangeForRange:NSMakeRange(index, 0)];
if (currentParagraph.location > 0) {
OrderedListStyle *olStyle =
- input->stylesDict[@([OrderedListStyle getStyleType])];
+ input->stylesDict[@([OrderedListStyle getType])];
NSInteger prevParagraphsCount = 0;
NSInteger recentParagraphLocation =
@@ -339,7 +342,7 @@ - (NSString *)getDecimalMarkerForList:(EnrichedTextInputView *)input
// seek for previous lists
while (true) {
- if ([olStyle detectStyle:NSMakeRange(recentParagraphLocation, 0)]) {
+ if ([olStyle detect:NSMakeRange(recentParagraphLocation, 0)]) {
prevParagraphsCount += 1;
if (recentParagraphLocation > 0) {
@@ -366,7 +369,7 @@ - (void)drawCheckbox:(EnrichedTextInputView *)typedInput
markerFormat:(NSString *)markerFormat
origin:(CGPoint)origin
usedRect:(CGRect)usedRect {
- BOOL isChecked = [markerFormat isEqualToString:@"{checkbox:1}"];
+ BOOL isChecked = [markerFormat isEqualToString:@"EnrichedCheckbox1"];
UIImage *image = isChecked ? typedInput->config.checkboxCheckedImage
: typedInput->config.checkboxUncheckedImage;
@@ -406,7 +409,7 @@ - (void)drawDecimal:(EnrichedTextInputView *)typedInput
usedRect:(CGRect)usedRect {
CGFloat gapWidth = [typedInput->config orderedListGapWidth];
CGFloat markerWidth = [marker sizeWithAttributes:markerAttributes].width;
- CGFloat markerX = usedRect.origin.x - gapWidth - markerWidth / 2;
+ CGFloat markerX = origin.x + usedRect.origin.x - gapWidth - markerWidth / 2;
[marker drawAtPoint:CGPointMake(markerX, usedRect.origin.y + origin.y)
withAttributes:markerAttributes];
diff --git a/ios/inputParser/InputParser.mm b/ios/inputParser/InputParser.mm
index a2558c190..3806037e5 100644
--- a/ios/inputParser/InputParser.mm
+++ b/ios/inputParser/InputParser.mm
@@ -45,8 +45,8 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range {
// check each existing style existence
for (NSNumber *type in _input->stylesDict) {
- id style = _input->stylesDict[type];
- if ([style detectStyle:currentRange]) {
+ StyleBase *style = _input->stylesDict[type];
+ if ([style detect:currentRange]) {
[currentActiveStyles addObject:type];
if (![previousActiveStyles member:type]) {
@@ -70,8 +70,7 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range {
// checked on 0 length range, not on the newline character
if (inOrderedList) {
OrderedListStyle *oStyle = _input->stylesDict[@(OrderedList)];
- BOOL detected =
- [oStyle detectStyle:NSMakeRange(currentRange.location, 0)];
+ BOOL detected = [oStyle detect:NSMakeRange(currentRange.location, 0)];
if (detected) {
[result appendString:@"\n"];
} else {
@@ -80,8 +79,7 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range {
}
} else if (inUnorderedList) {
UnorderedListStyle *uStyle = _input->stylesDict[@(UnorderedList)];
- BOOL detected =
- [uStyle detectStyle:NSMakeRange(currentRange.location, 0)];
+ BOOL detected = [uStyle detect:NSMakeRange(currentRange.location, 0)];
if (detected) {
[result appendString:@"\n"];
} else {
@@ -91,7 +89,7 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range {
} else if (inBlockQuote) {
BlockQuoteStyle *bqStyle = _input->stylesDict[@(BlockQuote)];
BOOL detected =
- [bqStyle detectStyle:NSMakeRange(currentRange.location, 0)];
+ [bqStyle detect:NSMakeRange(currentRange.location, 0)];
if (detected) {
[result appendString:@"\n
"];
} else {
@@ -101,7 +99,7 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range {
} else if (inCodeBlock) {
CodeBlockStyle *cbStyle = _input->stylesDict[@(CodeBlock)];
BOOL detected =
- [cbStyle detectStyle:NSMakeRange(currentRange.location, 0)];
+ [cbStyle detect:NSMakeRange(currentRange.location, 0)];
if (detected) {
[result appendString:@"\n
"];
} else {
@@ -111,7 +109,7 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range {
} else if (inCheckboxList) {
CheckboxListStyle *cbLStyle = _input->stylesDict[@(CheckboxList)];
BOOL detected =
- [cbLStyle detectStyle:NSMakeRange(currentRange.location, 0)];
+ [cbLStyle detect:NSMakeRange(currentRange.location, 0)];
if (detected) {
BOOL checked = [cbLStyle getCheckboxStateAt:currentRange.location];
if (checked) {
@@ -149,21 +147,20 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range {
// append closing paragraph tag
if ([previousActiveStyles
- containsObject:@([UnorderedListStyle getStyleType])] ||
- [previousActiveStyles
- containsObject:@([OrderedListStyle getStyleType])] ||
- [previousActiveStyles containsObject:@([H1Style getStyleType])] ||
- [previousActiveStyles containsObject:@([H2Style getStyleType])] ||
- [previousActiveStyles containsObject:@([H3Style getStyleType])] ||
- [previousActiveStyles containsObject:@([H4Style getStyleType])] ||
- [previousActiveStyles containsObject:@([H5Style getStyleType])] ||
- [previousActiveStyles containsObject:@([H6Style getStyleType])] ||
+ containsObject:@([UnorderedListStyle getType])] ||
[previousActiveStyles
- containsObject:@([BlockQuoteStyle getStyleType])] ||
+ containsObject:@([OrderedListStyle getType])] ||
+ [previousActiveStyles containsObject:@([H1Style getType])] ||
+ [previousActiveStyles containsObject:@([H2Style getType])] ||
+ [previousActiveStyles containsObject:@([H3Style getType])] ||
+ [previousActiveStyles containsObject:@([H4Style getType])] ||
+ [previousActiveStyles containsObject:@([H5Style getType])] ||
+ [previousActiveStyles containsObject:@([H6Style getType])] ||
[previousActiveStyles
- containsObject:@([CodeBlockStyle getStyleType])] ||
+ containsObject:@([BlockQuoteStyle getType])] ||
+ [previousActiveStyles containsObject:@([CodeBlockStyle getType])] ||
[previousActiveStyles
- containsObject:@([CheckboxListStyle getStyleType])]) {
+ containsObject:@([CheckboxListStyle getType])]) {
// do nothing, proper closing paragraph tags have been already
// appended
} else {
@@ -184,35 +181,33 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range {
// handle ending unordered list
if (inUnorderedList &&
![currentActiveStyles
- containsObject:@([UnorderedListStyle getStyleType])]) {
+ containsObject:@([UnorderedListStyle getType])]) {
inUnorderedList = NO;
[result appendString:@"\n"];
}
// handle ending ordered list
if (inOrderedList &&
![currentActiveStyles
- containsObject:@([OrderedListStyle getStyleType])]) {
+ containsObject:@([OrderedListStyle getType])]) {
inOrderedList = NO;
[result appendString:@"\n"];
}
// handle ending blockquotes
- if (inBlockQuote &&
- ![currentActiveStyles
- containsObject:@([BlockQuoteStyle getStyleType])]) {
+ if (inBlockQuote && ![currentActiveStyles
+ containsObject:@([BlockQuoteStyle getType])]) {
inBlockQuote = NO;
[result appendString:@"\n"];
}
// handle ending codeblock
if (inCodeBlock &&
- ![currentActiveStyles
- containsObject:@([CodeBlockStyle getStyleType])]) {
+ ![currentActiveStyles containsObject:@([CodeBlockStyle getType])]) {
inCodeBlock = NO;
[result appendString:@"\n"];
}
// handle ending checkbox list
if (inCheckboxList &&
![currentActiveStyles
- containsObject:@([CheckboxListStyle getStyleType])]) {
+ containsObject:@([CheckboxListStyle getType])]) {
inCheckboxList = NO;
[result appendString:@"\n"];
}
@@ -220,56 +215,52 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range {
// handle starting unordered list
if (!inUnorderedList &&
[currentActiveStyles
- containsObject:@([UnorderedListStyle getStyleType])]) {
+ containsObject:@([UnorderedListStyle getType])]) {
inUnorderedList = YES;
[result appendString:@"\n"];
}
// handle starting ordered list
if (!inOrderedList &&
[currentActiveStyles
- containsObject:@([OrderedListStyle getStyleType])]) {
+ containsObject:@([OrderedListStyle getType])]) {
inOrderedList = YES;
[result appendString:@"\n"];
}
// handle starting blockquotes
if (!inBlockQuote &&
- [currentActiveStyles
- containsObject:@([BlockQuoteStyle getStyleType])]) {
+ [currentActiveStyles containsObject:@([BlockQuoteStyle getType])]) {
inBlockQuote = YES;
[result appendString:@"\n"];
}
// handle starting codeblock
if (!inCodeBlock &&
- [currentActiveStyles
- containsObject:@([CodeBlockStyle getStyleType])]) {
+ [currentActiveStyles containsObject:@([CodeBlockStyle getType])]) {
inCodeBlock = YES;
[result appendString:@"\n"];
}
// handle starting checkbox list
if (!inCheckboxList &&
[currentActiveStyles
- containsObject:@([CheckboxListStyle getStyleType])]) {
+ containsObject:@([CheckboxListStyle getType])]) {
inCheckboxList = YES;
[result appendString:@"\n"];
}
// don't add the tag if some paragraph styles are present
if ([currentActiveStyles
- containsObject:@([UnorderedListStyle getStyleType])] ||
- [currentActiveStyles
- containsObject:@([OrderedListStyle getStyleType])] ||
- [currentActiveStyles containsObject:@([H1Style getStyleType])] ||
- [currentActiveStyles containsObject:@([H2Style getStyleType])] ||
- [currentActiveStyles containsObject:@([H3Style getStyleType])] ||
- [currentActiveStyles containsObject:@([H4Style getStyleType])] ||
- [currentActiveStyles containsObject:@([H5Style getStyleType])] ||
- [currentActiveStyles containsObject:@([H6Style getStyleType])] ||
+ containsObject:@([UnorderedListStyle getType])] ||
[currentActiveStyles
- containsObject:@([BlockQuoteStyle getStyleType])] ||
+ containsObject:@([OrderedListStyle getType])] ||
+ [currentActiveStyles containsObject:@([H1Style getType])] ||
+ [currentActiveStyles containsObject:@([H2Style getType])] ||
+ [currentActiveStyles containsObject:@([H3Style getType])] ||
+ [currentActiveStyles containsObject:@([H4Style getType])] ||
+ [currentActiveStyles containsObject:@([H5Style getType])] ||
+ [currentActiveStyles containsObject:@([H6Style getType])] ||
+ [currentActiveStyles containsObject:@([BlockQuoteStyle getType])] ||
+ [currentActiveStyles containsObject:@([CodeBlockStyle getType])] ||
[currentActiveStyles
- containsObject:@([CodeBlockStyle getStyleType])] ||
- [currentActiveStyles
- containsObject:@([CheckboxListStyle getStyleType])]) {
+ containsObject:@([CheckboxListStyle getType])]) {
[result appendString:@"\n"];
} else {
[result appendString:@"\n
"];
@@ -403,33 +394,26 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range {
// finish the paragraph
// handle ending of some paragraph styles
- if ([previousActiveStyles
- containsObject:@([UnorderedListStyle getStyleType])]) {
+ if ([previousActiveStyles containsObject:@([UnorderedListStyle getType])]) {
[result appendString:@"\n
"];
} else if ([previousActiveStyles
- containsObject:@([OrderedListStyle getStyleType])]) {
+ containsObject:@([OrderedListStyle getType])]) {
[result appendString:@"\n
"];
} else if ([previousActiveStyles
- containsObject:@([BlockQuoteStyle getStyleType])]) {
+ containsObject:@([BlockQuoteStyle getType])]) {
[result appendString:@"\n"];
} else if ([previousActiveStyles
- containsObject:@([CodeBlockStyle getStyleType])]) {
+ containsObject:@([CodeBlockStyle getType])]) {
[result appendString:@"\n"];
} else if ([previousActiveStyles
- containsObject:@([CheckboxListStyle getStyleType])]) {
+ containsObject:@([CheckboxListStyle getType])]) {
[result appendString:@"\n
"];
- } else if ([previousActiveStyles
- containsObject:@([H1Style getStyleType])] ||
- [previousActiveStyles
- containsObject:@([H2Style getStyleType])] ||
- [previousActiveStyles
- containsObject:@([H3Style getStyleType])] ||
- [previousActiveStyles
- containsObject:@([H4Style getStyleType])] ||
- [previousActiveStyles
- containsObject:@([H5Style getStyleType])] ||
- [previousActiveStyles
- containsObject:@([H6Style getStyleType])]) {
+ } else if ([previousActiveStyles containsObject:@([H1Style getType])] ||
+ [previousActiveStyles containsObject:@([H2Style getType])] ||
+ [previousActiveStyles containsObject:@([H3Style getType])] ||
+ [previousActiveStyles containsObject:@([H4Style getType])] ||
+ [previousActiveStyles containsObject:@([H5Style getType])] ||
+ [previousActiveStyles containsObject:@([H6Style getType])]) {
// do nothing, heading closing tag has already been appended
} else {
[result appendString:@"
"];
@@ -484,9 +468,9 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range {
- (NSString *)tagContentForStyle:(NSNumber *)style
openingTag:(BOOL)openingTag
location:(NSInteger)location {
- if ([style isEqualToNumber:@([BoldStyle getStyleType])]) {
+ if ([style isEqualToNumber:@([BoldStyle getType])]) {
return @"b";
- } else if ([style isEqualToNumber:@([ItalicStyle getStyleType])]) {
+ } else if ([style isEqualToNumber:@([ItalicStyle getType])]) {
return @"i";
} else if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
if (openingTag) {
@@ -504,11 +488,11 @@ - (NSString *)tagContentForStyle:(NSNumber *)style
} else {
return @"";
}
- } else if ([style isEqualToNumber:@([UnderlineStyle getStyleType])]) {
+ } else if ([style isEqualToNumber:@([UnderlineStyle getType])]) {
return @"u";
- } else if ([style isEqualToNumber:@([StrikethroughStyle getStyleType])]) {
+ } else if ([style isEqualToNumber:@([StrikethroughStyle getType])]) {
return @"s";
- } else if ([style isEqualToNumber:@([InlineCodeStyle getStyleType])]) {
+ } else if ([style isEqualToNumber:@([InlineCodeStyle getType])]) {
return @"code";
} else if ([style isEqualToNumber:@([LinkStyle getStyleType])]) {
if (openingTag) {
@@ -562,26 +546,26 @@ - (NSString *)tagContentForStyle:(NSNumber *)style
} else {
return @"mention";
}
- } else if ([style isEqualToNumber:@([H1Style getStyleType])]) {
+ } else if ([style isEqualToNumber:@([H1Style getType])]) {
return @"h1";
- } else if ([style isEqualToNumber:@([H2Style getStyleType])]) {
+ } else if ([style isEqualToNumber:@([H2Style getType])]) {
return @"h2";
- } else if ([style isEqualToNumber:@([H3Style getStyleType])]) {
+ } else if ([style isEqualToNumber:@([H3Style getType])]) {
return @"h3";
- } else if ([style isEqualToNumber:@([H4Style getStyleType])]) {
+ } else if ([style isEqualToNumber:@([H4Style getType])]) {
return @"h4";
- } else if ([style isEqualToNumber:@([H5Style getStyleType])]) {
+ } else if ([style isEqualToNumber:@([H5Style getType])]) {
return @"h5";
- } else if ([style isEqualToNumber:@([H6Style getStyleType])]) {
+ } else if ([style isEqualToNumber:@([H6Style getType])]) {
return @"h6";
- } else if ([style isEqualToNumber:@([UnorderedListStyle getStyleType])] ||
- [style isEqualToNumber:@([OrderedListStyle getStyleType])]) {
+ } else if ([style isEqualToNumber:@([UnorderedListStyle getType])] ||
+ [style isEqualToNumber:@([OrderedListStyle getType])]) {
return @"li";
- } else if ([style isEqualToNumber:@([CheckboxListStyle getStyleType])]) {
+ } else if ([style isEqualToNumber:@([CheckboxListStyle getType])]) {
if (openingTag) {
CheckboxListStyle *checkboxListStyleClass =
(CheckboxListStyle *)
- _input->stylesDict[@([CheckboxListStyle getStyleType])];
+ _input->stylesDict[@([CheckboxListStyle getType])];
BOOL checked = [checkboxListStyleClass getCheckboxStateAt:location];
if (checked) {
@@ -591,8 +575,8 @@ - (NSString *)tagContentForStyle:(NSNumber *)style
} else {
return @"li";
}
- } else if ([style isEqualToNumber:@([BlockQuoteStyle getStyleType])] ||
- [style isEqualToNumber:@([CodeBlockStyle getStyleType])]) {
+ } else if ([style isEqualToNumber:@([BlockQuoteStyle getType])] ||
+ [style isEqualToNumber:@([CodeBlockStyle getType])]) {
// blockquotes and codeblock use tags the same way lists use
return @"p";
}
@@ -689,8 +673,7 @@ - (void)applyProcessedStyles:(NSArray *)processedStyles
[((ImageStyle *)baseStyle) addImageAtRange:styleRange
imageData:imgData
withSelection:NO];
- } else if ([styleType
- isEqualToNumber:@([CheckboxListStyle getStyleType])]) {
+ } else if ([styleType isEqualToNumber:@([CheckboxListStyle getType])]) {
NSDictionary *checkboxStates = (NSDictionary *)stylePair.styleValue;
CheckboxListStyle *cbLStyle = (CheckboxListStyle *)baseStyle;
@@ -698,7 +681,10 @@ - (void)applyProcessedStyles:(NSArray *)processedStyles
// unchecked value
BOOL shouldAddTypingAttr =
styleRange.location + styleRange.length == plainTextLength;
- [cbLStyle addAttributes:styleRange withTypingAttr:shouldAddTypingAttr];
+ [cbLStyle addWithChecked:NO
+ range:styleRange
+ withTyping:shouldAddTypingAttr
+ withDirtyRange:YES];
if (!checkboxStates && checkboxStates.count == 0) {
continue;
@@ -1226,9 +1212,9 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
NSMutableArray *styleArr = [[NSMutableArray alloc] init];
StylePair *stylePair = [[StylePair alloc] init];
if ([tagName isEqualToString:@"b"]) {
- [styleArr addObject:@([BoldStyle getStyleType])];
+ [styleArr addObject:@([BoldStyle getType])];
} else if ([tagName isEqualToString:@"i"]) {
- [styleArr addObject:@([ItalicStyle getStyleType])];
+ [styleArr addObject:@([ItalicStyle getType])];
} else if ([tagName isEqualToString:@"img"]) {
NSRegularExpression *srcRegex =
[NSRegularExpression regularExpressionWithPattern:@"src=\"([^\"]+)\""
@@ -1284,11 +1270,11 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
stylePair.styleValue = imageData;
} else if ([tagName isEqualToString:@"u"]) {
- [styleArr addObject:@([UnderlineStyle getStyleType])];
+ [styleArr addObject:@([UnderlineStyle getType])];
} else if ([tagName isEqualToString:@"s"]) {
- [styleArr addObject:@([StrikethroughStyle getStyleType])];
+ [styleArr addObject:@([StrikethroughStyle getType])];
} else if ([tagName isEqualToString:@"code"]) {
- [styleArr addObject:@([InlineCodeStyle getStyleType])];
+ [styleArr addObject:@([InlineCodeStyle getType])];
} else if ([tagName isEqualToString:@"a"]) {
NSRegularExpression *hrefRegex =
[NSRegularExpression regularExpressionWithPattern:@"href=\".+\""
@@ -1354,33 +1340,33 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
} else if ([[tagName substringWithRange:NSMakeRange(0, 1)]
isEqualToString:@"h"]) {
if ([tagName isEqualToString:@"h1"]) {
- [styleArr addObject:@([H1Style getStyleType])];
+ [styleArr addObject:@([H1Style getType])];
} else if ([tagName isEqualToString:@"h2"]) {
- [styleArr addObject:@([H2Style getStyleType])];
+ [styleArr addObject:@([H2Style getType])];
} else if ([tagName isEqualToString:@"h3"]) {
- [styleArr addObject:@([H3Style getStyleType])];
+ [styleArr addObject:@([H3Style getType])];
} else if ([tagName isEqualToString:@"h4"]) {
- [styleArr addObject:@([H4Style getStyleType])];
+ [styleArr addObject:@([H4Style getType])];
} else if ([tagName isEqualToString:@"h5"]) {
- [styleArr addObject:@([H5Style getStyleType])];
+ [styleArr addObject:@([H5Style getType])];
} else if ([tagName isEqualToString:@"h6"]) {
- [styleArr addObject:@([H6Style getStyleType])];
+ [styleArr addObject:@([H6Style getType])];
}
} else if ([tagName isEqualToString:@"ul"]) {
if ([self isUlCheckboxList:params]) {
- [styleArr addObject:@([CheckboxListStyle getStyleType])];
+ [styleArr addObject:@([CheckboxListStyle getType])];
stylePair.styleValue =
[self prepareCheckboxListStyleValue:tagRangeValue
checkboxStates:checkboxStates];
} else {
- [styleArr addObject:@([UnorderedListStyle getStyleType])];
+ [styleArr addObject:@([UnorderedListStyle getType])];
}
} else if ([tagName isEqualToString:@"ol"]) {
- [styleArr addObject:@([OrderedListStyle getStyleType])];
+ [styleArr addObject:@([OrderedListStyle getType])];
} else if ([tagName isEqualToString:@"blockquote"]) {
- [styleArr addObject:@([BlockQuoteStyle getStyleType])];
+ [styleArr addObject:@([BlockQuoteStyle getType])];
} else if ([tagName isEqualToString:@"codeblock"]) {
- [styleArr addObject:@([CodeBlockStyle getStyleType])];
+ [styleArr addObject:@([CodeBlockStyle getType])];
} else {
// some other external tags like span just don't get put into the
// processed styles
diff --git a/ios/interfaces/AttributeEntry.h b/ios/interfaces/AttributeEntry.h
new file mode 100644
index 000000000..798823e7a
--- /dev/null
+++ b/ios/interfaces/AttributeEntry.h
@@ -0,0 +1,9 @@
+#pragma once
+#import
+
+@interface AttributeEntry : NSObject
+
+@property NSString *key;
+@property id value;
+
+@end
diff --git a/ios/interfaces/AttributeEntry.mm b/ios/interfaces/AttributeEntry.mm
new file mode 100644
index 000000000..66ab1fadb
--- /dev/null
+++ b/ios/interfaces/AttributeEntry.mm
@@ -0,0 +1,4 @@
+#import "AttributeEntry.h"
+
+@implementation AttributeEntry
+@end
diff --git a/ios/interfaces/StyleBase.h b/ios/interfaces/StyleBase.h
new file mode 100644
index 000000000..a404b9f93
--- /dev/null
+++ b/ios/interfaces/StyleBase.h
@@ -0,0 +1,36 @@
+#pragma once
+#import "AttributeEntry.h"
+#import "StylePair.h"
+#import "StyleTypeEnum.h"
+#import
+
+@class EnrichedTextInputView;
+
+@interface StyleBase : NSObject
+@property(nonatomic, weak) EnrichedTextInputView *input;
++ (StyleType)getType;
+- (NSString *)getKey;
+- (NSString *)getValue;
+- (BOOL)isParagraph;
+- (BOOL)needsZWS;
+- (instancetype)initWithInput:(EnrichedTextInputView *)input;
+- (NSRange)actualUsedRange:(NSRange)range;
+- (void)toggle:(NSRange)range;
+- (void)add:(NSRange)range
+ withTyping:(BOOL)withTyping
+ withDirtyRange:(BOOL)withDirtyRange;
+- (void)add:(NSRange)range
+ withValue:(NSString *)value
+ withTyping:(BOOL)withTyping
+ withDirtyRange:(BOOL)withDirtyRange;
+- (void)remove:(NSRange)range withDirtyRange:(BOOL)withDirtyRange;
+- (void)addTypingWithValue:(NSString *)value;
+- (void)removeTyping;
+- (BOOL)styleCondition:(id)value range:(NSRange)range;
+- (BOOL)detect:(NSRange)range;
+- (BOOL)any:(NSRange)range;
+- (NSArray *)all:(NSRange)range;
+- (void)applyStyling:(NSRange)range;
+- (void)reapplyAttributesFromStylePair:(StylePair *)pair;
+- (AttributeEntry *)getEntryIfPresent:(NSRange)range;
+@end
diff --git a/ios/interfaces/StyleBase.mm b/ios/interfaces/StyleBase.mm
new file mode 100644
index 000000000..0b1e1ec3e
--- /dev/null
+++ b/ios/interfaces/StyleBase.mm
@@ -0,0 +1,257 @@
+#import "StyleBase.h"
+#import "AttributeEntry.h"
+#import "EnrichedTextInputView.h"
+#import "OccurenceUtils.h"
+#import "RangeUtils.h"
+#import "ZeroWidthSpaceUtils.h"
+
+@implementation StyleBase
+
+// This method gets overridden
++ (StyleType)getType {
+ return None;
+}
+
+// This method gets overridden
+- (NSString *)getKey {
+ if ([self isParagraph]) {
+ return NSParagraphStyleAttributeName;
+ }
+ return @"NoneAttribute";
+}
+
+// Basic inline styles will use this default value, paragraph styles will
+// override it and parametrised ones completely don't use it
+- (NSString *)getValue {
+ return @"AnyValue";
+}
+
+// This method gets overridden
+- (BOOL)isParagraph {
+ return false;
+}
+
+- (BOOL)needsZWS {
+ return NO;
+}
+
+- (instancetype)initWithInput:(EnrichedTextInputView *)input {
+ self = [super init];
+ _input = input;
+ return self;
+}
+
+// aligns range to whole paragraph for the paragraph stlyes
+- (NSRange)actualUsedRange:(NSRange)range {
+ if (![self isParagraph])
+ return range;
+ return [_input->textView.textStorage.string paragraphRangeForRange:range];
+}
+
+- (void)toggle:(NSRange)range {
+ NSRange actualRange = [self actualUsedRange:range];
+
+ BOOL isPresent = [self detect:actualRange];
+ if (actualRange.length >= 1) {
+ isPresent ? [self remove:actualRange withDirtyRange:YES]
+ : [self add:actualRange withTyping:YES withDirtyRange:YES];
+ } else {
+ isPresent ? [self removeTyping] : [self addTypingWithValue:[self getValue]];
+ }
+}
+
+- (void)add:(NSRange)range
+ withTyping:(BOOL)withTyping
+ withDirtyRange:(BOOL)withDirtyRange {
+ [self add:range
+ withValue:[self getValue]
+ withTyping:withTyping
+ withDirtyRange:withDirtyRange];
+}
+
+- (void)add:(NSRange)range
+ withValue:(NSString *)value
+ withTyping:(BOOL)withTyping
+ withDirtyRange:(BOOL)withDirtyRange {
+ NSRange actualRange = [self actualUsedRange:range];
+
+ if (![self isParagraph]) {
+ [_input->textView.textStorage addAttribute:[self getKey]
+ value:value
+ range:actualRange];
+ } else {
+ [_input->textView.textStorage
+ enumerateAttribute:NSParagraphStyleAttributeName
+ inRange:actualRange
+ options:0
+ usingBlock:^(id _Nullable existingValue, NSRange subRange,
+ BOOL *_Nonnull stop) {
+ NSMutableParagraphStyle *pStyle =
+ [(NSParagraphStyle *)existingValue mutableCopy];
+ if (pStyle == nullptr)
+ return;
+ pStyle.textLists =
+ @[ [[NSTextList alloc] initWithMarkerFormat:value
+ options:0] ];
+ [_input->textView.textStorage
+ addAttribute:NSParagraphStyleAttributeName
+ value:pStyle
+ range:subRange];
+ }];
+
+ // Only paragraph styles need additional typing attributes when toggling.
+ if (withTyping) {
+ [self addTypingWithValue:value];
+ }
+ }
+
+ // Notify attributes manager of styling to be re-done if needed.
+ if (withDirtyRange) {
+ [self.input->attributesManager addDirtyRange:actualRange];
+ }
+}
+
+- (void)remove:(NSRange)range withDirtyRange:(BOOL)withDirtyRange {
+ NSRange actualRange = [self actualUsedRange:range];
+
+ if (![self isParagraph]) {
+ [_input->textView.textStorage removeAttribute:[self getKey]
+ range:actualRange];
+ } else {
+ [_input->textView.textStorage
+ enumerateAttribute:NSParagraphStyleAttributeName
+ inRange:actualRange
+ options:0
+ usingBlock:^(id _Nullable existingValue, NSRange subRange,
+ BOOL *_Nonnull stop) {
+ NSMutableParagraphStyle *pStyle =
+ [(NSParagraphStyle *)existingValue mutableCopy];
+ if (pStyle == nullptr)
+ return;
+ pStyle.textLists = @[];
+ [_input->textView.textStorage
+ addAttribute:NSParagraphStyleAttributeName
+ value:pStyle
+ range:subRange];
+ }];
+
+ // Paragraph styles also need typing attributes removal.
+ [self removeTyping];
+ }
+
+ // Notify attributes manager of styling to be re-done if needed.
+ if (withDirtyRange) {
+ [self.input->attributesManager addDirtyRange:actualRange];
+ }
+}
+
+- (void)addTypingWithValue:(NSString *)value {
+ NSMutableDictionary *newTypingAttrs =
+ [_input->textView.typingAttributes mutableCopy];
+
+ if (![self isParagraph]) {
+ newTypingAttrs[[self getKey]] = value;
+ } else {
+ NSMutableParagraphStyle *pStyle =
+ [newTypingAttrs[NSParagraphStyleAttributeName] mutableCopy];
+ pStyle.textLists = @[ [[NSTextList alloc] initWithMarkerFormat:value
+ options:0] ];
+ newTypingAttrs[NSParagraphStyleAttributeName] = pStyle;
+ }
+
+ _input->textView.typingAttributes = newTypingAttrs;
+}
+
+- (void)removeTyping {
+ NSMutableDictionary *newTypingAttrs =
+ [_input->textView.typingAttributes mutableCopy];
+
+ if (![self isParagraph]) {
+ [newTypingAttrs removeObjectForKey:[self getKey]];
+ // attributes manager also needs to be notified of custom attributes that
+ // shouldn't be extended
+ [_input->attributesManager didRemoveTypingAttribute:[self getKey]];
+ } else {
+ NSMutableParagraphStyle *pStyle =
+ [newTypingAttrs[NSParagraphStyleAttributeName] mutableCopy];
+ pStyle.textLists = @[];
+ newTypingAttrs[NSParagraphStyleAttributeName] = pStyle;
+ }
+
+ _input->textView.typingAttributes = newTypingAttrs;
+}
+
+- (BOOL)styleCondition:(id)value range:(NSRange)range {
+ if (![self isParagraph]) {
+ NSString *valueString = (NSString *)value;
+ return valueString != nullptr &&
+ [valueString isEqualToString:[self getValue]];
+ } else {
+ NSParagraphStyle *pStyle = (NSParagraphStyle *)value;
+ return pStyle != nullptr && [pStyle.textLists.firstObject.markerFormat
+ isEqualToString:[self getValue]];
+ }
+}
+
+- (BOOL)detect:(NSRange)range {
+ if (range.length >= 1) {
+ return [OccurenceUtils detect:[self getKey]
+ withInput:_input
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value range:range];
+ }];
+ } else {
+ return [OccurenceUtils detect:[self getKey]
+ withInput:_input
+ atIndex:range.location
+ checkPrevious:[self isParagraph]
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value range:range];
+ }];
+ }
+}
+
+- (BOOL)any:(NSRange)range {
+ return [OccurenceUtils any:[self getKey]
+ withInput:_input
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value range:range];
+ }];
+}
+
+- (NSArray *)all:(NSRange)range {
+ return [OccurenceUtils all:[self getKey]
+ withInput:_input
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value range:range];
+ }];
+}
+
+// This method gets overridden
+- (void)applyStyling:(NSRange)range {
+}
+
+// Called during dirty range re-application to restore a style from a saved
+// StylePair
+- (void)reapplyAttributesFromStylePair:(StylePair *)pair {
+ NSRange range = [pair.rangeValue rangeValue];
+ [self add:range withTyping:NO withDirtyRange:NO];
+}
+
+// Gets a custom attribtue entry for the typingAttributes.
+// Only used with inline styles.
+- (AttributeEntry *)getEntryIfPresent:(NSRange)range {
+ if (![self detect:range]) {
+ return nullptr;
+ }
+
+ AttributeEntry *entry = [[AttributeEntry alloc] init];
+ entry.key = [self getKey];
+ entry.value = [self getValue];
+ return entry;
+}
+
+@end
diff --git a/ios/interfaces/StyleHeaders.h b/ios/interfaces/StyleHeaders.h
index 3adeee7b5..5cb1e3ded 100644
--- a/ios/interfaces/StyleHeaders.h
+++ b/ios/interfaces/StyleHeaders.h
@@ -3,21 +3,21 @@
#import "ImageData.h"
#import "LinkData.h"
#import "MentionParams.h"
+#import "StyleBase.h"
-@interface BoldStyle : NSObject
+@interface BoldStyle : StyleBase
@end
-@interface ItalicStyle : NSObject
+@interface ItalicStyle : StyleBase
@end
-@interface UnderlineStyle : NSObject
+@interface UnderlineStyle : StyleBase
@end
-@interface StrikethroughStyle : NSObject
+@interface StrikethroughStyle : StyleBase
@end
-@interface InlineCodeStyle : NSObject
-- (void)handleNewlines;
+@interface InlineCodeStyle : StyleBase
@end
@interface LinkStyle : NSObject
@@ -51,14 +51,10 @@
- (NSValue *)getActiveMentionRange;
@end
-@interface HeadingStyleBase : NSObject {
- id input;
-}
+@interface HeadingStyleBase : StyleBase
- (CGFloat)getHeadingFontSize;
- (BOOL)isHeadingBold;
- (BOOL)handleNewlinesInRange:(NSRange)range replacementText:(NSString *)text;
-- (void)handleImproperHeadings;
-@property(nonatomic, assign) CGFloat lastAppliedFontSize;
@end
@interface H1Style : HeadingStyleBase
@@ -79,37 +75,31 @@
@interface H6Style : HeadingStyleBase
@end
-@interface UnorderedListStyle : NSObject
-- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text;
+@interface UnorderedListStyle : StyleBase
- (BOOL)tryHandlingListShorcutInRange:(NSRange)range
replacementText:(NSString *)text;
@end
-@interface OrderedListStyle : NSObject
-- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text;
+@interface OrderedListStyle : StyleBase
- (BOOL)tryHandlingListShorcutInRange:(NSRange)range
replacementText:(NSString *)text;
@end
-@interface CheckboxListStyle : NSObject
-- (void)applyStyleWithCheckedValue:(BOOL)checked inRange:(NSRange)range;
-- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text;
-- (BOOL)getCheckboxStateAt:(NSUInteger)location;
+@interface CheckboxListStyle : StyleBase
+- (void)toggleWithChecked:(BOOL)checked range:(NSRange)range;
+- (void)addWithChecked:(BOOL)checked
+ range:(NSRange)range
+ withTyping:(BOOL)withTyping
+ withDirtyRange:(BOOL)withDirtyRange;
- (void)toggleCheckedAt:(NSUInteger)location;
+- (BOOL)getCheckboxStateAt:(NSUInteger)location;
- (BOOL)handleNewlinesInRange:(NSRange)range replacementText:(NSString *)text;
-- (void)addAttributesWithCheckedValue:(BOOL)checked
- inRange:(NSRange)range
- withTypingAttr:(BOOL)withTypingAttr;
@end
-@interface BlockQuoteStyle : NSObject
-- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text;
-- (void)manageBlockquoteColor;
+@interface BlockQuoteStyle : StyleBase
@end
-@interface CodeBlockStyle : NSObject
-- (void)manageCodeBlockFontAndColor;
-- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text;
+@interface CodeBlockStyle : StyleBase
@end
@interface ImageStyle : NSObject
diff --git a/ios/styles/BlockQuoteStyle.mm b/ios/styles/BlockQuoteStyle.mm
index 0f1a6fb67..fc3b02896 100644
--- a/ios/styles/BlockQuoteStyle.mm
+++ b/ios/styles/BlockQuoteStyle.mm
@@ -1,293 +1,58 @@
#import "ColorExtension.h"
#import "EnrichedTextInputView.h"
-#import "OccurenceUtils.h"
-#import "ParagraphsUtils.h"
#import "StyleHeaders.h"
-#import "TextInsertionUtils.h"
-@implementation BlockQuoteStyle {
- EnrichedTextInputView *_input;
- NSArray *_stylesToExclude;
-}
+@implementation BlockQuoteStyle
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return BlockQuote;
}
-+ (BOOL)isParagraphStyle {
- return YES;
-}
-
-- (instancetype)initWithInput:(id)input {
- self = [super init];
- _input = (EnrichedTextInputView *)input;
- _stylesToExclude = @[ @(InlineCode), @(Mention), @(Link) ];
- return self;
-}
-
-- (CGFloat)getHeadIndent {
- // rectangle width + gap
- return [_input->config blockquoteBorderWidth] +
- [_input->config blockquoteGapWidth];
+- (NSString *)getValue {
+ return @"EnrichedBlockQuote";
}
-// the range will already be the full paragraph/s range
-- (void)applyStyle:(NSRange)range {
- BOOL isStylePresent = [self detectStyle:range];
- if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
- } else {
- isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
- }
-}
-
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
- NSArray *paragraphs =
- [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView
- range:range];
- // if we fill empty lines with zero width spaces, we need to offset later
- // ranges
- NSInteger offset = 0;
- NSRange preModificationRange = _input->textView.selectedRange;
-
- // to not emit any space filling selection/text changes
- _input->blockEmitting = YES;
-
- for (NSValue *value in paragraphs) {
- NSRange pRange = NSMakeRange([value rangeValue].location + offset,
- [value rangeValue].length);
-
- // length 0 with first line, length 1 and newline with some empty lines in
- // the middle
- if (pRange.length == 0 ||
- (pRange.length == 1 &&
- [[NSCharacterSet newlineCharacterSet]
- characterIsMember:[_input->textView.textStorage.string
- characterAtIndex:pRange.location]])) {
- [TextInsertionUtils insertText:@"\u200B"
- at:pRange.location
- additionalAttributes:nullptr
- input:_input
- withSelection:NO];
- pRange = NSMakeRange(pRange.location, pRange.length + 1);
- offset += 1;
- }
-
- [_input->textView.textStorage
- enumerateAttribute:NSParagraphStyleAttributeName
- inRange:pRange
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- NSMutableParagraphStyle *pStyle =
- [(NSParagraphStyle *)value mutableCopy];
- pStyle.headIndent = [self getHeadIndent];
- pStyle.firstLineHeadIndent = [self getHeadIndent];
- [_input->textView.textStorage
- addAttribute:NSParagraphStyleAttributeName
- value:pStyle
- range:range];
- }];
- }
-
- // back to emitting
- _input->blockEmitting = NO;
-
- if (preModificationRange.length == 0) {
- // fix selection if only one line was possibly made a list and filled with a
- // space
- _input->textView.selectedRange = preModificationRange;
- } else {
- // in other cases, fix the selection with newly made offsets
- _input->textView.selectedRange = NSMakeRange(
- preModificationRange.location, preModificationRange.length + offset);
- }
-
- // also add typing attributes
- if (withTypingAttr) {
- NSMutableDictionary *typingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- NSMutableParagraphStyle *pStyle =
- [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
- pStyle.headIndent = [self getHeadIndent];
- pStyle.firstLineHeadIndent = [self getHeadIndent];
- typingAttrs[NSParagraphStyleAttributeName] = pStyle;
- _input->textView.typingAttributes = typingAttrs;
- }
-}
-
-// does pretty much the same as addAttributes
-- (void)addTypingAttributes {
- [self addAttributes:_input->textView.selectedRange withTypingAttr:YES];
-}
-
-- (void)removeAttributes:(NSRange)range {
- NSArray *paragraphs =
- [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView
- range:range];
-
- for (NSValue *value in paragraphs) {
- NSRange pRange = [value rangeValue];
- [_input->textView.textStorage
- enumerateAttribute:NSParagraphStyleAttributeName
- inRange:pRange
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- NSMutableParagraphStyle *pStyle =
- [(NSParagraphStyle *)value mutableCopy];
- pStyle.headIndent = 0;
- pStyle.firstLineHeadIndent = 0;
- [_input->textView.textStorage
- addAttribute:NSParagraphStyleAttributeName
- value:pStyle
- range:range];
- }];
- }
-
- // also remove typing attributes
- NSMutableDictionary *typingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- NSMutableParagraphStyle *pStyle =
- [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
- pStyle.headIndent = 0;
- pStyle.firstLineHeadIndent = 0;
- typingAttrs[NSParagraphStyleAttributeName] = pStyle;
- _input->textView.typingAttributes = typingAttrs;
-}
-
-// needed for the sake of style conflicts, needs to do exactly the same as
-// removeAttribtues
-- (void)removeTypingAttributes {
- [self removeAttributes:_input->textView.selectedRange];
-}
-
-- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
- if ([self detectStyle:_input->textView.selectedRange] && text.length == 0) {
- // backspace while the style is active
-
- NSRange paragraphRange = [_input->textView.textStorage.string
- paragraphRangeForRange:_input->textView.selectedRange];
-
- if (NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) {
- // a backspace on the very first input's line quote
- // it doesn't run textVieDidChange so we need to manually remove
- // attributes
- [self removeAttributes:paragraphRange];
- return YES;
- } else if (range.location == paragraphRange.location - 1) {
- // same case in other lines; here, the removed range location will be
- // exactly 1 less than paragraph range location
- [self removeAttributes:paragraphRange];
- return YES;
- }
- }
- return NO;
-}
-
-- (BOOL)styleCondition:(id _Nullable)value range:(NSRange)range {
- NSParagraphStyle *pStyle = (NSParagraphStyle *)value;
- return pStyle != nullptr && pStyle.headIndent == [self getHeadIndent] &&
- pStyle.firstLineHeadIndent == [self getHeadIndent] &&
- pStyle.textLists.count == 0;
-}
-
-- (BOOL)detectStyle:(NSRange)range {
- if (range.length >= 1) {
- return [OccurenceUtils detect:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- } else {
- return [OccurenceUtils detect:NSParagraphStyleAttributeName
- withInput:_input
- atIndex:range.location
- checkPrevious:YES
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- }
-}
-
-- (BOOL)anyOccurence:(NSRange)range {
- return [OccurenceUtils any:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
+- (BOOL)isParagraph {
+ return YES;
}
-- (NSArray *_Nullable)findAllOccurences:(NSRange)range {
- return [OccurenceUtils all:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
+- (BOOL)needsZWS {
+ return YES;
}
-// general checkup correcting blockquote color
-// since links, mentions and inline code affects coloring, the checkup gets done
-// only outside of them
-- (void)manageBlockquoteColor {
- if ([[_input->config blockquoteColor]
- isEqualToColor:[_input->config primaryColor]]) {
- return;
- }
-
- NSRange wholeRange =
- NSMakeRange(0, _input->textView.textStorage.string.length);
-
- NSArray *paragraphs =
- [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView
- range:wholeRange];
- for (NSValue *pValue in paragraphs) {
- NSRange paragraphRange = [pValue rangeValue];
- NSArray *properRanges = [OccurenceUtils getRangesWithout:_stylesToExclude
- withInput:_input
- inRange:paragraphRange];
-
- for (NSValue *value in properRanges) {
- NSRange currRange = [value rangeValue];
- BOOL selfDetected = [self detectStyle:currRange];
-
- [_input->textView.textStorage
- enumerateAttribute:NSForegroundColorAttributeName
- inRange:currRange
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- UIColor *newColor = nullptr;
- BOOL colorApplied = [(UIColor *)value
- isEqualToColor:[_input->config blockquoteColor]];
-
- if (colorApplied && !selfDetected) {
- newColor = [_input->config primaryColor];
- } else if (!colorApplied && selfDetected) {
- newColor = [_input->config blockquoteColor];
- }
-
- if (newColor != nullptr) {
- [_input->textView.textStorage
- addAttribute:NSForegroundColorAttributeName
- value:newColor
- range:currRange];
- [_input->textView.textStorage
- addAttribute:NSUnderlineColorAttributeName
- value:newColor
- range:currRange];
- [_input->textView.textStorage
- addAttribute:NSStrikethroughColorAttributeName
- value:newColor
- range:currRange];
- }
- }];
- }
- }
+- (void)applyStyling:(NSRange)range {
+ NSRange fullRange =
+ [self.input->textView.textStorage.string paragraphRangeForRange:range];
+
+ CGFloat indent = [self.input->config blockquoteBorderWidth] +
+ [self.input->config blockquoteGapWidth];
+ [self.input->textView.textStorage
+ enumerateAttribute:NSParagraphStyleAttributeName
+ inRange:fullRange
+ options:0
+ usingBlock:^(id _Nullable value, NSRange subRange,
+ BOOL *_Nonnull stop) {
+ NSMutableParagraphStyle *pStyle =
+ [(NSParagraphStyle *)value mutableCopy];
+ pStyle.headIndent = indent;
+ pStyle.firstLineHeadIndent = indent;
+ [self.input->textView.textStorage
+ addAttribute:NSParagraphStyleAttributeName
+ value:pStyle
+ range:subRange];
+ }];
+
+ UIColor *bqColor = [self.input->config blockquoteColor];
+ [self.input->textView.textStorage addAttribute:NSForegroundColorAttributeName
+ value:bqColor
+ range:range];
+ [self.input->textView.textStorage addAttribute:NSUnderlineColorAttributeName
+ value:bqColor
+ range:range];
+ [self.input->textView.textStorage
+ addAttribute:NSStrikethroughColorAttributeName
+ value:bqColor
+ range:range];
}
@end
diff --git a/ios/styles/BoldStyle.mm b/ios/styles/BoldStyle.mm
index 0f8a37432..c9f25123d 100644
--- a/ios/styles/BoldStyle.mm
+++ b/ios/styles/BoldStyle.mm
@@ -1,39 +1,23 @@
#import "EnrichedTextInputView.h"
#import "FontExtension.h"
-#import "OccurenceUtils.h"
#import "StyleHeaders.h"
-@implementation BoldStyle {
- EnrichedTextInputView *_input;
-}
+@implementation BoldStyle : StyleBase
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return Bold;
}
-+ (BOOL)isParagraphStyle {
- return NO;
-}
-
-- (instancetype)initWithInput:(id)input {
- self = [super init];
- _input = (EnrichedTextInputView *)input;
- return self;
+- (NSString *)getKey {
+ return @"EnrichedBold";
}
-- (void)applyStyle:(NSRange)range {
- BOOL isStylePresent = [self detectStyle:range];
- if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
- } else {
- isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
- }
+- (BOOL)isParagraph {
+ return NO;
}
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
- [_input->textView.textStorage beginEditing];
- [_input->textView.textStorage
+- (void)applyStyling:(NSRange)range {
+ [self.input->textView.textStorage
enumerateAttribute:NSFontAttributeName
inRange:range
options:0
@@ -42,133 +26,12 @@ - (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
UIFont *font = (UIFont *)value;
if (font != nullptr) {
UIFont *newFont = [font setBold];
- [_input->textView.textStorage addAttribute:NSFontAttributeName
- value:newFont
- range:range];
+ [self.input->textView.textStorage
+ addAttribute:NSFontAttributeName
+ value:newFont
+ range:range];
}
}];
- [_input->textView.textStorage endEditing];
-}
-
-- (void)addTypingAttributes {
- UIFont *currentFontAttr =
- (UIFont *)_input->textView.typingAttributes[NSFontAttributeName];
- if (currentFontAttr != nullptr) {
- NSMutableDictionary *newTypingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- newTypingAttrs[NSFontAttributeName] = [currentFontAttr setBold];
- _input->textView.typingAttributes = newTypingAttrs;
- }
-}
-
-- (void)removeAttributes:(NSRange)range {
- [_input->textView.textStorage beginEditing];
- [_input->textView.textStorage
- enumerateAttribute:NSFontAttributeName
- inRange:range
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- UIFont *font = (UIFont *)value;
- if (font != nullptr) {
- UIFont *newFont = [font removeBold];
- [_input->textView.textStorage addAttribute:NSFontAttributeName
- value:newFont
- range:range];
- }
- }];
- [_input->textView.textStorage endEditing];
-}
-
-- (void)removeTypingAttributes {
- UIFont *currentFontAttr =
- (UIFont *)_input->textView.typingAttributes[NSFontAttributeName];
- if (currentFontAttr != nullptr) {
- NSMutableDictionary *newTypingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- newTypingAttrs[NSFontAttributeName] = [currentFontAttr removeBold];
- _input->textView.typingAttributes = newTypingAttrs;
- }
-}
-
-- (BOOL)boldHeadingConflictsInRange:(NSRange)range type:(StyleType)type {
- if (type == H1) {
- if (![_input->config h1Bold]) {
- return NO;
- }
- } else if (type == H2) {
- if (![_input->config h2Bold]) {
- return NO;
- }
- } else if (type == H3) {
- if (![_input->config h3Bold]) {
- return NO;
- }
- } else if (type == H4) {
- if (![_input->config h4Bold]) {
- return NO;
- }
- } else if (type == H5) {
- if (![_input->config h5Bold]) {
- return NO;
- }
- } else if (type == H6) {
- if (![_input->config h6Bold]) {
- return NO;
- }
- }
-
- id headingStyle = _input->stylesDict[@(type)];
- return range.length > 0 ? [headingStyle anyOccurence:range]
- : [headingStyle detectStyle:range];
-}
-
-- (BOOL)styleCondition:(id _Nullable)value range:(NSRange)range {
- UIFont *font = (UIFont *)value;
- return font != nullptr && [font isBold] &&
- ![self boldHeadingConflictsInRange:range type:H1] &&
- ![self boldHeadingConflictsInRange:range type:H2] &&
- ![self boldHeadingConflictsInRange:range type:H3] &&
- ![self boldHeadingConflictsInRange:range type:H4] &&
- ![self boldHeadingConflictsInRange:range type:H5] &&
- ![self boldHeadingConflictsInRange:range type:H6];
-}
-
-- (BOOL)detectStyle:(NSRange)range {
- if (range.length >= 1) {
- return [OccurenceUtils detect:NSFontAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- } else {
- return [OccurenceUtils detect:NSFontAttributeName
- withInput:_input
- atIndex:range.location
- checkPrevious:NO
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- }
-}
-
-- (BOOL)anyOccurence:(NSRange)range {
- return [OccurenceUtils any:NSFontAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
-}
-
-- (NSArray *_Nullable)findAllOccurences:(NSRange)range {
- return [OccurenceUtils all:NSFontAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
}
@end
diff --git a/ios/styles/CheckboxListStyle.mm b/ios/styles/CheckboxListStyle.mm
index b6a2b8a6b..ae7f740a4 100644
--- a/ios/styles/CheckboxListStyle.mm
+++ b/ios/styles/CheckboxListStyle.mm
@@ -1,321 +1,153 @@
#import "EnrichedTextInputView.h"
-#import "FontExtension.h"
-#import "OccurenceUtils.h"
-#import "ParagraphsUtils.h"
+#import "RangeUtils.h"
#import "StyleHeaders.h"
#import "TextInsertionUtils.h"
-@implementation CheckboxListStyle {
- EnrichedTextInputView *_input;
-}
+@implementation CheckboxListStyle
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return CheckboxList;
}
-+ (BOOL)isParagraphStyle {
- return YES;
+- (NSString *)getValue {
+ return @"EnrichedCheckbox0";
}
-- (CGFloat)getHeadIndent {
- return [_input->config checkboxListMarginLeft] +
- [_input->config checkboxListGapWidth] +
- [_input->config checkboxListBoxSize];
-}
-
-- (instancetype)initWithInput:(id)input {
- self = [super init];
- _input = (EnrichedTextInputView *)input;
- return self;
+- (BOOL)isParagraph {
+ return YES;
}
-- (void)applyStyle:(NSRange)range {
- // Applying a checkbox list style requires a checked value,
- // which is why this method is not implemented.
- // Use 'applyStyleWithCheckedValue' and provide the checked value instead.
+- (BOOL)needsZWS {
+ return YES;
}
-- (void)applyStyleWithCheckedValue:(BOOL)checked inRange:(NSRange)range {
- BOOL isStylePresent = [self detectStyle:range];
- if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributesWithCheckedValue:checked
- inRange:range
- withTypingAttr:YES];
+- (void)applyStyling:(NSRange)range {
+ CGFloat listHeadIndent = [self.input->config checkboxListMarginLeft] +
+ [self.input->config checkboxListGapWidth] +
+ [self.input->config checkboxListBoxSize];
+
+ [self.input->textView.textStorage
+ enumerateAttribute:NSParagraphStyleAttributeName
+ inRange:range
+ options:0
+ usingBlock:^(id _Nullable value, NSRange range,
+ BOOL *_Nonnull stop) {
+ NSMutableParagraphStyle *pStyle =
+ [(NSParagraphStyle *)value mutableCopy];
+ pStyle.headIndent = listHeadIndent;
+ pStyle.firstLineHeadIndent = listHeadIndent;
+ [self.input->textView.textStorage
+ addAttribute:NSParagraphStyleAttributeName
+ value:pStyle
+ range:range];
+ }];
+}
+
+- (BOOL)styleCondition:(id)value range:(NSRange)range {
+ NSParagraphStyle *pStyle = (NSParagraphStyle *)value;
+ return pStyle != nullptr && pStyle.textLists.count == 1 &&
+ [pStyle.textLists.firstObject.markerFormat
+ hasPrefix:@"EnrichedCheckbox"];
+}
+
+- (void)toggleWithChecked:(BOOL)checked range:(NSRange)range {
+ NSRange actualRange = [self actualUsedRange:range];
+ BOOL isPresent = [self detect:actualRange];
+
+ if (isPresent) {
+ [self remove:actualRange withDirtyRange:YES];
} else {
- isStylePresent
- ? [self removeTypingAttributes]
- : [self addAttributesWithCheckedValue:checked
- inRange:_input->textView.selectedRange
- withTypingAttr:YES];
- ;
+ [self addWithChecked:checked
+ range:actualRange
+ withTyping:YES
+ withDirtyRange:YES];
}
}
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
- [self addAttributesWithCheckedValue:NO
- inRange:range
- withTypingAttr:withTypingAttr];
+- (void)addWithChecked:(BOOL)checked
+ range:(NSRange)range
+ withTyping:(BOOL)withTyping
+ withDirtyRange:(BOOL)withDirtyRange {
+ NSString *value = checked ? @"EnrichedCheckbox1" : @"EnrichedCheckbox0";
+ [self add:range
+ withValue:value
+ withTyping:withTyping
+ withDirtyRange:withDirtyRange];
}
-- (void)addTypingAttributes {
+// During dirty range re-application the default add: would use getValue
+// (EnrichedCheckbox0) and lose the checked state. Instead, read the original
+// marker format from the saved StylePair
+- (void)reapplyAttributesFromStylePair:(StylePair *)pair {
+ NSRange range = [pair.rangeValue rangeValue];
+ NSParagraphStyle *savedPStyle = (NSParagraphStyle *)pair.styleValue;
+ BOOL checked =
+ savedPStyle != nullptr && [savedPStyle.textLists.firstObject.markerFormat
+ isEqualToString:@"EnrichedCheckbox1"];
+ [self addWithChecked:checked range:range withTyping:NO withDirtyRange:NO];
}
-- (void)removeAttributes:(NSRange)range {
- NSArray *paragraphs =
- [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView
- range:range];
-
- [_input->textView.textStorage beginEditing];
-
- for (NSValue *value in paragraphs) {
- NSRange range = [value rangeValue];
- [_input->textView.textStorage
- enumerateAttribute:NSParagraphStyleAttributeName
- inRange:range
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- NSMutableParagraphStyle *pStyle =
- [(NSParagraphStyle *)value mutableCopy];
- pStyle.textLists = @[];
- pStyle.headIndent = 0;
- pStyle.firstLineHeadIndent = 0;
- [_input->textView.textStorage
- addAttribute:NSParagraphStyleAttributeName
- value:pStyle
- range:range];
- }];
+- (void)toggleCheckedAt:(NSUInteger)location {
+ if (location >= self.input->textView.textStorage.length) {
+ return;
}
- [_input->textView.textStorage endEditing];
+ NSParagraphStyle *pStyle =
+ [self.input->textView.textStorage attribute:NSParagraphStyleAttributeName
+ atIndex:location
+ effectiveRange:NULL];
+ NSTextList *list = pStyle.textLists.firstObject;
- // also remove typing attributes
- NSMutableDictionary *typingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- NSMutableParagraphStyle *pStyle =
- [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
- pStyle.textLists = @[];
- pStyle.headIndent = 0;
- pStyle.firstLineHeadIndent = 0;
- typingAttrs[NSParagraphStyleAttributeName] = pStyle;
- _input->textView.typingAttributes = typingAttrs;
-}
+ BOOL isCurrentlyChecked =
+ [list.markerFormat isEqualToString:@"EnrichedCheckbox1"];
-- (void)removeTypingAttributes {
- [self removeAttributes:_input->textView.selectedRange];
+ NSRange paragraphRange = [self.input->textView.textStorage.string
+ paragraphRangeForRange:NSMakeRange(location, 0)];
+
+ [self addWithChecked:!isCurrentlyChecked
+ range:paragraphRange
+ withTyping:YES
+ withDirtyRange:YES];
}
-- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
- if ([self detectStyle:_input->textView.selectedRange] && text.length == 0) {
- // backspace while the style is active
+- (BOOL)getCheckboxStateAt:(NSUInteger)location {
+ if (location >= self.input->textView.textStorage.length) {
+ return NO;
+ }
- NSRange paragraphRange = [_input->textView.textStorage.string
- paragraphRangeForRange:_input->textView.selectedRange];
+ NSParagraphStyle *style =
+ [self.input->textView.textStorage attribute:NSParagraphStyleAttributeName
+ atIndex:location
+ effectiveRange:NULL];
- if (NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) {
- // a backspace on the very first input's line list point
- // it doesn't run textVieDidChange so we need to manually remove
- // attributes
- [self removeAttributes:paragraphRange];
- return YES;
- } else if (range.location == paragraphRange.location - 1) {
- // same case in other lines; here, the removed range location will be
- // exactly 1 less than paragraph range location
- [self removeAttributes:paragraphRange];
+ if (style && style.textLists.count > 0) {
+ NSTextList *list = style.textLists.firstObject;
+ if ([list.markerFormat isEqualToString:@"EnrichedCheckbox1"]) {
return YES;
}
}
+
return NO;
}
-// used to make sure checkbox marker is correct when a newline is placed
- (BOOL)handleNewlinesInRange:(NSRange)range replacementText:(NSString *)text {
- // in a checkbox list and a new text ends with a newline
- if ([self detectStyle:_input->textView.selectedRange] && text.length > 0 &&
+ if ([self detect:self.input->textView.selectedRange] && text.length > 0 &&
[[NSCharacterSet newlineCharacterSet]
characterIsMember:[text characterAtIndex:text.length - 1]]) {
// do the replacement manually
[TextInsertionUtils replaceText:text
at:range
additionalAttributes:nullptr
- input:_input
+ input:self.input
withSelection:YES];
- // apply checkbox attributes to the new paragraph
- [self addAttributes:_input->textView.selectedRange withTypingAttr:YES];
+ // apply unchecked checkbox attributes to the new paragraph
+ [self addWithChecked:NO
+ range:self.input->textView.selectedRange
+ withTyping:YES
+ withDirtyRange:YES];
return YES;
}
return NO;
}
-- (void)toggleCheckedAt:(NSUInteger)location {
- if (location >= _input->textView.textStorage.length) {
- return;
- }
-
- NSParagraphStyle *pStyle =
- [_input->textView.textStorage attribute:NSParagraphStyleAttributeName
- atIndex:location
- effectiveRange:NULL];
- NSTextList *list = pStyle.textLists.firstObject;
-
- BOOL isCurrentlyChecked = [list.markerFormat isEqualToString:@"{checkbox:1}"];
-
- NSString *fullText = _input->textView.textStorage.string;
- NSRange paragraphRange =
- [fullText paragraphRangeForRange:NSMakeRange(location, 0)];
-
- [self addAttributesWithCheckedValue:!isCurrentlyChecked
- inRange:paragraphRange
- withTypingAttr:YES];
-}
-
-- (void)addAttributesWithCheckedValue:(BOOL)checked
- inRange:(NSRange)range
- withTypingAttr:(BOOL)withTypingAttr {
- NSString *markerFormat = checked ? @"{checkbox:1}" : @"{checkbox:0}";
- NSTextList *checkboxMarker =
- [[NSTextList alloc] initWithMarkerFormat:markerFormat options:0];
- NSArray *paragraphs =
- [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView
- range:range];
- // if we fill empty lines with zero width spaces, we need to offset later
- // ranges
- NSInteger offset = 0;
- // needed for range adjustments
- NSRange preModificationRange = _input->textView.selectedRange;
-
- // let's not emit some weird selection changes or text/html changes
- _input->blockEmitting = YES;
-
- for (NSValue *value in paragraphs) {
- // take previous offsets into consideration
- NSRange fixedRange = NSMakeRange([value rangeValue].location + offset,
- [value rangeValue].length);
-
- // length 0 with first line, length 1 and newline with some empty lines in
- // the middle
- if (fixedRange.length == 0 ||
- (fixedRange.length == 1 &&
- [[NSCharacterSet newlineCharacterSet]
- characterIsMember:[_input->textView.textStorage.string
- characterAtIndex:fixedRange.location]])) {
- [TextInsertionUtils insertText:@"\u200B"
- at:fixedRange.location
- additionalAttributes:nullptr
- input:_input
- withSelection:NO];
- fixedRange = NSMakeRange(fixedRange.location, fixedRange.length + 1);
- offset += 1;
- }
-
- [_input->textView.textStorage
- enumerateAttribute:NSParagraphStyleAttributeName
- inRange:fixedRange
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- NSMutableParagraphStyle *pStyle =
- [(NSParagraphStyle *)value mutableCopy];
- pStyle.textLists = @[ checkboxMarker ];
- pStyle.headIndent = [self getHeadIndent];
- pStyle.firstLineHeadIndent = [self getHeadIndent];
- [_input->textView.textStorage
- addAttribute:NSParagraphStyleAttributeName
- value:pStyle
- range:range];
- }];
- }
-
- // back to emitting
- _input->blockEmitting = NO;
-
- if (preModificationRange.length == 0) {
- // fix selection if only one line was possibly made a list and filled with a
- // space
- _input->textView.selectedRange = preModificationRange;
- } else {
- // in other cases, fix the selection with newly made offsets
- _input->textView.selectedRange = NSMakeRange(
- preModificationRange.location, preModificationRange.length + offset);
- }
-
- // also add typing attributes
- if (withTypingAttr) {
- NSMutableDictionary *typingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- NSMutableParagraphStyle *pStyle =
- [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
- pStyle.textLists = @[ checkboxMarker ];
- pStyle.headIndent = [self getHeadIndent];
- pStyle.firstLineHeadIndent = [self getHeadIndent];
- typingAttrs[NSParagraphStyleAttributeName] = pStyle;
- _input->textView.typingAttributes = typingAttrs;
- }
-}
-
-- (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
- NSParagraphStyle *paragraph = (NSParagraphStyle *)value;
- return paragraph != nullptr && paragraph.textLists.count == 1 &&
- [paragraph.textLists.firstObject.markerFormat hasPrefix:@"{checkbox"];
-}
-
-- (BOOL)detectStyle:(NSRange)range {
- if (range.length >= 1) {
- return [OccurenceUtils detect:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value:range];
- }];
- } else {
- return [OccurenceUtils detect:NSParagraphStyleAttributeName
- withInput:_input
- atIndex:range.location
- checkPrevious:YES
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value:range];
- }];
- }
-}
-
-- (BOOL)anyOccurence:(NSRange)range {
- return [OccurenceUtils any:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value:range];
- }];
-}
-
-- (NSArray *_Nullable)findAllOccurences:(NSRange)range {
- return [OccurenceUtils all:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value:range];
- }];
-}
-
-- (BOOL)getCheckboxStateAt:(NSUInteger)location {
- if (location >= _input->textView.textStorage.length) {
- return NO;
- }
-
- NSParagraphStyle *style =
- [_input->textView.textStorage attribute:NSParagraphStyleAttributeName
- atIndex:location
- effectiveRange:NULL];
-
- if (style && style.textLists.count > 0) {
- NSTextList *list = style.textLists.firstObject;
-
- if ([list.markerFormat isEqualToString:@"{checkbox:1}"]) {
- return YES;
- }
- }
-
- return NO;
-}
-
@end
diff --git a/ios/styles/CodeBlockStyle.mm b/ios/styles/CodeBlockStyle.mm
index 3495a1ddb..f4576b22d 100644
--- a/ios/styles/CodeBlockStyle.mm
+++ b/ios/styles/CodeBlockStyle.mm
@@ -1,304 +1,52 @@
-#import "ColorExtension.h"
#import "EnrichedTextInputView.h"
#import "FontExtension.h"
-#import "OccurenceUtils.h"
-#import "ParagraphsUtils.h"
#import "StyleHeaders.h"
-#import "TextInsertionUtils.h"
-@implementation CodeBlockStyle {
- EnrichedTextInputView *_input;
- NSArray *_stylesToExclude;
-}
+@implementation CodeBlockStyle
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return CodeBlock;
}
-+ (BOOL)isParagraphStyle {
- return YES;
-}
-
-- (instancetype)initWithInput:(id)input {
- self = [super init];
- _input = (EnrichedTextInputView *)input;
- _stylesToExclude = @[ @(InlineCode), @(Mention), @(Link) ];
- return self;
-}
-
-- (void)applyStyle:(NSRange)range {
- BOOL isStylePresent = [self detectStyle:range];
- if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
- } else {
- isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
- }
-}
-
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
- NSTextList *codeBlockList =
- [[NSTextList alloc] initWithMarkerFormat:@"codeblock" options:0];
- NSArray *paragraphs =
- [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView
- range:range];
- // if we fill empty lines with zero width spaces, we need to offset later
- // ranges
- NSInteger offset = 0;
- NSRange preModificationRange = _input->textView.selectedRange;
-
- // to not emit any space filling selection/text changes
- _input->blockEmitting = YES;
-
- for (NSValue *value in paragraphs) {
- NSRange pRange = NSMakeRange([value rangeValue].location + offset,
- [value rangeValue].length);
- // length 0 with first line, length 1 and newline with some empty lines in
- // the middle
- if (pRange.length == 0 ||
- (pRange.length == 1 &&
- [[NSCharacterSet newlineCharacterSet]
- characterIsMember:[_input->textView.textStorage.string
- characterAtIndex:pRange.location]])) {
- [TextInsertionUtils insertText:@"\u200B"
- at:pRange.location
- additionalAttributes:nullptr
- input:_input
- withSelection:NO];
- pRange = NSMakeRange(pRange.location, pRange.length + 1);
- offset += 1;
- }
-
- [_input->textView.textStorage
- enumerateAttribute:NSParagraphStyleAttributeName
- inRange:pRange
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- NSMutableParagraphStyle *pStyle =
- [(NSParagraphStyle *)value mutableCopy];
- pStyle.textLists = @[ codeBlockList ];
- [_input->textView.textStorage
- addAttribute:NSParagraphStyleAttributeName
- value:pStyle
- range:range];
- }];
- }
-
- // back to emitting
- _input->blockEmitting = NO;
-
- if (preModificationRange.length == 0) {
- // fix selection if only one line was possibly made a list and filled with a
- // space
- _input->textView.selectedRange = preModificationRange;
- } else {
- // in other cases, fix the selection with newly made offsets
- _input->textView.selectedRange = NSMakeRange(
- preModificationRange.location, preModificationRange.length + offset);
- }
-
- // also add typing attributes
- if (withTypingAttr) {
- NSMutableDictionary *typingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- NSMutableParagraphStyle *pStyle =
- [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
- pStyle.textLists = @[ codeBlockList ];
- typingAttrs[NSParagraphStyleAttributeName] = pStyle;
-
- _input->textView.typingAttributes = typingAttrs;
- }
-}
-
-- (void)addTypingAttributes {
- [self addAttributes:_input->textView.selectedRange withTypingAttr:YES];
-}
-
-- (void)removeAttributes:(NSRange)range {
- NSArray *paragraphs =
- [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView
- range:range];
-
- [_input->textView.textStorage beginEditing];
-
- for (NSValue *value in paragraphs) {
- NSRange pRange = [value rangeValue];
-
- [_input->textView.textStorage
- enumerateAttribute:NSParagraphStyleAttributeName
- inRange:pRange
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- NSMutableParagraphStyle *pStyle =
- [(NSParagraphStyle *)value mutableCopy];
- pStyle.textLists = @[];
- [_input->textView.textStorage
- addAttribute:NSParagraphStyleAttributeName
- value:pStyle
- range:range];
- }];
- }
-
- [_input->textView.textStorage endEditing];
-
- // also remove typing attributes
- NSMutableDictionary *typingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- NSMutableParagraphStyle *pStyle =
- [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
- pStyle.textLists = @[];
-
- typingAttrs[NSParagraphStyleAttributeName] = pStyle;
-
- _input->textView.typingAttributes = typingAttrs;
-}
-
-- (void)removeTypingAttributes {
- [self removeAttributes:_input->textView.selectedRange];
-}
-
-- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
- if ([self detectStyle:_input->textView.selectedRange] && text.length == 0) {
- // backspace while the style is active
-
- NSRange paragraphRange = [_input->textView.textStorage.string
- paragraphRangeForRange:_input->textView.selectedRange];
-
- if (NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) {
- // a backspace on the very first input's line quote
- // it doesn't run textVieDidChange so we need to manually remove
- // attributes
- [self removeAttributes:paragraphRange];
- return YES;
- }
- }
- return NO;
-}
-
-- (BOOL)styleCondition:(id _Nullable)value range:(NSRange)range {
- NSParagraphStyle *paragraph = (NSParagraphStyle *)value;
- return paragraph != nullptr && paragraph.textLists.count == 1 &&
- [paragraph.textLists.firstObject.markerFormat
- isEqualToString:@"codeblock"];
-}
-
-- (BOOL)detectStyle:(NSRange)range {
- if (range.length >= 1) {
- return [OccurenceUtils detect:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- } else {
- return [OccurenceUtils detect:NSParagraphStyleAttributeName
- withInput:_input
- atIndex:range.location
- checkPrevious:YES
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- }
-}
-
-- (BOOL)anyOccurence:(NSRange)range {
- return [OccurenceUtils any:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
+- (NSString *)getValue {
+ return @"EnrichedCodeBlock";
}
-- (NSArray *_Nullable)findAllOccurences:(NSRange)range {
- return [OccurenceUtils all:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
+- (BOOL)isParagraph {
+ return YES;
}
-- (void)manageCodeBlockFontAndColor {
- if ([[_input->config codeBlockFgColor]
- isEqualToColor:[_input->config primaryColor]]) {
- return;
- }
-
- NSRange wholeRange =
- NSMakeRange(0, _input->textView.textStorage.string.length);
- NSArray *paragraphs =
- [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView
- range:wholeRange];
-
- for (NSValue *pValue in paragraphs) {
- NSRange paragraphRange = [pValue rangeValue];
- NSArray *properRanges = [OccurenceUtils getRangesWithout:_stylesToExclude
- withInput:_input
- inRange:paragraphRange];
-
- for (NSValue *value in properRanges) {
- NSRange currRange = [value rangeValue];
- BOOL selfDetected = [self detectStyle:currRange];
-
- [_input->textView.textStorage
- enumerateAttribute:NSFontAttributeName
- inRange:currRange
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- UIFont *currentFont = (UIFont *)value;
- UIFont *newFont = nullptr;
-
- BOOL isCodeFont = [[currentFont familyName]
- isEqualToString:[[_input->config monospacedFont]
- familyName]];
-
- if (isCodeFont && !selfDetected) {
- newFont = [[[_input->config primaryFont]
- withFontTraits:currentFont]
- setSize:currentFont.pointSize];
- } else if (!isCodeFont && selfDetected) {
- newFont = [[[_input->config monospacedFont]
- withFontTraits:currentFont]
- setSize:currentFont.pointSize];
- }
-
- if (newFont != nullptr) {
- [_input->textView.textStorage
- addAttribute:NSFontAttributeName
- value:newFont
- range:range];
- }
- }];
-
- [_input->textView.textStorage
- enumerateAttribute:NSForegroundColorAttributeName
- inRange:currRange
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- UIColor *newColor = nullptr;
- BOOL colorApplied = [(UIColor *)value
- isEqualToColor:[_input->config codeBlockFgColor]];
-
- if (colorApplied && !selfDetected) {
- newColor = [_input->config primaryColor];
- } else if (!colorApplied && selfDetected) {
- newColor = [_input->config codeBlockFgColor];
- }
-
- if (newColor != nullptr) {
- [_input->textView.textStorage
- addAttribute:NSForegroundColorAttributeName
- value:newColor
- range:range];
- }
- }];
- }
- }
+- (BOOL)needsZWS {
+ return YES;
}
-@end
+- (void)applyStyling:(NSRange)range {
+ NSRange fullRange =
+ [self.input->textView.textStorage.string paragraphRangeForRange:range];
+
+ [self.input->textView.textStorage
+ enumerateAttribute:NSFontAttributeName
+ inRange:fullRange
+ options:0
+ usingBlock:^(id _Nullable value, NSRange subRange,
+ BOOL *_Nonnull stop) {
+ UIFont *currentFont = (UIFont *)value;
+ if (currentFont == nullptr)
+ return;
+ UIFont *monoFont = [[[self.input->config monospacedFont]
+ withFontTraits:currentFont] setSize:currentFont.pointSize];
+ if (monoFont != nullptr) {
+ [self.input->textView.textStorage
+ addAttribute:NSFontAttributeName
+ value:monoFont
+ range:subRange];
+ }
+ }];
+
+ [self.input->textView.textStorage
+ addAttribute:NSForegroundColorAttributeName
+ value:[self.input->config codeBlockFgColor]
+ range:range];
+}
+
+@end
\ No newline at end of file
diff --git a/ios/styles/H1Style.mm b/ios/styles/H1Style.mm
index ca22c1154..c04c26f3b 100644
--- a/ios/styles/H1Style.mm
+++ b/ios/styles/H1Style.mm
@@ -2,16 +2,19 @@
#import "StyleHeaders.h"
@implementation H1Style
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return H1;
}
-+ (BOOL)isParagraphStyle {
+- (NSString *)getValue {
+ return @"EnrichedH1";
+}
+- (BOOL)isParagraph {
return YES;
}
- (CGFloat)getHeadingFontSize {
- return [((EnrichedTextInputView *)input)->config h1FontSize];
+ return [self.input->config h1FontSize];
}
- (BOOL)isHeadingBold {
- return [((EnrichedTextInputView *)input)->config h1Bold];
+ return [self.input->config h1Bold];
}
@end
diff --git a/ios/styles/H2Style.mm b/ios/styles/H2Style.mm
index a943b96a7..785b2b72a 100644
--- a/ios/styles/H2Style.mm
+++ b/ios/styles/H2Style.mm
@@ -2,16 +2,19 @@
#import "StyleHeaders.h"
@implementation H2Style
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return H2;
}
-+ (BOOL)isParagraphStyle {
+- (NSString *)getValue {
+ return @"EnrichedH2";
+}
+- (BOOL)isParagraph {
return YES;
}
- (CGFloat)getHeadingFontSize {
- return [((EnrichedTextInputView *)input)->config h2FontSize];
+ return [self.input->config h2FontSize];
}
- (BOOL)isHeadingBold {
- return [((EnrichedTextInputView *)input)->config h2Bold];
+ return [self.input->config h2Bold];
}
@end
diff --git a/ios/styles/H3Style.mm b/ios/styles/H3Style.mm
index e6c5765b3..9091f6e3b 100644
--- a/ios/styles/H3Style.mm
+++ b/ios/styles/H3Style.mm
@@ -2,16 +2,19 @@
#import "StyleHeaders.h"
@implementation H3Style
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return H3;
}
-+ (BOOL)isParagraphStyle {
+- (NSString *)getValue {
+ return @"EnrichedH3";
+}
+- (BOOL)isParagraph {
return YES;
}
- (CGFloat)getHeadingFontSize {
- return [((EnrichedTextInputView *)input)->config h3FontSize];
+ return [self.input->config h3FontSize];
}
- (BOOL)isHeadingBold {
- return [((EnrichedTextInputView *)input)->config h3Bold];
+ return [self.input->config h3Bold];
}
@end
diff --git a/ios/styles/H4Style.mm b/ios/styles/H4Style.mm
index 0fef5cc7d..a641f5ed8 100644
--- a/ios/styles/H4Style.mm
+++ b/ios/styles/H4Style.mm
@@ -2,16 +2,19 @@
#import "StyleHeaders.h"
@implementation H4Style
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return H4;
}
-+ (BOOL)isParagraphStyle {
+- (NSString *)getValue {
+ return @"EnrichedH4";
+}
+- (BOOL)isParagraph {
return YES;
}
- (CGFloat)getHeadingFontSize {
- return [((EnrichedTextInputView *)input)->config h4FontSize];
+ return [self.input->config h4FontSize];
}
- (BOOL)isHeadingBold {
- return [((EnrichedTextInputView *)input)->config h4Bold];
+ return [self.input->config h4Bold];
}
@end
diff --git a/ios/styles/H5Style.mm b/ios/styles/H5Style.mm
index 60a06abaf..40fab0f28 100644
--- a/ios/styles/H5Style.mm
+++ b/ios/styles/H5Style.mm
@@ -2,16 +2,19 @@
#import "StyleHeaders.h"
@implementation H5Style
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return H5;
}
-+ (BOOL)isParagraphStyle {
+- (NSString *)getValue {
+ return @"EnrichedH5";
+}
+- (BOOL)isParagraph {
return YES;
}
- (CGFloat)getHeadingFontSize {
- return [((EnrichedTextInputView *)input)->config h5FontSize];
+ return [self.input->config h5FontSize];
}
- (BOOL)isHeadingBold {
- return [((EnrichedTextInputView *)input)->config h5Bold];
+ return [self.input->config h5Bold];
}
@end
diff --git a/ios/styles/H6Style.mm b/ios/styles/H6Style.mm
index 80b46f810..2e576bb0a 100644
--- a/ios/styles/H6Style.mm
+++ b/ios/styles/H6Style.mm
@@ -2,16 +2,19 @@
#import "StyleHeaders.h"
@implementation H6Style
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return H6;
}
-+ (BOOL)isParagraphStyle {
+- (NSString *)getValue {
+ return @"EnrichedH6";
+}
+- (BOOL)isParagraph {
return YES;
}
- (CGFloat)getHeadingFontSize {
- return [((EnrichedTextInputView *)input)->config h6FontSize];
+ return [self.input->config h6FontSize];
}
- (BOOL)isHeadingBold {
- return [((EnrichedTextInputView *)input)->config h6Bold];
+ return [self.input->config h6Bold];
}
@end
diff --git a/ios/styles/HeadingStyleBase.mm b/ios/styles/HeadingStyleBase.mm
index c32314e69..d689c4581 100644
--- a/ios/styles/HeadingStyleBase.mm
+++ b/ios/styles/HeadingStyleBase.mm
@@ -1,238 +1,64 @@
#import "EnrichedTextInputView.h"
#import "FontExtension.h"
-#import "OccurenceUtils.h"
#import "StyleHeaders.h"
#import "TextInsertionUtils.h"
@implementation HeadingStyleBase
-// mock values since H1/2/3/4/5/6Style classes anyway are used
-+ (StyleType)getStyleType {
+// mock values since H1/2/3/4/5/6Style classes are used
++ (StyleType)getType {
return None;
}
+
- (CGFloat)getHeadingFontSize {
return 0;
}
+
- (BOOL)isHeadingBold {
- return false;
-}
-+ (BOOL)isParagraphStyle {
- return true;
+ return NO;
}
-- (EnrichedTextInputView *)typedInput {
- return (EnrichedTextInputView *)input;
+- (BOOL)isParagraph {
+ return YES;
}
-- (instancetype)initWithInput:(id)input {
- self = [super init];
- self->input = input;
- _lastAppliedFontSize = 0.0;
- return self;
-}
+- (void)applyStyling:(NSRange)range {
+ NSRange fullRange =
+ [self.input->textView.textStorage.string paragraphRangeForRange:range];
-// the range will already be the full paragraph/s range
-// but if the paragraph is empty it still is of length 0
-- (void)applyStyle:(NSRange)range {
- BOOL isStylePresent = [self detectStyle:range];
- if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
- } else {
- isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
- }
- _lastAppliedFontSize = [self getHeadingFontSize];
-}
-
-// the range will already be the proper full paragraph/s range
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
- [[self typedInput]->textView.textStorage beginEditing];
- [[self typedInput]->textView.textStorage
+ [self.input->textView.textStorage
enumerateAttribute:NSFontAttributeName
- inRange:range
+ inRange:fullRange
options:0
- usingBlock:^(id _Nullable value, NSRange range,
+ usingBlock:^(id _Nullable value, NSRange subRange,
BOOL *_Nonnull stop) {
UIFont *font = (UIFont *)value;
- if (font != nullptr) {
- UIFont *newFont = [font setSize:[self getHeadingFontSize]];
- if ([self isHeadingBold]) {
- newFont = [newFont setBold];
- }
- [[self typedInput]->textView.textStorage
- addAttribute:NSFontAttributeName
- value:newFont
- range:range];
+ if (font == nullptr)
+ return;
+ UIFont *newFont = [font setSize:[self getHeadingFontSize]];
+ if ([self isHeadingBold]) {
+ newFont = [newFont setBold];
}
+ [self.input->textView.textStorage
+ addAttribute:NSFontAttributeName
+ value:newFont
+ range:subRange];
}];
- [[self typedInput]->textView.textStorage endEditing];
-
- // also toggle typing attributes
- if (withTypingAttr) {
- [self addTypingAttributes];
- }
}
-// will always be called on empty paragraphs so only typing attributes can be
-// changed
-- (void)addTypingAttributes {
- UIFont *currentFontAttr =
- (UIFont *)[self typedInput]
- ->textView.typingAttributes[NSFontAttributeName];
- if (currentFontAttr != nullptr) {
- NSMutableDictionary *newTypingAttrs =
- [[self typedInput]->textView.typingAttributes mutableCopy];
- UIFont *newFont = [currentFontAttr setSize:[self getHeadingFontSize]];
- if ([self isHeadingBold]) {
- newFont = [newFont setBold];
- }
- newTypingAttrs[NSFontAttributeName] = newFont;
- [self typedInput]->textView.typingAttributes = newTypingAttrs;
- }
-}
-
-// we need to remove the style from the whole paragraph
-- (void)removeAttributes:(NSRange)range {
- NSRange paragraphRange = [[self typedInput]->textView.textStorage.string
- paragraphRangeForRange:range];
-
- [[self typedInput]->textView.textStorage beginEditing];
- [[self typedInput]->textView.textStorage
- enumerateAttribute:NSFontAttributeName
- inRange:paragraphRange
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- if ([self styleCondition:value range:range]) {
- UIFont *newFont = [(UIFont *)value
- setSize:[[[self typedInput]->config scaledPrimaryFontSize]
- floatValue]];
- if ([self isHeadingBold]) {
- newFont = [newFont removeBold];
- }
- [[self typedInput]->textView.textStorage
- addAttribute:NSFontAttributeName
- value:newFont
- range:range];
- }
- }];
- [[self typedInput]->textView.textStorage endEditing];
-
- // typing attributes still need to be removed
- UIFont *currentFontAttr =
- (UIFont *)[self typedInput]
- ->textView.typingAttributes[NSFontAttributeName];
- if (currentFontAttr != nullptr) {
- NSMutableDictionary *newTypingAttrs =
- [[self typedInput]->textView.typingAttributes mutableCopy];
- UIFont *newFont = [currentFontAttr
- setSize:[[[self typedInput]->config scaledPrimaryFontSize] floatValue]];
- if ([self isHeadingBold]) {
- newFont = [newFont removeBold];
- }
- newTypingAttrs[NSFontAttributeName] = newFont;
- [self typedInput]->textView.typingAttributes = newTypingAttrs;
- }
-}
-
-- (void)removeTypingAttributes {
- // all the heading still needs to be removed because this function may be
- // called in conflicting styles logic typing attributes already get removed in
- // there as well
- [self removeAttributes:[self typedInput]->textView.selectedRange];
-}
-
-// when the traits already change, the getHeadginFontSize will return the new
-// font size and no headings would be properly detected, so that's why we have
-// to use the latest applied font size rather than that value.
-- (BOOL)styleCondition:(id _Nullable)value range:(NSRange)range {
- UIFont *font = (UIFont *)value;
- if (font == nullptr) {
- return NO;
- }
-
- if (self.lastAppliedFontSize > 0.0) {
- return font.pointSize == self.lastAppliedFontSize;
- }
-
- return font.pointSize == [self getHeadingFontSize];
-}
-
-- (BOOL)detectStyle:(NSRange)range {
- if (range.length >= 1) {
- return [OccurenceUtils detect:NSFontAttributeName
- withInput:[self typedInput]
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- } else {
- return [OccurenceUtils detect:NSFontAttributeName
- withInput:[self typedInput]
- atIndex:range.location
- checkPrevious:YES
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- }
-}
-
-- (BOOL)anyOccurence:(NSRange)range {
- return [OccurenceUtils any:NSFontAttributeName
- withInput:[self typedInput]
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
-}
-
-- (NSArray *_Nullable)findAllOccurences:(NSRange)range {
- return [OccurenceUtils all:NSFontAttributeName
- withInput:[self typedInput]
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
-}
-
-// used to make sure headings dont persist after a newline is placed
- (BOOL)handleNewlinesInRange:(NSRange)range replacementText:(NSString *)text {
- // in a heading and a new text ends with a newline
- if ([self detectStyle:[self typedInput]->textView.selectedRange] &&
- text.length > 0 &&
+ if ([self detect:self.input->textView.selectedRange] && text.length > 0 &&
[[NSCharacterSet newlineCharacterSet]
characterIsMember:[text characterAtIndex:text.length - 1]]) {
- // do the replacement manually
[TextInsertionUtils replaceText:text
at:range
additionalAttributes:nullptr
- input:[self typedInput]
+ input:self.input
withSelection:YES];
- // remove the attribtues at the new selection
- [self removeAttributes:[self typedInput]->textView.selectedRange];
+ [self remove:self.input->textView.selectedRange withDirtyRange:YES];
return YES;
}
return NO;
}
-// backspacing a line after a heading "into" a heading will not result in the
-// text attaining heading attributes so, we do it manually
-- (void)handleImproperHeadings {
- NSArray *occurences = [self
- findAllOccurences:NSMakeRange(0,
- [self typedInput]
- ->textView.textStorage.string.length)];
- for (StylePair *pair in occurences) {
- NSRange occurenceRange = [pair.rangeValue rangeValue];
- NSRange paragraphRange = [[self typedInput]->textView.textStorage.string
- paragraphRangeForRange:occurenceRange];
- if (!NSEqualRanges(occurenceRange, paragraphRange)) {
- // we have a heading but it does not span its whole paragraph - let's fix
- // it
- [self addAttributes:paragraphRange withTypingAttr:NO];
- }
- }
- _lastAppliedFontSize = [self getHeadingFontSize];
-}
-
@end
diff --git a/ios/styles/InlineCodeStyle.mm b/ios/styles/InlineCodeStyle.mm
index ed5880240..054332a75 100644
--- a/ios/styles/InlineCodeStyle.mm
+++ b/ios/styles/InlineCodeStyle.mm
@@ -1,243 +1,65 @@
#import "ColorExtension.h"
#import "EnrichedTextInputView.h"
#import "FontExtension.h"
-#import "OccurenceUtils.h"
-#import "ParagraphsUtils.h"
+#import "RangeUtils.h"
#import "StyleHeaders.h"
-@implementation InlineCodeStyle {
- EnrichedTextInputView *_input;
-}
+@implementation InlineCodeStyle
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return InlineCode;
}
-+ (BOOL)isParagraphStyle {
- return NO;
+- (NSString *)getKey {
+ return @"EnrichedInlineCode";
}
-- (instancetype)initWithInput:(id)input {
- self = [super init];
- _input = (EnrichedTextInputView *)input;
- return self;
-}
-
-- (void)applyStyle:(NSRange)range {
- BOOL isStylePresent = [self detectStyle:range];
- if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
- } else {
- isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
- }
+- (BOOL)isParagraph {
+ return NO;
}
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
+- (void)applyStyling:(NSRange)range {
// we don't want to apply inline code to newline characters, it looks bad
NSArray *nonNewlineRanges =
- [ParagraphsUtils getNonNewlineRangesIn:_input->textView range:range];
+ [RangeUtils getNonNewlineRangesIn:self.input->textView range:range];
for (NSValue *value in nonNewlineRanges) {
- NSRange currentRange = [value rangeValue];
- [_input->textView.textStorage beginEditing];
+ NSRange subRange = [value rangeValue];
- [_input->textView.textStorage
+ [self.input->textView.textStorage
addAttribute:NSBackgroundColorAttributeName
- value:[[_input->config inlineCodeBgColor]
+ value:[[self.input->config inlineCodeBgColor]
colorWithAlphaIfNotTransparent:0.4]
- range:currentRange];
- [_input->textView.textStorage
+ range:subRange];
+ [self.input->textView.textStorage
addAttribute:NSForegroundColorAttributeName
- value:[_input->config inlineCodeFgColor]
- range:currentRange];
- [_input->textView.textStorage
+ value:[self.input->config inlineCodeFgColor]
+ range:subRange];
+ [self.input->textView.textStorage
addAttribute:NSUnderlineColorAttributeName
- value:[_input->config inlineCodeFgColor]
- range:currentRange];
- [_input->textView.textStorage
+ value:[self.input->config inlineCodeFgColor]
+ range:subRange];
+ [self.input->textView.textStorage
addAttribute:NSStrikethroughColorAttributeName
- value:[_input->config inlineCodeFgColor]
- range:currentRange];
- [_input->textView.textStorage
+ value:[self.input->config inlineCodeFgColor]
+ range:subRange];
+ [self.input->textView.textStorage
enumerateAttribute:NSFontAttributeName
- inRange:currentRange
+ inRange:subRange
options:0
- usingBlock:^(id _Nullable value, NSRange range,
+ usingBlock:^(id _Nullable value, NSRange fontRange,
BOOL *_Nonnull stop) {
UIFont *font = (UIFont *)value;
if (font != nullptr) {
- UIFont *newFont = [[[_input->config monospacedFont]
+ UIFont *newFont = [[[self.input->config monospacedFont]
withFontTraits:font] setSize:font.pointSize];
- [_input->textView.textStorage
+ [self.input->textView.textStorage
addAttribute:NSFontAttributeName
value:newFont
- range:range];
+ range:fontRange];
}
}];
-
- [_input->textView.textStorage endEditing];
- }
-}
-
-- (void)addTypingAttributes {
- NSMutableDictionary *newTypingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- newTypingAttrs[NSBackgroundColorAttributeName] =
- [[_input->config inlineCodeBgColor] colorWithAlphaIfNotTransparent:0.4];
- newTypingAttrs[NSForegroundColorAttributeName] =
- [_input->config inlineCodeFgColor];
- newTypingAttrs[NSUnderlineColorAttributeName] =
- [_input->config inlineCodeFgColor];
- newTypingAttrs[NSStrikethroughColorAttributeName] =
- [_input->config inlineCodeFgColor];
- UIFont *currentFont = (UIFont *)newTypingAttrs[NSFontAttributeName];
- if (currentFont != nullptr) {
- newTypingAttrs[NSFontAttributeName] = [[[_input->config monospacedFont]
- withFontTraits:currentFont] setSize:currentFont.pointSize];
}
- _input->textView.typingAttributes = newTypingAttrs;
-}
-
-- (void)removeAttributes:(NSRange)range {
- [_input->textView.textStorage beginEditing];
-
- [_input->textView.textStorage removeAttribute:NSBackgroundColorAttributeName
- range:range];
- [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName
- value:[_input->config primaryColor]
- range:range];
- [_input->textView.textStorage addAttribute:NSUnderlineColorAttributeName
- value:[_input->config primaryColor]
- range:range];
- [_input->textView.textStorage addAttribute:NSStrikethroughColorAttributeName
- value:[_input->config primaryColor]
- range:range];
- [_input->textView.textStorage
- enumerateAttribute:NSFontAttributeName
- inRange:range
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- UIFont *font = (UIFont *)value;
- if (font != nullptr) {
- UIFont *newFont = [[[_input->config primaryFont]
- withFontTraits:font] setSize:font.pointSize];
- [_input->textView.textStorage addAttribute:NSFontAttributeName
- value:newFont
- range:range];
- }
- }];
-
- [_input->textView.textStorage endEditing];
-}
-
-- (void)removeTypingAttributes {
- NSMutableDictionary *newTypingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- [newTypingAttrs removeObjectForKey:NSBackgroundColorAttributeName];
- newTypingAttrs[NSForegroundColorAttributeName] =
- [_input->config primaryColor];
- newTypingAttrs[NSUnderlineColorAttributeName] = [_input->config primaryColor];
- newTypingAttrs[NSStrikethroughColorAttributeName] =
- [_input->config primaryColor];
- UIFont *currentFont = (UIFont *)newTypingAttrs[NSFontAttributeName];
- if (currentFont != nullptr) {
- newTypingAttrs[NSFontAttributeName] = [[[_input->config primaryFont]
- withFontTraits:currentFont] setSize:currentFont.pointSize];
- }
- _input->textView.typingAttributes = newTypingAttrs;
-}
-
-// making sure no newlines get inline code style, it looks bad
-- (void)handleNewlines {
- NSTextStorage *storage = _input->textView.textStorage;
- NSString *string = storage.string;
- NSUInteger length = string.length;
-
- if (length == 0)
- return;
-
- CFStringInlineBuffer buffer;
- CFStringInitInlineBuffer((CFStringRef)string, &buffer,
- CFRangeMake(0, length));
-
- for (NSUInteger index = 0; index < length; index++) {
- unichar ch = CFStringGetCharacterFromInlineBuffer(&buffer, index);
- // check new lines only
- if (![[NSCharacterSet newlineCharacterSet] characterIsMember:ch])
- continue;
-
- NSRange newlineRange = NSMakeRange(index, 1);
-
- UIColor *bgColor = [storage attribute:NSBackgroundColorAttributeName
- atIndex:index
- effectiveRange:nil];
-
- if (bgColor != nil && [self styleCondition:bgColor range:newlineRange]) {
- [self removeAttributes:newlineRange];
- }
- }
-}
-
-// emojis don't retain monospace font attribute so we check for the background
-// color if there is no mention
-- (BOOL)styleCondition:(id _Nullable)value range:(NSRange)range {
- UIColor *bgColor = (UIColor *)value;
- MentionStyle *mStyle = _input->stylesDict[@([MentionStyle getStyleType])];
- return bgColor != nullptr && mStyle != nullptr && ![mStyle detectStyle:range];
-}
-
-- (BOOL)detectStyle:(NSRange)range {
- if (range.length >= 1) {
- // detect only in non-newline characters
- NSArray *nonNewlineRanges =
- [ParagraphsUtils getNonNewlineRangesIn:_input->textView range:range];
- if (nonNewlineRanges.count == 0) {
- return NO;
- }
-
- BOOL detected = YES;
- for (NSValue *value in nonNewlineRanges) {
- NSRange currentRange = [value rangeValue];
- BOOL currentDetected =
- [OccurenceUtils detect:NSBackgroundColorAttributeName
- withInput:_input
- inRange:currentRange
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- detected = detected && currentDetected;
- }
-
- return detected;
- } else {
- return [OccurenceUtils detect:NSBackgroundColorAttributeName
- withInput:_input
- atIndex:range.location
- checkPrevious:NO
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- }
-}
-
-- (BOOL)anyOccurence:(NSRange)range {
- return [OccurenceUtils any:NSBackgroundColorAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
-}
-
-- (NSArray *_Nullable)findAllOccurences:(NSRange)range {
- return [OccurenceUtils all:NSBackgroundColorAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
}
@end
diff --git a/ios/styles/ItalicStyle.mm b/ios/styles/ItalicStyle.mm
index 052c88f12..963a213e6 100644
--- a/ios/styles/ItalicStyle.mm
+++ b/ios/styles/ItalicStyle.mm
@@ -1,39 +1,23 @@
#import "EnrichedTextInputView.h"
#import "FontExtension.h"
-#import "OccurenceUtils.h"
#import "StyleHeaders.h"
-@implementation ItalicStyle {
- EnrichedTextInputView *_input;
-}
+@implementation ItalicStyle : StyleBase
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return Italic;
}
-+ (BOOL)isParagraphStyle {
- return NO;
-}
-
-- (instancetype)initWithInput:(id)input {
- self = [super init];
- _input = (EnrichedTextInputView *)input;
- return self;
+- (NSString *)getKey {
+ return @"EnrichedItalic";
}
-- (void)applyStyle:(NSRange)range {
- BOOL isStylePresent = [self detectStyle:range];
- if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
- } else {
- isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
- }
+- (BOOL)isParagraph {
+ return NO;
}
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
- [_input->textView.textStorage beginEditing];
- [_input->textView.textStorage
+- (void)applyStyling:(NSRange)range {
+ [self.input->textView.textStorage
enumerateAttribute:NSFontAttributeName
inRange:range
options:0
@@ -42,95 +26,12 @@ - (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
UIFont *font = (UIFont *)value;
if (font != nullptr) {
UIFont *newFont = [font setItalic];
- [_input->textView.textStorage addAttribute:NSFontAttributeName
- value:newFont
- range:range];
- }
- }];
- [_input->textView.textStorage endEditing];
-}
-
-- (void)addTypingAttributes {
- UIFont *currentFontAttr =
- (UIFont *)_input->textView.typingAttributes[NSFontAttributeName];
- if (currentFontAttr != nullptr) {
- NSMutableDictionary *newTypingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- newTypingAttrs[NSFontAttributeName] = [currentFontAttr setItalic];
- _input->textView.typingAttributes = newTypingAttrs;
- }
-}
-
-- (void)removeAttributes:(NSRange)range {
- [_input->textView.textStorage beginEditing];
- [_input->textView.textStorage
- enumerateAttribute:NSFontAttributeName
- inRange:range
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- UIFont *font = (UIFont *)value;
- if (font != nullptr) {
- UIFont *newFont = [font removeItalic];
- [_input->textView.textStorage addAttribute:NSFontAttributeName
- value:newFont
- range:range];
+ [self.input->textView.textStorage
+ addAttribute:NSFontAttributeName
+ value:newFont
+ range:range];
}
}];
- [_input->textView.textStorage endEditing];
-}
-
-- (void)removeTypingAttributes {
- UIFont *currentFontAttr =
- (UIFont *)_input->textView.typingAttributes[NSFontAttributeName];
- if (currentFontAttr != nullptr) {
- NSMutableDictionary *newTypingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- newTypingAttrs[NSFontAttributeName] = [currentFontAttr removeItalic];
- _input->textView.typingAttributes = newTypingAttrs;
- }
-}
-
-- (BOOL)styleCondition:(id _Nullable)value range:(NSRange)range {
- UIFont *font = (UIFont *)value;
- return font != nullptr && [font isItalic];
-}
-
-- (BOOL)detectStyle:(NSRange)range {
- if (range.length >= 1) {
- return [OccurenceUtils detect:NSFontAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- } else {
- return [OccurenceUtils detect:NSFontAttributeName
- withInput:_input
- atIndex:range.location
- checkPrevious:NO
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- }
-}
-
-- (BOOL)anyOccurence:(NSRange)range {
- return [OccurenceUtils any:NSFontAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
-}
-
-- (NSArray *_Nullable)findAllOccurences:(NSRange)range {
- return [OccurenceUtils all:NSFontAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
}
@end
diff --git a/ios/styles/LinkStyle.mm b/ios/styles/LinkStyle.mm
index ac23f19fb..db0467dd2 100644
--- a/ios/styles/LinkStyle.mm
+++ b/ios/styles/LinkStyle.mm
@@ -397,11 +397,11 @@ - (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange {
}
InlineCodeStyle *inlineCodeStyle =
- [_input->stylesDict objectForKey:@([InlineCodeStyle getStyleType])];
+ [_input->stylesDict objectForKey:@([InlineCodeStyle getType])];
MentionStyle *mentionStyle =
[_input->stylesDict objectForKey:@([MentionStyle getStyleType])];
CodeBlockStyle *codeBlockStyle =
- [_input->stylesDict objectForKey:@([CodeBlockStyle getStyleType])];
+ [_input->stylesDict objectForKey:@([CodeBlockStyle getType])];
if (inlineCodeStyle == nullptr || mentionStyle == nullptr) {
return;
@@ -413,12 +413,12 @@ - (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange {
}
// we don't recognize links among inline code
- if ([inlineCodeStyle anyOccurence:wordRange]) {
+ if ([inlineCodeStyle any:wordRange]) {
return;
}
// we don't recognize links in codeblocks
- if ([codeBlockStyle anyOccurence:wordRange]) {
+ if ([codeBlockStyle any:wordRange]) {
return;
}
diff --git a/ios/styles/OrderedListStyle.mm b/ios/styles/OrderedListStyle.mm
index 933b57e11..60a6089a5 100644
--- a/ios/styles/OrderedListStyle.mm
+++ b/ios/styles/OrderedListStyle.mm
@@ -1,233 +1,81 @@
#import "EnrichedTextInputView.h"
-#import "FontExtension.h"
-#import "OccurenceUtils.h"
-#import "ParagraphsUtils.h"
+#import "RangeUtils.h"
#import "StyleHeaders.h"
#import "TextInsertionUtils.h"
-@implementation OrderedListStyle {
- EnrichedTextInputView *_input;
-}
+@implementation OrderedListStyle
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return OrderedList;
}
-+ (BOOL)isParagraphStyle {
- return YES;
-}
-
-- (CGFloat)getHeadIndent {
- // lists are drawn manually
- // margin before marker + gap between marker and paragraph
- return [_input->config orderedListMarginLeft] +
- [_input->config orderedListGapWidth];
-}
-
-- (instancetype)initWithInput:(id)input {
- self = [super init];
- _input = (EnrichedTextInputView *)input;
- return self;
-}
-
-- (void)applyStyle:(NSRange)range {
- BOOL isStylePresent = [self detectStyle:range];
- if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
- } else {
- isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
- }
-}
-
-// we assume correct paragraph range is already given
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
- NSTextList *numberBullet =
- [[NSTextList alloc] initWithMarkerFormat:NSTextListMarkerDecimal
- options:0];
- NSArray *paragraphs =
- [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView
- range:range];
- // if we fill empty lines with zero width spaces, we need to offset later
- // ranges
- NSInteger offset = 0;
- // needed for range adjustments
- NSRange preModificationRange = _input->textView.selectedRange;
-
- // let's not emit some weird selection changes or text/html changes
- _input->blockEmitting = YES;
-
- for (NSValue *value in paragraphs) {
- // take previous offsets into consideration
- NSRange fixedRange = NSMakeRange([value rangeValue].location + offset,
- [value rangeValue].length);
-
- // length 0 with first line, length 1 and newline with some empty lines in
- // the middle
- if (fixedRange.length == 0 ||
- (fixedRange.length == 1 &&
- [[NSCharacterSet newlineCharacterSet]
- characterIsMember:[_input->textView.textStorage.string
- characterAtIndex:fixedRange.location]])) {
- [TextInsertionUtils insertText:@"\u200B"
- at:fixedRange.location
- additionalAttributes:nullptr
- input:_input
- withSelection:NO];
- fixedRange = NSMakeRange(fixedRange.location, fixedRange.length + 1);
- offset += 1;
- }
-
- [_input->textView.textStorage
- enumerateAttribute:NSParagraphStyleAttributeName
- inRange:fixedRange
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- NSMutableParagraphStyle *pStyle =
- [(NSParagraphStyle *)value mutableCopy];
- pStyle.textLists = @[ numberBullet ];
- pStyle.headIndent = [self getHeadIndent];
- pStyle.firstLineHeadIndent = [self getHeadIndent];
- [_input->textView.textStorage
- addAttribute:NSParagraphStyleAttributeName
- value:pStyle
- range:range];
- }];
- }
-
- // back to emitting
- _input->blockEmitting = NO;
-
- if (preModificationRange.length == 0) {
- // fix selection if only one line was possibly made a list and filled with a
- // space
- _input->textView.selectedRange = preModificationRange;
- } else {
- // in other cases, fix the selection with newly made offsets
- _input->textView.selectedRange = NSMakeRange(
- preModificationRange.location, preModificationRange.length + offset);
- }
-
- // also add typing attributes
- if (withTypingAttr) {
- NSMutableDictionary *typingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- NSMutableParagraphStyle *pStyle =
- [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
- pStyle.textLists = @[ numberBullet ];
- pStyle.headIndent = [self getHeadIndent];
- pStyle.firstLineHeadIndent = [self getHeadIndent];
- typingAttrs[NSParagraphStyleAttributeName] = pStyle;
- _input->textView.typingAttributes = typingAttrs;
- }
-}
-
-// does pretty much the same as normal addAttributes, just need to get the range
-- (void)addTypingAttributes {
- [self addAttributes:_input->textView.selectedRange withTypingAttr:YES];
+- (NSString *)getValue {
+ return @"EnrichedOrderedList";
}
-- (void)removeAttributes:(NSRange)range {
- NSArray *paragraphs =
- [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView
- range:range];
-
- [_input->textView.textStorage beginEditing];
-
- for (NSValue *value in paragraphs) {
- NSRange range = [value rangeValue];
- [_input->textView.textStorage
- enumerateAttribute:NSParagraphStyleAttributeName
- inRange:range
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- NSMutableParagraphStyle *pStyle =
- [(NSParagraphStyle *)value mutableCopy];
- pStyle.textLists = @[];
- pStyle.headIndent = 0;
- pStyle.firstLineHeadIndent = 0;
- [_input->textView.textStorage
- addAttribute:NSParagraphStyleAttributeName
- value:pStyle
- range:range];
- }];
- }
-
- [_input->textView.textStorage endEditing];
-
- // also remove typing attributes
- NSMutableDictionary *typingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- NSMutableParagraphStyle *pStyle =
- [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
- pStyle.textLists = @[];
- pStyle.headIndent = 0;
- pStyle.firstLineHeadIndent = 0;
- typingAttrs[NSParagraphStyleAttributeName] = pStyle;
- _input->textView.typingAttributes = typingAttrs;
+- (BOOL)isParagraph {
+ return YES;
}
-// needed for the sake of style conflicts, needs to do exactly the same as
-// removeAttribtues
-- (void)removeTypingAttributes {
- [self removeAttributes:_input->textView.selectedRange];
+- (BOOL)needsZWS {
+ return YES;
}
-- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
- if ([self detectStyle:_input->textView.selectedRange] && text.length == 0) {
- // backspace while the style is active
-
- NSRange paragraphRange = [_input->textView.textStorage.string
- paragraphRangeForRange:_input->textView.selectedRange];
-
- if (NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) {
- // a backspace on the very first input's line list point
- // it doesn't run textVieDidChange so we need to manually remove
- // attributes
- [self removeAttributes:paragraphRange];
- return YES;
- } else if (range.location == paragraphRange.location - 1) {
- // same case in other lines; here, the removed range location will be
- // exactly 1 less than paragraph range location
- [self removeAttributes:paragraphRange];
- return YES;
- }
- }
- return NO;
+- (void)applyStyling:(NSRange)range {
+ // lists are drawn manually
+ // margin before marker + gap between marker and paragraph
+ CGFloat listHeadIndent = [self.input->config orderedListMarginLeft] +
+ [self.input->config orderedListGapWidth];
+
+ [self.input->textView.textStorage
+ enumerateAttribute:NSParagraphStyleAttributeName
+ inRange:range
+ options:0
+ usingBlock:^(id _Nullable value, NSRange range,
+ BOOL *_Nonnull stop) {
+ NSMutableParagraphStyle *pStyle =
+ [(NSParagraphStyle *)value mutableCopy];
+ pStyle.headIndent = listHeadIndent;
+ pStyle.firstLineHeadIndent = listHeadIndent;
+ [self.input->textView.textStorage
+ addAttribute:NSParagraphStyleAttributeName
+ value:pStyle
+ range:range];
+ }];
}
- (BOOL)tryHandlingListShorcutInRange:(NSRange)range
replacementText:(NSString *)text {
NSRange paragraphRange =
- [_input->textView.textStorage.string paragraphRangeForRange:range];
+ [self.input->textView.textStorage.string paragraphRangeForRange:range];
// a dot was added - check if we are both at the paragraph beginning + 1
- // character (which we want to be a dash)
+ // character (which we want to be a digit '1')
if ([text isEqualToString:@"."] &&
range.location - 1 == paragraphRange.location) {
- unichar charBefore = [_input->textView.textStorage.string
+ unichar charBefore = [self.input->textView.textStorage.string
characterAtIndex:range.location - 1];
if (charBefore == '1') {
// we got a match - add a list if possible
- if ([_input handleStyleBlocksAndConflicts:[[self class] getStyleType]
- range:paragraphRange]) {
+ if ([self.input handleStyleBlocksAndConflicts:[[self class] getType]
+ range:paragraphRange]) {
// don't emit during the replacing
- _input->blockEmitting = YES;
+ self.input->blockEmitting = YES;
// remove the number
[TextInsertionUtils replaceText:@""
at:NSMakeRange(paragraphRange.location, 1)
additionalAttributes:nullptr
- input:_input
+ input:self.input
withSelection:YES];
- _input->blockEmitting = NO;
+ self.input->blockEmitting = NO;
// add attributes on the paragraph
- [self addAttributes:NSMakeRange(paragraphRange.location,
- paragraphRange.length - 1)
- withTypingAttr:YES];
+ [self add:NSMakeRange(paragraphRange.location,
+ paragraphRange.length - 1)
+ withTyping:YES
+ withDirtyRange:YES];
+
return YES;
}
}
@@ -235,48 +83,4 @@ - (BOOL)tryHandlingListShorcutInRange:(NSRange)range
return NO;
}
-- (BOOL)styleCondition:(id _Nullable)value range:(NSRange)range {
- NSParagraphStyle *paragraph = (NSParagraphStyle *)value;
- return paragraph != nullptr && paragraph.textLists.count == 1 &&
- paragraph.textLists.firstObject.markerFormat ==
- NSTextListMarkerDecimal;
-}
-
-- (BOOL)detectStyle:(NSRange)range {
- if (range.length >= 1) {
- return [OccurenceUtils detect:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- } else {
- return [OccurenceUtils detect:NSParagraphStyleAttributeName
- withInput:_input
- atIndex:range.location
- checkPrevious:YES
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- }
-}
-
-- (BOOL)anyOccurence:(NSRange)range {
- return [OccurenceUtils any:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
-}
-
-- (NSArray *_Nullable)findAllOccurences:(NSRange)range {
- return [OccurenceUtils all:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
-}
-
@end
diff --git a/ios/styles/StrikethroughStyle.mm b/ios/styles/StrikethroughStyle.mm
index f695efcb3..050b226ae 100644
--- a/ios/styles/StrikethroughStyle.mm
+++ b/ios/styles/StrikethroughStyle.mm
@@ -1,102 +1,24 @@
#import "EnrichedTextInputView.h"
-#import "OccurenceUtils.h"
#import "StyleHeaders.h"
-@implementation StrikethroughStyle {
- EnrichedTextInputView *_input;
-}
+@implementation StrikethroughStyle : StyleBase
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return Strikethrough;
}
-+ (BOOL)isParagraphStyle {
- return NO;
-}
-
-- (instancetype)initWithInput:(id)input {
- self = [super init];
- _input = (EnrichedTextInputView *)input;
- return self;
-}
-
-- (void)applyStyle:(NSRange)range {
- BOOL isStylePresent = [self detectStyle:range];
- if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
- } else {
- isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
- }
-}
-
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
- [_input->textView.textStorage addAttribute:NSStrikethroughStyleAttributeName
- value:@(NSUnderlineStyleSingle)
- range:range];
-}
-
-- (void)addTypingAttributes {
- NSMutableDictionary *newTypingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- newTypingAttrs[NSStrikethroughStyleAttributeName] = @(NSUnderlineStyleSingle);
- _input->textView.typingAttributes = newTypingAttrs;
+- (NSString *)getKey {
+ return @"EnrichedStrikethrough";
}
-- (void)removeAttributes:(NSRange)range {
- [_input->textView.textStorage
- removeAttribute:NSStrikethroughStyleAttributeName
- range:range];
-}
-
-- (void)removeTypingAttributes {
- NSMutableDictionary *newTypingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- [newTypingAttrs removeObjectForKey:NSStrikethroughStyleAttributeName];
- _input->textView.typingAttributes = newTypingAttrs;
-}
-
-- (BOOL)styleCondition:(id _Nullable)value range:(NSRange)range {
- NSNumber *strikethroughStyle = (NSNumber *)value;
- return strikethroughStyle != nullptr &&
- [strikethroughStyle intValue] != NSUnderlineStyleNone;
-}
-
-- (BOOL)detectStyle:(NSRange)range {
- if (range.length >= 1) {
- return [OccurenceUtils detect:NSStrikethroughStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- } else {
- return [OccurenceUtils detect:NSStrikethroughStyleAttributeName
- withInput:_input
- atIndex:range.location
- checkPrevious:NO
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- }
-}
-
-- (BOOL)anyOccurence:(NSRange)range {
- return [OccurenceUtils any:NSStrikethroughStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
+- (BOOL)isParagraph {
+ return NO;
}
-- (NSArray *_Nullable)findAllOccurences:(NSRange)range {
- return [OccurenceUtils all:NSStrikethroughStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
+- (void)applyStyling:(NSRange)range {
+ NSDictionary *styles =
+ @{NSStrikethroughStyleAttributeName : @(NSUnderlineStyleSingle)};
+ [self.input->textView.textStorage addAttributes:styles range:range];
}
@end
diff --git a/ios/styles/UnderlineStyle.mm b/ios/styles/UnderlineStyle.mm
index ae2ecfeb7..2d30eec58 100644
--- a/ios/styles/UnderlineStyle.mm
+++ b/ios/styles/UnderlineStyle.mm
@@ -1,139 +1,24 @@
#import "EnrichedTextInputView.h"
-#import "OccurenceUtils.h"
#import "StyleHeaders.h"
-@implementation UnderlineStyle {
- EnrichedTextInputView *_input;
-}
+@implementation UnderlineStyle
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return Underline;
}
-+ (BOOL)isParagraphStyle {
- return NO;
-}
-
-- (instancetype)initWithInput:(id)input {
- self = [super init];
- _input = (EnrichedTextInputView *)input;
- return self;
-}
-
-- (void)applyStyle:(NSRange)range {
- BOOL isStylePresent = [self detectStyle:range];
- if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
- } else {
- isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
- }
-}
-
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
- [_input->textView.textStorage addAttribute:NSUnderlineStyleAttributeName
- value:@(NSUnderlineStyleSingle)
- range:range];
-}
-
-- (void)addTypingAttributes {
- NSMutableDictionary *newTypingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- newTypingAttrs[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle);
- _input->textView.typingAttributes = newTypingAttrs;
-}
-
-- (void)removeAttributes:(NSRange)range {
- [_input->textView.textStorage removeAttribute:NSUnderlineStyleAttributeName
- range:range];
+- (NSString *)getKey {
+ return @"EnrichedUnderline";
}
-- (void)removeTypingAttributes {
- NSMutableDictionary *newTypingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- [newTypingAttrs removeObjectForKey:NSUnderlineStyleAttributeName];
- _input->textView.typingAttributes = newTypingAttrs;
-}
-
-- (BOOL)underlinedLinkConflictsInRange:(NSRange)range {
- BOOL conflicted = NO;
- if ([_input->config linkDecorationLine] == DecorationUnderline) {
- LinkStyle *linkStyle = _input->stylesDict[@([LinkStyle getStyleType])];
- conflicted = range.length > 0 ? [linkStyle anyOccurence:range]
- : [linkStyle detectStyle:range];
- }
- return conflicted;
-}
-
-- (BOOL)underlinedMentionConflictsInRange:(NSRange)range {
- BOOL conflicted = NO;
- MentionStyle *mentionStyle =
- _input->stylesDict[@([MentionStyle getStyleType])];
- if (range.length == 0) {
- if ([mentionStyle detectStyle:range]) {
- MentionParams *params = [mentionStyle getMentionParamsAt:range.location];
- conflicted =
- [_input->config mentionStylePropsForIndicator:params.indicator]
- .decorationLine == DecorationUnderline;
- }
- } else {
- NSArray *occurences = [mentionStyle findAllOccurences:range];
- for (StylePair *pair in occurences) {
- MentionParams *params = [mentionStyle
- getMentionParamsAt:[pair.rangeValue rangeValue].location];
- if ([_input->config mentionStylePropsForIndicator:params.indicator]
- .decorationLine == DecorationUnderline) {
- conflicted = YES;
- break;
- }
- }
- }
- return conflicted;
-}
-
-- (BOOL)styleCondition:(id _Nullable)value range:(NSRange)range {
- NSNumber *underlineStyle = (NSNumber *)value;
- return underlineStyle != nullptr &&
- [underlineStyle intValue] != NSUnderlineStyleNone &&
- ![self underlinedLinkConflictsInRange:range] &&
- ![self underlinedMentionConflictsInRange:range];
-}
-
-- (BOOL)detectStyle:(NSRange)range {
- if (range.length >= 1) {
- return [OccurenceUtils detect:NSUnderlineStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- } else {
- return [OccurenceUtils detect:NSUnderlineStyleAttributeName
- withInput:_input
- atIndex:range.location
- checkPrevious:NO
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- }
-}
-
-- (BOOL)anyOccurence:(NSRange)range {
- return [OccurenceUtils any:NSUnderlineStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
+- (BOOL)isParagraph {
+ return NO;
}
-- (NSArray *_Nullable)findAllOccurences:(NSRange)range {
- return [OccurenceUtils all:NSUnderlineStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
+- (void)applyStyling:(NSRange)range {
+ [self.input->textView.textStorage addAttribute:NSUnderlineStyleAttributeName
+ value:@(NSUnderlineStyleSingle)
+ range:range];
}
@end
diff --git a/ios/styles/UnorderedListStyle.mm b/ios/styles/UnorderedListStyle.mm
index d2aa15099..2e0a1a103 100644
--- a/ios/styles/UnorderedListStyle.mm
+++ b/ios/styles/UnorderedListStyle.mm
@@ -1,232 +1,81 @@
#import "EnrichedTextInputView.h"
-#import "FontExtension.h"
-#import "OccurenceUtils.h"
-#import "ParagraphsUtils.h"
+#import "RangeUtils.h"
#import "StyleHeaders.h"
#import "TextInsertionUtils.h"
-@implementation UnorderedListStyle {
- EnrichedTextInputView *_input;
-}
+@implementation UnorderedListStyle
-+ (StyleType)getStyleType {
++ (StyleType)getType {
return UnorderedList;
}
-+ (BOOL)isParagraphStyle {
- return YES;
-}
-
-- (CGFloat)getHeadIndent {
- // lists are drawn manually
- // margin before bullet + gap between bullet and paragraph
- return [_input->config unorderedListMarginLeft] +
- [_input->config unorderedListGapWidth];
-}
-
-- (instancetype)initWithInput:(id)input {
- self = [super init];
- _input = (EnrichedTextInputView *)input;
- return self;
-}
-
-- (void)applyStyle:(NSRange)range {
- BOOL isStylePresent = [self detectStyle:range];
- if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
- } else {
- isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
- }
-}
-
-// we assume correct paragraph range is already given
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
- NSTextList *bullet =
- [[NSTextList alloc] initWithMarkerFormat:NSTextListMarkerDisc options:0];
- NSArray *paragraphs =
- [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView
- range:range];
- // if we fill empty lines with zero width spaces, we need to offset later
- // ranges
- NSInteger offset = 0;
- // needed for range adjustments
- NSRange preModificationRange = _input->textView.selectedRange;
-
- // let's not emit some weird selection changes or text/html changes
- _input->blockEmitting = YES;
-
- for (NSValue *value in paragraphs) {
- // take previous offsets into consideration
- NSRange fixedRange = NSMakeRange([value rangeValue].location + offset,
- [value rangeValue].length);
-
- // length 0 with first line, length 1 and newline with some empty lines in
- // the middle
- if (fixedRange.length == 0 ||
- (fixedRange.length == 1 &&
- [[NSCharacterSet newlineCharacterSet]
- characterIsMember:[_input->textView.textStorage.string
- characterAtIndex:fixedRange.location]])) {
- [TextInsertionUtils insertText:@"\u200B"
- at:fixedRange.location
- additionalAttributes:nullptr
- input:_input
- withSelection:NO];
- fixedRange = NSMakeRange(fixedRange.location, fixedRange.length + 1);
- offset += 1;
- }
-
- [_input->textView.textStorage
- enumerateAttribute:NSParagraphStyleAttributeName
- inRange:fixedRange
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- NSMutableParagraphStyle *pStyle =
- [(NSParagraphStyle *)value mutableCopy];
- pStyle.textLists = @[ bullet ];
- pStyle.headIndent = [self getHeadIndent];
- pStyle.firstLineHeadIndent = [self getHeadIndent];
- [_input->textView.textStorage
- addAttribute:NSParagraphStyleAttributeName
- value:pStyle
- range:range];
- }];
- }
-
- // back to emitting
- _input->blockEmitting = NO;
-
- if (preModificationRange.length == 0) {
- // fix selection if only one line was possibly made a list and filled with a
- // space
- _input->textView.selectedRange = preModificationRange;
- } else {
- // in other cases, fix the selection with newly made offsets
- _input->textView.selectedRange = NSMakeRange(
- preModificationRange.location, preModificationRange.length + offset);
- }
-
- // also add typing attributes
- if (withTypingAttr) {
- NSMutableDictionary *typingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- NSMutableParagraphStyle *pStyle =
- [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
- pStyle.textLists = @[ bullet ];
- pStyle.headIndent = [self getHeadIndent];
- pStyle.firstLineHeadIndent = [self getHeadIndent];
- typingAttrs[NSParagraphStyleAttributeName] = pStyle;
- _input->textView.typingAttributes = typingAttrs;
- }
-}
-
-// does pretty much the same as normal addAttributes, just need to get the range
-- (void)addTypingAttributes {
- [self addAttributes:_input->textView.selectedRange withTypingAttr:YES];
+- (NSString *)getValue {
+ return @"EnrichedUnorderedList";
}
-- (void)removeAttributes:(NSRange)range {
- NSArray *paragraphs =
- [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView
- range:range];
-
- [_input->textView.textStorage beginEditing];
-
- for (NSValue *value in paragraphs) {
- NSRange range = [value rangeValue];
- [_input->textView.textStorage
- enumerateAttribute:NSParagraphStyleAttributeName
- inRange:range
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- NSMutableParagraphStyle *pStyle =
- [(NSParagraphStyle *)value mutableCopy];
- pStyle.textLists = @[];
- pStyle.headIndent = 0;
- pStyle.firstLineHeadIndent = 0;
- [_input->textView.textStorage
- addAttribute:NSParagraphStyleAttributeName
- value:pStyle
- range:range];
- }];
- }
-
- [_input->textView.textStorage endEditing];
-
- // also remove typing attributes
- NSMutableDictionary *typingAttrs =
- [_input->textView.typingAttributes mutableCopy];
- NSMutableParagraphStyle *pStyle =
- [typingAttrs[NSParagraphStyleAttributeName] mutableCopy];
- pStyle.textLists = @[];
- pStyle.headIndent = 0;
- pStyle.firstLineHeadIndent = 0;
- typingAttrs[NSParagraphStyleAttributeName] = pStyle;
- _input->textView.typingAttributes = typingAttrs;
+- (BOOL)isParagraph {
+ return YES;
}
-// needed for the sake of style conflicts, needs to do exactly the same as
-// removeAttribtues
-- (void)removeTypingAttributes {
- [self removeAttributes:_input->textView.selectedRange];
+- (BOOL)needsZWS {
+ return YES;
}
-- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
- if ([self detectStyle:_input->textView.selectedRange] && text.length == 0) {
- // backspace while the style is active
-
- NSRange paragraphRange = [_input->textView.textStorage.string
- paragraphRangeForRange:_input->textView.selectedRange];
-
- if (NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) {
- // a backspace on the very first input's line list point
- // it doesn't run textVieDidChange so we need to manually remove
- // attributes
- [self removeAttributes:paragraphRange];
- return YES;
- } else if (range.location == paragraphRange.location - 1) {
- // same case in other lines; here, the removed range location will be
- // exactly 1 less than paragraph range location
- [self removeAttributes:paragraphRange];
- return YES;
- }
- }
- return NO;
+- (void)applyStyling:(NSRange)range {
+ // lists are drawn manually
+ // margin before bullet + gap between bullet and paragraph
+ CGFloat listHeadIndent = [self.input->config unorderedListMarginLeft] +
+ [self.input->config unorderedListGapWidth];
+
+ [self.input->textView.textStorage
+ enumerateAttribute:NSParagraphStyleAttributeName
+ inRange:range
+ options:0
+ usingBlock:^(id _Nullable value, NSRange range,
+ BOOL *_Nonnull stop) {
+ NSMutableParagraphStyle *pStyle =
+ [(NSParagraphStyle *)value mutableCopy];
+ pStyle.headIndent = listHeadIndent;
+ pStyle.firstLineHeadIndent = listHeadIndent;
+ [self.input->textView.textStorage
+ addAttribute:NSParagraphStyleAttributeName
+ value:pStyle
+ range:range];
+ }];
}
- (BOOL)tryHandlingListShorcutInRange:(NSRange)range
replacementText:(NSString *)text {
NSRange paragraphRange =
- [_input->textView.textStorage.string paragraphRangeForRange:range];
+ [self.input->textView.textStorage.string paragraphRangeForRange:range];
// space was added - check if we are both at the paragraph beginning + 1
// character (which we want to be a dash)
if ([text isEqualToString:@" "] &&
range.location - 1 == paragraphRange.location) {
- unichar charBefore = [_input->textView.textStorage.string
+ unichar charBefore = [self.input->textView.textStorage.string
characterAtIndex:range.location - 1];
if (charBefore == '-') {
// we got a match - add a list if possible
- if ([_input handleStyleBlocksAndConflicts:[[self class] getStyleType]
- range:paragraphRange]) {
+ if ([self.input handleStyleBlocksAndConflicts:[[self class] getType]
+ range:paragraphRange]) {
// don't emit during the replacing
- _input->blockEmitting = YES;
+ self.input->blockEmitting = YES;
// remove the dash
[TextInsertionUtils replaceText:@""
at:NSMakeRange(paragraphRange.location, 1)
additionalAttributes:nullptr
- input:_input
+ input:self.input
withSelection:YES];
- _input->blockEmitting = NO;
+ self.input->blockEmitting = NO;
// add attributes on the dashless paragraph
- [self addAttributes:NSMakeRange(paragraphRange.location,
- paragraphRange.length - 1)
- withTypingAttr:YES];
+ [self add:NSMakeRange(paragraphRange.location,
+ paragraphRange.length - 1)
+ withTyping:YES
+ withDirtyRange:YES];
+
return YES;
}
}
@@ -234,47 +83,4 @@ - (BOOL)tryHandlingListShorcutInRange:(NSRange)range
return NO;
}
-- (BOOL)styleCondition:(id _Nullable)value range:(NSRange)range {
- NSParagraphStyle *paragraph = (NSParagraphStyle *)value;
- return paragraph != nullptr && paragraph.textLists.count == 1 &&
- paragraph.textLists.firstObject.markerFormat == NSTextListMarkerDisc;
-}
-
-- (BOOL)detectStyle:(NSRange)range {
- if (range.length >= 1) {
- return [OccurenceUtils detect:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- } else {
- return [OccurenceUtils detect:NSParagraphStyleAttributeName
- withInput:_input
- atIndex:range.location
- checkPrevious:YES
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
- }
-}
-
-- (BOOL)anyOccurence:(NSRange)range {
- return [OccurenceUtils any:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
-}
-
-- (NSArray *_Nullable)findAllOccurences:(NSRange)range {
- return [OccurenceUtils all:NSParagraphStyleAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value range:range];
- }];
-}
-
@end
diff --git a/ios/utils/CheckboxHitTestUtils.mm b/ios/utils/CheckboxHitTestUtils.mm
index e020fca6f..935229876 100644
--- a/ios/utils/CheckboxHitTestUtils.mm
+++ b/ios/utils/CheckboxHitTestUtils.mm
@@ -42,14 +42,13 @@ + (BOOL)isCheckboxGlyph:(NSUInteger)glyphIndex
}
CheckboxListStyle *checkboxListStyle =
- (CheckboxListStyle *)
- input->stylesDict[@([CheckboxListStyle getStyleType])];
+ (CheckboxListStyle *)input->stylesDict[@([CheckboxListStyle getType])];
if (!checkboxListStyle) {
return NO;
}
- return [checkboxListStyle detectStyle:NSMakeRange(charIndex, 0)];
+ return [checkboxListStyle detect:NSMakeRange(charIndex, 0)];
}
// MARK: - Checkbox rect
diff --git a/ios/utils/DotReplacementUtils.h b/ios/utils/DotReplacementUtils.h
new file mode 100644
index 000000000..5e4fc8225
--- /dev/null
+++ b/ios/utils/DotReplacementUtils.h
@@ -0,0 +1,10 @@
+#import
+#pragma once
+
+@interface DotReplacementUtils : NSObject
++ (void)handleDotReplacement:(id)input
+ textStorage:(NSTextStorage *)textStorage
+ editedMask:(NSTextStorageEditActions)editedMask
+ editedRange:(NSRange)editedRange
+ delta:(NSInteger)delta;
+@end
diff --git a/ios/utils/DotReplacementUtils.mm b/ios/utils/DotReplacementUtils.mm
new file mode 100644
index 000000000..3a9abd596
--- /dev/null
+++ b/ios/utils/DotReplacementUtils.mm
@@ -0,0 +1,68 @@
+#import "DotReplacementUtils.h"
+#import "EnrichedTextInputView.h"
+
+@implementation DotReplacementUtils
+
+// This is a fix for iOS replacing a space with a dot when two spaces are
+// quickly inputted That operation doesn't properly extend our custom attributes
+// and we do it here manually
++ (void)handleDotReplacement:(id)input
+ textStorage:(NSTextStorage *)textStorage
+ editedMask:(NSTextStorageEditActions)editedMask
+ editedRange:(NSRange)editedRange
+ delta:(NSInteger)delta {
+ EnrichedTextInputView *typedInput = (EnrichedTextInputView *)input;
+ if (typedInput == nullptr) {
+ return;
+ }
+
+ // Conditions for the dot attributes fix:
+ // - character edition was done
+ // - it edited one character
+ // - new character is a dot
+ // - delta=0, meaning a replacement was done
+ // - there is something before the edited range to get attributes from
+ if ((editedMask & NSTextStorageEditedCharacters) != 0 &&
+ editedRange.length == 1 &&
+ [[textStorage.string substringWithRange:editedRange]
+ isEqualToString:@"."] &&
+ delta == 0 && editedRange.location > 0) {
+ // If all of the above are true, we are sure some dot replacement has been
+ // done So we manually need to apply the preceeding attribtues to the dot
+ NSDictionary *prevAttrs =
+ [textStorage attributesAtIndex:editedRange.location - 1
+ effectiveRange:nullptr];
+ [textStorage addAttributes:prevAttrs range:editedRange];
+ typedInput->dotReplacementRange = [NSValue valueWithRange:editedRange];
+ return;
+ }
+
+ // Space after the dot added by iOS comes in a separate, second callback.
+ // Checking its conditions:
+ // - dotReplacementRange defined
+ // - dotReplacementRange was exactly before the new edited range
+ // - character edition was done
+ // - it edited one character
+ // - edited character is a space
+ // - delta=1, meaning addition was done
+ if (typedInput->dotReplacementRange != nullptr &&
+ [typedInput->dotReplacementRange rangeValue].location + 1 ==
+ editedRange.location &&
+ (editedMask & NSTextStorageEditedCharacters) != 0 &&
+ editedRange.length == 1 &&
+ [[textStorage.string substringWithRange:editedRange]
+ isEqualToString:@" "] &&
+ delta == 1) {
+ // If all of the above are true, we are now sure it was the iOS dot
+ // replacement Only then do we also fix attribtues of the space added
+ // afterwards
+ NSDictionary *prevAttrs =
+ [textStorage attributesAtIndex:editedRange.location - 1
+ effectiveRange:nullptr];
+ [textStorage addAttributes:prevAttrs range:editedRange];
+ }
+ // always reset the replacement range after any processing
+ typedInput->dotReplacementRange = nullptr;
+}
+
+@end
diff --git a/ios/utils/OccurenceUtils.h b/ios/utils/OccurenceUtils.h
index 83cf12a65..8f54f1d1b 100644
--- a/ios/utils/OccurenceUtils.h
+++ b/ios/utils/OccurenceUtils.h
@@ -11,7 +11,7 @@
+ (BOOL)detect:(NSAttributedStringKey _Nonnull)key
withInput:(EnrichedTextInputView *_Nonnull)input
atIndex:(NSUInteger)index
- checkPrevious:(BOOL)check
+ checkPrevious:(BOOL)checkPrev
withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
NSRange range))condition;
+ (BOOL)detectMultiple:(NSArray *_Nonnull)keys
diff --git a/ios/utils/ParagraphAttributesUtils.mm b/ios/utils/ParagraphAttributesUtils.mm
index fee5503f5..09093bfaa 100644
--- a/ios/utils/ParagraphAttributesUtils.mm
+++ b/ios/utils/ParagraphAttributesUtils.mm
@@ -1,6 +1,6 @@
#import "ParagraphAttributesUtils.h"
#import "EnrichedTextInputView.h"
-#import "ParagraphsUtils.h"
+#import "RangeUtils.h"
#import "StyleHeaders.h"
#import "TextInsertionUtils.h"
@@ -14,17 +14,6 @@ + (BOOL)handleBackspaceInRange:(NSRange)range
replacementText:(NSString *)text
input:(id)input {
EnrichedTextInputView *typedInput = (EnrichedTextInputView *)input;
- UnorderedListStyle *ulStyle =
- typedInput->stylesDict[@([UnorderedListStyle getStyleType])];
- OrderedListStyle *olStyle =
- typedInput->stylesDict[@([OrderedListStyle getStyleType])];
- BlockQuoteStyle *bqStyle =
- typedInput->stylesDict[@([BlockQuoteStyle getStyleType])];
- CodeBlockStyle *cbStyle =
- typedInput->stylesDict[@([CodeBlockStyle getStyleType])];
- CheckboxListStyle *cbLStyle =
- typedInput->stylesDict[@([CheckboxListStyle getStyleType])];
-
if (typedInput == nullptr) {
return NO;
}
@@ -39,9 +28,8 @@ + (BOOL)handleBackspaceInRange:(NSRange)range
NSRange paragraphRange =
[typedInput->textView.textStorage.string paragraphRangeForRange:range];
- NSArray *paragraphs =
- [ParagraphsUtils getNonNewlineRangesIn:typedInput->textView
- range:paragraphRange];
+ NSArray *paragraphs = [RangeUtils getNonNewlineRangesIn:typedInput->textView
+ range:paragraphRange];
if (paragraphs.count == 0) {
return NO;
}
@@ -53,41 +41,25 @@ + (BOOL)handleBackspaceInRange:(NSRange)range
if (range.location == nonNewlineRange.location &&
range.length >= nonNewlineRange.length) {
- // for lists, quotes and codeblocks present we do the following:
+ // for styles that need ZWS (lists, quotes, etc.) we do the following:
// - manually do the removing
// - reset typing attribtues so that the previous line styles don't get
// applied
// - reapply the paragraph style that was present so that a zero width space
// appears here
- NSArray *handledStyles = @[ ulStyle, olStyle, bqStyle, cbStyle, cbLStyle ];
- for (id style in handledStyles) {
- if ([style detectStyle:nonNewlineRange]) {
- // For checkbox lists, preserve the current checked state
- if (style == cbLStyle) {
- BOOL isCurrentlyChecked =
- [cbLStyle getCheckboxStateAt:range.location];
- [TextInsertionUtils replaceText:text
- at:range
- additionalAttributes:nullptr
- input:typedInput
- withSelection:YES];
- typedInput->textView.typingAttributes =
- typedInput->defaultTypingAttributes;
- [cbLStyle addAttributesWithCheckedValue:isCurrentlyChecked
- inRange:NSMakeRange(range.location, 0)
- withTypingAttr:YES];
- } else {
- [TextInsertionUtils replaceText:text
- at:range
- additionalAttributes:nullptr
- input:typedInput
- withSelection:YES];
- typedInput->textView.typingAttributes =
- typedInput->defaultTypingAttributes;
- [style addAttributes:NSMakeRange(range.location, 0)
- withTypingAttr:YES];
- }
-
+ for (NSNumber *type in typedInput->stylesDict) {
+ StyleBase *style = typedInput->stylesDict[type];
+ if ([style needsZWS] && [style detect:nonNewlineRange]) {
+ [TextInsertionUtils replaceText:text
+ at:range
+ additionalAttributes:nullptr
+ input:typedInput
+ withSelection:YES];
+ typedInput->textView.typingAttributes =
+ typedInput->defaultTypingAttributes;
+ [style add:NSMakeRange(range.location, 0)
+ withTyping:YES
+ withDirtyRange:YES];
return YES;
}
}
diff --git a/ios/utils/ParagraphsUtils.mm b/ios/utils/ParagraphsUtils.mm
deleted file mode 100644
index 342d7334e..000000000
--- a/ios/utils/ParagraphsUtils.mm
+++ /dev/null
@@ -1,68 +0,0 @@
-#import "ParagraphsUtils.h"
-
-@implementation ParagraphsUtils
-
-+ (NSArray *)getSeparateParagraphsRangesIn:(UITextView *)textView
- range:(NSRange)range {
- // just in case, get full paragraphs range
- NSRange fullRange =
- [textView.textStorage.string paragraphRangeForRange:range];
-
- // we are in an empty paragraph
- if (fullRange.length == 0) {
- return @[ [NSValue valueWithRange:fullRange] ];
- }
-
- NSMutableArray *results = [[NSMutableArray alloc] init];
-
- NSInteger lastStart = fullRange.location;
- for (int i = int(fullRange.location);
- i < fullRange.location + fullRange.length; i++) {
- unichar currentChar = [textView.textStorage.string characterAtIndex:i];
- if ([[NSCharacterSet newlineCharacterSet] characterIsMember:currentChar]) {
- NSRange paragraphRange = [textView.textStorage.string
- paragraphRangeForRange:NSMakeRange(lastStart, i - lastStart)];
- [results addObject:[NSValue valueWithRange:paragraphRange]];
- lastStart = i + 1;
- }
- }
-
- if (lastStart < fullRange.location + fullRange.length) {
- NSRange paragraphRange = [textView.textStorage.string
- paragraphRangeForRange:NSMakeRange(lastStart, fullRange.location +
- fullRange.length -
- lastStart)];
- [results addObject:[NSValue valueWithRange:paragraphRange]];
- }
-
- return results;
-}
-
-+ (NSArray *)getNonNewlineRangesIn:(UITextView *)textView range:(NSRange)range {
- NSMutableArray *nonNewlineRanges = [[NSMutableArray alloc] init];
- int lastRangeLocation = int(range.location);
-
- for (int i = int(range.location); i < range.location + range.length; i++) {
- unichar currentChar = [textView.textStorage.string characterAtIndex:i];
- if ([[NSCharacterSet newlineCharacterSet] characterIsMember:currentChar]) {
- if (i - lastRangeLocation > 0) {
- [nonNewlineRanges
- addObject:[NSValue
- valueWithRange:NSMakeRange(lastRangeLocation,
- i - lastRangeLocation)]];
- }
- lastRangeLocation = i + 1;
- }
- }
- if (lastRangeLocation < range.location + range.length) {
- [nonNewlineRanges
- addObject:[NSValue
- valueWithRange:NSMakeRange(lastRangeLocation,
- range.location + range.length -
- lastRangeLocation)]];
- }
-
- return nonNewlineRanges;
-}
-
-@end
diff --git a/ios/utils/ParagraphsUtils.h b/ios/utils/RangeUtils.h
similarity index 52%
rename from ios/utils/ParagraphsUtils.h
rename to ios/utils/RangeUtils.h
index a6a1cfabf..7d5211d09 100644
--- a/ios/utils/ParagraphsUtils.h
+++ b/ios/utils/RangeUtils.h
@@ -1,8 +1,12 @@
#pragma once
#import
-@interface ParagraphsUtils : NSObject
+@interface RangeUtils : NSObject
+ (NSArray *)getSeparateParagraphsRangesIn:(UITextView *)textView
range:(NSRange)range;
+ (NSArray *)getNonNewlineRangesIn:(UITextView *)textView range:(NSRange)range;
++ (NSArray *)connectAndDedupeRanges:(NSArray *)ranges;
++ (NSArray *)shiftRanges:(NSArray *)ranges
+ withEditedRange:(NSRange)editedRange
+ changeInLength:(NSInteger)delta;
@end
diff --git a/ios/utils/RangeUtils.mm b/ios/utils/RangeUtils.mm
new file mode 100644
index 000000000..1a4d79825
--- /dev/null
+++ b/ios/utils/RangeUtils.mm
@@ -0,0 +1,183 @@
+#import "RangeUtils.h"
+
+@implementation RangeUtils
+
++ (NSArray *)getSeparateParagraphsRangesIn:(UITextView *)textView
+ range:(NSRange)range {
+ // just in case, get full paragraphs range
+ NSRange fullRange =
+ [textView.textStorage.string paragraphRangeForRange:range];
+
+ // we are in an empty paragraph
+ if (fullRange.length == 0) {
+ return @[ [NSValue valueWithRange:fullRange] ];
+ }
+
+ NSMutableArray *results = [[NSMutableArray alloc] init];
+
+ NSInteger lastStart = fullRange.location;
+ for (int i = int(fullRange.location);
+ i < fullRange.location + fullRange.length; i++) {
+ unichar currentChar = [textView.textStorage.string characterAtIndex:i];
+ if ([[NSCharacterSet newlineCharacterSet] characterIsMember:currentChar]) {
+ NSRange paragraphRange = [textView.textStorage.string
+ paragraphRangeForRange:NSMakeRange(lastStart, i - lastStart)];
+ [results addObject:[NSValue valueWithRange:paragraphRange]];
+ lastStart = i + 1;
+ }
+ }
+
+ if (lastStart < fullRange.location + fullRange.length) {
+ NSRange paragraphRange = [textView.textStorage.string
+ paragraphRangeForRange:NSMakeRange(lastStart, fullRange.location +
+ fullRange.length -
+ lastStart)];
+ [results addObject:[NSValue valueWithRange:paragraphRange]];
+ }
+
+ return results;
+}
+
++ (NSArray *)getNonNewlineRangesIn:(UITextView *)textView range:(NSRange)range {
+ NSMutableArray *nonNewlineRanges = [[NSMutableArray alloc] init];
+ int lastRangeLocation = int(range.location);
+
+ for (int i = int(range.location); i < range.location + range.length; i++) {
+ unichar currentChar = [textView.textStorage.string characterAtIndex:i];
+ if ([[NSCharacterSet newlineCharacterSet] characterIsMember:currentChar]) {
+ if (i - lastRangeLocation > 0) {
+ [nonNewlineRanges
+ addObject:[NSValue
+ valueWithRange:NSMakeRange(lastRangeLocation,
+ i - lastRangeLocation)]];
+ }
+ lastRangeLocation = i + 1;
+ }
+ }
+ if (lastRangeLocation < range.location + range.length) {
+ [nonNewlineRanges
+ addObject:[NSValue
+ valueWithRange:NSMakeRange(lastRangeLocation,
+ range.location + range.length -
+ lastRangeLocation)]];
+ }
+
+ return nonNewlineRanges;
+}
+
+// Condenses an array of NSRange to make sure the overlapping ones are connected
+// + sorted based on NSRange.location
++ (NSArray *)connectAndDedupeRanges:(NSArray *)ranges {
+ if (ranges.count == 0) {
+ return @[];
+ }
+
+ // We sort primarily by location. If locations match, shorter length goes
+ // first
+ NSArray *sortedRanges =
+ [ranges sortedArrayUsingComparator:^NSComparisonResult(NSValue *obj1,
+ NSValue *obj2) {
+ NSRange range1 = obj1.rangeValue;
+ NSRange range2 = obj2.rangeValue;
+
+ if (range1.location < range2.location)
+ return NSOrderedAscending;
+ if (range1.location > range2.location)
+ return NSOrderedDescending;
+
+ if (range1.length < range2.length)
+ return NSOrderedAscending;
+ if (range1.length > range2.length)
+ return NSOrderedDescending;
+
+ return NSOrderedSame;
+ }];
+
+ NSMutableArray *mergedRanges = [[NSMutableArray alloc] init];
+
+ // We work by comparing each two ranges.
+ // If we connected some ranges, the newly created one is still compared with
+ // the next ranges from the sorted list.
+ NSRange currentRange = sortedRanges[0].rangeValue;
+
+ for (NSUInteger i = 1; i < sortedRanges.count; i++) {
+ NSRange nextRange = sortedRanges[i].rangeValue;
+
+ // Calculate the end points
+ NSUInteger currentMax = currentRange.location + currentRange.length;
+ NSUInteger nextMax = nextRange.location + nextRange.length;
+
+ // If next range starts before (or exactly when) the current one ends.
+ if (nextRange.location <= currentMax) {
+ // Merge them; the new end is the maximum of the two ends
+ NSUInteger newMax = MAX(currentMax, nextMax);
+ currentRange.length = newMax - currentRange.location;
+ } else {
+ // No overlap; push the current range and start a new one
+ [mergedRanges addObject:[NSValue valueWithRange:currentRange]];
+ currentRange = nextRange;
+ }
+ }
+
+ // Add the final range
+ [mergedRanges addObject:[NSValue valueWithRange:currentRange]];
+
+ return [mergedRanges copy];
+}
+
+// Updates a list of NSRanges based on a text change, so that they are still
+// pointing to the same characters. editedRange is the post-change range of the
+// edited fragment. delta tells what is the length change of the edited
+// fragment. While the ranges outside of the change are being just shifted, the
+// ones intersecting with it are just merging with the change.
++ (NSArray *)shiftRanges:(NSArray *)ranges
+ withEditedRange:(NSRange)editedRange
+ changeInLength:(NSInteger)delta {
+ NSMutableArray *result = [[NSMutableArray alloc] init];
+
+ // Calculate what the changed range was like before being edited
+ NSUInteger oldEditLength = editedRange.length - delta;
+ NSUInteger oldEditEnd = editedRange.location + oldEditLength;
+
+ NSUInteger newEditEnd = editedRange.location + editedRange.length;
+
+ for (NSValue *value in ranges) {
+ NSRange range = [value rangeValue];
+ NSUInteger rangeEnd = range.location + range.length;
+
+ if (rangeEnd <= editedRange.location) {
+ // Range was strictly before the old edit range.
+ // Do nothing.
+ [result addObject:value];
+ } else if (range.location >= oldEditEnd) {
+ // Range was strictly after the old edit range.
+ // Shift it by the delta.
+ [result
+ addObject:[NSValue valueWithRange:NSMakeRange(range.location + delta,
+ range.length)]];
+ } else {
+ // Range overlaps the old edit range in some way.
+ // Our best bet is to merge it with the edit range.
+
+ NSUInteger newStart = MIN(range.location, editedRange.location);
+
+ NSUInteger newEnd;
+ if (rangeEnd <= oldEditEnd) {
+ // The range was inside the editedRange before.
+ // So we use the newer editRange as the end here.
+ newEnd = newEditEnd;
+ } else {
+ // The range sticked outside of the editedRange before.
+ // It is safe to shift its end and use it.
+ newEnd = rangeEnd + delta;
+ }
+
+ NSRange adjustedRange = NSMakeRange(newStart, newEnd - newStart);
+ [result addObject:[NSValue valueWithRange:adjustedRange]];
+ }
+ }
+
+ return result;
+}
+
+@end
diff --git a/ios/utils/ZeroWidthSpaceUtils.mm b/ios/utils/ZeroWidthSpaceUtils.mm
index 4df7d10b5..2ab5769ed 100644
--- a/ios/utils/ZeroWidthSpaceUtils.mm
+++ b/ios/utils/ZeroWidthSpaceUtils.mm
@@ -43,24 +43,16 @@ + (void)removeSpacesIfNeededinInput:(EnrichedTextInputView *)input {
continue;
}
- UnorderedListStyle *ulStyle =
- input->stylesDict[@([UnorderedListStyle getStyleType])];
- OrderedListStyle *olStyle =
- input->stylesDict[@([OrderedListStyle getStyleType])];
- BlockQuoteStyle *bqStyle =
- input->stylesDict[@([BlockQuoteStyle getStyleType])];
- CodeBlockStyle *cbStyle =
- input->stylesDict[@([CodeBlockStyle getStyleType])];
- CheckboxListStyle *cbLStyle =
- input->stylesDict[@([CheckboxListStyle getStyleType])];
-
- // zero width spaces with no lists/blockquotes/codeblocks on them get
- // removed
- if (![ulStyle detectStyle:characterRange] &&
- ![olStyle detectStyle:characterRange] &&
- ![bqStyle detectStyle:characterRange] &&
- ![cbStyle detectStyle:characterRange] &&
- ![cbLStyle detectStyle:characterRange]) {
+ // zero width spaces with no needsZWS style on them get removed
+ BOOL anyZWSStylePresent = NO;
+ for (NSNumber *type in input->stylesDict) {
+ StyleBase *style = input->stylesDict[type];
+ if ([style needsZWS] && [style detect:characterRange]) {
+ anyZWSStylePresent = YES;
+ break;
+ }
+ }
+ if (!anyZWSStylePresent) {
[indexesToBeRemoved addObject:@(characterRange.location)];
}
}
@@ -95,20 +87,29 @@ + (void)removeSpacesIfNeededinInput:(EnrichedTextInputView *)input {
}
}
+// Collects active inline (non-paragraph) meta-attributes from the style
+// dictionary so that ZWS characters carry the same meta-attributes that are
+// currently active in the typing attributes.
++ (NSDictionary *)inlineMetaAttributesForInput:(EnrichedTextInputView *)input {
+ NSMutableDictionary *metaAttrs = [NSMutableDictionary new];
+ for (NSNumber *type in input->stylesDict) {
+ StyleBase *style = input->stylesDict[type];
+ if (![style isParagraph]) {
+ AttributeEntry *entry =
+ [style getEntryIfPresent:input->textView.selectedRange];
+ if (entry) {
+ metaAttrs[entry.key] = entry.value;
+ }
+ }
+ }
+ return metaAttrs.count > 0 ? metaAttrs : nullptr;
+}
+
+ (void)addSpacesIfNeededinInput:(EnrichedTextInputView *)input {
- UnorderedListStyle *ulStyle =
- input->stylesDict[@([UnorderedListStyle getStyleType])];
- OrderedListStyle *olStyle =
- input->stylesDict[@([OrderedListStyle getStyleType])];
- BlockQuoteStyle *bqStyle =
- input->stylesDict[@([BlockQuoteStyle getStyleType])];
- CodeBlockStyle *cbStyle = input->stylesDict[@([CodeBlockStyle getStyleType])];
- CheckboxListStyle *cbLStyle =
- input->stylesDict[@([CheckboxListStyle getStyleType])];
NSMutableArray *indexesToBeInserted = [[NSMutableArray alloc] init];
NSRange preAddSelection = input->textView.selectedRange;
- for (int i = 0; i < input->textView.textStorage.string.length; i++) {
+ for (NSUInteger i = 0; i < input->textView.textStorage.string.length; i++) {
unichar character = [input->textView.textStorage.string characterAtIndex:i];
if ([[NSCharacterSet newlineCharacterSet] characterIsMember:character]) {
@@ -117,11 +118,15 @@ + (void)addSpacesIfNeededinInput:(EnrichedTextInputView *)input {
paragraphRangeForRange:characterRange];
if (paragraphRange.length == 1) {
- if ([ulStyle detectStyle:characterRange] ||
- [olStyle detectStyle:characterRange] ||
- [bqStyle detectStyle:characterRange] ||
- [cbStyle detectStyle:characterRange] ||
- [cbLStyle detectStyle:characterRange]) {
+ BOOL anyZWSStylePresent = NO;
+ for (NSNumber *type in input->stylesDict) {
+ StyleBase *style = input->stylesDict[type];
+ if ([style needsZWS] && [style detect:characterRange]) {
+ anyZWSStylePresent = YES;
+ break;
+ }
+ }
+ if (anyZWSStylePresent) {
// we have an empty list or quote item with no space: add it!
[indexesToBeInserted addObject:@(paragraphRange.location)];
}
@@ -129,6 +134,8 @@ + (void)addSpacesIfNeededinInput:(EnrichedTextInputView *)input {
}
}
+ NSDictionary *metaAttrs = [self inlineMetaAttributesForInput:input];
+
// do the replacing
NSInteger offset = 0;
NSInteger postAddLocationOffset = 0;
@@ -137,7 +144,7 @@ + (void)addSpacesIfNeededinInput:(EnrichedTextInputView *)input {
NSRange replaceRange = NSMakeRange([index integerValue] + offset, 1);
[TextInsertionUtils replaceText:@"\u200B\n"
at:replaceRange
- additionalAttributes:nullptr
+ additionalAttributes:metaAttrs
input:input
withSelection:NO];
offset += 1;
@@ -154,15 +161,22 @@ + (void)addSpacesIfNeededinInput:(EnrichedTextInputView *)input {
NSRange lastRange = NSMakeRange(input->textView.textStorage.string.length, 0);
NSRange lastParagraphRange =
[input->textView.textStorage.string paragraphRangeForRange:lastRange];
- if (lastParagraphRange.length == 0 &&
- ([ulStyle detectStyle:lastRange] || [olStyle detectStyle:lastRange] ||
- [bqStyle detectStyle:lastRange] || [cbStyle detectStyle:lastRange] ||
- [cbLStyle detectStyle:lastRange])) {
- [TextInsertionUtils insertText:@"\u200B"
- at:lastRange.location
- additionalAttributes:nullptr
- input:input
- withSelection:NO];
+ if (lastParagraphRange.length == 0) {
+ BOOL anyZWSStylePresent = NO;
+ for (NSNumber *type in input->stylesDict) {
+ StyleBase *style = input->stylesDict[type];
+ if ([style needsZWS] && [style detect:lastRange]) {
+ anyZWSStylePresent = YES;
+ break;
+ }
+ }
+ if (anyZWSStylePresent) {
+ [TextInsertionUtils insertText:@"\u200B"
+ at:lastRange.location
+ additionalAttributes:metaAttrs
+ input:input
+ withSelection:NO];
+ }
}
// fix the selection if needed
@@ -176,7 +190,7 @@ + (void)addSpacesIfNeededinInput:(EnrichedTextInputView *)input {
+ (BOOL)handleBackspaceInRange:(NSRange)range
replacementText:(NSString *)text
input:(id)input {
- if (range.length != 1 || ![text isEqualToString:@""]) {
+ if (![text isEqualToString:@""]) {
return NO;
}
EnrichedTextInputView *typedInput = (EnrichedTextInputView *)input;
@@ -184,6 +198,26 @@ + (BOOL)handleBackspaceInRange:(NSRange)range
return NO;
}
+ // Backspace at the very beginning of the input ({0, 0}).
+ // Nothing to delete, but if the first paragraph has a needsZWS style,
+ // remove it.
+ if (range.length == 0 && range.location == 0) {
+ NSRange firstParagraphRange = [typedInput->textView.textStorage.string
+ paragraphRangeForRange:NSMakeRange(0, 0)];
+ for (NSNumber *type in typedInput->stylesDict) {
+ StyleBase *style = typedInput->stylesDict[type];
+ if ([style needsZWS] && [style detect:firstParagraphRange]) {
+ [style remove:firstParagraphRange withDirtyRange:YES];
+ return YES;
+ }
+ }
+ return NO;
+ }
+
+ if (range.length != 1) {
+ return NO;
+ }
+
unichar character =
[typedInput->textView.textStorage.string characterAtIndex:range.location];
// zero-width space got backspaced
@@ -207,52 +241,44 @@ + (BOOL)handleBackspaceInRange:(NSRange)range
styleRemovalRange = NSMakeRange(paragraphRange.location, 1);
}
- // and then remove associated styling
-
- UnorderedListStyle *ulStyle =
- typedInput->stylesDict[@([UnorderedListStyle getStyleType])];
- OrderedListStyle *olStyle =
- typedInput->stylesDict[@([OrderedListStyle getStyleType])];
- BlockQuoteStyle *bqStyle =
- typedInput->stylesDict[@([BlockQuoteStyle getStyleType])];
- CodeBlockStyle *cbStyle =
- typedInput->stylesDict[@([CodeBlockStyle getStyleType])];
- CheckboxListStyle *cbLStyle =
- typedInput->stylesDict[@([CheckboxListStyle getStyleType])];
-
- if ([cbStyle detectStyle:removalRange]) {
- // code blocks are being handled differently; we want to remove previous
- // newline if there is a one
- if (range.location > 0) {
- removalRange =
- NSMakeRange(removalRange.location - 1, removalRange.length + 1);
- }
- [TextInsertionUtils replaceText:@""
- at:removalRange
- additionalAttributes:nullptr
- input:typedInput
- withSelection:YES];
- return YES;
- }
-
+ // remove the ZWS (keep the newline if present)
[TextInsertionUtils replaceText:@""
at:removalRange
additionalAttributes:nullptr
input:typedInput
withSelection:YES];
- if ([ulStyle detectStyle:styleRemovalRange]) {
- [ulStyle removeAttributes:styleRemovalRange];
- } else if ([olStyle detectStyle:styleRemovalRange]) {
- [olStyle removeAttributes:styleRemovalRange];
- } else if ([bqStyle detectStyle:styleRemovalRange]) {
- [bqStyle removeAttributes:styleRemovalRange];
- } else if ([cbLStyle detectStyle:styleRemovalRange]) {
- [cbLStyle removeAttributes:styleRemovalRange];
+ // and then remove associated styling
+ for (NSNumber *type in typedInput->stylesDict) {
+ StyleBase *style = typedInput->stylesDict[type];
+ if ([style needsZWS] && [style detect:styleRemovalRange]) {
+ [style remove:styleRemovalRange withDirtyRange:YES];
+ break;
+ }
}
return YES;
}
+
+ // Backspace at the start of a paragraph that has a ZWS-needing style.
+ // The character being deleted is the newline at the end of the previous
+ // paragraph. Instead of letting iOS merge the two lines, just remove the
+ // style from the current paragraph.
+ if ([[NSCharacterSet newlineCharacterSet] characterIsMember:character]) {
+ NSUInteger nextParaStart = NSMaxRange(range);
+ if (nextParaStart < typedInput->textView.textStorage.string.length) {
+ NSRange nextParagraphRange = [typedInput->textView.textStorage.string
+ paragraphRangeForRange:NSMakeRange(nextParaStart, 0)];
+ for (NSNumber *type in typedInput->stylesDict) {
+ StyleBase *style = typedInput->stylesDict[type];
+ if ([style needsZWS] && [style detect:nextParagraphRange]) {
+ [style remove:nextParagraphRange withDirtyRange:YES];
+ return YES;
+ }
+ }
+ }
+ }
+
return NO;
}